@north-light/crouter 0.3.197 → 0.3.198
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 +2 -2
- package/dist/api/dto/inbox.d.ts +46 -0
- package/dist/builtin-memory/00-runtime-base.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/render/chat-view.d.ts +3 -8
- package/dist/clients/attach/render/chat-view.js +3 -24
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/viewer.js +613 -602
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +2 -2
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +11 -4
- package/dist/clients/inbox/controller.js +4 -4
- package/dist/clients/inbox/page-adapter.d.ts +2 -1
- package/dist/clients/inbox/tui/render.js +46 -25
- package/dist/clients/inbox/tui/slots.d.ts +1 -3
- package/dist/clients/inbox/tui/slots.js +8 -37
- package/dist/clients/inbox/tui/types.d.ts +4 -3
- package/dist/commands/__tests__/human.test.js +160 -23
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/human/doc.js +21 -74
- package/dist/commands/human/html.d.ts +2 -0
- package/dist/commands/human/html.js +81 -0
- package/dist/commands/human/prompts.d.ts +5 -4
- package/dist/commands/human/prompts.js +81 -153
- package/dist/commands/human/shared.d.ts +5 -6
- package/dist/commands/human/shared.js +13 -61
- package/dist/commands/human.js +7 -2
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/human-deliver.test.js +28 -37
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +63 -4
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +3 -13
- package/dist/core/canvas/__tests__/attention.test.js +2 -2
- package/dist/core/canvas/__tests__/render-remote.test.js +2 -2
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +26 -0
- package/dist/core/canvas/canvas.js +55 -0
- package/dist/core/command.js +6 -11
- package/dist/core/config.js +3 -2
- package/dist/core/help.d.ts +3 -0
- package/dist/core/human/__tests__/html-markdown.test.d.ts +1 -0
- package/dist/core/human/__tests__/html-markdown.test.js +52 -0
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +16 -0
- package/dist/core/human/__tests__/page-render.test.js +40 -23
- package/dist/core/human/__tests__/page-tickets.test.js +48 -27
- package/dist/core/human/__tests__/page.test.js +48 -65
- package/dist/core/human/__tests__/serial/inbox-core.test.js +5 -15
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +51 -0
- package/dist/core/human/answer.js +125 -0
- package/dist/core/human/component-docs.d.ts +14 -0
- package/dist/core/human/component-docs.js +161 -0
- package/dist/core/human/html-markdown.d.ts +3 -0
- package/dist/core/human/html-markdown.js +288 -0
- package/dist/core/human/page-catalog.d.ts +19 -3
- package/dist/core/human/page-catalog.js +73 -16
- package/dist/core/human/page-render.d.ts +3 -2
- package/dist/core/human/page-render.js +19 -45
- package/dist/core/human/page-schema.d.ts +108 -8
- package/dist/core/human/page-schema.js +88 -31
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +23 -7
- package/dist/core/human/page.d.ts +14 -7
- package/dist/core/human/page.js +178 -90
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/scan.d.ts +3 -2
- package/dist/core/human/scan.js +7 -8
- package/dist/core/human/summary.d.ts +4 -7
- package/dist/core/human/summary.js +4 -60
- package/dist/core/human/tickets.d.ts +10 -9
- package/dist/core/human/tickets.js +5 -3
- package/dist/core/human/types.d.ts +4 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/review/realize.js +9 -1
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker.js +8 -6
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/user-settings.d.ts +17 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/daemon/api/handlers/human.js +1 -1
- package/dist/daemon/api/handlers/inbox.js +22 -7
- package/dist/daemon/cron-run.js +16 -2
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +2 -1
- package/dist/daemon/human/finish.js +45 -28
- package/dist/daemon/human/sweep.js +4 -2
- package/dist/daemon/reconcilers/broker-supervision.js +11 -8
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pages/bundle.css +1 -1
- package/dist/pages/bundle.js +1008 -577
- package/dist/pages/comments.d.ts +29 -0
- package/dist/pages/comments.js +51 -0
- package/dist/pages/controls.d.ts +98 -0
- package/dist/pages/controls.js +261 -0
- package/dist/pages/elements/cards.d.ts +15 -4
- package/dist/pages/elements/cards.js +299 -186
- package/dist/pages/elements/chart.d.ts +22 -0
- package/dist/pages/elements/chart.js +107 -91
- package/dist/pages/elements/options.d.ts +9 -0
- package/dist/pages/elements/options.js +268 -155
- package/dist/pages/elements/pages.d.ts +6 -0
- package/dist/pages/elements/pages.js +60 -38
- package/dist/pages/elements/slot.d.ts +5 -0
- package/dist/pages/elements/slot.js +17 -10
- package/dist/pages/elements/table.d.ts +17 -0
- package/dist/pages/elements/table.js +336 -228
- package/dist/pages/elements/text.d.ts +9 -3
- package/dist/pages/elements/text.js +350 -143
- package/dist/pages/host.d.ts +0 -24
- package/dist/pages/host.js +0 -17
- package/dist/pages/readonly.d.ts +24 -0
- package/dist/pages/readonly.js +31 -0
- package/dist/pages/responses.d.ts +37 -0
- package/dist/pages/responses.js +56 -0
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +12 -8
- package/dist/types.d.ts +19 -5
- package/dist/types.js +2 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/scripts/install-runtime.mjs +19 -24
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
package/dist/pages/host.d.ts
CHANGED
|
@@ -34,11 +34,6 @@ export interface CrtrThemeBridge {
|
|
|
34
34
|
/** Registers `listener` for later theme changes; returns its unsubscribe. */
|
|
35
35
|
subscribe(listener: (tokens: Record<string, string>) => void): () => void;
|
|
36
36
|
}
|
|
37
|
-
/** Data behind a `source`-bearing config (table, cards, chart). */
|
|
38
|
-
export interface CrtrArtifactBridge {
|
|
39
|
-
/** Resolves the already-parsed data for `source`, or rejects when it is unreachable. */
|
|
40
|
-
data(source: string): Promise<unknown>;
|
|
41
|
-
}
|
|
42
37
|
/** What `mountWorkspaceComponent` can answer: the host rendered it, or it has no renderer. */
|
|
43
38
|
export type WorkspaceMountOutcome = 'mounted' | 'unavailable';
|
|
44
39
|
/** The bridge a host installs at `window.crtr`. */
|
|
@@ -59,7 +54,6 @@ export interface CrtrHost {
|
|
|
59
54
|
/** Flushes the pending autosave of the partial map. */
|
|
60
55
|
progress(): Promise<void>;
|
|
61
56
|
readonly theme: CrtrThemeBridge;
|
|
62
|
-
readonly artifact: CrtrArtifactBridge;
|
|
63
57
|
/**
|
|
64
58
|
* Asks the host to render a product-registered (`unvalidated`) slot into `element`.
|
|
65
59
|
* A host with no renderer for `kind` answers `'unavailable'`.
|
|
@@ -90,17 +84,6 @@ export type HostCall = {
|
|
|
90
84
|
status: 'failed';
|
|
91
85
|
reason: string;
|
|
92
86
|
};
|
|
93
|
-
/** The outcome of an artifact read, with the same three-way split as `HostCall`. */
|
|
94
|
-
export type ArtifactData = {
|
|
95
|
-
status: 'ok';
|
|
96
|
-
data: unknown;
|
|
97
|
-
} | {
|
|
98
|
-
status: 'unavailable';
|
|
99
|
-
reason: string;
|
|
100
|
-
} | {
|
|
101
|
-
status: 'failed';
|
|
102
|
-
reason: string;
|
|
103
|
-
};
|
|
104
87
|
/** The installed bridge, or `undefined` on a bare page. */
|
|
105
88
|
export declare function host(): CrtrHost | undefined;
|
|
106
89
|
/**
|
|
@@ -143,13 +126,6 @@ export declare function themeTokens(): Record<string, string>;
|
|
|
143
126
|
* to `disconnectedCallback` without checking whether a host was there.
|
|
144
127
|
*/
|
|
145
128
|
export declare function subscribeTheme(listener: (tokens: Record<string, string>) => void): () => void;
|
|
146
|
-
/**
|
|
147
|
-
* Reads the data behind a `source`-bearing config. The host resolves the reference and hands
|
|
148
|
-
* back parsed data; an element never resolves a reference itself and never fetches a URL.
|
|
149
|
-
* Anything other than `'ok'` is a named unavailable state to render, not a reason to retry
|
|
150
|
-
* some other way.
|
|
151
|
-
*/
|
|
152
|
-
export declare function artifactData(source: string): Promise<ArtifactData>;
|
|
153
129
|
/**
|
|
154
130
|
* Asks the host to render a product-registered slot into `element`. A bare page, a host with
|
|
155
131
|
* no registry, and a host whose mount threw all answer `'unavailable'` — the escape element's
|
package/dist/pages/host.js
CHANGED
|
@@ -147,23 +147,6 @@ export function subscribeTheme(listener) {
|
|
|
147
147
|
return () => { };
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
/**
|
|
151
|
-
* Reads the data behind a `source`-bearing config. The host resolves the reference and hands
|
|
152
|
-
* back parsed data; an element never resolves a reference itself and never fetches a URL.
|
|
153
|
-
* Anything other than `'ok'` is a named unavailable state to render, not a reason to retry
|
|
154
|
-
* some other way.
|
|
155
|
-
*/
|
|
156
|
-
export async function artifactData(source) {
|
|
157
|
-
const bridge = host();
|
|
158
|
-
if (typeof bridge?.artifact?.data !== 'function')
|
|
159
|
-
return missing('artifact.data');
|
|
160
|
-
try {
|
|
161
|
-
return { status: 'ok', data: await bridge.artifact.data(source) };
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
return { status: 'failed', reason: reasonOf(error) };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
150
|
/**
|
|
168
151
|
* Asks the host to render a product-registered slot into `element`. A bare page, a host with
|
|
169
152
|
* no registry, and a host whose mount threw all answer `'unavailable'` — the escape element's
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only presentation for a page that can no longer take an answer.
|
|
3
|
+
*
|
|
4
|
+
* A resolved or canceled page is still opened, read, and saved — the host renders it exactly
|
|
5
|
+
* as before and marks it out of reach: `inert` on each response-bearing element (Northlight's
|
|
6
|
+
* srcdoc host does this on every `[slot-id]`), and `disabled` on the pager so submit is gone.
|
|
7
|
+
* `inert` blocks pointer and keyboard interaction, but it is only a hit-testing rule: the
|
|
8
|
+
* controls underneath still LOOK live, and a scripted click still flips a checkbox. So the
|
|
9
|
+
* elements take the same signal and put it where a person can see it — every input renders
|
|
10
|
+
* disabled and every writing box read-only, which is also what makes the state unreachable
|
|
11
|
+
* rather than merely unclickable.
|
|
12
|
+
*
|
|
13
|
+
* The signal is read from the DOM rather than from a new attribute, because both halves of it
|
|
14
|
+
* already exist: `inert` is a standard reflected attribute the host sets, and `disabled` on
|
|
15
|
+
* `<crtr-pages>` is the attribute the pager itself observes.
|
|
16
|
+
*/
|
|
17
|
+
/** Whether this element sits on a page that can no longer take an answer. */
|
|
18
|
+
export declare function isReadOnly(element: HTMLElement): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Paints `root`'s controls read-only (or live again). Inputs go `disabled` — greyed, skipped
|
|
21
|
+
* by the tab order, and deaf even to a scripted `click()` — and writing boxes go `readOnly`,
|
|
22
|
+
* which keeps their text selectable and copyable while refusing every edit.
|
|
23
|
+
*/
|
|
24
|
+
export declare function applyReadOnly(root: ParentNode, readOnly: boolean): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only presentation for a page that can no longer take an answer.
|
|
3
|
+
*
|
|
4
|
+
* A resolved or canceled page is still opened, read, and saved — the host renders it exactly
|
|
5
|
+
* as before and marks it out of reach: `inert` on each response-bearing element (Northlight's
|
|
6
|
+
* srcdoc host does this on every `[slot-id]`), and `disabled` on the pager so submit is gone.
|
|
7
|
+
* `inert` blocks pointer and keyboard interaction, but it is only a hit-testing rule: the
|
|
8
|
+
* controls underneath still LOOK live, and a scripted click still flips a checkbox. So the
|
|
9
|
+
* elements take the same signal and put it where a person can see it — every input renders
|
|
10
|
+
* disabled and every writing box read-only, which is also what makes the state unreachable
|
|
11
|
+
* rather than merely unclickable.
|
|
12
|
+
*
|
|
13
|
+
* The signal is read from the DOM rather than from a new attribute, because both halves of it
|
|
14
|
+
* already exist: `inert` is a standard reflected attribute the host sets, and `disabled` on
|
|
15
|
+
* `<crtr-pages>` is the attribute the pager itself observes.
|
|
16
|
+
*/
|
|
17
|
+
/** Whether this element sits on a page that can no longer take an answer. */
|
|
18
|
+
export function isReadOnly(element) {
|
|
19
|
+
return element.closest('[inert], crtr-pages[disabled]') !== null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Paints `root`'s controls read-only (or live again). Inputs go `disabled` — greyed, skipped
|
|
23
|
+
* by the tab order, and deaf even to a scripted `click()` — and writing boxes go `readOnly`,
|
|
24
|
+
* which keeps their text selectable and copyable while refusing every edit.
|
|
25
|
+
*/
|
|
26
|
+
export function applyReadOnly(root, readOnly) {
|
|
27
|
+
for (const input of root.querySelectorAll('input'))
|
|
28
|
+
input.disabled = readOnly;
|
|
29
|
+
for (const area of root.querySelectorAll('textarea'))
|
|
30
|
+
area.readOnly = readOnly;
|
|
31
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the page would submit — every response-bearing slot, not only the touched ones.
|
|
3
|
+
*
|
|
4
|
+
* `respond()` is a user-change channel: an element calls it when the person changes
|
|
5
|
+
* something, so a slot nobody touched has never crossed the bridge and the host's map does
|
|
6
|
+
* not name it. A final submit must name every response-bearing slot, and crtrd rejects a
|
|
7
|
+
* map that does not — correctly, because "this slot has no answer" and "the page forgot to
|
|
8
|
+
* mention it" would otherwise be the same thing on the wire.
|
|
9
|
+
*
|
|
10
|
+
* The answer for an untouched slot is not a guess and it is not a per-kind default anyone
|
|
11
|
+
* has to re-derive: it is exactly what that slot is showing right now — a text slot's
|
|
12
|
+
* authored text, unedited; a selection nobody made. Each element already builds that object
|
|
13
|
+
* for `respond()`. So each one registers its reader here at mount, and the pager reports all
|
|
14
|
+
* of them through `respond()` immediately before it submits.
|
|
15
|
+
*
|
|
16
|
+
* Registration itself writes nothing and asks for no autosave: opening a page and closing it
|
|
17
|
+
* again saves exactly what it saved before. The reporting happens at submit, where the
|
|
18
|
+
* autosave the reports schedule is flushed by the submit itself.
|
|
19
|
+
*/
|
|
20
|
+
import type { SlotResponse } from './types.js';
|
|
21
|
+
/**
|
|
22
|
+
* Registers this element as the answer for `slotId`. Call it once at mount, after the slot's
|
|
23
|
+
* config has been read and its state hydrated, and only from an element whose slot actually
|
|
24
|
+
* bears a response — reporting a response for a display-only slot is a submit the host will
|
|
25
|
+
* reject by name. `read` is called at submit time, so it must return the slot's complete
|
|
26
|
+
* current response, exactly what `respond()` is handed on a user change.
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerSlotResponse(element: HTMLElement, slotId: string, read: () => SlotResponse): void;
|
|
29
|
+
/**
|
|
30
|
+
* Hands the host every registered slot's current response, so the map it holds is the whole
|
|
31
|
+
* page. Only the pager calls this, and only immediately before `submit()`.
|
|
32
|
+
*
|
|
33
|
+
* A reader whose element has left the document names a slot that is no longer on screen, and
|
|
34
|
+
* a reader that throws has no answer to give; both are dropped rather than reported, which
|
|
35
|
+
* leaves the host's map exactly as complete as the page really is.
|
|
36
|
+
*/
|
|
37
|
+
export declare function reportSlotResponses(): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the page would submit — every response-bearing slot, not only the touched ones.
|
|
3
|
+
*
|
|
4
|
+
* `respond()` is a user-change channel: an element calls it when the person changes
|
|
5
|
+
* something, so a slot nobody touched has never crossed the bridge and the host's map does
|
|
6
|
+
* not name it. A final submit must name every response-bearing slot, and crtrd rejects a
|
|
7
|
+
* map that does not — correctly, because "this slot has no answer" and "the page forgot to
|
|
8
|
+
* mention it" would otherwise be the same thing on the wire.
|
|
9
|
+
*
|
|
10
|
+
* The answer for an untouched slot is not a guess and it is not a per-kind default anyone
|
|
11
|
+
* has to re-derive: it is exactly what that slot is showing right now — a text slot's
|
|
12
|
+
* authored text, unedited; a selection nobody made. Each element already builds that object
|
|
13
|
+
* for `respond()`. So each one registers its reader here at mount, and the pager reports all
|
|
14
|
+
* of them through `respond()` immediately before it submits.
|
|
15
|
+
*
|
|
16
|
+
* Registration itself writes nothing and asks for no autosave: opening a page and closing it
|
|
17
|
+
* again saves exactly what it saved before. The reporting happens at submit, where the
|
|
18
|
+
* autosave the reports schedule is flushed by the submit itself.
|
|
19
|
+
*/
|
|
20
|
+
import { respond } from './host.js';
|
|
21
|
+
/** One entry per slot id; a second registration for an id replaces the first. */
|
|
22
|
+
const readers = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Registers this element as the answer for `slotId`. Call it once at mount, after the slot's
|
|
25
|
+
* config has been read and its state hydrated, and only from an element whose slot actually
|
|
26
|
+
* bears a response — reporting a response for a display-only slot is a submit the host will
|
|
27
|
+
* reject by name. `read` is called at submit time, so it must return the slot's complete
|
|
28
|
+
* current response, exactly what `respond()` is handed on a user change.
|
|
29
|
+
*/
|
|
30
|
+
export function registerSlotResponse(element, slotId, read) {
|
|
31
|
+
readers.set(slotId, { element, read });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hands the host every registered slot's current response, so the map it holds is the whole
|
|
35
|
+
* page. Only the pager calls this, and only immediately before `submit()`.
|
|
36
|
+
*
|
|
37
|
+
* A reader whose element has left the document names a slot that is no longer on screen, and
|
|
38
|
+
* a reader that throws has no answer to give; both are dropped rather than reported, which
|
|
39
|
+
* leaves the host's map exactly as complete as the page really is.
|
|
40
|
+
*/
|
|
41
|
+
export function reportSlotResponses() {
|
|
42
|
+
for (const [slotId, reader] of [...readers]) {
|
|
43
|
+
if (!reader.element.isConnected) {
|
|
44
|
+
readers.delete(slotId);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = reader.read();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
respond(slotId, response);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BashOperations } from '@earendil-works/pi-coding-agent';
|
|
1
|
+
import { createBashToolDefinition, type BashOperations } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
3
3
|
/** Seconds a leading `sleep ...` will block for, or null when the command does
|
|
4
4
|
* not open with a sleep (or its duration isn't statically knowable). */
|
|
@@ -7,4 +7,12 @@ export declare function leadingSleepSeconds(command: string): number | null;
|
|
|
7
7
|
* `takePurpose`, so concurrent calls cannot exchange labels. It is consumed even
|
|
8
8
|
* when the command is invalid or refused, leaving no state to leak later. */
|
|
9
9
|
export declare function createValveOperations(nodeId: string, contextDir: string, takePurpose?: () => string | null): BashOperations;
|
|
10
|
+
type BashToolDefinition = ReturnType<typeof createBashToolDefinition>;
|
|
11
|
+
/** Build the valve-backed bash tool and, when configured, add `purpose` to its
|
|
12
|
+
* schema. Bind each purpose directly to its own execution: Pi may preflight a
|
|
13
|
+
* batch before starting its calls, so a shared "next purpose" slot could give a
|
|
14
|
+
* concurrent call the wrong label. Disabled means absent, not merely ignored —
|
|
15
|
+
* the model receives pi's upstream bash schema verbatim. */
|
|
16
|
+
export declare function createValveToolDefinition(nodeId: string, contextDir: string, cwd: string, exposePurpose: boolean): BashToolDefinition;
|
|
10
17
|
export default function (pi: ExtensionAPI): void;
|
|
18
|
+
export {};
|
|
@@ -28,6 +28,7 @@ import { spawn } from 'node:child_process';
|
|
|
28
28
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
29
29
|
import { homedir } from 'node:os';
|
|
30
30
|
import { backgroundBashJob, bashJobPaths, formatBashElapsed, newBashJobId, normalizeBashJobPurpose } from '../core/bash-jobs.js';
|
|
31
|
+
import { readConfig } from '../core/config.js';
|
|
31
32
|
import { Type } from 'typebox';
|
|
32
33
|
import { createBashToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
@@ -351,7 +352,7 @@ export function createValveOperations(nodeId, contextDir, takePurpose = () => nu
|
|
|
351
352
|
// ---------------------------------------------------------------------------
|
|
352
353
|
// `purpose` — the one line a person reads while a command runs
|
|
353
354
|
//
|
|
354
|
-
//
|
|
355
|
+
// An optional deviation from taking pi's definition verbatim, and the only one
|
|
355
356
|
// here. Everywhere crtr's output reaches a non-technical audience, the host has
|
|
356
357
|
// to render SOMETHING per step, and without this field its only material is the
|
|
357
358
|
// command — which yields "Running command" for an entire turn. The model is the
|
|
@@ -393,13 +394,16 @@ function withPurpose(definition) {
|
|
|
393
394
|
}),
|
|
394
395
|
};
|
|
395
396
|
}
|
|
396
|
-
/**
|
|
397
|
-
*
|
|
398
|
-
* a
|
|
399
|
-
|
|
400
|
-
|
|
397
|
+
/** Build the valve-backed bash tool and, when configured, add `purpose` to its
|
|
398
|
+
* schema. Bind each purpose directly to its own execution: Pi may preflight a
|
|
399
|
+
* batch before starting its calls, so a shared "next purpose" slot could give a
|
|
400
|
+
* concurrent call the wrong label. Disabled means absent, not merely ignored —
|
|
401
|
+
* the model receives pi's upstream bash schema verbatim. */
|
|
402
|
+
export function createValveToolDefinition(nodeId, contextDir, cwd, exposePurpose) {
|
|
403
|
+
const baseDefinition = createBashToolDefinition(cwd, { operations: createValveOperations(nodeId, contextDir) });
|
|
404
|
+
const schemaDefinition = exposePurpose ? withPurpose(baseDefinition) : baseDefinition;
|
|
401
405
|
const execute = (toolCallId, params, signal, onUpdate, ctx) => {
|
|
402
|
-
const purpose = normalizeBashJobPurpose(params['purpose']);
|
|
406
|
+
const purpose = exposePurpose ? normalizeBashJobPurpose(params['purpose']) : null;
|
|
403
407
|
const definition = createBashToolDefinition(cwd, { operations: createValveOperations(nodeId, contextDir, () => purpose) });
|
|
404
408
|
return definition.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
405
409
|
};
|
|
@@ -418,6 +422,6 @@ export default function (pi) {
|
|
|
418
422
|
// is built against. registerTool replaces the builtin by name; re-firing on
|
|
419
423
|
// every session_start is idempotent (last registration wins).
|
|
420
424
|
pi.on('session_start', (_event, ctx) => {
|
|
421
|
-
pi.registerTool(
|
|
425
|
+
pi.registerTool(createValveToolDefinition(nodeId, contextDir, ctx.cwd, readConfig('user').bash_tool_purpose));
|
|
422
426
|
});
|
|
423
427
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -160,6 +160,16 @@ export interface KindConfig {
|
|
|
160
160
|
whenToUse: string;
|
|
161
161
|
availableTo?: string[];
|
|
162
162
|
}
|
|
163
|
+
export interface PageComponentRegistration {
|
|
164
|
+
kind: string;
|
|
165
|
+
description?: string;
|
|
166
|
+
useWhen?: string;
|
|
167
|
+
doc?: string;
|
|
168
|
+
/** A display-only product slot contributes no page response. */
|
|
169
|
+
display?: boolean;
|
|
170
|
+
}
|
|
171
|
+
/** The normalized product component catalog used to validate page slots. */
|
|
172
|
+
export type ProductPageComponents = readonly PageComponentRegistration[];
|
|
163
173
|
export interface ScopeConfig {
|
|
164
174
|
schema_version: number;
|
|
165
175
|
marketplaces: Record<string, ConfigMarketplaceEntry>;
|
|
@@ -183,12 +193,12 @@ export interface ScopeConfig {
|
|
|
183
193
|
* or empty lists fall back to `['working']`. */
|
|
184
194
|
working_gerunds: string[];
|
|
185
195
|
/** Play the whip header animation whenever the human sends a prompt, and once
|
|
186
|
-
* when an
|
|
196
|
+
* when a viewer opens for an agent with an initial prompt. Default false. */
|
|
187
197
|
whip_mode: boolean;
|
|
188
|
-
/**
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
|
|
198
|
+
/** Product-registered page components beyond crtr's built-in catalog. */
|
|
199
|
+
page_components: PageComponentRegistration[];
|
|
200
|
+
/** Prompt agents to use HTML pages by default for structured or interactive human content. Default false. */
|
|
201
|
+
html_surface: boolean;
|
|
192
202
|
/** Playful urgency messages the attach-viewer whip action sends to an agent. Missing, malformed, or empty lists fall back to the built-in rotation. */
|
|
193
203
|
whip_messages: string[];
|
|
194
204
|
/** Initial mouse wheel scrolling mode for each attach viewer. `tmux` follows
|
|
@@ -211,6 +221,10 @@ export interface ScopeConfig {
|
|
|
211
221
|
* `both` keeps both. Tool calls and results are never kept. Full detail for
|
|
212
222
|
* every cycle stays in the session file. */
|
|
213
223
|
condensed_history: CondensedHistoryMode;
|
|
224
|
+
/** Expose crouter's optional `purpose` label in the bash tool schema. Turning
|
|
225
|
+
* this off leaves the bash valve active but gives agents pi's ordinary bash
|
|
226
|
+
* definition with only its upstream parameters. Default true. */
|
|
227
|
+
bash_tool_purpose: boolean;
|
|
214
228
|
/** Fold every SETTLED tool call in the attach viewer down to its single call
|
|
215
229
|
* line (`/fold-tools`, Alt+C → z → f). A tool still running is never folded —
|
|
216
230
|
* it folds itself once it settles. Toggling it in a viewer writes back here,
|
package/dist/types.js
CHANGED
|
@@ -78,12 +78,13 @@ export function defaultScopeConfig() {
|
|
|
78
78
|
completion_bell: true,
|
|
79
79
|
working_gerunds: [...DEFAULT_WORKING_GERUNDS],
|
|
80
80
|
whip_mode: false,
|
|
81
|
-
html_pages: false,
|
|
82
81
|
page_components: [],
|
|
82
|
+
html_surface: false,
|
|
83
83
|
whip_messages: [...DEFAULT_WHIP_MESSAGES],
|
|
84
84
|
mouse_mode_default: 'tmux',
|
|
85
85
|
live_cycles: DEFAULT_LIVE_CYCLES,
|
|
86
86
|
condensed_history: DEFAULT_CONDENSED_HISTORY,
|
|
87
|
+
bash_tool_purpose: true,
|
|
87
88
|
fold_finished_tools: false,
|
|
88
89
|
summarize_tool_calls: false,
|
|
89
90
|
detailed_tool_recaps: true,
|
package/package.json
CHANGED
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.198",
|
|
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.198",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -14,7 +14,10 @@ const GENERATIONS = join(RUNTIME_HOME, 'generations');
|
|
|
14
14
|
const LOCK = join(RUNTIME_HOME, 'install.lock');
|
|
15
15
|
const LOCK_OWNER_FILE = 'owner.json';
|
|
16
16
|
const RUNTIME_LOCK = 'runtime.lock.json';
|
|
17
|
-
|
|
17
|
+
// The selected generation plus one prior, so a bad selection can be rolled
|
|
18
|
+
// back by hand. Anything older is reinstallable from npm and is not worth the
|
|
19
|
+
// ~500MB/generation it costs on disk.
|
|
20
|
+
const KEEP_RECENT_GENERATIONS = 2;
|
|
18
21
|
const FS_CONCURRENCY = 64;
|
|
19
22
|
// How long an install.lock with no readable owner record is given the
|
|
20
23
|
// benefit of the doubt (the owner is mid-write, or on an ancient version of
|
|
@@ -174,40 +177,36 @@ function generationIdsIn(text) {
|
|
|
174
177
|
return [...text.matchAll(/generations\/([a-f0-9]{64})(?:[/\s]|$)/g)].map((match) => match[1]);
|
|
175
178
|
}
|
|
176
179
|
|
|
177
|
-
/**
|
|
178
|
-
*
|
|
180
|
+
/** Every generation a running process is using — named in its argv, or held as
|
|
181
|
+
* an open entry-file lease (how a stable `crtr`/`crtrd` shim, whose argv names
|
|
182
|
+
* no generation, keeps the one it resolved; see `holdGenerationEntryFd` in
|
|
183
|
+
* bin/runtime-selector.mjs and the matching open in src/cli.ts).
|
|
184
|
+
*
|
|
185
|
+
* Detection is positive-only and that is deliberate: a generation nothing here
|
|
186
|
+
* names is prunable. An earlier revision also tracked whether every stable shim
|
|
187
|
+
* had exposed a lease and skipped the whole GC when one had not. That made one
|
|
188
|
+
* unidentifiable process veto all pruning permanently — a single pre-upgrade
|
|
189
|
+
* viewer, which could never hold a lease the mechanism predated, silently
|
|
190
|
+
* stopped GC forever and grew this directory by ~500MB per install. */
|
|
179
191
|
async function liveGenerationIds() {
|
|
180
192
|
const output = await run('ps', ['-axww', '-o', 'pid=,command='], RUNTIME_HOME, true);
|
|
181
193
|
const live = new Set(generationIdsIn(output));
|
|
182
194
|
const nodePids = [];
|
|
183
|
-
const opaqueStablePids = new Set();
|
|
184
195
|
for (const line of output.split('\n')) {
|
|
185
196
|
const match = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
186
197
|
if (!match) continue;
|
|
187
198
|
const [, pid, command] = match;
|
|
188
|
-
const stableSelector = /^(?:\S*node\s+)?\S*\/bin\/(?:crtr|crtrd|crouter)(?:\s|$)/.test(command);
|
|
189
|
-
if (stableSelector && generationIdsIn(command).length === 0) opaqueStablePids.add(pid);
|
|
190
199
|
if (/(?:^|[\s/])(?:node|crtrd?|crouter)(?:[\s]|$)/.test(command)) nodePids.push(pid);
|
|
191
200
|
}
|
|
192
|
-
if (nodePids.length === 0) return
|
|
201
|
+
if (nodePids.length === 0) return live;
|
|
193
202
|
|
|
194
203
|
// One lsof invocation resolves cwd references and the runtime entry-file
|
|
195
204
|
// lease held by each stable CLI process without a per-process startup penalty.
|
|
196
205
|
const open = await probe('lsof', ['-n', '-P', '-a', '-p', nodePids.join(','), '-Fn'], RUNTIME_HOME);
|
|
197
|
-
let pid;
|
|
198
|
-
const exposedStablePids = new Set();
|
|
199
206
|
for (const line of open.split('\n')) {
|
|
200
|
-
const
|
|
201
|
-
if (processMatch) {
|
|
202
|
-
pid = processMatch[1];
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
const ids = generationIdsIn(line);
|
|
206
|
-
for (const id of ids) live.add(id);
|
|
207
|
-
if (pid !== undefined && ids.length > 0 && opaqueStablePids.has(pid)) exposedStablePids.add(pid);
|
|
207
|
+
for (const id of generationIdsIn(line)) live.add(id);
|
|
208
208
|
}
|
|
209
|
-
|
|
210
|
-
return { live, uncertain };
|
|
209
|
+
return live;
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
async function selectedGenerationId() {
|
|
@@ -219,11 +218,7 @@ async function selectedGenerationId() {
|
|
|
219
218
|
|
|
220
219
|
async function pruneGenerations() {
|
|
221
220
|
const selected = await selectedGenerationId();
|
|
222
|
-
const
|
|
223
|
-
if (uncertain) {
|
|
224
|
-
process.stderr.write('runtime generation GC skipped: a live stable crouter shim does not expose its generation lease\n');
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
221
|
+
const live = await liveGenerationIds();
|
|
227
222
|
const entries = await readdir(GENERATIONS, { withFileTypes: true });
|
|
228
223
|
const candidates = [];
|
|
229
224
|
for (const entry of entries) {
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export interface HtmlMarkdownStyles {
|
|
2
|
-
kbd: (text: string) => string;
|
|
3
|
-
mark: (text: string) => string;
|
|
4
|
-
hint: (text: string) => string;
|
|
5
|
-
}
|
|
6
|
-
export interface HtmlMarkdownOptions {
|
|
7
|
-
/** Undefined honors each block's `open` attribute. A boolean is the viewer's
|
|
8
|
-
* global Ctrl+O override. */
|
|
9
|
-
detailsExpanded: boolean | undefined;
|
|
10
|
-
styles: HtmlMarkdownStyles;
|
|
11
|
-
}
|
|
12
|
-
/** Transform the strict supported HTML subset in one Markdown string. */
|
|
13
|
-
export declare function transformHtmlMarkdown(source: string, options: HtmlMarkdownOptions): string;
|