@crouton-kit/crouter 0.3.70 → 0.3.78
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/README.md +1 -1
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +622 -724
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +231 -68
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +361 -90
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +3 -3
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +20 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +1 -1
- package/dist/clients/attach/attach-cmd.js +738 -735
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +11 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +127 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +9 -2
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/setup-core.d.ts +12 -1
- package/dist/commands/sys/setup-core.js +23 -3
- package/dist/commands/sys/setup.js +42 -12
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +164 -5
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/config.d.ts +53 -2
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +256 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +163 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +78 -20
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +47 -44
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/session-list-cache.d.ts +23 -0
- package/dist/core/runtime/session-list-cache.js +270 -0
- package/dist/core/runtime/spawn.js +51 -32
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +216 -110
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +38 -37
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/types.d.ts +3 -13
- package/dist/types.js +2 -26
- package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +3 -3
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { normalizeStroke } from './resolve.js';
|
|
2
|
+
const KEY_LABELS = {
|
|
3
|
+
enter: 'Enter',
|
|
4
|
+
escape: 'Esc',
|
|
5
|
+
tab: 'Tab',
|
|
6
|
+
backspace: 'Backspace',
|
|
7
|
+
delete: 'Delete',
|
|
8
|
+
space: 'Space',
|
|
9
|
+
up: 'Up',
|
|
10
|
+
down: 'Down',
|
|
11
|
+
left: 'Left',
|
|
12
|
+
right: 'Right',
|
|
13
|
+
pageup: 'PageUp',
|
|
14
|
+
pagedown: 'PageDown',
|
|
15
|
+
home: 'Home',
|
|
16
|
+
end: 'End',
|
|
17
|
+
};
|
|
18
|
+
export function formatStroke(stroke) {
|
|
19
|
+
let key;
|
|
20
|
+
let modifiers;
|
|
21
|
+
if (stroke === '+') {
|
|
22
|
+
key = '+';
|
|
23
|
+
modifiers = [];
|
|
24
|
+
}
|
|
25
|
+
else if (stroke.endsWith('++')) {
|
|
26
|
+
key = '+';
|
|
27
|
+
modifiers = stroke.slice(0, -2).split('+');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const parts = stroke.split('+');
|
|
31
|
+
key = parts.pop();
|
|
32
|
+
modifiers = parts;
|
|
33
|
+
}
|
|
34
|
+
const labels = modifiers.map((modifier) => modifier[0].toUpperCase() + modifier.slice(1));
|
|
35
|
+
labels.push(KEY_LABELS[key] ?? (key.length === 1 ? key.toUpperCase() : key));
|
|
36
|
+
return labels.join('+');
|
|
37
|
+
}
|
|
38
|
+
export function formatGesture(gesture) {
|
|
39
|
+
return gesture.split(' > ').map(formatStroke).join(' then ');
|
|
40
|
+
}
|
|
41
|
+
export function formatBinding(bindingSet, id) {
|
|
42
|
+
const gestures = bindingSet.gestures(id);
|
|
43
|
+
return gestures.length === 0 ? 'Disabled' : gestures.map(formatGesture).join(' / ');
|
|
44
|
+
}
|
|
45
|
+
export function canonicalTerminalStroke(raw) {
|
|
46
|
+
let key = null;
|
|
47
|
+
if (raw.upArrow)
|
|
48
|
+
key = 'up';
|
|
49
|
+
else if (raw.downArrow)
|
|
50
|
+
key = 'down';
|
|
51
|
+
else if (raw.leftArrow)
|
|
52
|
+
key = 'left';
|
|
53
|
+
else if (raw.rightArrow)
|
|
54
|
+
key = 'right';
|
|
55
|
+
else if (raw.return)
|
|
56
|
+
key = 'enter';
|
|
57
|
+
else if (raw.escape)
|
|
58
|
+
key = 'escape';
|
|
59
|
+
else if (raw.shiftTab)
|
|
60
|
+
return 'shift+tab';
|
|
61
|
+
else if (raw.tab)
|
|
62
|
+
key = 'tab';
|
|
63
|
+
else if (raw.backspace)
|
|
64
|
+
key = 'backspace';
|
|
65
|
+
else if (raw.delete)
|
|
66
|
+
key = 'delete';
|
|
67
|
+
else if (raw.pageUp)
|
|
68
|
+
key = 'pageup';
|
|
69
|
+
else if (raw.pageDown)
|
|
70
|
+
key = 'pagedown';
|
|
71
|
+
else if (raw.home)
|
|
72
|
+
key = 'home';
|
|
73
|
+
else if (raw.end)
|
|
74
|
+
key = 'end';
|
|
75
|
+
else if (raw.input?.length === 1)
|
|
76
|
+
key = raw.input === ' ' ? 'space' : raw.input;
|
|
77
|
+
if (key === null)
|
|
78
|
+
return null;
|
|
79
|
+
const modifiers = [];
|
|
80
|
+
if (raw.ctrl)
|
|
81
|
+
modifiers.push('ctrl');
|
|
82
|
+
if (raw.meta)
|
|
83
|
+
modifiers.push('alt');
|
|
84
|
+
if (raw.shift)
|
|
85
|
+
modifiers.push('shift');
|
|
86
|
+
try {
|
|
87
|
+
return normalizeStroke([...modifiers, key].join('+'));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function matchesTerminalInput(bindingSet, id, raw) {
|
|
94
|
+
const stroke = canonicalTerminalStroke(raw);
|
|
95
|
+
return stroke !== null && bindingSet.gestures(id).includes(stroke);
|
|
96
|
+
}
|
|
97
|
+
export function matchesPiTuiInput(bindingSet, id, input, matchesKey) {
|
|
98
|
+
return bindingSet.gestures(id).some((gesture) => !gesture.includes(' > ') && matchesKey(input, gesture));
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BindingId } from './catalog.js';
|
|
2
|
+
import type { SparseKeybindingOverrides } from './types.js';
|
|
3
|
+
export declare class KeybindingConcurrentEditError extends Error {
|
|
4
|
+
readonly latestKeybindings: unknown;
|
|
5
|
+
constructor(latestKeybindings: unknown);
|
|
6
|
+
}
|
|
7
|
+
export interface PersistKeybindingsOptions {
|
|
8
|
+
readonly expectedKeybindings?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface PersistedKeybindings {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly overrides: SparseKeybindingOverrides<BindingId>;
|
|
13
|
+
}
|
|
14
|
+
export declare function persistUserKeybindings(prospectiveOverrides: unknown, options?: PersistKeybindingsOptions): PersistedKeybindings;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { configPath, updateRawConfigAtomically } from '../config.js';
|
|
3
|
+
import { normalizeSparseOverrides } from './resolve.js';
|
|
4
|
+
export class KeybindingConcurrentEditError extends Error {
|
|
5
|
+
latestKeybindings;
|
|
6
|
+
constructor(latestKeybindings) {
|
|
7
|
+
super('keybindings changed on disk; reload before saving');
|
|
8
|
+
this.name = 'KeybindingConcurrentEditError';
|
|
9
|
+
this.latestKeybindings = latestKeybindings;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function persistUserKeybindings(prospectiveOverrides, options = {}) {
|
|
13
|
+
const overrides = normalizeSparseOverrides(prospectiveOverrides);
|
|
14
|
+
const path = configPath('user');
|
|
15
|
+
if (path === null)
|
|
16
|
+
throw new Error('user config path is unavailable');
|
|
17
|
+
updateRawConfigAtomically('user', (latest) => {
|
|
18
|
+
if (Object.hasOwn(options, 'expectedKeybindings')
|
|
19
|
+
&& !isDeepStrictEqual(latest['keybindings'], options.expectedKeybindings)) {
|
|
20
|
+
throw new KeybindingConcurrentEditError(latest['keybindings']);
|
|
21
|
+
}
|
|
22
|
+
const next = { ...latest };
|
|
23
|
+
delete next['canvasNav'];
|
|
24
|
+
if (Object.keys(overrides).length === 0)
|
|
25
|
+
delete next['keybindings'];
|
|
26
|
+
else
|
|
27
|
+
next['keybindings'] = overrides;
|
|
28
|
+
return next;
|
|
29
|
+
});
|
|
30
|
+
return Object.freeze({ path, overrides: Object.freeze({ ...overrides }) });
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BindingId } from './catalog.js';
|
|
2
|
+
import type { BindingDefinition, BindingResolution, KeybindingDiagnostic, SparseKeybindingOverrides } from './types.js';
|
|
3
|
+
export declare class GestureSyntaxError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function normalizeStroke(raw: string): string;
|
|
7
|
+
export declare function normalizeGesture(raw: string, definition?: Pick<BindingDefinition, 'inputKind'>): string;
|
|
8
|
+
export declare function resolveKeybindings(rawOverrides?: unknown): BindingResolution<BindingId>;
|
|
9
|
+
export declare function resolveUserKeybindings(): BindingResolution<BindingId>;
|
|
10
|
+
export declare function normalizeSparseOverrides(rawOverrides: unknown): SparseKeybindingOverrides<BindingId>;
|
|
11
|
+
export declare class KeybindingValidationError extends Error {
|
|
12
|
+
readonly diagnostics: readonly KeybindingDiagnostic[];
|
|
13
|
+
constructor(diagnostics: readonly KeybindingDiagnostic[]);
|
|
14
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { BINDING_CATALOG, BINDING_IDS, SAFETY_REQUIREMENTS } from './catalog.js';
|
|
3
|
+
import { readRawScopeConfig } from '../config.js';
|
|
4
|
+
const MODIFIER_ORDER = ['ctrl', 'alt', 'shift'];
|
|
5
|
+
const INPUT_MODIFIERS = new Set(['ctrl', 'alt', 'shift', 'meta']);
|
|
6
|
+
const NAMED_KEYS = new Set([
|
|
7
|
+
'enter', 'escape', 'tab', 'backspace', 'delete', 'space', 'up', 'down',
|
|
8
|
+
'left', 'right', 'pageup', 'pagedown', 'home', 'end',
|
|
9
|
+
]);
|
|
10
|
+
const KEY_ALIASES = { return: 'enter', esc: 'escape' };
|
|
11
|
+
export class GestureSyntaxError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'GestureSyntaxError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function tmuxNotation(value) {
|
|
18
|
+
const match = value.match(/^((?:[CMS]-)+)(.+)$/i);
|
|
19
|
+
if (match === null)
|
|
20
|
+
return value;
|
|
21
|
+
const modifiers = match[1].split('-').filter(Boolean).map((token) => {
|
|
22
|
+
if (token.toUpperCase() === 'C')
|
|
23
|
+
return 'ctrl';
|
|
24
|
+
if (token.toUpperCase() === 'M')
|
|
25
|
+
return 'alt';
|
|
26
|
+
return 'shift';
|
|
27
|
+
});
|
|
28
|
+
return `${modifiers.join('+')}+${match[2]}`;
|
|
29
|
+
}
|
|
30
|
+
export function normalizeStroke(raw) {
|
|
31
|
+
const value = tmuxNotation(raw.trim());
|
|
32
|
+
if (value === '')
|
|
33
|
+
throw new GestureSyntaxError('a stroke cannot be empty');
|
|
34
|
+
let rawKey;
|
|
35
|
+
let modifierParts;
|
|
36
|
+
if (value === '+') {
|
|
37
|
+
rawKey = '+';
|
|
38
|
+
modifierParts = [];
|
|
39
|
+
}
|
|
40
|
+
else if (value.endsWith('++')) {
|
|
41
|
+
rawKey = '+';
|
|
42
|
+
modifierParts = value.slice(0, -2).split('+');
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const parts = value.split('+');
|
|
46
|
+
if (parts.some((part) => part === ''))
|
|
47
|
+
throw new GestureSyntaxError(`invalid stroke "${raw}"`);
|
|
48
|
+
rawKey = parts.pop();
|
|
49
|
+
modifierParts = parts;
|
|
50
|
+
}
|
|
51
|
+
const inputModifiers = modifierParts.map((part) => part.toLowerCase());
|
|
52
|
+
if (inputModifiers.some((modifier) => !INPUT_MODIFIERS.has(modifier))) {
|
|
53
|
+
throw new GestureSyntaxError(`unsupported modifier in "${raw}"`);
|
|
54
|
+
}
|
|
55
|
+
const modifiers = inputModifiers.map((modifier) => modifier === 'meta' ? 'alt' : modifier);
|
|
56
|
+
if (new Set(modifiers).size !== modifiers.length) {
|
|
57
|
+
throw new GestureSyntaxError(`duplicate modifier in "${raw}"`);
|
|
58
|
+
}
|
|
59
|
+
let key = KEY_ALIASES[rawKey.toLowerCase()] ?? rawKey;
|
|
60
|
+
if (key.length === 1 && key >= 'A' && key <= 'Z') {
|
|
61
|
+
key = key.toLowerCase();
|
|
62
|
+
if (!modifiers.includes('shift'))
|
|
63
|
+
modifiers.push('shift');
|
|
64
|
+
}
|
|
65
|
+
else if (key.length !== 1) {
|
|
66
|
+
key = key.toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
if (!(key.length === 1 && key.charCodeAt(0) >= 32 && key.charCodeAt(0) <= 126) && !NAMED_KEYS.has(key)) {
|
|
69
|
+
throw new GestureSyntaxError(`unsupported key "${rawKey}"`);
|
|
70
|
+
}
|
|
71
|
+
const ordered = MODIFIER_ORDER.filter((modifier) => modifiers.includes(modifier));
|
|
72
|
+
return [...ordered, key].join('+');
|
|
73
|
+
}
|
|
74
|
+
export function normalizeGesture(raw, definition) {
|
|
75
|
+
const parts = raw.trim().split(/\s+>\s+/);
|
|
76
|
+
if (parts.length > 2)
|
|
77
|
+
throw new GestureSyntaxError('a gesture may contain at most two strokes');
|
|
78
|
+
const normalized = parts.map(normalizeStroke);
|
|
79
|
+
if (normalized.length === 2 && definition?.inputKind !== 'tmux-sequence') {
|
|
80
|
+
throw new GestureSyntaxError('two-stroke sequences are supported only by tmux sequence actions');
|
|
81
|
+
}
|
|
82
|
+
if (definition?.inputKind === 'tmux-sequence' && normalized.length !== 2) {
|
|
83
|
+
throw new GestureSyntaxError('this action requires a two-stroke tmux sequence');
|
|
84
|
+
}
|
|
85
|
+
if (definition?.inputKind === 'tmux-menu-selector') {
|
|
86
|
+
if (normalized.length !== 1)
|
|
87
|
+
throw new GestureSyntaxError('a menu selector must be one stroke');
|
|
88
|
+
const stroke = normalized[0];
|
|
89
|
+
const pieces = stroke.split('+');
|
|
90
|
+
const key = pieces.at(-1);
|
|
91
|
+
const modifiers = pieces.slice(0, -1);
|
|
92
|
+
if ((key.length !== 1 && key !== 'space') || modifiers.some((modifier) => modifier !== 'shift')) {
|
|
93
|
+
throw new GestureSyntaxError('a menu selector must be one printable character');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return normalized.join(' > ');
|
|
97
|
+
}
|
|
98
|
+
function normalizeList(raw, definition) {
|
|
99
|
+
if (!Array.isArray(raw))
|
|
100
|
+
throw new GestureSyntaxError('the override must be an array of gesture strings');
|
|
101
|
+
const normalized = [];
|
|
102
|
+
for (const item of raw) {
|
|
103
|
+
if (typeof item !== 'string')
|
|
104
|
+
throw new GestureSyntaxError('every override entry must be a gesture string');
|
|
105
|
+
const gesture = normalizeGesture(item, definition);
|
|
106
|
+
if (!normalized.includes(gesture))
|
|
107
|
+
normalized.push(gesture);
|
|
108
|
+
}
|
|
109
|
+
return Object.freeze(normalized);
|
|
110
|
+
}
|
|
111
|
+
function contextsOverlap(a, b) {
|
|
112
|
+
return a.contexts.some((context) => b.contexts.includes(context));
|
|
113
|
+
}
|
|
114
|
+
function isTmuxRoot(definition) {
|
|
115
|
+
return definition.contexts.includes('tmux.root');
|
|
116
|
+
}
|
|
117
|
+
function isInPane(definition) {
|
|
118
|
+
return !isTmuxRoot(definition)
|
|
119
|
+
&& definition.inputKind !== 'tmux-menu-selector'
|
|
120
|
+
&& definition.inputKind !== 'tmux-sequence';
|
|
121
|
+
}
|
|
122
|
+
function firstStroke(gesture) {
|
|
123
|
+
return gesture.split(' > ')[0];
|
|
124
|
+
}
|
|
125
|
+
function gesturesConflict(leftDefinition, leftGesture, rightDefinition, rightGesture) {
|
|
126
|
+
const leftSequence = leftGesture.includes(' > ');
|
|
127
|
+
const rightSequence = rightGesture.includes(' > ');
|
|
128
|
+
if (contextsOverlap(leftDefinition, rightDefinition)) {
|
|
129
|
+
if (leftSequence && rightSequence)
|
|
130
|
+
return leftGesture === rightGesture;
|
|
131
|
+
if (leftSequence || rightSequence)
|
|
132
|
+
return firstStroke(leftGesture) === firstStroke(rightGesture);
|
|
133
|
+
return leftGesture === rightGesture;
|
|
134
|
+
}
|
|
135
|
+
const leftRoot = isTmuxRoot(leftDefinition);
|
|
136
|
+
const rightRoot = isTmuxRoot(rightDefinition);
|
|
137
|
+
if (!leftRoot && !rightRoot)
|
|
138
|
+
return false;
|
|
139
|
+
if (leftRoot && rightRoot && leftSequence && rightSequence)
|
|
140
|
+
return leftGesture === rightGesture;
|
|
141
|
+
if (leftRoot && rightRoot)
|
|
142
|
+
return firstStroke(leftGesture) === firstStroke(rightGesture);
|
|
143
|
+
if (leftRoot && isInPane(rightDefinition))
|
|
144
|
+
return firstStroke(leftGesture) === rightGesture;
|
|
145
|
+
if (rightRoot && isInPane(leftDefinition))
|
|
146
|
+
return leftGesture === firstStroke(rightGesture);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
function defaultGestures(definition) {
|
|
150
|
+
return Object.freeze(definition.defaults.map((gesture) => normalizeGesture(gesture, definition)));
|
|
151
|
+
}
|
|
152
|
+
class FrozenBindingMap {
|
|
153
|
+
#map;
|
|
154
|
+
constructor(entries) { this.#map = new Map(entries); }
|
|
155
|
+
get size() { return this.#map.size; }
|
|
156
|
+
get(key) { return this.#map.get(key); }
|
|
157
|
+
has(key) { return this.#map.has(key); }
|
|
158
|
+
entries() { return this.#map.entries(); }
|
|
159
|
+
keys() { return this.#map.keys(); }
|
|
160
|
+
values() { return this.#map.values(); }
|
|
161
|
+
forEach(callbackfn, thisArg) {
|
|
162
|
+
for (const [key, value] of this.#map)
|
|
163
|
+
callbackfn.call(thisArg, value, key, this);
|
|
164
|
+
}
|
|
165
|
+
[Symbol.iterator]() { return this.#map[Symbol.iterator](); }
|
|
166
|
+
}
|
|
167
|
+
function diagnostic(code, message, offendingValue, bindingId, relatedBindingIds) {
|
|
168
|
+
return Object.freeze({ severity: 'error', code, message, offendingValue, ...(bindingId ? { bindingId } : {}), ...(relatedBindingIds ? { relatedBindingIds: Object.freeze([...relatedBindingIds]) } : {}) });
|
|
169
|
+
}
|
|
170
|
+
function collisionPairs(records) {
|
|
171
|
+
const out = [];
|
|
172
|
+
for (let leftIndex = 0; leftIndex < records.length; leftIndex += 1) {
|
|
173
|
+
const left = records[leftIndex];
|
|
174
|
+
for (let rightIndex = leftIndex + 1; rightIndex < records.length; rightIndex += 1) {
|
|
175
|
+
const right = records[rightIndex];
|
|
176
|
+
for (const leftGesture of left.gestures) {
|
|
177
|
+
const conflict = right.gestures.find((rightGesture) => gesturesConflict(left.definition, leftGesture, right.definition, rightGesture));
|
|
178
|
+
if (conflict !== undefined) {
|
|
179
|
+
out.push([left, right, leftGesture.includes(' > ') ? leftGesture : conflict]);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
function requirementSatisfied(records, context, capability) {
|
|
188
|
+
return records.some((record) => record.definition.safety === capability
|
|
189
|
+
&& record.gestures.length > 0
|
|
190
|
+
&& record.definition.contexts.includes(context));
|
|
191
|
+
}
|
|
192
|
+
function rejectCollidingUserOverrides(records, diagnostics) {
|
|
193
|
+
while (true) {
|
|
194
|
+
const rejected = new Set();
|
|
195
|
+
for (const [left, right, gesture] of collisionPairs(records)) {
|
|
196
|
+
if (left.source === 'user')
|
|
197
|
+
rejected.add(left);
|
|
198
|
+
if (right.source === 'user')
|
|
199
|
+
rejected.add(right);
|
|
200
|
+
if (left.source === 'default' && right.source === 'default') {
|
|
201
|
+
throw new Error(`keybinding catalog default collision: ${left.definition.id} / ${right.definition.id} (${gesture})`);
|
|
202
|
+
}
|
|
203
|
+
diagnostics.push(diagnostic('collision', `${left.definition.id} and ${right.definition.id} both claim ${gesture} in overlapping contexts`, gesture, left.source === 'user' ? left.definition.id : right.definition.id, [left.definition.id, right.definition.id]));
|
|
204
|
+
}
|
|
205
|
+
if (rejected.size === 0)
|
|
206
|
+
return;
|
|
207
|
+
for (const record of rejected) {
|
|
208
|
+
record.gestures = defaultGestures(record.definition);
|
|
209
|
+
record.source = 'default';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export function resolveKeybindings(rawOverrides = undefined) {
|
|
214
|
+
const diagnostics = [];
|
|
215
|
+
let overrides = {};
|
|
216
|
+
if (rawOverrides !== undefined) {
|
|
217
|
+
if (rawOverrides === null || typeof rawOverrides !== 'object' || Array.isArray(rawOverrides)) {
|
|
218
|
+
diagnostics.push(diagnostic('invalid-keybindings', 'keybindings must be an object keyed by catalog binding ID', rawOverrides));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
overrides = rawOverrides;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const [id, value] of Object.entries(overrides)) {
|
|
225
|
+
if (!BINDING_IDS.has(id)) {
|
|
226
|
+
diagnostics.push(diagnostic('unknown-binding', `unknown keybinding ID: ${id}`, value, id));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const records = BINDING_CATALOG.map((definition) => {
|
|
230
|
+
const defaults = defaultGestures(definition);
|
|
231
|
+
if (!Object.hasOwn(overrides, definition.id))
|
|
232
|
+
return { definition, gestures: defaults, source: 'default' };
|
|
233
|
+
const rawOverride = overrides[definition.id];
|
|
234
|
+
try {
|
|
235
|
+
return { definition, gestures: normalizeList(rawOverride, definition), source: 'user', rawOverride };
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
239
|
+
diagnostics.push(diagnostic('invalid-override', `${definition.id}: ${message}`, rawOverride, definition.id));
|
|
240
|
+
return { definition, gestures: defaults, source: 'default' };
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
rejectCollidingUserOverrides(records, diagnostics);
|
|
244
|
+
for (const requirement of SAFETY_REQUIREMENTS) {
|
|
245
|
+
if (requirementSatisfied(records, requirement.context, requirement.capability))
|
|
246
|
+
continue;
|
|
247
|
+
const offenders = records.filter((record) => record.source === 'user'
|
|
248
|
+
&& record.definition.safety === requirement.capability
|
|
249
|
+
&& record.definition.contexts.includes(requirement.context));
|
|
250
|
+
diagnostics.push(diagnostic('unreachable', `${requirement.context} must retain an enabled ${requirement.capability} binding`, offenders.map((record) => record.rawOverride), offenders[0]?.definition.id, offenders.map((record) => record.definition.id)));
|
|
251
|
+
for (const offender of offenders) {
|
|
252
|
+
offender.gestures = defaultGestures(offender.definition);
|
|
253
|
+
offender.source = 'default';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
rejectCollidingUserOverrides(records, diagnostics);
|
|
257
|
+
const entries = records.map((record) => {
|
|
258
|
+
const effective = Object.freeze({
|
|
259
|
+
definition: record.definition,
|
|
260
|
+
gestures: record.gestures,
|
|
261
|
+
source: record.source,
|
|
262
|
+
});
|
|
263
|
+
return [record.definition.id, effective];
|
|
264
|
+
});
|
|
265
|
+
const bindings = new FrozenBindingMap(entries);
|
|
266
|
+
const result = {
|
|
267
|
+
bindings,
|
|
268
|
+
diagnostics: Object.freeze(diagnostics),
|
|
269
|
+
get(id) {
|
|
270
|
+
const found = bindings.get(id);
|
|
271
|
+
if (found === undefined)
|
|
272
|
+
throw new Error(`unknown crouter keybinding ID: ${id}`);
|
|
273
|
+
return found;
|
|
274
|
+
},
|
|
275
|
+
gestures(id) { return this.get(id).gestures; },
|
|
276
|
+
};
|
|
277
|
+
return Object.freeze(result);
|
|
278
|
+
}
|
|
279
|
+
export function resolveUserKeybindings() {
|
|
280
|
+
const raw = readRawScopeConfig('user');
|
|
281
|
+
return resolveKeybindings(raw?.keybindings);
|
|
282
|
+
}
|
|
283
|
+
export function normalizeSparseOverrides(rawOverrides) {
|
|
284
|
+
const resolved = resolveKeybindings(rawOverrides);
|
|
285
|
+
if (resolved.diagnostics.length > 0) {
|
|
286
|
+
throw new KeybindingValidationError(resolved.diagnostics);
|
|
287
|
+
}
|
|
288
|
+
const sparse = {};
|
|
289
|
+
for (const definition of BINDING_CATALOG) {
|
|
290
|
+
const effective = resolved.get(definition.id);
|
|
291
|
+
const defaults = defaultGestures(definition);
|
|
292
|
+
if (effective.source === 'user' && !isDeepStrictEqual(effective.gestures, defaults)) {
|
|
293
|
+
sparse[definition.id] = [...effective.gestures];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return sparse;
|
|
297
|
+
}
|
|
298
|
+
export class KeybindingValidationError extends Error {
|
|
299
|
+
diagnostics;
|
|
300
|
+
constructor(diagnostics) {
|
|
301
|
+
super(diagnostics[0]?.message ?? 'invalid keybindings');
|
|
302
|
+
this.name = 'KeybindingValidationError';
|
|
303
|
+
this.diagnostics = diagnostics;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type BindingInputKind = 'terminal' | 'tmux-sequence' | 'tmux-menu-selector' | 'host-global';
|
|
2
|
+
export type BindingSafetyCapability = 'dismiss-modal' | 'leave-screen' | 'save-keybindings';
|
|
3
|
+
export interface BindingDefinition<Id extends string = string> {
|
|
4
|
+
readonly id: Id;
|
|
5
|
+
readonly group: string;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly defaults: readonly string[];
|
|
9
|
+
readonly inputKind: BindingInputKind;
|
|
10
|
+
readonly contexts: readonly string[];
|
|
11
|
+
readonly safety?: BindingSafetyCapability;
|
|
12
|
+
}
|
|
13
|
+
export interface EffectiveBinding<Id extends string = string> {
|
|
14
|
+
readonly definition: BindingDefinition<Id>;
|
|
15
|
+
readonly gestures: readonly string[];
|
|
16
|
+
readonly source: 'default' | 'user';
|
|
17
|
+
}
|
|
18
|
+
export type KeybindingDiagnosticCode = 'invalid-keybindings' | 'unknown-binding' | 'invalid-override' | 'collision' | 'unreachable';
|
|
19
|
+
export interface KeybindingDiagnostic {
|
|
20
|
+
readonly severity: 'error';
|
|
21
|
+
readonly code: KeybindingDiagnosticCode;
|
|
22
|
+
readonly bindingId?: string;
|
|
23
|
+
readonly relatedBindingIds?: readonly string[];
|
|
24
|
+
readonly offendingValue: unknown;
|
|
25
|
+
readonly message: string;
|
|
26
|
+
}
|
|
27
|
+
export interface SafetyRequirement {
|
|
28
|
+
readonly context: string;
|
|
29
|
+
readonly capability: BindingSafetyCapability;
|
|
30
|
+
}
|
|
31
|
+
export interface BindingResolution<Id extends string = string> {
|
|
32
|
+
readonly bindings: ReadonlyMap<Id, EffectiveBinding<Id>>;
|
|
33
|
+
readonly diagnostics: readonly KeybindingDiagnostic[];
|
|
34
|
+
get(id: Id): EffectiveBinding<Id>;
|
|
35
|
+
gestures(id: Id): readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export type SparseKeybindingOverrides<Id extends string = string> = Partial<Record<Id, string[]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type InstalledPlugin, type Scope } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Thin memory-document resolver for the document substrate. Precedence is the
|
|
4
4
|
* OUTER loop, not a global scan: scopes/sources are tried NEAREST-FIRST
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join, relative, sep } from 'node:path';
|
|
2
2
|
import { readdirSync } from 'node:fs';
|
|
3
|
+
import { CRTR_DIR_NAME } from '../types.js';
|
|
3
4
|
import { pathExists, readText, walkFiles } from './fs-utils.js';
|
|
4
5
|
import { parseFrontmatterGeneric } from './frontmatter.js';
|
|
5
6
|
import { listInstalledPlugins, listInstalledPluginsInRoot, parseSkillQualifier } from './resolver.js';
|
|
@@ -117,8 +118,14 @@ function listMemoryDocsInDir(scope, dir, quiet = false) {
|
|
|
117
118
|
continue;
|
|
118
119
|
}
|
|
119
120
|
for (const e of entries) {
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
// Enumeration stops at store boundaries: a nested `.crouter/` is another
|
|
122
|
+
// scope's mount point (e.g. a dir-scoped store inside a dir that is itself
|
|
123
|
+
// a store, like builtin-memory in the crouter repo), never this store's
|
|
124
|
+
// own content — walking into it would register a different tier's docs.
|
|
125
|
+
if (e.isDirectory()) {
|
|
126
|
+
if (e.name !== CRTR_DIR_NAME)
|
|
127
|
+
stack.push(join(d, e.name));
|
|
128
|
+
}
|
|
122
129
|
else if (e.isFile() && e.name.endsWith('.md') && e.name !== 'SKILL.md') {
|
|
123
130
|
const file = join(d, e.name);
|
|
124
131
|
const raw = relative(dir, file).replace(/\.md$/i, '').split(sep).join('/');
|
|
@@ -182,7 +189,7 @@ export function listPluginMemoryDocs(plugin, scope, quiet = false) {
|
|
|
182
189
|
if (!pathExists(dir))
|
|
183
190
|
return [];
|
|
184
191
|
const docs = [];
|
|
185
|
-
for (const file of walkFiles(dir, (n) => n.endsWith('.md') && n !== 'SKILL.md')) {
|
|
192
|
+
for (const file of walkFiles(dir, (n) => n.endsWith('.md') && n !== 'SKILL.md', (d) => d === CRTR_DIR_NAME)) {
|
|
186
193
|
const derived = normalizeDocName(relative(dir, file).replace(/\.md$/i, '').split(sep).join('/'));
|
|
187
194
|
if (!derived)
|
|
188
195
|
continue;
|
package/dist/core/predicate.d.ts
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
* Predicate matcher engine — vendored verbatim from pi's frontmatter-rules
|
|
3
3
|
* extension.
|
|
4
4
|
*
|
|
5
|
-
* Source: pi-
|
|
5
|
+
* Source: src/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts
|
|
6
6
|
* (the "Condition language" block, functions `asArray` … `evalCondition`).
|
|
7
7
|
*
|
|
8
8
|
* The unified document substrate uses this to evaluate a document's `gate`
|
|
9
9
|
* predicate against a node-config subject (e.g. `orchestration.depth: { gte: 2 }`).
|
|
10
10
|
*
|
|
11
|
-
* This is intentionally a COPY, not an import: the
|
|
12
|
-
*
|
|
11
|
+
* This is intentionally a COPY, not an import: the bundled pi package is
|
|
12
|
+
* excluded from crouter's tsc compile unit (tsconfig `exclude`) — it is loaded
|
|
13
|
+
* by pi's own TS loader at runtime — so compiled core cannot import from it;
|
|
14
|
+
* cross-boundary imports go extension→core only. The functions are pure
|
|
13
15
|
* TS over `unknown` / `Record<string, unknown>` with zero external imports, so
|
|
14
16
|
* vendoring them keeps this module standalone and dependency-free. Keep it in
|
|
15
17
|
* sync with the source by hand if the matcher language ever changes.
|
package/dist/core/predicate.js
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
* Predicate matcher engine — vendored verbatim from pi's frontmatter-rules
|
|
3
3
|
* extension.
|
|
4
4
|
*
|
|
5
|
-
* Source: pi-
|
|
5
|
+
* Source: src/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts
|
|
6
6
|
* (the "Condition language" block, functions `asArray` … `evalCondition`).
|
|
7
7
|
*
|
|
8
8
|
* The unified document substrate uses this to evaluate a document's `gate`
|
|
9
9
|
* predicate against a node-config subject (e.g. `orchestration.depth: { gte: 2 }`).
|
|
10
10
|
*
|
|
11
|
-
* This is intentionally a COPY, not an import: the
|
|
12
|
-
*
|
|
11
|
+
* This is intentionally a COPY, not an import: the bundled pi package is
|
|
12
|
+
* excluded from crouter's tsc compile unit (tsconfig `exclude`) — it is loaded
|
|
13
|
+
* by pi's own TS loader at runtime — so compiled core cannot import from it;
|
|
14
|
+
* cross-boundary imports go extension→core only. The functions are pure
|
|
13
15
|
* TS over `unknown` / `Record<string, unknown>` with zero external imports, so
|
|
14
16
|
* vendoring them keeps this module standalone and dependency-free. Keep it in
|
|
15
17
|
* sync with the source by hand if the matcher language ever changes.
|
|
@@ -169,6 +169,15 @@ export interface CycleModelFrame {
|
|
|
169
169
|
type: 'cycle_model';
|
|
170
170
|
direction?: 'forward' | 'backward';
|
|
171
171
|
}
|
|
172
|
+
/** `cycleLadder(direction)` — alt+m forward / alt+shift+m backward. Walks the INTERLEAVED model ladder
|
|
173
|
+
* (anthropic/ultra → openai/ultra → anthropic/strong → openai/strong → …),
|
|
174
|
+
* starting from the current model, applying each rung's own thinking level.
|
|
175
|
+
* Distinct from `cycle_model`, which steps the flat registry list. `direction`
|
|
176
|
+
* defaults to `'forward'` when absent. */
|
|
177
|
+
export interface CycleLadderFrame {
|
|
178
|
+
type: 'cycle_ladder';
|
|
179
|
+
direction?: 'forward' | 'backward';
|
|
180
|
+
}
|
|
172
181
|
/** `cycleThinkingLevel()` — shift+tab. Cycles to the next thinking level; the new
|
|
173
182
|
* level reaches viewers via the relayed `thinking_level_changed` event, so the
|
|
174
183
|
* broker replies a plain `ack` (no payload). Distinct from `set_thinking_level`,
|
|
@@ -303,7 +312,7 @@ export interface ShareFrame {
|
|
|
303
312
|
export interface ReloadAuthFrame {
|
|
304
313
|
type: 'reload_auth';
|
|
305
314
|
}
|
|
306
|
-
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | BashFrame | RequestControlFrame | ReleaseControlFrame | ByeFrame | ShutdownFrame | SetModelFrame | CycleModelFrame | CycleThinkingFrame | SetThinkingLevelFrame | DequeueFrame | SetAutoRetryFrame | SetAutoCompactionFrame | CompactFrame | NewSessionFrame | SwitchSessionFrame | ForkFrame | SetSessionNameFrame | GetCommandsFrame | NavigateTreeFrame | ReloadFrame | ExportFrame | ListModelsFrame | ListSessionsFrame | GetTreeFrame | GetSettingsFrame | ListScopedModelsFrame | ExtensionUIResponseFrame | CloneFrame | ShareFrame | ReloadAuthFrame;
|
|
315
|
+
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | BashFrame | RequestControlFrame | ReleaseControlFrame | ByeFrame | ShutdownFrame | SetModelFrame | CycleModelFrame | CycleLadderFrame | CycleThinkingFrame | SetThinkingLevelFrame | DequeueFrame | SetAutoRetryFrame | SetAutoCompactionFrame | CompactFrame | NewSessionFrame | SwitchSessionFrame | ForkFrame | SetSessionNameFrame | GetCommandsFrame | NavigateTreeFrame | ReloadFrame | ExportFrame | ListModelsFrame | ListSessionsFrame | GetTreeFrame | GetSettingsFrame | ListScopedModelsFrame | ExtensionUIResponseFrame | CloneFrame | ShareFrame | ReloadAuthFrame;
|
|
307
316
|
export interface WelcomeFrame {
|
|
308
317
|
type: 'welcome';
|
|
309
318
|
snapshot: BrokerSnapshot;
|
|
@@ -330,6 +339,13 @@ export interface ControlChangedFrame {
|
|
|
330
339
|
export interface ModelChangedFrame {
|
|
331
340
|
type: 'model_changed';
|
|
332
341
|
model: string | undefined;
|
|
342
|
+
/** The full resolved `provider/id[:thinking]` spec (`formatModelSpec`) — what
|
|
343
|
+
* the broker persists into the node's own launch recipe. Viewers use it to
|
|
344
|
+
* persist a non-portable explicit pick as a kind DEFAULT
|
|
345
|
+
* (`persistDefaultKindModel`) without guessing the provider from the bare
|
|
346
|
+
* id. Null when the engine has no model. Optional so older brokers'
|
|
347
|
+
* frames still parse. */
|
|
348
|
+
spec?: string | null;
|
|
333
349
|
}
|
|
334
350
|
export interface ErrorFrame {
|
|
335
351
|
type: 'error';
|
|
@@ -380,9 +396,10 @@ export interface WireScopedModel {
|
|
|
380
396
|
model: Model<Api>;
|
|
381
397
|
thinkingLevel?: AgentSession['thinkingLevel'];
|
|
382
398
|
}
|
|
383
|
-
/** Wire form of pi's `SessionInfo`:
|
|
384
|
-
*
|
|
385
|
-
*
|
|
399
|
+
/** Wire form of pi's `SessionInfo`: `created`/`modified` are ISO strings (pi's
|
|
400
|
+
* are `Date`, which JSON cannot round-trip), and `allMessagesText` is a bounded
|
|
401
|
+
* opening/latest search excerpt rather than the unbounded transcript text. The
|
|
402
|
+
* viewer revives the dates before handing them to `SessionSelectorComponent`. */
|
|
386
403
|
export interface WireSessionInfo extends Omit<SessionInfo, 'created' | 'modified'> {
|
|
387
404
|
created: string;
|
|
388
405
|
modified: string;
|
|
@@ -494,10 +511,21 @@ export interface DisplayTitleFrame {
|
|
|
494
511
|
}
|
|
495
512
|
/** A blocking dialog routed to the controller — pi's public RPC request type. */
|
|
496
513
|
export type ExtensionUIRequestFrame = RpcExtensionUIRequest;
|
|
514
|
+
/** Broker → client: dismiss a previously-forwarded blocking dialog by request id,
|
|
515
|
+
* WITHOUT a client answer. The broker sends this exactly when it resolves a pending
|
|
516
|
+
* dialog ITSELF rather than the client answering it — the extension aborted the
|
|
517
|
+
* request out-of-band (e.g. a local OAuth loopback callback won the race against a
|
|
518
|
+
* still-open manual-paste dialog) or the broker-side timeout fired. The client tears
|
|
519
|
+
* down ONLY the overlay whose `id` matches; every other dialog stays exactly as it
|
|
520
|
+
* was, so an unrelated request can never dismiss an unrelated blocking dialog. */
|
|
521
|
+
export interface ExtensionUIDismissFrame {
|
|
522
|
+
type: 'extension_ui_dismiss';
|
|
523
|
+
id: string;
|
|
524
|
+
}
|
|
497
525
|
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
498
526
|
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
499
527
|
* non-colliding `type` discriminants. */
|
|
500
|
-
export type BrokerToClient = WelcomeFrame | ControlChangedFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | DisplayStatusFrame | DisplayWidgetFrame | DisplayTitleFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | AgentSessionEvent;
|
|
528
|
+
export type BrokerToClient = WelcomeFrame | ControlChangedFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | DisplayStatusFrame | DisplayWidgetFrame | DisplayTitleFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | AgentSessionEvent;
|
|
501
529
|
/** Encode one frame as a single newline-terminated JSON line. */
|
|
502
530
|
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
503
531
|
/** Byte bounds for a {@link FrameDecoder} (C5). */
|