@north-light/crouter 0.3.281 → 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/plugin-manifest-schema.d.ts +208 -0
- package/dist/api/plugin-manifest-schema.js +23 -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/cli.js +8 -2
- 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/pkg/browse/catalog.js +1 -1
- package/dist/commands/pkg/plugin-manage.d.ts +46 -1
- package/dist/commands/pkg/plugin-manage.js +170 -20
- 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/plugin-revalidate.test.d.ts +1 -0
- package/dist/core/__tests__/integration/plugin-revalidate.test.js +355 -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/command-manifests/registry.d.ts +3 -1
- package/dist/core/command-manifests/schema.d.ts +2 -75
- package/dist/core/command-manifests/schema.js +5 -0
- package/dist/core/command-plugins/compose.js +1 -1
- package/dist/core/command-plugins/revalidate.d.ts +18 -0
- package/dist/core/command-plugins/revalidate.js +152 -0
- package/dist/core/command-plugins/transport/http-fetch.d.ts +29 -4
- package/dist/core/command-plugins/transport/http-fetch.js +18 -8
- package/dist/core/command-plugins/transport/http-invoke.js +7 -0
- package/dist/core/command.d.ts +8 -0
- package/dist/core/command.js +19 -3
- package/dist/core/config.d.ts +4 -1
- package/dist/core/config.js +25 -1
- package/dist/core/exclusive-lock.d.ts +12 -0
- package/dist/core/exclusive-lock.js +29 -0
- 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/installed-plugins.js +31 -0
- package/dist/core/keybindings/catalog.d.ts +1 -1
- package/dist/core/keybindings/catalog.js +3 -0
- package/dist/core/manifest-recovery.d.ts +15 -0
- package/dist/core/manifest-recovery.js +72 -0
- package/dist/core/manifest-stale.d.ts +18 -0
- package/dist/core/manifest-stale.js +39 -0
- package/dist/core/plugin-swap-lock.d.ts +9 -0
- package/dist/core/plugin-swap-lock.js +31 -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 +19 -0
- package/dist/types.js +1 -0
- package/package.json +9 -8
- package/runtime.lock.json +227 -383
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { emitEvent } from '../../events/emit.js';
|
|
2
2
|
import { operationIdContext } from '../../events/operation-id.js';
|
|
3
|
+
import { parkSummaryGraceMs } from '../../../shared/env.js';
|
|
4
|
+
import { headlessTurn } from '../headless-pi.js';
|
|
5
|
+
import { completeBrokerPark, recordBrokerParkActivity } from './daemon-ops.js';
|
|
3
6
|
import { promptWithAdvertisedCommandInvocation } from '../advertised-command-invocation.js';
|
|
4
7
|
import { heldDeferredInboxPromptJoin } from './held-deferred-inbox.js';
|
|
5
8
|
import { announceMessageOrigin } from '../stamp/channel.js';
|
|
6
|
-
import { clearTurnVisibility, currentTurnVisibility, setTurnVisibility, } from '../turn-visibility.js';
|
|
7
9
|
import { chooseGuidanceDeliveryMode, promptWithNextTurnGuidance, resolveEngineRoute, resolveSteerCall, } from './engine-routing.js';
|
|
8
10
|
export function createEngineDriver(deps) {
|
|
9
|
-
const { registry, replies, session, dispatches, memoryRefs, authReloadGate, rebind, projection, toolGroups, turnAdmission, } = deps;
|
|
11
|
+
const { nodeId, registry, replies, session, dispatches, memoryRefs, authReloadGate, rebind, projection, toolGroups, turnAdmission, } = deps;
|
|
10
12
|
const { ackTo, engineErrorTo } = replies;
|
|
11
13
|
const errorRelay = (client) => {
|
|
12
14
|
let failure;
|
|
@@ -36,32 +38,62 @@ export function createEngineDriver(deps) {
|
|
|
36
38
|
error: err,
|
|
37
39
|
});
|
|
38
40
|
});
|
|
41
|
+
let parkingSummaryInFlight = false;
|
|
42
|
+
const recordParkActivityBeforeInput = async () => {
|
|
43
|
+
if (!parkingSummaryInFlight)
|
|
44
|
+
return;
|
|
45
|
+
const result = await recordBrokerParkActivity(nodeId);
|
|
46
|
+
if (result.activity !== 'recorded') {
|
|
47
|
+
throw new Error('parking completion took priority before input admission');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const runParkTurn = (text) => {
|
|
51
|
+
parkingSummaryInFlight = true;
|
|
52
|
+
const liveSession = session();
|
|
53
|
+
const model = liveSession.model;
|
|
54
|
+
const source = liveSession.sessionManager;
|
|
55
|
+
const run = model === undefined || typeof model.provider !== 'string' || typeof model.id !== 'string'
|
|
56
|
+
? Promise.resolve('failed')
|
|
57
|
+
: headlessTurn({
|
|
58
|
+
systemPrompt: liveSession.systemPrompt,
|
|
59
|
+
userPrompt: text,
|
|
60
|
+
modelSpec: `${model.provider}/${model.id}`,
|
|
61
|
+
timeoutMs: parkSummaryGraceMs(),
|
|
62
|
+
seedMessages: source.buildSessionContext().messages,
|
|
63
|
+
sessionId: source.getSessionId(),
|
|
64
|
+
toolPolicy: 'builtin',
|
|
65
|
+
});
|
|
66
|
+
void run
|
|
67
|
+
.catch(() => 'failed')
|
|
68
|
+
.then((outcome) => completeBrokerPark(nodeId, { outcome }))
|
|
69
|
+
.then((directive) => {
|
|
70
|
+
if (directive.action === 'shutdown')
|
|
71
|
+
rebind.requestShutdown();
|
|
72
|
+
})
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
emitEvent({ level: 'error', event: 'broker.park.complete_failed', error });
|
|
75
|
+
})
|
|
76
|
+
.finally(() => {
|
|
77
|
+
parkingSummaryInFlight = false;
|
|
78
|
+
});
|
|
79
|
+
};
|
|
39
80
|
const drivePromptOrFollowUp = (client, frame, errors) => {
|
|
40
81
|
const relayError = errors.relay;
|
|
41
82
|
// Settle on the engine call's outcome: the failure is set by relayError
|
|
42
83
|
// before the caught promise resolves, so reading it after the chain is
|
|
43
84
|
// the call's verdict.
|
|
44
85
|
const settle = (p) => p.then(() => errors.failure());
|
|
45
|
-
const
|
|
86
|
+
const drive = (release) => {
|
|
46
87
|
// A prior prompt can be in asynchronous preflight while isStreaming is
|
|
47
88
|
// false. Admission waits for that preflight, then routes against the
|
|
48
89
|
// state it actually established instead of its stale snapshot.
|
|
49
90
|
const route = resolveEngineRoute(frame, session().isStreaming);
|
|
50
91
|
const promptOptions = route.call === 'prompt' ? route.options : undefined;
|
|
51
92
|
const guidance = memoryRefs.guidanceFor(frame.text);
|
|
52
|
-
const deliveryIsInternal = frame.delivery?.origin?.type === 'runtime'
|
|
53
|
-
&& frame.delivery.origin.kind === 'park'
|
|
54
|
-
&& route.call === 'prompt'
|
|
55
|
-
&& route.options.streamingBehavior === undefined;
|
|
56
93
|
const announceDelivery = () => {
|
|
57
|
-
if (frame.delivery === undefined)
|
|
94
|
+
if (frame.delivery?.origin === undefined)
|
|
58
95
|
return;
|
|
59
|
-
|
|
60
|
-
if (frame.delivery.origin === undefined) {
|
|
61
|
-
setTurnVisibility('visible');
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
announceMessageOrigin(frame.text, frame.delivery.origin, visibility);
|
|
96
|
+
announceMessageOrigin(frame.text, frame.delivery.origin);
|
|
65
97
|
};
|
|
66
98
|
const prompt = async () => {
|
|
67
99
|
// Both human surfaces converge at this admitted boundary. The watcher
|
|
@@ -77,18 +109,7 @@ export function createEngineDriver(deps) {
|
|
|
77
109
|
}
|
|
78
110
|
}
|
|
79
111
|
announceDelivery();
|
|
80
|
-
|
|
81
|
-
type: 'turn_visibility',
|
|
82
|
-
visibility: currentTurnVisibility(),
|
|
83
|
-
});
|
|
84
|
-
try {
|
|
85
|
-
await session().prompt(frame.text, { ...promptOptions, preflightResult: release });
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
if (deliveryIsInternal)
|
|
89
|
-
clearTurnVisibility();
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
112
|
+
await session().prompt(frame.text, { ...promptOptions, preflightResult: release });
|
|
92
113
|
};
|
|
93
114
|
if (guidance === null) {
|
|
94
115
|
if (route.call === 'prompt') {
|
|
@@ -125,6 +146,19 @@ export function createEngineDriver(deps) {
|
|
|
125
146
|
.followUp(frame.text, frame.images)
|
|
126
147
|
.catch(relayError)
|
|
127
148
|
.then(() => sendGuidance(guidance, deliverAs)));
|
|
149
|
+
};
|
|
150
|
+
const dispatch = () => turnAdmission.admit((release) => {
|
|
151
|
+
// Do not make the normal path async: prompt admission and auth reload
|
|
152
|
+
// ordering depend on Pi seeing this call in the current stack.
|
|
153
|
+
if (!parkingSummaryInFlight)
|
|
154
|
+
return drive(release);
|
|
155
|
+
// This operation is the input-admission half of an in-flight parking
|
|
156
|
+
// summary: it completes before Pi receives the prompt, so completion
|
|
157
|
+
// cannot tear down a viewer or live delivery that won admission.
|
|
158
|
+
return recordParkActivityBeforeInput().then(() => drive(release), (error) => {
|
|
159
|
+
relayError(error);
|
|
160
|
+
return errors.failure();
|
|
161
|
+
});
|
|
128
162
|
});
|
|
129
163
|
// /login acknowledges before its asynchronous local refresh completes.
|
|
130
164
|
// An idle turn must not overtake that refresh and launch on the stale
|
|
@@ -180,7 +214,7 @@ export function createEngineDriver(deps) {
|
|
|
180
214
|
*/
|
|
181
215
|
const driveSteer = (frame, errors) => {
|
|
182
216
|
const relayError = errors.relay;
|
|
183
|
-
|
|
217
|
+
const drive = (release) => {
|
|
184
218
|
if (resolveSteerCall(frame.text, session()) === 'prompt') {
|
|
185
219
|
return promptWithAdvertisedCommandInvocation(session(), frame.text, () => session().prompt(frame.text, {
|
|
186
220
|
images: frame.images,
|
|
@@ -196,6 +230,13 @@ export function createEngineDriver(deps) {
|
|
|
196
230
|
return session()
|
|
197
231
|
.steer(frame.text, frame.images)
|
|
198
232
|
.then(() => sendGuidance(guidance, 'steer'));
|
|
233
|
+
};
|
|
234
|
+
void turnAdmission.admit((release) => {
|
|
235
|
+
if (!parkingSummaryInFlight)
|
|
236
|
+
return drive(release);
|
|
237
|
+
return recordParkActivityBeforeInput().then(() => drive(release), (error) => {
|
|
238
|
+
relayError(error);
|
|
239
|
+
});
|
|
199
240
|
}).catch(relayError);
|
|
200
241
|
};
|
|
201
242
|
/**
|
|
@@ -285,6 +326,12 @@ export function createEngineDriver(deps) {
|
|
|
285
326
|
* and returns `undefined`.
|
|
286
327
|
*/
|
|
287
328
|
return (client, frame) => {
|
|
329
|
+
if ((frame.type === 'prompt' || frame.type === 'follow_up')
|
|
330
|
+
&& frame.delivery?.origin?.type === 'runtime'
|
|
331
|
+
&& frame.delivery.origin.kind === 'park') {
|
|
332
|
+
runParkTurn(frame.text);
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
288
335
|
const errors = errorRelay(client);
|
|
289
336
|
switch (frame.type) {
|
|
290
337
|
case 'prompt':
|
|
@@ -104,8 +104,19 @@ export function buildSettingsData(id, session) {
|
|
|
104
104
|
catch {
|
|
105
105
|
/* loader without themes — viewer falls back to its local theme registry */
|
|
106
106
|
}
|
|
107
|
+
const defaultProvider = manager.getDefaultProvider();
|
|
108
|
+
const defaultModelId = manager.getDefaultModel();
|
|
109
|
+
let availableDefaultModels = [];
|
|
110
|
+
try {
|
|
111
|
+
availableDefaultModels = session.modelRuntime.getAvailableSnapshot();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* the snapshot touches auth config; degrade to none known available */
|
|
115
|
+
}
|
|
107
116
|
const settings = {
|
|
108
117
|
autoCompact: session.autoCompactionEnabled,
|
|
118
|
+
defaultModel: defaultProvider && defaultModelId ? `${defaultProvider}/${defaultModelId}` : 'not set',
|
|
119
|
+
availableDefaultModels,
|
|
109
120
|
showImages: manager.getShowImages(),
|
|
110
121
|
imageWidthCells: manager.getImageWidthCells(),
|
|
111
122
|
autoResizeImages: manager.getImageAutoResize(),
|
|
@@ -117,6 +128,7 @@ export function buildSettingsData(id, session) {
|
|
|
117
128
|
httpIdleTimeoutMs: manager.getHttpIdleTimeoutMs(),
|
|
118
129
|
thinkingLevel: session.thinkingLevel,
|
|
119
130
|
availableThinkingLevels: session.getAvailableThinkingLevels(),
|
|
131
|
+
modelThinkingLevels: manager.getAllModelThinkingLevels(),
|
|
120
132
|
currentTheme: manager.getTheme() ?? '',
|
|
121
133
|
terminalTheme: 'dark',
|
|
122
134
|
availableThemes,
|
|
@@ -133,7 +145,9 @@ export function buildSettingsData(id, session) {
|
|
|
133
145
|
clearOnShrink: manager.getClearOnShrink(),
|
|
134
146
|
showTerminalProgress: manager.getShowTerminalProgress(),
|
|
135
147
|
tuiMode: manager.getTuiMode(),
|
|
148
|
+
fullscreenExitOutput: manager.getFullscreenExitOutput(),
|
|
136
149
|
fullscreenScrollbar: manager.getFullscreenScrollbar(),
|
|
150
|
+
fullscreenCopyOnSelect: manager.getFullscreenCopyOnSelect(),
|
|
137
151
|
warnings: manager.getWarnings(),
|
|
138
152
|
defaultProjectTrust: manager.getDefaultProjectTrust(),
|
|
139
153
|
showCacheMissNotices: manager.getShowCacheMissNotices(),
|
|
@@ -6,7 +6,6 @@ import { operationIdContext } from '../../events/operation-id.js';
|
|
|
6
6
|
import { installPersonaTransitionGate } from '../broker-persona-guidance.js';
|
|
7
7
|
import { abortSessionFromExtension } from './extension-abort.js';
|
|
8
8
|
import { clearFault, invalidateProviderRetryEpisode, readFault } from '../fault.js';
|
|
9
|
-
import { clearTurnVisibility } from '../turn-visibility.js';
|
|
10
9
|
const MAX_ADMITTED_REPLACEMENTS = 2;
|
|
11
10
|
const REPLACEMENT_DEADLINE_MS = 30_000;
|
|
12
11
|
const REPLACEMENT_DEADLINE_EXCEEDED = Symbol('replacement-deadline-exceeded');
|
|
@@ -303,12 +302,6 @@ export class RebindDriver {
|
|
|
303
302
|
catch (err) {
|
|
304
303
|
emitEvent({ level: 'debug', event: 'broker.runtime.diagnostic', fields: { message: `event relay threw: ${String(err)}` } });
|
|
305
304
|
}
|
|
306
|
-
finally {
|
|
307
|
-
// Projection sees the settlement while the run is still internal; every
|
|
308
|
-
// later input and output fails open to visible after this boundary.
|
|
309
|
-
if (event.type === 'agent_settled')
|
|
310
|
-
clearTurnVisibility();
|
|
311
|
-
}
|
|
312
305
|
}
|
|
313
306
|
ackTo(client, op, ok = true, detail, id) {
|
|
314
307
|
this.deps.registry.sendFrame(client, { type: 'ack', for: op, ok, ...(detail !== undefined ? { detail } : {}), ...(id !== undefined ? { id } : {}) });
|
|
@@ -402,11 +402,6 @@ export interface WelcomeFrame {
|
|
|
402
402
|
* the other four learn nothing); the PAYLOAD is pi's — `Model`, exactly what
|
|
403
403
|
* pi's own `set_model` response carries and what `snapshot.state.model` holds.
|
|
404
404
|
* Undefined when the engine has none. */
|
|
405
|
-
export interface TurnVisibilityFrame {
|
|
406
|
-
type: 'turn_visibility';
|
|
407
|
-
/** Presentation visibility of the run whose native events follow. */
|
|
408
|
-
visibility: MessageVisibility;
|
|
409
|
-
}
|
|
410
405
|
export interface WorkingActivityFrame {
|
|
411
406
|
type: 'working_activity';
|
|
412
407
|
/** Full display label selected from the user's configured gerunds. */
|
|
@@ -652,7 +647,7 @@ export interface ToolGroupSummaryFrame {
|
|
|
652
647
|
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
653
648
|
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
654
649
|
* non-colliding `type` discriminants. */
|
|
655
|
-
export type BrokerToClient = WelcomeFrame |
|
|
650
|
+
export type BrokerToClient = WelcomeFrame | WorkingActivityFrame | ModelChangedFrame | ResourcesReloadedFrame | ErrorFrame | NodeNamedFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
|
|
656
651
|
/** Encode one frame as a single newline-terminated JSON line. */
|
|
657
652
|
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
658
653
|
/** Byte bounds for a {@link FrameDecoder} (C5). */
|
|
@@ -58,6 +58,13 @@ export interface FleetRegistry {
|
|
|
58
58
|
/** Give back a slot claimed by `reserveSlot` and not consumed by a launch. */
|
|
59
59
|
releaseReservation(nodeId: string): void;
|
|
60
60
|
entries(): Iterable<[string, FleetEntry]>;
|
|
61
|
+
/** Mark this exact broker's pause interruption before directly sending it
|
|
62
|
+
* SIGTERM. An explicit pid is the identity-checked fallback when the daemon
|
|
63
|
+
* has not registered a handle yet. */
|
|
64
|
+
interruptForProfilePause(nodeId: string, pid?: number): void;
|
|
65
|
+
/** Whether the next dead-row policy pass must absorb this pause interruption
|
|
66
|
+
* before a later pass may recover the row normally. */
|
|
67
|
+
hasProfilePauseInterruption(nodeId: string): boolean;
|
|
61
68
|
/** Drop an entry without policy (terminal exits + close paths). */
|
|
62
69
|
forget(nodeId: string): void;
|
|
63
70
|
/** Wired by `headlessBrokerHost.launch` to the handle's `exited` promise.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ConstrainedSamplingConfig } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
2
3
|
import type { TSchema } from 'typebox';
|
|
3
4
|
export interface HeadlessTool {
|
|
4
5
|
name: string;
|
|
@@ -15,6 +16,12 @@ export interface HeadlessRequest {
|
|
|
15
16
|
modelIntent?: string;
|
|
16
17
|
timeoutMs: number;
|
|
17
18
|
tool?: HeadlessTool;
|
|
19
|
+
/** Exact context to seed into an isolated in-memory session. */
|
|
20
|
+
seedMessages?: readonly AgentMessage[];
|
|
21
|
+
/** Reuses a provider cache identity without binding or persisting a session. */
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
/** Omit `noTools` to load Pi's builtin tool suite. */
|
|
24
|
+
toolPolicy?: 'builtin';
|
|
18
25
|
}
|
|
19
26
|
/** Run a one-shot call whose only output channel is one structured tool call. */
|
|
20
27
|
export declare function headlessToolCall(request: HeadlessRequest & {
|
|
@@ -22,3 +29,9 @@ export declare function headlessToolCall(request: HeadlessRequest & {
|
|
|
22
29
|
}): Promise<unknown>;
|
|
23
30
|
/** Run a one-shot call and return the final assistant text. */
|
|
24
31
|
export declare function headlessText(request: HeadlessRequest): Promise<string>;
|
|
32
|
+
/** Run a tool-capable, extension-free in-memory turn against seeded context. */
|
|
33
|
+
export declare function headlessTurn(request: HeadlessRequest & {
|
|
34
|
+
seedMessages: readonly AgentMessage[];
|
|
35
|
+
sessionId: string;
|
|
36
|
+
toolPolicy: 'builtin';
|
|
37
|
+
}): Promise<'completed' | 'failed'>;
|
|
@@ -112,13 +112,17 @@ async function runHeadless(request, fallback, collect) {
|
|
|
112
112
|
};
|
|
113
113
|
},
|
|
114
114
|
});
|
|
115
|
+
const sessionManager = sdk.SessionManager.inMemory(nodeCwd, request.sessionId === undefined ? undefined : { id: request.sessionId });
|
|
116
|
+
for (const message of request.seedMessages ?? []) {
|
|
117
|
+
sessionManager.appendMessage(message);
|
|
118
|
+
}
|
|
115
119
|
({ session } = await sdk.createAgentSessionFromServices({
|
|
116
120
|
services,
|
|
117
|
-
sessionManager
|
|
121
|
+
sessionManager,
|
|
118
122
|
model: selected.model,
|
|
119
123
|
thinkingLevel: 'off',
|
|
120
124
|
...(tool === undefined
|
|
121
|
-
? { noTools: 'all' }
|
|
125
|
+
? request.toolPolicy === 'builtin' ? {} : { noTools: 'all' }
|
|
122
126
|
: { tools: [tool.name], customTools: [customTool] }),
|
|
123
127
|
}));
|
|
124
128
|
if (timedOut)
|
|
@@ -151,3 +155,7 @@ export function headlessToolCall(request) {
|
|
|
151
155
|
export function headlessText(request) {
|
|
152
156
|
return runHeadless(request, '', (session) => lastAssistantText(session.messages));
|
|
153
157
|
}
|
|
158
|
+
/** Run a tool-capable, extension-free in-memory turn against seeded context. */
|
|
159
|
+
export function headlessTurn(request) {
|
|
160
|
+
return runHeadless(request, 'failed', () => 'completed');
|
|
161
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type { NodeMeta, NodeStatus, ExitIntent, TerminalReason } from '../canvas/types.js';
|
|
1
|
+
import type { NodeMeta, NodeOutcomeDetailV1, NodeStatus, ExitIntent, TerminalReason } from '../canvas/types.js';
|
|
2
2
|
/** The lifecycle events — the only vocabulary for moving a node's status/intent.
|
|
3
3
|
* Each maps (in the table below) to a target status and/or intent plus the set
|
|
4
4
|
* of from-statuses it is legal from. */
|
|
5
5
|
export type LifecycleEvent = 'finish' | 'park' | 'cancel' | 'crash' | 'yield' | 'release' | 'refresh-launch' | 'revive';
|
|
6
|
+
export interface TerminalOutcomeInput {
|
|
7
|
+
/** Omit to derive: a lifecycle terminal transition is always a failure. */
|
|
8
|
+
detail?: NodeOutcomeDetailV1;
|
|
9
|
+
}
|
|
6
10
|
/** Enact a lifecycle event on a node: validate the from-status against the
|
|
7
11
|
* table, then write status+intent in ONE atomic statement (so they can never
|
|
8
12
|
* disagree). Returns the hydrated node view after the write.
|
|
@@ -11,6 +15,7 @@ export type LifecycleEvent = 'finish' | 'park' | 'cancel' | 'crash' | 'yield' |
|
|
|
11
15
|
* legality authoritative at SQLite write time rather than at a stale read. */
|
|
12
16
|
export declare function transition(nodeId: string, event: LifecycleEvent, opts?: {
|
|
13
17
|
reason?: TerminalReason;
|
|
18
|
+
outcome?: TerminalOutcomeInput;
|
|
14
19
|
}): NodeMeta;
|
|
15
20
|
/** Whether this row is a PARKED node — terminalized by the unattended clock
|
|
16
21
|
* rather than by finishing its own work. The one read of the park marker:
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// unknown-node/illegal-move/already-finalized-cancel branching — stays here;
|
|
32
32
|
// only the raw conditional UPDATE lives in canvas.ts, keeping every `.prepare(`
|
|
33
33
|
// inside `core/canvas/`).
|
|
34
|
-
import { getNode, cancelCronsOnWake, transitionStatusIntent } from '../canvas/index.js';
|
|
34
|
+
import { getNode, cancelCronsOnWake, composeOutcomeDeliveryDocument, readOutcomeDelivery, transitionStatusIntent } from '../canvas/index.js';
|
|
35
35
|
const ANY = '*';
|
|
36
36
|
const TERMINAL_REASONS = {
|
|
37
37
|
finish: 'finished',
|
|
@@ -72,6 +72,29 @@ const TRANSITIONS = {
|
|
|
72
72
|
// reviveNode ordinary launch · resetRoot · stophook boot confirmation.
|
|
73
73
|
revive: { status: 'active', intent: null, from: ANY },
|
|
74
74
|
};
|
|
75
|
+
function firstLineAtMost(value, maxBytes) {
|
|
76
|
+
const line = value.split('\n')[0] ?? '';
|
|
77
|
+
let bytes = 0;
|
|
78
|
+
let result = '';
|
|
79
|
+
for (const character of line) {
|
|
80
|
+
const next = Buffer.byteLength(character, 'utf8');
|
|
81
|
+
if (bytes + next > maxBytes)
|
|
82
|
+
break;
|
|
83
|
+
bytes += next;
|
|
84
|
+
result += character;
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function detailJson(detail) {
|
|
89
|
+
if (detail === undefined)
|
|
90
|
+
return null;
|
|
91
|
+
const message = detail.message === undefined ? undefined : firstLineAtMost(detail.message, 500);
|
|
92
|
+
const bounded = { ...detail, ...(message === undefined ? {} : { message }) };
|
|
93
|
+
const serialized = JSON.stringify(bounded);
|
|
94
|
+
if (Buffer.byteLength(serialized, 'utf8') <= 4 * 1024)
|
|
95
|
+
return serialized;
|
|
96
|
+
return JSON.stringify({ schema: 'crtr.node-outcome-detail/v1', truncated: true, ...(message === undefined ? {} : { message }) });
|
|
97
|
+
}
|
|
75
98
|
/** Enact a lifecycle event on a node: validate the from-status against the
|
|
76
99
|
* table, then write status+intent in ONE atomic statement (so they can never
|
|
77
100
|
* disagree). Returns the hydrated node view after the write.
|
|
@@ -90,10 +113,36 @@ export function transition(nodeId, event, opts) {
|
|
|
90
113
|
fields.intent = spec.intent ?? null;
|
|
91
114
|
if (reason !== undefined)
|
|
92
115
|
fields.terminal_reason = reason;
|
|
116
|
+
const terminal = event === 'finish' || event === 'park' || event === 'cancel' || event === 'crash';
|
|
117
|
+
const current = terminal ? getNode(nodeId) : null;
|
|
118
|
+
const recordsOutcome = terminal && current?.kind !== 'human';
|
|
119
|
+
const outcomeReason = reason;
|
|
120
|
+
const outcomeAt = terminal ? new Date().toISOString() : null;
|
|
121
|
+
const outcomeDetailJson = detailJson(opts?.outcome?.detail);
|
|
122
|
+
let documentJson;
|
|
123
|
+
if (recordsOutcome && current !== null && current.outcome_at === null) {
|
|
124
|
+
const delivery = readOutcomeDelivery(nodeId);
|
|
125
|
+
if (delivery?.state === 'armed') {
|
|
126
|
+
documentJson = composeOutcomeDeliveryDocument({
|
|
127
|
+
nodeId,
|
|
128
|
+
outcome: { kind: 'failure', reason: outcomeReason, at: outcomeAt, revision: (current.outcome_revision ?? 0) + 1, detailJson: outcomeDetailJson },
|
|
129
|
+
action: { name: delivery.actionName, payload: delivery.payload },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
93
133
|
// A committed canonical result is protected from a concurrent close.
|
|
94
134
|
const changes = transitionStatusIntent(nodeId, fields, {
|
|
95
135
|
fromStatuses: spec.from === ANY ? undefined : spec.from,
|
|
96
136
|
requireNoFinalReport: event === 'cancel',
|
|
137
|
+
...(recordsOutcome ? {
|
|
138
|
+
outcome: {
|
|
139
|
+
kind: 'failure',
|
|
140
|
+
reason: outcomeReason,
|
|
141
|
+
detailJson: outcomeDetailJson,
|
|
142
|
+
at: outcomeAt,
|
|
143
|
+
...(documentJson === undefined ? {} : { documentJson }),
|
|
144
|
+
},
|
|
145
|
+
} : {}),
|
|
97
146
|
});
|
|
98
147
|
if (changes === 0) {
|
|
99
148
|
const cur = getNode(nodeId);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type NodeMeta, type Mode, type Lifecycle, type LaunchSpec, type ManagedWorktree, type ReviewBinding } from '../canvas/index.js';
|
|
1
|
+
import { type NodeMeta, type Mode, type Lifecycle, type LaunchSpec, type ManagedWorktree, type OutcomeDeliveryBinding, type ReviewBinding } from '../canvas/index.js';
|
|
2
2
|
/** Explicitly set the resolved install id. crtrd calls this once at boot,
|
|
3
3
|
* immediately after `resolveInstallId()` — see crtrd.ts — so a provisioning
|
|
4
4
|
* mismatch fails the daemon loudly at STARTUP rather than lazily on first
|
|
@@ -97,6 +97,10 @@ export interface SpawnNodeOpts {
|
|
|
97
97
|
profile_id?: string | null;
|
|
98
98
|
/** Managed worktree already created for this node, if any. */
|
|
99
99
|
managedWorktree?: ManagedWorktree;
|
|
100
|
+
/** Absolute wall-clock deadline persisted with the row at birth. */
|
|
101
|
+
deadlineAt?: string;
|
|
102
|
+
/** Delivery target persisted with the row before any broker can launch. */
|
|
103
|
+
outcomeDelivery?: OutcomeDeliveryBinding;
|
|
100
104
|
/** Override the generated id — either an internally pre-allocated id
|
|
101
105
|
* (worktree naming; always valid+fresh) or an external caller's explicit id
|
|
102
106
|
* (`--node-id`), which is format-validated
|
|
@@ -264,6 +264,7 @@ export function spawnNode(opts) {
|
|
|
264
264
|
passive_default: opts.passiveDefault ?? false,
|
|
265
265
|
...(opts.managedWorktree !== undefined ? { managed_worktree: opts.managedWorktree } : {}),
|
|
266
266
|
intent: null,
|
|
267
|
+
deadline_at: opts.deadlineAt ?? null,
|
|
267
268
|
pi_session_id: null,
|
|
268
269
|
pi_session_file: null,
|
|
269
270
|
launch: opts.launch,
|
|
@@ -278,7 +279,7 @@ export function spawnNode(opts) {
|
|
|
278
279
|
throw new Error(`cannot spawn from unknown creator node: ${creator}`);
|
|
279
280
|
}
|
|
280
281
|
assertProfileAvailableForBirth(meta.profile_id ?? null);
|
|
281
|
-
createNode(meta);
|
|
282
|
+
createNode(meta, opts.outcomeDelivery === undefined ? undefined : { outcomeDelivery: opts.outcomeDelivery });
|
|
282
283
|
// Create the node-local memory directory so substrate docs can be written
|
|
283
284
|
// directly into it without a separate mkdir. Skip for ephemeral human-bridge
|
|
284
285
|
// rows (kind 'human') — they are not agent nodes.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { DeclinedResult, NodeMeta, NodeOutcomeDetailV1, NodeOutcomeKind, TerminalReason } from '../canvas/types.js';
|
|
2
|
+
export interface NodeOutcomeDocumentV1 {
|
|
3
|
+
schema: 'crtr.node-outcome/v1';
|
|
4
|
+
outcomeId: string;
|
|
5
|
+
nodeId: string;
|
|
6
|
+
revision: number;
|
|
7
|
+
kind: NodeOutcomeKind;
|
|
8
|
+
reason: TerminalReason;
|
|
9
|
+
settledAt: string;
|
|
10
|
+
node: {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
kind: string;
|
|
14
|
+
mode: 'base' | 'orchestrator';
|
|
15
|
+
lifecycle: 'terminal' | 'resident';
|
|
16
|
+
status: 'active' | 'idle' | 'done' | 'dead' | 'canceled';
|
|
17
|
+
terminalReason: TerminalReason | null;
|
|
18
|
+
cwd: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
parent: string | null;
|
|
21
|
+
spawnedBy: string | null;
|
|
22
|
+
};
|
|
23
|
+
result?: {
|
|
24
|
+
finalReport: {
|
|
25
|
+
basename: string;
|
|
26
|
+
path: string;
|
|
27
|
+
};
|
|
28
|
+
structuredResult?: unknown;
|
|
29
|
+
structuredResultOmitted?: true;
|
|
30
|
+
structuredResultPath?: string;
|
|
31
|
+
};
|
|
32
|
+
failure?: {
|
|
33
|
+
message: string | null;
|
|
34
|
+
faultKind: NodeOutcomeDetailV1['fault_kind'] | null;
|
|
35
|
+
errorClass: NodeOutcomeDetailV1['error_class'] | null;
|
|
36
|
+
respawnFailures: number | null;
|
|
37
|
+
deadline: {
|
|
38
|
+
deadlineAt: string;
|
|
39
|
+
elapsedMs: number;
|
|
40
|
+
} | null;
|
|
41
|
+
/** Set only for reason `declined`: the reason, requester-classified code, and retryable claim. */
|
|
42
|
+
declined: DeclinedResult | null;
|
|
43
|
+
};
|
|
44
|
+
action: {
|
|
45
|
+
name: string;
|
|
46
|
+
payload: unknown;
|
|
47
|
+
};
|
|
48
|
+
delivery: {
|
|
49
|
+
attempt: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Compose a document at outcome time; delivery only replaces `delivery.attempt`. */
|
|
53
|
+
export declare function composeNodeOutcomeDocument(args: {
|
|
54
|
+
node: NodeMeta;
|
|
55
|
+
outcome: {
|
|
56
|
+
kind: NodeOutcomeKind;
|
|
57
|
+
reason: TerminalReason;
|
|
58
|
+
at: string;
|
|
59
|
+
revision: number;
|
|
60
|
+
detailJson: string | null;
|
|
61
|
+
};
|
|
62
|
+
action: {
|
|
63
|
+
name: string;
|
|
64
|
+
payload: unknown;
|
|
65
|
+
};
|
|
66
|
+
/** Required for a result before the final-report pointer has committed. */
|
|
67
|
+
finalReport?: string | null;
|
|
68
|
+
/** Lets the outcome transaction supply a parsed result without a second read. */
|
|
69
|
+
structuredResult?: unknown;
|
|
70
|
+
}): NodeOutcomeDocumentV1;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// The immutable outcome document handed to a registered delivery target.
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { contextDir, reportsDir } from '../canvas/paths.js';
|
|
4
|
+
import { outputResultPath } from './structured-output.js';
|
|
5
|
+
const MAX_DOCUMENT_BYTES = 256 * 1024;
|
|
6
|
+
function structuredResult(nodeId) {
|
|
7
|
+
const path = outputResultPath(nodeId);
|
|
8
|
+
try {
|
|
9
|
+
return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : undefined;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function parseDetail(detailJson) {
|
|
16
|
+
if (detailJson === null)
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(detailJson);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Preserve valid Unicode while bounding a display-only field by UTF-8 bytes. */
|
|
26
|
+
function truncateDescription(value) {
|
|
27
|
+
const maxBytes = 1024;
|
|
28
|
+
if (Buffer.byteLength(value, 'utf8') <= maxBytes)
|
|
29
|
+
return value;
|
|
30
|
+
const ellipsis = '…';
|
|
31
|
+
let bytes = Buffer.byteLength(ellipsis, 'utf8');
|
|
32
|
+
let result = '';
|
|
33
|
+
for (const character of value) {
|
|
34
|
+
const next = Buffer.byteLength(character, 'utf8');
|
|
35
|
+
if (bytes + next > maxBytes)
|
|
36
|
+
break;
|
|
37
|
+
bytes += next;
|
|
38
|
+
result += character;
|
|
39
|
+
}
|
|
40
|
+
return `${result}${ellipsis}`;
|
|
41
|
+
}
|
|
42
|
+
/** Compose a document at outcome time; delivery only replaces `delivery.attempt`. */
|
|
43
|
+
export function composeNodeOutcomeDocument(args) {
|
|
44
|
+
const { node, outcome, action } = args;
|
|
45
|
+
const detail = parseDetail(outcome.detailJson);
|
|
46
|
+
const finalReport = args.finalReport ?? node.final_report ?? '';
|
|
47
|
+
const name = truncateDescription(node.name);
|
|
48
|
+
const description = node.description === undefined ? undefined : truncateDescription(node.description);
|
|
49
|
+
const result = outcome.kind === 'result'
|
|
50
|
+
? args.structuredResult === undefined ? structuredResult(node.node_id) : args.structuredResult
|
|
51
|
+
: undefined;
|
|
52
|
+
const document = {
|
|
53
|
+
schema: 'crtr.node-outcome/v1',
|
|
54
|
+
outcomeId: `${node.node_id}:${outcome.revision}`,
|
|
55
|
+
nodeId: node.node_id,
|
|
56
|
+
revision: outcome.revision,
|
|
57
|
+
kind: outcome.kind,
|
|
58
|
+
reason: outcome.reason,
|
|
59
|
+
settledAt: outcome.at,
|
|
60
|
+
node: {
|
|
61
|
+
name,
|
|
62
|
+
...(description === undefined ? {} : { description }),
|
|
63
|
+
kind: node.kind,
|
|
64
|
+
mode: node.mode,
|
|
65
|
+
lifecycle: node.lifecycle,
|
|
66
|
+
status: node.status,
|
|
67
|
+
terminalReason: node.terminal_reason ?? null,
|
|
68
|
+
cwd: node.cwd,
|
|
69
|
+
createdAt: node.created,
|
|
70
|
+
parent: node.parent ?? null,
|
|
71
|
+
spawnedBy: node.spawned_by ?? null,
|
|
72
|
+
},
|
|
73
|
+
...(outcome.kind === 'result' ? {
|
|
74
|
+
result: {
|
|
75
|
+
finalReport: { basename: finalReport, path: `${reportsDir(node.node_id)}/${finalReport}` },
|
|
76
|
+
...(result === undefined ? {} : { structuredResult: result }),
|
|
77
|
+
},
|
|
78
|
+
} : {
|
|
79
|
+
failure: {
|
|
80
|
+
message: detail?.message ?? null,
|
|
81
|
+
faultKind: detail?.fault_kind ?? null,
|
|
82
|
+
errorClass: detail?.error_class ?? null,
|
|
83
|
+
respawnFailures: detail?.respawn_failures ?? null,
|
|
84
|
+
deadline: detail?.deadline === undefined ? null : { deadlineAt: detail.deadline.deadline_at, elapsedMs: detail.deadline.elapsed_ms },
|
|
85
|
+
declined: detail?.declined ?? null,
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
action,
|
|
89
|
+
delivery: { attempt: 0 },
|
|
90
|
+
};
|
|
91
|
+
if (Buffer.byteLength(JSON.stringify(document), 'utf8') > MAX_DOCUMENT_BYTES && document.result?.structuredResult !== undefined) {
|
|
92
|
+
delete document.result.structuredResult;
|
|
93
|
+
document.result.structuredResultOmitted = true;
|
|
94
|
+
document.result.structuredResultPath = `${contextDir(node.node_id)}/result.json`;
|
|
95
|
+
}
|
|
96
|
+
return document;
|
|
97
|
+
}
|