@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,72 @@
|
|
|
1
|
+
export function classifyGoalRoundAdmission(input) {
|
|
2
|
+
if (input.phase !== "active")
|
|
3
|
+
return { action: "idle", reason: "not-active" };
|
|
4
|
+
if (!input.armed)
|
|
5
|
+
return { action: "idle", reason: "disarmed" };
|
|
6
|
+
if (input.round >= input.maxRounds)
|
|
7
|
+
return { action: "idle", reason: "round-limit" };
|
|
8
|
+
if (input.hasQueuedUserTurn)
|
|
9
|
+
return { action: "wait", reason: "user-queue" };
|
|
10
|
+
if (input.hasActiveTurn || input.runtimeBusy)
|
|
11
|
+
return { action: "wait", reason: "turn-busy" };
|
|
12
|
+
if (input.operationBusy)
|
|
13
|
+
return { action: "wait", reason: "operation-busy" };
|
|
14
|
+
return { action: "ready" };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Small process-local wake coordinator. Durable Goal phase lives in the event
|
|
18
|
+
* stream; timers deliberately do not. A Console restart therefore cannot
|
|
19
|
+
* resume work until a human explicitly arms the Goal again.
|
|
20
|
+
*/
|
|
21
|
+
export class GoalRoundDriver {
|
|
22
|
+
drive;
|
|
23
|
+
waitMs;
|
|
24
|
+
timers = new Map();
|
|
25
|
+
running = new Set();
|
|
26
|
+
disposed = false;
|
|
27
|
+
constructor(drive, waitMs = 1_500) {
|
|
28
|
+
this.drive = drive;
|
|
29
|
+
this.waitMs = waitMs;
|
|
30
|
+
}
|
|
31
|
+
wake(sessionId, delayMs = 0) {
|
|
32
|
+
if (this.disposed || this.timers.has(sessionId) || this.running.has(sessionId))
|
|
33
|
+
return;
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
this.timers.delete(sessionId);
|
|
36
|
+
void this.run(sessionId);
|
|
37
|
+
}, Math.max(0, delayMs));
|
|
38
|
+
timer.unref?.();
|
|
39
|
+
this.timers.set(sessionId, timer);
|
|
40
|
+
}
|
|
41
|
+
cancel(sessionId) {
|
|
42
|
+
const timer = this.timers.get(sessionId);
|
|
43
|
+
if (timer)
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
this.timers.delete(sessionId);
|
|
46
|
+
}
|
|
47
|
+
dispose() {
|
|
48
|
+
this.disposed = true;
|
|
49
|
+
for (const timer of this.timers.values())
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
this.timers.clear();
|
|
52
|
+
}
|
|
53
|
+
async run(sessionId) {
|
|
54
|
+
if (this.disposed || this.running.has(sessionId))
|
|
55
|
+
return;
|
|
56
|
+
this.running.add(sessionId);
|
|
57
|
+
let retry = false;
|
|
58
|
+
try {
|
|
59
|
+
const result = await this.drive(sessionId);
|
|
60
|
+
retry = result === "wait";
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// The route-level driver disarms on unsafe failures. A thrown observer or
|
|
64
|
+
// transient read must not create a hot retry loop.
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
this.running.delete(sessionId);
|
|
68
|
+
if (retry)
|
|
69
|
+
this.wake(sessionId, this.waitMs);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export const DEFAULT_GOAL_MAX_ROUNDS = 32;
|
|
3
|
+
function cloneGoal(goal) {
|
|
4
|
+
return structuredClone(goal);
|
|
5
|
+
}
|
|
6
|
+
export function normalizeGoalObjective(value) {
|
|
7
|
+
if (typeof value !== "string")
|
|
8
|
+
throw new Error("objective must be a string");
|
|
9
|
+
const objective = value.replace(/\r\n/g, "\n").trim();
|
|
10
|
+
if (!objective)
|
|
11
|
+
throw new Error("objective must not be empty");
|
|
12
|
+
if (objective.length > 8_000)
|
|
13
|
+
throw new Error("objective exceeds 8000 characters");
|
|
14
|
+
return objective;
|
|
15
|
+
}
|
|
16
|
+
export function isExplicitGoalCreationRequest(text) {
|
|
17
|
+
const normalized = text.trim().toLowerCase();
|
|
18
|
+
return (/^\/goal(?:\s|$)/i.test(normalized) ||
|
|
19
|
+
/(创建|新建|启动|开启|设定|设置).{0,10}(持续目标|goal)/i.test(normalized) ||
|
|
20
|
+
/(create|start|set up|enable).{0,16}\bgoal\b/i.test(normalized));
|
|
21
|
+
}
|
|
22
|
+
export function parseGoalSnapshot(value) {
|
|
23
|
+
if (!value || typeof value !== "object")
|
|
24
|
+
return undefined;
|
|
25
|
+
const item = value;
|
|
26
|
+
if (item.schemaVersion !== 1 ||
|
|
27
|
+
typeof item.goalId !== "string" ||
|
|
28
|
+
!item.goalId ||
|
|
29
|
+
!Number.isSafeInteger(item.revision) ||
|
|
30
|
+
(item.revision ?? 0) < 1 ||
|
|
31
|
+
typeof item.objective !== "string" ||
|
|
32
|
+
!item.objective.trim() ||
|
|
33
|
+
!["active", "paused", "blocked", "complete"].includes(item.phase) ||
|
|
34
|
+
!Number.isSafeInteger(item.round) ||
|
|
35
|
+
(item.round ?? -1) < 0 ||
|
|
36
|
+
!Number.isSafeInteger(item.maxRounds) ||
|
|
37
|
+
(item.maxRounds ?? 0) < 1 ||
|
|
38
|
+
typeof item.createdAt !== "string" ||
|
|
39
|
+
typeof item.updatedAt !== "string") {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
schemaVersion: 1,
|
|
44
|
+
goalId: item.goalId,
|
|
45
|
+
revision: item.revision,
|
|
46
|
+
objective: item.objective.trim(),
|
|
47
|
+
phase: item.phase,
|
|
48
|
+
round: item.round,
|
|
49
|
+
maxRounds: item.maxRounds,
|
|
50
|
+
createdAt: item.createdAt,
|
|
51
|
+
updatedAt: item.updatedAt,
|
|
52
|
+
...(typeof item.lastTurnId === "string" && item.lastTurnId
|
|
53
|
+
? { lastTurnId: item.lastTurnId }
|
|
54
|
+
: {}),
|
|
55
|
+
...(typeof item.blocker === "string" && item.blocker.trim()
|
|
56
|
+
? { blocker: item.blocker.trim() }
|
|
57
|
+
: {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function projectCurrentGoal(events) {
|
|
61
|
+
let current = null;
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
if (event.kind === "goal_cleared")
|
|
64
|
+
current = null;
|
|
65
|
+
if (event.kind === "goal_changed") {
|
|
66
|
+
const parsed = parseGoalSnapshot(event.data.goal);
|
|
67
|
+
if (parsed)
|
|
68
|
+
current = parsed;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return current ? cloneGoal(current) : null;
|
|
72
|
+
}
|
|
73
|
+
export function buildGoalCompactionInstructions(rawInstructions, goal) {
|
|
74
|
+
const reminder = goal
|
|
75
|
+
? [
|
|
76
|
+
"Preserve this durable Goal snapshot in the compaction summary. Do not mark it complete or change its phase unless the Goal tool already did so:",
|
|
77
|
+
JSON.stringify(goal),
|
|
78
|
+
].join("\n")
|
|
79
|
+
: undefined;
|
|
80
|
+
return [rawInstructions?.trim(), reminder]
|
|
81
|
+
.filter((item) => Boolean(item))
|
|
82
|
+
.join("\n\n") || undefined;
|
|
83
|
+
}
|
|
84
|
+
export function buildGoalContinuationContext(goal) {
|
|
85
|
+
if (!goal)
|
|
86
|
+
return undefined;
|
|
87
|
+
return [
|
|
88
|
+
"<current_goal>",
|
|
89
|
+
"This is the durable cross-turn Goal for this session. Continue working toward its objective without broadening user authorization.",
|
|
90
|
+
JSON.stringify(goal),
|
|
91
|
+
"Use get_goal to refresh it. Use update_goal with status=complete only when the objective is genuinely achieved and no required work remains; use blocked only when safe progress cannot continue.",
|
|
92
|
+
"</current_goal>",
|
|
93
|
+
].join("\n");
|
|
94
|
+
}
|
|
95
|
+
export class GoalRegistry {
|
|
96
|
+
events;
|
|
97
|
+
armed = new Set();
|
|
98
|
+
modelCreateGrants = new Set();
|
|
99
|
+
constructor(events) {
|
|
100
|
+
this.events = events;
|
|
101
|
+
}
|
|
102
|
+
get(sessionId) {
|
|
103
|
+
const goal = projectCurrentGoal(this.events.snapshot(sessionId));
|
|
104
|
+
return goal ? { ...goal, armed: this.armed.has(sessionId) } : null;
|
|
105
|
+
}
|
|
106
|
+
startTurn(sessionId, userText) {
|
|
107
|
+
if (isExplicitGoalCreationRequest(userText))
|
|
108
|
+
this.modelCreateGrants.add(sessionId);
|
|
109
|
+
else
|
|
110
|
+
this.modelCreateGrants.delete(sessionId);
|
|
111
|
+
}
|
|
112
|
+
consumeModelCreateGrant(sessionId) {
|
|
113
|
+
const granted = this.modelCreateGrants.has(sessionId);
|
|
114
|
+
this.modelCreateGrants.delete(sessionId);
|
|
115
|
+
return granted;
|
|
116
|
+
}
|
|
117
|
+
create(input) {
|
|
118
|
+
let objective;
|
|
119
|
+
try {
|
|
120
|
+
objective = normalizeGoalObjective(input.objective);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
code: "GOAL_INVALID_INPUT",
|
|
126
|
+
message: error instanceof Error ? error.message : String(error),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const existing = this.get(input.sessionId);
|
|
130
|
+
if (existing && existing.phase !== "complete") {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
code: "GOAL_ALREADY_EXISTS",
|
|
134
|
+
message: "an unfinished Goal already exists for this session",
|
|
135
|
+
goal: existing,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const maxRounds = Number.isSafeInteger(input.maxRounds)
|
|
139
|
+
? Math.max(1, Math.min(128, input.maxRounds))
|
|
140
|
+
: DEFAULT_GOAL_MAX_ROUNDS;
|
|
141
|
+
const now = new Date().toISOString();
|
|
142
|
+
const goal = {
|
|
143
|
+
schemaVersion: 1,
|
|
144
|
+
goalId: randomUUID(),
|
|
145
|
+
revision: 1,
|
|
146
|
+
objective,
|
|
147
|
+
phase: "active",
|
|
148
|
+
round: 0,
|
|
149
|
+
maxRounds,
|
|
150
|
+
createdAt: now,
|
|
151
|
+
updatedAt: now,
|
|
152
|
+
};
|
|
153
|
+
this.events.append(input.sessionId, input.turnId ?? `${input.sessionId}:goal`, { kind: "goal_changed", data: { goal } });
|
|
154
|
+
if (input.arm !== false)
|
|
155
|
+
this.armed.add(input.sessionId);
|
|
156
|
+
return { ok: true, goal: { ...goal, armed: this.armed.has(input.sessionId) } };
|
|
157
|
+
}
|
|
158
|
+
update(input) {
|
|
159
|
+
const current = this.get(input.sessionId);
|
|
160
|
+
if (!current)
|
|
161
|
+
return { ok: false, code: "GOAL_NOT_FOUND", message: "Goal not found" };
|
|
162
|
+
if (current.goalId !== input.goalId)
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
code: "GOAL_ID_CONFLICT",
|
|
166
|
+
message: "Goal id does not match the current session Goal",
|
|
167
|
+
goal: current,
|
|
168
|
+
};
|
|
169
|
+
if (current.revision !== input.expectedRevision)
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
code: "GOAL_REVISION_CONFLICT",
|
|
173
|
+
message: `Goal revision changed: expected ${input.expectedRevision}, current ${current.revision}`,
|
|
174
|
+
goal: current,
|
|
175
|
+
};
|
|
176
|
+
let objective = current.objective;
|
|
177
|
+
if (input.objective !== undefined) {
|
|
178
|
+
try {
|
|
179
|
+
objective = normalizeGoalObjective(input.objective);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
return {
|
|
183
|
+
ok: false,
|
|
184
|
+
code: "GOAL_INVALID_INPUT",
|
|
185
|
+
message: error instanceof Error ? error.message : String(error),
|
|
186
|
+
goal: current,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const phase = input.phase ?? current.phase;
|
|
191
|
+
const blocker = typeof input.blocker === "string" ? input.blocker.trim().slice(0, 2_000) : "";
|
|
192
|
+
if (phase === "blocked" && !blocker && !current.blocker) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
code: "GOAL_INVALID_INPUT",
|
|
196
|
+
message: "blocked Goal requires a concrete blocker",
|
|
197
|
+
goal: current,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const next = {
|
|
201
|
+
...current,
|
|
202
|
+
revision: current.revision + 1,
|
|
203
|
+
objective,
|
|
204
|
+
phase,
|
|
205
|
+
updatedAt: new Date().toISOString(),
|
|
206
|
+
...(phase === "blocked" && blocker
|
|
207
|
+
? { blocker }
|
|
208
|
+
: current.blocker && phase === "blocked"
|
|
209
|
+
? { blocker: current.blocker }
|
|
210
|
+
: {}),
|
|
211
|
+
};
|
|
212
|
+
delete next.armed;
|
|
213
|
+
if (phase !== "blocked")
|
|
214
|
+
delete next.blocker;
|
|
215
|
+
this.events.append(input.sessionId, input.turnId ?? current.lastTurnId ?? `${input.sessionId}:goal`, { kind: "goal_changed", data: { goal: next } });
|
|
216
|
+
if (phase !== "active" || input.arm === false)
|
|
217
|
+
this.armed.delete(input.sessionId);
|
|
218
|
+
else if (input.arm === true)
|
|
219
|
+
this.armed.add(input.sessionId);
|
|
220
|
+
return { ok: true, goal: { ...next, armed: this.armed.has(input.sessionId) } };
|
|
221
|
+
}
|
|
222
|
+
beginRound(input) {
|
|
223
|
+
const current = this.get(input.sessionId);
|
|
224
|
+
if (!current)
|
|
225
|
+
return { ok: false, code: "GOAL_NOT_FOUND", message: "Goal not found" };
|
|
226
|
+
if (current.round >= current.maxRounds) {
|
|
227
|
+
this.armed.delete(input.sessionId);
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
code: "GOAL_ROUND_LIMIT",
|
|
231
|
+
message: `Goal reached its ${current.maxRounds}-round safety limit`,
|
|
232
|
+
goal: { ...current, armed: false },
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (current.goalId !== input.goalId)
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
code: "GOAL_ID_CONFLICT",
|
|
239
|
+
message: "Goal id changed before the next round started",
|
|
240
|
+
goal: current,
|
|
241
|
+
};
|
|
242
|
+
if (current.revision !== input.expectedRevision)
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
code: "GOAL_REVISION_CONFLICT",
|
|
246
|
+
message: `Goal revision changed: expected ${input.expectedRevision}, current ${current.revision}`,
|
|
247
|
+
goal: current,
|
|
248
|
+
};
|
|
249
|
+
const next = {
|
|
250
|
+
...current,
|
|
251
|
+
revision: current.revision + 1,
|
|
252
|
+
round: current.round + 1,
|
|
253
|
+
lastTurnId: input.turnId,
|
|
254
|
+
updatedAt: new Date().toISOString(),
|
|
255
|
+
};
|
|
256
|
+
delete next.armed;
|
|
257
|
+
this.events.append(input.sessionId, input.turnId, {
|
|
258
|
+
kind: "goal_changed",
|
|
259
|
+
data: { goal: next },
|
|
260
|
+
});
|
|
261
|
+
return { ok: true, goal: { ...next, armed: this.armed.has(input.sessionId) } };
|
|
262
|
+
}
|
|
263
|
+
clear(input) {
|
|
264
|
+
const current = this.get(input.sessionId);
|
|
265
|
+
if (!current)
|
|
266
|
+
return { ok: false, code: "GOAL_NOT_FOUND", message: "Goal not found" };
|
|
267
|
+
if (current.goalId !== input.goalId || current.revision !== input.expectedRevision) {
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
code: current.goalId !== input.goalId
|
|
271
|
+
? "GOAL_ID_CONFLICT"
|
|
272
|
+
: "GOAL_REVISION_CONFLICT",
|
|
273
|
+
message: "Goal changed before it could be cleared",
|
|
274
|
+
goal: current,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
this.armed.delete(input.sessionId);
|
|
278
|
+
this.events.append(input.sessionId, input.turnId ?? current.lastTurnId ?? `${input.sessionId}:goal`, { kind: "goal_cleared", data: {} });
|
|
279
|
+
return { ok: true, goal: { ...current, armed: false } };
|
|
280
|
+
}
|
|
281
|
+
arm(sessionId) {
|
|
282
|
+
const goal = this.get(sessionId);
|
|
283
|
+
if (!goal || goal.phase !== "active" || goal.round >= goal.maxRounds)
|
|
284
|
+
return goal;
|
|
285
|
+
this.armed.add(sessionId);
|
|
286
|
+
return { ...goal, armed: true };
|
|
287
|
+
}
|
|
288
|
+
disarm(sessionId) {
|
|
289
|
+
this.armed.delete(sessionId);
|
|
290
|
+
const goal = this.get(sessionId);
|
|
291
|
+
return goal ? { ...goal, armed: false } : null;
|
|
292
|
+
}
|
|
293
|
+
isArmed(sessionId) {
|
|
294
|
+
return this.armed.has(sessionId);
|
|
295
|
+
}
|
|
296
|
+
clearRuntime(sessionId) {
|
|
297
|
+
this.armed.delete(sessionId);
|
|
298
|
+
this.modelCreateGrants.delete(sessionId);
|
|
299
|
+
}
|
|
300
|
+
}
|