loopforge 2.11.0 → 3.0.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/README.md +145 -128
- package/dist/canonical-event-writer.d.ts +20 -0
- package/dist/canonical-event-writer.d.ts.map +1 -0
- package/dist/canonical-event-writer.js +131 -0
- package/dist/canonical-event-writer.js.map +1 -0
- package/dist/canonical-state.d.ts +20 -16
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +170 -61
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -10
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-governance.d.ts +48 -0
- package/dist/cognitive-governance.d.ts.map +1 -0
- package/dist/cognitive-governance.js +226 -0
- package/dist/cognitive-governance.js.map +1 -0
- package/dist/compiler-subgoals.d.ts +7 -0
- package/dist/compiler-subgoals.d.ts.map +1 -0
- package/dist/compiler-subgoals.js +93 -0
- package/dist/compiler-subgoals.js.map +1 -0
- package/dist/derive-id.d.ts +12 -0
- package/dist/derive-id.d.ts.map +1 -0
- package/dist/derive-id.js +31 -0
- package/dist/derive-id.js.map +1 -0
- package/dist/evidence-provider.d.ts +23 -21
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +34 -74
- package/dist/evidence-provider.js.map +1 -1
- package/dist/generate-schema.js +90 -2
- package/dist/generate-schema.js.map +1 -1
- package/dist/index.d.ts +22 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -16
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +1 -1
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +250 -337
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-extras-parser.d.ts +2 -2
- package/dist/loop-extras-parser.d.ts.map +1 -1
- package/dist/loop-extras-parser.js +2 -2
- package/dist/loop-extras-parser.js.map +1 -1
- package/dist/loop-store.d.ts +20 -58
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +232 -309
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/server.d.ts +3 -3
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +83 -13
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session-projection.d.ts +5 -0
- package/dist/mcp/session-projection.d.ts.map +1 -0
- package/dist/mcp/session-projection.js +18 -0
- package/dist/mcp/session-projection.js.map +1 -0
- package/dist/mcp/session-queue.d.ts +3 -0
- package/dist/mcp/session-queue.d.ts.map +1 -0
- package/dist/mcp/session-queue.js +18 -0
- package/dist/mcp/session-queue.js.map +1 -0
- package/dist/mcp/session-types.d.ts +72 -0
- package/dist/mcp/session-types.d.ts.map +1 -0
- package/dist/mcp/session-types.js +2 -0
- package/dist/mcp/session-types.js.map +1 -0
- package/dist/mcp/session.d.ts +49 -93
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +544 -245
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +26 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +399 -274
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +17 -1
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +164 -1
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +34 -18
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +113 -18
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +5 -3
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +126 -77
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-policy.d.ts +8 -1
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +26 -8
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +232 -192
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +15 -100
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts +11 -3
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +51 -14
- package/dist/replay.js.map +1 -1
- package/dist/round-commit-store.d.ts +12 -0
- package/dist/round-commit-store.d.ts.map +1 -0
- package/dist/round-commit-store.js +13 -0
- package/dist/round-commit-store.js.map +1 -0
- package/dist/round-coordinator.d.ts +25 -18
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +83 -71
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +14 -10
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +9 -13
- package/dist/round-driver.js.map +1 -1
- package/dist/round-gate.d.ts +31 -0
- package/dist/round-gate.d.ts.map +1 -0
- package/dist/round-gate.js +259 -0
- package/dist/round-gate.js.map +1 -0
- package/dist/round-recovery.d.ts +17 -0
- package/dist/round-recovery.d.ts.map +1 -0
- package/dist/round-recovery.js +117 -0
- package/dist/round-recovery.js.map +1 -0
- package/dist/round-report.d.ts +16 -0
- package/dist/round-report.d.ts.map +1 -0
- package/dist/round-report.js +401 -0
- package/dist/round-report.js.map +1 -0
- package/dist/round-transaction.d.ts +19 -12
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +102 -88
- package/dist/round-transaction.js.map +1 -1
- package/dist/runtime-services.d.ts +59 -0
- package/dist/runtime-services.d.ts.map +1 -0
- package/dist/runtime-services.js +460 -0
- package/dist/runtime-services.js.map +1 -0
- package/dist/storage.d.ts +3 -9
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +6 -17
- package/dist/storage.js.map +1 -1
- package/dist/token-utils.d.ts +9 -8
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +50 -10
- package/dist/token-utils.js.map +1 -1
- package/dist/vault-format.d.ts +4 -0
- package/dist/vault-format.d.ts.map +1 -0
- package/dist/vault-format.js +4 -0
- package/dist/vault-format.js.map +1 -0
- package/dist/vault-types.d.ts +24 -0
- package/dist/vault-types.d.ts.map +1 -0
- package/dist/vault-types.js +2 -0
- package/dist/vault-types.js.map +1 -0
- package/loop_policy.json +10 -12
- package/loopforge-policy.json +340 -0
- package/package.json +12 -6
- package/skills/perception/SKILL.md +199 -164
- package/dist/backends/interface.d.ts +0 -39
- package/dist/backends/interface.d.ts.map +0 -1
- package/dist/backends/interface.js +0 -6
- package/dist/backends/interface.js.map +0 -1
- package/dist/enforcement-gate.d.ts +0 -71
- package/dist/enforcement-gate.d.ts.map +0 -1
- package/dist/enforcement-gate.js +0 -744
- package/dist/enforcement-gate.js.map +0 -1
- package/dist/engine.d.ts +0 -59
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -596
- package/dist/engine.js.map +0 -1
- package/dist/self-eval.d.ts +0 -23
- package/dist/self-eval.d.ts.map +0 -1
- package/dist/self-eval.js +0 -181
- package/dist/self-eval.js.map +0 -1
- package/dist/verification-gate.d.ts +0 -56
- package/dist/verification-gate.d.ts.map +0 -1
- package/dist/verification-gate.js +0 -563
- package/dist/verification-gate.js.map +0 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -1,14 +1,106 @@
|
|
|
1
1
|
/** LoopForge MCP — Tool definitions and handlers.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
3
|
+
* Public tools for round execution, recovery, gates, and audit.
|
|
4
4
|
* Each handler receives SessionManager + parsed input, returns the output object.
|
|
5
5
|
*/
|
|
6
|
-
import { buildSelfEvaluation } from "../engine.js";
|
|
7
|
-
import { getPolicyMetrics } from "../policy-metrics.js";
|
|
8
6
|
import { isRecord } from "../token-utils.js";
|
|
7
|
+
import { parseRoundReport } from "../round-report.js";
|
|
8
|
+
import { getPolicy } from "../policy.js";
|
|
9
9
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
10
10
|
// Tool schemas (MCP JSON Schema format)
|
|
11
11
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
const GATE_ACTION_SCHEMA = {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
description: { type: "string" },
|
|
16
|
+
scope: { type: "array", items: { type: "string" } },
|
|
17
|
+
effects: { type: "array", items: { type: "string", enum: ["workspace_write", "production", "credentials", "data_migration", "public_api", "publish", "payment", "external_communication", "network"] } },
|
|
18
|
+
reversibility: { type: "string", enum: ["reversible", "recoverable", "irreversible", "unknown"] },
|
|
19
|
+
authorization: { type: "string", enum: ["agent_allowed", "user_required", "unknown"] },
|
|
20
|
+
},
|
|
21
|
+
required: ["description", "scope", "effects", "reversibility", "authorization"],
|
|
22
|
+
};
|
|
23
|
+
const ROUND_REPORT_SCHEMA = {
|
|
24
|
+
type: "object",
|
|
25
|
+
required: ["format", "outcome", "continuation", "summary", "violations"],
|
|
26
|
+
properties: {
|
|
27
|
+
format: { type: "string", const: "loopforge.round-report" },
|
|
28
|
+
outcome: { type: "string", enum: ["success", "partial", "failed", "blocked"] },
|
|
29
|
+
continuation: { type: "string", enum: ["continue", "stop"] },
|
|
30
|
+
summary: { type: "string" },
|
|
31
|
+
violations: { type: "array", items: { type: "string" } },
|
|
32
|
+
agentIntent: { type: "string" },
|
|
33
|
+
driftClarification: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
explanation: { type: "string", minLength: 1, maxLength: 2000 },
|
|
37
|
+
anchors: { type: "array", maxItems: 8, items: { type: "string", maxLength: 240 } },
|
|
38
|
+
},
|
|
39
|
+
required: ["explanation", "anchors"],
|
|
40
|
+
},
|
|
41
|
+
evidence: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
files: { type: "array", items: { type: "string" } },
|
|
45
|
+
checks: { type: "array", items: { type: "object", properties: {
|
|
46
|
+
name: { type: "string" }, status: { type: "string", enum: ["passed", "failed", "not_run"] },
|
|
47
|
+
summary: { type: "string" }, counts: { type: "object", properties: {
|
|
48
|
+
passed: { type: "integer", minimum: 0 }, failed: { type: "integer", minimum: 0 }, skipped: { type: "integer", minimum: 0 },
|
|
49
|
+
} },
|
|
50
|
+
}, required: ["name", "status"] } },
|
|
51
|
+
claims: { type: "array", items: { type: "object", properties: {
|
|
52
|
+
id: { type: "string", pattern: "^claim-[a-f0-9]{12}$" }, targetId: { type: "string" }, evidenceRefs: { type: "array", items: { type: "string" } },
|
|
53
|
+
}, required: ["id", "targetId", "evidenceRefs"] } },
|
|
54
|
+
provenance: {
|
|
55
|
+
type: "object",
|
|
56
|
+
description: "Runtime-owned evidence provenance keyed by evidence reference.",
|
|
57
|
+
additionalProperties: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
source: { type: "string", enum: ["agent", "command", "provider", "verification"] },
|
|
61
|
+
observedAt: { type: "string" },
|
|
62
|
+
status: { type: "string", enum: ["verified", "claimed", "contradicted", "unavailable"] },
|
|
63
|
+
detail: { type: "string" },
|
|
64
|
+
},
|
|
65
|
+
required: ["source", "observedAt", "status"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
noChangeReason: { type: "string" },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
blocker: { type: "object", properties: {
|
|
72
|
+
kind: { type: "string", enum: ["dependency", "external", "needs_human_input", "agent"] },
|
|
73
|
+
reason: { type: "string" }, references: { type: "array", items: { type: "string" } },
|
|
74
|
+
action: GATE_ACTION_SCHEMA,
|
|
75
|
+
}, required: ["kind", "reason"] },
|
|
76
|
+
discoveries: { type: "object", properties: {
|
|
77
|
+
wrongAssumptions: { type: "array", items: { type: "string" } },
|
|
78
|
+
emergedWork: { type: "array", items: { type: "string" } },
|
|
79
|
+
facts: { type: "array", items: { type: "string" } },
|
|
80
|
+
newConstraints: { type: "array", items: { type: "string" } },
|
|
81
|
+
} },
|
|
82
|
+
subGoalEvents: { type: "object", properties: {
|
|
83
|
+
completed: { type: "array", items: { type: "string" } },
|
|
84
|
+
blocked: { type: "array", items: { type: "string" } },
|
|
85
|
+
canceled: { type: "array", items: { type: "string" } },
|
|
86
|
+
} },
|
|
87
|
+
constraintEvents: { type: "object", properties: {
|
|
88
|
+
retired: { type: "array", items: { type: "string" } },
|
|
89
|
+
} },
|
|
90
|
+
criterionRevisions: { type: "array", items: { type: "object", properties: {
|
|
91
|
+
old: { type: "string" }, new: { type: "string" },
|
|
92
|
+
}, required: ["old", "new"] } },
|
|
93
|
+
objectiveRefinement: { type: "string" },
|
|
94
|
+
phaseBoundaryProposal: { type: "object", properties: {
|
|
95
|
+
label: { type: "string" }, reason: { type: "string" },
|
|
96
|
+
}, required: ["label", "reason"] },
|
|
97
|
+
contextRequest: { type: "object", properties: {
|
|
98
|
+
emphasize: { type: "array", items: { type: "string" } },
|
|
99
|
+
expand: { type: "array", items: { type: "string" } },
|
|
100
|
+
confusion_points: { type: "array", items: { type: "string" } },
|
|
101
|
+
} },
|
|
102
|
+
},
|
|
103
|
+
};
|
|
12
104
|
const TOOL_BASE_SCHEMAS = [
|
|
13
105
|
{
|
|
14
106
|
name: "loopforge_start",
|
|
@@ -26,7 +118,9 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
26
118
|
},
|
|
27
119
|
maxRounds: {
|
|
28
120
|
type: "number",
|
|
29
|
-
|
|
121
|
+
minimum: 1,
|
|
122
|
+
multipleOf: 1,
|
|
123
|
+
description: "Maximum rounds before auto-stop. Default: 100 from policy.",
|
|
30
124
|
},
|
|
31
125
|
domain: {
|
|
32
126
|
type: "string",
|
|
@@ -41,258 +135,122 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
41
135
|
items: { type: "string" },
|
|
42
136
|
description: "Hard constraints to enforce across all rounds.",
|
|
43
137
|
},
|
|
138
|
+
successCriteria: {
|
|
139
|
+
type: "array",
|
|
140
|
+
items: { type: "string" },
|
|
141
|
+
description: "Explicit success criteria to verify across the loop.",
|
|
142
|
+
},
|
|
44
143
|
},
|
|
45
144
|
required: ["task"],
|
|
46
145
|
},
|
|
47
146
|
},
|
|
48
147
|
{
|
|
49
148
|
name: "loopforge_next",
|
|
50
|
-
description: "Submit the
|
|
149
|
+
description: "Submit the canonical round report for the current round and advance to the next. Returns the next-round prompt, or null with a stopReason when the loop ends.",
|
|
51
150
|
inputSchema: {
|
|
52
151
|
type: "object",
|
|
152
|
+
additionalProperties: false,
|
|
53
153
|
properties: {
|
|
54
154
|
sessionId: {
|
|
55
155
|
type: "string",
|
|
56
156
|
description: "Session ID returned by loopforge_start.",
|
|
57
157
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
description: "
|
|
61
|
-
},
|
|
62
|
-
evaluation: {
|
|
63
|
-
type: "object",
|
|
64
|
-
description: "Structured self-evaluation for this round. Required for the loop to continue. Preferred over embedding ---loopforge-eval blocks in output text.",
|
|
65
|
-
required: ["success", "output_summary", "should_continue", "constraint_violations"],
|
|
66
|
-
properties: {
|
|
67
|
-
success: {
|
|
68
|
-
type: "boolean",
|
|
69
|
-
description: "true ONLY if all hard constraints met AND the task goal achieved.",
|
|
70
|
-
},
|
|
71
|
-
output_summary: {
|
|
72
|
-
type: "string",
|
|
73
|
-
description: "Specific, actionable summary of what was DONE this round — not what was attempted.",
|
|
74
|
-
},
|
|
75
|
-
should_continue: {
|
|
76
|
-
type: "boolean",
|
|
77
|
-
description: "false ONLY when the ENTIRE task is complete. Partial progress = true.",
|
|
78
|
-
},
|
|
79
|
-
constraint_violations: {
|
|
80
|
-
type: "array",
|
|
81
|
-
items: { type: "string" },
|
|
82
|
-
description: "Constraints the agent actually violated this round. Be honest.",
|
|
83
|
-
},
|
|
84
|
-
discovered_constraints: {
|
|
85
|
-
type: "array",
|
|
86
|
-
items: { type: "string" },
|
|
87
|
-
description: "Optional. New constraints discovered this round.",
|
|
88
|
-
},
|
|
89
|
-
objective_refinement: {
|
|
90
|
-
type: "string",
|
|
91
|
-
description: "Optional. If this round deepened understanding of the task objective.",
|
|
92
|
-
},
|
|
93
|
-
emerged_subtasks: {
|
|
94
|
-
type: "array",
|
|
95
|
-
items: { type: "string" },
|
|
96
|
-
description: "Optional. Sub-problems that surfaced during execution.",
|
|
97
|
-
},
|
|
98
|
-
execution_evidence: {
|
|
99
|
-
type: "object",
|
|
100
|
-
description: "Optional. Structured record of what actually happened this round.",
|
|
101
|
-
properties: {
|
|
102
|
-
files_changed: {
|
|
103
|
-
type: "array",
|
|
104
|
-
items: { type: "string" },
|
|
105
|
-
description: "Files modified this round.",
|
|
106
|
-
},
|
|
107
|
-
test_results: {
|
|
108
|
-
type: "object",
|
|
109
|
-
description: "Test results from this round.",
|
|
110
|
-
properties: {
|
|
111
|
-
passed: { type: "integer", minimum: 0, description: "Number of passing tests." },
|
|
112
|
-
failed: { type: "integer", minimum: 0, description: "Number of failing tests." },
|
|
113
|
-
skipped: { type: "integer", minimum: 0, description: "Number of skipped tests." },
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
success_criteria_met: {
|
|
117
|
-
type: "array",
|
|
118
|
-
items: { type: "string" },
|
|
119
|
-
description: "Success criteria satisfied this round.",
|
|
120
|
-
},
|
|
121
|
-
success_criteria_remaining: {
|
|
122
|
-
type: "array",
|
|
123
|
-
items: { type: "string" },
|
|
124
|
-
description: "Success criteria still outstanding.",
|
|
125
|
-
},
|
|
126
|
-
progress_estimate: {
|
|
127
|
-
type: "number",
|
|
128
|
-
minimum: 0,
|
|
129
|
-
maximum: 1,
|
|
130
|
-
description: "Estimated progress toward task completion (0.0–1.0).",
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
retracted_constraints: {
|
|
135
|
-
type: "array",
|
|
136
|
-
items: { type: "string" },
|
|
137
|
-
description: "Optional. Constraints the agent now believes are wrong.",
|
|
138
|
-
},
|
|
139
|
-
revised_success_criteria: {
|
|
140
|
-
type: "array",
|
|
141
|
-
description: "Optional. Success criteria that need reformulation.",
|
|
142
|
-
items: {
|
|
143
|
-
type: "object",
|
|
144
|
-
properties: {
|
|
145
|
-
old: { type: "string", description: "Original criterion." },
|
|
146
|
-
new: { type: "string", description: "Revised criterion." },
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
wrong_assumptions: {
|
|
151
|
-
type: "array",
|
|
152
|
-
items: { type: "string" },
|
|
153
|
-
description: "Optional. Assumptions from earlier rounds that were incorrect.",
|
|
154
|
-
},
|
|
155
|
-
worker_results: {
|
|
156
|
-
type: "array",
|
|
157
|
-
description: "Optional. Results of sub-agent / Worker delegations this round.",
|
|
158
|
-
items: {
|
|
159
|
-
type: "object",
|
|
160
|
-
properties: {
|
|
161
|
-
agentId: { type: "string" },
|
|
162
|
-
subAgentType: { type: "string" },
|
|
163
|
-
subTask: { type: "string" },
|
|
164
|
-
resultSummary: { type: "string" },
|
|
165
|
-
success: { type: "boolean" },
|
|
166
|
-
discoveredConstraints: {
|
|
167
|
-
type: "array",
|
|
168
|
-
items: { type: "string" },
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
required: ["agentId", "subAgentType", "subTask", "resultSummary", "success"],
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
compression_checkpoint: {
|
|
175
|
-
type: "boolean",
|
|
176
|
-
description: "Optional. Set true at subtask boundaries to trigger a full-state checkpoint (L2 prompt) that survives rolling-window eviction. The checkpoint label is carried into future prompts.",
|
|
177
|
-
},
|
|
178
|
-
checkpoint_label: {
|
|
179
|
-
type: "string",
|
|
180
|
-
description: "Optional. Human-readable label for this checkpoint (e.g. 'core-functions-complete'). Only meaningful when compression_checkpoint is true.",
|
|
181
|
-
},
|
|
182
|
-
next_action: {
|
|
183
|
-
type: "string",
|
|
184
|
-
description: "Optional. What the agent plans to do in the next round. Helps LoopForge detect task drift early by comparing declared intent with actual work.",
|
|
185
|
-
},
|
|
186
|
-
completed_subtasks: {
|
|
187
|
-
type: "array",
|
|
188
|
-
items: { type: "string" },
|
|
189
|
-
description: "Optional. Sub-tasks completed this round. Matched to previously emerged sub-tasks by description similarity.",
|
|
190
|
-
},
|
|
191
|
-
blocked_subtasks: {
|
|
192
|
-
type: "array",
|
|
193
|
-
items: { type: "string" },
|
|
194
|
-
description: "Optional. Sub-tasks that are now blocked. Describe what is blocking and why.",
|
|
195
|
-
},
|
|
196
|
-
canceled_subtasks: {
|
|
197
|
-
type: "array",
|
|
198
|
-
items: { type: "string" },
|
|
199
|
-
description: "Optional. Sub-tasks no longer needed. Removes them from the active sub-goal list.",
|
|
200
|
-
},
|
|
201
|
-
stop_reason: {
|
|
202
|
-
type: "string",
|
|
203
|
-
enum: ["gave_up", "blocked", "needs_human_input"],
|
|
204
|
-
description: "Optional. Why the agent is stopping. Only meaningful when should_continue=false and success=false. 'blocked' means work cannot proceed under current constraints.",
|
|
205
|
-
},
|
|
206
|
-
prompt_requests: {
|
|
207
|
-
type: "object",
|
|
208
|
-
description: "Optional. Information needs for the next round's prompt. Use when you know what context you'll need to succeed in the next round.",
|
|
209
|
-
properties: {
|
|
210
|
-
emphasize: {
|
|
211
|
-
type: "array",
|
|
212
|
-
items: { type: "string" },
|
|
213
|
-
description: "Constraints, discoveries, or decisions that need emphasis in the next prompt. Matched by meaning — not exact text. Matched items appear in a 'Critical Context' section. Max 5 entries. Pure reordering — no token overhead.",
|
|
214
|
-
},
|
|
215
|
-
expand: {
|
|
216
|
-
type: "array",
|
|
217
|
-
items: {
|
|
218
|
-
type: "string",
|
|
219
|
-
enum: ["milestones", "sub_goals", "constraint_lifecycle", "agent_trust", "progress", "loop_synthesis"],
|
|
220
|
-
},
|
|
221
|
-
description: "Sections to expand to full detail in the next prompt. Useful when you need full context on a specific area (e.g. full sub-goal dashboard when replanning). L1 max 1 section. L2 already expanded — redundant. L0 ignored.",
|
|
222
|
-
},
|
|
223
|
-
confusion_points: {
|
|
224
|
-
type: "array",
|
|
225
|
-
items: { type: "string" },
|
|
226
|
-
description: "Things you're confused about. Rendered at the TOP of the next prompt before the objective. Max 3 entries, each ≤ 200 chars recommended. Be specific — 'I don't understand why milestone 3 is marked complete when phase 2 appears unfinished.'",
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
},
|
|
158
|
+
report: {
|
|
159
|
+
...ROUND_REPORT_SCHEMA,
|
|
160
|
+
description: "Canonical round report. This is the only execution report accepted by loopforge_next.",
|
|
231
161
|
},
|
|
232
162
|
},
|
|
233
|
-
required: ["sessionId", "
|
|
163
|
+
required: ["sessionId", "report"],
|
|
234
164
|
},
|
|
235
165
|
},
|
|
236
166
|
{
|
|
237
167
|
name: "loopforge_status",
|
|
238
|
-
description: "
|
|
168
|
+
description: "Read session, loop health, or all persisted loops through one canonical status view.",
|
|
239
169
|
inputSchema: {
|
|
240
170
|
type: "object",
|
|
241
171
|
properties: {
|
|
172
|
+
scope: { type: "string", enum: ["session", "loop", "all"] },
|
|
242
173
|
sessionId: {
|
|
243
174
|
type: "string",
|
|
244
175
|
description: "Session ID returned by loopforge_start.",
|
|
245
176
|
},
|
|
177
|
+
loopId: { type: "string" },
|
|
178
|
+
detail: { type: "string", enum: ["summary", "full"] },
|
|
246
179
|
},
|
|
247
|
-
required: ["
|
|
180
|
+
required: ["scope"],
|
|
248
181
|
},
|
|
249
182
|
},
|
|
250
183
|
{
|
|
251
|
-
name: "
|
|
252
|
-
description: "
|
|
184
|
+
name: "loopforge_retry",
|
|
185
|
+
description: "Submit a corrected round report for the current rejected logical round. The round ID and pre-round evidence baseline remain unchanged.",
|
|
253
186
|
inputSchema: {
|
|
254
187
|
type: "object",
|
|
255
188
|
properties: {
|
|
256
189
|
sessionId: {
|
|
257
190
|
type: "string",
|
|
258
|
-
description: "Session ID
|
|
191
|
+
description: "Session ID returned by loopforge_start.",
|
|
192
|
+
},
|
|
193
|
+
report: {
|
|
194
|
+
...ROUND_REPORT_SCHEMA,
|
|
195
|
+
description: "Corrected canonical report for the rejected attempt.",
|
|
259
196
|
},
|
|
260
197
|
},
|
|
261
|
-
required: ["sessionId"],
|
|
198
|
+
required: ["sessionId", "report"],
|
|
262
199
|
},
|
|
263
200
|
},
|
|
264
201
|
{
|
|
265
|
-
name: "
|
|
266
|
-
description: "
|
|
202
|
+
name: "loopforge_backtrack",
|
|
203
|
+
description: "Move an active session to the round after its nearest clean restore point. Returns restore instructions but never changes workspace files.",
|
|
267
204
|
inputSchema: {
|
|
268
205
|
type: "object",
|
|
269
206
|
properties: {
|
|
270
207
|
sessionId: {
|
|
271
208
|
type: "string",
|
|
272
|
-
description: "Session ID
|
|
209
|
+
description: "Session ID returned by loopforge_start.",
|
|
273
210
|
},
|
|
274
211
|
},
|
|
275
212
|
required: ["sessionId"],
|
|
276
213
|
},
|
|
277
214
|
},
|
|
278
215
|
{
|
|
279
|
-
name: "
|
|
280
|
-
description: "
|
|
216
|
+
name: "loopforge_history",
|
|
217
|
+
description: "Read one persisted round or a deterministic event timeline.",
|
|
281
218
|
inputSchema: {
|
|
282
219
|
type: "object",
|
|
283
|
-
properties: {
|
|
284
|
-
|
|
220
|
+
properties: {
|
|
221
|
+
loopId: { type: "string" },
|
|
222
|
+
view: { type: "string", enum: ["round", "timeline"] },
|
|
223
|
+
round: { type: "integer", minimum: 1 },
|
|
224
|
+
fromSequence: { type: "integer", minimum: 1 },
|
|
225
|
+
toSequence: { type: "integer", minimum: 1 },
|
|
226
|
+
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
227
|
+
},
|
|
228
|
+
required: ["loopId", "view"],
|
|
285
229
|
},
|
|
286
230
|
},
|
|
287
231
|
{
|
|
288
|
-
name: "
|
|
289
|
-
description: "
|
|
232
|
+
name: "loopforge_stop",
|
|
233
|
+
description: "Manually stop a loop session and return its final trajectory.",
|
|
290
234
|
inputSchema: {
|
|
291
235
|
type: "object",
|
|
292
236
|
properties: {
|
|
293
237
|
sessionId: {
|
|
294
238
|
type: "string",
|
|
295
|
-
description: "Session ID to
|
|
239
|
+
description: "Session ID to stop.",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
required: ["sessionId"],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "loopforge_pause",
|
|
247
|
+
description: "Pause a running loop session. The loop suspends at the next round boundary and its state is persisted to vault. Paused loops can be resumed with loopforge_resume. Use this to interrupt a long-running loop without losing progress.",
|
|
248
|
+
inputSchema: {
|
|
249
|
+
type: "object",
|
|
250
|
+
properties: {
|
|
251
|
+
sessionId: {
|
|
252
|
+
type: "string",
|
|
253
|
+
description: "Session ID to pause.",
|
|
296
254
|
},
|
|
297
255
|
},
|
|
298
256
|
required: ["sessionId"],
|
|
@@ -313,16 +271,37 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
313
271
|
},
|
|
314
272
|
},
|
|
315
273
|
{
|
|
316
|
-
name: "
|
|
317
|
-
description: "
|
|
274
|
+
name: "loopforge_gate_check",
|
|
275
|
+
description: "Classify whether an action is an agent-resolvable gate or requires explicit user authority. Read-only.",
|
|
318
276
|
inputSchema: {
|
|
319
277
|
type: "object",
|
|
320
278
|
properties: {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
279
|
+
action: GATE_ACTION_SCHEMA,
|
|
280
|
+
},
|
|
281
|
+
required: ["action"],
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "loopforge_gate_resolve",
|
|
286
|
+
description: "Record an explicit user decision for a previously classified gate.",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
sessionId: { type: "string" },
|
|
291
|
+
gateId: { type: "string" },
|
|
292
|
+
action: GATE_ACTION_SCHEMA,
|
|
293
|
+
approved: { type: "boolean" },
|
|
294
|
+
note: { type: "string" },
|
|
325
295
|
},
|
|
296
|
+
required: ["sessionId", "gateId", "action", "approved", "note"],
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "loopforge_audit",
|
|
301
|
+
description: "Run a read-only final audit over accepted vault events, evidence claims, checks, and gate decisions.",
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: "object",
|
|
304
|
+
properties: { loopId: { type: "string" } },
|
|
326
305
|
required: ["loopId"],
|
|
327
306
|
},
|
|
328
307
|
},
|
|
@@ -335,12 +314,18 @@ const ADVANCE_OUTPUT_SCHEMA = {
|
|
|
335
314
|
round: { type: "number" },
|
|
336
315
|
roundId: { type: ["string", "null"] },
|
|
337
316
|
prompt: { type: ["string", "null"] },
|
|
317
|
+
decision: { enum: ["accepted", "rejected", "backtracked", "stopped", "terminated", "errored"] },
|
|
318
|
+
attempt: { type: "number" },
|
|
338
319
|
stopReason: { type: "string" },
|
|
339
320
|
stopDetail: { type: "string", description: "Human-readable context for why the loop stopped. Provides facts without prescribing a specific agent action." },
|
|
340
321
|
level: { type: "string" },
|
|
341
322
|
roundSuccess: { type: "boolean" },
|
|
342
323
|
enforcementAction: { enum: ["accept", "reject", "terminate", "backtrack"] },
|
|
343
324
|
enforcementReason: { type: "string" },
|
|
325
|
+
projection: { type: "object", additionalProperties: true },
|
|
326
|
+
evidenceSummary: { type: "object", additionalProperties: true },
|
|
327
|
+
stateHash: { type: "string" },
|
|
328
|
+
promptHash: { type: "string" },
|
|
344
329
|
warnings: { type: "array", items: { type: "string" } },
|
|
345
330
|
},
|
|
346
331
|
additionalProperties: true,
|
|
@@ -348,7 +333,10 @@ const ADVANCE_OUTPUT_SCHEMA = {
|
|
|
348
333
|
const TOOL_OUTPUT_SCHEMAS = {
|
|
349
334
|
loopforge_start: ADVANCE_OUTPUT_SCHEMA,
|
|
350
335
|
loopforge_next: ADVANCE_OUTPUT_SCHEMA,
|
|
336
|
+
loopforge_retry: ADVANCE_OUTPUT_SCHEMA,
|
|
337
|
+
loopforge_backtrack: ADVANCE_OUTPUT_SCHEMA,
|
|
351
338
|
loopforge_resume: ADVANCE_OUTPUT_SCHEMA,
|
|
339
|
+
loopforge_history: { type: "object", additionalProperties: true },
|
|
352
340
|
loopforge_status: {
|
|
353
341
|
type: "object",
|
|
354
342
|
properties: {
|
|
@@ -385,43 +373,17 @@ const TOOL_OUTPUT_SCHEMAS = {
|
|
|
385
373
|
},
|
|
386
374
|
additionalProperties: true,
|
|
387
375
|
},
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
sessions: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
392
|
-
},
|
|
393
|
-
required: ["sessions"],
|
|
394
|
-
additionalProperties: false,
|
|
395
|
-
},
|
|
396
|
-
loopforge_replay: {
|
|
397
|
-
type: "object",
|
|
398
|
-
properties: {
|
|
399
|
-
error: { type: "string" },
|
|
400
|
-
sessionId: { type: "string" },
|
|
401
|
-
loopId: { type: "string" },
|
|
402
|
-
timeline: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
403
|
-
},
|
|
404
|
-
additionalProperties: true,
|
|
405
|
-
},
|
|
406
|
-
loopforge_health: {
|
|
407
|
-
type: "object",
|
|
408
|
-
properties: {
|
|
409
|
-
error: { type: "string" },
|
|
410
|
-
loopId: { type: "string" },
|
|
411
|
-
goal_alignment: { type: "object", additionalProperties: true },
|
|
412
|
-
constraint_integrity: { type: "object", additionalProperties: true },
|
|
413
|
-
drift_detected: { type: "boolean" },
|
|
414
|
-
strategy_stability: { type: "object", additionalProperties: true },
|
|
415
|
-
task_continuity: { type: "object", additionalProperties: true },
|
|
416
|
-
policy_metrics: { type: "object", additionalProperties: true },
|
|
417
|
-
},
|
|
418
|
-
additionalProperties: true,
|
|
419
|
-
},
|
|
376
|
+
loopforge_gate_check: { type: "object", additionalProperties: true },
|
|
377
|
+
loopforge_gate_resolve: { type: "object", additionalProperties: true },
|
|
378
|
+
loopforge_audit: { type: "object", additionalProperties: true },
|
|
420
379
|
};
|
|
421
380
|
function closeObjectSchemas(schema) {
|
|
422
381
|
const result = { ...schema };
|
|
423
382
|
if (result.type === "object") {
|
|
424
|
-
|
|
383
|
+
// Preserve explicitly open maps such as EvidenceEnvelope.provenance.
|
|
384
|
+
// Fixed-shape objects remain closed by default.
|
|
385
|
+
if (!("additionalProperties" in result))
|
|
386
|
+
result.additionalProperties = false;
|
|
425
387
|
if (isRecord(result.properties)) {
|
|
426
388
|
result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, value]) => [
|
|
427
389
|
key,
|
|
@@ -490,6 +452,13 @@ function validateSchema(value, schema, path) {
|
|
|
490
452
|
if (type === "string" && typeof value !== "string") {
|
|
491
453
|
throw new ToolInputValidationError(`${path} must be a string`);
|
|
492
454
|
}
|
|
455
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => candidate === value)) {
|
|
456
|
+
throw new ToolInputValidationError(`${path} must be one of ${schema.enum.join(", ")}`);
|
|
457
|
+
}
|
|
458
|
+
if (type === "string" && typeof value === "string" &&
|
|
459
|
+
typeof schema.pattern === "string" && !new RegExp(schema.pattern).test(value)) {
|
|
460
|
+
throw new ToolInputValidationError(`${path} must match ${schema.pattern}`);
|
|
461
|
+
}
|
|
493
462
|
if (type === "boolean" && typeof value !== "boolean") {
|
|
494
463
|
throw new ToolInputValidationError(`${path} must be a boolean`);
|
|
495
464
|
}
|
|
@@ -513,6 +482,55 @@ export function validateToolInput(name, input) {
|
|
|
513
482
|
throw new ToolInputValidationError(`Unknown tool: ${name}`);
|
|
514
483
|
validateSchema(input, contract.inputSchema, "arguments");
|
|
515
484
|
}
|
|
485
|
+
function enrichAdvanceResult(mgr, result) {
|
|
486
|
+
const sessionId = typeof result.sessionId === "string" ? result.sessionId : "";
|
|
487
|
+
const session = sessionId ? mgr.get(sessionId) : undefined;
|
|
488
|
+
const projection = sessionId ? mgr.cognitiveProjection(sessionId) : null;
|
|
489
|
+
const audit = session ? mgr.audit(session.loopId) : null;
|
|
490
|
+
const enforcement = String(result.enforcementAction ?? "");
|
|
491
|
+
const stopReason = String(result.stopReason ?? "");
|
|
492
|
+
const decision = enforcement === "reject" ? "rejected"
|
|
493
|
+
: enforcement === "backtrack" ? "backtracked"
|
|
494
|
+
: enforcement === "terminate" ? "terminated"
|
|
495
|
+
: stopReason ? "stopped" : "accepted";
|
|
496
|
+
const artifact = session?.roundSnapshot?.promptArtifact;
|
|
497
|
+
const criteria = isRecord(audit?.criteria) ? audit.criteria : {};
|
|
498
|
+
const checks = isRecord(audit?.checks) ? audit.checks : {};
|
|
499
|
+
return {
|
|
500
|
+
...result,
|
|
501
|
+
decision,
|
|
502
|
+
attempt: session?.roundSnapshot?.attempt ?? result.attempt,
|
|
503
|
+
projection: projection ? {
|
|
504
|
+
phase: Array.isArray(projection.phaseHistory)
|
|
505
|
+
? projection.phaseHistory.at(-1)?.label ?? ""
|
|
506
|
+
: "",
|
|
507
|
+
focus: projection.currentFocus ?? "",
|
|
508
|
+
todo: projection.todoProjection ?? null,
|
|
509
|
+
gate: projection.activeGate ?? null,
|
|
510
|
+
} : undefined,
|
|
511
|
+
evidenceSummary: {
|
|
512
|
+
verified: Array.isArray(criteria.evidenced) ? criteria.evidenced : [],
|
|
513
|
+
contradicted: Array.isArray(checks.failed) ? checks.failed : [],
|
|
514
|
+
unavailable: Array.isArray(checks.unavailable) ? checks.unavailable : [],
|
|
515
|
+
},
|
|
516
|
+
stateHash: artifact?.stateHash,
|
|
517
|
+
promptHash: artifact?.promptHash,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
/** Uniform decision enrichment for advance-family tools.
|
|
521
|
+
*
|
|
522
|
+
* Handler-level `{error}` responses and session-level lease conflicts are
|
|
523
|
+
* not loop decisions — without this guard the enforcement-only derivation
|
|
524
|
+
* above would label them "stopped" (any stopReason) or even "accepted"
|
|
525
|
+
* (a bare error with no enforcement fields). */
|
|
526
|
+
function finalizeAdvanceOutput(mgr, result) {
|
|
527
|
+
const stopReason = String(result.stopReason ?? "");
|
|
528
|
+
if ((result.error !== undefined && result.error !== null) ||
|
|
529
|
+
stopReason.startsWith("session_owned_elsewhere")) {
|
|
530
|
+
return { ...result, decision: "errored" };
|
|
531
|
+
}
|
|
532
|
+
return enrichAdvanceResult(mgr, result);
|
|
533
|
+
}
|
|
516
534
|
export const TOOL_HANDLERS = {
|
|
517
535
|
async loopforge_start(mgr, input) {
|
|
518
536
|
const startInput = {
|
|
@@ -524,37 +542,92 @@ export const TOOL_HANDLERS = {
|
|
|
524
542
|
constraints: Array.isArray(input.constraints)
|
|
525
543
|
? input.constraints
|
|
526
544
|
: undefined,
|
|
545
|
+
successCriteria: Array.isArray(input.successCriteria)
|
|
546
|
+
? input.successCriteria
|
|
547
|
+
: undefined,
|
|
527
548
|
};
|
|
528
549
|
if (!startInput.task.trim()) {
|
|
529
|
-
return { error: "task is required and must be non-empty" };
|
|
550
|
+
return { error: "task is required and must be non-empty", decision: "errored" };
|
|
551
|
+
}
|
|
552
|
+
const policyMaxRounds = getPolicy().engine.max_rounds;
|
|
553
|
+
if (startInput.maxRounds !== undefined &&
|
|
554
|
+
(!Number.isInteger(startInput.maxRounds) || startInput.maxRounds < 1 || startInput.maxRounds > policyMaxRounds)) {
|
|
555
|
+
return { error: `maxRounds must be an integer between 1 and ${policyMaxRounds}`, decision: "errored" };
|
|
530
556
|
}
|
|
531
557
|
const result = await mgr.create(startInput);
|
|
532
|
-
|
|
558
|
+
const enriched = finalizeAdvanceOutput(mgr, { ...result });
|
|
559
|
+
const session = typeof result.sessionId === "string" ? mgr.get(result.sessionId) : undefined;
|
|
560
|
+
return {
|
|
561
|
+
...enriched,
|
|
562
|
+
loopId: session?.loopId,
|
|
563
|
+
goalId: session?.runtime.state?.task_id ?? session?.loopId,
|
|
564
|
+
};
|
|
533
565
|
},
|
|
534
566
|
async loopforge_next(mgr, input) {
|
|
535
567
|
const sessionId = String(input.sessionId ?? "");
|
|
536
|
-
const output = String(input.output ?? "");
|
|
537
|
-
const rawEval = input.evaluation;
|
|
538
568
|
if (!sessionId)
|
|
539
|
-
return { error: "sessionId is required" };
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
569
|
+
return { error: "sessionId is required", decision: "errored" };
|
|
570
|
+
const report = parseRoundReport(input.report);
|
|
571
|
+
if (!report)
|
|
572
|
+
return { error: "report is not a valid RoundReport", decision: "errored" };
|
|
573
|
+
const result = await mgr.advance(sessionId, report);
|
|
574
|
+
return finalizeAdvanceOutput(mgr, { ...result });
|
|
575
|
+
},
|
|
576
|
+
async loopforge_retry(mgr, input) {
|
|
577
|
+
const sessionId = String(input.sessionId ?? "");
|
|
578
|
+
if (!sessionId)
|
|
579
|
+
return { error: "sessionId is required", decision: "errored" };
|
|
580
|
+
const report = parseRoundReport(input.report);
|
|
581
|
+
if (!report)
|
|
582
|
+
return { error: "report is not a valid RoundReport", decision: "errored" };
|
|
583
|
+
return finalizeAdvanceOutput(mgr, { ...await mgr.retry(sessionId, report) });
|
|
584
|
+
},
|
|
585
|
+
async loopforge_backtrack(mgr, input) {
|
|
586
|
+
const sessionId = String(input.sessionId ?? "");
|
|
587
|
+
if (!sessionId)
|
|
588
|
+
return { error: "sessionId is required", decision: "errored" };
|
|
589
|
+
// Backtrack is an enforcement action: enrich it like the other advance
|
|
590
|
+
// tools so the output honors the shared advance contract.
|
|
591
|
+
return finalizeAdvanceOutput(mgr, { ...await mgr.backtrack(sessionId) });
|
|
548
592
|
},
|
|
549
593
|
async loopforge_status(mgr, input) {
|
|
594
|
+
const scope = String(input.scope ?? "");
|
|
595
|
+
const hasSessionId = typeof input.sessionId === "string" && input.sessionId.trim().length > 0;
|
|
596
|
+
const hasLoopId = typeof input.loopId === "string" && input.loopId.trim().length > 0;
|
|
597
|
+
if (scope === "all") {
|
|
598
|
+
if (hasSessionId || hasLoopId)
|
|
599
|
+
return { error: "scope=all does not accept sessionId or loopId" };
|
|
600
|
+
return { scope, sessions: mgr.list() };
|
|
601
|
+
}
|
|
602
|
+
if (scope === "loop") {
|
|
603
|
+
if (hasSessionId)
|
|
604
|
+
return { error: "scope=loop does not accept sessionId" };
|
|
605
|
+
const loopId = String(input.loopId ?? "").trim();
|
|
606
|
+
if (!loopId)
|
|
607
|
+
return { error: "loopId is required for loop scope" };
|
|
608
|
+
const health = mgr.getHealth(loopId);
|
|
609
|
+
if (!health)
|
|
610
|
+
return { error: `no data found for loop "${loopId}"` };
|
|
611
|
+
const audit = mgr.audit(loopId);
|
|
612
|
+
return input.detail === "full"
|
|
613
|
+
? { scope, loopId, health, audit }
|
|
614
|
+
: { scope, loopId, health };
|
|
615
|
+
}
|
|
616
|
+
if (scope !== "session")
|
|
617
|
+
return { error: "scope must be session, loop, or all" };
|
|
618
|
+
if (hasLoopId)
|
|
619
|
+
return { error: "scope=session does not accept loopId" };
|
|
550
620
|
const sessionId = String(input.sessionId ?? "");
|
|
551
621
|
if (!sessionId)
|
|
552
622
|
return { error: "sessionId is required" };
|
|
553
623
|
const session = mgr.get(sessionId);
|
|
554
624
|
if (!session)
|
|
555
625
|
return { error: `session not found: ${sessionId}` };
|
|
556
|
-
const metrics = session.
|
|
557
|
-
|
|
626
|
+
const metrics = session.runtime.getMetrics();
|
|
627
|
+
const state = mgr.cognitiveProjection(sessionId);
|
|
628
|
+
const artifact = session.roundSnapshot?.promptArtifact;
|
|
629
|
+
const audit = mgr.audit(session.loopId);
|
|
630
|
+
const summary = {
|
|
558
631
|
sessionId: session.sessionId,
|
|
559
632
|
loopId: session.loopId,
|
|
560
633
|
round: session.currentRound,
|
|
@@ -563,9 +636,40 @@ export const TOOL_HANDLERS = {
|
|
|
563
636
|
status: session.status,
|
|
564
637
|
successTrajectory: session.successTrajectory,
|
|
565
638
|
lease: mgr.getLeaseStatus(session.loopId),
|
|
639
|
+
};
|
|
640
|
+
if (input.detail !== "full")
|
|
641
|
+
return summary;
|
|
642
|
+
return {
|
|
643
|
+
...summary,
|
|
644
|
+
cognition: state ? {
|
|
645
|
+
phaseHistory: state.phaseHistory ?? [],
|
|
646
|
+
currentFocus: state.currentFocus ?? "",
|
|
647
|
+
todoProjection: state.todoProjection ?? null,
|
|
648
|
+
suggestedNextAction: state.suggestedNextAction ?? "",
|
|
649
|
+
activeGate: state.activeGate ?? null,
|
|
650
|
+
handoff: state.handoff ?? null,
|
|
651
|
+
stateFreshness: state.stateFreshness ?? "missing",
|
|
652
|
+
regressionObligations: state.regressionObligations ?? [],
|
|
653
|
+
} : null,
|
|
654
|
+
promptDiagnostics: artifact ? {
|
|
655
|
+
level: artifact.level,
|
|
656
|
+
reasons: artifact.levelReasons,
|
|
657
|
+
includedSections: artifact.includedSections,
|
|
658
|
+
omittedSections: artifact.omittedSections,
|
|
659
|
+
stateHash: artifact.stateHash,
|
|
660
|
+
promptHash: artifact.promptHash,
|
|
661
|
+
lastL2Round: artifact.lastL2Round,
|
|
662
|
+
lastMeaningfulProgressRound: artifact.lastMeaningfulProgressRound,
|
|
663
|
+
stateAgeRounds: artifact.stateAgeRounds,
|
|
664
|
+
} : null,
|
|
665
|
+
audit: {
|
|
666
|
+
verdict: audit.verdict,
|
|
667
|
+
missingCriteria: isRecord(audit.criteria) ? audit.criteria.missing : [],
|
|
668
|
+
unresolvedUserGates: audit.unresolvedUserGates ?? [],
|
|
669
|
+
},
|
|
566
670
|
metrics: {
|
|
567
671
|
vaultWriteErrors: metrics.vaultWriteErrors,
|
|
568
|
-
policy:
|
|
672
|
+
policy: mgr.getLoopPolicyMetrics(session.loopId),
|
|
569
673
|
},
|
|
570
674
|
};
|
|
571
675
|
},
|
|
@@ -579,7 +683,17 @@ export const TOOL_HANDLERS = {
|
|
|
579
683
|
const roundsCompleted = session.currentRound;
|
|
580
684
|
const successTrajectory = [...session.successTrajectory];
|
|
581
685
|
mgr.delete(sessionId);
|
|
582
|
-
|
|
686
|
+
const audit = mgr.audit(session.loopId);
|
|
687
|
+
return {
|
|
688
|
+
success: true,
|
|
689
|
+
sessionId,
|
|
690
|
+
loopId: session.loopId,
|
|
691
|
+
stopReason: "cancelled",
|
|
692
|
+
stopDetail: "The loop was manually stopped. Vault state was retained for audit and inspection.",
|
|
693
|
+
roundsCompleted,
|
|
694
|
+
successTrajectory,
|
|
695
|
+
audit,
|
|
696
|
+
};
|
|
583
697
|
},
|
|
584
698
|
async loopforge_pause(mgr, input) {
|
|
585
699
|
const sessionId = String(input.sessionId ?? "");
|
|
@@ -588,42 +702,53 @@ export const TOOL_HANDLERS = {
|
|
|
588
702
|
const result = mgr.pause(sessionId);
|
|
589
703
|
return { ...result };
|
|
590
704
|
},
|
|
591
|
-
async
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
705
|
+
async loopforge_history(mgr, input) {
|
|
706
|
+
const loopId = String(input.loopId ?? "").trim();
|
|
707
|
+
const view = String(input.view ?? "");
|
|
708
|
+
if (!loopId)
|
|
709
|
+
return { error: "loopId is required" };
|
|
710
|
+
if (view !== "round" && view !== "timeline")
|
|
711
|
+
return { error: "view must be round or timeline" };
|
|
712
|
+
if (view === "round") {
|
|
713
|
+
const round = Number(input.round);
|
|
714
|
+
if (!Number.isInteger(round) || round < 1)
|
|
715
|
+
return { error: "round must be a positive integer" };
|
|
716
|
+
const inspected = mgr.inspect(loopId, round);
|
|
717
|
+
return inspected ?? { error: `round not found: ${loopId}#${round}` };
|
|
718
|
+
}
|
|
719
|
+
const fromSequence = typeof input.fromSequence === "number" ? input.fromSequence : undefined;
|
|
720
|
+
const toSequence = typeof input.toSequence === "number" ? input.toSequence : undefined;
|
|
721
|
+
const limit = typeof input.limit === "number" ? input.limit : 50;
|
|
722
|
+
const result = mgr.replayTimelineByLoop(loopId, fromSequence, toSequence, limit);
|
|
723
|
+
return { loopId, view, timeline: result.timeline, nextSequence: result.nextSequence };
|
|
604
724
|
},
|
|
605
725
|
async loopforge_resume(mgr, input) {
|
|
606
726
|
const loopId = String(input.loopId ?? "");
|
|
607
727
|
if (!loopId)
|
|
608
|
-
return { error: "loopId is required" };
|
|
728
|
+
return { error: "loopId is required", decision: "errored" };
|
|
609
729
|
// Paused recovery must run first so the persisted status is atomically
|
|
610
730
|
// changed back to running before a prompt is returned.
|
|
611
731
|
let result = await mgr.unpause(loopId);
|
|
612
732
|
if (!result) {
|
|
613
|
-
result = mgr.resume(loopId);
|
|
733
|
+
result = await mgr.resume(loopId);
|
|
614
734
|
}
|
|
615
735
|
if (!result)
|
|
616
|
-
return { error: `no saved session found for loop "${loopId}"
|
|
617
|
-
return { ...result };
|
|
736
|
+
return { error: `no saved session found for loop "${loopId}"`, decision: "errored" };
|
|
737
|
+
return finalizeAdvanceOutput(mgr, { ...result });
|
|
618
738
|
},
|
|
619
|
-
async
|
|
620
|
-
|
|
739
|
+
async loopforge_gate_check(mgr, input) {
|
|
740
|
+
if (!isRecord(input.action))
|
|
741
|
+
return { error: "action descriptor is required" };
|
|
742
|
+
return mgr.checkGate(input.action);
|
|
743
|
+
},
|
|
744
|
+
async loopforge_gate_resolve(mgr, input) {
|
|
745
|
+
return mgr.resolveGate(String(input.sessionId ?? ""), String(input.gateId ?? ""), isRecord(input.action) ? input.action : {}, input.approved === true, String(input.note ?? ""));
|
|
746
|
+
},
|
|
747
|
+
async loopforge_audit(mgr, input) {
|
|
748
|
+
const loopId = String(input.loopId ?? "").trim();
|
|
621
749
|
if (!loopId)
|
|
622
750
|
return { error: "loopId is required" };
|
|
623
|
-
|
|
624
|
-
if (!health)
|
|
625
|
-
return { error: `no data found for loop "${loopId}"` };
|
|
626
|
-
return health;
|
|
751
|
+
return mgr.audit(loopId);
|
|
627
752
|
},
|
|
628
753
|
};
|
|
629
754
|
//# sourceMappingURL=tools.js.map
|