@north-light/crouter 0.3.317 → 0.3.318
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 +1 -1
- package/dist/api/client.js +9 -9
- package/dist/clients/attach/viewer.js +441 -441
- package/dist/core/runtime/warm-pool.d.ts +3 -2
- package/dist/core/runtime/warm-pool.js +12 -2
- package/dist/daemon/api/__tests__/node-create-description.test.js +20 -0
- package/dist/daemon/api/handlers/nodes.js +2 -6
- package/package.json +2 -2
- package/runtime.lock.json +5 -5
|
@@ -31,11 +31,12 @@ export interface WarmRequest {
|
|
|
31
31
|
}
|
|
32
32
|
/** Claim a spare for `request`, reshaped onto its persona — or null when the
|
|
33
33
|
* pool cannot serve it. The caller falls back to a cold spawn; a refused
|
|
34
|
-
* create hook leaves the spare untouched and rejects the create.
|
|
34
|
+
* create hook leaves the spare untouched and rejects the create. `beforeClaimDelivery`
|
|
35
|
+
* persists birth state before the claimant's first session message can arrive. */
|
|
35
36
|
export declare function claimWarmNode(request: WarmRequest, birth?: {
|
|
36
37
|
deadlineAt?: string;
|
|
37
38
|
outcomeDelivery?: OutcomeDeliveryBinding;
|
|
38
|
-
}): Promise<string | null>;
|
|
39
|
+
}, beforeClaimDelivery?: (nodeId: string) => void): Promise<string | null>;
|
|
39
40
|
/** Return every spare that is no longer worth its broker: one whose engine is
|
|
40
41
|
* confirmed GONE (the common case — a daemon handover tears down every broker
|
|
41
42
|
* of its epoch and nothing resumes a spare), one whose row left `active`/`idle`,
|
|
@@ -112,8 +112,9 @@ async function resolveRecipe(request) {
|
|
|
112
112
|
}
|
|
113
113
|
/** Claim a spare for `request`, reshaped onto its persona — or null when the
|
|
114
114
|
* pool cannot serve it. The caller falls back to a cold spawn; a refused
|
|
115
|
-
* create hook leaves the spare untouched and rejects the create.
|
|
116
|
-
|
|
115
|
+
* create hook leaves the spare untouched and rejects the create. `beforeClaimDelivery`
|
|
116
|
+
* persists birth state before the claimant's first session message can arrive. */
|
|
117
|
+
export async function claimWarmNode(request, birth = {}, beforeClaimDelivery) {
|
|
117
118
|
const recipe = await resolveRecipe(request);
|
|
118
119
|
if (warmSpareCount(recipe.key) === 0)
|
|
119
120
|
return null;
|
|
@@ -156,6 +157,15 @@ export async function claimWarmNode(request, birth = {}) {
|
|
|
156
157
|
persona_ack: { kind: request.kind, mode: request.mode, lifecycle: 'resident' },
|
|
157
158
|
...(request.model !== null ? { model_override: request.model } : {}),
|
|
158
159
|
});
|
|
160
|
+
// Persist any caller-owned birth state before the first claimant delivery.
|
|
161
|
+
// A spare has no claimant turn yet, but this keeps claimed and cold roots on
|
|
162
|
+
// the same durable-before-observable boundary.
|
|
163
|
+
try {
|
|
164
|
+
beforeClaimDelivery?.(nodeId);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
return abandonSpare(nodeId, 'birth preparation', err);
|
|
168
|
+
}
|
|
159
169
|
// Bearings FIRST, before any other delivery: a spare mints without them (see
|
|
160
170
|
// canvas-context-intro.ts), so this is the node's orienting frame and it must
|
|
161
171
|
// land as the session's first entry. Built from the row we just reshaped, so
|
|
@@ -2,6 +2,8 @@ import { after, before, test } from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { createServer } from 'node:net';
|
|
4
4
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
5
|
+
import { readOutputRequest } from '../../../core/runtime/structured-output.js';
|
|
6
|
+
import { structuredOutputPromptBlock } from '../../../pi-extensions/canvas-structured-output.js';
|
|
5
7
|
import { join } from 'node:path';
|
|
6
8
|
import { closeDb, migrate, openDb } from '../../../core/canvas/db.js';
|
|
7
9
|
import { closeScopedStores } from '../../../core/scoped-state/db.js';
|
|
@@ -20,6 +22,7 @@ let originalCanvasHome;
|
|
|
20
22
|
let originalUserHome;
|
|
21
23
|
let originalLaunch;
|
|
22
24
|
const brokers = [];
|
|
25
|
+
const firstRunPrompts = new Map();
|
|
23
26
|
before(() => {
|
|
24
27
|
originalCanvasHome = process.env['CRTR_HOME'];
|
|
25
28
|
originalUserHome = process.env['HOME'];
|
|
@@ -34,6 +37,10 @@ before(() => {
|
|
|
34
37
|
bindTestFleet();
|
|
35
38
|
originalLaunch = headlessBrokerHost.launch;
|
|
36
39
|
headlessBrokerHost.launch = (nodeId) => {
|
|
40
|
+
const outputRequest = readOutputRequest(nodeId);
|
|
41
|
+
if (outputRequest.state === 'valid') {
|
|
42
|
+
firstRunPrompts.set(nodeId, structuredOutputPromptBlock(outputRequest.request));
|
|
43
|
+
}
|
|
37
44
|
const broker = createServer();
|
|
38
45
|
brokers.push(broker);
|
|
39
46
|
broker.listen(join(nodeDir(nodeId), 'view.sock'));
|
|
@@ -70,6 +77,19 @@ function context(method, path, id, body) {
|
|
|
70
77
|
body,
|
|
71
78
|
};
|
|
72
79
|
}
|
|
80
|
+
test('root create writes its output schema before the first broker run', async () => {
|
|
81
|
+
const created = await handleCreate(context('POST', '/v1/nodes', undefined, {
|
|
82
|
+
kind: 'general',
|
|
83
|
+
root: true,
|
|
84
|
+
profile: null,
|
|
85
|
+
cwd: home,
|
|
86
|
+
prompt: 'Return the structured result.',
|
|
87
|
+
output_schema: JSON.stringify({ type: 'object', properties: { verdict: { type: 'string' } }, required: ['verdict'] }),
|
|
88
|
+
}));
|
|
89
|
+
const nodeId = created.body.node_id;
|
|
90
|
+
assert.match(firstRunPrompts.get(nodeId) ?? '', /<structured-output-request>/);
|
|
91
|
+
assert.match(firstRunPrompts.get(nodeId) ?? '', /"verdict"/);
|
|
92
|
+
});
|
|
73
93
|
test('node create stores caller description and automatic naming leaves it and name unchanged', async () => {
|
|
74
94
|
const created = await handleCreate(context('POST', '/v1/nodes', undefined, {
|
|
75
95
|
kind: 'general',
|
|
@@ -299,11 +299,9 @@ export async function handleCreate(ctx, deps = {}) {
|
|
|
299
299
|
const claimed = await claimWarmNode(warm, {
|
|
300
300
|
...(deadline === undefined ? {} : { deadlineAt: deadline }),
|
|
301
301
|
...(resolvedOutcomeDelivery === undefined ? {} : { outcomeDelivery: resolvedOutcomeDelivery }),
|
|
302
|
-
});
|
|
302
|
+
}, schema === null ? undefined : (nodeId) => writeOutputSchema(nodeId, 'terminal', schema));
|
|
303
303
|
if (claimed !== null) {
|
|
304
304
|
refillWarmPool(warm);
|
|
305
|
-
if (schema !== null)
|
|
306
|
-
writeOutputSchema(claimed, 'terminal', schema);
|
|
307
305
|
return { status: 201, body: detailOf(claimed) };
|
|
308
306
|
}
|
|
309
307
|
warmRefill = warm;
|
|
@@ -314,12 +312,10 @@ export async function handleCreate(ctx, deps = {}) {
|
|
|
314
312
|
if (resolvedOutcomeDelivery !== undefined)
|
|
315
313
|
recipe.outcomeDelivery = resolvedOutcomeDelivery;
|
|
316
314
|
recipe.readiness = req.root === true ? 'await' : 'launch';
|
|
317
|
-
const result = await spawnChildPrepared(recipe, undefined, () => {
|
|
315
|
+
const result = await spawnChildPrepared(recipe, schema === null ? undefined : (nodeId) => writeOutputSchema(nodeId, 'terminal', schema), () => {
|
|
318
316
|
if (warmRefill !== null)
|
|
319
317
|
refillWarmPool(warmRefill);
|
|
320
318
|
});
|
|
321
|
-
if (schema !== null)
|
|
322
|
-
writeOutputSchema(result.node.node_id, 'terminal', schema);
|
|
323
319
|
return { status: 201, body: detailOf(result.node.node_id) };
|
|
324
320
|
}
|
|
325
321
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.318",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"link": "npm link",
|
|
93
93
|
"prepublishOnly": "npm run build",
|
|
94
94
|
"test": "CRTR_NO_DAEMON_AUTOSTART=1 node --conditions=crtr-src --import tsx/esm --import ./scripts/test-env-scrub.mjs --test --test-force-exit --test-concurrency=$(node -p 'Math.max(4, require(`node:os`).availableParallelism() - 1)') --test-timeout=10000 'src/**/__tests__/*.test.ts'",
|
|
95
|
-
"test:integration": "CRTR_NO_DAEMON_AUTOSTART=1 node --conditions=crtr-src --import tsx/esm --import ./scripts/test-env-scrub.mjs --test --test-concurrency=$(node -p 'Math.min(8, Math.max(2, require(`node:os`).availableParallelism() - 1))') --test-timeout=60000 'src/**/__tests__/integration/*.test.ts'",
|
|
95
|
+
"test:integration": "npm run build:api && CRTR_NO_DAEMON_AUTOSTART=1 node --conditions=crtr-src --import tsx/esm --import ./scripts/test-env-scrub.mjs --test --test-concurrency=$(node -p 'Math.min(8, Math.max(2, require(`node:os`).availableParallelism() - 1))') --test-timeout=60000 'src/**/__tests__/integration/*.test.ts' 'packages/crouter-sdk/src/**/__tests__/integration/*.test.ts'",
|
|
96
96
|
"test:seam": "npm run build && npm run test:seam:built",
|
|
97
97
|
"test:seam:built": "CRTR_NO_DAEMON_AUTOSTART=1 node --import ./scripts/test-env-scrub.mjs --test --test-concurrency=$(node -p 'Math.min(4, Math.max(2, require(`node:os`).availableParallelism() - 2))') --test-timeout=180000 'dist/**/__tests__/seam/*.test.js'",
|
|
98
98
|
"test:all": "npm test && npm run test:integration && npm run test:seam"
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.318",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/crouter",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.318",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"workspaces": [
|
|
@@ -5206,17 +5206,17 @@
|
|
|
5206
5206
|
},
|
|
5207
5207
|
"packages/crouter-api": {
|
|
5208
5208
|
"name": "@north-light/crouter-api",
|
|
5209
|
-
"version": "0.3.
|
|
5209
|
+
"version": "0.3.318",
|
|
5210
5210
|
"license": "UNLICENSED"
|
|
5211
5211
|
},
|
|
5212
5212
|
"packages/crouter-env-docker": {
|
|
5213
5213
|
"name": "@north-light/crouter-env-docker",
|
|
5214
|
-
"version": "0.3.
|
|
5214
|
+
"version": "0.3.318",
|
|
5215
5215
|
"license": "UNLICENSED"
|
|
5216
5216
|
},
|
|
5217
5217
|
"packages/crouter-sdk": {
|
|
5218
5218
|
"name": "@north-light/crouter-sdk",
|
|
5219
|
-
"version": "0.3.
|
|
5219
|
+
"version": "0.3.318",
|
|
5220
5220
|
"license": "UNLICENSED",
|
|
5221
5221
|
"dependencies": {
|
|
5222
5222
|
"@north-light/crouter-api": "^0.3.295"
|