@migor/cli 0.9.2 → 0.9.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 +122 -1
- package/dist/AGENTS.md +2 -2
- package/dist/dev-child.js +84 -0
- package/dist/dev-logic.js +46 -0
- package/dist/dev-machine.js +489 -0
- package/dist/dev-runner.js +8 -1
- package/dist/dev-watch.js +223 -0
- package/dist/dev.js +377 -626
- package/dist/inspector-page.html +94 -10
- package/dist/inspector-routes.js +530 -0
- package/dist/inspector.js +44 -421
- package/dist/markdown.js +20 -1
- package/dist/native-pick.js +168 -0
- package/dist/panel-logic.js +32 -0
- package/dist/templates/package.json +2 -2
- package/dist/templates/src/app.ts +3 -0
- package/dist/templates/src/dev.config.ts +5 -0
- package/dist/templates/src/registry.ts +5 -0
- package/dist/templates/src/session-store.ts +11 -2
- package/dist/templates/src/tools/read-file/index.ts +50 -3
- package/package.json +1 -1
package/dist/inspector-page.html
CHANGED
|
@@ -283,6 +283,9 @@
|
|
|
283
283
|
</details>
|
|
284
284
|
<input id="workdir" class="ctl" type="text" spellcheck="false" placeholder="工作目录(绝对路径)" />
|
|
285
285
|
<button id="browse" class="ctl mini" type="button">浏览…</button>
|
|
286
|
+
<!-- 系统选择…:浏览器弹不出系统目录对话框(拿不到绝对路径),由 dev 进程代弹
|
|
287
|
+
(POST /api/fs/pick)。与「浏览…」(服务端列目录)并列,互为兜底。 -->
|
|
288
|
+
<button id="pick" class="ctl mini" type="button" title="弹出系统目录选择框(在你的桌面上,不在浏览器里)">系统选择…</button>
|
|
286
289
|
<label class="mt ctl" title="多轮的初始值 = 所选能力声明的 OR;可在这里覆盖">
|
|
287
290
|
<input type="checkbox" id="mt" /> <span id="mt-label">单轮</span>
|
|
288
291
|
</label>
|
|
@@ -318,8 +321,11 @@
|
|
|
318
321
|
applyTraceEvent,
|
|
319
322
|
browseTarget,
|
|
320
323
|
capabilityBadge,
|
|
324
|
+
chatViewVisible,
|
|
321
325
|
collapseDecision,
|
|
322
326
|
emptyTraceAccumulator,
|
|
327
|
+
filterSelected,
|
|
328
|
+
formatToolSources,
|
|
323
329
|
historyStep,
|
|
324
330
|
historyText,
|
|
325
331
|
mergeConversation,
|
|
@@ -333,6 +339,7 @@
|
|
|
333
339
|
runDoneNotice,
|
|
334
340
|
runIsFailure,
|
|
335
341
|
shortenPath,
|
|
342
|
+
turnKey,
|
|
336
343
|
} from './panel-logic.js';
|
|
337
344
|
|
|
338
345
|
const $ = (id) => document.getElementById(id);
|
|
@@ -438,6 +445,9 @@
|
|
|
438
445
|
|
|
439
446
|
// 能力多选(默认全选)。全选时 badge 显示 N/N —— 一眼看出这次是全量还是收窄
|
|
440
447
|
const all = dev.capabilities;
|
|
448
|
+
// runner 重启后菜单可能变了:先丢掉已失效的 token(不丢的话徽标会说谎成「4/3」,
|
|
449
|
+
// 分子里混着菜单上已不存在的名字),再兜空集回落全选
|
|
450
|
+
state.selected = filterSelected(state.selected, all);
|
|
441
451
|
if (state.selected.length === 0 && all.length > 0) state.selected = [...all];
|
|
442
452
|
capsSumEl.textContent = `能力 ${capabilityBadge(state.selected, all)} ▾`;
|
|
443
453
|
capsPopEl.replaceChildren();
|
|
@@ -551,7 +561,7 @@
|
|
|
551
561
|
if (r.note) {
|
|
552
562
|
const r3 = document.createElement('span');
|
|
553
563
|
r3.className = 'r3';
|
|
554
|
-
const caps = r.note.toolSources
|
|
564
|
+
const caps = formatToolSources(r.note.toolSources);
|
|
555
565
|
r3.textContent = `${short(r.note.workdir)} · ${caps}${r.note.multiTurn ? ' · 多轮' : ''}`;
|
|
556
566
|
b.appendChild(r3);
|
|
557
567
|
}
|
|
@@ -565,7 +575,9 @@
|
|
|
565
575
|
/**
|
|
566
576
|
* 展开态**活在渲染之外**(与 trace-view 的 `expanded` 同一个理由):面板每次全量重画,
|
|
567
577
|
* 状态留在 DOM 里就会被下一次重画合上 —— 而「刚点开的那段自己收起来了」是最烦人的那种 bug。
|
|
568
|
-
* 键要**跨重画稳定**:回复用 traceId
|
|
578
|
+
* 键要**跨重画稳定**:回复用 traceId、对话轮用 `sessionId + 下标`
|
|
579
|
+
* (`turnKey` —— 只追加的会话里下标稳定,而 sessionId 防「清空对话」后
|
|
580
|
+
* 旧下标的展开态套到新对话上)。
|
|
569
581
|
*/
|
|
570
582
|
const expandedMsgs = new Set();
|
|
571
583
|
|
|
@@ -683,7 +695,8 @@
|
|
|
683
695
|
const def = multiTurnDefault(state.selected, dev ? dev.multiTurn : []);
|
|
684
696
|
const value = state.multiTurn ?? def.value;
|
|
685
697
|
// 三条都要满足才显示:多轮开着、会话文件读得到、dev 环在场 —— 不做空壳
|
|
686
|
-
|
|
698
|
+
// (判定是纯逻辑 chatViewVisible,这里只做取值 → 调它 → 写回)
|
|
699
|
+
const show = chatViewVisible(dev, value, state.session);
|
|
687
700
|
tabsEl.hidden = !show;
|
|
688
701
|
// 清空对话条与对话视图同生共死(它清的就是这份对话)——
|
|
689
702
|
// **还要跟页签走**:它长在对话视图上,切回 run 列表时不该留在那儿
|
|
@@ -704,7 +717,8 @@
|
|
|
704
717
|
d.textContent = '会话文件还是空的。跑一次多轮 run 就有了。';
|
|
705
718
|
chatEl.appendChild(d);
|
|
706
719
|
}
|
|
707
|
-
//
|
|
720
|
+
// 下标进折叠态的键时必须混进 sessionId(turnKey):「清空对话」换 id 后下标
|
|
721
|
+
// 从 0 重来,不带 id 会把旧会话的展开态套到新对话的第 0/1/… 轮上
|
|
708
722
|
let ti = -1;
|
|
709
723
|
for (const t of turns) {
|
|
710
724
|
ti++;
|
|
@@ -726,7 +740,7 @@
|
|
|
726
740
|
// 助手轮是**模型正文** ⇒ Markdown 渲染 + 长了折叠。
|
|
727
741
|
// 用户轮保持字面(`u.textContent = t.user` 上面那两行没动)—— 那是「我打的字」,
|
|
728
742
|
// 把它按 Markdown 重排会让面板看起来像改过我的输入。
|
|
729
|
-
renderMessage(a, t.assistant,
|
|
743
|
+
renderMessage(a, t.assistant, turnKey(state.sessionId, ti));
|
|
730
744
|
box.append(who2, a);
|
|
731
745
|
} else if (t.failed) {
|
|
732
746
|
// 失败轮**标失败**,不假装那轮没发生(它没回写会话文件,靠 run 列表 join 出来)
|
|
@@ -804,11 +818,21 @@
|
|
|
804
818
|
|
|
805
819
|
// ---------- 拉数据 ----------
|
|
806
820
|
async function refreshDev() {
|
|
821
|
+
// ⚠️ 与 refresh() 同一口径:必须查 res.ok。403(token 失效 / cookie 过期)时
|
|
822
|
+
// 旧实现静默把 dev 置空 ⇒ 输入条无声消失;服务端 403 的 error 文案本来就写清了
|
|
823
|
+
// 解决办法,把它亮出来,别让用户对着「输入条怎么没了」干瞪眼。
|
|
807
824
|
try {
|
|
808
825
|
const res = await fetch('/api/dev');
|
|
809
|
-
|
|
810
|
-
|
|
826
|
+
if (!res.ok) {
|
|
827
|
+
const body = await res.json().catch(() => ({}));
|
|
828
|
+
state.dev = null;
|
|
829
|
+
setNotice(`读 dev 状态失败:${body.error || res.status}(输入条因此先收起来了)`, true);
|
|
830
|
+
} else {
|
|
831
|
+
state.dev = await res.json();
|
|
832
|
+
}
|
|
833
|
+
} catch (e) {
|
|
811
834
|
state.dev = null;
|
|
835
|
+
setNotice(`读 dev 状态失败:${e.message}(输入条因此先收起来了)`, true);
|
|
812
836
|
}
|
|
813
837
|
// 会话 id 由 dev 环持有(清空对话会换它)—— 面板不自己造,只跟着走
|
|
814
838
|
state.sessionId = state.dev?.sessionId ?? '';
|
|
@@ -822,12 +846,21 @@
|
|
|
822
846
|
|
|
823
847
|
async function refreshSession() {
|
|
824
848
|
if (!state.dev?.available) return;
|
|
849
|
+
// ⚠️ 与 refresh() 同一口径:失败必须亮出来。旧实现静默把 session 置空 ⇒
|
|
850
|
+
// 对话无声变空(看起来像「对话被清了」,其实只是 403 / 读文件失败)
|
|
825
851
|
try {
|
|
826
852
|
const res = await fetch('/api/session');
|
|
827
|
-
|
|
853
|
+
if (!res.ok) {
|
|
854
|
+
const body = await res.json().catch(() => ({}));
|
|
855
|
+
state.session = null;
|
|
856
|
+
setNotice(`读会话失败:${body.error || res.status}(对话视图因此先收起来了)`, true);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const body = await res.json();
|
|
828
860
|
state.session = body.session ?? null;
|
|
829
|
-
} catch {
|
|
861
|
+
} catch (e) {
|
|
830
862
|
state.session = null;
|
|
863
|
+
setNotice(`读会话失败:${e.message}(对话视图因此先收起来了)`, true);
|
|
831
864
|
}
|
|
832
865
|
}
|
|
833
866
|
|
|
@@ -965,6 +998,57 @@
|
|
|
965
998
|
void openFs(browseTarget(workdirEl.value, state.dev ? state.dev.defaultWorkdir : '.'));
|
|
966
999
|
});
|
|
967
1000
|
|
|
1001
|
+
// ---------- 系统目录选择器(与「浏览…」并列,互为兜底) ----------
|
|
1002
|
+
// 浏览器弹不出系统对话框 ⇒ 由 dev 进程代弹(POST /api/fs/pick;token 走 cookie,
|
|
1003
|
+
// 与其它请求同一条缝)。服务端契约:
|
|
1004
|
+
// 200 {path} 选了 / 200 {path:null} 取消 / 409 已有一个在等 / 501 平台没有可用选择器
|
|
1005
|
+
// 等待期间按钮置灰防重点(第一道闸;服务端那条 409 是第二道)。
|
|
1006
|
+
// 任何一个失败分支都必须有用户可见的提示 —— 不许静默。
|
|
1007
|
+
const pickEl = $('pick');
|
|
1008
|
+
pickEl.addEventListener('click', async () => {
|
|
1009
|
+
pickEl.disabled = true;
|
|
1010
|
+
setNotice('系统选择器已打开…(在你的桌面上,不在浏览器里)', false);
|
|
1011
|
+
try {
|
|
1012
|
+
const res = await fetch('/api/fs/pick', { method: 'POST' });
|
|
1013
|
+
const body = await res.json().catch(() => ({}));
|
|
1014
|
+
if (res.status === 501) {
|
|
1015
|
+
setNotice(`系统选择器不可用:${body.error || '当前平台没有可用的系统选择器'} —— 请改用旁边的「浏览…」`, true);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (!res.ok) {
|
|
1019
|
+
// 409 与其余失败都走这里:服务端的 error 文案说明原因
|
|
1020
|
+
setNotice(`系统选择器打开失败:${body.error || res.status}`, true);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (body.path === null || body.path === undefined) {
|
|
1024
|
+
setNotice('已取消(没选目录)', false);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
workdirEl.value = body.path;
|
|
1028
|
+
state.workdir = body.path;
|
|
1029
|
+
setNotice(`工作目录:${short(body.path)}`, false);
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
setNotice(`系统选择器请求失败:${e.message}`, true);
|
|
1032
|
+
} finally {
|
|
1033
|
+
pickEl.disabled = false;
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// 页面卸载(刷新 / 关标签页 / 跳走)⇒ **显式**告诉服务端「在飞的选择框没人看了」。
|
|
1038
|
+
// 为什么不能只靠连接断开:实测(2026-09-23,带插桩的产物 + 真浏览器)刷新后那条连接
|
|
1039
|
+
// **还开着** ⇒ 服务端收不到 close ⇒ 选择框留在桌面上、之后每次点都 409。
|
|
1040
|
+
// beacon 是**一次新的请求**,与服务端看不看得见断开无关;token 走 cookie(同一道缝)。
|
|
1041
|
+
// 无条件发:服务端幂等(没有在飞的 pick 就是空操作),省掉一份会漂的页面态。
|
|
1042
|
+
// ⚠️ 路径字面量与 `inspector-routes.ts` 的 PICK_CANCEL_PATH 必须逐字一致 ——
|
|
1043
|
+
// inspector.test.mjs 有逐字对拍断言钉着(页面不能 import 那个常量)。
|
|
1044
|
+
window.addEventListener('pagehide', () => {
|
|
1045
|
+
try {
|
|
1046
|
+
navigator.sendBeacon('/api/fs/pick/cancel');
|
|
1047
|
+
} catch {
|
|
1048
|
+
/* 老浏览器 / 被策略拦下:还有 res close 那条路兜着,不阻断卸载 */
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
|
|
968
1052
|
// ---------- 控件接线(只做「取值 → 调 panel-logic → 写回」) ----------
|
|
969
1053
|
workdirEl.addEventListener('input', () => {
|
|
970
1054
|
state.workdir = workdirEl.value;
|
|
@@ -1098,7 +1182,7 @@
|
|
|
1098
1182
|
// 右栏从这一轮的空树开始(① 实时):不这么做的话它留着上一轮那棵树
|
|
1099
1183
|
beginLiveRender();
|
|
1100
1184
|
setNotice(
|
|
1101
|
-
`运行中:${short(ev.workdir)} · ${ev.toolSources
|
|
1185
|
+
`运行中:${short(ev.workdir)} · ${formatToolSources(ev.toolSources)}` +
|
|
1102
1186
|
`${ev.multiTurn ? ' · 多轮' : ''}`,
|
|
1103
1187
|
false,
|
|
1104
1188
|
);
|