@north-light/cli 0.3.592 → 0.3.594
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/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/imports.d.ts +6 -0
- package/dist/cli.js +7 -7
- package/dist/commands/pkg/market-inspect.js +1 -1
- package/dist/commands/pkg/market-manage.js +1 -1
- package/dist/commands/pkg/plugin-inspect.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +6 -6
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/grants/remove.d.ts +4 -0
- package/dist/core/grants/remove.js +1 -1
- package/dist/core/import/load/prepare.js +5 -5
- package/dist/core/import/load/publish-plugins.d.ts +10 -0
- package/dist/core/import/load/publish-plugins.js +1 -0
- package/dist/core/import/load/run.js +2 -2
- package/dist/core/import/load/stores.d.ts +8 -1
- package/dist/core/import/load/stores.js +2 -2
- package/dist/core/packages/directory-sync.d.ts +2 -0
- package/dist/core/packages/directory-sync.js +3 -3
- package/dist/core/peer/home.d.ts +1 -1
- package/dist/core/peer/home.js +2 -2
- package/dist/core/runtime/runner/blaxel/runner.js +3 -3
- package/dist/daemon/api/handlers/imports.js +1 -1
- package/dist/daemon/crtrd.js +12 -12
- package/dist/daemon/reconcilers/grant-effects.js +1 -1
- package/dist/shared/env.d.ts +4 -0
- package/dist/shared/env.js +2 -2
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/scripts/sandbox-bootstrap.sh +10 -4
- package/scripts/sandbox-start.mjs +54 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.594",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
},
|
|
146
146
|
"license": "UNLICENSED",
|
|
147
147
|
"dependencies": {
|
|
148
|
-
"@north-light/identity": "0.3.
|
|
148
|
+
"@north-light/identity": "0.3.594",
|
|
149
149
|
"@earendil-works/chord": "1.1.0",
|
|
150
150
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
151
151
|
"@earendil-works/pi-ai": "1.1.0",
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.594",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.594",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"workspaces": [
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@milkdown/preset-gfm": "^7.22.3",
|
|
29
29
|
"@milkdown/prose": "^7.22.3",
|
|
30
30
|
"@milkdown/utils": "^7.22.3",
|
|
31
|
-
"@north-light/identity": "0.3.
|
|
31
|
+
"@north-light/identity": "0.3.594",
|
|
32
32
|
"cron-parser": "^5.6.0",
|
|
33
33
|
"esbuild": "^0.27.7",
|
|
34
34
|
"jose": "^6.2.12",
|
|
@@ -11956,17 +11956,17 @@
|
|
|
11956
11956
|
},
|
|
11957
11957
|
"packages/crouter-api": {
|
|
11958
11958
|
"name": "@north-light/api",
|
|
11959
|
-
"version": "0.3.
|
|
11959
|
+
"version": "0.3.594",
|
|
11960
11960
|
"license": "UNLICENSED"
|
|
11961
11961
|
},
|
|
11962
11962
|
"packages/crouter-env-docker": {
|
|
11963
11963
|
"name": "@north-light/env-docker",
|
|
11964
|
-
"version": "0.3.
|
|
11964
|
+
"version": "0.3.594",
|
|
11965
11965
|
"license": "UNLICENSED"
|
|
11966
11966
|
},
|
|
11967
11967
|
"packages/crouter-identity": {
|
|
11968
11968
|
"name": "@north-light/identity",
|
|
11969
|
-
"version": "0.3.
|
|
11969
|
+
"version": "0.3.594",
|
|
11970
11970
|
"license": "UNLICENSED",
|
|
11971
11971
|
"dependencies": {
|
|
11972
11972
|
"jose": "^6.2.1"
|
|
@@ -11974,21 +11974,21 @@
|
|
|
11974
11974
|
},
|
|
11975
11975
|
"packages/crouter-plugin": {
|
|
11976
11976
|
"name": "@north-light/plugin",
|
|
11977
|
-
"version": "0.3.
|
|
11977
|
+
"version": "0.3.594",
|
|
11978
11978
|
"license": "UNLICENSED",
|
|
11979
11979
|
"dependencies": {
|
|
11980
|
-
"@north-light/api": "0.3.
|
|
11981
|
-
"@north-light/identity": "0.3.
|
|
11980
|
+
"@north-light/api": "0.3.594",
|
|
11981
|
+
"@north-light/identity": "0.3.594"
|
|
11982
11982
|
}
|
|
11983
11983
|
},
|
|
11984
11984
|
"packages/crouter-sdk": {
|
|
11985
11985
|
"name": "@north-light/sdk",
|
|
11986
|
-
"version": "0.3.
|
|
11986
|
+
"version": "0.3.594",
|
|
11987
11987
|
"license": "UNLICENSED",
|
|
11988
11988
|
"dependencies": {
|
|
11989
|
-
"@north-light/api": "0.3.
|
|
11990
|
-
"@north-light/identity": "0.3.
|
|
11991
|
-
"@north-light/plugin": "0.3.
|
|
11989
|
+
"@north-light/api": "0.3.594",
|
|
11990
|
+
"@north-light/identity": "0.3.594",
|
|
11991
|
+
"@north-light/plugin": "0.3.594",
|
|
11992
11992
|
"jose": "^6.2.1"
|
|
11993
11993
|
},
|
|
11994
11994
|
"bin": {
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
# CRTR_RELEASE_URL, CRTR_RELEASE_SHA256 and the rest the runner set. Once per sandbox it creates the `crtr` user
|
|
6
6
|
# (`crtrd` refuses root) and its directories; it installs the release if it is absent (download, sha256 check,
|
|
7
7
|
# unpack into a temp dir, rename into place, keep three), then hands over to `crtr-sandbox-start` as `crtr`.
|
|
8
|
-
# The node token reaches the start script on stdin, never in its environment, so
|
|
9
|
-
#
|
|
8
|
+
# The node token reaches the start script on stdin, never in its environment, so it is in no process's
|
|
9
|
+
# /proc/<pid>/environ. It is not hidden from user `crtr` otherwise: the broker holds it in memory, and the agent's
|
|
10
|
+
# shell runs as the same user (design-fleet D13/D78 scope the token to this execution's node, nothing more). The final `exec` chain forks nothing (busybox `nsenter -S/-G` execs, and the token goes in as a here-document, not a pipe, which would
|
|
11
|
+
# put a forking shell in front of it), so the process Blaxel tracks as `broker-<execution id>` is the start script itself: stopping it reaches
|
|
12
|
+
# the node and the broker, and its exit means the broker is gone. A failed download or checksum exits non-zero and prints `crtr-sandbox-start: failed <reason>`
|
|
10
13
|
# as its last line (the runner's `status` reports that reason).
|
|
11
14
|
set -eu
|
|
12
15
|
|
|
@@ -51,5 +54,8 @@ done
|
|
|
51
54
|
|
|
52
55
|
token=$CRTR_NODE_TOKEN
|
|
53
56
|
unset CRTR_NODE_TOKEN
|
|
54
|
-
# `su
|
|
55
|
-
|
|
57
|
+
# busybox `su` forks, which leaves the tracked process a wrapper whose children outlive a stop; `nsenter` with no namespace flag only sets the
|
|
58
|
+
# uid and gid, then execs. It keeps the environment (as `su -p` did); the start script derives HOME and the layout itself.
|
|
59
|
+
exec nsenter -S "$(id -u "$RUN_USER")" -G "$(id -g "$RUN_USER")" node "$RELEASE_DIR/scripts/sandbox-start.mjs" <<EOT
|
|
60
|
+
$token
|
|
61
|
+
EOT
|
|
@@ -344,34 +344,71 @@ async function start(env) {
|
|
|
344
344
|
gitConfig('credential.https://github.com.helper', join(releaseDir, 'scripts', 'git-credential-crtr'));
|
|
345
345
|
if (bundle.git_identity) { gitConfig('user.name', bundle.git_identity.name); gitConfig('user.email', bundle.git_identity.email); }
|
|
346
346
|
|
|
347
|
-
// 5.5 plugins, once per revision.
|
|
347
|
+
// 5.5 plugins, once per revision. The person's install rows are already authoritative on the control plane, and no broker or daemon exists yet, so
|
|
348
|
+
// this materializes each plugin's code the way `pkg plugin install` lays it out in the user scope (`<runtime>/marketplaces/<name>`, `<runtime>/plugins/<plugin>`)
|
|
349
|
+
// with git alone; the release CLI would load the person's settings from a daemon this start cannot reach.
|
|
348
350
|
const marker = join(work, '.crtr', 'plugins-revision');
|
|
349
351
|
const have = existsSync(marker) ? readFileSync(marker, 'utf8').trim() : null;
|
|
350
|
-
if (have !== String(bundle.plugins_revision)
|
|
351
|
-
const
|
|
352
|
-
const
|
|
353
|
-
const
|
|
352
|
+
if (have !== String(bundle.plugins_revision)) {
|
|
353
|
+
const pluginsDir = join(root, 'runtime', 'plugins');
|
|
354
|
+
const marketplacesDir = join(root, 'runtime', 'marketplaces');
|
|
355
|
+
const cloneEnv = { ...gitEnv, GIT_TERMINAL_PROMPT: '0' };
|
|
354
356
|
const detail = (r) => (r.stderr || r.stdout || '').slice(0, 300);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
const cloneInto = (url, ref, dest) => {
|
|
358
|
+
const r = run('git', ['clone', '--depth', '1', ...(ref ? ['--branch', ref] : []), url, dest], { env: cloneEnv, timeout: 300_000 });
|
|
359
|
+
return r.status === 0 ? null : detail(r) || `git clone exited ${r.status}`;
|
|
360
|
+
};
|
|
361
|
+
// The directory holds exactly the bundle's plugins: one an earlier revision installed and this one no longer lists is removed.
|
|
362
|
+
let current = null;
|
|
363
|
+
try {
|
|
364
|
+
const wanted = new Set(bundle.plugins.map((plugin) => plugin.name));
|
|
365
|
+
if (existsSync(pluginsDir)) for (const name of readdirSync(pluginsDir)) if (!wanted.has(name)) rmSync(join(pluginsDir, name), { recursive: true, force: true });
|
|
366
|
+
const readJson = (path) => { try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; } };
|
|
367
|
+
// A plugin of a marketplace is installed as `<plugin>` from the marketplace the bundle's url and ref name. Cloning again each start keeps a marketplace
|
|
368
|
+
// current; a plugin installed by an earlier revision is replaced.
|
|
358
369
|
const marketplaces = new Map();
|
|
359
370
|
for (const plugin of bundle.plugins) {
|
|
371
|
+
current = plugin.name;
|
|
360
372
|
if ('path' in plugin.source) throw new StartFailure('plugin_source_unreachable', `${plugin.name}: its source is ${plugin.source.path} on the machine that registered it, which a sandbox cannot read`);
|
|
361
373
|
const { url, ref } = plugin.source.marketplace;
|
|
362
374
|
if (!url) throw new StartFailure('plugin_source_unreachable', `${plugin.name}: its marketplace has no url`);
|
|
375
|
+
const fail = (message) => new StartFailure('plugin_install_failed', `${plugin.name}: ${message}`);
|
|
376
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(plugin.name)) throw fail('invalid plugin name');
|
|
363
377
|
const key = `${url}\0${ref ?? ''}`;
|
|
364
378
|
if (!marketplaces.has(key)) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
379
|
+
mkdirSync(marketplacesDir, { recursive: true });
|
|
380
|
+
const staging = join(marketplacesDir, `.clone-${process.pid}-${marketplaces.size}`);
|
|
381
|
+
rmSync(staging, { recursive: true, force: true });
|
|
382
|
+
const cloneError = cloneInto(url, ref, staging);
|
|
383
|
+
if (cloneError !== null) throw fail(`marketplace ${url}: ${cloneError}`);
|
|
384
|
+
const manifest = readJson(join(staging, '.crouter-marketplace', 'marketplace.json'));
|
|
385
|
+
if (typeof manifest?.name !== 'string' || !Array.isArray(manifest.plugins)) { rmSync(staging, { recursive: true, force: true }); throw fail(`marketplace ${url}: not a valid marketplace`); }
|
|
386
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(manifest.name)) { rmSync(staging, { recursive: true, force: true }); throw fail(`marketplace ${url}: invalid marketplace name ${JSON.stringify(manifest.name)}`); }
|
|
387
|
+
const dest = join(marketplacesDir, manifest.name);
|
|
388
|
+
rmSync(dest, { recursive: true, force: true });
|
|
389
|
+
renameSync(staging, dest);
|
|
390
|
+
marketplaces.set(key, { dest, manifest });
|
|
370
391
|
}
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
392
|
+
const { dest: marketplaceDir, manifest } = marketplaces.get(key);
|
|
393
|
+
const entry = manifest.plugins.find((candidate) => candidate?.name === plugin.name);
|
|
394
|
+
if (!entry || typeof entry.source !== 'string') throw fail(`not found in marketplace ${manifest.name}`);
|
|
395
|
+
const target = join(pluginsDir, plugin.name);
|
|
396
|
+
mkdirSync(pluginsDir, { recursive: true });
|
|
397
|
+
rmSync(target, { recursive: true, force: true });
|
|
398
|
+
if (/^(https?:\/\/|git@|ssh:\/\/|file:\/\/)/.test(entry.source) || entry.source.endsWith('.git')) {
|
|
399
|
+
const cloneError = cloneInto(entry.source, undefined, target);
|
|
400
|
+
if (cloneError !== null) throw fail(cloneError);
|
|
401
|
+
} else {
|
|
402
|
+
const source = resolve(marketplaceDir, entry.source);
|
|
403
|
+
if (!existsSync(source) || !within(realpathSync(marketplaceDir), realpathSync(source))) throw fail(`plugin source ${entry.source} is not in marketplace ${manifest.name}`);
|
|
404
|
+
symlinkSync(source, target, 'dir');
|
|
405
|
+
}
|
|
406
|
+
if (readJson(join(target, '.crouter-plugin', 'plugin.json')) === null) { rmSync(target, { recursive: true, force: true }); throw fail('its .crouter-plugin/plugin.json is missing or invalid'); }
|
|
407
|
+
}
|
|
408
|
+
} catch (error) {
|
|
409
|
+
// A filesystem error (mkdir, remove, rename, symlink) is an install failure of the plugin being installed, not a generic start failure.
|
|
410
|
+
if (error instanceof StartFailure) throw error;
|
|
411
|
+
throw new StartFailure('plugin_install_failed', `${current ?? 'plugins'}: ${error.message}`.slice(0, 300));
|
|
375
412
|
}
|
|
376
413
|
}
|
|
377
414
|
if (have !== String(bundle.plugins_revision)) { mkdirSync(dirname(marker), { recursive: true }); writeFileSync(marker, String(bundle.plugins_revision)); }
|