@north-light/crouter 0.3.176 → 0.3.177
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 +4 -4
- package/dist/api/client.js +2 -2
- package/dist/api/dto/lifecycle.d.ts +1 -1
- package/dist/api/dto/reports.d.ts +1 -1
- package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
- package/dist/clients/attach/config.js +5 -5
- package/dist/clients/attach/input/clipboard-image.js +2 -2
- package/dist/clients/attach/input/ref-autocomplete.js +1 -1
- package/dist/clients/attach/input/titled-editor.d.ts +12 -13
- package/dist/clients/attach/input/titled-editor.js +14 -15
- package/dist/clients/attach/overlays/dialogs.d.ts +1 -1
- package/dist/clients/attach/overlays/dialogs.js +2 -2
- package/dist/clients/attach/overlays/mcp.js +5 -12
- package/dist/clients/attach/render/chat-view.d.ts +1 -1
- package/dist/clients/attach/render/chat-view.js +2 -2
- package/dist/clients/attach/render/group-recap.js +7 -0
- package/dist/clients/attach/session/context.js +3 -3
- package/dist/clients/attach/session/editor-inventory.js +6 -6
- package/dist/clients/attach/session/input-wiring.js +1 -1
- package/dist/clients/attach/session/teardown.js +7 -9
- package/dist/clients/attach/slash/dispatch.d.ts +1 -2
- package/dist/clients/attach/slash/dispatch.js +4 -8
- package/dist/clients/attach/viewer.js +394 -394
- package/dist/clients/inbox/controller.js +3 -2
- package/dist/clients/inbox/deck-adapter.js +3 -2
- package/dist/clients/surfaces/host.d.ts +1 -1
- package/dist/commands/api-client.d.ts +12 -13
- package/dist/commands/api-client.js +15 -16
- package/dist/commands/chord.js +1 -2
- package/dist/commands/human/queue.js +6 -5
- package/dist/commands/memory/find.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node-lifecycle-revive.d.ts +1 -1
- package/dist/commands/node-lifecycle-revive.js +3 -3
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/placement.d.ts +1 -1
- package/dist/commands/surface/node/placement.js +1 -1
- package/dist/commands/surface-edit.js +1 -4
- package/dist/commands/sys/panels/panel.d.ts +3 -3
- package/dist/commands/sys/panels/panel.js +3 -3
- package/dist/commands/sys/setup-core.js +1 -7
- package/dist/commands/sys/sync-deps.js +1 -3
- package/dist/commands/sys/sync-project-guidance.js +2 -11
- package/dist/commands/sys/sysprompt.js +1 -3
- package/dist/core/__tests__/fixtures/fake-engine.js +1 -1
- package/dist/core/__tests__/helpers/harness.d.ts +1 -1
- package/dist/core/__tests__/helpers/harness.js +1 -1
- package/dist/core/canvas/boot.js +1 -1
- package/dist/core/canvas/browse/app.js +2 -2
- package/dist/core/canvas/canvas.d.ts +3 -3
- package/dist/core/canvas/canvas.js +3 -3
- package/dist/core/canvas/daemon-owner.js +1 -2
- package/dist/core/canvas/db.js +7 -8
- package/dist/core/canvas/index.js +2 -2
- package/dist/core/canvas/pid.d.ts +12 -14
- package/dist/core/canvas/pid.js +16 -20
- package/dist/core/canvas/remote-transport.js +2 -2
- package/dist/core/canvas/status-glyph.d.ts +1 -1
- package/dist/core/canvas/status-glyph.js +1 -1
- package/dist/core/canvas/types.d.ts +1 -1
- package/dist/core/command-manifests/manifest.js +4 -6
- package/dist/core/command-manifests/schema.js +10 -12
- package/dist/core/command-plugins/transport/exec-invoke.js +5 -7
- package/dist/core/command-plugins/transport/http-invoke.js +4 -6
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +2 -2
- package/dist/core/events/read.js +1 -3
- package/dist/core/fault-classifier.js +1 -3
- package/dist/core/fs-utils.d.ts +7 -0
- package/dist/core/fs-utils.js +29 -2
- package/dist/core/host-exports/export.d.ts +4 -5
- package/dist/core/host-exports/export.js +4 -5
- package/dist/core/human/claim.js +3 -2
- package/dist/core/human/convention.d.ts +0 -1
- package/dist/core/human/convention.js +1 -9
- package/dist/core/human/scan.js +5 -4
- package/dist/core/profiles/manifest.d.ts +1 -1
- package/dist/core/profiles/manifest.js +1 -1
- package/dist/core/review/types.d.ts +1 -1
- package/dist/core/review/types.js +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +4 -4
- package/dist/core/runtime/broker-cli.js +1 -1
- package/dist/core/runtime/broker-protocol.d.ts +9 -10
- package/dist/core/runtime/broker-protocol.js +9 -10
- package/dist/core/runtime/broker-sdk.d.ts +1 -1
- package/dist/core/runtime/broker.js +6 -6
- package/dist/core/runtime/host.js +14 -14
- package/dist/core/runtime/launch.d.ts +1 -1
- package/dist/core/runtime/launch.js +1 -1
- package/dist/core/runtime/lifecycle.js +16 -21
- package/dist/core/runtime/node-read.js +2 -3
- package/dist/core/runtime/package-health.js +1 -8
- package/dist/core/runtime/persona.js +2 -2
- package/dist/core/runtime/pi-vendored.d.ts +4 -2
- package/dist/core/runtime/pi-vendored.js +5 -16
- package/dist/core/runtime/promote.js +1 -1
- package/dist/core/runtime/reopen.js +2 -2
- package/dist/core/runtime/revive-all.d.ts +3 -3
- package/dist/core/runtime/revive-all.js +6 -6
- package/dist/core/runtime/revive.js +1 -1
- package/dist/core/runtime/spawn.d.ts +1 -2
- package/dist/core/runtime/spawn.js +2 -3
- package/dist/core/runtime/structured-output.js +4 -6
- package/dist/core/runtime/tmux-driver.d.ts +2 -2
- package/dist/core/runtime/tmux-driver.js +3 -5
- package/dist/core/runtime/tool-group-summary.js +5 -4
- package/dist/core/runtime/warm-pool.js +1 -1
- package/dist/core/scope.js +2 -9
- package/dist/core/self-update.js +3 -3
- package/dist/core/spawn.d.ts +0 -1
- package/dist/core/spawn.js +1 -4
- package/dist/core/substrate/on-read.js +1 -9
- package/dist/core/wake.js +2 -2
- package/dist/core/worktree.d.ts +3 -3
- package/dist/core/worktree.js +5 -7
- package/dist/daemon/api/bridge.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +1 -2
- package/dist/daemon/api/handlers/messages.js +2 -2
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/reports.js +1 -1
- package/dist/daemon/api/handlers/validate.d.ts +2 -1
- package/dist/daemon/api/handlers/validate.js +2 -3
- package/dist/daemon/cron-run.js +7 -7
- package/dist/daemon/human/finish.js +3 -2
- package/dist/daemon/manage.d.ts +6 -6
- package/dist/daemon/manage.js +15 -16
- package/dist/pi-extensions/canvas-context-intro.js +1 -14
- package/dist/pi-extensions/canvas-doc-substrate.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.js +1 -14
- package/dist/pi-extensions/truncate.d.ts +5 -0
- package/dist/pi-extensions/truncate.js +14 -0
- package/dist/shared/predicates.d.ts +2 -0
- package/dist/shared/predicates.js +4 -0
- package/dist/shared/shell-quote.d.ts +3 -0
- package/dist/shared/shell-quote.js +5 -0
- package/dist/shared/tool-groups.d.ts +4 -2
- package/dist/shared/tool-groups.js +3 -3
- package/dist/types.d.ts +4 -5
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
package/dist/api/client.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface CrtrClientOptions {
|
|
|
33
33
|
* autostart is on; after it resolves, the client polls `/healthz` and retries
|
|
34
34
|
* the original request once. Absent → a cold socket throws `daemon_unavailable`. */
|
|
35
35
|
onColdSocket?: () => Promise<void>;
|
|
36
|
-
/** Injected cold-start diagnostic
|
|
36
|
+
/** Injected cold-start diagnostic. Called ONLY when the bounded
|
|
37
37
|
* `/healthz` poll times out after `onColdSocket`, so the caller can attach
|
|
38
38
|
* operator-useful context (e.g. a bounded tail of crtrd's stderr log) to the
|
|
39
39
|
* `daemon_unavailable` error instead of it staying a bare message. Must
|
|
@@ -43,7 +43,7 @@ export interface CrtrClientOptions {
|
|
|
43
43
|
/** Bounded window (ms) to poll `/healthz` after `onColdSocket` resolves
|
|
44
44
|
* before giving up with `daemon_unavailable`. Defaults to
|
|
45
45
|
* `HEALTHZ_POLL_WINDOW_MS`. This must NOT silently drift from whatever
|
|
46
|
-
* window actually governs "did the daemon start"
|
|
46
|
+
* window actually governs "did the daemon start":
|
|
47
47
|
* `onColdSocket` is fire-and-forget, so this poll is the ONLY deadline that
|
|
48
48
|
* determines whether the CLI reports success. A caller whose `onColdSocket`
|
|
49
49
|
* hook triggers a differently-windowed startup verifier (e.g. the CLI's
|
|
@@ -253,8 +253,8 @@ export declare class CrtrClient {
|
|
|
253
253
|
private handleColdSocket;
|
|
254
254
|
}
|
|
255
255
|
/** Compose the cold-start `/healthz`-timeout `daemon_unavailable` message,
|
|
256
|
-
* appending the injected diagnostic
|
|
257
|
-
*
|
|
256
|
+
* appending the injected diagnostic when one is present instead of discarding
|
|
257
|
+
* the real startup failure behind a bare message. Exported (not
|
|
258
258
|
* from the package's public `index.ts` surface, which is dependency-light by
|
|
259
259
|
* design) purely so the regression test can assert the composition without
|
|
260
260
|
* waiting out the real poll window. */
|
package/dist/api/client.js
CHANGED
|
@@ -655,8 +655,8 @@ function sleep(ms) {
|
|
|
655
655
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
656
656
|
}
|
|
657
657
|
/** Compose the cold-start `/healthz`-timeout `daemon_unavailable` message,
|
|
658
|
-
* appending the injected diagnostic
|
|
659
|
-
*
|
|
658
|
+
* appending the injected diagnostic when one is present instead of discarding
|
|
659
|
+
* the real startup failure behind a bare message. Exported (not
|
|
660
660
|
* from the package's public `index.ts` surface, which is dependency-light by
|
|
661
661
|
* design) purely so the regression test can assert the composition without
|
|
662
662
|
* waiting out the real poll window. */
|
|
@@ -3,7 +3,7 @@ import type { NodeIdDTO, NodeStatusDTO } from './common.js';
|
|
|
3
3
|
export interface ReviveRequest {
|
|
4
4
|
/** Resume the saved conversation (true) vs a fresh launch (false). */
|
|
5
5
|
resume?: boolean;
|
|
6
|
-
/** Clear the finalization latch before reviving (
|
|
6
|
+
/** Clear the finalization latch before reviving (the `--reopen` gate). The
|
|
7
7
|
* latch clear is a canvas write, so the gate is enforced server-side: with
|
|
8
8
|
* `reopen:false` (default) a finalized node is REJECTED (`node_finalized`);
|
|
9
9
|
* with `reopen:true` a node that is NOT finalized is rejected
|
|
@@ -20,7 +20,7 @@ export interface PushReportResultDTO {
|
|
|
20
20
|
/** Present (and true) only for a `final` push whose reporting node owned a
|
|
21
21
|
* managed worktree with zero commits ahead of its base and no uncommitted
|
|
22
22
|
* changes: the server auto-dropped it instead of blocking on `node worktree
|
|
23
|
-
* close
|
|
23
|
+
* close`. A worktree with real unlanded work instead blocks with
|
|
24
24
|
* an `open_managed_worktree` error. */
|
|
25
25
|
worktree_auto_dropped?: boolean;
|
|
26
26
|
/** Present only alongside `worktree_auto_dropped`: the checkout path dropped. */
|
|
@@ -30,7 +30,7 @@ async function summarizedGroup(messages, key) {
|
|
|
30
30
|
stats: {},
|
|
31
31
|
state: { isStreaming: false },
|
|
32
32
|
toolGroupSummaries: {
|
|
33
|
-
[key]: { bullets: ['broker recap'], nodesSpawned: 0,
|
|
33
|
+
[key]: { bullets: ['broker recap'], nodesSpawned: 0, filesDeleted: 0 },
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
const group = internals.groups.find((candidate) => candidate.key === key);
|
|
@@ -70,7 +70,7 @@ test('a folded recap carries paths and counts, never diff or write payload bodie
|
|
|
70
70
|
bashMode: tone('bashMode'), bashModeAlt: tone('bashModeAlt'),
|
|
71
71
|
};
|
|
72
72
|
const recap = new GroupRecapComponent();
|
|
73
|
-
recap.setRecap({ bullets: ['finished'], nodesSpawned: 0,
|
|
73
|
+
recap.setRecap({ bullets: ['finished'], nodesSpawned: 0, filesDeleted: 2 }, 2, { memories: [{ action: 'read', name: 'alpha', path: '/tmp/memories/alpha.md' }], files: recorder.activity().files }, true, palette);
|
|
74
74
|
const text = recap.render(120).join('\n');
|
|
75
75
|
assert.match(text, /overwritten\.ts/, 'the recap still names what changed');
|
|
76
76
|
assert.doesNotMatch(text, /sensitive-old-diff|sensitive-new-diff|sensitive-write-payload/, 'payload bodies never leak into a recap');
|
|
@@ -29,10 +29,10 @@ export function defaultAgentDir() {
|
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* pi's `app.*` editor keybindings — VENDORED from pi `core/keybindings.ts`
|
|
32
|
-
* `KEYBINDINGS` (the const is not exported
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* `KEYBINDINGS` (the const is not exported). This is the subset CustomEditor's
|
|
33
|
+
* main editor consults; the selector-internal bindings (`app.tree.*`,
|
|
34
|
+
* `app.models.*`, session delete/rename) are omitted because the attach viewer
|
|
35
|
+
* does not open those selectors. Re-sync on each SDK
|
|
36
36
|
* bump against `core/keybindings.js`.
|
|
37
37
|
*/
|
|
38
38
|
const APP_KEYBINDINGS = {
|
|
@@ -148,7 +148,7 @@ export async function mirrorKittyProtocolToEditor(active) {
|
|
|
148
148
|
* Resolve the `fd` binary path the @-mention file picker needs. pi-tui's
|
|
149
149
|
* `CombinedAutocompleteProvider` only returns file suggestions when handed an
|
|
150
150
|
* `fdPath` (its third ctor arg) — without it, typing `@` triggers the picker but
|
|
151
|
-
* `getFuzzyFileSuggestions` returns `[]`, so the picker never appears
|
|
151
|
+
* `getFuzzyFileSuggestions` returns `[]`, so the picker never appears.
|
|
152
152
|
* pi's own interactive mode resolves this via `ensureTool('fd')` from its
|
|
153
153
|
* internal `utils/tools-manager` (checks pi's managed bin dir + system PATH,
|
|
154
154
|
* downloading the binary if missing). That module is NOT on pi-coding-agent's
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// input/clipboard-image.ts — read the clipboard image for `crtr surface attach
|
|
1
|
+
// input/clipboard-image.ts — read the clipboard image for `crtr surface attach`.
|
|
2
2
|
//
|
|
3
3
|
// pi's `readClipboardImage` is NOT re-exported (it lives behind a native
|
|
4
4
|
// clipboard binding + Photon worker the package gates off `.`-only exports), so
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// JPEG and picks the smaller, so it doubles as format normalization;
|
|
22
22
|
// `convertToPng` is the fallback when the Photon resizer is unavailable. The
|
|
23
23
|
// fallbacks (convertToPng / raw bytes) are NOT size-bounded by `resizeImage`, so
|
|
24
|
-
// each is gated on the SAME MAX_BYTES base64 ceiling
|
|
24
|
+
// each is gated on the SAME MAX_BYTES base64 ceiling: over it → the
|
|
25
25
|
// image is DROPPED with a user-visible note rather than shipped as an over-cap
|
|
26
26
|
// frame that would overflow BROKER_READ_CAPS.maxLineBytes (24 MiB) and destroy
|
|
27
27
|
// the viewer socket. The result is an `ImageContent` (pi-ai) ready to drop
|
|
@@ -94,7 +94,7 @@ export class RefAwareAutocompleteProvider {
|
|
|
94
94
|
}));
|
|
95
95
|
// Filtering above used the full slash-form `prefix` (context.prefix always
|
|
96
96
|
// starts with '/' for a matched token), but the RETURNED prefix strips that
|
|
97
|
-
// leading '/'
|
|
97
|
+
// leading '/'. pi-tui's `handleInput` (tui.select.confirm
|
|
98
98
|
// branch) falls through to `submitValue()` whenever `autocompletePrefix`
|
|
99
99
|
// (set verbatim from this return value) starts with '/' — that's its own
|
|
100
100
|
// leading-command-completion submit shortcut, and a non-leading ref
|
|
@@ -91,7 +91,7 @@ export declare class TitledEditor extends CustomEditor {
|
|
|
91
91
|
* The base `Editor` resolves newLine against `getKeybindings()` on ITS pi-tui
|
|
92
92
|
* instance, which in a non-deduped install is a DIFFERENT module than the one
|
|
93
93
|
* carrying crtr's `alt+enter` override — so the override can silently never
|
|
94
|
-
* reach it and Alt+Enter falls through to submit
|
|
94
|
+
* reach it and Alt+Enter falls through to submit. crtr's `km` (the
|
|
95
95
|
* same manager CustomEditor matches `app.*` against) always carries the
|
|
96
96
|
* override, so matching the chord here makes the newline behavior independent
|
|
97
97
|
* of cross-instance keybinding mirroring. Skipped while the autocomplete popup
|
|
@@ -159,8 +159,8 @@ export declare class TitledEditor extends CustomEditor {
|
|
|
159
159
|
paneFocused: boolean;
|
|
160
160
|
/** Canonical names the broker currently resolves — the exact-membership set
|
|
161
161
|
* a candidate ref token must be in to render as a resolved highlight.
|
|
162
|
-
* Empty by default (no decoration) until the caller
|
|
163
|
-
*
|
|
162
|
+
* Empty by default (no decoration) until the caller supplies the
|
|
163
|
+
* broker-supplied inventory. */
|
|
164
164
|
private resolvedRefNames;
|
|
165
165
|
/** Command names the VIEWER dispatches ENTIRELY locally (never reach the
|
|
166
166
|
* engine as prose) — checked against the TRIMMED leading-name rule
|
|
@@ -169,15 +169,14 @@ export declare class TitledEditor extends CustomEditor {
|
|
|
169
169
|
* `locallyConsumedCommandNames()` from `slash/dispatch.ts` — independent
|
|
170
170
|
* of `remote` and of the broker's `get_commands` reply (see that
|
|
171
171
|
* function's doc comment). Deliberately NEVER the merged display/
|
|
172
|
-
* completion list (
|
|
173
|
-
*
|
|
172
|
+
* completion list (`slashCommandList` is a different, remote-filtered,
|
|
173
|
+
* broker-augmented list, not a dispatch predicate). */
|
|
174
174
|
private localCommandNames;
|
|
175
175
|
/** Leading tokens the ENGINE (broker) independently consumes as a leading
|
|
176
176
|
* command/skill/template dispatch — the broker's own
|
|
177
|
-
* `engineLeadingCommandNames
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* `isLeadingEngineCommand` — review Minor 10). Populated from each
|
|
177
|
+
* `engineLeadingCommandNames`, checked against the UNTRIMMED leading-name
|
|
178
|
+
* rule (`matchesEngineLeadingCommand`, mirroring the broker's own
|
|
179
|
+
* `isLeadingEngineCommand`). Populated from each
|
|
181
180
|
* `list_memory_refs` reply, alongside `resolvedRefNames`. */
|
|
182
181
|
private engineCommandNames;
|
|
183
182
|
setResolvedRefNames(names: Iterable<string>): void;
|
|
@@ -192,12 +191,12 @@ export declare class TitledEditor extends CustomEditor {
|
|
|
192
191
|
* is simply left undecorated. Suppressed entirely (command-wide) whenever
|
|
193
192
|
* the composer's own leading token dispatches — as a LOCAL viewer command
|
|
194
193
|
* (`localCommandNames`, TRIMMED rule) or as an ENGINE-consumed leading
|
|
195
|
-
* command/skill/template (`engineCommandNames`, UNTRIMMED rule
|
|
196
|
-
*
|
|
194
|
+
* command/skill/template (`engineCommandNames`, UNTRIMMED rule) — the ref
|
|
195
|
+
* grammar and slash-dispatch's name charset differ, so
|
|
197
196
|
* highlighting a ref inside a command that's about to dispatch would be
|
|
198
197
|
* misleading chrome for text the command, not memory resolution, is about
|
|
199
|
-
* to consume. The two rules are kept deliberately SEPARATE
|
|
200
|
-
*
|
|
198
|
+
* to consume. The two rules are kept deliberately SEPARATE rather than
|
|
199
|
+
* collapsed into one set/predicate: they classify against
|
|
201
200
|
* different text (trimmed vs. raw) and different authorities (this
|
|
202
201
|
* viewer's own dispatch vs. the broker's). */
|
|
203
202
|
private decorateResolvedRefs;
|
|
@@ -401,7 +401,7 @@ function extractLeadingCommandName(text) {
|
|
|
401
401
|
return m ? m[1] : null;
|
|
402
402
|
}
|
|
403
403
|
/** Mirrors the broker's `isLeadingEngineCommand` (`core/runtime/broker.ts`)
|
|
404
|
-
* leading-token rule EXACTLY for classification purposes
|
|
404
|
+
* leading-token rule EXACTLY for classification purposes:
|
|
405
405
|
* the RAW, un-trimmed `text` must itself start with `/` — no `.trim()` — for
|
|
406
406
|
* ANY token to be a candidate, matching the broker's own untrimmed
|
|
407
407
|
* `text.startsWith('/')` checks byte-for-byte (space-based name split,
|
|
@@ -447,7 +447,7 @@ export class TitledEditor extends CustomEditor {
|
|
|
447
447
|
constructor(tui, theme, keybindings, options) {
|
|
448
448
|
super(tui, theme, keybindings, options);
|
|
449
449
|
this.km = keybindings;
|
|
450
|
-
// Preserve raw (untrimmed) submit text end-to-end
|
|
450
|
+
// Preserve raw (untrimmed) submit text end-to-end. The base
|
|
451
451
|
// `Editor.submitValue()` — private in the `.d.ts` (blocking a normal TS
|
|
452
452
|
// `override`), hence the structural cast, same pattern as the other private-
|
|
453
453
|
// internals casts throughout this file (`scrollOffset`, `historyIndex`,
|
|
@@ -497,7 +497,7 @@ export class TitledEditor extends CustomEditor {
|
|
|
497
497
|
* The base `Editor` resolves newLine against `getKeybindings()` on ITS pi-tui
|
|
498
498
|
* instance, which in a non-deduped install is a DIFFERENT module than the one
|
|
499
499
|
* carrying crtr's `alt+enter` override — so the override can silently never
|
|
500
|
-
* reach it and Alt+Enter falls through to submit
|
|
500
|
+
* reach it and Alt+Enter falls through to submit. crtr's `km` (the
|
|
501
501
|
* same manager CustomEditor matches `app.*` against) always carries the
|
|
502
502
|
* override, so matching the chord here makes the newline behavior independent
|
|
503
503
|
* of cross-instance keybinding mirroring. Skipped while the autocomplete popup
|
|
@@ -655,8 +655,8 @@ export class TitledEditor extends CustomEditor {
|
|
|
655
655
|
paneFocused = true;
|
|
656
656
|
/** Canonical names the broker currently resolves — the exact-membership set
|
|
657
657
|
* a candidate ref token must be in to render as a resolved highlight.
|
|
658
|
-
* Empty by default (no decoration) until the caller
|
|
659
|
-
*
|
|
658
|
+
* Empty by default (no decoration) until the caller supplies the
|
|
659
|
+
* broker-supplied inventory. */
|
|
660
660
|
resolvedRefNames = new Set();
|
|
661
661
|
/** Command names the VIEWER dispatches ENTIRELY locally (never reach the
|
|
662
662
|
* engine as prose) — checked against the TRIMMED leading-name rule
|
|
@@ -665,15 +665,14 @@ export class TitledEditor extends CustomEditor {
|
|
|
665
665
|
* `locallyConsumedCommandNames()` from `slash/dispatch.ts` — independent
|
|
666
666
|
* of `remote` and of the broker's `get_commands` reply (see that
|
|
667
667
|
* function's doc comment). Deliberately NEVER the merged display/
|
|
668
|
-
* completion list (
|
|
669
|
-
*
|
|
668
|
+
* completion list (`slashCommandList` is a different, remote-filtered,
|
|
669
|
+
* broker-augmented list, not a dispatch predicate). */
|
|
670
670
|
localCommandNames = new Set();
|
|
671
671
|
/** Leading tokens the ENGINE (broker) independently consumes as a leading
|
|
672
672
|
* command/skill/template dispatch — the broker's own
|
|
673
|
-
* `engineLeadingCommandNames
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
* `isLeadingEngineCommand` — review Minor 10). Populated from each
|
|
673
|
+
* `engineLeadingCommandNames`, checked against the UNTRIMMED leading-name
|
|
674
|
+
* rule (`matchesEngineLeadingCommand`, mirroring the broker's own
|
|
675
|
+
* `isLeadingEngineCommand`). Populated from each
|
|
677
676
|
* `list_memory_refs` reply, alongside `resolvedRefNames`. */
|
|
678
677
|
engineCommandNames = new Set();
|
|
679
678
|
setResolvedRefNames(names) {
|
|
@@ -694,12 +693,12 @@ export class TitledEditor extends CustomEditor {
|
|
|
694
693
|
* is simply left undecorated. Suppressed entirely (command-wide) whenever
|
|
695
694
|
* the composer's own leading token dispatches — as a LOCAL viewer command
|
|
696
695
|
* (`localCommandNames`, TRIMMED rule) or as an ENGINE-consumed leading
|
|
697
|
-
* command/skill/template (`engineCommandNames`, UNTRIMMED rule
|
|
698
|
-
*
|
|
696
|
+
* command/skill/template (`engineCommandNames`, UNTRIMMED rule) — the ref
|
|
697
|
+
* grammar and slash-dispatch's name charset differ, so
|
|
699
698
|
* highlighting a ref inside a command that's about to dispatch would be
|
|
700
699
|
* misleading chrome for text the command, not memory resolution, is about
|
|
701
|
-
* to consume. The two rules are kept deliberately SEPARATE
|
|
702
|
-
*
|
|
700
|
+
* to consume. The two rules are kept deliberately SEPARATE rather than
|
|
701
|
+
* collapsed into one set/predicate: they classify against
|
|
703
702
|
* different text (trimmed vs. raw) and different authorities (this
|
|
704
703
|
* viewer's own dispatch vs. the broker's). */
|
|
705
704
|
decorateResolvedRefs(lines, width) {
|
|
@@ -19,7 +19,7 @@ export declare function isBlockingDialog(req: RpcExtensionUIRequest): boolean;
|
|
|
19
19
|
* `keybindings.matches(key, "app.editor.external")` for the Ctrl+G external-editor
|
|
20
20
|
* shortcut. Defaults to a fresh `KeybindingsManager(TUI_KEYBINDINGS)` — that
|
|
21
21
|
* knows the base TUI bindings but not pi's app-level `app.editor.external`, so
|
|
22
|
-
* Ctrl+G is inert until the input controller
|
|
22
|
+
* Ctrl+G is inert until the input controller passes the app keybindings
|
|
23
23
|
* manager. `ExtensionEditorComponent` is typed against pi's CORE
|
|
24
24
|
* KeybindingsManager (a subclass of pi-tui's), so we widen via the constructor's
|
|
25
25
|
* own parameter type rather than importing the core class (type-only export).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// overlays/dialogs.ts — the blocking-dialog renderer for `crtr surface attach
|
|
1
|
+
// overlays/dialogs.ts — the blocking-dialog renderer for `crtr surface attach`.
|
|
2
2
|
//
|
|
3
3
|
// The broker forwards an extension's user-blocking UI request as an
|
|
4
4
|
// `extension_ui_request` frame (RpcExtensionUIRequest). The viewer renders it
|
|
@@ -45,7 +45,7 @@ const OVERLAY_OPTIONS = { anchor: 'center', width: '70%', maxHeight: '80%' };
|
|
|
45
45
|
* `keybindings.matches(key, "app.editor.external")` for the Ctrl+G external-editor
|
|
46
46
|
* shortcut. Defaults to a fresh `KeybindingsManager(TUI_KEYBINDINGS)` — that
|
|
47
47
|
* knows the base TUI bindings but not pi's app-level `app.editor.external`, so
|
|
48
|
-
* Ctrl+G is inert until the input controller
|
|
48
|
+
* Ctrl+G is inert until the input controller passes the app keybindings
|
|
49
49
|
* manager. `ExtensionEditorComponent` is typed against pi's CORE
|
|
50
50
|
* KeybindingsManager (a subclass of pi-tui's), so we widen via the constructor's
|
|
51
51
|
* own parameter type rather than importing the core class (type-only export).
|
|
@@ -40,12 +40,13 @@
|
|
|
40
40
|
// installed adapter (pi-mcp-adapter index.ts: "auth-start" / "auth-complete" /
|
|
41
41
|
// "ui-messages"; `connect: <name>`).
|
|
42
42
|
import { createHash } from 'node:crypto';
|
|
43
|
-
import { existsSync
|
|
43
|
+
import { existsSync } from 'node:fs';
|
|
44
44
|
import { homedir } from 'node:os';
|
|
45
45
|
import { join, resolve } from 'node:path';
|
|
46
46
|
import { piAgentDir } from '../../../core/runtime/package-health.js';
|
|
47
47
|
import { SelectList, visibleWidth } from '@earendil-works/pi-tui';
|
|
48
48
|
import { getSelectListTheme } from '@earendil-works/pi-coding-agent';
|
|
49
|
+
import { readJsonOrNull } from '../../../core/fs-utils.js';
|
|
49
50
|
/** Adapter metadata-cache.ts CACHE_MAX_AGE_MS — entries older than this are misses. */
|
|
50
51
|
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
51
52
|
/** The adapter's import-source table (pi-mcp-adapter config.ts IMPORT_PATHS).
|
|
@@ -72,14 +73,6 @@ function importPathCandidates(kind, home) {
|
|
|
72
73
|
return [];
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
|
-
function readJson(path) {
|
|
76
|
-
try {
|
|
77
|
-
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return null; // missing or corrupt → empty state, never a crash
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
76
|
/** Mirror of the adapter's validateConfig (config.ts): a source file's own
|
|
84
77
|
* servers may live under `mcpServers` OR the `mcp-servers` alias — but a
|
|
85
78
|
* non-object value invalidates the WHOLE file, dropping its `imports` too. */
|
|
@@ -130,7 +123,7 @@ function mergeServerMaps(base, next) {
|
|
|
130
123
|
/** Read one config file: its own servers layered OVER any `imports` servers
|
|
131
124
|
* (first import wins per name, own config wins overall — adapter expandImports). */
|
|
132
125
|
function readConfigSource(path, cwd, home) {
|
|
133
|
-
const raw =
|
|
126
|
+
const raw = readJsonOrNull(path);
|
|
134
127
|
if (raw === null)
|
|
135
128
|
return {};
|
|
136
129
|
const { servers: own, imports } = validateConfigSource(raw);
|
|
@@ -144,7 +137,7 @@ function readConfigSource(path, cwd, home) {
|
|
|
144
137
|
.find((candidate) => existsSync(candidate));
|
|
145
138
|
if (full === undefined)
|
|
146
139
|
continue;
|
|
147
|
-
const importedRaw =
|
|
140
|
+
const importedRaw = readJsonOrNull(full);
|
|
148
141
|
if (importedRaw === null)
|
|
149
142
|
continue;
|
|
150
143
|
for (const [name, spec] of Object.entries(extractImportedServers(importedRaw, kind))) {
|
|
@@ -228,7 +221,7 @@ export function readMcpServers(home = homedir(), cwd = process.cwd()) {
|
|
|
228
221
|
for (const path of sourcePaths) {
|
|
229
222
|
servers = mergeServerMaps(servers, readConfigSource(path, cwd, home));
|
|
230
223
|
}
|
|
231
|
-
const cache =
|
|
224
|
+
const cache = readJsonOrNull(join(agentDir, 'mcp-cache.json'));
|
|
232
225
|
return Object.entries(servers).map(([name, spec]) => {
|
|
233
226
|
const transport = spec.type ?? (spec.url !== undefined ? 'http' : 'stdio');
|
|
234
227
|
const target = spec.url ?? [spec.command, ...(spec.args ?? [])].filter(Boolean).join(' ');
|
|
@@ -35,7 +35,7 @@ export interface ChatViewOptions {
|
|
|
35
35
|
toolOutputExpanded?: boolean;
|
|
36
36
|
/** Sink for footer/header-only events ChatView does not itself render
|
|
37
37
|
* (queue_update, session_info_changed, thinking_level_changed). The attach
|
|
38
|
-
* command
|
|
38
|
+
* command wires a footer here; absent → these events are no-ops. */
|
|
39
39
|
onFooterEvent?: (event: AgentSessionEvent) => void;
|
|
40
40
|
/** Theme palette for the activity spinner / dim status / error lines. Absent →
|
|
41
41
|
* the module defaults (faint + ANSI red) are used. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// render/chat-view.ts — the render half of `crtr surface attach
|
|
1
|
+
// render/chat-view.ts — the render half of `crtr surface attach`.
|
|
2
2
|
//
|
|
3
3
|
// A direct port of pi's `InteractiveMode.handleEvent()`
|
|
4
4
|
// (interactive-mode.js:2169-2462) + its rebuild path
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// Its activity state (working spinner, compaction/retry indicators) is signaled
|
|
12
12
|
// to the viewer-owned fixed slot when one is supplied. It does NOT own the
|
|
13
13
|
// editor, the footer, or the terminal title — those are chrome laid out by the
|
|
14
|
-
// attach command
|
|
14
|
+
// attach command. Events that only affect that chrome
|
|
15
15
|
// (queue_update / session_info_changed / thinking_level_changed) are forwarded
|
|
16
16
|
// to the optional `onFooterEvent` sink instead of rendered here.
|
|
17
17
|
//
|
|
@@ -6,6 +6,8 @@ import { pathToFileURL } from 'node:url';
|
|
|
6
6
|
import { ICON } from '../../../core/inspector/model.js';
|
|
7
7
|
import { TOOL_ICON } from '../../../core/preview-registry.js';
|
|
8
8
|
const MEMORY_ICON = String.fromCodePoint(0xf02d);
|
|
9
|
+
// trash — deletion has no tool of its own, so it borrows no TOOL_ICON entry.
|
|
10
|
+
const DELETE_ICON = String.fromCodePoint(0xf1f8);
|
|
9
11
|
function count(value, singular, plural) {
|
|
10
12
|
return `${value} ${value === 1 ? singular : plural}`;
|
|
11
13
|
}
|
|
@@ -74,6 +76,11 @@ export class GroupRecapComponent extends Container {
|
|
|
74
76
|
if (changedFiles >= 1) {
|
|
75
77
|
stats.push(`${palette.warning(TOOL_ICON.edit)} ${palette.muted(count(changedFiles, 'file changed', 'files changed'))}`);
|
|
76
78
|
}
|
|
79
|
+
// Deletions never enter activity.files (only edit/write calls do), so the
|
|
80
|
+
// model's count covers a disjoint set and cannot double-count a change.
|
|
81
|
+
if (summary.filesDeleted >= 1) {
|
|
82
|
+
stats.push(`${palette.diffRemoved(DELETE_ICON)} ${palette.muted(count(summary.filesDeleted, 'file deleted', 'files deleted'))}`);
|
|
83
|
+
}
|
|
77
84
|
const memoriesRead = countMemories(activity.memories, 'read');
|
|
78
85
|
if (memoriesRead >= 1) {
|
|
79
86
|
stats.push(`${palette.info(MEMORY_ICON)} ${palette.muted(count(memoriesRead, 'memory read', 'memory reads'))}`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// session/context.ts — the named home for an attach session's STABLE facts.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Held as closure locals inside `runAttach`, pulling any block out of it would
|
|
4
|
+
// mean inventing a bespoke parameter list for that block alone.
|
|
5
|
+
// `AttachSession` gives the state one declaration; each
|
|
6
6
|
// collaborator then names the slice it needs as a `Pick<AttachSession, …>`
|
|
7
7
|
// view, so the module's dependencies are a *projection* of one truth rather
|
|
8
8
|
// than a hand-maintained bag that can drift from it.
|
|
@@ -62,8 +62,8 @@ export function createEditorInventory(ctx, capabilities, hooks) {
|
|
|
62
62
|
let brokerCommands;
|
|
63
63
|
let memoryRefs = [];
|
|
64
64
|
// The broker's `engineLeadingCommandNames` from the latest `list_memory_refs`
|
|
65
|
-
// reply
|
|
66
|
-
//
|
|
65
|
+
// reply — leading tokens the ENGINE independently consumes, kept separate
|
|
66
|
+
// from the viewer-local
|
|
67
67
|
// `locallyConsumedCommandNames()` set and fed to the editor's untrimmed
|
|
68
68
|
// engine-suppression rule via `setEngineCommandNames`.
|
|
69
69
|
let engineLeadingCommandNames = [];
|
|
@@ -82,9 +82,9 @@ export function createEditorInventory(ctx, capabilities, hooks) {
|
|
|
82
82
|
// Atomically rebuild the editor's provider/highlight state from whatever
|
|
83
83
|
// command + ref inventory is CURRENTLY known — the one seam `setCommands` and
|
|
84
84
|
// `setMemoryRefs` both call, so a command-only or ref-only response can never
|
|
85
|
-
// rebuild the provider having silently dropped the other inventory
|
|
86
|
-
//
|
|
87
|
-
//
|
|
85
|
+
// rebuild the provider having silently dropped the other inventory. This
|
|
86
|
+
// module owns command metadata, ref metadata, provider rebuild, and editor
|
|
87
|
+
// highlight sets in one place. Command-only
|
|
88
88
|
// autocomplete is unchanged: `RefAwareAutocompleteProvider` fully delegates
|
|
89
89
|
// leading-slash and every non-ref context to the wrapped command provider, so
|
|
90
90
|
// refs never enter command completion or the loaded-command preview (which
|
|
@@ -93,7 +93,7 @@ export function createEditorInventory(ctx, capabilities, hooks) {
|
|
|
93
93
|
const commandProvider = buildAttachAutocompleteProvider(meta.cwd, hooks.fdPath, brokerCommands, capabilities);
|
|
94
94
|
editor.setAutocompleteProvider(new RefAwareAutocompleteProvider(memoryRefs, commandProvider));
|
|
95
95
|
editor.setResolvedRefNames(memoryRefs.map((ref) => ref.name));
|
|
96
|
-
// Two independent suppression inventories
|
|
96
|
+
// Two independent suppression inventories: the viewer-local
|
|
97
97
|
// set (everything `dispatchSlashCommand` handles entirely locally, trimmed
|
|
98
98
|
// rule) and the broker's own `engineLeadingCommandNames` (untrimmed rule) —
|
|
99
99
|
// never the merged display/completion list `slashCommandList` returns.
|
|
@@ -36,7 +36,7 @@ export function createInputController(ctx, km, capabilities, hooks) {
|
|
|
36
36
|
//
|
|
37
37
|
// A read-only viewer wires NEITHER. A REMOTE attach (`--canvas`) disables both
|
|
38
38
|
// because they target the LOCAL `auth.json` via `defaultAgentDir()`, the wrong
|
|
39
|
-
// host for a remote node
|
|
39
|
+
// host for a remote node; an OBSERVER because
|
|
40
40
|
// switching credentials and telling the broker to reload them mutates the
|
|
41
41
|
// engine's auth, which a read-only viewer must not do. Unwired here means the
|
|
42
42
|
// slash context carries no picker at all, so `/login` and `/logout` report
|
|
@@ -57,18 +57,16 @@ export function createTeardown(s, hooks) {
|
|
|
57
57
|
socket.close();
|
|
58
58
|
restore();
|
|
59
59
|
// A clean detach leaves the broker running (the engine runs on) — the
|
|
60
|
-
// documented "detach leaves the broker running" contract
|
|
61
|
-
// reaping
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// API-only client cannot reach and no per-node reap route exposes, so it is
|
|
65
|
-
// dropped (B-10). Disposition of an abandoned empty broker: `crtr canvas
|
|
60
|
+
// documented "detach leaves the broker running" contract. Teardown reaps
|
|
61
|
+
// nothing per-action: reaping an empty husk here would need a direct
|
|
62
|
+
// canvas.db mutation the API-only client cannot reach, and no per-node reap
|
|
63
|
+
// route exposes one. Disposition of an abandoned empty broker: `crtr canvas
|
|
66
64
|
// prune --empty` (reapEmptyNodes) sweeps it, but only ONCE its pid is dead
|
|
67
65
|
// — it deliberately spares a LIVE broker as the daemon's domain. So an empty
|
|
68
66
|
// broker lingers until it idle-releases (or is closed) to a dormant husk,
|
|
69
|
-
// then prune reaps it
|
|
70
|
-
//
|
|
71
|
-
//
|
|
67
|
+
// then prune reaps it, leaving a narrow, transient live-empty window — the
|
|
68
|
+
// only faithful option under the API boundary. A per-node reap route would
|
|
69
|
+
// buy immediate cleanup (future work).
|
|
72
70
|
if (reason === 'broker-gone') {
|
|
73
71
|
const msg = hooks.lastSocketError() ??
|
|
74
72
|
`broker gone — node ${nodeId} is no longer running its engine. Re-focus to reattach.`;
|
|
@@ -87,8 +87,7 @@ export declare const CANVAS_SLASH_COMMANDS: ReadonlyArray<{
|
|
|
87
87
|
* `true` from `dispatchSlashCommand` — swallowed with a notice, never
|
|
88
88
|
* forwarded as prose — so it counts as consumed here too, independent of
|
|
89
89
|
* `remote`). Exactly BUILTIN_NAMES ∪ CANVAS_NAMES ∪ SCOPED_OUT — every name
|
|
90
|
-
* `dispatchSlashCommand` returns `true` for
|
|
91
|
-
* references, review Major 6/terminal). Used ONLY for terminal
|
|
90
|
+
* `dispatchSlashCommand` returns `true` for. Used ONLY for terminal
|
|
92
91
|
* ref-decoration suppression (`input/titled-editor.ts`); never merged into
|
|
93
92
|
* command completion, which is `slashCommandList` — a different,
|
|
94
93
|
* remote-filtered, broker-augmented list. */
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
// against pi 0.79.1 — `ScopedModelsSelectorComponent` is the one selector pi does
|
|
20
20
|
// not re-export, so /scoped-models renders read-only via a SelectList.)
|
|
21
21
|
//
|
|
22
|
-
// SCOPED OUT (
|
|
22
|
+
// SCOPED OUT (no engine method): `/login`, `/logout`, `/share`,
|
|
23
23
|
// `/trust` — a brief "not supported in attach" notice, never a frame.
|
|
24
24
|
import { execFile } from 'node:child_process';
|
|
25
25
|
import { BUILTIN_SLASH_COMMANDS } from '../../../core/runtime/pi-vendored.js';
|
|
26
26
|
import { surfaceTmuxStyleArgs } from '../../../core/runtime/surface-bg.js';
|
|
27
27
|
import { viewerOperationForSlash } from '../input/capabilities.js';
|
|
28
|
+
import { shellQuote } from '../../../shared/shell-quote.js';
|
|
28
29
|
/** Canvas slash-commands — implemented NATIVE in the viewer, the ONLY live host:
|
|
29
30
|
* the broker binds mode:'rpc', so broker pi-extensions never run their
|
|
30
31
|
* interactive (mode==='tui') paths. `/promote` triggers a turn after shelling
|
|
@@ -58,7 +59,7 @@ const TMUX_NAMED_COLORS = new Set([
|
|
|
58
59
|
'brightred', 'brightgreen', 'brightyellow', 'brightblue', 'brightmagenta', 'brightcyan',
|
|
59
60
|
'orange', 'grey', 'gray', 'default',
|
|
60
61
|
]);
|
|
61
|
-
/** Builtins with no
|
|
62
|
+
/** Builtins with no engine method — scoped out. */
|
|
62
63
|
const SCOPED_OUT = new Set(['trust']);
|
|
63
64
|
/** Valid `/settings thinking` levels — the `SetThinkingLevelFrame['level']`
|
|
64
65
|
* union (= AgentSession['thinkingLevel'], pi-agent-core ThinkingLevel),
|
|
@@ -92,8 +93,7 @@ function readOnlyRefusal(name, ctx) {
|
|
|
92
93
|
* `true` from `dispatchSlashCommand` — swallowed with a notice, never
|
|
93
94
|
* forwarded as prose — so it counts as consumed here too, independent of
|
|
94
95
|
* `remote`). Exactly BUILTIN_NAMES ∪ CANVAS_NAMES ∪ SCOPED_OUT — every name
|
|
95
|
-
* `dispatchSlashCommand` returns `true` for
|
|
96
|
-
* references, review Major 6/terminal). Used ONLY for terminal
|
|
96
|
+
* `dispatchSlashCommand` returns `true` for. Used ONLY for terminal
|
|
97
97
|
* ref-decoration suppression (`input/titled-editor.ts`); never merged into
|
|
98
98
|
* command completion, which is `slashCommandList` — a different,
|
|
99
99
|
* remote-filtered, broker-augmented list. */
|
|
@@ -605,10 +605,6 @@ function popup(cmd) {
|
|
|
605
605
|
/* best-effort */
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
|
-
/** Single-quote a string for safe interpolation into a `sh -c` command line. */
|
|
609
|
-
function shellQuote(s) {
|
|
610
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
611
|
-
}
|
|
612
608
|
// ---------------------------------------------------------------------------
|
|
613
609
|
function usage(ctx, form) {
|
|
614
610
|
ctx.notify(`Usage: ${form}`);
|