loopforge 2.7.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 +136 -182
- 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 +211 -64
- 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 +26 -26
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +45 -71
- 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 +23 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -26
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +2 -2
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +321 -315
- 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 -311
- 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 +123 -19
- 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 +65 -84
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +667 -224
- 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 +401 -254
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +24 -0
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +174 -0
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +74 -12
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +124 -19
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +8 -4
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +533 -86
- 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 +259 -173
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +17 -96
- 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 +53 -22
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +140 -44
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +18 -10
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +11 -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 +23 -12
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +104 -91
- 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/{engine.js → runtime-services.js} +157 -275
- 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 +12 -8
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +56 -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 +13 -7
- package/skills/perception/SKILL.md +200 -90
- 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 -50
- package/dist/enforcement-gate.d.ts.map +0 -1
- package/dist/enforcement-gate.js +0 -501
- 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.map +0 -1
- package/dist/interop.d.ts +0 -35
- package/dist/interop.d.ts.map +0 -1
- package/dist/interop.js +0 -34
- package/dist/interop.js.map +0 -1
- package/dist/run.d.ts +0 -51
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -200
- package/dist/run.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 -151
- package/dist/self-eval.js.map +0 -1
- package/dist/verification-gate.d.ts +0 -52
- package/dist/verification-gate.d.ts.map +0 -1
- package/dist/verification-gate.js +0 -527
- package/dist/verification-gate.js.map +0 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -1,13 +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 {
|
|
7
|
-
import {
|
|
6
|
+
import { isRecord } from "../token-utils.js";
|
|
7
|
+
import { parseRoundReport } from "../round-report.js";
|
|
8
|
+
import { getPolicy } from "../policy.js";
|
|
8
9
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
10
|
// Tool schemas (MCP JSON Schema format)
|
|
10
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
|
+
};
|
|
11
104
|
const TOOL_BASE_SCHEMAS = [
|
|
12
105
|
{
|
|
13
106
|
name: "loopforge_start",
|
|
@@ -25,7 +118,9 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
25
118
|
},
|
|
26
119
|
maxRounds: {
|
|
27
120
|
type: "number",
|
|
28
|
-
|
|
121
|
+
minimum: 1,
|
|
122
|
+
multipleOf: 1,
|
|
123
|
+
description: "Maximum rounds before auto-stop. Default: 100 from policy.",
|
|
29
124
|
},
|
|
30
125
|
domain: {
|
|
31
126
|
type: "string",
|
|
@@ -40,234 +135,122 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
40
135
|
items: { type: "string" },
|
|
41
136
|
description: "Hard constraints to enforce across all rounds.",
|
|
42
137
|
},
|
|
138
|
+
successCriteria: {
|
|
139
|
+
type: "array",
|
|
140
|
+
items: { type: "string" },
|
|
141
|
+
description: "Explicit success criteria to verify across the loop.",
|
|
142
|
+
},
|
|
43
143
|
},
|
|
44
144
|
required: ["task"],
|
|
45
145
|
},
|
|
46
146
|
},
|
|
47
147
|
{
|
|
48
148
|
name: "loopforge_next",
|
|
49
|
-
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.",
|
|
50
150
|
inputSchema: {
|
|
51
151
|
type: "object",
|
|
152
|
+
additionalProperties: false,
|
|
52
153
|
properties: {
|
|
53
154
|
sessionId: {
|
|
54
155
|
type: "string",
|
|
55
156
|
description: "Session ID returned by loopforge_start.",
|
|
56
157
|
},
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
description: "
|
|
60
|
-
},
|
|
61
|
-
evaluation: {
|
|
62
|
-
type: "object",
|
|
63
|
-
description: "Structured self-evaluation for this round. Required for the loop to continue. Preferred over embedding ---loopforge-eval blocks in output text.",
|
|
64
|
-
required: ["success", "output_summary", "should_continue", "constraint_violations"],
|
|
65
|
-
properties: {
|
|
66
|
-
success: {
|
|
67
|
-
type: "boolean",
|
|
68
|
-
description: "true ONLY if all hard constraints met AND the task goal achieved.",
|
|
69
|
-
},
|
|
70
|
-
output_summary: {
|
|
71
|
-
type: "string",
|
|
72
|
-
description: "Specific, actionable summary of what was DONE this round — not what was attempted.",
|
|
73
|
-
},
|
|
74
|
-
should_continue: {
|
|
75
|
-
type: "boolean",
|
|
76
|
-
description: "false ONLY when the ENTIRE task is complete. Partial progress = true.",
|
|
77
|
-
},
|
|
78
|
-
constraint_violations: {
|
|
79
|
-
type: "array",
|
|
80
|
-
items: { type: "string" },
|
|
81
|
-
description: "Constraints the agent actually violated this round. Be honest.",
|
|
82
|
-
},
|
|
83
|
-
discovered_constraints: {
|
|
84
|
-
type: "array",
|
|
85
|
-
items: { type: "string" },
|
|
86
|
-
description: "Optional. New constraints discovered this round.",
|
|
87
|
-
},
|
|
88
|
-
objective_refinement: {
|
|
89
|
-
type: "string",
|
|
90
|
-
description: "Optional. If this round deepened understanding of the task objective.",
|
|
91
|
-
},
|
|
92
|
-
emerged_subtasks: {
|
|
93
|
-
type: "array",
|
|
94
|
-
items: { type: "string" },
|
|
95
|
-
description: "Optional. Sub-problems that surfaced during execution.",
|
|
96
|
-
},
|
|
97
|
-
execution_evidence: {
|
|
98
|
-
type: "object",
|
|
99
|
-
description: "Optional. Structured record of what actually happened this round.",
|
|
100
|
-
properties: {
|
|
101
|
-
files_changed: {
|
|
102
|
-
type: "array",
|
|
103
|
-
items: { type: "string" },
|
|
104
|
-
description: "Files modified this round.",
|
|
105
|
-
},
|
|
106
|
-
test_results: {
|
|
107
|
-
type: "object",
|
|
108
|
-
description: "Test results from this round.",
|
|
109
|
-
properties: {
|
|
110
|
-
passed: { type: "integer", minimum: 0, description: "Number of passing tests." },
|
|
111
|
-
failed: { type: "integer", minimum: 0, description: "Number of failing tests." },
|
|
112
|
-
skipped: { type: "integer", minimum: 0, description: "Number of skipped tests." },
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
success_criteria_met: {
|
|
116
|
-
type: "array",
|
|
117
|
-
items: { type: "string" },
|
|
118
|
-
description: "Success criteria satisfied this round.",
|
|
119
|
-
},
|
|
120
|
-
success_criteria_remaining: {
|
|
121
|
-
type: "array",
|
|
122
|
-
items: { type: "string" },
|
|
123
|
-
description: "Success criteria still outstanding.",
|
|
124
|
-
},
|
|
125
|
-
progress_estimate: {
|
|
126
|
-
type: "number",
|
|
127
|
-
minimum: 0,
|
|
128
|
-
maximum: 1,
|
|
129
|
-
description: "Estimated progress toward task completion (0.0–1.0).",
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
retracted_constraints: {
|
|
134
|
-
type: "array",
|
|
135
|
-
items: { type: "string" },
|
|
136
|
-
description: "Optional. Constraints the agent now believes are wrong.",
|
|
137
|
-
},
|
|
138
|
-
revised_success_criteria: {
|
|
139
|
-
type: "array",
|
|
140
|
-
description: "Optional. Success criteria that need reformulation.",
|
|
141
|
-
items: {
|
|
142
|
-
type: "object",
|
|
143
|
-
properties: {
|
|
144
|
-
old: { type: "string", description: "Original criterion." },
|
|
145
|
-
new: { type: "string", description: "Revised criterion." },
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
wrong_assumptions: {
|
|
150
|
-
type: "array",
|
|
151
|
-
items: { type: "string" },
|
|
152
|
-
description: "Optional. Assumptions from earlier rounds that were incorrect.",
|
|
153
|
-
},
|
|
154
|
-
worker_results: {
|
|
155
|
-
type: "array",
|
|
156
|
-
description: "Optional. Results of sub-agent / Worker delegations this round.",
|
|
157
|
-
items: {
|
|
158
|
-
type: "object",
|
|
159
|
-
properties: {
|
|
160
|
-
agentId: { type: "string" },
|
|
161
|
-
subAgentType: { type: "string" },
|
|
162
|
-
subTask: { type: "string" },
|
|
163
|
-
resultSummary: { type: "string" },
|
|
164
|
-
success: { type: "boolean" },
|
|
165
|
-
discoveredConstraints: {
|
|
166
|
-
type: "array",
|
|
167
|
-
items: { type: "string" },
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
required: ["agentId", "subAgentType", "subTask", "resultSummary", "success"],
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
compression_checkpoint: {
|
|
174
|
-
type: "boolean",
|
|
175
|
-
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.",
|
|
176
|
-
},
|
|
177
|
-
checkpoint_label: {
|
|
178
|
-
type: "string",
|
|
179
|
-
description: "Optional. Human-readable label for this checkpoint (e.g. 'core-functions-complete'). Only meaningful when compression_checkpoint is true.",
|
|
180
|
-
},
|
|
181
|
-
next_action: {
|
|
182
|
-
type: "string",
|
|
183
|
-
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.",
|
|
184
|
-
},
|
|
185
|
-
completed_subtasks: {
|
|
186
|
-
type: "array",
|
|
187
|
-
items: { type: "string" },
|
|
188
|
-
description: "Optional. Sub-tasks completed this round. Matched to previously emerged sub-tasks by description similarity.",
|
|
189
|
-
},
|
|
190
|
-
blocked_subtasks: {
|
|
191
|
-
type: "array",
|
|
192
|
-
items: { type: "string" },
|
|
193
|
-
description: "Optional. Sub-tasks that are now blocked. Describe what is blocking and why.",
|
|
194
|
-
},
|
|
195
|
-
canceled_subtasks: {
|
|
196
|
-
type: "array",
|
|
197
|
-
items: { type: "string" },
|
|
198
|
-
description: "Optional. Sub-tasks no longer needed. Removes them from the active sub-goal list.",
|
|
199
|
-
},
|
|
200
|
-
stop_reason: {
|
|
201
|
-
type: "string",
|
|
202
|
-
enum: ["gave_up", "blocked", "needs_human_input"],
|
|
203
|
-
description: "Optional. Why the agent is stopping. Only meaningful when should_continue=false and success=false. 'blocked' means work cannot proceed under current constraints.",
|
|
204
|
-
},
|
|
205
|
-
},
|
|
158
|
+
report: {
|
|
159
|
+
...ROUND_REPORT_SCHEMA,
|
|
160
|
+
description: "Canonical round report. This is the only execution report accepted by loopforge_next.",
|
|
206
161
|
},
|
|
207
162
|
},
|
|
208
|
-
required: ["sessionId", "
|
|
163
|
+
required: ["sessionId", "report"],
|
|
209
164
|
},
|
|
210
165
|
},
|
|
211
166
|
{
|
|
212
167
|
name: "loopforge_status",
|
|
213
|
-
description: "
|
|
168
|
+
description: "Read session, loop health, or all persisted loops through one canonical status view.",
|
|
214
169
|
inputSchema: {
|
|
215
170
|
type: "object",
|
|
216
171
|
properties: {
|
|
172
|
+
scope: { type: "string", enum: ["session", "loop", "all"] },
|
|
217
173
|
sessionId: {
|
|
218
174
|
type: "string",
|
|
219
175
|
description: "Session ID returned by loopforge_start.",
|
|
220
176
|
},
|
|
177
|
+
loopId: { type: "string" },
|
|
178
|
+
detail: { type: "string", enum: ["summary", "full"] },
|
|
221
179
|
},
|
|
222
|
-
required: ["
|
|
180
|
+
required: ["scope"],
|
|
223
181
|
},
|
|
224
182
|
},
|
|
225
183
|
{
|
|
226
|
-
name: "
|
|
227
|
-
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.",
|
|
228
186
|
inputSchema: {
|
|
229
187
|
type: "object",
|
|
230
188
|
properties: {
|
|
231
189
|
sessionId: {
|
|
232
190
|
type: "string",
|
|
233
|
-
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.",
|
|
234
196
|
},
|
|
235
197
|
},
|
|
236
|
-
required: ["sessionId"],
|
|
198
|
+
required: ["sessionId", "report"],
|
|
237
199
|
},
|
|
238
200
|
},
|
|
239
201
|
{
|
|
240
|
-
name: "
|
|
241
|
-
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.",
|
|
242
204
|
inputSchema: {
|
|
243
205
|
type: "object",
|
|
244
206
|
properties: {
|
|
245
207
|
sessionId: {
|
|
246
208
|
type: "string",
|
|
247
|
-
description: "Session ID
|
|
209
|
+
description: "Session ID returned by loopforge_start.",
|
|
248
210
|
},
|
|
249
211
|
},
|
|
250
212
|
required: ["sessionId"],
|
|
251
213
|
},
|
|
252
214
|
},
|
|
253
215
|
{
|
|
254
|
-
name: "
|
|
255
|
-
description: "
|
|
216
|
+
name: "loopforge_history",
|
|
217
|
+
description: "Read one persisted round or a deterministic event timeline.",
|
|
256
218
|
inputSchema: {
|
|
257
219
|
type: "object",
|
|
258
|
-
properties: {
|
|
259
|
-
|
|
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"],
|
|
260
229
|
},
|
|
261
230
|
},
|
|
262
231
|
{
|
|
263
|
-
name: "
|
|
264
|
-
description: "
|
|
232
|
+
name: "loopforge_stop",
|
|
233
|
+
description: "Manually stop a loop session and return its final trajectory.",
|
|
265
234
|
inputSchema: {
|
|
266
235
|
type: "object",
|
|
267
236
|
properties: {
|
|
268
237
|
sessionId: {
|
|
269
238
|
type: "string",
|
|
270
|
-
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.",
|
|
271
254
|
},
|
|
272
255
|
},
|
|
273
256
|
required: ["sessionId"],
|
|
@@ -288,16 +271,37 @@ const TOOL_BASE_SCHEMAS = [
|
|
|
288
271
|
},
|
|
289
272
|
},
|
|
290
273
|
{
|
|
291
|
-
name: "
|
|
292
|
-
description: "
|
|
274
|
+
name: "loopforge_gate_check",
|
|
275
|
+
description: "Classify whether an action is an agent-resolvable gate or requires explicit user authority. Read-only.",
|
|
293
276
|
inputSchema: {
|
|
294
277
|
type: "object",
|
|
295
278
|
properties: {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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" },
|
|
300
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" } },
|
|
301
305
|
required: ["loopId"],
|
|
302
306
|
},
|
|
303
307
|
},
|
|
@@ -310,12 +314,18 @@ const ADVANCE_OUTPUT_SCHEMA = {
|
|
|
310
314
|
round: { type: "number" },
|
|
311
315
|
roundId: { type: ["string", "null"] },
|
|
312
316
|
prompt: { type: ["string", "null"] },
|
|
317
|
+
decision: { enum: ["accepted", "rejected", "backtracked", "stopped", "terminated", "errored"] },
|
|
318
|
+
attempt: { type: "number" },
|
|
313
319
|
stopReason: { type: "string" },
|
|
314
320
|
stopDetail: { type: "string", description: "Human-readable context for why the loop stopped. Provides facts without prescribing a specific agent action." },
|
|
315
321
|
level: { type: "string" },
|
|
316
322
|
roundSuccess: { type: "boolean" },
|
|
317
|
-
enforcementAction: { enum: ["accept", "reject", "terminate"] },
|
|
323
|
+
enforcementAction: { enum: ["accept", "reject", "terminate", "backtrack"] },
|
|
318
324
|
enforcementReason: { type: "string" },
|
|
325
|
+
projection: { type: "object", additionalProperties: true },
|
|
326
|
+
evidenceSummary: { type: "object", additionalProperties: true },
|
|
327
|
+
stateHash: { type: "string" },
|
|
328
|
+
promptHash: { type: "string" },
|
|
319
329
|
warnings: { type: "array", items: { type: "string" } },
|
|
320
330
|
},
|
|
321
331
|
additionalProperties: true,
|
|
@@ -323,7 +333,10 @@ const ADVANCE_OUTPUT_SCHEMA = {
|
|
|
323
333
|
const TOOL_OUTPUT_SCHEMAS = {
|
|
324
334
|
loopforge_start: ADVANCE_OUTPUT_SCHEMA,
|
|
325
335
|
loopforge_next: ADVANCE_OUTPUT_SCHEMA,
|
|
336
|
+
loopforge_retry: ADVANCE_OUTPUT_SCHEMA,
|
|
337
|
+
loopforge_backtrack: ADVANCE_OUTPUT_SCHEMA,
|
|
326
338
|
loopforge_resume: ADVANCE_OUTPUT_SCHEMA,
|
|
339
|
+
loopforge_history: { type: "object", additionalProperties: true },
|
|
327
340
|
loopforge_status: {
|
|
328
341
|
type: "object",
|
|
329
342
|
properties: {
|
|
@@ -360,46 +373,17 @@ const TOOL_OUTPUT_SCHEMAS = {
|
|
|
360
373
|
},
|
|
361
374
|
additionalProperties: true,
|
|
362
375
|
},
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
sessions: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
367
|
-
},
|
|
368
|
-
required: ["sessions"],
|
|
369
|
-
additionalProperties: false,
|
|
370
|
-
},
|
|
371
|
-
loopforge_replay: {
|
|
372
|
-
type: "object",
|
|
373
|
-
properties: {
|
|
374
|
-
error: { type: "string" },
|
|
375
|
-
sessionId: { type: "string" },
|
|
376
|
-
loopId: { type: "string" },
|
|
377
|
-
timeline: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
378
|
-
},
|
|
379
|
-
additionalProperties: true,
|
|
380
|
-
},
|
|
381
|
-
loopforge_health: {
|
|
382
|
-
type: "object",
|
|
383
|
-
properties: {
|
|
384
|
-
error: { type: "string" },
|
|
385
|
-
loopId: { type: "string" },
|
|
386
|
-
goal_alignment: { type: "object", additionalProperties: true },
|
|
387
|
-
constraint_integrity: { type: "object", additionalProperties: true },
|
|
388
|
-
drift_detected: { type: "boolean" },
|
|
389
|
-
strategy_stability: { type: "object", additionalProperties: true },
|
|
390
|
-
task_continuity: { type: "object", additionalProperties: true },
|
|
391
|
-
policy_metrics: { type: "object", additionalProperties: true },
|
|
392
|
-
},
|
|
393
|
-
additionalProperties: true,
|
|
394
|
-
},
|
|
376
|
+
loopforge_gate_check: { type: "object", additionalProperties: true },
|
|
377
|
+
loopforge_gate_resolve: { type: "object", additionalProperties: true },
|
|
378
|
+
loopforge_audit: { type: "object", additionalProperties: true },
|
|
395
379
|
};
|
|
396
|
-
function isRecord(value) {
|
|
397
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
398
|
-
}
|
|
399
380
|
function closeObjectSchemas(schema) {
|
|
400
381
|
const result = { ...schema };
|
|
401
382
|
if (result.type === "object") {
|
|
402
|
-
|
|
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;
|
|
403
387
|
if (isRecord(result.properties)) {
|
|
404
388
|
result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, value]) => [
|
|
405
389
|
key,
|
|
@@ -468,6 +452,13 @@ function validateSchema(value, schema, path) {
|
|
|
468
452
|
if (type === "string" && typeof value !== "string") {
|
|
469
453
|
throw new ToolInputValidationError(`${path} must be a string`);
|
|
470
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
|
+
}
|
|
471
462
|
if (type === "boolean" && typeof value !== "boolean") {
|
|
472
463
|
throw new ToolInputValidationError(`${path} must be a boolean`);
|
|
473
464
|
}
|
|
@@ -491,6 +482,55 @@ export function validateToolInput(name, input) {
|
|
|
491
482
|
throw new ToolInputValidationError(`Unknown tool: ${name}`);
|
|
492
483
|
validateSchema(input, contract.inputSchema, "arguments");
|
|
493
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
|
+
}
|
|
494
534
|
export const TOOL_HANDLERS = {
|
|
495
535
|
async loopforge_start(mgr, input) {
|
|
496
536
|
const startInput = {
|
|
@@ -502,37 +542,92 @@ export const TOOL_HANDLERS = {
|
|
|
502
542
|
constraints: Array.isArray(input.constraints)
|
|
503
543
|
? input.constraints
|
|
504
544
|
: undefined,
|
|
545
|
+
successCriteria: Array.isArray(input.successCriteria)
|
|
546
|
+
? input.successCriteria
|
|
547
|
+
: undefined,
|
|
505
548
|
};
|
|
506
549
|
if (!startInput.task.trim()) {
|
|
507
|
-
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" };
|
|
508
556
|
}
|
|
509
557
|
const result = await mgr.create(startInput);
|
|
510
|
-
|
|
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
|
+
};
|
|
511
565
|
},
|
|
512
566
|
async loopforge_next(mgr, input) {
|
|
513
567
|
const sessionId = String(input.sessionId ?? "");
|
|
514
|
-
const output = String(input.output ?? "");
|
|
515
|
-
const rawEval = input.evaluation;
|
|
516
568
|
if (!sessionId)
|
|
517
|
-
return { error: "sessionId is required" };
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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) });
|
|
526
592
|
},
|
|
527
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" };
|
|
528
620
|
const sessionId = String(input.sessionId ?? "");
|
|
529
621
|
if (!sessionId)
|
|
530
622
|
return { error: "sessionId is required" };
|
|
531
623
|
const session = mgr.get(sessionId);
|
|
532
624
|
if (!session)
|
|
533
625
|
return { error: `session not found: ${sessionId}` };
|
|
534
|
-
const metrics = session.
|
|
535
|
-
|
|
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 = {
|
|
536
631
|
sessionId: session.sessionId,
|
|
537
632
|
loopId: session.loopId,
|
|
538
633
|
round: session.currentRound,
|
|
@@ -541,9 +636,40 @@ export const TOOL_HANDLERS = {
|
|
|
541
636
|
status: session.status,
|
|
542
637
|
successTrajectory: session.successTrajectory,
|
|
543
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
|
+
},
|
|
544
670
|
metrics: {
|
|
545
671
|
vaultWriteErrors: metrics.vaultWriteErrors,
|
|
546
|
-
policy:
|
|
672
|
+
policy: mgr.getLoopPolicyMetrics(session.loopId),
|
|
547
673
|
},
|
|
548
674
|
};
|
|
549
675
|
},
|
|
@@ -557,7 +683,17 @@ export const TOOL_HANDLERS = {
|
|
|
557
683
|
const roundsCompleted = session.currentRound;
|
|
558
684
|
const successTrajectory = [...session.successTrajectory];
|
|
559
685
|
mgr.delete(sessionId);
|
|
560
|
-
|
|
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
|
+
};
|
|
561
697
|
},
|
|
562
698
|
async loopforge_pause(mgr, input) {
|
|
563
699
|
const sessionId = String(input.sessionId ?? "");
|
|
@@ -566,42 +702,53 @@ export const TOOL_HANDLERS = {
|
|
|
566
702
|
const result = mgr.pause(sessionId);
|
|
567
703
|
return { ...result };
|
|
568
704
|
},
|
|
569
|
-
async
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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 };
|
|
582
724
|
},
|
|
583
725
|
async loopforge_resume(mgr, input) {
|
|
584
726
|
const loopId = String(input.loopId ?? "");
|
|
585
727
|
if (!loopId)
|
|
586
|
-
return { error: "loopId is required" };
|
|
728
|
+
return { error: "loopId is required", decision: "errored" };
|
|
587
729
|
// Paused recovery must run first so the persisted status is atomically
|
|
588
730
|
// changed back to running before a prompt is returned.
|
|
589
731
|
let result = await mgr.unpause(loopId);
|
|
590
732
|
if (!result) {
|
|
591
|
-
result = mgr.resume(loopId);
|
|
733
|
+
result = await mgr.resume(loopId);
|
|
592
734
|
}
|
|
593
735
|
if (!result)
|
|
594
|
-
return { error: `no saved session found for loop "${loopId}"
|
|
595
|
-
return { ...result };
|
|
736
|
+
return { error: `no saved session found for loop "${loopId}"`, decision: "errored" };
|
|
737
|
+
return finalizeAdvanceOutput(mgr, { ...result });
|
|
596
738
|
},
|
|
597
|
-
async
|
|
598
|
-
|
|
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();
|
|
599
749
|
if (!loopId)
|
|
600
750
|
return { error: "loopId is required" };
|
|
601
|
-
|
|
602
|
-
if (!health)
|
|
603
|
-
return { error: `no data found for loop "${loopId}"` };
|
|
604
|
-
return health;
|
|
751
|
+
return mgr.audit(loopId);
|
|
605
752
|
},
|
|
606
753
|
};
|
|
607
754
|
//# sourceMappingURL=tools.js.map
|