@north-light/crouter 0.3.199 → 0.3.200
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.
|
@@ -28,7 +28,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
28
28
|
import { createRequire } from 'node:module';
|
|
29
29
|
import { createNode, getNode, recordPid, subscribersOf, subscriptionsOf, } from '../../canvas/canvas.js';
|
|
30
30
|
import { closeDb } from '../../canvas/db.js';
|
|
31
|
-
import {
|
|
31
|
+
import { spawnChild } from '../../runtime/spawn.js';
|
|
32
32
|
import { superviseTick } from '../../../daemon/crtrd.js';
|
|
33
33
|
import { DaemonFleet } from '../../../daemon/fleet.js';
|
|
34
34
|
import { apiSocketPath } from '../../canvas/paths.js';
|
|
@@ -150,11 +150,11 @@ export async function createHarness(opts = {}) {
|
|
|
150
150
|
const tmpHome = mkdtempSync(join(tmpdir(), 'crtr-harness-HOME-'));
|
|
151
151
|
const session = `${opts.sessionPrefix ?? 'crtr-harness'}-${process.pid}-${Date.now().toString(36)}`;
|
|
152
152
|
// The harness reads/writes the isolated canvas in-process. CRTR_PI_BINARY in
|
|
153
|
-
// OUR env makes in-process
|
|
153
|
+
// OUR env makes in-process launches (spawnChild and superviseTick → reviveNode)
|
|
154
154
|
// bake the fake-pi into the command string.
|
|
155
155
|
process.env['CRTR_HOME'] = home;
|
|
156
156
|
// HOME must be isolated in THIS process too, not just in the subprocess envs
|
|
157
|
-
// below: an in-process launch (spawnHeadlessChild's
|
|
157
|
+
// below: an in-process launch (spawnHeadlessChild's spawnChild, superviseTick
|
|
158
158
|
// pass-2 wakes) builds its broker env from process.env, so a real HOME here
|
|
159
159
|
// makes every broker read the developer's OWN ~/.pi/agent — auth.json and the
|
|
160
160
|
// managed subscription pools — instead of agentHome, and provider tests then
|
|
@@ -202,7 +202,7 @@ export async function createHarness(opts = {}) {
|
|
|
202
202
|
resetEventSourceForTests();
|
|
203
203
|
bindDaemonEventSource();
|
|
204
204
|
// The harness also stands in for the daemon's FLEET: every in-harness broker
|
|
205
|
-
// launch (reviveNode
|
|
205
|
+
// launch (spawnChild, reviveNode, superviseTick pass-2 wakes) registers its
|
|
206
206
|
// ChildProcess handle here, and real child exits enqueue exit-policy jobs on
|
|
207
207
|
// this serial work lane — the same single-lane discipline crtrd runs, but
|
|
208
208
|
// drainable to a deterministic quiescent point. Reset first for the same
|
|
@@ -367,17 +367,9 @@ export async function createHarness(opts = {}) {
|
|
|
367
367
|
}
|
|
368
368
|
function nodeDirs() {
|
|
369
369
|
try {
|
|
370
|
-
// Node ids
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
// opaque and its segment count has already changed once — mirror
|
|
374
|
-
// profiles/manifest.ts's ID_SHAPE rather than hard-coding a segment
|
|
375
|
-
// count). Scope the scan to "one or more lowercase-alnum segments ending
|
|
376
|
-
// in an 8-hex random suffix" so a stray non-node sibling under nodes/ is
|
|
377
|
-
// never miscounted as a new node dir — e.g. a `reports/` dir created for
|
|
378
|
-
// an empty node id (the rare CI flake on compiled-seam run 27459304455 that
|
|
379
|
-
// broke the spawn counters with `expected exactly 1 new node dir, got
|
|
380
|
-
// [<id>, reports]`), or literal `reports` (no hyphen, no hex suffix).
|
|
370
|
+
// Node ids end in an 8-hex random suffix and may have any number of
|
|
371
|
+
// lowercase-alphanumeric prefix segments. Only return those node dirs so
|
|
372
|
+
// fixture cleanup never treats a non-node sibling as a canvas row.
|
|
381
373
|
return readdirSync(join(home, 'nodes')).filter((d) => /^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/.test(d));
|
|
382
374
|
}
|
|
383
375
|
catch {
|
|
@@ -507,46 +499,16 @@ export async function createHarness(opts = {}) {
|
|
|
507
499
|
return id;
|
|
508
500
|
},
|
|
509
501
|
async spawnHeadlessChild(parentId, task, o = {}) {
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
args.push('--kind', o.kind);
|
|
521
|
-
if (o.mode)
|
|
522
|
-
args.push('--mode', o.mode);
|
|
523
|
-
if (o.model)
|
|
524
|
-
args.push('--model', o.model);
|
|
525
|
-
const res = cli(parentId, args);
|
|
526
|
-
if (res.code !== 0) {
|
|
527
|
-
throw new Error(`spawnHeadlessChild(${parentId}) failed (code ${res.code})\n--stdout--\n${res.stdout}\n--stderr--\n${res.stderr}`);
|
|
528
|
-
}
|
|
529
|
-
const added = nodeDirs().filter((d) => !before.has(d));
|
|
530
|
-
if (added.length !== 1) {
|
|
531
|
-
throw new Error(`spawnHeadlessChild: expected exactly 1 new node dir, got [${added.join(', ')}]`);
|
|
532
|
-
}
|
|
533
|
-
const childId = added[0];
|
|
534
|
-
closeDb();
|
|
535
|
-
const fixtureRow = getNode(childId);
|
|
536
|
-
const fixturePid = fixtureRow?.pi_pid ?? null;
|
|
537
|
-
const fixtureIdentity = fixtureRow?.pi_pid_identity ?? null;
|
|
538
|
-
const priorBoots = harness.bootCount(childId);
|
|
539
|
-
if (fixturePid !== null && fixtureIdentity !== null && recordedPidLiveness(fixturePid, fixtureIdentity) === 'alive') {
|
|
540
|
-
process.kill(fixturePid, 'SIGTERM');
|
|
541
|
-
await waitFor(() => recordedPidLiveness(fixturePid, fixtureIdentity) !== 'alive', {
|
|
542
|
-
timeoutMs: 10_000,
|
|
543
|
-
intervalMs: 25,
|
|
544
|
-
label: `API-fixture broker exit for ${childId}`,
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
reviveNode(childId, { resume: false });
|
|
548
|
-
await harness.awaitBoot(childId, { minCount: priorBoots + 1 });
|
|
549
|
-
return childId;
|
|
502
|
+
const result = await spawnChild({
|
|
503
|
+
kind: o.kind ?? 'general',
|
|
504
|
+
cwd: CROUTER,
|
|
505
|
+
prompt: task,
|
|
506
|
+
parent: parentId,
|
|
507
|
+
...(o.mode !== undefined ? { mode: o.mode } : {}),
|
|
508
|
+
...(o.model !== undefined ? { model: o.model } : {}),
|
|
509
|
+
});
|
|
510
|
+
await harness.awaitBoot(result.node.node_id);
|
|
511
|
+
return result.node.node_id;
|
|
550
512
|
},
|
|
551
513
|
cli,
|
|
552
514
|
async stop(nodeId, reason = 'stop', errorMessage) {
|
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
// lifecycle close` then tears the replacement down (shutdown frame → exit,
|
|
11
11
|
// socket unlinked, status canceled, daemon leaves it).
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// exit drives policy automatically — no synthetic delivery, no tick, no grace.
|
|
13
|
+
// spawnHeadlessChild calls the production birth path in the harness process, so
|
|
14
|
+
// every broker below is harness-fleet-owned from its first and only boot and
|
|
15
|
+
// every SIGKILL's REAL ChildProcess exit drives policy automatically — no
|
|
16
|
+
// synthetic delivery, no tick, no grace.
|
|
18
17
|
// Each crash has its own real broker setup, so either top-level test remains a
|
|
19
18
|
// complete journey under name filtering and pays only its own first respawn
|
|
20
19
|
// backoff.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.200",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -83,7 +83,8 @@
|
|
|
83
83
|
"prepublishOnly": "npm run build",
|
|
84
84
|
"test": "node --conditions=crtr-src --import tsx/esm --test --test-concurrency=4 --test-timeout=10000 'src/**/__tests__/*.test.ts'",
|
|
85
85
|
"test:serial": "node --conditions=crtr-src --import tsx/esm --test --test-concurrency=1 --test-timeout=60000 'src/**/__tests__/serial/*.test.ts'",
|
|
86
|
-
"test:seam": "npm run build &&
|
|
86
|
+
"test:seam": "npm run build && npm run test:seam:built",
|
|
87
|
+
"test:seam:built": "node --test --test-concurrency=2 --test-timeout=180000 'dist/**/__tests__/seam/*.test.js'"
|
|
87
88
|
},
|
|
88
89
|
"overrides": {
|
|
89
90
|
"@earendil-works/pi-ai": "0.84.1",
|
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.200",
|
|
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.200",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|