@north-light/crouter 0.3.276 → 0.3.277
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/clients/attach/viewer.js +406 -406
- package/dist/core/__tests__/integration/command-plugins.test.js +6 -6
- package/dist/core/command-manifests/manifest.d.ts +4 -2
- package/dist/core/command-plugins/help-addenda.d.ts +12 -3
- package/dist/core/command-plugins/help-addenda.js +17 -4
- package/dist/core/command.js +2 -2
- package/dist/daemon/reconcilers/live-obligation.d.ts +3 -2
- package/dist/daemon/reconcilers/live-obligation.js +4 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +2 -2
- package/package.json +3 -1
- package/runtime.lock.json +2 -2
|
@@ -821,9 +821,9 @@ describe('typed validation issues', () => {
|
|
|
821
821
|
describe('plugin help addenda', () => {
|
|
822
822
|
const gate = (paths) => ({
|
|
823
823
|
reservedCoreNames: RESERVED,
|
|
824
|
-
coreCommandPaths: new Set(paths),
|
|
824
|
+
coreCommandPaths: () => Promise.resolve(new Set(paths)),
|
|
825
825
|
});
|
|
826
|
-
test('a helpAddenda entry survives validation and is collected attributed to its plugin', () => {
|
|
826
|
+
test('a helpAddenda entry survives validation and is collected attributed to its plugin', async () => {
|
|
827
827
|
const m = commandsJson();
|
|
828
828
|
m['helpAddenda'] = { cron: 'Gate on the device: exit 75 parks the occurrence.' };
|
|
829
829
|
installPlugin(userRoot, 'northlight-fixture', { manifest: m });
|
|
@@ -833,12 +833,12 @@ describe('plugin help addenda', () => {
|
|
|
833
833
|
assert.equal(v.issues.length, 0, JSON.stringify(v.issues));
|
|
834
834
|
assert.deepEqual(v.manifest?.helpAddenda, { cron: 'Gate on the device: exit 75 parks the occurrence.' });
|
|
835
835
|
// The help-render lookup: attributed, and empty for an unaddended path.
|
|
836
|
-
assert.deepEqual(collectHelpAddenda('cron', gate(['cron', 'node']), emptyStart), [
|
|
836
|
+
assert.deepEqual(await collectHelpAddenda('cron', gate(['cron', 'node']), emptyStart), [
|
|
837
837
|
{ plugin: 'northlight-fixture', text: 'Gate on the device: exit 75 parks the occurrence.' },
|
|
838
838
|
]);
|
|
839
|
-
assert.deepEqual(collectHelpAddenda('node', gate(['cron', 'node']), emptyStart), []);
|
|
839
|
+
assert.deepEqual(await collectHelpAddenda('node', gate(['cron', 'node']), emptyStart), []);
|
|
840
840
|
});
|
|
841
|
-
test('a key naming no core command path fails the strict gate and renders nothing, even beside a valid key', () => {
|
|
841
|
+
test('a key naming no core command path fails the strict gate and renders nothing, even beside a valid key', async () => {
|
|
842
842
|
const m = commandsJson();
|
|
843
843
|
m['helpAddenda'] = {
|
|
844
844
|
cron: 'a valid target beside the typo',
|
|
@@ -860,7 +860,7 @@ describe('plugin help addenda', () => {
|
|
|
860
860
|
// The render lookup reproduces the GATE verdict, not the compose one: the
|
|
861
861
|
// rejected map contributes nothing — including its valid key — so render
|
|
862
862
|
// can never show what the install report said was rejected.
|
|
863
|
-
assert.deepEqual(collectHelpAddenda('cron', gate(['cron', 'cron add']), emptyStart), []);
|
|
863
|
+
assert.deepEqual(await collectHelpAddenda('cron', gate(['cron', 'cron add']), emptyStart), []);
|
|
864
864
|
});
|
|
865
865
|
test('an installed addendum renders through the real help path as an attributed block', async () => {
|
|
866
866
|
const m = commandsJson();
|
|
@@ -29,8 +29,10 @@ export declare function validateCommandManifest(raw: unknown, options: {
|
|
|
29
29
|
* (install, bundle parse, doctor/inspect reports) so a helpAddenda key
|
|
30
30
|
* naming no core command fails there — and by the help-render lookup
|
|
31
31
|
* (`collectHelpAddenda`), which reproduces the gate's verdict so render
|
|
32
|
-
* and ingress report can never disagree. Omitted
|
|
33
|
-
*
|
|
32
|
+
* and ingress report can never disagree. Omitted on the tolerant compose
|
|
33
|
+
* path, where mounting ignores addenda entirely, and on that lookup's
|
|
34
|
+
* claimant pass, which only needs to know whether any addendum targets the
|
|
35
|
+
* rendered path at all. */
|
|
34
36
|
coreCommandPaths?: ReadonlySet<string>;
|
|
35
37
|
/** A repository fragment attaches roots below an existing extensible branch. */
|
|
36
38
|
extensionFragment?: boolean;
|
|
@@ -5,8 +5,17 @@ export interface HelpAddendum {
|
|
|
5
5
|
/** Collect every enabled plugin's addendum targeting `commandPath` (the
|
|
6
6
|
* space-joined walked path, e.g. "cron add"), in plugin-name order. `gate`
|
|
7
7
|
* carries the strict-validation inputs the install/bundle gates use; the
|
|
8
|
-
* help renderer already holds both (build-root exports them).
|
|
8
|
+
* help renderer already holds both (build-root exports them).
|
|
9
|
+
*
|
|
10
|
+
* `coreCommandPaths` arrives as a thunk because producing that set walks EVERY
|
|
11
|
+
* core subtree — the whole command tree loaded on a path that renders one
|
|
12
|
+
* leaf's help. So the claimant pass below runs the SAME validator without it:
|
|
13
|
+
* the core path set can only reject an addendum, never introduce one, so a
|
|
14
|
+
* commandPath no manifest claims tolerantly claims none under the gate either,
|
|
15
|
+
* and the expensive input is never forced. Claimants then re-validate strictly,
|
|
16
|
+
* reproducing the gate's exact verdict — a manifest the gate rejects renders
|
|
17
|
+
* nothing, including its valid keys. */
|
|
9
18
|
export declare function collectHelpAddenda(commandPath: string, gate: {
|
|
10
19
|
reservedCoreNames: ReadonlySet<string>;
|
|
11
|
-
coreCommandPaths: ReadonlySet<string
|
|
12
|
-
}, startDir?: string, profileId?: string | null): HelpAddendum[]
|
|
20
|
+
coreCommandPaths: () => Promise<ReadonlySet<string>>;
|
|
21
|
+
}, startDir?: string, profileId?: string | null): Promise<HelpAddendum[]>;
|
|
@@ -15,13 +15,26 @@ import { effectiveCommandPlugins, validatePluginCommands } from './discovery.js'
|
|
|
15
15
|
/** Collect every enabled plugin's addendum targeting `commandPath` (the
|
|
16
16
|
* space-joined walked path, e.g. "cron add"), in plugin-name order. `gate`
|
|
17
17
|
* carries the strict-validation inputs the install/bundle gates use; the
|
|
18
|
-
* help renderer already holds both (build-root exports them).
|
|
19
|
-
|
|
18
|
+
* help renderer already holds both (build-root exports them).
|
|
19
|
+
*
|
|
20
|
+
* `coreCommandPaths` arrives as a thunk because producing that set walks EVERY
|
|
21
|
+
* core subtree — the whole command tree loaded on a path that renders one
|
|
22
|
+
* leaf's help. So the claimant pass below runs the SAME validator without it:
|
|
23
|
+
* the core path set can only reject an addendum, never introduce one, so a
|
|
24
|
+
* commandPath no manifest claims tolerantly claims none under the gate either,
|
|
25
|
+
* and the expensive input is never forced. Claimants then re-validate strictly,
|
|
26
|
+
* reproducing the gate's exact verdict — a manifest the gate rejects renders
|
|
27
|
+
* nothing, including its valid keys. */
|
|
28
|
+
export async function collectHelpAddenda(commandPath, gate, startDir, profileId) {
|
|
20
29
|
if (commandPath.length === 0)
|
|
21
30
|
return [];
|
|
31
|
+
const claimants = effectiveCommandPlugins(startDir, profileId).filter((plugin) => validatePluginCommands(plugin, gate.reservedCoreNames).manifest?.helpAddenda?.[commandPath] !== undefined);
|
|
32
|
+
if (claimants.length === 0)
|
|
33
|
+
return [];
|
|
34
|
+
const coreCommandPaths = await gate.coreCommandPaths();
|
|
22
35
|
const out = [];
|
|
23
|
-
for (const plugin of
|
|
24
|
-
const text = validatePluginCommands(plugin, gate.reservedCoreNames,
|
|
36
|
+
for (const plugin of claimants) {
|
|
37
|
+
const text = validatePluginCommands(plugin, gate.reservedCoreNames, coreCommandPaths).manifest?.helpAddenda?.[commandPath];
|
|
25
38
|
if (text !== undefined)
|
|
26
39
|
out.push({ plugin: plugin.name, text });
|
|
27
40
|
}
|
package/dist/core/command.js
CHANGED
|
@@ -209,9 +209,9 @@ async function renderNodeWithAddenda(node, path, focusedFlag) {
|
|
|
209
209
|
import('./command-plugins/help-addenda.js'),
|
|
210
210
|
import('../build-root.js'),
|
|
211
211
|
]);
|
|
212
|
-
const addenda = collectHelpAddenda(path.join(' '), {
|
|
212
|
+
const addenda = await collectHelpAddenda(path.join(' '), {
|
|
213
213
|
reservedCoreNames: new Set(SUBTREE_NAMES),
|
|
214
|
-
coreCommandPaths
|
|
214
|
+
coreCommandPaths,
|
|
215
215
|
});
|
|
216
216
|
return body + addenda.map((a) => `\n\n<plugin-help plugin="${a.plugin}">\n${a.text}\n</plugin-help>`).join('');
|
|
217
217
|
}
|
|
@@ -20,8 +20,9 @@ export declare function hasHumanViewer(nodeId: string): boolean;
|
|
|
20
20
|
/** What parking an unattended node actually amounts to — the one rule both the
|
|
21
21
|
* live-broker park clock and the lifecycle tick apply.
|
|
22
22
|
*
|
|
23
|
-
* `release` — something can still wake it,
|
|
24
|
-
*
|
|
23
|
+
* `release` — something can still wake it, a person is watching it, or a
|
|
24
|
+
* fatal provider fault prevents its engine from completing the
|
|
25
|
+
* summary turn; parking is a pause.
|
|
25
26
|
* `park` — a resident with nothing left to wake it has completed its
|
|
26
27
|
* conversation, and the row becomes prunable history. The live
|
|
27
28
|
* clock asks it for a summary turn first; this verdict is what
|
|
@@ -6,6 +6,7 @@ import { readOutputRequest } from '../../core/runtime/structured-output.js';
|
|
|
6
6
|
import { focusOf } from '../../core/runtime/placement.js';
|
|
7
7
|
import { hasAttachedBrokerViewers } from '../../core/runtime/broker/client-registry.js';
|
|
8
8
|
import { fanDoctrineWake } from '../../core/runtime/close.js';
|
|
9
|
+
import { readFault } from '../../core/runtime/fault.js';
|
|
9
10
|
import { transition } from '../../core/runtime/lifecycle.js';
|
|
10
11
|
/** Durable work that requires a node to remain automatically wakeable. */
|
|
11
12
|
export function hasLiveObligation(nodeId) {
|
|
@@ -50,6 +51,9 @@ export function parkEvent(nodeId, lifecycle) {
|
|
|
50
51
|
return 'release';
|
|
51
52
|
if (hasLiveObligation(nodeId))
|
|
52
53
|
return 'release';
|
|
54
|
+
const fault = readFault(nodeId);
|
|
55
|
+
if (fault?.link === 'pi→provider' && fault.retry.disposition === 'fatal')
|
|
56
|
+
return 'release';
|
|
53
57
|
if (lifecycle === 'resident')
|
|
54
58
|
return hasUnseenWakeMail(nodeId) ? 'release' : 'park';
|
|
55
59
|
if (readInboxSince(nodeId, readCursor(nodeId)).length > 0)
|
package/dist/types.d.ts
CHANGED
|
@@ -311,11 +311,11 @@ export interface ScopeConfig {
|
|
|
311
311
|
/** Fold every SETTLED tool call in the attach viewer down to its single call
|
|
312
312
|
* line (`/fold-tools`, Alt+C → z → f). A tool still running is never folded —
|
|
313
313
|
* it folds itself once it settles. Toggling it in a viewer writes back here,
|
|
314
|
-
* so the choice carries into later chats and later viewers. Default
|
|
314
|
+
* so the choice carries into later chats and later viewers. Default true. */
|
|
315
315
|
fold_finished_tools: boolean;
|
|
316
316
|
/** Replace completed folded tool-call groups with a broker-generated one-line
|
|
317
317
|
* summary when one is available. Pending and failed groups stay as calls.
|
|
318
|
-
* Default
|
|
318
|
+
* Default true. */
|
|
319
319
|
summarize_tool_calls: boolean;
|
|
320
320
|
/** Show individual memory reads and edited files in a folded tool recap.
|
|
321
321
|
* Off shows the compact activity-count line instead. Default true. */
|
package/dist/types.js
CHANGED
|
@@ -97,8 +97,8 @@ export function defaultScopeConfig() {
|
|
|
97
97
|
live_cycles: DEFAULT_LIVE_CYCLES,
|
|
98
98
|
condensed_history: DEFAULT_CONDENSED_HISTORY,
|
|
99
99
|
bash_tool_purpose: true,
|
|
100
|
-
fold_finished_tools:
|
|
101
|
-
summarize_tool_calls:
|
|
100
|
+
fold_finished_tools: true,
|
|
101
|
+
summarize_tool_calls: true,
|
|
102
102
|
detailed_tool_recaps: true,
|
|
103
103
|
keybindings: {},
|
|
104
104
|
tmux_passthrough: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.277",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
],
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "node scripts/build.mjs",
|
|
73
|
+
"activate": "npm run build && npm run install-runtime && crtr sys daemon restart",
|
|
74
|
+
"activate:pull": "git pull && npm install && npm run activate",
|
|
73
75
|
"build:api": "tsc -p packages/crouter-api/tsconfig.json",
|
|
74
76
|
"typecheck:pi-packages": "npx tsc -p src/builtin-pi-packages/pi-crtr-extensions/tsconfig.json",
|
|
75
77
|
"test:build-output": "node scripts/build-concurrency-test.mjs",
|
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.277",
|
|
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.277",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|