@tea-agent/loop-agent 0.44.0-next.5 → 0.44.0-next.6
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 +23 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/pi-sdk-executor.js +7 -7
- package/dist/infrastructure/console/operation-store.js +46 -14
- package/dist/shared/wizard-local-telemetry.js +2 -1
- package/dist/worker/console/chat/pi-runtime.js +217 -18
- package/dist/worker/console/chat/repo-browser.js +49 -0
- package/dist/worker/console/chat/routes.js +77 -57
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BHuhGM1e.js → abnfDiagram-N423BO3Z-Brn2sPzL.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CKh5E_sr.js → arc-BIMNTG0W.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DizhiBEv.js → architectureDiagram-T3A2C74G--WtcgR_A.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-3Neea-Z-.js → blockDiagram-VBNYF7ZC-BT_Ztog1.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCP_Qrgi.js → c4Diagram-5PPSVZJV-C5hbVr-l.js} +1 -1
- package/dist/worker/console/static/assets/channel-DNWB4hpR.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C_wydftK.js → chunk-2GRJ4B5K-BKKnyR9Z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-jRPXZbzx.js → chunk-2Q5K7J3B-BHs6biqA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BLQf2OYO.js → chunk-5RXB4S5H-BdSFy_vP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-0qRq64rI.js → chunk-5VM5RSS4-BpJIdTKV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-ClMg8xmV.js → chunk-6Q2QTUOP-C_iWJhkT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Coop2FlL.js → chunk-GF5L2VYU-BTbmx3-k.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CDdO-jPJ.js → chunk-JWPE2WC7-D4Ee6DmP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D8Gkppjl.js → chunk-KBJHAD2P-B487SORR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DA9HIySf.js → chunk-RYQCIY6F-BKYHjNrr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DZNDRX1k.js → chunk-XXDRQBXY-DrTdoYZG.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-COhFgJji.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-COhFgJji.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CZ1zGyjq.js → cose-bilkent-JH36ORCC-CttUY_Jo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B8u1_FtM.js → cynefin-VYW2F7L2-B4Rm5gnR.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CPBB6lfo.js → cynefinDiagram-MW4NZA55-ByxHBq_S.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CG3Eov3E.js → dagre-VZM6K2ZE-DGrfNFA0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ycI-dOjh.js → diagram-7IWD3JNH-KjrUPquv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bx_GyQ7Y.js → diagram-B4RE2ZJO-VS4mlm75.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-m0G6mP4d.js → diagram-LBJQPF4R-BLXYmFTM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DTDJkqcJ.js → diagram-Q27KOJAE-C6xoFAqH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BCfFrG-p.js → diagram-UB23O5K3-C478dCe7.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BIkTNPpp.js → ebnfDiagram-BXEA7PRR-DHEpW5zN.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DTcMWrd2.js → erDiagram-JOGREHBK-CA5GKfo9.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BqzDaNQ7.js → flowDiagram-UKHOOZJN-zysi0Fl-.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BSl_vGFe.js → ganttDiagram-PKOTCBZU-DGzcKA8Y.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCA5HXM6.js → gitGraphDiagram-DS77QQ5N-CHsuIG1w.js} +1 -1
- package/dist/worker/console/static/assets/{index-wKEJUaP5.js → index-CJh6pVLm.js} +97 -97
- package/dist/worker/console/static/assets/index-C_kvKmFM.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DvqT18ge.js → infoDiagram-6WML65LV-BexZHPCp.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B6zGKrQ3.js → ishikawaDiagram-WSZJBQD7-Gkx8q-DB.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BpLQPI9C.js → journeyDiagram-NVQOT4AX-XDW3qrJ7.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CzIz_-Lt.js → kanban-definition-27J2QSJJ-DJ07eEaN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CMQ8Jpp-.js → linear-DL-zeqOn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BdAMrTf7.js → mermaid.core-C0HdXtOf.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-hSwF6sOq.js → mindmap-definition-FAOFIHXS-CqRX0lrt.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DZdv509B.js → pegDiagram-VL7TDLO6-BVM9JZ2H.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CmD4-b0K.js → pieDiagram-7S7Q4E2Y-BA3ozdKk.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DtKxNxlS.js → quadrantDiagram-CIZ2JOQS-B-0_p9_4.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-D48iIoiS.js → railroadDiagram-AXF67PYL-DHRzPWFc.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C3N0K-2p.js → requirementDiagram-LRYGKXZP-9BEC_PcW.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CACWR-Xs.js → sankeyDiagram-W5VNT64P-DMxeW4rN.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-g_QgugvZ.js → sequenceDiagram-SI44F4Z6-DixP6LIU.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DqLoRPGK.js → sizeCapture-X5ZJPWSS-B_EifJBo.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-ES59VgMG.js → stateDiagram-OKZ733FA-BwikwWbX.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-S2ZHMo1c.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-fjyLVNWo.js → swimlanes-SLNWSIFB-C162l9o1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BZm4ByfV.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cd5-biKv.js → timeline-definition-Z64GVDOM-mpe_u1i4.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CZTu6mPf.js → vennDiagram-T6HMQDX7-DyUk54HX.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DR01jjbe.js → wardleyDiagram-T6FBY63Y-HYyMkzfm.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-iZcuGVGa.js → xychartDiagram-ELKLHX3M-DRoh7skR.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +4 -2
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +8 -1
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/docs/templates/backend-test-dag.json +1 -0
- package/package.json +4 -4
- package/dist/worker/console/static/assets/channel-DxgzA1GF.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BZcm7fs6.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BZcm7fs6.js +0 -1
- package/dist/worker/console/static/assets/index-BAEDjTbD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyCIq6jx.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CsKtO34q.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
- 修复 main 分支的 next 发布链在类型检查阶段内存溢出失败的问题:完整源码与测试的类型图已超过 Node 默认约 2 GiB 堆上限,类型检查进程因此中止(exit 134),导致 `next` 发布无法完成。此前该上限只在部分验证入口生效,其余入口直接运行类型检查就会因默认堆不足而失败;现把上限固化到类型检查命令本身,各验证入口与本地开发统一生效,不再依赖各入口自行声明。
|
|
6
|
+
|
|
7
|
+
- 修复 Operator Chat 冷启动卡在检查面板的问题:模型列表和 runtime-snapshot 改为读工作区目录,不再为了展示技能或系统提示去打开 Pi 历史 JSONL;发消息、切换模型、reload 仍会激活会话。会话栏也不再等 Pi 重开,工作区列表未返回前不再把侧栏滤空。
|
|
8
|
+
|
|
9
|
+
- 修复 Operator Chat 在工作区尚未选中时抢先请求仓库树和能力目录、浏览器 Network 出现 400 的问题;等 `workspaceId` 就绪后再请求,不再把启动竞态显示成功能异常。
|
|
10
|
+
|
|
11
|
+
- 修复 Console 成功更新后历史接棒记录仍占用操作位、导致工作区初始化持续被拒绝的问题;通用「需要核对」提示现在引导先核实实际执行结果,不再误导为重新更新程序。
|
|
12
|
+
|
|
13
|
+
- Console 更新前保留当前实例身份,修复重启断线后首次响应已来自新进程时仍等待超时、无法自动刷新的问题;关闭重开设置保留该身份,所有刷新等待分支均反馈超时。
|
|
14
|
+
- 修复观测页「执行历史」把仍在重试的节点误报为「最终失败」的问题:只要节点最后一个已记录的尝试失败,就会显示「N 次 · 最终失败」,即使该节点仍在运行、重试已经排队,会让人误判运行已结束。现在节点处于运行/已启动/暂停状态时改为「N 次 · 最近一次失败,仍在重试」,真正到达终态才显示「最终失败」;修复轮次的多轮标签同样处理。
|
|
15
|
+
|
|
16
|
+
- 修复 Console「更新程序」被一条卡住的旧操作永久堵死的问题:停在「等待核对」的操作记录已被启动对账跳过、自身无法再推进,却又被当成「仍在运行」,导致之后每次更新都提示「另一个 Console 操作正在进行」。现将其明确归为已结束、不再占用操作位,并在恢复页保留可见;同时补全「已安装成功但接棒失败」记录的自动收敛,不再永久停留。
|
|
17
|
+
|
|
18
|
+
- Console 操作文案改为面向操作者的说明并给出下一步:等待超时不再伪装成「需要核对」,改为提示操作可能仍在后台运行;「源内容已变更」「前端拆分状态被并发修改」等原本直接展示英文原文的错误,改为说明原因与具体处理步骤;恢复页按钮与提示去掉 reconcile / supersede / orphan / Human Gate 等术语。
|
|
19
|
+
|
|
20
|
+
- 修复后端测试 DAG 模板遗漏 `upstream-unavailable` 重试类别的问题:内置模板的 Markdown 计划节点缺少该类别,与运行时生成器不一致,导致照模板配置的项目少一类重试;现已补齐,并把模板漂移检查扩展到重试策略字段,避免同类不一致再次漏过。
|
|
21
|
+
|
|
22
|
+
- Operator Chat 右侧辅助栏改为三个表面:仓库、会话、工作台。点上下文环进入「会话 / 上下文」,文件和终端不再与会话检查挤在同一条标签上;工作台用内侧图标轨切换终端、浏览器和数据库。
|
|
23
|
+
|
|
24
|
+
- Operator Chat 侧栏折叠后改为 56px 图标轨:可直接点新建会话、搜索、添加工作区和设置,不再留下空槽。
|
|
25
|
+
- Operator Chat 的 `@` 文件面板改为按当前目录一层浏览:空 `@` 不再倒出整库路径,点文件默认隐藏,Tab 进入目录、Enter 选定,插入仍是 `@path` 文本。文件行改为左对齐菜单,不再被全局按钮样式居中成胶囊;目录右侧常驻进入箭头,浮层圆角与投影贴近 DSH 菜单。
|
|
26
|
+
- Pi SDK 运行时依赖升级:`@earendil-works/pi-ai` 与 `@earendil-works/pi-coding-agent` 从精确 pin `0.83.0` 升到 `0.84.4`(顺带修复其携带的 `undici` 高危漏洞与 `brace-expansion` DoS 告警)。适配 0.84 破坏性变更:`getApiKeyAndHeaders` 返回的 headers 现可含 `null` 删除标记、`setRuntimeApiKey` 第三参改为认证取消选项;扩展加载 fixture 改为 ESM(pi-ai 子路径导出不再提供 CJS 条件)。SDK 契约探针文案、发布包契约测试与自举 canary 同步对齐 0.84.4。
|
|
27
|
+
|
|
5
28
|
- 修复 Windows 上 pi 节点零提交失败的读写守卫误报:libuv 在 win32 的 `fstat` 返回卷序列号而 `lstat` 恒为 0,跨 API 比较必然失败并误报“文件在打开期间被替换”;现新建共享身份比较 helper,win32 跳过跨 API `dev` 比较但保留 `ino` 与其余全部防护,Console 仓库预览同类比较一并修复(用户 0.42.1/0.43.0 两次实测上报)。
|
|
6
29
|
- 修复研发工作流对 Git submodule 的误拦截:父仓库 `git status` 只能看到 submodule 目录级条目,子仓库内合法写入会被误判 `empty-diff`(脏基线)或越界(净基线),导致 verify/review/closeout 全部跳过;现在状态采集会展开到已初始化子仓库的文件级事实(含嵌套 submodule、中文/带引号路径解码、未初始化与采集失败时如实报错不吞证据),合法子仓库写入正常进入验证链,真实越界仍按文件精确拦截,无 submodule 仓库行为字节级不变。
|
|
7
30
|
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.44.0-next.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-09-
|
|
3
|
+
"version": "0.44.0-next.6",
|
|
4
|
+
"gitSha": "11c9ac87f792e61b1bc31b1ef2aeacb48a6334d4",
|
|
5
|
+
"builtAt": "2026-09-16T11:49:07.558Z"
|
|
6
6
|
}
|
|
@@ -40,21 +40,21 @@ export async function installPiProviderRequestBudget(session, reserve, onFailure
|
|
|
40
40
|
const agent = session.agent;
|
|
41
41
|
const original = agent?.streamFunction;
|
|
42
42
|
if (!agent || typeof original !== "function")
|
|
43
|
-
throw Error("FRONTEND_PROVIDER_BUDGET_UNSUPPORTED: SDK actual request stream unavailable");
|
|
43
|
+
throw new Error("FRONTEND_PROVIDER_BUDGET_UNSUPPORTED: SDK actual request stream unavailable");
|
|
44
44
|
const { createAssistantMessageEventStream } = await import("@earendil-works/pi-ai");
|
|
45
45
|
agent.streamFunction = async (...args) => {
|
|
46
46
|
try {
|
|
47
47
|
if (args[2]?.signal?.aborted)
|
|
48
|
-
throw Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
48
|
+
throw new Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
49
49
|
if (capacity) {
|
|
50
50
|
const measurement = measureFrontendRequestEnvelope(args[1], args[0], capacity.policy);
|
|
51
51
|
capacity.observe(measurement);
|
|
52
52
|
if (measurement.exceedsEstimate)
|
|
53
|
-
throw Error(`FRONTEND_CONTEXT_ESTIMATE_EXCEEDED: estimated=${measurement.estimatedTokens} available=${measurement.availableInputTokens}; estimator=${measurement.estimatorVersion}; registry capability is not a measured gateway limit`);
|
|
53
|
+
throw new Error(`FRONTEND_CONTEXT_ESTIMATE_EXCEEDED: estimated=${measurement.estimatedTokens} available=${measurement.availableInputTokens}; estimator=${measurement.estimatorVersion}; registry capability is not a measured gateway limit`);
|
|
54
54
|
}
|
|
55
55
|
await reserve();
|
|
56
56
|
if (args[2]?.signal?.aborted)
|
|
57
|
-
throw Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
57
|
+
throw new Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
58
58
|
}
|
|
59
59
|
catch (error) {
|
|
60
60
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -124,7 +124,7 @@ export function setPiSdkImportOverrideForTests(fn) {
|
|
|
124
124
|
export function setPiSdkModuleOverrideForTests(fn) {
|
|
125
125
|
sdkModuleOverrideForTests = fn;
|
|
126
126
|
}
|
|
127
|
-
/** Check whether the Pi SDK runtime dependency satisfies the 0.
|
|
127
|
+
/** Check whether the Pi SDK runtime dependency satisfies the 0.84.4 runtime contract. */
|
|
128
128
|
export async function checkPiSdkAvailability(_repoRoot) {
|
|
129
129
|
if (sdkSessionFactoryOverride) {
|
|
130
130
|
return { ok: true, detail: "pi SDK session factory override active" };
|
|
@@ -140,10 +140,10 @@ export async function checkPiSdkAvailability(_repoRoot) {
|
|
|
140
140
|
typeof ModelRuntime?.create !== "function") {
|
|
141
141
|
return {
|
|
142
142
|
ok: false,
|
|
143
|
-
detail: "pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.
|
|
143
|
+
detail: "pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.84.4 contract)",
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
return { ok: true, detail: "pi SDK 0.
|
|
146
|
+
return { ok: true, detail: "pi SDK 0.84.4 contract available" };
|
|
147
147
|
}
|
|
148
148
|
catch (error) {
|
|
149
149
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -6,6 +6,24 @@ import { buildFrontendSplitAggregateCloseout, computeFrontendSplitDigest, finali
|
|
|
6
6
|
import { frontendCloseoutFactsSchema, frontendTestHandoffSchema, } from "../../workflows/dag/frontend-closeout.js";
|
|
7
7
|
import { listJsonFiles, readJsonIfExists, sha256Json, writeSecureJson, } from "./app-data.js";
|
|
8
8
|
export const TERMINAL_OPERATION_STATES = new Set(["succeeded", "failed", "timed-out", "restarting"]);
|
|
9
|
+
/**
|
|
10
|
+
* Operation states that hold an admission slot. needs-reconcile requires
|
|
11
|
+
* inspection but does not itself prove a live operation; callers still check
|
|
12
|
+
* DAG/Worker/chat activity separately. restarting is the terminal receipt of
|
|
13
|
+
* a successful Console handoff, retained across boots, not a live slot.
|
|
14
|
+
*/
|
|
15
|
+
export const OCCUPIED_OPERATION_STATES = new Set(["queued", "starting", "running"]);
|
|
16
|
+
/** True when the state can still make progress and therefore holds the slot. */
|
|
17
|
+
export function isOperationOccupied(state) {
|
|
18
|
+
return OCCUPIED_OPERATION_STATES.has(state);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* True when no further state transition will ever occur: a business terminal
|
|
22
|
+
* (succeeded/failed/timed-out/restarting) or a stranded needs-reconcile.
|
|
23
|
+
*/
|
|
24
|
+
export function isOperationSettled(state) {
|
|
25
|
+
return TERMINAL_OPERATION_STATES.has(state) || state === "needs-reconcile";
|
|
26
|
+
}
|
|
9
27
|
function operationFile(appData, operationId) {
|
|
10
28
|
return path.join(appData.operations, `${operationId}.json`);
|
|
11
29
|
}
|
|
@@ -359,14 +377,11 @@ export class OperationStore {
|
|
|
359
377
|
for (const op of await this.list()) {
|
|
360
378
|
if (TERMINAL_OPERATION_STATES.has(op.state))
|
|
361
379
|
continue;
|
|
362
|
-
// A consoleUpdateAndInit
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
// handoff target. Mark it succeeded instead of surfacing the
|
|
366
|
-
// generic needs-reconcile failure copy to the operator.
|
|
380
|
+
// A consoleUpdateAndInit that proved its handoff before the process
|
|
381
|
+
// died must not be reported as a stranded failure: the booting
|
|
382
|
+
// process IS the handoff target, so the update actually succeeded.
|
|
367
383
|
if (op.action === "consoleUpdateAndInit" &&
|
|
368
|
-
op
|
|
369
|
-
isProvenConsoleUpdateHandoff(op.result)) {
|
|
384
|
+
isProvenConsoleUpdateHandoff(op)) {
|
|
370
385
|
changed.push(await this.update(op.operationId, {
|
|
371
386
|
state: "succeeded",
|
|
372
387
|
errorCode: undefined,
|
|
@@ -398,17 +413,34 @@ export function isTerminalOperationState(state) {
|
|
|
398
413
|
return TERMINAL_OPERATION_STATES.has(state);
|
|
399
414
|
}
|
|
400
415
|
/**
|
|
401
|
-
* True when a consoleUpdateAndInit
|
|
402
|
-
* (install verified +
|
|
403
|
-
*
|
|
404
|
-
*
|
|
416
|
+
* True when a consoleUpdateAndInit record proves the handoff actually
|
|
417
|
+
* succeeded (install verified + new Console spawned), so boot reconcile can
|
|
418
|
+
* close it as succeeded even though the dying process never wrote its
|
|
419
|
+
* terminal state.
|
|
420
|
+
*
|
|
421
|
+
* The proof reads the maintenance projection rather than only `result.state`.
|
|
422
|
+
* The old check required `result.state === "restarting"`, but the executor
|
|
423
|
+
* writes `state` and `maintenance` together (`console-update-and-init.ts`),
|
|
424
|
+
* so any path that recorded the handoff proof together with a non-restarting
|
|
425
|
+
* `state` — most importantly the `needs-reconcile` record that
|
|
426
|
+
* `acceptConsoleUpdateAndInit` writes when the executor throws *after* a
|
|
427
|
+
* successful handoff — could never satisfy it. Such records were stranded
|
|
428
|
+
* forever: boot reconcile skipped them and they blocked later updates.
|
|
429
|
+
*
|
|
430
|
+
* `handoff.status === "requested"` is only ever set on the success path, and
|
|
431
|
+
* `package.status === "failed"` explicitly disqualifies a record, so this
|
|
432
|
+
* stays fail-closed for genuine failures.
|
|
405
433
|
*/
|
|
406
|
-
function isProvenConsoleUpdateHandoff(
|
|
434
|
+
export function isProvenConsoleUpdateHandoff(operation) {
|
|
435
|
+
if (!operation || operation.action !== "consoleUpdateAndInit")
|
|
436
|
+
return false;
|
|
437
|
+
const result = operation.result;
|
|
407
438
|
if (!result || typeof result !== "object")
|
|
408
439
|
return false;
|
|
409
440
|
const shape = result;
|
|
410
|
-
|
|
411
|
-
|
|
441
|
+
if (shape.maintenance?.handoff?.status !== "requested")
|
|
442
|
+
return false;
|
|
443
|
+
return shape.maintenance?.package?.status !== "failed";
|
|
412
444
|
}
|
|
413
445
|
export function clipPreview(text, max = 4096) {
|
|
414
446
|
if (text.length <= max)
|
|
@@ -12,7 +12,8 @@ function deleteHeaderCaseInsensitive(headers, name) {
|
|
|
12
12
|
}
|
|
13
13
|
function configuredHeader(headers, name) {
|
|
14
14
|
const key = Object.keys(headers ?? {}).find((entry) => entry.toLowerCase() === name.toLowerCase());
|
|
15
|
-
|
|
15
|
+
const value = key ? headers?.[key] : undefined;
|
|
16
|
+
return typeof value === "string" ? value : undefined;
|
|
16
17
|
}
|
|
17
18
|
function setStaticHeader(headers, configured, name, defaultValue) {
|
|
18
19
|
deleteHeaderCaseInsensitive(headers, name);
|
|
@@ -296,6 +296,14 @@ export function createWizardLocalHeadersExtension(options) {
|
|
|
296
296
|
}
|
|
297
297
|
export { THINKING_LEVELS, RuntimeSnapshotUnavailableError, projectRuntimeSnapshot, };
|
|
298
298
|
export { createDefaultPiSdkBindings } from "./pi-runtime/sdk-bindings.js";
|
|
299
|
+
/** Catalog listing must not load skills/extensions/context — those belong to reopen. */
|
|
300
|
+
const CATALOG_RESOURCE_LOADER_OPTIONS = Object.freeze({
|
|
301
|
+
noContextFiles: true,
|
|
302
|
+
noSkills: true,
|
|
303
|
+
noExtensions: true,
|
|
304
|
+
noPromptTemplates: true,
|
|
305
|
+
noThemes: true,
|
|
306
|
+
});
|
|
299
307
|
export { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage };
|
|
300
308
|
export function buildRequestContextEvent(input) {
|
|
301
309
|
const systemPrompt = input.session.state?.systemPrompt ?? input.session.systemPrompt ?? "";
|
|
@@ -440,6 +448,8 @@ export class ConsolePiRuntime {
|
|
|
440
448
|
mainlineLeaves = new Map();
|
|
441
449
|
/** Per-sessionId single-flight materialization (create fast-path / readiness fence). */
|
|
442
450
|
sessionInit = new Map();
|
|
451
|
+
/** Concurrent reopen of the same historical JSONL shares one in-flight promise. */
|
|
452
|
+
reopenInFlight = new Map();
|
|
443
453
|
/** Resolved model binding after materialization (create may return before this is known). */
|
|
444
454
|
sessionModels = new Map();
|
|
445
455
|
/** Durable session mode; record is the source of truth, this is the live handle cache. */
|
|
@@ -1251,11 +1261,68 @@ export class ConsolePiRuntime {
|
|
|
1251
1261
|
entries: manager.getBranch?.() ?? [],
|
|
1252
1262
|
};
|
|
1253
1263
|
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Workspace-scoped model catalog. A live session's ModelRuntime is reused
|
|
1266
|
+
* when present; otherwise services are created without opening the Pi
|
|
1267
|
+
* conversation JSONL so the picker is not blocked on cold reopen.
|
|
1268
|
+
*/
|
|
1269
|
+
workspaceModelRuntime;
|
|
1270
|
+
workspaceInspection;
|
|
1271
|
+
async getWorkspaceModelRuntime() {
|
|
1272
|
+
if (!this.workspaceModelRuntime) {
|
|
1273
|
+
this.workspaceModelRuntime = (async () => {
|
|
1274
|
+
const agentDir = await safeGetAgentDir(this.bindings);
|
|
1275
|
+
const { services } = await this.bindings.createServices({
|
|
1276
|
+
cwd: this.options.cwd,
|
|
1277
|
+
agentDir,
|
|
1278
|
+
resourceLoaderOptions: { ...CATALOG_RESOURCE_LOADER_OPTIONS },
|
|
1279
|
+
});
|
|
1280
|
+
return services.modelRuntime;
|
|
1281
|
+
})().catch((error) => {
|
|
1282
|
+
this.workspaceModelRuntime = undefined;
|
|
1283
|
+
throw error;
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
return this.workspaceModelRuntime;
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Full OPEN-surface services for inspect UIs. No SessionManager / JSONL.
|
|
1290
|
+
* Cached without sessionId so todo/goal extensions stay off the shared scope.
|
|
1291
|
+
*/
|
|
1292
|
+
async getWorkspaceInspectionServices() {
|
|
1293
|
+
if (!this.workspaceInspection) {
|
|
1294
|
+
this.workspaceInspection = (async () => {
|
|
1295
|
+
const agentDir = await safeGetAgentDir(this.bindings);
|
|
1296
|
+
const appendSystemPrompt = composeChatSystemPromptParts({
|
|
1297
|
+
sessionMode: DEFAULT_CONSOLE_CHAT_SESSION_MODE,
|
|
1298
|
+
workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
1299
|
+
});
|
|
1300
|
+
const { services } = await this.bindings.createServices({
|
|
1301
|
+
cwd: this.options.cwd,
|
|
1302
|
+
agentDir,
|
|
1303
|
+
resourceLoaderOptions: await this.buildResourceLoaderOptions({
|
|
1304
|
+
appendSystemPrompt,
|
|
1305
|
+
}),
|
|
1306
|
+
});
|
|
1307
|
+
return {
|
|
1308
|
+
modelRuntime: services.modelRuntime,
|
|
1309
|
+
resourceLoader: services
|
|
1310
|
+
.resourceLoader,
|
|
1311
|
+
settingsManager: services.settingsManager,
|
|
1312
|
+
};
|
|
1313
|
+
})().catch((error) => {
|
|
1314
|
+
this.workspaceInspection = undefined;
|
|
1315
|
+
throw error;
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
return this.workspaceInspection;
|
|
1319
|
+
}
|
|
1254
1320
|
async listModels(sessionId) {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1321
|
+
const live = this.modelRuntimes.get(sessionId);
|
|
1322
|
+
if (live && this.sessions.has(sessionId)) {
|
|
1323
|
+
return this.bindings.listAvailableModels({ modelRuntime: live });
|
|
1324
|
+
}
|
|
1325
|
+
const modelRuntime = await this.getWorkspaceModelRuntime();
|
|
1259
1326
|
return this.bindings.listAvailableModels({ modelRuntime });
|
|
1260
1327
|
}
|
|
1261
1328
|
async applyModel(sessionId, model) {
|
|
@@ -1518,11 +1585,11 @@ export class ConsolePiRuntime {
|
|
|
1518
1585
|
return { ok: true, revision: this.revisions.get(sessionId) ?? 0 };
|
|
1519
1586
|
}
|
|
1520
1587
|
/**
|
|
1521
|
-
* Project the unified
|
|
1522
|
-
*
|
|
1523
|
-
*
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1588
|
+
* Project the unified runtime snapshot (design §2.2). A live Pi session is
|
|
1589
|
+
* the source of truth when present. Inspect GETs may pass
|
|
1590
|
+
* `workspaceProjection` to render ResourceLoader inventories without
|
|
1591
|
+
* opening the conversation JSONL. Unknown ids without that hint stay
|
|
1592
|
+
* undefined so callers fail closed.
|
|
1526
1593
|
*/
|
|
1527
1594
|
/**
|
|
1528
1595
|
* ConsolePiRuntime.listSlashCommands
|
|
@@ -1541,18 +1608,27 @@ export class ConsolePiRuntime {
|
|
|
1541
1608
|
return collectSlashCommands({ session, resourceLoader });
|
|
1542
1609
|
}
|
|
1543
1610
|
async getRuntimeSnapshot(sessionId, options) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1611
|
+
const initializing = this.sessionInit.get(sessionId)?.status === "initializing";
|
|
1612
|
+
if (this.sessions.has(sessionId) || initializing) {
|
|
1613
|
+
try {
|
|
1614
|
+
await this.ensureSessionReady(sessionId);
|
|
1615
|
+
}
|
|
1616
|
+
catch (error) {
|
|
1617
|
+
if (error instanceof PiSessionInitFailedError)
|
|
1618
|
+
throw error;
|
|
1619
|
+
if (options?.workspaceProjection) {
|
|
1620
|
+
return this.projectWorkspaceRuntimeSnapshot(sessionId, options);
|
|
1621
|
+
}
|
|
1622
|
+
return undefined;
|
|
1623
|
+
}
|
|
1552
1624
|
}
|
|
1553
1625
|
const session = this.sessions.get(sessionId);
|
|
1554
|
-
if (!session)
|
|
1626
|
+
if (!session) {
|
|
1627
|
+
if (options?.workspaceProjection) {
|
|
1628
|
+
return this.projectWorkspaceRuntimeSnapshot(sessionId, options);
|
|
1629
|
+
}
|
|
1555
1630
|
return undefined;
|
|
1631
|
+
}
|
|
1556
1632
|
const services = this.serviceScopes.get(sessionId);
|
|
1557
1633
|
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1558
1634
|
const resourceLoader = services?.resourceLoader;
|
|
@@ -1653,6 +1729,116 @@ export class ConsolePiRuntime {
|
|
|
1653
1729
|
hasBash: true,
|
|
1654
1730
|
});
|
|
1655
1731
|
}
|
|
1732
|
+
resolveWorkspaceThinkingSelection(sessionId, stored) {
|
|
1733
|
+
const live = this.thinkingSelections.get(sessionId);
|
|
1734
|
+
if (live)
|
|
1735
|
+
return live;
|
|
1736
|
+
if (!stored || stored === "auto")
|
|
1737
|
+
return "auto";
|
|
1738
|
+
if (THINKING_LEVELS.includes(stored)) {
|
|
1739
|
+
return stored;
|
|
1740
|
+
}
|
|
1741
|
+
return "auto";
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Inspect projection without SessionManager.open. Used only when the Agent
|
|
1745
|
+
* is not live and the caller confirmed a durable session record.
|
|
1746
|
+
*/
|
|
1747
|
+
async projectWorkspaceRuntimeSnapshot(sessionId, options) {
|
|
1748
|
+
const hint = options.workspaceProjection ?? {};
|
|
1749
|
+
const sessionMode = hint.sessionMode ?? this.sessionModeOf(sessionId);
|
|
1750
|
+
let inspection;
|
|
1751
|
+
try {
|
|
1752
|
+
inspection = await this.getWorkspaceInspectionServices();
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
throw new RuntimeSnapshotUnavailableError(`workspace runtime projection failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1756
|
+
}
|
|
1757
|
+
const resourceLoader = inspection.resourceLoader;
|
|
1758
|
+
const settingsManager = inspection.settingsManager;
|
|
1759
|
+
const thinkingSelection = this.resolveWorkspaceThinkingSelection(sessionId, hint.thinkingLevel);
|
|
1760
|
+
const effectiveThinkingLevel = settingsManager?.getDefaultThinkingLevel?.() ??
|
|
1761
|
+
(thinkingSelection === "auto" ? undefined : thinkingSelection);
|
|
1762
|
+
const rawSystemPrompt = resourceLoader?.getSystemPrompt?.() ||
|
|
1763
|
+
composeChatSystemPrompt({
|
|
1764
|
+
sessionMode,
|
|
1765
|
+
workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
1766
|
+
});
|
|
1767
|
+
if (!rawSystemPrompt) {
|
|
1768
|
+
throw new RuntimeSnapshotUnavailableError("system prompt projection failed: workspace prompt was empty");
|
|
1769
|
+
}
|
|
1770
|
+
let models = [];
|
|
1771
|
+
try {
|
|
1772
|
+
models = this.bindings
|
|
1773
|
+
.listAvailableModels({ modelRuntime: inspection.modelRuntime })
|
|
1774
|
+
.map((entry) => ({
|
|
1775
|
+
provider: entry.provider,
|
|
1776
|
+
id: entry.id,
|
|
1777
|
+
...(entry.name ? { name: entry.name } : {}),
|
|
1778
|
+
hasCredentials: entry.hasCredentials,
|
|
1779
|
+
}));
|
|
1780
|
+
}
|
|
1781
|
+
catch (error) {
|
|
1782
|
+
throw new RuntimeSnapshotUnavailableError(`model enumeration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1783
|
+
}
|
|
1784
|
+
const thinkingLevels = [
|
|
1785
|
+
{ value: "auto" },
|
|
1786
|
+
...THINKING_LEVELS.map((value) => ({ value })),
|
|
1787
|
+
];
|
|
1788
|
+
let skillPrefs = { schemaVersion: 1, skills: {} };
|
|
1789
|
+
let skillPrefsError;
|
|
1790
|
+
if (resourceLoader) {
|
|
1791
|
+
try {
|
|
1792
|
+
skillPrefs = await this.loadSkillAutoInvocationPreferences();
|
|
1793
|
+
}
|
|
1794
|
+
catch (error) {
|
|
1795
|
+
skillPrefsError = error;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
const activeTools = chatActiveToolBaseline(sessionMode);
|
|
1799
|
+
const inventories = await collectRuntimeInventories({
|
|
1800
|
+
resourceLoader,
|
|
1801
|
+
skillPrefs,
|
|
1802
|
+
skillPrefsError,
|
|
1803
|
+
agentDir: await safeGetAgentDir(this.bindings),
|
|
1804
|
+
cwd: this.options.cwd,
|
|
1805
|
+
tools: [],
|
|
1806
|
+
activeToolNames: activeTools,
|
|
1807
|
+
});
|
|
1808
|
+
const storedModel = this.sessionModels.get(sessionId);
|
|
1809
|
+
const provider = storedModel?.provider ?? hint.modelProvider;
|
|
1810
|
+
const modelId = storedModel?.modelId ?? hint.modelId;
|
|
1811
|
+
return projectRuntimeSnapshot({
|
|
1812
|
+
sessionId,
|
|
1813
|
+
revision: this.revisions.get(sessionId) ?? 0,
|
|
1814
|
+
loadedAt: new Date().toISOString(),
|
|
1815
|
+
selection: {
|
|
1816
|
+
...(provider && modelId
|
|
1817
|
+
? { model: { provider, id: modelId } }
|
|
1818
|
+
: {}),
|
|
1819
|
+
thinkingSelection,
|
|
1820
|
+
...(effectiveThinkingLevel
|
|
1821
|
+
? { effectiveThinkingLevel: effectiveThinkingLevel }
|
|
1822
|
+
: {}),
|
|
1823
|
+
},
|
|
1824
|
+
models,
|
|
1825
|
+
thinkingLevels,
|
|
1826
|
+
systemPrompt: {
|
|
1827
|
+
...(options.includeRaw ? { raw: rawSystemPrompt } : {}),
|
|
1828
|
+
text: rawSystemPrompt,
|
|
1829
|
+
sha256: createHash("sha256").update(rawSystemPrompt).digest("hex"),
|
|
1830
|
+
characters: rawSystemPrompt.length,
|
|
1831
|
+
},
|
|
1832
|
+
activeTools,
|
|
1833
|
+
contextFiles: inventories.contextFiles,
|
|
1834
|
+
skills: inventories.skills,
|
|
1835
|
+
extensions: inventories.extensions,
|
|
1836
|
+
packages: inventories.packages,
|
|
1837
|
+
mcp: inventories.mcp,
|
|
1838
|
+
diagnostics: inventories.diagnostics,
|
|
1839
|
+
hasBash: true,
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1656
1842
|
/**
|
|
1657
1843
|
* Re-open a previously persisted Chat session after Console restart or
|
|
1658
1844
|
* browser refresh (roadmap M1-S04 / S05).
|
|
@@ -1667,6 +1853,19 @@ export class ConsolePiRuntime {
|
|
|
1667
1853
|
* than silently creating a fresh empty session and losing prior context.
|
|
1668
1854
|
*/
|
|
1669
1855
|
async reopenSession(init) {
|
|
1856
|
+
const sessionId = init.sessionId;
|
|
1857
|
+
const existing = this.reopenInFlight.get(sessionId);
|
|
1858
|
+
if (existing)
|
|
1859
|
+
return existing;
|
|
1860
|
+
const pending = this.reopenSessionExclusive(init).finally(() => {
|
|
1861
|
+
if (this.reopenInFlight.get(sessionId) === pending) {
|
|
1862
|
+
this.reopenInFlight.delete(sessionId);
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
this.reopenInFlight.set(sessionId, pending);
|
|
1866
|
+
return pending;
|
|
1867
|
+
}
|
|
1868
|
+
async reopenSessionExclusive(init) {
|
|
1670
1869
|
await this.syncAimaxAuthToken();
|
|
1671
1870
|
const sessionId = init.sessionId;
|
|
1672
1871
|
const sessionMode = this.rememberSessionMode(sessionId, init.sessionMode ?? this.sessionModeOf(sessionId));
|
|
@@ -127,6 +127,55 @@ export async function listRepoDirectory(repoRoot, rawPath) {
|
|
|
127
127
|
throw error;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
/** Composer `@` palette keeps the first page of a directory listing scannable. */
|
|
131
|
+
export const FILE_PALETTE_LIMIT = 50;
|
|
132
|
+
/**
|
|
133
|
+
* Split an `@` token query into a directory listing target and basename filter.
|
|
134
|
+
* Empty or slash-free queries stay on the workspace root (M3 fuzzy is out of
|
|
135
|
+
* scope). A trailing slash lists that directory with an empty fragment.
|
|
136
|
+
*/
|
|
137
|
+
export function parseFilePaletteQuery(rawQuery) {
|
|
138
|
+
const query = rawQuery.replaceAll("\\", "/").replace(/^\/+/u, "");
|
|
139
|
+
const slash = query.lastIndexOf("/");
|
|
140
|
+
if (slash < 0)
|
|
141
|
+
return { directory: "", fragment: query };
|
|
142
|
+
return {
|
|
143
|
+
directory: query.slice(0, slash),
|
|
144
|
+
fragment: query.slice(slash + 1),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export function projectFilePaletteEntries(entries, fragment) {
|
|
148
|
+
const showDots = fragment.startsWith(".");
|
|
149
|
+
const needle = fragment.toLowerCase();
|
|
150
|
+
const projected = [];
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
if (!showDots && entry.name.startsWith("."))
|
|
153
|
+
continue;
|
|
154
|
+
if (needle !== "" && !entry.name.toLowerCase().includes(needle))
|
|
155
|
+
continue;
|
|
156
|
+
const isDir = entry.kind === "directory";
|
|
157
|
+
projected.push({
|
|
158
|
+
path: isDir ? `${entry.path}/` : entry.path,
|
|
159
|
+
isDir,
|
|
160
|
+
});
|
|
161
|
+
if (projected.length >= FILE_PALETTE_LIMIT)
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
return projected;
|
|
165
|
+
}
|
|
166
|
+
/** One-level `@` file palette listing. Unreadable or out-of-repo queries are empty. */
|
|
167
|
+
export async function listChatFilePalette(repoRoot, rawQuery) {
|
|
168
|
+
const { directory, fragment } = parseFilePaletteQuery(rawQuery);
|
|
169
|
+
try {
|
|
170
|
+
const listed = await listRepoDirectory(repoRoot, directory);
|
|
171
|
+
return projectFilePaletteEntries(listed.entries, fragment);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof RepoBrowserError)
|
|
175
|
+
return [];
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
130
179
|
export async function readRepoPreview(repoRoot, rawPath) {
|
|
131
180
|
const guard = await guardReadPath(repoRoot, rawPath);
|
|
132
181
|
if (!guard.ok)
|