@north-light/crouter 0.3.282 → 0.3.283
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/dist/api/client.d.ts +19 -3
- package/dist/api/client.js +34 -0
- package/dist/api/dto/broker-ops.d.ts +11 -0
- package/dist/api/dto/broker.d.ts +3 -8
- package/dist/api/dto/common.d.ts +1 -1
- package/dist/api/dto/messages.d.ts +2 -2
- package/dist/api/dto/node-outcomes.d.ts +80 -0
- package/dist/api/dto/node-outcomes.js +2 -0
- package/dist/api/dto/nodes.d.ts +9 -0
- package/dist/api/dto/profiles.d.ts +5 -0
- package/dist/api/dto/reports.d.ts +45 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +7 -0
- package/dist/api/routes.js +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +24 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +31 -4
- package/dist/clients/attach/overlays/graph.d.ts +5 -0
- package/dist/clients/attach/overlays/graph.js +65 -47
- package/dist/clients/attach/overlays/pickers.js +8 -9
- package/dist/clients/attach/render/card-presentation.js +1 -1
- package/dist/clients/attach/viewer.js +1286 -1260
- package/dist/commands/api-client.js +22 -23
- package/dist/commands/node/create.js +38 -3
- package/dist/commands/node/inspect.js +3 -7
- package/dist/commands/node/message.js +3 -3
- package/dist/commands/node/outcome.d.ts +2 -0
- package/dist/commands/node/outcome.js +134 -0
- package/dist/commands/node.js +2 -1
- package/dist/commands/profile/pause.js +17 -14
- package/dist/commands/push.js +102 -1
- package/dist/commands/sys/__tests__/setup-core.test.js +4 -1
- package/dist/commands/sys/panels/broker-limits-panel.js +7 -1
- package/dist/commands/sys/panels/models-panel.js +2 -2
- package/dist/commands/sys/settings-shell.d.ts +1 -1
- package/dist/commands/sys/settings-shell.js +6 -0
- package/dist/commands/sys/settings.js +3 -3
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +2 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +42 -4
- package/dist/core/__tests__/helpers/harness.d.ts +1 -0
- package/dist/core/__tests__/helpers/harness.js +9 -0
- package/dist/core/__tests__/integration/profile-pause.test.d.ts +1 -0
- package/dist/core/__tests__/integration/profile-pause.test.js +115 -0
- package/dist/core/__tests__/node-outcome.test.d.ts +1 -0
- package/dist/core/__tests__/node-outcome.test.js +184 -0
- package/dist/core/__tests__/revive-parked-fresh.test.js +13 -20
- package/dist/core/__tests__/seam/broker-provider-retry.test.js +6 -2
- package/dist/core/__tests__/seam/dormancy-release.test.js +3 -3
- package/dist/core/__tests__/seam/node-outcome-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/seam/node-outcome-delivery.test.js +153 -0
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +22 -0
- package/dist/core/canvas/canvas.d.ts +41 -3
- package/dist/core/canvas/canvas.js +153 -39
- package/dist/core/canvas/crons.js +2 -1
- package/dist/core/canvas/index.d.ts +1 -0
- package/dist/core/canvas/index.js +1 -0
- package/dist/core/canvas/migrations.js +47 -0
- package/dist/core/canvas/node-outcome-deliveries.d.ts +80 -0
- package/dist/core/canvas/node-outcome-deliveries.js +163 -0
- package/dist/core/canvas/pid.d.ts +7 -0
- package/dist/core/canvas/pid.js +17 -0
- package/dist/core/canvas/render-source.js +2 -2
- package/dist/core/canvas/types.d.ts +42 -1
- package/dist/core/canvas/types.js +2 -1
- package/dist/core/config.d.ts +4 -1
- package/dist/core/config.js +25 -1
- package/dist/core/feed/feed.d.ts +8 -0
- package/dist/core/feed/feed.js +25 -4
- package/dist/core/feed/inbox.d.ts +5 -0
- package/dist/core/feed/inbox.js +13 -0
- package/dist/core/keybindings/catalog.d.ts +1 -1
- package/dist/core/keybindings/catalog.js +3 -0
- package/dist/core/profiles/manifest.d.ts +3 -1
- package/dist/core/profiles/manifest.js +6 -1
- package/dist/core/runtime/broker/daemon-ops.d.ts +3 -1
- package/dist/core/runtime/broker/daemon-ops.js +6 -0
- package/dist/core/runtime/broker/engine-drive.d.ts +2 -3
- package/dist/core/runtime/broker/engine-drive.js +74 -27
- package/dist/core/runtime/broker/frame-dispatch.js +1 -0
- package/dist/core/runtime/broker/read-ops.js +14 -0
- package/dist/core/runtime/broker/rebind.js +0 -7
- package/dist/core/runtime/broker-protocol.d.ts +1 -6
- package/dist/core/runtime/fleet.d.ts +7 -0
- package/dist/core/runtime/headless-pi.d.ts +13 -0
- package/dist/core/runtime/headless-pi.js +10 -2
- package/dist/core/runtime/lifecycle.d.ts +6 -1
- package/dist/core/runtime/lifecycle.js +50 -1
- package/dist/core/runtime/nodes.d.ts +5 -1
- package/dist/core/runtime/nodes.js +2 -1
- package/dist/core/runtime/outcome-document.d.ts +70 -0
- package/dist/core/runtime/outcome-document.js +97 -0
- package/dist/core/runtime/revive.js +16 -21
- package/dist/core/runtime/spawn.d.ts +5 -1
- package/dist/core/runtime/spawn.js +18 -7
- package/dist/core/runtime/stamp/channel.d.ts +3 -5
- package/dist/core/runtime/stamp/channel.js +3 -10
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/structured-output.d.ts +24 -3
- package/dist/core/runtime/structured-output.js +42 -3
- package/dist/core/runtime/tmux-bindings.js +4 -0
- package/dist/core/runtime/warm-pool.d.ts +5 -2
- package/dist/core/runtime/warm-pool.js +5 -5
- package/dist/core/subscription-state.js +7 -5
- package/dist/core/substrate/render.js +10 -10
- package/dist/core/user-settings.d.ts +18 -1
- package/dist/core/user-settings.js +37 -0
- package/dist/daemon/__tests__/node-deadline-lane.test.d.ts +1 -0
- package/dist/daemon/__tests__/node-deadline-lane.test.js +76 -0
- package/dist/daemon/__tests__/node-outcome-birth-invariants.test.d.ts +1 -0
- package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +122 -0
- package/dist/daemon/__tests__/node-outcome-delivery.test.d.ts +1 -0
- package/dist/daemon/__tests__/node-outcome-delivery.test.js +86 -0
- package/dist/daemon/api/__tests__/broker-settle-park.test.js +86 -18
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +10 -1
- package/dist/daemon/api/__tests__/seam/node-outcome-api.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/node-outcome-api.test.js +159 -0
- package/dist/daemon/api/__tests__/submit-result.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/submit-result.test.js +214 -0
- package/dist/daemon/api/handlers/broker-ops.js +53 -24
- package/dist/daemon/api/handlers/canvas.js +2 -2
- package/dist/daemon/api/handlers/messages.js +12 -2
- package/dist/daemon/api/handlers/node-outcomes.d.ts +2 -0
- package/dist/daemon/api/handlers/node-outcomes.js +136 -0
- package/dist/daemon/api/handlers/nodes.js +67 -1
- package/dist/daemon/api/handlers/profiles.js +53 -1
- package/dist/daemon/api/handlers/reports.js +197 -33
- package/dist/daemon/api/map.d.ts +7 -2
- package/dist/daemon/api/map.js +78 -4
- package/dist/daemon/api/server.js +2 -0
- package/dist/daemon/crtrd.js +12 -0
- package/dist/daemon/fleet.d.ts +4 -8
- package/dist/daemon/fleet.js +48 -9
- package/dist/daemon/messaging/node-message.js +2 -1
- package/dist/daemon/node-outcome/deliver-outcome.d.ts +14 -0
- package/dist/daemon/node-outcome/deliver-outcome.js +155 -0
- package/dist/daemon/park-activity.d.ts +9 -0
- package/dist/daemon/park-activity.js +30 -0
- package/dist/daemon/park-pending.d.ts +10 -5
- package/dist/daemon/park-pending.js +18 -8
- package/dist/daemon/reconcilers/bash-deadline.js +6 -0
- package/dist/daemon/reconcilers/broker-supervision.d.ts +3 -7
- package/dist/daemon/reconcilers/broker-supervision.js +29 -33
- package/dist/daemon/reconcilers/node-deadline.d.ts +5 -0
- package/dist/daemon/reconcilers/node-deadline.js +82 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +9 -1
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +12 -3
- package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +10 -0
- package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +66 -66
- package/dist/pi-extensions/canvas-structured-output.d.ts +9 -5
- package/dist/pi-extensions/canvas-structured-output.js +23 -297
- package/dist/shared/env.d.ts +3 -0
- package/dist/shared/env.js +6 -0
- package/dist/shared/generated-context.d.ts +3 -9
- package/dist/shared/generated-context.js +8 -16
- package/dist/types.d.ts +12 -0
- package/dist/types.js +1 -0
- package/package.json +8 -8
- package/runtime.lock.json +227 -383
|
@@ -1,309 +1,35 @@
|
|
|
1
|
-
// canvas-structured-output.ts —
|
|
1
|
+
// canvas-structured-output.ts — surfaces a pending structured-output request in
|
|
2
|
+
// the system prompt so the node answers it with `crtr push result`.
|
|
2
3
|
//
|
|
3
|
-
// Loaded into every canvas node. Inert unless nodes/<id>/output-schema.json
|
|
4
|
-
// and is valid
|
|
5
|
-
//
|
|
4
|
+
// Loaded into every canvas node. Inert unless nodes/<id>/output-schema.json
|
|
5
|
+
// exists and is valid; the request file is re-read every agent run (never
|
|
6
|
+
// cached), so a request installed or cleared between runs takes effect on the
|
|
7
|
+
// next run. If the file is malformed, nothing is injected — the stop guard owns
|
|
8
|
+
// the error surface and reprompts the node about the invalid file.
|
|
9
|
+
//
|
|
10
|
+
// Validation, the result write, the report push, and the schema clear all live
|
|
11
|
+
// server-side behind `POST /nodes/{id}/result` — this extension only tells the
|
|
12
|
+
// model that a request is pending and how to answer it.
|
|
6
13
|
import { envNodeId } from '../shared/env.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
function activateTool(pi) {
|
|
17
|
-
try {
|
|
18
|
-
const current = pi.getActiveTools?.() ?? [];
|
|
19
|
-
if (!current.includes('submit'))
|
|
20
|
-
pi.setActiveTools?.([...current, 'submit']);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
/* best-effort; registration refresh normally activates new extension tools */
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function retireTool(pi) {
|
|
27
|
-
try {
|
|
28
|
-
const current = pi.getActiveTools?.() ?? [];
|
|
29
|
-
pi.setActiveTools?.(current.filter((name) => name !== 'submit'));
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
/* best-effort; the execute handler is inert when no schema file exists */
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const STRICT_STRING_FORMATS = new Set([
|
|
36
|
-
'date-time', 'time', 'date', 'duration', 'email', 'hostname', 'uri', 'ipv4', 'ipv6', 'uuid',
|
|
37
|
-
]);
|
|
38
|
-
// Provider strict modes accept a structural subset; omitted constraints stay visible in descriptions.
|
|
39
|
-
function providerStrictSchema(schema) {
|
|
40
|
-
const transform = (value) => {
|
|
41
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
42
|
-
return value;
|
|
43
|
-
const source = { ...value };
|
|
44
|
-
const take = (key) => {
|
|
45
|
-
const child = source[key];
|
|
46
|
-
delete source[key];
|
|
47
|
-
return child;
|
|
48
|
-
};
|
|
49
|
-
const transformed = {};
|
|
50
|
-
const ref = take('$ref');
|
|
51
|
-
if (ref !== undefined)
|
|
52
|
-
return { $ref: ref };
|
|
53
|
-
const defs = take('$defs');
|
|
54
|
-
if (defs !== null && typeof defs === 'object' && !Array.isArray(defs)) {
|
|
55
|
-
transformed['$defs'] = Object.fromEntries(Object.entries(defs).map(([name, definition]) => [name, transform(definition)]));
|
|
56
|
-
}
|
|
57
|
-
const type = take('type');
|
|
58
|
-
const anyOf = take('anyOf');
|
|
59
|
-
const oneOf = take('oneOf');
|
|
60
|
-
const allOf = take('allOf');
|
|
61
|
-
if (Array.isArray(anyOf))
|
|
62
|
-
transformed['anyOf'] = anyOf.map(transform);
|
|
63
|
-
else if (Array.isArray(oneOf))
|
|
64
|
-
transformed['anyOf'] = oneOf.map(transform);
|
|
65
|
-
else if (Array.isArray(allOf))
|
|
66
|
-
transformed['allOf'] = allOf.map(transform);
|
|
67
|
-
else if (type !== undefined)
|
|
68
|
-
transformed['type'] = type;
|
|
69
|
-
else
|
|
70
|
-
return value;
|
|
71
|
-
const description = take('description');
|
|
72
|
-
if (description !== undefined)
|
|
73
|
-
transformed['description'] = description;
|
|
74
|
-
const title = take('title');
|
|
75
|
-
if (title !== undefined)
|
|
76
|
-
transformed['title'] = title;
|
|
77
|
-
if (type === 'object') {
|
|
78
|
-
const properties = take('properties');
|
|
79
|
-
transformed['properties'] = properties !== null && typeof properties === 'object' && !Array.isArray(properties)
|
|
80
|
-
? Object.fromEntries(Object.entries(properties).map(([name, property]) => [name, transform(property)]))
|
|
81
|
-
: {};
|
|
82
|
-
take('additionalProperties');
|
|
83
|
-
transformed['additionalProperties'] = false;
|
|
84
|
-
const required = take('required');
|
|
85
|
-
if (required !== undefined)
|
|
86
|
-
transformed['required'] = required;
|
|
87
|
-
}
|
|
88
|
-
else if (type === 'string') {
|
|
89
|
-
const format = take('format');
|
|
90
|
-
if (typeof format === 'string' && STRICT_STRING_FORMATS.has(format))
|
|
91
|
-
transformed['format'] = format;
|
|
92
|
-
else if (format !== undefined)
|
|
93
|
-
source['format'] = format;
|
|
94
|
-
}
|
|
95
|
-
else if (type === 'array') {
|
|
96
|
-
const items = take('items');
|
|
97
|
-
if (items !== undefined)
|
|
98
|
-
transformed['items'] = transform(items);
|
|
99
|
-
const minItems = take('minItems');
|
|
100
|
-
if (minItems === 0 || minItems === 1)
|
|
101
|
-
transformed['minItems'] = minItems;
|
|
102
|
-
else if (minItems !== undefined)
|
|
103
|
-
source['minItems'] = minItems;
|
|
104
|
-
}
|
|
105
|
-
if (Object.keys(source).length > 0) {
|
|
106
|
-
const existing = typeof transformed['description'] === 'string' ? transformed['description'] : '';
|
|
107
|
-
const constraints = `{${Object.entries(source).map(([key, child]) => `${key}: ${JSON.stringify(child)}`).join(', ')}}`;
|
|
108
|
-
transformed['description'] = existing === '' ? constraints : `${existing}\n\n${constraints}`;
|
|
109
|
-
}
|
|
110
|
-
return transformed;
|
|
111
|
-
};
|
|
112
|
-
return transform(schema);
|
|
113
|
-
}
|
|
114
|
-
function strictToolSchema(schema) {
|
|
115
|
-
const transform = (value) => {
|
|
116
|
-
if (Array.isArray(value))
|
|
117
|
-
return value.map(transform);
|
|
118
|
-
if (value === null || typeof value !== 'object')
|
|
119
|
-
return value;
|
|
120
|
-
const source = value;
|
|
121
|
-
const transformed = {};
|
|
122
|
-
for (const [key, child] of Object.entries(source)) {
|
|
123
|
-
if (key === 'properties' && child !== null && typeof child === 'object' && !Array.isArray(child)) {
|
|
124
|
-
transformed[key] = Object.fromEntries(Object.entries(child).map(([name, property]) => [name, transform(property)]));
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
transformed[key] = transform(child);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const properties = transformed['properties'];
|
|
131
|
-
const isObjectSchema = transformed['type'] === 'object'
|
|
132
|
-
|| (properties !== null && typeof properties === 'object' && !Array.isArray(properties));
|
|
133
|
-
if (isObjectSchema)
|
|
134
|
-
transformed['additionalProperties'] = false;
|
|
135
|
-
if (properties === null || typeof properties !== 'object' || Array.isArray(properties))
|
|
136
|
-
return transformed;
|
|
137
|
-
const required = new Set(Array.isArray(source['required']) ? source['required'].filter((name) => typeof name === 'string') : []);
|
|
138
|
-
transformed['properties'] = Object.fromEntries(Object.entries(properties).map(([name, property]) => [
|
|
139
|
-
name,
|
|
140
|
-
required.has(name) ? property : { anyOf: [property, { type: 'null' }] },
|
|
141
|
-
]));
|
|
142
|
-
transformed['required'] = Object.keys(properties);
|
|
143
|
-
return transformed;
|
|
144
|
-
};
|
|
145
|
-
return providerStrictSchema(transform(schema));
|
|
146
|
-
}
|
|
147
|
-
function validateResult(value, schema) {
|
|
148
|
-
return validateToolArguments({ name: 'submit', description: '', parameters: schema }, { type: 'toolCall', id: 'structured-output', name: 'submit', arguments: value });
|
|
149
|
-
}
|
|
150
|
-
function omitStrictNulls(value, schema) {
|
|
151
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value) || schema === null || typeof schema !== 'object' || Array.isArray(schema))
|
|
152
|
-
return value;
|
|
153
|
-
const source = schema;
|
|
154
|
-
const properties = source['properties'];
|
|
155
|
-
if (properties === null || typeof properties !== 'object' || Array.isArray(properties))
|
|
156
|
-
return value;
|
|
157
|
-
const requiredNames = source['required'];
|
|
158
|
-
const required = new Set(Array.isArray(requiredNames) ? requiredNames.filter((name) => typeof name === 'string') : []);
|
|
159
|
-
return Object.fromEntries(Object.entries(value).flatMap(([name, child]) => {
|
|
160
|
-
if (child === null && !required.has(name) && Object.hasOwn(properties, name))
|
|
161
|
-
return [];
|
|
162
|
-
return [[name, omitStrictNulls(child, properties[name])]];
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
165
|
-
function toolDescription(request) {
|
|
166
|
-
if (request.mode === 'terminal') {
|
|
167
|
-
return 'Submit your final structured result. Calling this ends your run: the result is recorded and a final report is pushed automatically — do NOT call `crtr push final` yourself.';
|
|
168
|
-
}
|
|
169
|
-
return 'Submit the requested one-off structured result. Calling this records the result, pushes an update report automatically, frees you from the structured-output gate, and lets you continue working.';
|
|
14
|
+
import { readOutputRequest } from '../core/runtime/structured-output.js';
|
|
15
|
+
/** Render the prompt block for one valid request. Exported for the test suite. */
|
|
16
|
+
export function structuredOutputPromptBlock(request) {
|
|
17
|
+
const schema = JSON.stringify(request.schema, null, 2);
|
|
18
|
+
const mandate = request.mode === 'terminal'
|
|
19
|
+
? 'This node owes a terminal structured result. When the work is complete, submit the result with `crtr push result` (pipe the JSON on stdin) — that records it and pushes your final report in one step, finishing this node. Do NOT call `crtr push final` yourself; it is blocked while this request is pending. If the schema cannot be honestly satisfied (blocked, unavailable, bad input), decline with `crtr push result --decline "<reason>" --code <token>` instead of inventing a value — that finishes the node with a declined outcome. You cannot stop or go dormant until the result is accepted or declined.'
|
|
20
|
+
: 'This node owes a one-off structured result. Submit it with `crtr push result` (pipe the JSON on stdin) — that records it and pushes an update report to your subscribers, then you continue working normally. If the schema cannot be honestly satisfied, decline with `crtr push result --decline "<reason>" --code <token>` instead of inventing a value. You cannot stop or go dormant until the result is accepted or declined.';
|
|
21
|
+
return `<structured-output-request>\n${mandate}\n\nThe submitted JSON is validated against this schema; a mismatch rejects with field errors and records nothing, so fix and rerun.\n\nRequired schema:\n\n\`\`\`json\n${schema}\n\`\`\`\n</structured-output-request>`;
|
|
170
22
|
}
|
|
171
23
|
export function registerCanvasStructuredOutput(pi) {
|
|
172
24
|
const nodeId = envNodeId();
|
|
173
25
|
if (nodeId === undefined || nodeId.trim() === '')
|
|
174
26
|
return;
|
|
175
|
-
|
|
176
|
-
let active = false;
|
|
177
|
-
const ensureSubmitTool = (activate) => {
|
|
27
|
+
pi.on('before_agent_start', (event) => {
|
|
178
28
|
const state = readOutputRequest(nodeId);
|
|
179
|
-
|
|
180
|
-
if (state.state === 'absent') {
|
|
181
|
-
if (active) {
|
|
182
|
-
active = false;
|
|
183
|
-
retireTool(pi);
|
|
184
|
-
}
|
|
29
|
+
if (state.state !== 'valid')
|
|
185
30
|
return;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// The stop-guard will reprompt with an explicit error message.
|
|
189
|
-
if (state.state === 'invalid') {
|
|
190
|
-
if (active) {
|
|
191
|
-
active = false;
|
|
192
|
-
retireTool(pi);
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
// The request is valid; proceed with tool registration and activation.
|
|
197
|
-
const request = state.request;
|
|
198
|
-
const key = JSON.stringify(request);
|
|
199
|
-
if (registeredKey !== key) {
|
|
200
|
-
pi.registerTool({
|
|
201
|
-
name: 'submit',
|
|
202
|
-
label: 'Submit',
|
|
203
|
-
description: toolDescription(request),
|
|
204
|
-
promptSnippet: request.mode === 'terminal'
|
|
205
|
-
? 'Submit the final structured result and end this node'
|
|
206
|
-
: 'Submit the requested one-off structured result',
|
|
207
|
-
promptGuidelines: request.mode === 'terminal'
|
|
208
|
-
? [
|
|
209
|
-
'Use submit as your final action when the required structured result is ready.',
|
|
210
|
-
'After calling submit, do not call crtr push final yourself and do not emit another assistant response.',
|
|
211
|
-
]
|
|
212
|
-
: [
|
|
213
|
-
'Use submit to answer the pending one-off structured-output request before stopping or going dormant.',
|
|
214
|
-
'After one-off submit succeeds, continue with any remaining work normally.',
|
|
215
|
-
],
|
|
216
|
-
// Strict provider tools require every property to be required and every
|
|
217
|
-
// object to reject undeclared properties. Optional fields become nullable
|
|
218
|
-
// in the provider-only schema and null is removed before persistence.
|
|
219
|
-
parameters: strictToolSchema(request.schema),
|
|
220
|
-
constrainedSampling: { type: 'json_schema', strict: 'prefer' },
|
|
221
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
222
|
-
const current = readOutputRequest(nodeId);
|
|
223
|
-
if (current.state === 'absent') {
|
|
224
|
-
return {
|
|
225
|
-
content: [{ type: 'text', text: 'No structured output is currently requested.' }],
|
|
226
|
-
details: { submitted: false, reason: 'no_request' },
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
if (current.state === 'invalid') {
|
|
230
|
-
return {
|
|
231
|
-
content: [{ type: 'text', text: `Structured-output request is invalid: ${current.error}` }],
|
|
232
|
-
details: { submitted: false, reason: 'invalid_request', error: current.error },
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
if (JSON.stringify(current.request) !== key) {
|
|
236
|
-
registeredKey = null;
|
|
237
|
-
ensureSubmitTool(true);
|
|
238
|
-
return {
|
|
239
|
-
content: [{ type: 'text', text: 'The structured-output request changed while you were working. Call submit again using the current schema.' }],
|
|
240
|
-
details: { submitted: false, reason: 'schema_replaced' },
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
const result = validateResult(omitStrictNulls(params, current.request.schema), current.request.schema);
|
|
244
|
-
const resultPath = outputResultPath(nodeId);
|
|
245
|
-
const json = JSON.stringify(result, null, 2);
|
|
246
|
-
mkdirSync(dirname(resultPath), { recursive: true });
|
|
247
|
-
writeFileSync(resultPath, `${json}\n`, 'utf8');
|
|
248
|
-
const body = reportBody(resultPath, json);
|
|
249
|
-
try {
|
|
250
|
-
await publishBrokerReport(nodeId, {
|
|
251
|
-
tier: current.request.mode === 'terminal' ? 'final' : 'update',
|
|
252
|
-
body,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
catch (error) {
|
|
256
|
-
// A rejected daemon operation leaves the schema active and does not
|
|
257
|
-
// terminate Pi, so the submitted result remains retryable.
|
|
258
|
-
emitEvent({
|
|
259
|
-
event: 'broker.daemon.operation.failed',
|
|
260
|
-
level: 'error',
|
|
261
|
-
node_id: nodeId,
|
|
262
|
-
error,
|
|
263
|
-
fields: { operation: 'structured_output_report' },
|
|
264
|
-
});
|
|
265
|
-
throw error;
|
|
266
|
-
}
|
|
267
|
-
if (current.request.mode === 'terminal') {
|
|
268
|
-
removeOutputSchema(nodeId);
|
|
269
|
-
active = false;
|
|
270
|
-
retireTool(pi);
|
|
271
|
-
try {
|
|
272
|
-
ctx?.abort?.();
|
|
273
|
-
}
|
|
274
|
-
catch { /* ignore */ }
|
|
275
|
-
try {
|
|
276
|
-
ctx?.shutdown?.();
|
|
277
|
-
}
|
|
278
|
-
catch { /* ignore */ }
|
|
279
|
-
return {
|
|
280
|
-
content: [{ type: 'text', text: `Structured output submitted and finalized: ${resultPath}` }],
|
|
281
|
-
details: { submitted: true, mode: current.request.mode, resultPath, result },
|
|
282
|
-
terminate: true,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
removeOutputSchema(nodeId);
|
|
286
|
-
active = false;
|
|
287
|
-
retireTool(pi);
|
|
288
|
-
return {
|
|
289
|
-
content: [{ type: 'text', text: `Structured output submitted: ${resultPath}` }],
|
|
290
|
-
details: { submitted: true, mode: current.request.mode, resultPath, result },
|
|
291
|
-
};
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
registeredKey = key;
|
|
295
|
-
}
|
|
296
|
-
if (activate && !active) {
|
|
297
|
-
active = true;
|
|
298
|
-
activateTool(pi);
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
ensureSubmitTool(false);
|
|
302
|
-
pi.on('before_agent_start', () => {
|
|
303
|
-
ensureSubmitTool(true);
|
|
304
|
-
});
|
|
305
|
-
pi.on('agent_start', () => {
|
|
306
|
-
ensureSubmitTool(true);
|
|
31
|
+
// Appended at the END so the mandate lands after every other prompt layer.
|
|
32
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${structuredOutputPromptBlock(state.request)}` };
|
|
307
33
|
});
|
|
308
34
|
}
|
|
309
35
|
export default registerCanvasStructuredOutput;
|
package/dist/shared/env.d.ts
CHANGED
|
@@ -82,3 +82,6 @@ export declare function envUnattendedParkMs(): number | undefined;
|
|
|
82
82
|
/** Test-only park-summary grace-period override
|
|
83
83
|
* (`CRTR_TEST_PARK_SUMMARY_GRACE_MS`); no production default. */
|
|
84
84
|
export declare function envTestParkSummaryGraceMs(): number | undefined;
|
|
85
|
+
/** The bounded lifetime shared by the daemon's pending-park sweep and its
|
|
86
|
+
* in-broker isolated turn. */
|
|
87
|
+
export declare function parkSummaryGraceMs(): number;
|
package/dist/shared/env.js
CHANGED
|
@@ -195,3 +195,9 @@ export function envUnattendedParkMs() {
|
|
|
195
195
|
export function envTestParkSummaryGraceMs() {
|
|
196
196
|
return parsePositiveMsNoDefault(process.env['CRTR_TEST_PARK_SUMMARY_GRACE_MS']);
|
|
197
197
|
}
|
|
198
|
+
const DEFAULT_PARK_SUMMARY_GRACE_MS = 5 * 60_000;
|
|
199
|
+
/** The bounded lifetime shared by the daemon's pending-park sweep and its
|
|
200
|
+
* in-broker isolated turn. */
|
|
201
|
+
export function parkSummaryGraceMs() {
|
|
202
|
+
return envTestParkSummaryGraceMs() ?? DEFAULT_PARK_SUMMARY_GRACE_MS;
|
|
203
|
+
}
|
|
@@ -8,15 +8,9 @@ export declare const CONTEXT_NUDGE_CUSTOM_TYPE = "crtr-context-nudge";
|
|
|
8
8
|
export declare const REVIEW_BOUNDARY_CUSTOM_TYPE = "crtr-review-boundary";
|
|
9
9
|
/** Generic completion mandate issued by the terminal-node stop guard. */
|
|
10
10
|
export declare const STALL_REPROMPT: string;
|
|
11
|
-
/** The daemon's parking mandate
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* one update for subscribers and history, then a brief completion reply.
|
|
15
|
-
*
|
|
16
|
-
* The durable inheritance shares a CONTRACT with `node yield`'s pre-invocation
|
|
17
|
-
* guide (roadmap current, short and shrinking; context dir for in-progress
|
|
18
|
-
* material; memory only for gated permanent lessons) but not a string: yield is
|
|
19
|
-
* read by an agent choosing to refresh, this by an agent being told to conclude. */
|
|
11
|
+
/** The daemon's parking mandate for an isolated turn. It leaves current truth,
|
|
12
|
+
* supporting material when needed, lasting lessons, and one deferred update;
|
|
13
|
+
* the main conversation remains untouched and resumes without a fresh cycle. */
|
|
20
14
|
export declare const PARK_SUMMARY_PROMPT: string;
|
|
21
15
|
/** Static recovery prompts shared by the broker producer and display classifier. */
|
|
22
16
|
export declare const AUTH_FAULT_RECOVERY_BODY = "Provider credentials were just updated (a new login landed). Your previous turn stopped on a provider authentication failure. Continue from where you left off and retry the work that failed.";
|
|
@@ -13,22 +13,14 @@ export const REVIEW_BOUNDARY_CUSTOM_TYPE = 'crtr-review-boundary';
|
|
|
13
13
|
/** Generic completion mandate issued by the terminal-node stop guard. */
|
|
14
14
|
export const STALL_REPROMPT = "You've stopped but you're not waiting on anyone and haven't finished. " +
|
|
15
15
|
"Pipe the result to `crtr push final` through a single-quoted heredoc if the work is done, or use `crtr human send` if you are blocked or need the user.";
|
|
16
|
-
/** The daemon's parking mandate
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* The durable inheritance shares a CONTRACT with `node yield`'s pre-invocation
|
|
22
|
-
* guide (roadmap current, short and shrinking; context dir for in-progress
|
|
23
|
-
* material; memory only for gated permanent lessons) but not a string: yield is
|
|
24
|
-
* read by an agent choosing to refresh, this by an agent being told to conclude. */
|
|
25
|
-
export const PARK_SUMMARY_PROMPT = 'This conversation has been idle with nothing left to wake it, so it is being concluded. This is your last turn. Use it to leave a trustworthy inheritance, not to restart or broaden the work. Do these six things now, then stop.\n\n'
|
|
16
|
+
/** The daemon's parking mandate for an isolated turn. It leaves current truth,
|
|
17
|
+
* supporting material when needed, lasting lessons, and one deferred update;
|
|
18
|
+
* the main conversation remains untouched and resumes without a fresh cycle. */
|
|
19
|
+
export const PARK_SUMMARY_PROMPT = 'This conversation has been idle with nothing left to wake it, so it is being concluded. This is your last turn. Use it to leave a trustworthy inheritance, not to restart or broaden the work. Do these four things now, then stop.\n\n'
|
|
26
20
|
+ '1. Establish current truth. Check only state that may have changed outside the transcript and matters to resuming—such as the working tree, a remote run, or an external decision. Do not start new work; perform only a quick check needed to avoid recording an unverified claim. If no mandate or work ever began, record that plainly in the inheritance and keep every artifact minimal.\n\n'
|
|
27
|
-
+ '2.
|
|
28
|
-
+ '3.
|
|
29
|
-
+ '4.
|
|
30
|
-
+ '5. Push exactly one regular update with `crtr push update --tier deferred`, never `crtr push final`. Write for someone who has not seen this conversation: name the work in plain terms, say where it stands, and say what remains or is blocked. Put the current outcome in the first line; include only needed decisions and recovery handles. This concludes the conversation, not the mandate.\n\n'
|
|
31
|
-
+ '6. When finished, reply with exactly `done`.';
|
|
21
|
+
+ '2. Put supporting material in your context directory only when it is needed to keep the inheritance concise. Rewrite existing living documents rather than leave superseded versions. Task state, identifiers, and recovery detail belong here, not in memory.\n\n'
|
|
22
|
+
+ '3. Use memory only for a non-obvious, reusable lesson that should survive this task and is not already recorded. Read `crtr memory write -h`, find before writing, and choose the narrowest scope that will reach the next agent who needs it. Do not put a conversation recap, task status, recovery handles, or facts already captured in code or docs into memory.\n\n'
|
|
23
|
+
+ '4. Push exactly one regular update with `crtr push update --tier deferred`, never `crtr push final`. Write for someone who has not seen this conversation: name the work in plain terms, say where it stands, and say what remains or is blocked. Put the current outcome in the first line; include only needed decisions and recovery handles, then stop.';
|
|
32
24
|
/** Static recovery prompts shared by the broker producer and display classifier. */
|
|
33
25
|
export const AUTH_FAULT_RECOVERY_BODY = 'Provider credentials were just updated (a new login landed). Your previous turn stopped on a provider authentication failure. Continue from where you left off and retry the work that failed.';
|
|
34
26
|
export const CONNECTION_FAULT_RECOVERY_BODY = 'The network connection is back online. Your previous turn stopped on a connection error (the network was down). Continue from where you left off and retry the work that failed.';
|
|
@@ -37,7 +29,7 @@ const REVIEW_APPROVAL_OPEN = '<crtr-review-approval>';
|
|
|
37
29
|
const REVIEW_APPROVAL_CLOSE = '</crtr-review-approval>';
|
|
38
30
|
const MODEL_FALLBACK_RECOVERY_OPEN = '<model-fallback-recovery>';
|
|
39
31
|
const MODEL_FALLBACK_RECOVERY_CLOSE = '</model-fallback-recovery>';
|
|
40
|
-
const STRUCTURED_OUTPUT_REPROMPT_PREFIX = 'You must
|
|
32
|
+
const STRUCTURED_OUTPUT_REPROMPT_PREFIX = 'You must submit a result matching the required schema with `crtr push result` before you can stop, or decline it with `crtr push result --decline "<reason>" --code <token>` when the schema cannot be honestly satisfied. You cannot finish or go dormant any other way while this request is pending.\n\nRequired schema:\n\n```json\n';
|
|
41
33
|
const STRUCTURED_OUTPUT_REPROMPT_SUFFIX = '\n```';
|
|
42
34
|
/** Format the stop guard's dynamic structured-output mandate. */
|
|
43
35
|
export function formatStructuredOutputReprompt(schema) {
|
package/dist/types.d.ts
CHANGED
|
@@ -211,6 +211,14 @@ export interface ModelRoutingConfig {
|
|
|
211
211
|
familyOrder?: string[];
|
|
212
212
|
strengthFallback?: ModelStrength[];
|
|
213
213
|
}
|
|
214
|
+
/** Per-provider behavior options, keyed by RUNTIME provider id (`openai-codex`,
|
|
215
|
+
* `anthropic`) — not the portable ladder key (`openai`). Merged per provider
|
|
216
|
+
* across scopes like the rest of `ScopeConfig`. */
|
|
217
|
+
export interface ProviderOptionsConfig {
|
|
218
|
+
/** Codex fast mode: ask the ChatGPT backend to serve the turn at its
|
|
219
|
+
* priority tier (~1.5x generation speed, ~2x credit consumption). */
|
|
220
|
+
fastMode?: boolean;
|
|
221
|
+
}
|
|
214
222
|
/** Launch metadata for one kind (a top-level kind like `developer`, or a full
|
|
215
223
|
* sub-kind string like `audit/security`) — the settings-file
|
|
216
224
|
* replacement for per-kind persona frontmatter. `model`/`tools`/`extensions`
|
|
@@ -330,6 +338,10 @@ export interface ScopeConfig {
|
|
|
330
338
|
modelRoutes?: Record<string, ModelRouteConfig>;
|
|
331
339
|
/** Ordering overrides for declared model routes. */
|
|
332
340
|
modelRouting?: ModelRoutingConfig;
|
|
341
|
+
/** Per-provider behavior options keyed by runtime provider id
|
|
342
|
+
* (`openai-codex`, `anthropic`). Merged per provider across scopes: a
|
|
343
|
+
* nearer scope overrides only the keys it names. */
|
|
344
|
+
providerOptions: Record<string, ProviderOptionsConfig>;
|
|
333
345
|
/** The kind registry (spec §1.5): kind existence + launch knobs, keyed by
|
|
334
346
|
* full kind string (top-level e.g. `developer`, or sub-kind e.g.
|
|
335
347
|
* `audit/security`). Builtins ship a default registry via
|
package/dist/types.js
CHANGED
|
@@ -103,6 +103,7 @@ export function defaultScopeConfig() {
|
|
|
103
103
|
keybindings: {},
|
|
104
104
|
tmux_passthrough: [],
|
|
105
105
|
modelLadders: defaultModelLaddersConfig(),
|
|
106
|
+
providerOptions: {},
|
|
106
107
|
kinds: defaultKindsConfig(),
|
|
107
108
|
remoteCanvas: defaultRemoteCanvasConfig(),
|
|
108
109
|
spawnEnv: { allow: [] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.283",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -93,9 +93,9 @@
|
|
|
93
93
|
"test:all": "npm test && npm run test:integration && npm run test:seam"
|
|
94
94
|
},
|
|
95
95
|
"overrides": {
|
|
96
|
-
"@earendil-works/pi-ai": "0.84.
|
|
97
|
-
"@earendil-works/pi-agent-core": "0.84.
|
|
98
|
-
"@earendil-works/pi-tui": "0.84.
|
|
96
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
97
|
+
"@earendil-works/pi-agent-core": "0.84.4",
|
|
98
|
+
"@earendil-works/pi-tui": "0.84.4"
|
|
99
99
|
},
|
|
100
100
|
"repository": {
|
|
101
101
|
"type": "git",
|
|
@@ -106,10 +106,10 @@
|
|
|
106
106
|
},
|
|
107
107
|
"license": "MIT",
|
|
108
108
|
"dependencies": {
|
|
109
|
-
"@earendil-works/pi-agent-core": "0.84.
|
|
110
|
-
"@earendil-works/pi-ai": "0.84.
|
|
111
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
112
|
-
"@earendil-works/pi-tui": "0.84.
|
|
109
|
+
"@earendil-works/pi-agent-core": "0.84.4",
|
|
110
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
111
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
112
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
113
113
|
"cron-parser": "^5.6.0",
|
|
114
114
|
"esbuild": "^0.27.7",
|
|
115
115
|
"proper-lockfile": "4.1.2",
|