@north-light/crouter 0.3.316 → 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/__tests__/fixtures/fake-engine.d.ts +5 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +93 -12
- package/dist/core/__tests__/seam/broker-provider-retry.test.js +4 -0
- 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
|
@@ -298,14 +298,15 @@ interface FakeServices {
|
|
|
298
298
|
resourceLoader: ResourceLoader;
|
|
299
299
|
modelRuntime: FakeModelRuntime;
|
|
300
300
|
}
|
|
301
|
+
type FakeResourceLoaderOptions = {
|
|
302
|
+
additionalExtensionPaths?: string[];
|
|
303
|
+
appendSystemPrompt?: string[];
|
|
304
|
+
};
|
|
301
305
|
export declare function createAgentSessionServices(options: {
|
|
302
306
|
cwd: string;
|
|
303
307
|
agentDir?: string;
|
|
304
308
|
modelRuntime?: unknown;
|
|
305
|
-
resourceLoaderOptions?:
|
|
306
|
-
additionalExtensionPaths?: string[];
|
|
307
|
-
appendSystemPrompt?: string[];
|
|
308
|
-
};
|
|
309
|
+
resourceLoaderOptions?: FakeResourceLoaderOptions;
|
|
309
310
|
}): Promise<FakeServices>;
|
|
310
311
|
export declare function createAgentSessionFromServices(options: {
|
|
311
312
|
services: FakeServices;
|
|
@@ -31,11 +31,12 @@
|
|
|
31
31
|
// <nodeDir>/fake-pi.dialog.jsonl — one line per resolved unattended dialog (§5.4)
|
|
32
32
|
import { spawn } from 'node:child_process';
|
|
33
33
|
import { writeFileSync, readFileSync, existsSync, mkdirSync, appendFileSync, unlinkSync, } from 'node:fs';
|
|
34
|
-
import { join, dirname } from 'node:path';
|
|
34
|
+
import { join, dirname, resolve } from 'node:path';
|
|
35
35
|
import { randomUUID } from 'node:crypto';
|
|
36
|
-
import { DefaultResourceLoader, getAgentDir } from '@earendil-works/pi-coding-agent';
|
|
36
|
+
import { DefaultPackageManager, DefaultResourceLoader, getAgentDir, SettingsManager } from '@earendil-works/pi-coding-agent';
|
|
37
37
|
import { MANAGED_PROVIDER_COOLING_DIAGNOSTIC } from '../../runtime/managed-provider-cooling.js';
|
|
38
38
|
import { PROVIDER_FAILURE_DIAGNOSTIC } from '../../provider-failure.js';
|
|
39
|
+
import { CANVAS_EXTENSIONS } from '../../runtime/canvas-extensions.js';
|
|
39
40
|
import registerCanvasBashValve, { createValveOperations } from '../../../pi-extensions/canvas-bash-valve.js';
|
|
40
41
|
/** Test seams for prompt-scoped failures before session_start. The one-shot
|
|
41
42
|
* variant records its first failure per node so the next cycling retry can
|
|
@@ -678,6 +679,14 @@ class FakeSession {
|
|
|
678
679
|
// A turn consumes whatever was queued for it, as the real session does.
|
|
679
680
|
this.steering.length = 0;
|
|
680
681
|
this.followUps.length = 0;
|
|
682
|
+
let releaseGate;
|
|
683
|
+
if (process.env['PI_FAKE_ENGINE_HOLD_TURN'] === '1') {
|
|
684
|
+
let release;
|
|
685
|
+
let resolveSettled;
|
|
686
|
+
const settled = new Promise((resolve) => { resolveSettled = resolve; });
|
|
687
|
+
releaseGate = new Promise((resolve) => { release = resolve; });
|
|
688
|
+
this.heldTurn = { release, settled, resolveSettled };
|
|
689
|
+
}
|
|
681
690
|
this.streaming = true;
|
|
682
691
|
if (opts.runHooks === true) {
|
|
683
692
|
await this.fire('agent_start', {}, this.buildCtx());
|
|
@@ -685,14 +694,7 @@ class FakeSession {
|
|
|
685
694
|
this.emit({ type: 'agent_start' });
|
|
686
695
|
this.emit({ type: 'turn_start' });
|
|
687
696
|
this.emit({ type: 'message_start', message: small });
|
|
688
|
-
|
|
689
|
-
let release;
|
|
690
|
-
let resolveSettled;
|
|
691
|
-
const settled = new Promise((resolve) => { resolveSettled = resolve; });
|
|
692
|
-
const releaseGate = new Promise((resolve) => { release = resolve; });
|
|
693
|
-
this.heldTurn = { release, settled, resolveSettled };
|
|
694
|
-
await releaseGate;
|
|
695
|
-
}
|
|
697
|
+
await releaseGate;
|
|
696
698
|
// F2 (message_update coalescing): the broker now coalesces message_update
|
|
697
699
|
// relays (latest-wins, ~75 ms window), so a padded flood riding message_update
|
|
698
700
|
// can no longer climb a stalled viewer's backlog — the coalescer would shed
|
|
@@ -1291,14 +1293,93 @@ const FAKE_REGISTERED_MODELS = [
|
|
|
1291
1293
|
{ provider: 'openai-codex', id: 'gpt-5.6-luna' },
|
|
1292
1294
|
FAKE_AVAILABLE_MODEL,
|
|
1293
1295
|
];
|
|
1296
|
+
/** Source harnesses use native imports only for an isolated, mandatory-only
|
|
1297
|
+
* canvas fixture. Every other source stays on the SDK file-loader path. */
|
|
1298
|
+
async function sourceCanvasExtensionLoaderOptions(cwd, agentDir, options) {
|
|
1299
|
+
const extensionPaths = options?.additionalExtensionPaths;
|
|
1300
|
+
const mandatoryOnly = extensionPaths?.length === CANVAS_EXTENSIONS.length
|
|
1301
|
+
&& extensionPaths.every((path, index) => path === CANVAS_EXTENSIONS[index]);
|
|
1302
|
+
if (!import.meta.url.endsWith('.ts') || !mandatoryOnly)
|
|
1303
|
+
return { additionalExtensionPaths: extensionPaths };
|
|
1304
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
1305
|
+
const resolved = await new DefaultPackageManager({ cwd, agentDir, settingsManager }).resolve();
|
|
1306
|
+
if (resolved.extensions.some((extension) => extension.enabled)) {
|
|
1307
|
+
return { additionalExtensionPaths: extensionPaths, settingsManager };
|
|
1308
|
+
}
|
|
1309
|
+
const inlinePaths = new Map();
|
|
1310
|
+
const fileLoaderErrors = new Map();
|
|
1311
|
+
const missingPaths = [];
|
|
1312
|
+
const extensionFactories = [];
|
|
1313
|
+
for (const inputPath of extensionPaths) {
|
|
1314
|
+
const path = resolve(cwd, inputPath);
|
|
1315
|
+
if (!existsSync(path)) {
|
|
1316
|
+
missingPaths.push(path);
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
inlinePaths.set(`<inline:${path}>`, path);
|
|
1320
|
+
let factory;
|
|
1321
|
+
try {
|
|
1322
|
+
const loaded = await import(path);
|
|
1323
|
+
if (typeof loaded.default !== 'function') {
|
|
1324
|
+
fileLoaderErrors.set(path, `Extension does not export a valid factory function: ${path}`);
|
|
1325
|
+
factory = () => { throw new Error(fileLoaderErrors.get(path)); };
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
factory = loaded.default;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
catch {
|
|
1332
|
+
// Let the original loader produce its own import/transform diagnostic.
|
|
1333
|
+
return { additionalExtensionPaths: extensionPaths, settingsManager };
|
|
1334
|
+
}
|
|
1335
|
+
extensionFactories.push({ name: path, factory });
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
additionalExtensionPaths: [],
|
|
1339
|
+
extensionFactories,
|
|
1340
|
+
settingsManager,
|
|
1341
|
+
restoreCliExtensionMetadata: (loader) => {
|
|
1342
|
+
const result = loader.getExtensions();
|
|
1343
|
+
for (const extension of result.extensions) {
|
|
1344
|
+
const path = inlinePaths.get(extension.path);
|
|
1345
|
+
if (path === undefined)
|
|
1346
|
+
continue;
|
|
1347
|
+
const sourceInfo = { path, source: 'cli', scope: 'temporary', origin: 'top-level', baseDir: undefined };
|
|
1348
|
+
extension.path = path;
|
|
1349
|
+
extension.resolvedPath = path;
|
|
1350
|
+
extension.sourceInfo = sourceInfo;
|
|
1351
|
+
for (const command of extension.commands.values())
|
|
1352
|
+
command.sourceInfo = sourceInfo;
|
|
1353
|
+
for (const tool of extension.tools.values())
|
|
1354
|
+
tool.sourceInfo = sourceInfo;
|
|
1355
|
+
}
|
|
1356
|
+
for (const error of result.errors) {
|
|
1357
|
+
const path = inlinePaths.get(error.path);
|
|
1358
|
+
if (path !== undefined) {
|
|
1359
|
+
error.path = path;
|
|
1360
|
+
const isConflict = /^(?:Tool|Flag) ".*" conflicts with /.test(error.error);
|
|
1361
|
+
error.error = fileLoaderErrors.get(path) ?? (isConflict ? error.error : `Failed to load extension: ${error.error}`);
|
|
1362
|
+
}
|
|
1363
|
+
for (const [inlinePath, filePath] of inlinePaths) {
|
|
1364
|
+
error.error = error.error.replaceAll(inlinePath, filePath);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
result.errors.push(...missingPaths.map((path) => ({ path, error: `Extension path does not exist: ${path}` })));
|
|
1368
|
+
},
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1294
1371
|
export async function createAgentSessionServices(options) {
|
|
1372
|
+
const agentDir = options.agentDir ?? getAgentDir();
|
|
1373
|
+
const extensionOptions = await sourceCanvasExtensionLoaderOptions(options.cwd, agentDir, options.resourceLoaderOptions);
|
|
1374
|
+
const { restoreCliExtensionMetadata, ...loaderOptions } = extensionOptions;
|
|
1295
1375
|
const loader = new DefaultResourceLoader({
|
|
1296
1376
|
cwd: options.cwd,
|
|
1297
|
-
agentDir
|
|
1298
|
-
|
|
1377
|
+
agentDir,
|
|
1378
|
+
...loaderOptions,
|
|
1299
1379
|
appendSystemPrompt: options.resourceLoaderOptions?.appendSystemPrompt,
|
|
1300
1380
|
});
|
|
1301
1381
|
await loader.reload();
|
|
1382
|
+
restoreCliExtensionMetadata?.(loader);
|
|
1302
1383
|
// Zero-auth test seam. While <CRTR_HOME>/fake-pi.no-auth exists, the registry
|
|
1303
1384
|
// reports a GENUINE zero-auth boot (nothing registered, nothing authenticated)
|
|
1304
1385
|
// so resolveLaunchModel (broker.ts) falls all the way to a tier-3 model-less
|
|
@@ -367,6 +367,8 @@ test('reload_auth re-drives a parked fatal provider-auth turn exactly once', { t
|
|
|
367
367
|
await seedPool('openai-codex', false);
|
|
368
368
|
const child = await h.spawnHeadlessChild(parent, 'auth reload child', { model: OPENAI_STRONG.spec });
|
|
369
369
|
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial auth fixture turn started' });
|
|
370
|
+
const kickoffObserver = await attachUntil(child, 'observer', 'auth-reload-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'auth reload kickoff settled');
|
|
371
|
+
kickoffObserver.close();
|
|
370
372
|
const baselineStarts = agentStartCount(child);
|
|
371
373
|
h.fakeCmd(child, {
|
|
372
374
|
cmd: 'stop',
|
|
@@ -397,6 +399,8 @@ test('reload_auth leaves a parked auth fault with its original session when navi
|
|
|
397
399
|
await seedPool('openai-codex', false);
|
|
398
400
|
const child = await h.spawnHeadlessChild(parent, 'auth replacement child', { model: OPENAI_STRONG.spec });
|
|
399
401
|
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial replacement fixture turn started' });
|
|
402
|
+
const kickoffObserver = await attachUntil(child, 'observer', 'auth-replacement-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'auth replacement kickoff settled');
|
|
403
|
+
kickoffObserver.close();
|
|
400
404
|
h.fakeCmd(child, {
|
|
401
405
|
cmd: 'stop',
|
|
402
406
|
reason: 'error',
|
|
@@ -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"
|