@north-light/crouter 0.3.161 → 0.3.163
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 +3 -1
- package/dist/api/client.js +6 -0
- package/dist/api/dto/modelauth.d.ts +32 -0
- package/dist/api/dto/modelauth.js +2 -1
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/00-runtime-base.md +1 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -0
- package/dist/builtin-memory/init.md +38 -0
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +0 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +88 -396
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +215 -419
- package/dist/clients/attach/__tests__/crtr-output.test.js +14 -9
- package/dist/clients/attach/__tests__/edit-diff.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/edit-diff.test.js +34 -0
- package/dist/clients/attach/__tests__/editor-frame-title.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/editor-frame-title.test.js +19 -0
- package/dist/clients/attach/render/chat-view.js +8 -3
- package/dist/clients/attach/render/crtr-output.js +31 -2
- package/dist/clients/attach/render/edit-diff.js +26 -2
- package/dist/clients/attach/render/tool-calls.d.ts +7 -2
- package/dist/clients/attach/render/tool-calls.js +25 -4
- package/dist/clients/attach/session/connection.d.ts +1 -1
- package/dist/clients/attach/session/editor-frame.d.ts +5 -1
- package/dist/clients/attach/session/editor-frame.js +21 -15
- package/dist/clients/attach/session/identity.d.ts +1 -1
- package/dist/clients/attach/viewer.js +441 -439
- package/dist/commands/__tests__/api-canvas-source.test.d.ts +1 -0
- package/dist/commands/__tests__/api-canvas-source.test.js +37 -0
- package/dist/commands/__tests__/human.test.js +2 -0
- package/dist/commands/__tests__/search-contents.test.d.ts +1 -0
- package/dist/commands/__tests__/search-contents.test.js +52 -0
- package/dist/commands/api-client.js +1 -0
- package/dist/commands/human/prompts.js +2 -2
- package/dist/commands/human/shared.d.ts +1 -0
- package/dist/commands/human/shared.js +7 -4
- package/dist/commands/memory/lint.d.ts +6 -0
- package/dist/commands/memory/lint.js +177 -35
- package/dist/commands/memory/read.js +22 -1
- package/dist/commands/memory/write.js +18 -4
- package/dist/commands/memory.js +1 -1
- package/dist/commands/search/contents.js +2 -2
- package/dist/commands/sys/__tests__/sync-deps.test.js +12 -21
- package/dist/commands/sys/__tests__/sync-import.test.js +30 -29
- package/dist/commands/sys/setup-wizard.d.ts +8 -1
- package/dist/commands/sys/setup-wizard.js +209 -58
- package/dist/commands/sys/sync-deps.js +14 -18
- package/dist/commands/sys/sync-project-guidance.js +16 -19
- package/dist/core/__tests__/broker-launch-candidates.test.js +13 -3
- package/dist/core/__tests__/broker-sdk-wiring.test.js +53 -25
- package/dist/core/__tests__/full/broker-provider-retry.test.js +6 -122
- package/dist/core/__tests__/migration.test.js +8 -3
- package/dist/core/__tests__/model-pin-durability.test.js +7 -5
- package/dist/core/__tests__/model-routes.test.d.ts +1 -0
- package/dist/core/__tests__/model-routes.test.js +41 -0
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +7 -10
- package/dist/core/__tests__/on-read-dedup-resume.test.js +13 -25
- package/dist/core/__tests__/on-read-identity.test.js +8 -15
- package/dist/core/__tests__/review-model-floor.test.js +2 -2
- package/dist/core/__tests__/revive.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -4
- package/dist/core/__tests__/worktree.test.js +3 -3
- package/dist/core/auth-file.d.ts +3 -2
- package/dist/core/auth-file.js +5 -3
- package/dist/core/canvas/extensions.d.ts +1 -1
- package/dist/core/canvas/extensions.js +18 -11
- package/dist/core/canvas/labels.d.ts +4 -5
- package/dist/core/canvas/labels.js +9 -9
- package/dist/core/canvas/types.d.ts +6 -1
- package/dist/core/command-manifests/schema.js +18 -5
- package/dist/core/command.js +17 -7
- package/dist/core/config.d.ts +3 -1
- package/dist/core/config.js +88 -2
- package/dist/core/configured-clis/invoker.js +2 -0
- package/dist/core/help.d.ts +3 -1
- package/dist/core/help.js +5 -2
- package/dist/core/keybindings/__tests__/resolve.test.js +5 -2
- package/dist/core/keybindings/catalog.d.ts +3 -2
- package/dist/core/keybindings/catalog.js +34 -25
- package/dist/core/keybindings/index.d.ts +1 -1
- package/dist/core/keybindings/resolve.js +8 -0
- package/dist/core/keybindings/types.d.ts +6 -1
- package/dist/core/memory/doc-link-grammar.d.ts +20 -0
- package/dist/core/memory/doc-link-grammar.js +110 -0
- package/dist/core/memory-resolver.d.ts +5 -0
- package/dist/core/memory-resolver.js +12 -1
- package/dist/core/model-routes.d.ts +37 -0
- package/dist/core/model-routes.js +140 -0
- package/dist/core/runtime/bearings.d.ts +8 -8
- package/dist/core/runtime/bearings.js +20 -16
- package/dist/core/runtime/broker.d.ts +0 -4
- package/dist/core/runtime/broker.js +119 -385
- package/dist/core/runtime/canvas-extensions.d.ts +1 -0
- package/dist/core/runtime/canvas-extensions.js +2 -0
- package/dist/core/runtime/launch.d.ts +10 -12
- package/dist/core/runtime/launch.js +16 -22
- package/dist/core/runtime/managed-provider-cooling.d.ts +0 -14
- package/dist/core/runtime/managed-provider-cooling.js +0 -19
- package/dist/core/runtime/model-swap.js +7 -1
- package/dist/core/runtime/promote.js +1 -0
- package/dist/core/runtime/tmux.js +136 -100
- package/dist/core/scope.js +4 -5
- package/dist/core/subscription-state.d.ts +12 -36
- package/dist/core/subscription-state.js +19 -203
- package/dist/core/substrate/ceiling.d.ts +3 -6
- package/dist/core/substrate/ceiling.js +13 -15
- package/dist/core/substrate/index.d.ts +2 -3
- package/dist/core/substrate/index.js +2 -2
- package/dist/core/substrate/on-read.d.ts +4 -13
- package/dist/core/substrate/on-read.js +142 -262
- package/dist/core/substrate/render.js +4 -4
- package/dist/core/substrate/schema.d.ts +3 -2
- package/dist/core/substrate/schema.js +5 -5
- package/dist/daemon/api/__tests__/full/api-server.test.js +9 -6
- package/dist/daemon/api/__tests__/full/b10-attach-modelauth.test.js +123 -1
- package/dist/daemon/api/handlers/modelauth.js +51 -2
- package/dist/daemon/api/handlers/nodes.js +2 -0
- package/dist/pi-extensions/__tests__/canvas-tool-guide.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-tool-guide.test.js +96 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +7 -8
- package/dist/pi-extensions/canvas-tool-guide.d.ts +14 -0
- package/dist/pi-extensions/canvas-tool-guide.js +74 -0
- package/dist/types.d.ts +17 -0
- package/dist/web-client/assets/{index-B76ZKfT_.js → index-NIuSCOHM.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crouter-help.ts +0 -95
|
@@ -4,6 +4,7 @@ export declare const CANVAS_RECAP_PATH: string;
|
|
|
4
4
|
export declare const CANVAS_GOAL_CAPTURE_PATH: string;
|
|
5
5
|
export declare const CANVAS_PASSIVE_CONTEXT_PATH: string;
|
|
6
6
|
export declare const CANVAS_CONTEXT_INTRO_PATH: string;
|
|
7
|
+
export declare const CANVAS_TOOL_GUIDE_PATH: string;
|
|
7
8
|
export declare const CANVAS_DOC_SUBSTRATE_PATH: string;
|
|
8
9
|
export declare const CANVAS_STRUCTURED_OUTPUT_PATH: string;
|
|
9
10
|
export declare const CANVAS_BASH_VALVE_PATH: string;
|
|
@@ -16,6 +16,7 @@ export const CANVAS_RECAP_PATH = resolveExtension('canvas-recap');
|
|
|
16
16
|
export const CANVAS_GOAL_CAPTURE_PATH = resolveExtension('canvas-goal-capture');
|
|
17
17
|
export const CANVAS_PASSIVE_CONTEXT_PATH = resolveExtension('canvas-passive-context');
|
|
18
18
|
export const CANVAS_CONTEXT_INTRO_PATH = resolveExtension('canvas-context-intro');
|
|
19
|
+
export const CANVAS_TOOL_GUIDE_PATH = resolveExtension('canvas-tool-guide');
|
|
19
20
|
export const CANVAS_DOC_SUBSTRATE_PATH = resolveExtension('canvas-doc-substrate');
|
|
20
21
|
export const CANVAS_STRUCTURED_OUTPUT_PATH = resolveExtension('canvas-structured-output');
|
|
21
22
|
export const CANVAS_BASH_VALVE_PATH = resolveExtension('canvas-bash-valve');
|
|
@@ -32,6 +33,7 @@ export const CANVAS_EXTENSIONS = [
|
|
|
32
33
|
CANVAS_GOAL_CAPTURE_PATH,
|
|
33
34
|
CANVAS_PASSIVE_CONTEXT_PATH,
|
|
34
35
|
CANVAS_CONTEXT_INTRO_PATH,
|
|
36
|
+
CANVAS_TOOL_GUIDE_PATH,
|
|
35
37
|
CANVAS_DOC_SUBSTRATE_PATH,
|
|
36
38
|
CANVAS_STRUCTURED_OUTPUT_PATH,
|
|
37
39
|
CANVAS_BASH_VALVE_PATH,
|
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
export { CANVAS_EXTENSIONS, CANVAS_STOPHOOK_PATH, CANVAS_INBOX_WATCHER_PATH, CANVAS_RECAP_PATH, CANVAS_GOAL_CAPTURE_PATH, CANVAS_PASSIVE_CONTEXT_PATH, CANVAS_CONTEXT_INTRO_PATH, CANVAS_DOC_SUBSTRATE_PATH, CANVAS_STRUCTURED_OUTPUT_PATH, CANVAS_BASH_VALVE_PATH, } from './canvas-extensions.js';
|
|
1
|
+
export { CANVAS_EXTENSIONS, CANVAS_STOPHOOK_PATH, CANVAS_INBOX_WATCHER_PATH, CANVAS_RECAP_PATH, CANVAS_GOAL_CAPTURE_PATH, CANVAS_PASSIVE_CONTEXT_PATH, CANVAS_CONTEXT_INTRO_PATH, CANVAS_TOOL_GUIDE_PATH, CANVAS_DOC_SUBSTRATE_PATH, CANVAS_STRUCTURED_OUTPUT_PATH, CANVAS_BASH_VALVE_PATH, } from './canvas-extensions.js';
|
|
2
2
|
import type { NodeMeta, LaunchSpec, Mode, Lifecycle } from '../canvas/index.js';
|
|
3
3
|
import type { ModelProvider, ModelStrength, ScopeConfig } from '../../types.js';
|
|
4
4
|
export { OPENAI_PROVIDER_ENV, configuredLadderProviders, inferPortableModelSelection } from './model-selection.js';
|
|
5
5
|
export type { PortableModelSelection } from './model-selection.js';
|
|
6
|
-
export interface EquivalentProviderModel {
|
|
7
|
-
fromProvider: ModelProvider;
|
|
8
|
-
toProvider: ModelProvider;
|
|
9
|
-
strength: ModelStrength;
|
|
10
|
-
model: string;
|
|
11
|
-
}
|
|
12
|
-
/** Map a concrete ladder model back to its provider×strength cell, then return
|
|
13
|
-
* the other configured provider's model at the same strength. `failedProviders`
|
|
14
|
-
* is per-turn state: once a provider has produced a retryable outage error, do
|
|
15
|
-
* not bounce back to it or keep reattempting the same failed provider. */
|
|
16
|
-
export declare function equivalentOtherProviderModel(currentModel: string, failedProviders?: ReadonlySet<ModelProvider>): EquivalentProviderModel | null;
|
|
17
6
|
/** True when an explicit model token is QUALIFIED — a `<provider>/<model-id>`
|
|
18
7
|
* or `<provider>/<tier>` raw token — the structural marker of a concrete exact
|
|
19
8
|
* pick. Deliberately provider-agnostic: a real registry provider namespace
|
|
@@ -31,6 +20,10 @@ export declare function isProviderPinnedModelToken(model: string): boolean;
|
|
|
31
20
|
* ladder the running build ships, so they never freeze a stale concrete
|
|
32
21
|
* model id to disk. Mirrors `normalizeModel`'s branch structure exactly. */
|
|
33
22
|
export declare function isPortableModelToken(model: string): boolean;
|
|
23
|
+
export declare function logicalModelIntent(model: string, ladders?: ScopeConfig['modelLadders']): {
|
|
24
|
+
family: string;
|
|
25
|
+
strength: ModelStrength;
|
|
26
|
+
} | undefined;
|
|
34
27
|
export declare function normalizeModel(model: string, ladders?: ScopeConfig['modelLadders']): string;
|
|
35
28
|
/** The interleaved model-ladder cycle Alt+M / Alt+Shift+M walks: for each strength tier
|
|
36
29
|
* from strongest to weakest, each offered provider before descending — anthropic/ultra,
|
|
@@ -95,6 +88,11 @@ export declare function buildLaunchSpec(kind: string, mode: Mode, opts: {
|
|
|
95
88
|
* promote/config, where `opts.model`'s shape IS the raw token and
|
|
96
89
|
* legitimately re-decides the pin. */
|
|
97
90
|
modelProviderPinned?: boolean;
|
|
91
|
+
/** Preserved intent from an existing launch recipe when rebuilding it. */
|
|
92
|
+
modelIntent?: {
|
|
93
|
+
family: string;
|
|
94
|
+
strength: ModelStrength;
|
|
95
|
+
};
|
|
98
96
|
/** Exact-consult launch: pass the captured `provider/id:thinking` spec
|
|
99
97
|
* verbatim as `launch.model`, skipping `floorReviewModel`/`normalizeModel`
|
|
100
98
|
* entirely (avoids any tier-collision reinterpretation). `opts.model` MUST
|
|
@@ -14,7 +14,7 @@ import { readMergedLaunchConfig } from '../config.js';
|
|
|
14
14
|
import { LAUNCH_SYSTEM_PROMPT } from './launch-prompt.js';
|
|
15
15
|
import { nodeEnv } from './nodes.js';
|
|
16
16
|
import { editorLabel } from '../canvas/index.js';
|
|
17
|
-
export { CANVAS_EXTENSIONS, CANVAS_STOPHOOK_PATH, CANVAS_INBOX_WATCHER_PATH, CANVAS_RECAP_PATH, CANVAS_GOAL_CAPTURE_PATH, CANVAS_PASSIVE_CONTEXT_PATH, CANVAS_CONTEXT_INTRO_PATH, CANVAS_DOC_SUBSTRATE_PATH, CANVAS_STRUCTURED_OUTPUT_PATH, CANVAS_BASH_VALVE_PATH, } from './canvas-extensions.js';
|
|
17
|
+
export { CANVAS_EXTENSIONS, CANVAS_STOPHOOK_PATH, CANVAS_INBOX_WATCHER_PATH, CANVAS_RECAP_PATH, CANVAS_GOAL_CAPTURE_PATH, CANVAS_PASSIVE_CONTEXT_PATH, CANVAS_CONTEXT_INTRO_PATH, CANVAS_TOOL_GUIDE_PATH, CANVAS_DOC_SUBSTRATE_PATH, CANVAS_STRUCTURED_OUTPUT_PATH, CANVAS_BASH_VALVE_PATH, } from './canvas-extensions.js';
|
|
18
18
|
import { nodeDir } from '../canvas/paths.js';
|
|
19
19
|
import { CANVAS_EXTENSIONS, mergeCanvasExtensions, assertMandatoryCanvasExtensions } from './canvas-extensions.js';
|
|
20
20
|
import { modelLadders, defaultProvider, STRENGTH_ALIASES, MODEL_PROVIDER_KEYS, MODEL_STRENGTHS, THINKING_SUFFIXES, stripThinkingSuffix, } from './model-selection.js';
|
|
@@ -43,26 +43,7 @@ export { OPENAI_PROVIDER_ENV, configuredLadderProviders, inferPortableModelSelec
|
|
|
43
43
|
function ladderFor(provider, ladders = modelLadders()) {
|
|
44
44
|
return ladders[provider];
|
|
45
45
|
}
|
|
46
|
-
// Automatic turn-time fallback ordering + strength ranking stay local to the
|
|
47
|
-
// launch recipe (not needed by the pure selection primitives).
|
|
48
|
-
const FALLBACK_PROVIDER_KEYS = ['anthropic', 'openai'];
|
|
49
46
|
const MODEL_STRENGTH_RANK = { light: 0, medium: 1, strong: 2, ultra: 3 };
|
|
50
|
-
/** Map a concrete ladder model back to its provider×strength cell, then return
|
|
51
|
-
* the other configured provider's model at the same strength. `failedProviders`
|
|
52
|
-
* is per-turn state: once a provider has produced a retryable outage error, do
|
|
53
|
-
* not bounce back to it or keep reattempting the same failed provider. */
|
|
54
|
-
export function equivalentOtherProviderModel(currentModel, failedProviders = new Set()) {
|
|
55
|
-
const ladders = modelLadders();
|
|
56
|
-
const cell = findLadderCell(normalizeModel(currentModel), ladders, FALLBACK_PROVIDER_KEYS);
|
|
57
|
-
if (cell === null)
|
|
58
|
-
return null;
|
|
59
|
-
if (failedProviders.has(cell.provider))
|
|
60
|
-
return null;
|
|
61
|
-
const toProvider = cell.provider === 'anthropic' ? 'openai' : 'anthropic';
|
|
62
|
-
if (failedProviders.has(toProvider))
|
|
63
|
-
return null;
|
|
64
|
-
return { fromProvider: cell.provider, toProvider, strength: cell.strength, model: ladders[toProvider][cell.strength] };
|
|
65
|
-
}
|
|
66
47
|
/** Back-map a concrete model spec to the provider×strength ladder cell it came
|
|
67
48
|
* from. Two cells may legitimately name the SAME model id at different thinking
|
|
68
49
|
* levels (the openai ladder's `ultra` and `strong` are both gpt-5.6-sol, at
|
|
@@ -147,7 +128,8 @@ function floorReviewModel(kind, requestedModel, kindModel, ladders = modelLadder
|
|
|
147
128
|
* (`ultra`) or bare family alias (`opus`/`sonnet`/`haiku`) has no `/` and
|
|
148
129
|
* stays unpinned. */
|
|
149
130
|
export function isProviderPinnedModelToken(model) {
|
|
150
|
-
|
|
131
|
+
// Logical family/strength refs express a preference, not provider exclusivity.
|
|
132
|
+
return model.includes('/') && !isPortableModelToken(model);
|
|
151
133
|
}
|
|
152
134
|
/** True when `model` is a PORTABLE ladder token — a bare strength/alias
|
|
153
135
|
* (`ultra`, `normal`, `opus`, …) or a `anthropic|openai / strength` pair —
|
|
@@ -165,6 +147,16 @@ export function isPortableModelToken(model) {
|
|
|
165
147
|
}
|
|
166
148
|
return STRENGTH_ALIASES[model] !== undefined || model === 'opus' || model === 'sonnet' || model === 'haiku';
|
|
167
149
|
}
|
|
150
|
+
export function logicalModelIntent(model, ladders = modelLadders()) {
|
|
151
|
+
const strength = modelTokenStrength(model);
|
|
152
|
+
if (strength === null)
|
|
153
|
+
return undefined;
|
|
154
|
+
if (model.includes('/'))
|
|
155
|
+
return { family: model.slice(0, model.indexOf('/')), strength };
|
|
156
|
+
if (model === 'opus' || model === 'sonnet' || model === 'haiku')
|
|
157
|
+
return { family: 'anthropic', strength };
|
|
158
|
+
return { family: defaultProvider(ladders), strength };
|
|
159
|
+
}
|
|
168
160
|
export function normalizeModel(model, ladders = modelLadders()) {
|
|
169
161
|
// 1. Qualified provider/rest.
|
|
170
162
|
if (model.includes('/')) {
|
|
@@ -265,10 +257,12 @@ export function buildLaunchSpec(kind, mode, opts) {
|
|
|
265
257
|
const ladders = merged.modelLadders;
|
|
266
258
|
const kindModel = mode === 'orchestrator' ? kindConfig?.orchestratorModel ?? kindConfig?.model : kindConfig?.model;
|
|
267
259
|
const chosenModel = opts.modelExact === true ? opts.model : floorReviewModel(kind, opts.model, kindModel, ladders);
|
|
260
|
+
const intent = opts.modelExact === true ? undefined : (opts.modelIntent ?? (chosenModel !== undefined ? logicalModelIntent(chosenModel, ladders) : undefined));
|
|
268
261
|
const launch = {
|
|
269
262
|
model: opts.modelExact === true
|
|
270
263
|
? chosenModel
|
|
271
264
|
: (chosenModel !== undefined ? normalizeModel(chosenModel, ladders) : undefined),
|
|
265
|
+
modelIntent: intent,
|
|
272
266
|
modelProviderPinned: opts.modelProviderPinned !== undefined
|
|
273
267
|
? opts.modelProviderPinned
|
|
274
268
|
: opts.model !== undefined && isProviderPinnedModelToken(opts.model),
|
|
@@ -276,7 +270,7 @@ export function buildLaunchSpec(kind, mode, opts) {
|
|
|
276
270
|
tools: kindConfig?.tools ?? [],
|
|
277
271
|
extensions: mergeCanvasExtensions([...CANVAS_EXTENSIONS, ...(kindConfig?.extensions ?? [])]),
|
|
278
272
|
systemPrompt: LAUNCH_SYSTEM_PROMPT,
|
|
279
|
-
env: { ...(opts.extraEnv ?? {}) },
|
|
273
|
+
env: { ...(opts.extraEnv ?? {}), ...(intent ? { CRTR_MODEL_INTENT: `${intent.family}/${intent.strength}` } : {}) },
|
|
280
274
|
};
|
|
281
275
|
return { launch, lifecycle: opts.lifecycle };
|
|
282
276
|
}
|
|
@@ -4,20 +4,6 @@ import type { AssistantMessageDiagnostic } from "@earendil-works/pi-ai";
|
|
|
4
4
|
export declare const MANAGED_PROVIDER_COOLING_DIAGNOSTIC = "crtr:managed-provider-cooling";
|
|
5
5
|
/** Stable error code so a caller can recognize the typed error without message parsing. */
|
|
6
6
|
export declare const MANAGED_PROVIDER_COOLING_CODE = "managed_provider_cooling";
|
|
7
|
-
/**
|
|
8
|
-
* Broker-owned, live policy exposed to the jiti-loaded provider-rotation
|
|
9
|
-
* extension through its UI context. A callback (rather than a boot-time value)
|
|
10
|
-
* is required because an attached viewer can pin or unpin the running broker
|
|
11
|
-
* with `set_model`; the very next provider attempt must observe that decision.
|
|
12
|
-
*/
|
|
13
|
-
export declare const MANAGED_PROVIDER_ROTATION_POLICY_UI: unique symbol;
|
|
14
|
-
export type ManagedProviderRotationPolicy = {
|
|
15
|
-
allowCrossProviderFallback: boolean;
|
|
16
|
-
reason?: "provider-pinned" | "model-exact";
|
|
17
|
-
};
|
|
18
|
-
export type ManagedProviderRotationPolicyControl = () => ManagedProviderRotationPolicy;
|
|
19
|
-
/** Non-broker/test hosts retain the historical permissive fallback policy. */
|
|
20
|
-
export declare function readManagedProviderRotationPolicy(ui: unknown): ManagedProviderRotationPolicy;
|
|
21
7
|
/** Every managed provider pool tried this turn is cooling; `rateLimitedUntil` is
|
|
22
8
|
* the earliest future deadline (epoch ms) across those pools. */
|
|
23
9
|
export declare class ManagedProviderCoolingError extends Error {
|
|
@@ -16,25 +16,6 @@
|
|
|
16
16
|
export const MANAGED_PROVIDER_COOLING_DIAGNOSTIC = "crtr:managed-provider-cooling";
|
|
17
17
|
/** Stable error code so a caller can recognize the typed error without message parsing. */
|
|
18
18
|
export const MANAGED_PROVIDER_COOLING_CODE = "managed_provider_cooling";
|
|
19
|
-
/**
|
|
20
|
-
* Broker-owned, live policy exposed to the jiti-loaded provider-rotation
|
|
21
|
-
* extension through its UI context. A callback (rather than a boot-time value)
|
|
22
|
-
* is required because an attached viewer can pin or unpin the running broker
|
|
23
|
-
* with `set_model`; the very next provider attempt must observe that decision.
|
|
24
|
-
*/
|
|
25
|
-
export const MANAGED_PROVIDER_ROTATION_POLICY_UI = Symbol.for("@crouton-kit/crtr:managed-provider-rotation-policy");
|
|
26
|
-
/** Non-broker/test hosts retain the historical permissive fallback policy. */
|
|
27
|
-
export function readManagedProviderRotationPolicy(ui) {
|
|
28
|
-
const control = ui?.[MANAGED_PROVIDER_ROTATION_POLICY_UI];
|
|
29
|
-
if (typeof control !== "function")
|
|
30
|
-
return { allowCrossProviderFallback: true };
|
|
31
|
-
try {
|
|
32
|
-
return control();
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return { allowCrossProviderFallback: true };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
19
|
/** Every managed provider pool tried this turn is cooling; `rateLimitedUntil` is
|
|
39
20
|
* the earliest future deadline (epoch ms) across those pools. */
|
|
40
21
|
export class ManagedProviderCoolingError extends Error {
|
|
@@ -65,6 +65,12 @@ export function persistDormantModel(nodeId, spec, pinned) {
|
|
|
65
65
|
const meta = getNode(nodeId);
|
|
66
66
|
if (meta === null)
|
|
67
67
|
return;
|
|
68
|
-
const launch = meta.launch
|
|
68
|
+
const launch = meta.launch === undefined
|
|
69
|
+
? undefined
|
|
70
|
+
: (() => {
|
|
71
|
+
const { modelIntent: _modelIntent, env, ...rest } = meta.launch;
|
|
72
|
+
const { CRTR_MODEL_INTENT: _intentEnv, ...remainingEnv } = env;
|
|
73
|
+
return { ...rest, model: spec, modelProviderPinned: pinned, env: remainingEnv };
|
|
74
|
+
})();
|
|
69
75
|
updateNode(nodeId, { model_override: spec, ...(launch !== undefined ? { launch } : {}) });
|
|
70
76
|
}
|
|
@@ -59,6 +59,7 @@ function reshapeNode(nodeId, opts = {}, beforePersist) {
|
|
|
59
59
|
// just re-supplies the current value for resolution, not a new user
|
|
60
60
|
// choice — so carry the node's existing pin verbatim instead.
|
|
61
61
|
modelProviderPinned: opts.model === undefined ? node.launch?.modelProviderPinned : undefined,
|
|
62
|
+
modelIntent: opts.model === undefined ? node.launch?.modelIntent : undefined,
|
|
62
63
|
// Resolve kind/ladder config against the NODE's own cwd + profile, not
|
|
63
64
|
// this process's ambient scope.
|
|
64
65
|
cwd: node.cwd,
|
|
@@ -11,7 +11,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
11
11
|
import { readFileSync, rmSync } from 'node:fs';
|
|
12
12
|
import { homedir } from 'node:os';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
-
import { formatGesture, resolveUserKeybindings, } from '../keybindings/index.js';
|
|
14
|
+
import { BINDING_CATALOG, formatGesture, resolveUserKeybindings, } from '../keybindings/index.js';
|
|
15
15
|
import { bundledPiCommandForShell } from './pi-cli.js';
|
|
16
16
|
import { surfaceTmuxStyleArgs } from './surface-bg.js';
|
|
17
17
|
import { inboxToggleTmuxCommand } from '@crouton-kit/humanloop';
|
|
@@ -470,7 +470,7 @@ export function sendKeysEnter(pane, text) {
|
|
|
470
470
|
// ---------------------------------------------------------------------------
|
|
471
471
|
const TMUX_BINDING_MANIFEST = '@crtr_keybindings_manifest';
|
|
472
472
|
const ROOT_TABLE = 'root';
|
|
473
|
-
const
|
|
473
|
+
const LEAF_MENU_ACTIONS = {
|
|
474
474
|
'crtr.tmux.menu.promote': { description: 'promote to orchestrator', action: { kind: 'keys', keys: '/promote' } },
|
|
475
475
|
'crtr.tmux.menu.resume': { description: 'resume node', action: { kind: 'keys', keys: '/resume-node' } },
|
|
476
476
|
'crtr.tmux.menu.fork': { description: 'fork into idle root', action: { kind: 'direct', run: 'node fork --pane {pane}' } },
|
|
@@ -484,7 +484,6 @@ const MENU_ACTIONS = {
|
|
|
484
484
|
'crtr.tmux.menu.file-review': { description: 'review a file', action: { kind: 'raw-keys', keys: 'M-S-r' } },
|
|
485
485
|
'crtr.tmux.menu.metadata': { description: 'metadata', action: { kind: 'keys', keys: '/node-metadata' } },
|
|
486
486
|
'crtr.tmux.menu.fold-tools': { description: 'fold finished tool calls', action: { kind: 'keys', keys: '/fold-tools' } },
|
|
487
|
-
'crtr.tmux.menu.view-settings': undefined,
|
|
488
487
|
'crtr.tmux.menu.context': { description: 'context + reports', action: { kind: 'keys', keys: '/context' } },
|
|
489
488
|
'crtr.tmux.menu.providers': { description: 'settings', action: { kind: 'popup', run: 'sys setup' } },
|
|
490
489
|
'crtr.tmux.menu.graph': { description: 'graph view', action: { kind: 'keys', keys: '/graph' } },
|
|
@@ -492,16 +491,8 @@ const MENU_ACTIONS = {
|
|
|
492
491
|
'crtr.tmux.menu.copy-id': { description: 'copy node id', action: { kind: 'command', run: 'surface node id --pane {pane}' } },
|
|
493
492
|
'crtr.tmux.menu.reload-viewer': { description: 'reload viewer', action: { kind: 'direct', run: 'surface reload --pane {pane}' } },
|
|
494
493
|
'crtr.tmux.menu.pin': { description: 'pin conversation', action: { kind: 'direct', run: 'surface node pin --pane {pane}' } },
|
|
495
|
-
'crtr.tmux.menu.
|
|
496
|
-
'crtr.tmux.menu.
|
|
497
|
-
'crtr.tmux.menu.issues': undefined,
|
|
498
|
-
'crtr.tmux.menu.issue.idea': undefined,
|
|
499
|
-
'crtr.tmux.menu.issue.bug': undefined,
|
|
500
|
-
'crtr.tmux.node.next': undefined,
|
|
501
|
-
'crtr.tmux.node.previous': undefined,
|
|
502
|
-
'crtr.tmux.inbox.toggle': undefined,
|
|
503
|
-
'crtr.tmux.view.next': undefined,
|
|
504
|
-
'crtr.tmux.view.previous': undefined,
|
|
494
|
+
'crtr.tmux.menu.issue.idea': { description: 'idea', action: { kind: 'popup', run: 'canvas issue new --type idea' } },
|
|
495
|
+
'crtr.tmux.menu.issue.bug': { description: 'bug', action: { kind: 'popup', run: 'canvas issue new --type bug' } },
|
|
505
496
|
};
|
|
506
497
|
function escapeTmuxFormats(value) {
|
|
507
498
|
return value.replace(/#/g, '##');
|
|
@@ -512,34 +503,69 @@ function tmuxCommand(args) {
|
|
|
512
503
|
function chordCommand(run, interactive = false) {
|
|
513
504
|
const command = `crtr canvas chord --pane '#{pane_id}' --run ${shellQuote(escapeTmuxFormats(run))}${interactive ? ' --interactive' : ''}`;
|
|
514
505
|
return interactive
|
|
515
|
-
?
|
|
516
|
-
|
|
506
|
+
? {
|
|
507
|
+
argv: ['display-popup', '-E', '-w', '60%', '-h', '50%', '-d', '#{pane_current_path}', command],
|
|
508
|
+
menu: `display-popup -E -w 60% -h 50% -d "#{pane_current_path}" ${shellQuote(command)}`,
|
|
509
|
+
}
|
|
510
|
+
: {
|
|
511
|
+
argv: ['run-shell', `${command} >/dev/null 2>&1`],
|
|
512
|
+
menu: `run-shell ${shellQuote(`${command} >/dev/null 2>&1`)}`,
|
|
513
|
+
};
|
|
517
514
|
}
|
|
518
|
-
/** Dispatch
|
|
515
|
+
/** Dispatch an action straight to a `crtr` invocation, skipping the
|
|
519
516
|
* `canvas chord` hop entirely. Only sound for an action whose sole
|
|
520
517
|
* interpolation need is `{pane}` — tmux expands it natively as `#{pane_id}` at
|
|
521
|
-
*
|
|
518
|
+
* keypress time (a real tmux format, substituted AFTER escaping the static
|
|
522
519
|
* run text so the `#` here is never doubled), so chord's node-resolution
|
|
523
|
-
* round trip buys nothing. `-b` keeps
|
|
520
|
+
* round trip buys nothing. `-b` keeps dispatch non-blocking. */
|
|
524
521
|
function directCommand(run) {
|
|
525
522
|
const argv = escapeTmuxFormats(run).replace('{pane}', '#{pane_id}');
|
|
526
|
-
|
|
523
|
+
const command = `crtr ${argv} >/dev/null 2>&1`;
|
|
524
|
+
return {
|
|
525
|
+
argv: ['run-shell', '-b', command],
|
|
526
|
+
menu: `run-shell -b ${shellQuote(command)}`,
|
|
527
|
+
};
|
|
527
528
|
}
|
|
528
|
-
|
|
529
|
+
/** One command constructor serves both display-menu selections and direct root
|
|
530
|
+
* shortcuts. Each result carries the argv bind-key needs plus the stable menu
|
|
531
|
+
* serialization existing Alt+C installs already use. */
|
|
532
|
+
function menuActionCommand(action) {
|
|
529
533
|
switch (action.kind) {
|
|
530
|
-
case 'disabled': return
|
|
531
|
-
case 'separator': return
|
|
534
|
+
case 'disabled': return null;
|
|
535
|
+
case 'separator': return null;
|
|
532
536
|
// Choosing any menu item closes it; refresh-client is the deliberately
|
|
533
537
|
// inert command that lets the prefix shortcut choose this close row.
|
|
534
|
-
case 'dismiss': return 'refresh-client';
|
|
535
|
-
case 'keys':
|
|
536
|
-
|
|
538
|
+
case 'dismiss': return { argv: ['refresh-client'], menu: 'refresh-client' };
|
|
539
|
+
case 'keys': {
|
|
540
|
+
const keys = escapeTmuxFormats(action.keys);
|
|
541
|
+
return {
|
|
542
|
+
// A root binding already runs in the pressed key's pane context. Its
|
|
543
|
+
// target must stay implicit: bind-key does not expand `#{pane_id}` in
|
|
544
|
+
// send-keys' target argument. A menu command is serialized later and
|
|
545
|
+
// does need the pane captured when the menu opened.
|
|
546
|
+
argv: ['send-keys', keys, 'Enter'],
|
|
547
|
+
menu: `send-keys -t '#{pane_id}' ${shellQuote(keys)} Enter`,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
case 'raw-keys': {
|
|
551
|
+
const keys = escapeTmuxFormats(action.keys);
|
|
552
|
+
return {
|
|
553
|
+
argv: ['send-keys', keys],
|
|
554
|
+
menu: `send-keys -t '#{pane_id}' ${shellQuote(keys)}`,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
537
557
|
case 'command': return chordCommand(action.run);
|
|
538
558
|
case 'direct': return directCommand(action.run);
|
|
539
559
|
case 'popup': return chordCommand(action.run, true);
|
|
540
|
-
case 'submenu':
|
|
560
|
+
case 'submenu': {
|
|
561
|
+
const argv = buildMenuArgs(action.title, action.items);
|
|
562
|
+
return { argv, menu: tmuxCommand(argv) };
|
|
563
|
+
}
|
|
541
564
|
}
|
|
542
565
|
}
|
|
566
|
+
function renderMenuAction(action) {
|
|
567
|
+
return menuActionCommand(action)?.menu ?? '';
|
|
568
|
+
}
|
|
543
569
|
/** Visible width of a menu item name: style markup (`#[...]`) takes no cells. */
|
|
544
570
|
function menuItemWidth(description) {
|
|
545
571
|
return description.replace(/#\[[^\]]*\]/g, '').length;
|
|
@@ -573,8 +599,12 @@ function tmuxKey(stroke) {
|
|
|
573
599
|
let key = parts.pop();
|
|
574
600
|
const modifiers = new Set(parts);
|
|
575
601
|
if (modifiers.has('shift') && key.length === 1 && /[a-z]/.test(key)) {
|
|
602
|
+
// tmux names a shifted printable with its uppercase character even when an
|
|
603
|
+
// extended-key terminal also preserves Shift as a modifier: Alt+Shift+F is
|
|
604
|
+
// `M-S-F`. Shift by itself needs only the uppercase character (`F`).
|
|
576
605
|
key = key.toUpperCase();
|
|
577
|
-
modifiers.
|
|
606
|
+
if (!modifiers.has('ctrl') && !modifiers.has('alt'))
|
|
607
|
+
modifiers.delete('shift');
|
|
578
608
|
}
|
|
579
609
|
key = TMUX_NAMED_KEYS[key] ?? key;
|
|
580
610
|
const prefix = [
|
|
@@ -589,34 +619,56 @@ function menuSelector(stroke) {
|
|
|
589
619
|
const key = parts.at(-1);
|
|
590
620
|
return parts.includes('shift') && /[a-z]/.test(key) ? key.toUpperCase() : key === 'space' ? ' ' : key;
|
|
591
621
|
}
|
|
592
|
-
function
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
continue;
|
|
599
|
-
for (const gesture of bindings.gestures(id)) {
|
|
600
|
-
items.push({ description: entry.description, selector: menuSelector(gesture), action: entry.action });
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return items;
|
|
604
|
-
};
|
|
605
|
-
const issueItems = [];
|
|
606
|
-
for (const [id, description, type] of [
|
|
607
|
-
['crtr.tmux.menu.issue.idea', 'idea', 'idea'],
|
|
608
|
-
['crtr.tmux.menu.issue.bug', 'bug', 'bug'],
|
|
609
|
-
]) {
|
|
622
|
+
function boundMenuItems(ids, entries, bindings) {
|
|
623
|
+
const items = [];
|
|
624
|
+
for (const id of ids) {
|
|
625
|
+
const entry = entries.get(id);
|
|
626
|
+
if (entry === undefined)
|
|
627
|
+
continue;
|
|
610
628
|
for (const gesture of bindings.gestures(id)) {
|
|
611
|
-
|
|
629
|
+
items.push({ description: entry.description, selector: menuSelector(gesture), action: entry.action });
|
|
612
630
|
}
|
|
613
631
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
632
|
+
return items;
|
|
633
|
+
}
|
|
634
|
+
/** Resolve the complete action descriptor set once. Submenu openers point at
|
|
635
|
+
* the same child MenuItems the prefix menu renders, so their direct global
|
|
636
|
+
* shortcuts cannot drift from the menu path. */
|
|
637
|
+
function resolvedMenuActions(bindings) {
|
|
638
|
+
const entries = new Map();
|
|
639
|
+
for (const [id, entry] of Object.entries(LEAF_MENU_ACTIONS)) {
|
|
640
|
+
if (entry !== undefined)
|
|
641
|
+
entries.set(id, entry);
|
|
642
|
+
}
|
|
643
|
+
const showItems = boundMenuItems([
|
|
644
|
+
'crtr.tmux.menu.metadata',
|
|
645
|
+
'crtr.tmux.menu.context',
|
|
646
|
+
], entries, bindings);
|
|
647
|
+
entries.set('crtr.tmux.menu.show', {
|
|
648
|
+
description: 'show ›',
|
|
649
|
+
action: { kind: 'submenu', title: ' show node ', items: showItems },
|
|
650
|
+
});
|
|
651
|
+
// How this viewer PAINTS the transcript, as opposed to what it shows. Each item
|
|
652
|
+
// is a toggle the viewer applies to its own render state (a `/`-command sent to
|
|
653
|
+
// the pane); a tmux menu is built once and cannot show live state, so the item
|
|
654
|
+
// reads as the action and the viewer answers with a notice.
|
|
655
|
+
const viewSettingsItems = boundMenuItems(['crtr.tmux.menu.fold-tools'], entries, bindings);
|
|
656
|
+
entries.set('crtr.tmux.menu.view-settings', {
|
|
657
|
+
description: 'view settings ›',
|
|
658
|
+
action: { kind: 'submenu', title: ' view settings ', items: viewSettingsItems },
|
|
659
|
+
});
|
|
660
|
+
const issueItems = boundMenuItems([
|
|
661
|
+
'crtr.tmux.menu.issue.idea',
|
|
662
|
+
'crtr.tmux.menu.issue.bug',
|
|
663
|
+
], entries, bindings);
|
|
664
|
+
entries.set('crtr.tmux.menu.issues', {
|
|
665
|
+
description: 'create ›',
|
|
666
|
+
action: { kind: 'submenu', title: ' create ', items: issueItems },
|
|
667
|
+
});
|
|
668
|
+
return entries;
|
|
669
|
+
}
|
|
670
|
+
function menuItems(bindings, entries) {
|
|
671
|
+
const boundItems = (ids) => boundMenuItems(ids, entries, bindings);
|
|
620
672
|
const items = [];
|
|
621
673
|
const addGroup = (heading, groupItems) => {
|
|
622
674
|
if (groupItems.length === 0)
|
|
@@ -625,41 +677,15 @@ function menuItems(bindings) {
|
|
|
625
677
|
items.push({ description: '', selector: '', action: { kind: 'separator' } });
|
|
626
678
|
items.push({ description: `-#[bold]${heading}`, selector: '', action: { kind: 'disabled' } }, ...groupItems);
|
|
627
679
|
};
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
]);
|
|
632
|
-
// How this viewer PAINTS the transcript, as opposed to what it shows. Each item
|
|
633
|
-
// is a toggle the viewer applies to its own render state (a `/`-command sent to
|
|
634
|
-
// the pane); a tmux menu is built once and cannot show live state, so the item
|
|
635
|
-
// reads as the action and the viewer answers with a notice.
|
|
636
|
-
const viewSettingsItems = boundItems(['crtr.tmux.menu.fold-tools']);
|
|
637
|
-
const exploreItems = boundItems([
|
|
680
|
+
// Explore changes what is in view or opens a canvas-level browser. Current
|
|
681
|
+
// node acts on the agent in this pane without changing its lifecycle.
|
|
682
|
+
addGroup('Explore', boundItems([
|
|
638
683
|
'crtr.tmux.menu.resume',
|
|
639
684
|
'crtr.tmux.menu.graph',
|
|
640
685
|
'crtr.tmux.menu.focus-manager',
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
exploreItems.push({
|
|
645
|
-
description: 'show ›',
|
|
646
|
-
selector: menuSelector(gesture),
|
|
647
|
-
action: { kind: 'submenu', title: ' show node ', items: showItems },
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
if (viewSettingsItems.length > 0) {
|
|
652
|
-
for (const gesture of bindings.gestures('crtr.tmux.menu.view-settings')) {
|
|
653
|
-
exploreItems.push({
|
|
654
|
-
description: 'view settings ›',
|
|
655
|
-
selector: menuSelector(gesture),
|
|
656
|
-
action: { kind: 'submenu', title: ' view settings ', items: viewSettingsItems },
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
// Explore changes what is in view or opens a canvas-level browser. Current
|
|
661
|
-
// node acts on the agent in this pane without changing its lifecycle.
|
|
662
|
-
addGroup('Explore', exploreItems);
|
|
686
|
+
'crtr.tmux.menu.show',
|
|
687
|
+
'crtr.tmux.menu.view-settings',
|
|
688
|
+
]));
|
|
663
689
|
addGroup('Current node', boundItems([
|
|
664
690
|
'crtr.tmux.menu.background-bash',
|
|
665
691
|
'crtr.tmux.menu.file-review',
|
|
@@ -667,21 +693,17 @@ function menuItems(bindings) {
|
|
|
667
693
|
'crtr.tmux.menu.copy-id',
|
|
668
694
|
'crtr.tmux.menu.reload-viewer',
|
|
669
695
|
]));
|
|
670
|
-
addGroup('Lifecycle', [
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
addGroup('System', systemItems);
|
|
696
|
+
addGroup('Lifecycle', boundItems([
|
|
697
|
+
'crtr.tmux.menu.fork',
|
|
698
|
+
'crtr.tmux.menu.promote',
|
|
699
|
+
'crtr.tmux.menu.demote',
|
|
700
|
+
'crtr.tmux.menu.detach',
|
|
701
|
+
'crtr.tmux.menu.close-subtree',
|
|
702
|
+
]));
|
|
703
|
+
addGroup('System', boundItems([
|
|
704
|
+
'crtr.tmux.menu.providers',
|
|
705
|
+
'crtr.tmux.menu.issues',
|
|
706
|
+
]));
|
|
685
707
|
const prefixGestures = bindings.gestures('crtr.tmux.menu.open');
|
|
686
708
|
if (prefixGestures.length > 0 && items.length > 0) {
|
|
687
709
|
items.push({ description: '', selector: '', action: { kind: 'separator' } });
|
|
@@ -835,10 +857,24 @@ export function installTmuxBindings(bindings = resolveUserKeybindings()) {
|
|
|
835
857
|
installed.push({ table, key });
|
|
836
858
|
return true;
|
|
837
859
|
};
|
|
838
|
-
const
|
|
860
|
+
const menuActions = resolvedMenuActions(bindings);
|
|
861
|
+
const menuCommand = buildMenuArgs(' crtr ', menuItems(bindings, menuActions));
|
|
839
862
|
for (const gesture of bindings.gestures('crtr.tmux.menu.open')) {
|
|
840
863
|
install(ROOT_TABLE, tmuxKey(gesture), menuCommand, 'crtr.tmux.menu.open', gesture);
|
|
841
864
|
}
|
|
865
|
+
for (const definition of BINDING_CATALOG) {
|
|
866
|
+
if (definition.pair?.field !== 'menu')
|
|
867
|
+
continue;
|
|
868
|
+
const menuId = definition.id;
|
|
869
|
+
const globalId = definition.pair.pairedId;
|
|
870
|
+
const entry = menuActions.get(menuId);
|
|
871
|
+
const command = entry === undefined ? null : menuActionCommand(entry.action);
|
|
872
|
+
if (command === null)
|
|
873
|
+
throw new Error(`no direct tmux action for paired menu binding ${menuId}`);
|
|
874
|
+
for (const gesture of bindings.gestures(globalId)) {
|
|
875
|
+
install(ROOT_TABLE, tmuxKey(gesture), command.argv, globalId, gesture);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
842
878
|
for (const id of ['crtr.tmux.node.next', 'crtr.tmux.node.previous', 'crtr.tmux.inbox.toggle']) {
|
|
843
879
|
for (const gesture of bindings.gestures(id))
|
|
844
880
|
install(ROOT_TABLE, tmuxKey(gesture), actionCommand(id), id, gesture);
|
package/dist/core/scope.js
CHANGED
|
@@ -87,11 +87,10 @@ export function findProjectScopeRoot(startDir = process.cwd()) {
|
|
|
87
87
|
* nested under a directory tree that itself contains a `.crouter/` (e.g. a
|
|
88
88
|
* mock-VM home under the real host home) would sweep the host's `~/.crouter`
|
|
89
89
|
* (and any other above-HOME `.crouter`) in as a bogus project ancestor,
|
|
90
|
-
* leaking one machine's user-global memory into a nested-HOME node.
|
|
91
|
-
*
|
|
92
|
-
* whose start dir is outside HOME (e.g. a production VM
|
|
93
|
-
* ancestor `.crouter`)
|
|
94
|
-
* before. */
|
|
90
|
+
* leaking one machine's user-global memory into a nested-HOME node. The
|
|
91
|
+
* explicit file-context store discovery in substrate/on-read.ts applies the
|
|
92
|
+
* same fence. A read whose start dir is outside HOME (e.g. a production VM
|
|
93
|
+
* with HOME=/root and no ancestor `.crouter`) walks to the filesystem root. */
|
|
95
94
|
function collectAncestorScopeRoots(startDir, userRoot) {
|
|
96
95
|
const roots = [];
|
|
97
96
|
const home = realpathOrSelf(homedir());
|