dsh-any-agent 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -1
- package/README.md +47 -6
- package/README.zh.md +48 -7
- package/SECURITY.md +46 -0
- package/USAGE-NOTES.en.md +6 -2
- package/USAGE-NOTES.md +10 -2
- package/bin/dsh-any-agent.mjs +2 -1
- package/docs/en/architecture.md +12 -0
- package/docs/en/limits.md +6 -0
- package/docs/en/live-view-design.md +177 -0
- package/docs/en/reference.md +1 -0
- package/docs/en/release.md +21 -14
- package/docs/en/tests.md +6 -5
- package/docs/en/usage.md +2 -1
- package/docs/zh/architecture.md +12 -0
- package/docs/zh/limits.md +6 -0
- package/docs/zh/live-view-design.md +11 -6
- package/docs/zh/reference.md +1 -0
- package/docs/zh/release.md +20 -11
- package/docs/zh/tests.md +6 -5
- package/docs/zh/usage.md +2 -1
- package/package.json +6 -5
- package/src/doctor.mjs +63 -2
- package/src/live-cli.mjs +52 -15
- package/src/live-dashboard.html +191 -79
- package/src/live-view.mjs +111 -24
- package/DESIGN.md +0 -484
- package/docs/brand/README.md +0 -58
- package/docs/brand/header.jpg +0 -0
- package/scripts/check-release.mjs +0 -73
- package/scripts/commit.mjs +0 -122
- package/scripts/detect-dsh.mjs +0 -55
- package/scripts/lint.mjs +0 -595
- package/scripts/sync-test-counts.mjs +0 -699
package/src/live-view.mjs
CHANGED
|
@@ -612,16 +612,101 @@ export function clipLine(s, cols) {
|
|
|
612
612
|
return out + '…\x1b[0m';
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
+
// ── 界面语言 ────────────────────────────────────────────────
|
|
616
|
+
|
|
617
|
+
/** watch / dashboard 命令行界面的两份文案。函数式条目负责插值。 */
|
|
618
|
+
export const TEXT = {
|
|
619
|
+
zh: {
|
|
620
|
+
header: (c, r) => `DSH live · ${c} 会话 · ${r} 运行中`,
|
|
621
|
+
queued: (n) => `${n} 排队`,
|
|
622
|
+
pending: (n) => `${n} 待审批`,
|
|
623
|
+
escalations: (n) => `⚠ ${n} 次提权`,
|
|
624
|
+
tokens: (i, o) => `token 入 ${i} / 出 ${o}`,
|
|
625
|
+
unknownClient: '未知客户端',
|
|
626
|
+
exited: ' (已退出)',
|
|
627
|
+
running: '运行中',
|
|
628
|
+
queuedState: '排队',
|
|
629
|
+
closed: '已释放',
|
|
630
|
+
idle: '空闲',
|
|
631
|
+
total: (i, o) => ` · 累计 ${i}/${o}`,
|
|
632
|
+
context: (p) => ` · 上下文 ${p}%`,
|
|
633
|
+
untitled: '(无标题)',
|
|
634
|
+
tools: (n) => `${n} 工具`,
|
|
635
|
+
runEscalations: (n) => `⚠${n} 提权`,
|
|
636
|
+
earlier: (n) => `…前面还有 ${n} 条`,
|
|
637
|
+
noMatch: (q) => `没有匹配 ${q} 的会话。`,
|
|
638
|
+
empty: '还没有任何事件。派一次活(dsh_send)之后这里就会出现。',
|
|
639
|
+
approval: '审批',
|
|
640
|
+
waiting: ' → 等待裁决',
|
|
641
|
+
interject: '插话',
|
|
642
|
+
interrupt: '■ 中断',
|
|
643
|
+
hiddenRows: (n) => `…还有 ${n} 行没显示(给会话 id 前缀、或用 --runs / --items 收窄)`,
|
|
644
|
+
footer: (time, dir) => `更新于 ${time} · live 目录: ${dir} · Ctrl+C 退出`,
|
|
645
|
+
},
|
|
646
|
+
en: {
|
|
647
|
+
header: (c, r) => `DSH live · ${c} conversation${c === 1 ? '' : 's'} · ${r} running`,
|
|
648
|
+
queued: (n) => `${n} queued`,
|
|
649
|
+
pending: (n) => `${n} awaiting approval`,
|
|
650
|
+
escalations: (n) => `⚠ ${n} escalation${n === 1 ? '' : 's'}`,
|
|
651
|
+
tokens: (i, o) => `tokens in ${i} / out ${o}`,
|
|
652
|
+
unknownClient: 'unknown client',
|
|
653
|
+
exited: ' (exited)',
|
|
654
|
+
running: 'running',
|
|
655
|
+
queuedState: 'queued',
|
|
656
|
+
closed: 'released',
|
|
657
|
+
idle: 'idle',
|
|
658
|
+
total: (i, o) => ` · total ${i}/${o}`,
|
|
659
|
+
context: (p) => ` · context ${p}%`,
|
|
660
|
+
untitled: '(untitled)',
|
|
661
|
+
tools: (n) => `${n} tool${n === 1 ? '' : 's'}`,
|
|
662
|
+
runEscalations: (n) => `⚠${n} escalation${n === 1 ? '' : 's'}`,
|
|
663
|
+
earlier: (n) => `…${n} earlier`,
|
|
664
|
+
noMatch: (q) => `No conversation matches ${q}.`,
|
|
665
|
+
empty: 'No events yet. Dispatch something (dsh_send) and it shows up here.',
|
|
666
|
+
approval: 'approval',
|
|
667
|
+
waiting: ' → awaiting decision',
|
|
668
|
+
interject: 'interject',
|
|
669
|
+
interrupt: '■ interrupted',
|
|
670
|
+
hiddenRows: (n) => `…${n} more line${n === 1 ? '' : 's'} hidden (pass a conversation id prefix, or narrow with --runs / --items)`,
|
|
671
|
+
footer: (time, dir) => `updated ${time} · live dir: ${dir} · Ctrl+C to quit`,
|
|
672
|
+
},
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* 界面语言:显式 `zh` / `en` 优先;否则看 DSH_MCP_LANG,再看 LC_ALL / LC_MESSAGES / LANG,
|
|
677
|
+
* 最后看系统区域设置(Windows 上通常没有 LANG)。以 zh 开头 → 中文,其余 → 英文。
|
|
678
|
+
*/
|
|
679
|
+
export function detectLang(explicit = null, env = process.env, locale = null) {
|
|
680
|
+
const pick = (v) => (typeof v === 'string' && v.trim() ? v.trim().toLowerCase() : null);
|
|
681
|
+
const norm = (v) => (v.startsWith('zh') ? 'zh' : 'en');
|
|
682
|
+
const direct = pick(explicit) ?? pick(env.DSH_MCP_LANG);
|
|
683
|
+
if (direct === 'zh' || direct === 'en') return direct;
|
|
684
|
+
if (direct) return norm(direct);
|
|
685
|
+
const fromEnv = pick(env.LC_ALL) ?? pick(env.LC_MESSAGES) ?? pick(env.LANG);
|
|
686
|
+
if (fromEnv && fromEnv !== 'c' && fromEnv !== 'posix') return norm(fromEnv);
|
|
687
|
+
let sys = locale;
|
|
688
|
+
if (!sys) {
|
|
689
|
+
try {
|
|
690
|
+
sys = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
691
|
+
} catch {
|
|
692
|
+
sys = 'en';
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return norm(String(sys).toLowerCase());
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const T = (lang) => TEXT[lang] ?? TEXT.zh;
|
|
699
|
+
|
|
615
700
|
/**
|
|
616
701
|
* 把一帧文本排成"适合这个终端"的行:截宽、截高(超出的行数在最后一行说明)。
|
|
617
702
|
* 纯函数,方便测试。
|
|
618
703
|
*/
|
|
619
|
-
export function layoutFrame(text, { cols = 120, rows = 40 } = {}) {
|
|
704
|
+
export function layoutFrame(text, { cols = 120, rows = 40, lang = 'zh' } = {}) {
|
|
620
705
|
let lines = String(text).split('\n').map((l) => clipLine(l, cols));
|
|
621
706
|
if (rows > 2 && lines.length > rows) {
|
|
622
707
|
const hidden = lines.length - (rows - 1);
|
|
623
708
|
lines = lines.slice(0, rows - 1);
|
|
624
|
-
lines.push(clipLine(`\x1b[2m
|
|
709
|
+
lines.push(clipLine(`\x1b[2m${T(lang).hiddenRows(hidden)}\x1b[0m`, cols));
|
|
625
710
|
}
|
|
626
711
|
return lines;
|
|
627
712
|
}
|
|
@@ -677,8 +762,10 @@ function clip(s, n) {
|
|
|
677
762
|
* @param {number} [opts.runs] 每个会话显示最近几轮(默认 3)
|
|
678
763
|
* @param {number} [opts.items] 每轮显示最后几条工具/审批(默认 12)
|
|
679
764
|
* @param {boolean} [opts.color] ANSI 颜色
|
|
765
|
+
* @param {'zh'|'en'} [opts.lang] 界面语言(默认中文)
|
|
680
766
|
*/
|
|
681
|
-
export function renderText(view, { conv = null, runs = 3, items = 12, color = false } = {}) {
|
|
767
|
+
export function renderText(view, { conv = null, runs = 3, items = 12, color = false, lang = 'zh' } = {}) {
|
|
768
|
+
const L = T(lang);
|
|
682
769
|
const c = (code, s) => (color ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
683
770
|
const dim = (s) => c('2', s);
|
|
684
771
|
const warn = (s) => c('33', s);
|
|
@@ -687,27 +774,27 @@ export function renderText(view, { conv = null, runs = 3, items = 12, color = fa
|
|
|
687
774
|
const out = [];
|
|
688
775
|
const t = view.totals;
|
|
689
776
|
out.push(
|
|
690
|
-
|
|
691
|
-
(t.queued ? ` · ${t.queued}
|
|
692
|
-
(t.pending_approvals ? ` · ${warn(
|
|
693
|
-
(t.escalations ? ` · ${warn(
|
|
694
|
-
(t.tokens ? ` ·
|
|
777
|
+
L.header(t.conversations, t.running) +
|
|
778
|
+
(t.queued ? ` · ${L.queued(t.queued)}` : '') +
|
|
779
|
+
(t.pending_approvals ? ` · ${warn(L.pending(t.pending_approvals))}` : '') +
|
|
780
|
+
(t.escalations ? ` · ${warn(L.escalations(t.escalations))}` : '') +
|
|
781
|
+
(t.tokens ? ` · ${L.tokens(fmtTokens(tokenInputTotal(t.tokens)), fmtTokens(t.tokens.output))}` : ''),
|
|
695
782
|
);
|
|
696
783
|
let shown = 0;
|
|
697
784
|
for (const g of view.groups) {
|
|
698
785
|
const convs = conv ? g.conversations.filter((x) => x.id.startsWith(conv)) : g.conversations;
|
|
699
786
|
if (!convs.length) continue;
|
|
700
787
|
out.push('');
|
|
701
|
-
out.push(`${g.client ??
|
|
788
|
+
out.push(`${g.client ?? L.unknownClient} ${dim(`pid ${g.pid ?? '?'}${g.alive ? '' : L.exited}`)}`);
|
|
702
789
|
convs.forEach((cv, ci) => {
|
|
703
790
|
shown++;
|
|
704
791
|
const lastConv = ci === convs.length - 1;
|
|
705
792
|
const p1 = lastConv ? '└─ ' : '├─ ';
|
|
706
793
|
const p1c = lastConv ? ' ' : '│ ';
|
|
707
|
-
const stateTxt = cv.state === 'running' ? good(
|
|
708
|
-
const ctx = cv.context && cv.context.window ?
|
|
709
|
-
const tok = cv.tokens ?
|
|
710
|
-
out.push(`${p1}${shortId(cv.id)} ${clip(cv.title ??
|
|
794
|
+
const stateTxt = cv.state === 'running' ? good(L.running) : cv.state === 'queued' ? warn(L.queuedState) : dim(cv.state === 'closed' ? L.closed : L.idle);
|
|
795
|
+
const ctx = cv.context && cv.context.window ? L.context(Math.round((cv.context.used / cv.context.window) * 100)) : '';
|
|
796
|
+
const tok = cv.tokens ? L.total(fmtTokens(tokenInputTotal(cv.tokens)), fmtTokens(cv.tokens.output)) : '';
|
|
797
|
+
out.push(`${p1}${shortId(cv.id)} ${clip(cv.title ?? L.untitled, 40)} ${stateTxt}${dim(tok + ctx)} ${dim(`${cv.permission ?? ''} · ${clip(cv.cwd ?? '', 50)}`)}`);
|
|
711
798
|
const rs = cv.runs.slice(-runs);
|
|
712
799
|
rs.forEach((r, ri) => {
|
|
713
800
|
const lastRun = ri === rs.length - 1;
|
|
@@ -719,20 +806,20 @@ export function renderText(view, { conv = null, runs = 3, items = 12, color = fa
|
|
|
719
806
|
r.via ? `[${r.via}]` : null,
|
|
720
807
|
r.status,
|
|
721
808
|
r.duration_ms != null ? fmtMs(r.duration_ms) : null,
|
|
722
|
-
r.tool_count != null ?
|
|
809
|
+
r.tool_count != null ? L.tools(r.tool_count) : null,
|
|
723
810
|
r.tokens ? `${fmtTokens(tokenInputTotal(r.tokens))}↑ ${fmtTokens(r.tokens.output)}↓ tok` : null,
|
|
724
|
-
r.escalations ? warn(
|
|
725
|
-
r.pending_approvals ? warn(
|
|
811
|
+
r.escalations ? warn(L.runEscalations(r.escalations)) : null,
|
|
812
|
+
r.pending_approvals ? warn(L.pending(r.pending_approvals)) : null,
|
|
726
813
|
].filter(Boolean);
|
|
727
814
|
out.push(`${p2}${markC} ${r.run} ${meta.join(' · ')}${r.prompt_preview ? ` ${dim(`“${clip(r.prompt_preview, 60)}”`)}` : ''}`);
|
|
728
815
|
if (r.error) out.push(`${p2c} ${bad(clip(r.error, 100))}`);
|
|
729
816
|
const its = r.items.slice(-items);
|
|
730
|
-
if (r.items.length > its.length) out.push(`${p2c} ${dim(
|
|
731
|
-
for (const it of its) out.push(p2c + ' ' + renderItem(it, { warn, bad, good, dim }, cv.cwd));
|
|
817
|
+
if (r.items.length > its.length) out.push(`${p2c} ${dim(L.earlier(r.items.length - its.length))}`);
|
|
818
|
+
for (const it of its) out.push(p2c + ' ' + renderItem(it, { warn, bad, good, dim }, cv.cwd, L));
|
|
732
819
|
});
|
|
733
820
|
});
|
|
734
821
|
}
|
|
735
|
-
if (!shown) out.push('', dim(conv ?
|
|
822
|
+
if (!shown) out.push('', dim(conv ? L.noMatch(conv) : L.empty));
|
|
736
823
|
return out.join('\n');
|
|
737
824
|
}
|
|
738
825
|
|
|
@@ -745,18 +832,18 @@ export function relToCwd(text, cwd) {
|
|
|
745
832
|
return out.split(root).join('.');
|
|
746
833
|
}
|
|
747
834
|
|
|
748
|
-
function renderItem(it, { warn, bad, good, dim }, cwd) {
|
|
835
|
+
function renderItem(it, { warn, bad, good, dim }, cwd, L = TEXT.zh) {
|
|
749
836
|
if (it.kind === 'tool') {
|
|
750
837
|
const st = it.status === 'completed' ? good('✓') : it.status === 'failed' ? bad('✗') : it.status === 'unfinished' ? dim('·') : warn('▶');
|
|
751
838
|
const what = relToCwd(it.input_preview ?? (it.title !== it.name ? it.title : '') ?? '', cwd);
|
|
752
839
|
return `${st} ${it.name ?? it.title ?? it.id ?? 'tool'}${what ? ` ${dim(clip(what, 70))}` : ''}${it.elapsed_ms != null ? ` ${dim(fmtMs(it.elapsed_ms))}` : ''}`;
|
|
753
840
|
}
|
|
754
841
|
if (it.kind === 'approval') {
|
|
755
|
-
const decided = it.decided ? ` → ${it.decided.decision ?? it.decided.effective ?? ''}${it.decided.by ? `(${it.decided.by})` : ''}` : it.mode === 'ask' ? warn(
|
|
842
|
+
const decided = it.decided ? ` → ${it.decided.decision ?? it.decided.effective ?? ''}${it.decided.by ? `(${it.decided.by})` : ''}` : it.mode === 'ask' ? warn(L.waiting) : '';
|
|
756
843
|
const esc = it.escalation ? warn(` ⚠ ${it.escalation}`) : '';
|
|
757
|
-
return `${warn('◆')}
|
|
844
|
+
return `${warn('◆')} ${L.approval} ${it.mode ?? ''}${esc}${decided}${it.command_preview ? ` ${dim(clip(relToCwd(it.command_preview, cwd), 60))}` : ''}`;
|
|
758
845
|
}
|
|
759
|
-
if (it.kind === 'interject') return `${warn('↪')}
|
|
760
|
-
if (it.kind === 'interrupt') return bad(
|
|
846
|
+
if (it.kind === 'interject') return `${warn('↪')} ${L.interject}(${it.mode ?? ''})${it.text_preview ? ` ${dim(clip(it.text_preview, 60))}` : ''}`;
|
|
847
|
+
if (it.kind === 'interrupt') return bad(L.interrupt);
|
|
761
848
|
return dim(it.kind);
|
|
762
849
|
}
|