@tea-agent/loop-agent 0.40.0-next.8 → 0.40.0
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/AGENTS.md +1 -1
- package/CHANGELOG.md +21 -20
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +9 -5
- package/dist/cli/program.js +38 -3
- package/dist/commands/client-recovery.js +8 -36
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/dag-pi-executor.js +3 -0
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +79 -6
- package/dist/executors/shell-executor.js +94 -20
- package/dist/governance/checks.js +1 -0
- package/dist/shared/operator/capabilities.js +9 -1
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +82 -11
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +285 -8
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/routes.js +495 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +27 -18
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/turn-process.js +42 -5
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/operator-actions.js +20 -5
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +179 -0
- package/dist/worker/console/routes.js +246 -6
- package/dist/worker/console/server.js +173 -42
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BYBmDfPW.js → abnfDiagram-N423BO3Z-Dy71QJp3.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BKMmxpGF.js → arc-M25fNMTE.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DJpwxr1d.js → architectureDiagram-T3A2C74G-CUWlVl1a.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BaPj_ZlY.js → blockDiagram-VBNYF7ZC-Dd0Whpx2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8iGmHlm.js → c4Diagram-5PPSVZJV-BdfeCkI5.js} +1 -1
- package/dist/worker/console/static/assets/channel-Ba0AKiEZ.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DaxXQayc.js → chunk-2GRJ4B5K-D_1sUkyD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DqQKGA7q.js → chunk-2Q5K7J3B-Cxnxf36E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CgMKpJBB.js → chunk-5RXB4S5H-D9RG3G_x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BpToUlaW.js → chunk-5VM5RSS4-Zz9jgjLX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BsEWOHxv.js → chunk-6Q2QTUOP-BCggDRCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CKGSCe71.js → chunk-GF5L2VYU-CFg-5iAS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Bm1U5299.js → chunk-JWPE2WC7-DRQTE8x_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BI0ov870.js → chunk-KBJHAD2P-C_Dw7uZ8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BmnQo0iJ.js → chunk-RYQCIY6F-BjIiApsk.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BJBqa6Xc.js → chunk-XXDRQBXY-B5XQD4KG.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BuN9-7AR.js → cose-bilkent-JH36ORCC-huHqH6WQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bw22jc2k.js → cynefin-VYW2F7L2-BSi6yZqP.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BHnSH8Dr.js → cynefinDiagram-MW4NZA55-Bs_Jp31y.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BoXXIegj.js → dagre-VZM6K2ZE-CYxavikT.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CU8qyPhT.js → diagram-7IWD3JNH-B6TErl71.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-lrjhF0S8.js → diagram-B4RE2ZJO-CvH4J2ii.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DL0QDEpo.js → diagram-LBJQPF4R-v1NBAqXS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DxVMJMBH.js → diagram-Q27KOJAE-CUNHVAn2.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CFg9FzvG.js → diagram-UB23O5K3-Dzz1gUu-.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CQgP9fTG.js → ebnfDiagram-BXEA7PRR-CCF8_LjK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-A5vQXx9U.js → erDiagram-JOGREHBK-DObdPVLa.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-ytXnLyXD.js → flowDiagram-UKHOOZJN-DlN8Pxrj.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CYuksfnS.js → ganttDiagram-PKOTCBZU-BJKIPTGX.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-f_jW3zgU.js → gitGraphDiagram-DS77QQ5N-XcSSnObi.js} +1 -1
- package/dist/worker/console/static/assets/index-CIYzS16i.css +1 -0
- package/dist/worker/console/static/assets/index-CS3QOTfl.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C3HLE-xV.js → infoDiagram-6WML65LV-BKrRKMww.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DwpgejS6.js → ishikawaDiagram-WSZJBQD7-CN5dk-yb.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ll0YWX1y.js → journeyDiagram-NVQOT4AX-B4P4KZ-Y.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Hq3rchvU.js → kanban-definition-27J2QSJJ-BRlteP32.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BaGjJx8z.js → linear-CG8D9fuL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CKGQHWX_.js → mermaid.core-BuB6P2rX.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DM1FA6Dr.js → mindmap-definition-FAOFIHXS-DYXGl_DR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Xa9fHkE2.js → pegDiagram-VL7TDLO6-BM48yEVG.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-pCd4PDqJ.js → pieDiagram-7S7Q4E2Y-mT8NL-Ow.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-KqjpEBLk.js → quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DgvR7LZD.js → railroadDiagram-AXF67PYL-BKAcr70m.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Bys1UCWA.js → requirementDiagram-LRYGKXZP-CRxcKpoO.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Ms9npnZt.js → sankeyDiagram-W5VNT64P-Ds4LxQDI.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DEIWrtQZ.js → sequenceDiagram-SI44F4Z6-CPLwPf_p.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DELJ0AhZ.js → sizeCapture-X5ZJPWSS-CL65uij0.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BpHt9wKh.js → stateDiagram-OKZ733FA-CO0Mgd1W.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BUewAVYS.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-GKRsBluT.js → swimlanes-SLNWSIFB-Cxv3TSNw.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bhe67Wjl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cwsq1DxL.js → timeline-definition-Z64GVDOM-Dw85f9L2.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CfaqtKx0.js → vennDiagram-T6HMQDX7-B4QLCqCy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BlEUtK_g.js → wardleyDiagram-T6FBY63Y-B_yKCWwn.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CHSRXPvO.js → xychartDiagram-ELKLHX3M-nniBEGv2.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
- package/dist/worker/console/workspace-context.js +28 -3
- package/dist/worker/console/workspace-registry.js +333 -60
- package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +7 -2
- package/dist/worker/observe/static/styles.css +87 -25
- package/dist/worker/observe/static/views/dag-inspector.js +42 -1
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/worker/observe/static/views/session-timeline.js +135 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +5 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +18 -12
- package/dist/workflows/dag/init-hybrid.js +32 -9
- package/dist/workflows/dag/types.js +13 -0
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +27 -9
- package/docs/templates/init-managed-agents.md +1 -1
- package/package.json +3 -2
- package/skills/loop-agent/references/command-reference.md +15 -1
- package/dist/worker/console/static/assets/channel-CrzAa3FA.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/index-BB6KxwT_.css +0 -1
- package/dist/worker/console/static/assets/index-D_wxbadT.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BEeSswlv.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B2m_wca8.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { projectDeferredTurnSummaries, projectDeferredTurnSummary, } from "./deferred-turn.js";
|
|
2
|
+
/**
|
|
3
|
+
* Process-wide Session baseline, mirroring DSH's independent session.list.
|
|
4
|
+
* Full records stay in their fingerprint-isolated stores; this catalog only
|
|
5
|
+
* removes repeated filesystem discovery and resolves Session ownership.
|
|
6
|
+
*/
|
|
7
|
+
export class ChatSessionCatalog {
|
|
8
|
+
entries = new Map();
|
|
9
|
+
upsert(record, ownerWorkspaceId) {
|
|
10
|
+
const workspaceId = ownerWorkspaceId?.trim() || record.workspaceId?.trim();
|
|
11
|
+
const existing = this.entries.get(record.sessionId);
|
|
12
|
+
if (existing &&
|
|
13
|
+
existing.repoFingerprint !== record.repoFingerprint) {
|
|
14
|
+
throw new Error(`chat session catalog conflict: ${record.sessionId} belongs to multiple repositories`);
|
|
15
|
+
}
|
|
16
|
+
this.entries.set(record.sessionId, {
|
|
17
|
+
sessionId: record.sessionId,
|
|
18
|
+
repoFingerprint: record.repoFingerprint,
|
|
19
|
+
repoRootDisplay: record.repoRootDisplay,
|
|
20
|
+
state: record.state,
|
|
21
|
+
...(record.title !== undefined ? { title: record.title } : {}),
|
|
22
|
+
...(record.displayTitle !== undefined
|
|
23
|
+
? { displayTitle: record.displayTitle }
|
|
24
|
+
: {}),
|
|
25
|
+
createdAt: record.createdAt,
|
|
26
|
+
updatedAt: record.updatedAt,
|
|
27
|
+
...(record.modelProvider !== undefined
|
|
28
|
+
? { modelProvider: record.modelProvider }
|
|
29
|
+
: {}),
|
|
30
|
+
...(record.modelId !== undefined ? { modelId: record.modelId } : {}),
|
|
31
|
+
...(record.thinkingLevel !== undefined
|
|
32
|
+
? { thinkingLevel: record.thinkingLevel }
|
|
33
|
+
: {}),
|
|
34
|
+
deferredTurn: projectDeferredTurnSummary(record.deferredTurns?.[0]),
|
|
35
|
+
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
36
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
remove(sessionId) {
|
|
40
|
+
this.entries.delete(sessionId);
|
|
41
|
+
}
|
|
42
|
+
get(sessionId) {
|
|
43
|
+
return this.entries.get(sessionId);
|
|
44
|
+
}
|
|
45
|
+
list() {
|
|
46
|
+
return [...this.entries.values()].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -156,6 +156,102 @@ export function deriveChatSessionTelemetry(events) {
|
|
|
156
156
|
export function billedInputTokens(usage) {
|
|
157
157
|
return usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
158
158
|
}
|
|
159
|
+
export function deriveChatTurnTelemetry(events) {
|
|
160
|
+
const byTurn = new Map();
|
|
161
|
+
const turnOf = (turnId) => {
|
|
162
|
+
let entry = byTurn.get(turnId);
|
|
163
|
+
if (!entry) {
|
|
164
|
+
entry = {
|
|
165
|
+
turnId,
|
|
166
|
+
steps: 0,
|
|
167
|
+
llmMs: 0,
|
|
168
|
+
toolMs: 0,
|
|
169
|
+
toolCalls: 0,
|
|
170
|
+
samples: [],
|
|
171
|
+
};
|
|
172
|
+
byTurn.set(turnId, entry);
|
|
173
|
+
}
|
|
174
|
+
return entry;
|
|
175
|
+
};
|
|
176
|
+
const toolStarts = new Map();
|
|
177
|
+
for (const event of events) {
|
|
178
|
+
const at = eventTime(event);
|
|
179
|
+
const turn = turnOf(event.turnId);
|
|
180
|
+
if (event.kind === "usage") {
|
|
181
|
+
const sample = usageSample(event.data);
|
|
182
|
+
if (sample)
|
|
183
|
+
turn.samples.push(sample);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (event.kind === "user_message") {
|
|
187
|
+
if (turn.startedAt === undefined && at !== undefined)
|
|
188
|
+
turn.startedAt = at;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (event.kind === "agent_start") {
|
|
192
|
+
if (at !== undefined)
|
|
193
|
+
turn.stepStart = at;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (event.kind === "tool_call") {
|
|
197
|
+
turn.toolCalls += 1;
|
|
198
|
+
const toolCallId = event.data.toolCallId;
|
|
199
|
+
if (typeof toolCallId === "string" && toolCallId && at !== undefined) {
|
|
200
|
+
toolStarts.set(`${event.turnId}\u0000${toolCallId}`, at);
|
|
201
|
+
}
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (event.kind === "tool_result") {
|
|
205
|
+
const toolCallId = event.data.toolCallId;
|
|
206
|
+
if (typeof toolCallId === "string" && toolCallId && at !== undefined) {
|
|
207
|
+
const key = `${event.turnId}\u0000${toolCallId}`;
|
|
208
|
+
const startedAt = toolStarts.get(key);
|
|
209
|
+
if (startedAt !== undefined && at >= startedAt) {
|
|
210
|
+
turn.toolMs += at - startedAt;
|
|
211
|
+
}
|
|
212
|
+
toolStarts.delete(key);
|
|
213
|
+
// Next assistant step within the same turn starts after this tool.
|
|
214
|
+
turn.stepStart = at;
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (event.kind !== "message_end")
|
|
219
|
+
continue;
|
|
220
|
+
turn.steps += 1;
|
|
221
|
+
const recorded = stepTiming(event.data);
|
|
222
|
+
if (recorded?.llmMs !== undefined)
|
|
223
|
+
turn.llmMs += recorded.llmMs;
|
|
224
|
+
else if (at !== undefined &&
|
|
225
|
+
turn.stepStart !== undefined &&
|
|
226
|
+
at >= turn.stepStart) {
|
|
227
|
+
turn.llmMs += at - turn.stepStart;
|
|
228
|
+
}
|
|
229
|
+
if (recorded?.ttftMs !== undefined) {
|
|
230
|
+
turn.ttftMs = (turn.ttftMs ?? 0) + recorded.ttftMs;
|
|
231
|
+
}
|
|
232
|
+
if (recorded?.decodeMs !== undefined &&
|
|
233
|
+
recorded.decodeMs > 0 &&
|
|
234
|
+
recorded.outputTokens !== undefined) {
|
|
235
|
+
turn.decodeMs = (turn.decodeMs ?? 0) + recorded.decodeMs;
|
|
236
|
+
turn.decodeTokens = (turn.decodeTokens ?? 0) + recorded.outputTokens;
|
|
237
|
+
}
|
|
238
|
+
turn.stepStart = undefined;
|
|
239
|
+
}
|
|
240
|
+
return [...byTurn.values()].map((entry) => ({
|
|
241
|
+
turnId: entry.turnId,
|
|
242
|
+
steps: entry.steps,
|
|
243
|
+
llmMs: entry.llmMs,
|
|
244
|
+
toolMs: entry.toolMs,
|
|
245
|
+
toolCalls: entry.toolCalls,
|
|
246
|
+
...(entry.ttftMs !== undefined ? { ttftMs: entry.ttftMs } : {}),
|
|
247
|
+
...(entry.decodeMs !== undefined ? { decodeMs: entry.decodeMs } : {}),
|
|
248
|
+
...(entry.decodeTokens !== undefined
|
|
249
|
+
? { decodeTokens: entry.decodeTokens }
|
|
250
|
+
: {}),
|
|
251
|
+
usage: aggregateUsage(entry.samples),
|
|
252
|
+
...(entry.startedAt !== undefined ? { startedAt: entry.startedAt } : {}),
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
159
255
|
export function cacheHitPercent(usage) {
|
|
160
256
|
const billed = billedInputTokens(usage);
|
|
161
257
|
return billed > 0 ? Math.round((usage.cacheReadTokens / billed) * 100) : null;
|
|
@@ -40,14 +40,20 @@ function recordPath(appData, sessionId) {
|
|
|
40
40
|
}
|
|
41
41
|
export class ChatSessionStore {
|
|
42
42
|
appData;
|
|
43
|
+
options;
|
|
43
44
|
/**
|
|
44
45
|
* Per-session write lock. Each mutating op chains onto the previous one so
|
|
45
46
|
* concurrent appendMessage / recordToolInvocation calls do not lose updates
|
|
46
47
|
* via last-write-wins on the same JSON record.
|
|
47
48
|
*/
|
|
48
49
|
writeChains = new Map();
|
|
49
|
-
constructor(appData) {
|
|
50
|
+
constructor(appData, options = {}) {
|
|
50
51
|
this.appData = appData;
|
|
52
|
+
this.options = options;
|
|
53
|
+
}
|
|
54
|
+
async persist(record) {
|
|
55
|
+
await writeSecureJson(recordPath(this.appData, record.sessionId), record);
|
|
56
|
+
this.options.onRecordChange?.(record.sessionId, record);
|
|
51
57
|
}
|
|
52
58
|
/** Read + migrate legacy V1 records into the V2 in-memory shape. */
|
|
53
59
|
async get(sessionId) {
|
|
@@ -147,7 +153,7 @@ export class ChatSessionStore {
|
|
|
147
153
|
messages: [],
|
|
148
154
|
toolInvocations: [],
|
|
149
155
|
};
|
|
150
|
-
await
|
|
156
|
+
await this.persist(record);
|
|
151
157
|
return record;
|
|
152
158
|
});
|
|
153
159
|
}
|
|
@@ -169,7 +175,7 @@ export class ChatSessionStore {
|
|
|
169
175
|
messages: structuredClone(source.messages),
|
|
170
176
|
toolInvocations: structuredClone(source.toolInvocations),
|
|
171
177
|
};
|
|
172
|
-
await
|
|
178
|
+
await this.persist(record);
|
|
173
179
|
return record;
|
|
174
180
|
});
|
|
175
181
|
}
|
|
@@ -180,7 +186,7 @@ export class ChatSessionStore {
|
|
|
180
186
|
throw new Error(`chat session not found: ${sessionId}`);
|
|
181
187
|
record.title = title.trim().slice(0, 160) || undefined;
|
|
182
188
|
record.updatedAt = new Date().toISOString();
|
|
183
|
-
await
|
|
189
|
+
await this.persist(record);
|
|
184
190
|
return record;
|
|
185
191
|
});
|
|
186
192
|
}
|
|
@@ -195,7 +201,7 @@ export class ChatSessionStore {
|
|
|
195
201
|
return undefined;
|
|
196
202
|
record.title = cleaned;
|
|
197
203
|
record.updatedAt = new Date().toISOString();
|
|
198
|
-
await
|
|
204
|
+
await this.persist(record);
|
|
199
205
|
return record;
|
|
200
206
|
});
|
|
201
207
|
}
|
|
@@ -211,7 +217,7 @@ export class ChatSessionStore {
|
|
|
211
217
|
if (selection.thinkingLevel !== undefined)
|
|
212
218
|
record.thinkingLevel = selection.thinkingLevel;
|
|
213
219
|
record.updatedAt = new Date().toISOString();
|
|
214
|
-
await
|
|
220
|
+
await this.persist(record);
|
|
215
221
|
return record;
|
|
216
222
|
});
|
|
217
223
|
}
|
|
@@ -222,7 +228,7 @@ export class ChatSessionStore {
|
|
|
222
228
|
throw new Error(`chat session not found: ${sessionId}`);
|
|
223
229
|
record.state = state;
|
|
224
230
|
record.updatedAt = new Date().toISOString();
|
|
225
|
-
await
|
|
231
|
+
await this.persist(record);
|
|
226
232
|
return record;
|
|
227
233
|
});
|
|
228
234
|
}
|
|
@@ -232,6 +238,7 @@ export class ChatSessionStore {
|
|
|
232
238
|
if (!record)
|
|
233
239
|
return false;
|
|
234
240
|
await rm(recordPath(this.appData, sessionId), { force: true });
|
|
241
|
+
this.options.onRecordChange?.(sessionId, undefined);
|
|
235
242
|
return true;
|
|
236
243
|
});
|
|
237
244
|
}
|
|
@@ -285,7 +292,7 @@ export class ChatSessionStore {
|
|
|
285
292
|
record.deferredTurn = queue[0];
|
|
286
293
|
record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
|
|
287
294
|
record.updatedAt = now;
|
|
288
|
-
await
|
|
295
|
+
await this.persist(record);
|
|
289
296
|
return { ok: true, deferredTurn, idempotent: false };
|
|
290
297
|
});
|
|
291
298
|
}
|
|
@@ -313,7 +320,7 @@ export class ChatSessionStore {
|
|
|
313
320
|
record.deferredTurn = queue[0];
|
|
314
321
|
record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
|
|
315
322
|
record.updatedAt = next.updatedAt;
|
|
316
|
-
await
|
|
323
|
+
await this.persist(record);
|
|
317
324
|
return { ok: true, deferredTurn: next };
|
|
318
325
|
});
|
|
319
326
|
}
|
|
@@ -335,7 +342,7 @@ export class ChatSessionStore {
|
|
|
335
342
|
record.deferredTurn = queue[0];
|
|
336
343
|
record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
|
|
337
344
|
record.updatedAt = next.updatedAt;
|
|
338
|
-
await
|
|
345
|
+
await this.persist(record);
|
|
339
346
|
return next;
|
|
340
347
|
});
|
|
341
348
|
}
|
|
@@ -356,7 +363,7 @@ export class ChatSessionStore {
|
|
|
356
363
|
record.deferredTurn = queue[0];
|
|
357
364
|
record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
|
|
358
365
|
record.updatedAt = new Date().toISOString();
|
|
359
|
-
await
|
|
366
|
+
await this.persist(record);
|
|
360
367
|
return { ok: true, deferredTurn: existing };
|
|
361
368
|
});
|
|
362
369
|
}
|
|
@@ -373,7 +380,7 @@ export class ChatSessionStore {
|
|
|
373
380
|
record.deferredTurn = queue[0];
|
|
374
381
|
record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
|
|
375
382
|
record.updatedAt = new Date().toISOString();
|
|
376
|
-
await
|
|
383
|
+
await this.persist(record);
|
|
377
384
|
return { deferredTurn: existing, clearedRevision: existing.revision + 1 };
|
|
378
385
|
});
|
|
379
386
|
}
|
|
@@ -392,7 +399,7 @@ export class ChatSessionStore {
|
|
|
392
399
|
record.displayTitle = cleanChatTitle(stored.text);
|
|
393
400
|
}
|
|
394
401
|
record.updatedAt = stored.at;
|
|
395
|
-
await
|
|
402
|
+
await this.persist(record);
|
|
396
403
|
return stored;
|
|
397
404
|
});
|
|
398
405
|
}
|
|
@@ -403,7 +410,7 @@ export class ChatSessionStore {
|
|
|
403
410
|
return;
|
|
404
411
|
record.toolInvocations.push({ ...entry, at: new Date().toISOString() });
|
|
405
412
|
record.updatedAt = new Date().toISOString();
|
|
406
|
-
await
|
|
413
|
+
await this.persist(record);
|
|
407
414
|
});
|
|
408
415
|
}
|
|
409
416
|
}
|
|
@@ -501,10 +508,12 @@ export async function runChatTurn(options) {
|
|
|
501
508
|
},
|
|
502
509
|
};
|
|
503
510
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
511
|
+
if (options.persistUserMessage !== false) {
|
|
512
|
+
const userMessage = await store.appendMessage(sessionId, { role: "user", text });
|
|
513
|
+
const afterUserMessage = await store.get(sessionId);
|
|
514
|
+
if (afterUserMessage)
|
|
515
|
+
await options.onUserMessagePersisted?.(afterUserMessage, userMessage);
|
|
516
|
+
}
|
|
508
517
|
let turnError;
|
|
509
518
|
// Collect persistence promises so runChatTurn does not resolve before the
|
|
510
519
|
// chat history / audit trail is durably written (also makes the outcome
|
|
@@ -44,6 +44,13 @@ export const SHORTCUTS = [
|
|
|
44
44
|
];
|
|
45
45
|
/** Pi Web commands with real browser-equivalent behavior (UI-11). */
|
|
46
46
|
export const WEB_SLASH_COMMANDS = [
|
|
47
|
+
{
|
|
48
|
+
command: "/goal",
|
|
49
|
+
label: "持续目标",
|
|
50
|
+
description: "创建或编辑一个跨多个回合持续推进的目标",
|
|
51
|
+
action: "goal",
|
|
52
|
+
source: "web",
|
|
53
|
+
},
|
|
47
54
|
{
|
|
48
55
|
command: "/compact",
|
|
49
56
|
label: "压缩上下文",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSiftBridgeState, parseSiftBridgeConfig, compressProviderPayload, executeSiftRetrieve, housekeepSiftStash, createSiftRetrieveToolDefinition, SIFT_RETRIEVE_TOOL_NAME, SIFT_POLICY_VERSION, } from "../../../shared/pi-context-pressure/sift-bridge.js";
|
|
@@ -34,17 +34,21 @@ export function hydrateSessionThread(raw) {
|
|
|
34
34
|
for (const item of raw) {
|
|
35
35
|
const createdAt = Date.parse(item.at) || Date.now();
|
|
36
36
|
if (item.role === "user") {
|
|
37
|
-
turnId = `restored-${item.id}`;
|
|
37
|
+
turnId = item.turnId ?? `restored-${item.id}`;
|
|
38
38
|
pendingTools = [];
|
|
39
39
|
messages.push({
|
|
40
40
|
id: item.id,
|
|
41
41
|
role: "user",
|
|
42
42
|
text: item.text,
|
|
43
43
|
createdAt,
|
|
44
|
+
...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
|
|
45
|
+
...(item.turnId ? { turnId: item.turnId } : {}),
|
|
44
46
|
});
|
|
45
47
|
continue;
|
|
46
48
|
}
|
|
47
49
|
if (item.role === "assistant") {
|
|
50
|
+
if (item.turnId)
|
|
51
|
+
turnId = item.turnId;
|
|
48
52
|
if (!turnId)
|
|
49
53
|
turnId = `restored-${item.id}`;
|
|
50
54
|
const content = parseAssistantContent(item.content);
|
|
@@ -53,6 +57,8 @@ export function hydrateSessionThread(raw) {
|
|
|
53
57
|
role: "assistant",
|
|
54
58
|
text: item.text,
|
|
55
59
|
createdAt,
|
|
60
|
+
...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
|
|
61
|
+
...(item.turnId ? { turnId: item.turnId } : {}),
|
|
56
62
|
...(item.kind === "provider-error" && item.providerError
|
|
57
63
|
? { kind: item.kind, providerError: item.providerError }
|
|
58
64
|
: {}),
|
|
@@ -62,17 +68,31 @@ export function hydrateSessionThread(raw) {
|
|
|
62
68
|
continue;
|
|
63
69
|
}
|
|
64
70
|
if (item.role === "tool") {
|
|
71
|
+
if (item.turnId)
|
|
72
|
+
turnId = item.turnId;
|
|
65
73
|
if (!turnId)
|
|
66
74
|
turnId = `restored-tool-${item.id}`;
|
|
75
|
+
// Prefer event-ring timestamps (tool_call at → tool_result at) so
|
|
76
|
+
// refreshed sessions keep real elapsed durations instead of 0ms.
|
|
77
|
+
const startedAtMs = item.startedAt ? Date.parse(item.startedAt) : NaN;
|
|
78
|
+
const finishedAtMs = item.finishedAt ? Date.parse(item.finishedAt) : NaN;
|
|
67
79
|
const entry = {
|
|
68
80
|
id: item.toolCallId || item.id,
|
|
69
81
|
toolName: item.toolName || "tool",
|
|
70
82
|
status: item.isError === true ? "error" : "ok",
|
|
71
83
|
resultText: item.text,
|
|
72
84
|
result: item.text,
|
|
73
|
-
createdAt
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
createdAt: Number.isFinite(startedAtMs)
|
|
86
|
+
? startedAtMs
|
|
87
|
+
: createdAt,
|
|
88
|
+
finishedAt: Number.isFinite(finishedAtMs)
|
|
89
|
+
? finishedAtMs
|
|
90
|
+
: createdAt,
|
|
91
|
+
turnId: item.turnId ?? turnId,
|
|
92
|
+
...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
|
|
93
|
+
...(item.finishedSeq === undefined
|
|
94
|
+
? {}
|
|
95
|
+
: { finishedSeq: item.finishedSeq }),
|
|
76
96
|
...(item.operationId ? { operationId: item.operationId } : {}),
|
|
77
97
|
};
|
|
78
98
|
toolCalls.push(entry);
|
|
@@ -196,6 +216,8 @@ function fillProcessDetails(group) {
|
|
|
196
216
|
group.answer = finalAssistant;
|
|
197
217
|
return;
|
|
198
218
|
}
|
|
219
|
+
if (split.answerBlocks.length === 0)
|
|
220
|
+
return;
|
|
199
221
|
const answerText = joinAssistantText(split.answerBlocks);
|
|
200
222
|
group.answer = {
|
|
201
223
|
...finalAssistant,
|
|
@@ -207,7 +229,22 @@ function fillProcessDetails(group) {
|
|
|
207
229
|
* Build user → process → answer groups for the message timeline.
|
|
208
230
|
*/
|
|
209
231
|
export function buildTurnProcessGroups(input) {
|
|
210
|
-
const visible = input.messages
|
|
232
|
+
const visible = input.messages
|
|
233
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
234
|
+
.sort((a, b) => {
|
|
235
|
+
if (a.eventSeq !== undefined &&
|
|
236
|
+
b.eventSeq !== undefined &&
|
|
237
|
+
a.eventSeq !== b.eventSeq) {
|
|
238
|
+
return a.eventSeq - b.eventSeq;
|
|
239
|
+
}
|
|
240
|
+
if (a.createdAt !== b.createdAt)
|
|
241
|
+
return a.createdAt - b.createdAt;
|
|
242
|
+
if (a.eventSeq !== undefined && b.eventSeq === undefined)
|
|
243
|
+
return 1;
|
|
244
|
+
if (a.eventSeq === undefined && b.eventSeq !== undefined)
|
|
245
|
+
return -1;
|
|
246
|
+
return 0;
|
|
247
|
+
});
|
|
211
248
|
const groups = [];
|
|
212
249
|
let current = null;
|
|
213
250
|
const startGroup = (key) => {
|
|
@@ -176,8 +176,26 @@ export function deriveEligibilityFactsFromDagSpec(input) {
|
|
|
176
176
|
}));
|
|
177
177
|
const allWriteSetEntries = writers.flatMap((writer) => writer.writeSet);
|
|
178
178
|
const broadWriteSetRisk = allWriteSetEntries.some(isBroadWriteSetEntry);
|
|
179
|
-
const
|
|
180
|
-
|
|
179
|
+
const taskForbidden = input.forbiddenPaths.map(normalizePathEntry);
|
|
180
|
+
const forbiddenOverlapRisk = tasks.some((task) => stringList(task.writeSet).some((entry) => {
|
|
181
|
+
const normalizedEntry = normalizePathEntry(entry);
|
|
182
|
+
return stringList(task.forbiddenPaths).some((forbidden) => {
|
|
183
|
+
const normalizedForbidden = normalizePathEntry(forbidden);
|
|
184
|
+
const isTaskGlobal = taskForbidden.includes(normalizedForbidden);
|
|
185
|
+
if (isTaskGlobal) {
|
|
186
|
+
// Task-level forbidden paths are hard global boundaries. A broad
|
|
187
|
+
// writer that could include one is not autonomously eligible.
|
|
188
|
+
return (pathMatchesPattern(normalizedEntry, normalizedForbidden) ||
|
|
189
|
+
pathMatchesPattern(normalizedForbidden, normalizedEntry));
|
|
190
|
+
}
|
|
191
|
+
// Node-local forbidden paths may intentionally carve a narrower
|
|
192
|
+
// child (for example md/README.md) out of a broader writer
|
|
193
|
+
// writeSet. That exclusion is enforced by the runtime write guard.
|
|
194
|
+
// It is a conflict only when the writer entry itself is wholly
|
|
195
|
+
// inside the forbidden boundary.
|
|
196
|
+
return pathMatchesPattern(normalizedEntry, normalizedForbidden);
|
|
197
|
+
});
|
|
198
|
+
}));
|
|
181
199
|
const hasStructuredVerification = tasks.some((task) => {
|
|
182
200
|
if (task.executor !== "shell" || !task.shell)
|
|
183
201
|
return false;
|
|
@@ -1148,14 +1148,14 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1148
1148
|
if (usesUnifiedSurface) {
|
|
1149
1149
|
const inspectPath = target
|
|
1150
1150
|
? buildInspectDeepLink(consoleOrigin, target).slice(consoleOrigin.length)
|
|
1151
|
-
: "
|
|
1151
|
+
: "/#/inspect";
|
|
1152
1152
|
return {
|
|
1153
1153
|
kind: "sync",
|
|
1154
1154
|
status: 200,
|
|
1155
1155
|
body: operatorSucceeded("observeLink", {
|
|
1156
1156
|
url: target
|
|
1157
1157
|
? buildInspectDeepLink(consoleOrigin, target)
|
|
1158
|
-
: `${consoleOrigin.replace(/\/+$/, "")}
|
|
1158
|
+
: `${consoleOrigin.replace(/\/+$/, "")}/#/inspect`,
|
|
1159
1159
|
inspectPath,
|
|
1160
1160
|
status: "same-origin",
|
|
1161
1161
|
requiredCapability: requiredCapability ?? null,
|
|
@@ -1641,9 +1641,10 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1641
1641
|
broadWriteSetRisk: typeof reviewPacket.broadWriteSetRisk === "boolean"
|
|
1642
1642
|
? reviewPacket.broadWriteSetRisk
|
|
1643
1643
|
: derivedFacts.broadWriteSetRisk,
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1644
|
+
// Always use the staged DAG + live task boundary derivation here.
|
|
1645
|
+
// The generation review packet lacks task-boundary scope and can
|
|
1646
|
+
// mistake a narrower node-local exclusion for a global overlap.
|
|
1647
|
+
forbiddenOverlapRisk: derivedFacts.forbiddenOverlapRisk,
|
|
1647
1648
|
hasStructuredVerification: packetShellVerification,
|
|
1648
1649
|
};
|
|
1649
1650
|
const assessment = assessAutonomousExecutionEligibility(eligibility);
|
|
@@ -1816,6 +1817,18 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1816
1817
|
const taskIdError = taskIdInputError(taskId);
|
|
1817
1818
|
if (taskIdError)
|
|
1818
1819
|
return invalid(action, taskIdError);
|
|
1820
|
+
// Optional write-set gate approval: the token is the server-issued
|
|
1821
|
+
// `<gate-id>:sha256:<hex>` that `task status` reports in
|
|
1822
|
+
// gate.approveCommand. Forwarded as --approve-gate so an operator can
|
|
1823
|
+
// complete the human approval through the Console operation path
|
|
1824
|
+
// instead of dropping to a raw terminal. This dispatch already sits
|
|
1825
|
+
// behind the Console mutation gate (boot cookie + confirmation
|
|
1826
|
+
// header), so it carries the same authority as typing the CLI flag.
|
|
1827
|
+
const approveGateRaw = str(p.approveGate);
|
|
1828
|
+
if (approveGateRaw &&
|
|
1829
|
+
!/^[A-Za-z0-9._-]+:sha256:[0-9a-f]{64}$/.test(approveGateRaw)) {
|
|
1830
|
+
return invalid(action, "approveGate must look like <gate-id>:sha256:<64-hex> (copy the token from task status gate.approveCommand)");
|
|
1831
|
+
}
|
|
1819
1832
|
const args = ["task", "advance", taskId];
|
|
1820
1833
|
const title = str(p.title);
|
|
1821
1834
|
if (title)
|
|
@@ -1829,6 +1842,8 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1829
1842
|
const profile = str(p.profile);
|
|
1830
1843
|
if (profile)
|
|
1831
1844
|
args.push("--profile", profile);
|
|
1845
|
+
if (approveGateRaw)
|
|
1846
|
+
args.push("--approve-gate", approveGateRaw);
|
|
1832
1847
|
appendEngineeringCliArgs(args, parseEngineeringBoundaryFromParams(p));
|
|
1833
1848
|
args.push("--json");
|
|
1834
1849
|
return acceptOperation(ctx, {
|
|
@@ -27,7 +27,11 @@ export function encodeInspectHash(selection) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
export function buildInspectPath(selection) {
|
|
30
|
-
|
|
30
|
+
if (!selection)
|
|
31
|
+
return "/#/inspect";
|
|
32
|
+
const hash = encodeInspectHash(selection);
|
|
33
|
+
const internal = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
34
|
+
return `/#/inspect${internal}`;
|
|
31
35
|
}
|
|
32
36
|
export function buildInspectUrl(origin, selection) {
|
|
33
37
|
const base = origin.trim().replace(/\/+$/, "");
|
|
@@ -99,7 +103,7 @@ export function selectionFromObserveLinkTarget(target) {
|
|
|
99
103
|
export function normalizeLegacyConsoleDagHash(pathnameAndHash) {
|
|
100
104
|
const [pathname, hash = ""] = pathnameAndHash.split("#", 2);
|
|
101
105
|
if ((pathname === "/" || pathname === "") && /^\/dag\/[^/]+$/.test(hash)) {
|
|
102
|
-
return
|
|
106
|
+
return `/#/inspect${hash}`;
|
|
103
107
|
}
|
|
104
108
|
return pathnameAndHash;
|
|
105
109
|
}
|