@north-light/crouter 0.3.263 → 0.3.270
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 +5 -1
- package/dist/api/client.js +6 -0
- package/dist/api/dto/broker.d.ts +11 -2
- package/dist/api/dto/broker.js +2 -2
- package/dist/api/dto/canvas.d.ts +53 -2
- package/dist/api/dto/nodes.d.ts +10 -2
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +50 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +46 -6
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +6 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +7 -2
- package/dist/clients/attach/session/whip-achievements.d.ts +4 -0
- package/dist/clients/attach/session/whip-achievements.js +145 -0
- package/dist/clients/attach/slash/dispatch.js +5 -3
- package/dist/clients/attach/viewer.js +607 -606
- package/dist/commands/canvas-history/grep.js +3 -22
- package/dist/commands/canvas-history/read.js +5 -3
- package/dist/commands/canvas-history/search.js +3 -22
- package/dist/commands/canvas-history/shared.d.ts +8 -0
- package/dist/commands/canvas-history/shared.js +64 -1
- package/dist/commands/canvas-history/stats.d.ts +1 -0
- package/dist/commands/canvas-history/stats.js +68 -0
- package/dist/commands/canvas-history.js +4 -3
- package/dist/commands/node/create.js +2 -2
- package/dist/commands/pkg/plugin-inspect.js +2 -2
- package/dist/commands/pkg/plugin-manage.js +3 -3
- package/dist/commands/sys/config.js +19 -2
- package/dist/commands/sys/doctor.js +4 -3
- package/dist/commands/sys/panels/models-panel.js +3 -1
- package/dist/core/__tests__/human-deliver.test.js +1 -1
- package/dist/core/__tests__/integration/lifecycle-hooks.test.d.ts +1 -0
- package/dist/core/__tests__/integration/lifecycle-hooks.test.js +182 -0
- package/dist/core/__tests__/integration/revive.test.js +19 -17
- package/dist/core/__tests__/model-pin-durability.test.js +25 -1
- package/dist/core/__tests__/push-final-guard.test.js +1 -1
- package/dist/core/__tests__/relaunch-root.test.js +17 -0
- package/dist/core/__tests__/revive-capacity.test.js +20 -20
- package/dist/core/__tests__/revive-parked-fresh.test.js +14 -14
- package/dist/core/__tests__/seam/broker-cap-freeze.test.js +2 -2
- package/dist/core/__tests__/seam/broker-provider-retry.test.js +19 -4
- package/dist/core/__tests__/seam/dormancy-release.test.js +7 -7
- package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +1 -1
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +19 -2
- package/dist/core/__tests__/seam/yield-refresh-transaction.test.js +2 -2
- package/dist/core/broker-client/__tests__/transport-relay.test.js +3 -1
- package/dist/core/broker-client/transport-relay.js +5 -1
- package/dist/core/canvas/__tests__/history-transcript.test.d.ts +1 -0
- package/dist/core/canvas/__tests__/history-transcript.test.js +102 -0
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +13 -10
- package/dist/core/canvas/browse/model.js +16 -1
- package/dist/core/canvas/browse/render.d.ts +23 -3
- package/dist/core/canvas/browse/render.js +429 -64
- package/dist/core/canvas/canvas.js +12 -8
- package/dist/core/canvas/extensions.d.ts +1 -1
- package/dist/core/canvas/extensions.js +7 -1
- package/dist/core/canvas/history.d.ts +36 -1
- package/dist/core/canvas/history.js +328 -7
- package/dist/core/canvas/labels.d.ts +2 -2
- package/dist/core/canvas/migrations.js +32 -0
- package/dist/core/canvas/node-recap.d.ts +23 -0
- package/dist/core/canvas/node-recap.js +67 -0
- package/dist/core/canvas/render-source.d.ts +33 -1
- package/dist/core/canvas/render-source.js +80 -22
- package/dist/core/canvas/types.d.ts +9 -0
- package/dist/core/command-hooks/discovery.d.ts +27 -1
- package/dist/core/command-hooks/discovery.js +52 -0
- package/dist/core/command-hooks/index.d.ts +4 -3
- package/dist/core/command-hooks/index.js +2 -1
- package/dist/core/command-hooks/lifecycle-catalog.d.ts +3 -0
- package/dist/core/command-hooks/lifecycle-catalog.js +4 -0
- package/dist/core/command-hooks/report.d.ts +11 -2
- package/dist/core/command-hooks/report.js +10 -1
- package/dist/core/command-hooks/schema.d.ts +16 -1
- package/dist/core/command-hooks/schema.js +128 -40
- package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +12 -0
- package/dist/core/command-hooks/transport/exec-lifecycle.js +152 -0
- package/dist/core/config.js +13 -5
- package/dist/core/human/feedback-companion.js +1 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/model-routes.js +1 -1
- package/dist/core/review/realize.js +1 -1
- package/dist/core/runtime/broker/auth-reload.js +4 -1
- package/dist/core/runtime/broker/engine-drive.d.ts +14 -3
- package/dist/core/runtime/broker/engine-drive.js +29 -1
- package/dist/core/runtime/broker/fault-retry.js +7 -2
- package/dist/core/runtime/broker/frame-client.js +3 -0
- package/dist/core/runtime/broker/frame-dispatch.js +7 -0
- package/dist/core/runtime/broker/inbox.d.ts +12 -2
- package/dist/core/runtime/broker/inbox.js +68 -38
- package/dist/core/runtime/broker/rebind.js +7 -0
- package/dist/core/runtime/broker-protocol.d.ts +16 -1
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +27 -3
- package/dist/core/runtime/canvas-extensions.d.ts +1 -0
- package/dist/core/runtime/canvas-extensions.js +2 -0
- package/dist/core/runtime/interactive-deliver.d.ts +8 -2
- package/dist/core/runtime/interactive-deliver.js +12 -3
- package/dist/core/runtime/launch-target.js +1 -1
- package/dist/core/runtime/launch.d.ts +12 -6
- package/dist/core/runtime/launch.js +19 -17
- package/dist/core/runtime/model-swap.d.ts +5 -4
- package/dist/core/runtime/model-swap.js +7 -5
- package/dist/core/runtime/node-read.d.ts +2 -0
- package/dist/core/runtime/node-read.js +18 -9
- package/dist/core/runtime/promote.js +2 -1
- package/dist/core/runtime/reset.js +4 -2
- package/dist/core/runtime/revive-all.d.ts +1 -1
- package/dist/core/runtime/revive-all.js +2 -2
- package/dist/core/runtime/revive.d.ts +2 -2
- package/dist/core/runtime/revive.js +71 -13
- package/dist/core/runtime/session-visibility.d.ts +10 -14
- package/dist/core/runtime/session-visibility.js +43 -30
- package/dist/core/runtime/spawn.d.ts +3 -3
- package/dist/core/runtime/spawn.js +20 -3
- package/dist/core/runtime/stamp/channel.d.ts +22 -0
- package/dist/core/runtime/stamp/channel.js +83 -0
- package/dist/core/runtime/stamp/protocol.d.ts +90 -0
- package/dist/core/runtime/stamp/protocol.js +113 -0
- package/dist/core/runtime/tmux-driver.d.ts +11 -1
- package/dist/core/runtime/tmux-driver.js +27 -11
- package/dist/core/runtime/turn-visibility.d.ts +10 -0
- package/dist/core/runtime/turn-visibility.js +31 -0
- package/dist/core/self-update.js +2 -5
- package/dist/core/tui/draw.d.ts +1 -0
- package/dist/core/tui/draw.js +5 -1
- package/dist/core/tui/markdown.d.ts +17 -0
- package/dist/core/tui/markdown.js +407 -0
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -0
- package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +2 -2
- package/dist/daemon/api/bridge.d.ts +3 -2
- package/dist/daemon/api/bridge.js +34 -9
- package/dist/daemon/api/handlers/attach.js +4 -4
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/canvas.js +62 -16
- package/dist/daemon/api/handlers/messages.js +14 -3
- package/dist/daemon/api/handlers/nodes.js +17 -7
- package/dist/daemon/api/map.js +2 -0
- package/dist/daemon/cron/sinks.js +3 -3
- package/dist/daemon/fleet.js +1 -1
- package/dist/daemon/messaging/node-message.js +16 -2
- package/dist/daemon/profile-delete.js +1 -1
- package/dist/daemon/reconcilers/broker-supervision.js +26 -9
- package/dist/daemon/reconcilers/live-obligation.d.ts +8 -1
- package/dist/daemon/reconcilers/live-obligation.js +17 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +6 -6
- package/dist/hook-authoring.d.ts +31 -1
- package/dist/hook-authoring.js +42 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +27 -2
- package/dist/pi-extensions/canvas-recap.js +4 -0
- package/dist/pi-extensions/canvas-stamp.d.ts +14 -0
- package/dist/pi-extensions/canvas-stamp.js +77 -0
- package/dist/pi-extensions/canvas-stophook.js +3 -0
- package/dist/shared/__tests__/env-boundary.test.js +4 -2
- package/dist/shared/crtr-version.d.ts +2 -0
- package/dist/shared/crtr-version.js +18 -0
- package/dist/shared/env.d.ts +10 -0
- package/dist/shared/env.js +19 -4
- package/dist/shared/generated-context.d.ts +1 -2
- package/dist/shared/generated-context.js +3 -4
- package/dist/types.d.ts +10 -0
- package/dist/types.js +4 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -12,6 +12,7 @@ import { markFatalOnExhaust, nextFaultRetry, policyFor } from '../fault-recovery
|
|
|
12
12
|
import { parseModelSpec } from '../model-selection.js';
|
|
13
13
|
import { endedByAbort } from '../session-cycles.js';
|
|
14
14
|
import { resolveProviderCandidates } from '../../subscription-state.js';
|
|
15
|
+
import { announceMessageOrigin } from '../stamp/channel.js';
|
|
15
16
|
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
16
17
|
/** Owns provider fallback and daemon-driven fault retry state for one broker session. */
|
|
17
18
|
export class FaultRetry {
|
|
@@ -266,7 +267,9 @@ export class FaultRetry {
|
|
|
266
267
|
return;
|
|
267
268
|
if (unconfigured)
|
|
268
269
|
clearFault(this.deps.nodeId, { link: 'pi→provider' });
|
|
269
|
-
|
|
270
|
+
const fallbackCard = formatCard('recovery', {}, formatModelFallbackRecovery(currentSpec, resolved, unconfigured ? 'credential' : 'not-found'));
|
|
271
|
+
announceMessageOrigin(fallbackCard, { type: 'runtime', kind: 'recovery' });
|
|
272
|
+
await this.deps.turnAdmission.admit((release) => operationIdContext.fresh(() => candidateSession.prompt(fallbackCard, { preflightResult: release })));
|
|
270
273
|
})().catch((err) => {
|
|
271
274
|
emitEvent({ level: 'debug', event: 'broker.runtime.diagnostic', fields: { message: `${unconfigured ? 'unconfigured-provider' : 'not_found'} fallback failed: ${err instanceof Error ? err.message : String(err)}` } });
|
|
272
275
|
}).finally(() => {
|
|
@@ -405,7 +408,9 @@ export class FaultRetry {
|
|
|
405
408
|
return Promise.resolve();
|
|
406
409
|
}
|
|
407
410
|
recordFault(this.deps.nodeId, plan.fault);
|
|
408
|
-
|
|
411
|
+
const retryCard = formatCard('recovery', {}, fault.kind === 'connection' ? CONNECTION_FAULT_RECOVERY_BODY : PROVIDER_FAULT_RECOVERY_BODY);
|
|
412
|
+
announceMessageOrigin(retryCard, { type: 'runtime', kind: 'recovery' });
|
|
413
|
+
return operationIdContext.fresh(() => candidateSession.prompt(retryCard, { preflightResult: release }));
|
|
409
414
|
});
|
|
410
415
|
}
|
|
411
416
|
}
|
|
@@ -86,6 +86,9 @@ export function createClientHandlers(deps) {
|
|
|
86
86
|
type: 'prompt',
|
|
87
87
|
text: frame.text,
|
|
88
88
|
...(frame.images !== undefined ? { images: frame.images } : {}),
|
|
89
|
+
delivery: {
|
|
90
|
+
...(frame.origin === undefined ? {} : { origin: frame.origin }),
|
|
91
|
+
},
|
|
89
92
|
});
|
|
90
93
|
if (via === 'steer' || outcome === undefined) {
|
|
91
94
|
turnAcceptance.cancel(ackAccepted);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFrameReplies } from './frame-replies.js';
|
|
2
2
|
import { createEngineDriver } from './engine-drive.js';
|
|
3
|
+
import { announceMessageOrigin } from '../stamp/channel.js';
|
|
3
4
|
import { createClientHandlers } from './frame-client.js';
|
|
4
5
|
import { createMemoryRefGuidance } from './frame-memory-refs.js';
|
|
5
6
|
import { createAuthReloadGate } from './auth-reload.js';
|
|
@@ -51,6 +52,12 @@ export function createFrameDispatchContext(deps) {
|
|
|
51
52
|
const handleDriveFrame = (client, frame) => {
|
|
52
53
|
if (notWritable(client, 'drive the engine'))
|
|
53
54
|
return;
|
|
55
|
+
// These frames are the viewer's input vocabulary — a person typed them.
|
|
56
|
+
// (crtrd's `deliver` never lands here: frame-client synthesizes its own
|
|
57
|
+
// prompt frame and calls driveEngine directly, announcing its own origin.)
|
|
58
|
+
if (frame.type === 'prompt' || frame.type === 'steer' || frame.type === 'follow_up') {
|
|
59
|
+
announceMessageOrigin(frame.text, { type: 'human', channel: { type: 'viewer' } });
|
|
60
|
+
}
|
|
54
61
|
driveEngine(client, frame);
|
|
55
62
|
};
|
|
56
63
|
const clientHandlers = createClientHandlers({
|
|
@@ -27,7 +27,9 @@ export declare function readBrokerCanceledEntryIds(nodeId: string): Set<BrokerIn
|
|
|
27
27
|
export declare function reportRefNodeId(ref: string | undefined): string | undefined;
|
|
28
28
|
/** One-shot runtime cards an entry carries (rendered envelopes written by the
|
|
29
29
|
* message API). They ride the delivery their own entry belongs to, so a note
|
|
30
|
-
* can never land ahead of an unrelated person's message
|
|
30
|
+
* can never land ahead of an unrelated person's message — except in the one
|
|
31
|
+
* deliberate fold below, where deferred entries join a person's delivery as
|
|
32
|
+
* the context their turn reads. */
|
|
31
33
|
export declare function entryCards(entry: BrokerInboxEntry): readonly string[];
|
|
32
34
|
/** One deliverable user message: either a person's verbatim words or the whole
|
|
33
35
|
* `<runtime kind="inbox">` envelope. Never both — a reader parses an envelope
|
|
@@ -40,5 +42,13 @@ export interface InboxDelivery {
|
|
|
40
42
|
entries: readonly BrokerInboxEntry[];
|
|
41
43
|
}
|
|
42
44
|
/** Split unread entries into ordered deliveries: each human entry verbatim, and
|
|
43
|
-
*
|
|
45
|
+
* each CONTIGUOUS run of node/system entries as one card in the physical slot
|
|
46
|
+
* that run occupies. A deferred-only card is folded into the person's delivery
|
|
47
|
+
* waiting immediately behind it rather than being deliverable on its own.
|
|
48
|
+
*
|
|
49
|
+
* The runs are what keep the cursor honest. Exactly one delivery goes out per
|
|
50
|
+
* cycle and the rest requeue, while a sent delivery commits the cursor across
|
|
51
|
+
* its own entries — so a delivery that spanned a person's message would commit
|
|
52
|
+
* past words this cycle never sent. A run can never span one, and node mail
|
|
53
|
+
* that arrived after a person's message now waits behind it. */
|
|
44
54
|
export declare function coalesceBrokerInbox(entries: readonly BrokerInboxEntry[], reportNodes: ReadonlyMap<string, BrokerReportNodeDTO>): readonly InboxDelivery[];
|
|
@@ -163,7 +163,9 @@ function cardEntry(entry, reportNodes) {
|
|
|
163
163
|
}
|
|
164
164
|
/** One-shot runtime cards an entry carries (rendered envelopes written by the
|
|
165
165
|
* message API). They ride the delivery their own entry belongs to, so a note
|
|
166
|
-
* can never land ahead of an unrelated person's message
|
|
166
|
+
* can never land ahead of an unrelated person's message — except in the one
|
|
167
|
+
* deliberate fold below, where deferred entries join a person's delivery as
|
|
168
|
+
* the context their turn reads. */
|
|
167
169
|
export function entryCards(entry) {
|
|
168
170
|
const cards = entry.data?.['cards'];
|
|
169
171
|
if (!Array.isArray(cards))
|
|
@@ -171,49 +173,77 @@ export function entryCards(entry) {
|
|
|
171
173
|
return cards.filter((card) => typeof card === 'string');
|
|
172
174
|
}
|
|
173
175
|
/** Split unread entries into ordered deliveries: each human entry verbatim, and
|
|
174
|
-
*
|
|
176
|
+
* each CONTIGUOUS run of node/system entries as one card in the physical slot
|
|
177
|
+
* that run occupies. A deferred-only card is folded into the person's delivery
|
|
178
|
+
* waiting immediately behind it rather than being deliverable on its own.
|
|
179
|
+
*
|
|
180
|
+
* The runs are what keep the cursor honest. Exactly one delivery goes out per
|
|
181
|
+
* cycle and the rest requeue, while a sent delivery commits the cursor across
|
|
182
|
+
* its own entries — so a delivery that spanned a person's message would commit
|
|
183
|
+
* past words this cycle never sent. A run can never span one, and node mail
|
|
184
|
+
* that arrived after a person's message now waits behind it. */
|
|
175
185
|
export function coalesceBrokerInbox(entries, reportNodes) {
|
|
176
|
-
if (entries.length === 0)
|
|
177
|
-
return [];
|
|
178
|
-
const groups = new Map();
|
|
179
|
-
for (const entry of entries) {
|
|
180
|
-
const key = entry.from ?? 'system';
|
|
181
|
-
if (!groups.has(key))
|
|
182
|
-
groups.set(key, []);
|
|
183
|
-
groups.get(key).push(entry);
|
|
184
|
-
}
|
|
185
186
|
const deliveries = [];
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
continue;
|
|
187
|
+
let run = [];
|
|
188
|
+
const flushRun = () => {
|
|
189
|
+
if (run.length === 0)
|
|
190
|
+
return;
|
|
191
|
+
// Within one run the card still groups by sender, so a child that sent
|
|
192
|
+
// several entries reads as one section rather than a repeated header.
|
|
193
|
+
const sections = new Map();
|
|
194
|
+
for (const entry of run) {
|
|
195
|
+
const key = entry.from ?? 'system';
|
|
196
|
+
if (!sections.has(key))
|
|
197
|
+
sections.set(key, []);
|
|
198
|
+
sections.get(key).push(entry);
|
|
200
199
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
const card = [...sections].map(([id, items]) => ({
|
|
201
|
+
id,
|
|
202
|
+
entries: items.map((entry) => cardEntry(entry, reportNodes)),
|
|
203
|
+
}));
|
|
204
|
+
deliveries.push({
|
|
205
|
+
kind: 'card',
|
|
206
|
+
text: formatInboxCard(card),
|
|
207
|
+
cards: run.flatMap((entry) => entryCards(entry)),
|
|
208
|
+
entries: run,
|
|
209
|
+
});
|
|
210
|
+
run = [];
|
|
211
|
+
};
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
if ((entry.from ?? 'system') !== 'human') {
|
|
214
|
+
run.push(entry);
|
|
215
|
+
continue;
|
|
204
216
|
}
|
|
205
|
-
|
|
217
|
+
flushRun();
|
|
218
|
+
const body = inlineBody(entry);
|
|
219
|
+
deliveries.push({
|
|
220
|
+
kind: 'human',
|
|
221
|
+
text: body === '' ? entry.label : body,
|
|
222
|
+
cards: entryCards(entry),
|
|
223
|
+
entries: [entry],
|
|
224
|
+
});
|
|
206
225
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
flushRun();
|
|
227
|
+
// Deferred mail is background context, never a prompt of its own. With a
|
|
228
|
+
// person's words waiting immediately behind a deferred-only card, the card
|
|
229
|
+
// rides ahead of those words inside that SAME delivery: one turn, prompted by
|
|
230
|
+
// what the person actually said, with the card already in the transcript.
|
|
231
|
+
// Left as its own delivery it takes an earlier turn whose only prompt is
|
|
232
|
+
// invisible to the person reading along, so the agent answers something they
|
|
233
|
+
// cannot see. Folding backwards keeps the remaining indices valid.
|
|
234
|
+
for (let i = deliveries.length - 2; i >= 0; i -= 1) {
|
|
235
|
+
const card = deliveries[i];
|
|
236
|
+
const human = deliveries[i + 1];
|
|
237
|
+
if (card.kind !== 'card' || human.kind !== 'human')
|
|
238
|
+
continue;
|
|
239
|
+
if (!card.entries.every((entry) => entry.tier === 'deferred'))
|
|
240
|
+
continue;
|
|
241
|
+
deliveries[i] = {
|
|
242
|
+
...human,
|
|
243
|
+
cards: [...card.cards, card.text, ...human.cards],
|
|
244
|
+
entries: [...card.entries, ...human.entries],
|
|
216
245
|
};
|
|
246
|
+
deliveries.splice(i + 1, 1);
|
|
217
247
|
}
|
|
218
248
|
return deliveries;
|
|
219
249
|
}
|
|
@@ -6,6 +6,7 @@ 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';
|
|
9
10
|
const MAX_ADMITTED_REPLACEMENTS = 2;
|
|
10
11
|
const REPLACEMENT_DEADLINE_MS = 30_000;
|
|
11
12
|
const REPLACEMENT_DEADLINE_EXCEEDED = Symbol('replacement-deadline-exceeded');
|
|
@@ -302,6 +303,12 @@ export class RebindDriver {
|
|
|
302
303
|
catch (err) {
|
|
303
304
|
emitEvent({ level: 'debug', event: 'broker.runtime.diagnostic', fields: { message: `event relay threw: ${String(err)}` } });
|
|
304
305
|
}
|
|
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
|
+
}
|
|
305
312
|
}
|
|
306
313
|
ackTo(client, op, ok = true, detail, id) {
|
|
307
314
|
this.deps.registry.sendFrame(client, { type: 'ack', for: op, ok, ...(detail !== undefined ? { detail } : {}), ...(id !== undefined ? { id } : {}) });
|
|
@@ -2,6 +2,7 @@ import type { AgentSession, AgentSessionEvent, RpcExtensionUIRequest, RpcExtensi
|
|
|
2
2
|
import type { Api, ImageContent, Model } from '@earendil-works/pi-ai';
|
|
3
3
|
import type { BrokerErrorFrame, BrokerNodeNamedFrame } from '../../api/dto/broker.js';
|
|
4
4
|
import type { ToolGroupSummary } from '../../shared/tool-groups.js';
|
|
5
|
+
import type { MessageOrigin, MessageVisibility } from './stamp/protocol.js';
|
|
5
6
|
/** pi's own RPC types, re-exported unchanged. `broker-protocol` stays the one
|
|
6
7
|
* import site for the wire union; the shapes themselves are pi's, versioned by
|
|
7
8
|
* pi, so a pi change is a compile error here instead of silent drift. */
|
|
@@ -21,6 +22,10 @@ export interface BrokerSnapshot {
|
|
|
21
22
|
/** Stable session-entry ids aligned 1:1 with `messages`. Absent when the
|
|
22
23
|
* session manager cannot expose its entry tree. */
|
|
23
24
|
messageIds?: string[];
|
|
25
|
+
/** Presentation visibility aligned 1:1 with `messages`. */
|
|
26
|
+
messageVisibility: MessageVisibility[];
|
|
27
|
+
/** Presentation visibility of the run currently owned by the engine. */
|
|
28
|
+
turnVisibility: MessageVisibility;
|
|
24
29
|
stats: SessionStats;
|
|
25
30
|
/** pi's OWN `get_state` payload, verbatim — not a mirror. `buildSnapshot`
|
|
26
31
|
* fills it exactly as pi's `rpc-mode.js` does, from the same getters, so a
|
|
@@ -143,6 +148,11 @@ export interface DeliverFrame {
|
|
|
143
148
|
* between a card and the body it belongs to. Each is its own custom
|
|
144
149
|
* message and none triggers a turn; the body is the sole trigger. */
|
|
145
150
|
cards?: string[];
|
|
151
|
+
/** Who authored `text`, for the durable stamp the broker writes. The daemon
|
|
152
|
+
* holds this provenance (an inbox sender, a park request, a person on a
|
|
153
|
+
* dormant viewer) and the broker is the only process that can announce it,
|
|
154
|
+
* so it rides the same frame as the body. */
|
|
155
|
+
origin?: MessageOrigin;
|
|
146
156
|
}
|
|
147
157
|
/** Run a `!` bash command — writable clients only. Maps to `session.executeBash()`,
|
|
148
158
|
* which runs the command, records a `bashExecution` message in context, and
|
|
@@ -392,6 +402,11 @@ export interface WelcomeFrame {
|
|
|
392
402
|
* the other four learn nothing); the PAYLOAD is pi's — `Model`, exactly what
|
|
393
403
|
* pi's own `set_model` response carries and what `snapshot.state.model` holds.
|
|
394
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
|
+
}
|
|
395
410
|
export interface WorkingActivityFrame {
|
|
396
411
|
type: 'working_activity';
|
|
397
412
|
/** Full display label selected from the user's configured gerunds. */
|
|
@@ -637,7 +652,7 @@ export interface ToolGroupSummaryFrame {
|
|
|
637
652
|
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
638
653
|
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
639
654
|
* non-colliding `type` discriminants. */
|
|
640
|
-
export type BrokerToClient = WelcomeFrame | WorkingActivityFrame | ModelChangedFrame | ResourcesReloadedFrame | ErrorFrame | NodeNamedFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
|
|
655
|
+
export type BrokerToClient = WelcomeFrame | TurnVisibilityFrame | WorkingActivityFrame | ModelChangedFrame | ResourcesReloadedFrame | ErrorFrame | NodeNamedFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
|
|
641
656
|
/** Encode one frame as a single newline-terminated JSON line. */
|
|
642
657
|
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
643
658
|
/** Byte bounds for a {@link FrameDecoder} (C5). */
|
|
@@ -29,7 +29,7 @@ interface PendingDialog {
|
|
|
29
29
|
* bash children. No-op before the session is built or after a clean dispose. */
|
|
30
30
|
export declare function disposeActiveSession(): void;
|
|
31
31
|
export declare function runBroker(nodeId: string, startupAt?: bigint, startupTs?: string): Promise<void>;
|
|
32
|
-
export declare function snapshotMessages(session: CreateAgentSessionResult['session'], boundaryReviewId?: string): import("./session-
|
|
32
|
+
export declare function snapshotMessages(session: CreateAgentSessionResult['session'], boundaryReviewId?: string): import("./session-visibility.js").ProjectedMessage[] | undefined;
|
|
33
33
|
export declare function buildBrokerSession(engine: BrokerEngine, cfg: BrokerSdkConfig): Promise<{
|
|
34
34
|
session: CreateAgentSessionResult['session'];
|
|
35
35
|
services: AgentSessionServices;
|
|
@@ -40,7 +40,8 @@ import { modelRequestFromConfig, selectInitialModelRoute, unregisteredConcreteMo
|
|
|
40
40
|
import { KNOWN_STREAM_WAIT_UI } from './stream-watchdog.js';
|
|
41
41
|
import { probeOnline as probeOnlineReal } from './connectivity.js';
|
|
42
42
|
import { CRTR_CYCLE_CUSTOM_TYPE, cycleAwareMessages } from './session-cycles.js';
|
|
43
|
-
import {
|
|
43
|
+
import { projectTranscript } from './session-visibility.js';
|
|
44
|
+
import { currentTurnVisibility } from './turn-visibility.js';
|
|
44
45
|
import { isUnknownModel } from './model-selection.js';
|
|
45
46
|
import { BrokerClientRegistry } from './broker/client-registry.js';
|
|
46
47
|
import { ToolGroupTracker } from './broker/tool-groups.js';
|
|
@@ -52,6 +53,8 @@ import { EventProjection } from './broker/event-projection.js';
|
|
|
52
53
|
import { createFrameDispatchContext, handleFrame, } from './broker/frame-dispatch.js';
|
|
53
54
|
import { hydratePersistedAdvertisedCommandMessages, installAdvertisedCommandInvocationContract, } from './advertised-command-invocation.js';
|
|
54
55
|
import { RebindDriver } from './broker/rebind.js';
|
|
56
|
+
import { decodeKickoffOrigin, KICKOFF_ORIGIN_ENV } from './stamp/protocol.js';
|
|
57
|
+
import { announceMessageOrigin } from './stamp/channel.js';
|
|
55
58
|
import { brokerExtensionState, commitBrokerModel } from './broker/daemon-ops.js';
|
|
56
59
|
import { FrameOverflowError, normalizeSnapshotTail, } from './broker-protocol.js';
|
|
57
60
|
/** A review companion may only navigate or fork within a branch whose ancestry
|
|
@@ -201,6 +204,10 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
201
204
|
const launchFile = join(dir, 'broker-launch.json');
|
|
202
205
|
const inv = JSON.parse(readFileSync(launchFile, 'utf8'));
|
|
203
206
|
const cfg = piInvocationToSdkConfig(inv); // also merges inv.env
|
|
207
|
+
if (cfg.modelExact === true)
|
|
208
|
+
process.env['CRTR_MODEL_EXACT'] = '1';
|
|
209
|
+
else
|
|
210
|
+
delete process.env['CRTR_MODEL_EXACT'];
|
|
204
211
|
// 2–4. Build the engine session via the SERVICES path (C3) — see
|
|
205
212
|
// buildBrokerSession below. Register it so the FATAL exit path (M3) can
|
|
206
213
|
// dispose it and reap detached bash children.
|
|
@@ -314,7 +321,9 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
314
321
|
const result = await commitBrokerModel(nodeId, { spec, pinnedOverride, userSelected });
|
|
315
322
|
if (userSelected) {
|
|
316
323
|
delete process.env['CRTR_MODEL_INTENT'];
|
|
324
|
+
process.env['CRTR_MODEL_EXACT'] = '1';
|
|
317
325
|
cfg.model = result.modelOverride;
|
|
326
|
+
cfg.modelExact = true;
|
|
318
327
|
}
|
|
319
328
|
return result.modelOverride;
|
|
320
329
|
};
|
|
@@ -389,6 +398,9 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
389
398
|
return ({
|
|
390
399
|
messages,
|
|
391
400
|
...(retained === undefined ? {} : { messageIds: retained.map(({ id }) => id) }),
|
|
401
|
+
messageVisibility: retained?.map(({ visibility }) => visibility)
|
|
402
|
+
?? messages.map(() => 'visible'),
|
|
403
|
+
turnVisibility: currentTurnVisibility(),
|
|
392
404
|
stats: liveSession.getSessionStats(),
|
|
393
405
|
// pi's `get_state` (rpc-mode.js), field for field, from the same getters.
|
|
394
406
|
// The ONE divergence: a model-less session (undefined OR the SDK
|
|
@@ -637,6 +649,12 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
637
649
|
// reload_auth re-drive resolves a model, at which point it fires. For the
|
|
638
650
|
// common model-present path this fires immediately — unchanged behavior.
|
|
639
651
|
if (cfg.firstPrompt !== undefined && cfg.firstPrompt !== '') {
|
|
652
|
+
// The spawner recorded who authored this kickoff (a birth mandate, a
|
|
653
|
+
// cron wake, a revive card); announce it against the exact text so the
|
|
654
|
+
// stamp writer claims it on the message pi persists.
|
|
655
|
+
const kickoffOrigin = decodeKickoffOrigin(process.env[KICKOFF_ORIGIN_ENV]);
|
|
656
|
+
if (kickoffOrigin !== null)
|
|
657
|
+
announceMessageOrigin(cfg.firstPrompt, kickoffOrigin);
|
|
640
658
|
frameDispatch.kickoff(cfg.firstPrompt);
|
|
641
659
|
}
|
|
642
660
|
});
|
|
@@ -740,11 +758,17 @@ export function snapshotMessages(session, boundaryReviewId) {
|
|
|
740
758
|
// payload), narrower than `CycleSessionManagerLike`'s structural read of the
|
|
741
759
|
// `crtr-cycle` marker's `{cycle, fromLeaf}` shape — the fields we read off it
|
|
742
760
|
// are ones ONLY this module's own `appendCustomEntry` call (above) ever writes.
|
|
743
|
-
const
|
|
761
|
+
const manager = session.sessionManager;
|
|
762
|
+
const messages = cycleAwareMessages(manager);
|
|
744
763
|
if (messages !== undefined) {
|
|
745
764
|
hydratePersistedAdvertisedCommandMessages(messages.map(({ message }) => message));
|
|
746
765
|
}
|
|
747
|
-
return messages === undefined
|
|
766
|
+
return messages === undefined
|
|
767
|
+
? undefined
|
|
768
|
+
: projectTranscript(messages, {
|
|
769
|
+
boundaryReviewId,
|
|
770
|
+
sessionEntries: manager.getEntries?.(),
|
|
771
|
+
});
|
|
748
772
|
}
|
|
749
773
|
// resolveLaunchModel — the SOLE launch model-selection policy, module-scoped
|
|
750
774
|
// so both buildForManager (initial boot, inside buildBrokerSession below) and
|
|
@@ -11,6 +11,7 @@ export declare const CANVAS_STRUCTURED_OUTPUT_PATH: string;
|
|
|
11
11
|
export declare const CANVAS_BASH_VALVE_PATH: string;
|
|
12
12
|
export declare const CANVAS_PREVIEW_RESULT_PATH: string;
|
|
13
13
|
export declare const CANVAS_PROMPT_SCRUB_PATH: string;
|
|
14
|
+
export declare const CANVAS_STAMP_PATH: string;
|
|
14
15
|
/** The canvas extensions every node loads, in order. The inbox watcher commits
|
|
15
16
|
* its cursor at agent_settled before the stophook checks it for idle release.
|
|
16
17
|
* The review boundary must run before the stophook commits companion session
|
|
@@ -23,6 +23,7 @@ export const CANVAS_STRUCTURED_OUTPUT_PATH = resolveExtension('canvas-structured
|
|
|
23
23
|
export const CANVAS_BASH_VALVE_PATH = resolveExtension('canvas-bash-valve');
|
|
24
24
|
export const CANVAS_PREVIEW_RESULT_PATH = resolveExtension('canvas-preview-result');
|
|
25
25
|
export const CANVAS_PROMPT_SCRUB_PATH = resolveExtension('canvas-prompt-scrub');
|
|
26
|
+
export const CANVAS_STAMP_PATH = resolveExtension('canvas-stamp');
|
|
26
27
|
/** The canvas extensions every node loads, in order. The inbox watcher commits
|
|
27
28
|
* its cursor at agent_settled before the stophook checks it for idle release.
|
|
28
29
|
* The review boundary must run before the stophook commits companion session
|
|
@@ -46,6 +47,7 @@ export const CANVAS_EXTENSIONS = [
|
|
|
46
47
|
CANVAS_BASH_VALVE_PATH,
|
|
47
48
|
CANVAS_PREVIEW_RESULT_PATH,
|
|
48
49
|
CANVAS_PROMPT_SCRUB_PATH,
|
|
50
|
+
CANVAS_STAMP_PATH,
|
|
49
51
|
];
|
|
50
52
|
/** Preserve the invariant: mandatory extensions are always present exactly once.
|
|
51
53
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MessageOrigin } from './stamp/protocol.js';
|
|
1
2
|
/** Which engine call the broker routed a live deliver to (its ack `detail`):
|
|
2
3
|
* idle → `prompt`, turn in flight → `steer`. */
|
|
3
4
|
export type LiveDeliverRoute = 'prompt' | 'steer';
|
|
@@ -7,8 +8,13 @@ export type LiveInterruptOutcome = 'turn' | 'bash' | 'idle';
|
|
|
7
8
|
/** Deliver `text` to a LIVE node's engine on its serialized frame loop. The
|
|
8
9
|
* broker routes it itself (idle → prompt, streaming → steer) and acks once
|
|
9
10
|
* routed; resolves with the route taken. `cards` are rendered runtime-card
|
|
10
|
-
* envelopes placed ahead of the body inside that same frame handling
|
|
11
|
-
|
|
11
|
+
* envelopes placed ahead of the body inside that same frame handling, and
|
|
12
|
+
* `origin` is the authorship the broker stamps onto the persisted message —
|
|
13
|
+
* it rides the same frame as the body it describes. */
|
|
14
|
+
export declare function deliverLive(nodeId: string, text: string, opts?: {
|
|
15
|
+
cards?: readonly string[];
|
|
16
|
+
origin?: MessageOrigin;
|
|
17
|
+
}): Promise<LiveDeliverRoute>;
|
|
12
18
|
/** Abort whatever a LIVE node's engine is doing (turn or `!` bash). Resolves
|
|
13
19
|
* once the broker has PROCESSED the abort — which, on the single frame loop,
|
|
14
20
|
* also implies every earlier-accepted deliver was already routed (so a
|
|
@@ -15,14 +15,23 @@ import { oneShotControllerRequest } from './broker-request.js';
|
|
|
15
15
|
/** Deliver `text` to a LIVE node's engine on its serialized frame loop. The
|
|
16
16
|
* broker routes it itself (idle → prompt, streaming → steer) and acks once
|
|
17
17
|
* routed; resolves with the route taken. `cards` are rendered runtime-card
|
|
18
|
-
* envelopes placed ahead of the body inside that same frame handling
|
|
19
|
-
|
|
18
|
+
* envelopes placed ahead of the body inside that same frame handling, and
|
|
19
|
+
* `origin` is the authorship the broker stamps onto the persisted message —
|
|
20
|
+
* it rides the same frame as the body it describes. */
|
|
21
|
+
export function deliverLive(nodeId, text, opts = {}) {
|
|
22
|
+
const cards = opts.cards ?? [];
|
|
20
23
|
return oneShotControllerRequest({
|
|
21
24
|
nodeId,
|
|
22
25
|
what: 'the interactive delivery',
|
|
23
26
|
ackFor: 'deliver',
|
|
24
27
|
correlated: true,
|
|
25
|
-
frame: (id) => ({
|
|
28
|
+
frame: (id) => ({
|
|
29
|
+
type: 'deliver',
|
|
30
|
+
id,
|
|
31
|
+
text,
|
|
32
|
+
...(cards.length > 0 ? { cards: [...cards] } : {}),
|
|
33
|
+
...(opts.origin !== undefined ? { origin: opts.origin } : {}),
|
|
34
|
+
}),
|
|
26
35
|
result: (detail) => (detail === 'steer' ? 'steer' : 'prompt'),
|
|
27
36
|
});
|
|
28
37
|
}
|
|
@@ -34,7 +34,7 @@ export async function resolveLaunchTarget(req, options = {}) {
|
|
|
34
34
|
const launch = (await buildLaunchSpecAsync(kind, mode, {
|
|
35
35
|
lifecycle: req.root_lifecycle ?? (req.root === true ? 'resident' : 'terminal'),
|
|
36
36
|
hasManager: req.root !== true,
|
|
37
|
-
...(req.model !== undefined ? { model: req.model } : {}),
|
|
37
|
+
...(req.model !== undefined ? { model: req.model, modelOverride: true } : {}),
|
|
38
38
|
cwd,
|
|
39
39
|
profileId,
|
|
40
40
|
})).launch;
|
|
@@ -15,12 +15,12 @@ export type { PortableModelSelection } from './model-selection.js';
|
|
|
15
15
|
* stays unpinned. */
|
|
16
16
|
export declare function isProviderPinnedModelToken(model: string): boolean;
|
|
17
17
|
/** True when `model` is a PORTABLE ladder token — a bare strength/alias
|
|
18
|
-
* (`ultra`, `normal`, `opus`, …),
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* ladder the running build ships, so they never freeze a stale concrete
|
|
23
|
-
*
|
|
18
|
+
* (`ultra`, `normal`, `opus`, …), a `<family>/<strength>` pair, or an
|
|
19
|
+
* anthropic family alias — i.e. a logical preference rather than a concrete
|
|
20
|
+
* provider/model id. Portable tokens are safe to PERSIST as defaults
|
|
21
|
+
* (`persistDefaultKindModel`): they re-resolve against the declared routes or
|
|
22
|
+
* ladder the running build ships, so they never freeze a stale concrete model
|
|
23
|
+
* id to disk. */
|
|
24
24
|
export declare function isPortableModelToken(model: string): boolean;
|
|
25
25
|
export declare function logicalModelIntent(model: string, ladders?: ScopeConfig['modelLadders']): {
|
|
26
26
|
family: string;
|
|
@@ -65,6 +65,12 @@ export interface LaunchBuildOptions {
|
|
|
65
65
|
hasManager: boolean;
|
|
66
66
|
extraEnv?: Record<string, string>;
|
|
67
67
|
model?: string;
|
|
68
|
+
/** A newly supplied `--model` override. Resolve it through the target
|
|
69
|
+
* ladder and any quality floor once, then freeze the resulting concrete
|
|
70
|
+
* provider/id[:thinking] spec into the launch recipe so every launch,
|
|
71
|
+
* revive, and retry consults exactly that model. Existing recipes instead
|
|
72
|
+
* carry `modelExact`/`modelIntent` below verbatim. */
|
|
73
|
+
modelOverride?: boolean;
|
|
68
74
|
/** The TARGET node's cwd — anchors the project-scope stack for kind/ladder
|
|
69
75
|
* resolution. Defaults to this process's own cwd, which is correct only
|
|
70
76
|
* when the caller IS resolving for itself; spawn/polymorph paths must
|
|
@@ -85,7 +85,7 @@ function findLadderCell(normalized, ladders, providers) {
|
|
|
85
85
|
* strong/medium/light, ALWAYS anthropic (ignores the env/config default).
|
|
86
86
|
* 4. Anything else → pass through unchanged. */
|
|
87
87
|
function modelTokenStrength(model) {
|
|
88
|
-
const slash = model.
|
|
88
|
+
const slash = model.lastIndexOf('/');
|
|
89
89
|
const provider = slash === -1 ? undefined : model.slice(0, slash);
|
|
90
90
|
const rest = slash === -1 ? model : model.slice(slash + 1);
|
|
91
91
|
const strength = STRENGTH_ALIASES[rest];
|
|
@@ -137,19 +137,19 @@ export function isProviderPinnedModelToken(model) {
|
|
|
137
137
|
return model.includes('/') && !isPortableModelToken(model);
|
|
138
138
|
}
|
|
139
139
|
/** True when `model` is a PORTABLE ladder token — a bare strength/alias
|
|
140
|
-
* (`ultra`, `normal`, `opus`, …),
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* ladder the running build ships, so they never freeze a stale concrete
|
|
145
|
-
*
|
|
140
|
+
* (`ultra`, `normal`, `opus`, …), a `<family>/<strength>` pair, or an
|
|
141
|
+
* anthropic family alias — i.e. a logical preference rather than a concrete
|
|
142
|
+
* provider/model id. Portable tokens are safe to PERSIST as defaults
|
|
143
|
+
* (`persistDefaultKindModel`): they re-resolve against the declared routes or
|
|
144
|
+
* ladder the running build ships, so they never freeze a stale concrete model
|
|
145
|
+
* id to disk. */
|
|
146
146
|
export function isPortableModelToken(model) {
|
|
147
147
|
if (model.includes('/')) {
|
|
148
|
-
const slash = model.
|
|
149
|
-
const
|
|
148
|
+
const slash = model.lastIndexOf('/');
|
|
149
|
+
const family = model.slice(0, slash);
|
|
150
150
|
const rest = model.slice(slash + 1);
|
|
151
|
-
return
|
|
152
|
-
|| (
|
|
151
|
+
return family !== '' && (STRENGTH_ALIASES[rest] !== undefined
|
|
152
|
+
|| (family === 'anthropic' && (rest === 'opus' || rest === 'sonnet' || rest === 'haiku')));
|
|
153
153
|
}
|
|
154
154
|
return STRENGTH_ALIASES[model] !== undefined || model === 'opus' || model === 'sonnet' || model === 'haiku';
|
|
155
155
|
}
|
|
@@ -158,7 +158,7 @@ export function logicalModelIntent(model, ladders = modelLadders()) {
|
|
|
158
158
|
if (strength === null)
|
|
159
159
|
return undefined;
|
|
160
160
|
if (model.includes('/'))
|
|
161
|
-
return { family: model.slice(0, model.
|
|
161
|
+
return { family: model.slice(0, model.lastIndexOf('/')), strength };
|
|
162
162
|
if (model === 'opus' || model === 'sonnet' || model === 'haiku')
|
|
163
163
|
return { family: 'anthropic', strength };
|
|
164
164
|
return { family: defaultProvider(ladders), strength };
|
|
@@ -251,16 +251,18 @@ function buildLaunchSpecFromMerged(kind, mode, opts, merged) {
|
|
|
251
251
|
const ladders = merged.modelLadders;
|
|
252
252
|
const kindModel = mode === 'orchestrator' ? kindConfig?.orchestratorModel ?? kindConfig?.model : kindConfig?.model;
|
|
253
253
|
const chosenModel = opts.modelExact === true ? opts.model : floorReviewModel(kind, opts.model, kindModel, ladders);
|
|
254
|
-
const
|
|
254
|
+
const exact = opts.modelExact === true || opts.modelOverride === true;
|
|
255
|
+
const resolvedModel = opts.modelExact === true
|
|
256
|
+
? chosenModel
|
|
257
|
+
: (chosenModel !== undefined ? normalizeModel(chosenModel, ladders) : undefined);
|
|
258
|
+
const intent = exact ? undefined : (opts.modelIntent ?? (chosenModel !== undefined ? logicalModelIntent(chosenModel, ladders) : undefined));
|
|
255
259
|
const launch = {
|
|
256
|
-
model:
|
|
257
|
-
? chosenModel
|
|
258
|
-
: (chosenModel !== undefined ? normalizeModel(chosenModel, ladders) : undefined),
|
|
260
|
+
model: resolvedModel,
|
|
259
261
|
modelIntent: intent,
|
|
260
262
|
modelProviderPinned: opts.modelProviderPinned !== undefined
|
|
261
263
|
? opts.modelProviderPinned
|
|
262
264
|
: opts.model !== undefined && isProviderPinnedModelToken(opts.model),
|
|
263
|
-
modelExact:
|
|
265
|
+
modelExact: exact ? true : undefined,
|
|
264
266
|
tools: kindConfig?.tools ?? [],
|
|
265
267
|
extensions: mergeCanvasExtensions([...CANVAS_EXTENSIONS, ...(kindConfig?.extensions ?? [])]),
|
|
266
268
|
systemPrompt: LAUNCH_SYSTEM_PROMPT,
|
|
@@ -2,7 +2,7 @@ import { type NodeMeta } from '../canvas/index.js';
|
|
|
2
2
|
/** True when the node's broker engine is reachable: its recorded pid is alive
|
|
3
3
|
* AND its view.sock exists. Both are required — a dead pid with a stale socket,
|
|
4
4
|
* or a live pid mid-boot before it binds the socket, is NOT a live broker. */
|
|
5
|
-
export declare function isBrokerLive(
|
|
5
|
+
export declare function isBrokerLive(node: Pick<NodeMeta, 'node_id' | 'pi_pid'>): boolean;
|
|
6
6
|
/** Swap a LIVE node's model over its view.sock. Connects as a controller,
|
|
7
7
|
* sends `set_model`, and resolves with the broker-resolved durable spec from its
|
|
8
8
|
* `model_changed` frame. Rejects with the broker's message on an `error` frame (e.g.
|
|
@@ -16,9 +16,10 @@ export declare function isBrokerLive(meta: NodeMeta): boolean;
|
|
|
16
16
|
export declare function setModelLive(nodeId: string, spec: string, pinned: boolean): Promise<string>;
|
|
17
17
|
/** Persist a model choice into a DORMANT node's durable recipe — mirrors exactly
|
|
18
18
|
* what the broker's persistModelChoice writes on a live switch: `model_override`
|
|
19
|
-
* (so polymorphs preserve it via buildLaunchSpec)
|
|
20
|
-
* buildPiArgv replays on revive). `spec` must
|
|
21
|
-
* (normalizeModel maps tiers/aliases; a bare
|
|
19
|
+
* (so polymorphs preserve it via buildLaunchSpec), `launch.model`, and its
|
|
20
|
+
* exact-consult marker (the recipe buildPiArgv replays on revive). `spec` must
|
|
21
|
+
* already be a concrete `provider/id` (normalizeModel maps tiers/aliases; a bare
|
|
22
|
+
* substring cannot resolve offline).
|
|
22
23
|
* `pinned` is the durable-pin decision for this change — decided by the CALLER
|
|
23
24
|
* from the RAW pre-normalize token, never re-derived here from `spec`'s shape
|
|
24
25
|
* (a concrete `spec` always reads as "pinned", which would wrongly re-pin a
|