@timurproko/a1 0.2.7-dev.788 → 0.2.7-dev.792
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/app/session-shell/session-shell-root.d.ts +0 -1
- package/dist/app/session-shell/session-shell-root.js +41 -15
- package/dist/app/session-shell/session-shell.js +5 -4
- package/dist/app/session-shell/session-viewport-controller.d.ts +2 -2
- package/dist/app/session-shell/session-viewport-controller.js +24 -12
- package/dist/composition/owned-ui.d.ts +1 -0
- package/dist/composition/owned-ui.js +27 -2
- package/dist/contracts/owned-ui/index.d.ts +1 -1
- package/dist/contracts/owned-ui/model.d.ts +3 -0
- package/dist/features/owned-ui/settings-app.js +9 -6
- package/dist/features/resident-tab/index.d.ts +3 -1
- package/dist/features/resident-tab/index.js +5 -1
- package/dist/features/resident-tab/shortcut-bindings-file.d.ts +8 -0
- package/dist/features/resident-tab/shortcut-bindings-file.js +40 -0
- package/dist/features/resident-tab/{shortcuts.d.ts → shortcut-declarations.d.ts} +5 -2
- package/dist/features/resident-tab/{shortcuts.js → shortcut-declarations.js} +5 -17
- package/dist/features/resident-tab/shortcut-projection.d.ts +29 -0
- package/dist/features/resident-tab/shortcut-projection.js +64 -0
- package/dist/foundation/release/bootstrap.js +1 -0
- package/dist/foundation/resident-tabs/bridge-client.d.ts +5 -0
- package/dist/foundation/resident-tabs/bridge-client.js +17 -1
- package/dist/foundation/resident-tabs/prompt-journal.d.ts +0 -2
- package/dist/foundation/resident-tabs/prompt-journal.js +0 -1
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +13 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +1 -0
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +1 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +10 -30
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/manifest.json +3 -2
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-arm64/terminal-host.manifest.json +7 -6
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/manifest.json +5 -4
- package/dist/native/darwin-x64/process-guardian +0 -0
- package/dist/native/darwin-x64/terminal-host +0 -0
- package/dist/native/darwin-x64/terminal-host.manifest.json +7 -6
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/manifest.json +3 -2
- package/dist/native/linux-x64/terminal-host +0 -0
- package/dist/native/linux-x64/terminal-host.manifest.json +7 -6
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/manifest.json +4 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/native/win32-x64/terminal-host.exe +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +6 -5
- package/dist/ui/components/index.d.ts +2 -2
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/selection-copy.d.ts +2 -0
- package/dist/ui/components/selection-copy.js +13 -0
- package/dist/ui/components/submitted-prompt.d.ts +7 -4
- package/dist/ui/components/submitted-prompt.js +18 -10
- package/dist/ui/components/transcript-viewport.d.ts +10 -6
- package/dist/ui/components/transcript-viewport.js +28 -13
- package/dist/ui/settings/declarations.d.ts +13 -1
- package/dist/ui/settings/declarations.js +12 -1
- package/dist/ui/settings/migrations.js +7 -0
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +1 -1
- package/docs/architecture/process-guardian-provenance.md +1 -1
- package/docs/architecture/project-structure.md +8 -0
- package/docs/architecture/resident-tabs.md +18 -2
- package/docs/architecture/terminal-host-provenance.md +1 -1
- package/docs/architecture/toolchain.md +1 -1
- package/docs/validation.md +3 -3
- package/package.json +1 -1
|
@@ -96,9 +96,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
96
96
|
...(handlers.promptImageLimit === undefined ? {} : { imageLimit: handlers.promptImageLimit }) });
|
|
97
97
|
this.#submittedPromptComposer = this.#customViewport
|
|
98
98
|
? {
|
|
99
|
-
layout: submittedPromptLayout,
|
|
100
|
-
compose: (rows, width, source, style) => composeSubmittedPromptRows(rows, width, source, { ...style, prefix: text => promptArrow(text, piTheme()) })
|
|
101
|
-
.map(row => nativeHyperlinkStyle(row, nativeTranscriptLinkColor)),
|
|
99
|
+
layout: (width, source) => submittedPromptLayout(width, source, this.#viewportController.config.timeFormat ?? "8:00 PM"),
|
|
100
|
+
compose: (rows, width, source, style) => composeSubmittedPromptRows(rows, width, source, { ...style, prefix: text => promptArrow(text, piTheme()) }, this.#viewportController.config.timeFormat ?? "8:00 PM").map(row => nativeHyperlinkStyle(row, nativeTranscriptLinkColor)),
|
|
102
101
|
}
|
|
103
102
|
: undefined;
|
|
104
103
|
this.#cwd = cwd;
|
|
@@ -430,6 +429,23 @@ export class OwnedUiSessionShellRoot {
|
|
|
430
429
|
const selectableDocumentRowCount = document.rows.length;
|
|
431
430
|
const dockStartRow = height - dockRows.length + 1;
|
|
432
431
|
const editorOffset = dock.editorOffset;
|
|
432
|
+
// Rationale: rendered chip tags are lossy presentation (truncated labels), so a frame drag over
|
|
433
|
+
// a draft holding chips stays visual-only; editor-owned word/line selection still copies the
|
|
434
|
+
// expanded chip text through its semantic route.
|
|
435
|
+
const draft = this.usesDefaultInputSurface() ? this.editor.getText() : "";
|
|
436
|
+
const promptCopyRows = draft.length > 0
|
|
437
|
+
&& !draft.split("\n").some(line => this.#promptChips.atomicRanges(line).length > 0)
|
|
438
|
+
? Math.max(0, dock.inputRows - 2)
|
|
439
|
+
: 0;
|
|
440
|
+
const copyableDockRows = [
|
|
441
|
+
...Array.from({ length: promptCopyRows }, (_value, index) => ({
|
|
442
|
+
row: editorOffset + index + 1,
|
|
443
|
+
prompt: index === 0 ? "first" : "continuation",
|
|
444
|
+
})),
|
|
445
|
+
...Array.from({ length: dock.footerRows }, (_value, index) => ({
|
|
446
|
+
row: dockRows.length - dock.footerRows + index,
|
|
447
|
+
})),
|
|
448
|
+
];
|
|
433
449
|
this.#viewportController.setInputSurfaceFrame(this.usesDefaultInputSurface() ? undefined : {
|
|
434
450
|
component: this.#inputSurface,
|
|
435
451
|
columnStart: 1,
|
|
@@ -458,7 +474,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
458
474
|
&& snapshot.inputSurface === inputSurface
|
|
459
475
|
&& snapshot.viewportRevision === viewportRevision
|
|
460
476
|
&& snapshot.selectionRevision === selectionRevision
|
|
461
|
-
? this.#viewportController.composeDockOnly(dockRows, width, height,
|
|
477
|
+
? this.#viewportController.composeDockOnly(dockRows, width, height, copyableDockRows)
|
|
462
478
|
: null;
|
|
463
479
|
if (frame === null) {
|
|
464
480
|
frame = this.#viewportController.compose({
|
|
@@ -474,7 +490,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
474
490
|
...(document.liveTailStartRow === undefined ? {} : { liveTailStartRow: document.liveTailStartRow }),
|
|
475
491
|
bottomAlignedTailRowCount: steeringRows.length + statusRows.length,
|
|
476
492
|
dockRows,
|
|
477
|
-
|
|
493
|
+
copyableDockRows,
|
|
478
494
|
promptAnchors: document.promptAnchors,
|
|
479
495
|
width,
|
|
480
496
|
height,
|
|
@@ -541,8 +557,11 @@ export class OwnedUiSessionShellRoot {
|
|
|
541
557
|
return this.#viewportController.hasSelection || this.editor.hasSelection();
|
|
542
558
|
}
|
|
543
559
|
setViewportConfig(config) {
|
|
544
|
-
if (this.#viewportController.setConfig(config))
|
|
560
|
+
if (this.#viewportController.setConfig(config)) {
|
|
561
|
+
this.#renderedRows.clear();
|
|
545
562
|
this.#documentLayouts.clear();
|
|
563
|
+
this.#visibleViewportSnapshot = undefined;
|
|
564
|
+
}
|
|
546
565
|
}
|
|
547
566
|
setFullscreenCopyOnSelect(enabled) {
|
|
548
567
|
this.#viewportController.setCopyOnSelect(enabled);
|
|
@@ -751,11 +770,13 @@ export class OwnedUiSessionShellRoot {
|
|
|
751
770
|
}
|
|
752
771
|
rows.push(...blockRows);
|
|
753
772
|
if (promptLike && blockRows[0] !== undefined) {
|
|
773
|
+
const source = pinnedPromptSource(block, blockRows[0], blockWidth, this.#viewportController.config.timeFormat);
|
|
754
774
|
promptAnchors.push({
|
|
755
775
|
id: block.id,
|
|
756
776
|
firstRow,
|
|
757
777
|
lastRow: Math.max(firstRow, rows.length - 1),
|
|
758
|
-
sourceRow:
|
|
778
|
+
sourceRow: source.row,
|
|
779
|
+
copySuffixWidth: source.copySuffixWidth,
|
|
759
780
|
prominentSourceRow: blockRows[0],
|
|
760
781
|
});
|
|
761
782
|
}
|
|
@@ -1322,20 +1343,25 @@ function withoutTerminalBackground(text) {
|
|
|
1322
1343
|
return text.replace(TERMINAL_BACKGROUND, "");
|
|
1323
1344
|
}
|
|
1324
1345
|
/** Preserve the stable metadata-grey foreground for every pinned timestamp state. */
|
|
1325
|
-
function
|
|
1346
|
+
function pinnedPromptSource(block, sourceRow, width, timeFormat) {
|
|
1347
|
+
const unchanged = { row: sourceRow, copySuffixWidth: 0 };
|
|
1326
1348
|
if (typeof block.payload !== "object" || block.payload === null)
|
|
1327
|
-
return
|
|
1349
|
+
return unchanged;
|
|
1328
1350
|
const value = block.payload.timestamp;
|
|
1329
1351
|
if (typeof value !== "number" || !Number.isFinite(value))
|
|
1330
|
-
return
|
|
1331
|
-
const
|
|
1332
|
-
|
|
1333
|
-
|
|
1352
|
+
return unchanged;
|
|
1353
|
+
const format = timeFormat ?? "8:00 PM";
|
|
1354
|
+
const timestamp = formatSubmittedPromptTime(value, format);
|
|
1355
|
+
if (timestamp === null || submittedPromptLayout(width, value, format).timestamp === null)
|
|
1356
|
+
return unchanged;
|
|
1334
1357
|
const rowWidth = displayWidth(sourceRow);
|
|
1335
1358
|
const timestampWidth = displayWidth(timestamp);
|
|
1336
1359
|
if (rowWidth < timestampWidth)
|
|
1337
|
-
return
|
|
1338
|
-
return
|
|
1360
|
+
return unchanged;
|
|
1361
|
+
return {
|
|
1362
|
+
row: overlaySpan(sourceRow, rowWidth - timestampWidth, rowWidth, paintPiSubmittedPromptTimestamp(timestamp)),
|
|
1363
|
+
copySuffixWidth: timestampWidth,
|
|
1364
|
+
};
|
|
1339
1365
|
}
|
|
1340
1366
|
/**
|
|
1341
1367
|
* The pinned-profile screen: a followed transcript scroll above the dock. The runtime mounts one such layout,
|
|
@@ -270,7 +270,7 @@ export class OwnedUiSessionPresenter {
|
|
|
270
270
|
// Invariant: the presenter owns its root (theme subscription and helper clients) and disposes it once the
|
|
271
271
|
// terminal no longer renders it.
|
|
272
272
|
constructed.push(() => this.root.dispose());
|
|
273
|
-
this.#restoreResidentInput();
|
|
273
|
+
this.#restoreResidentInput(options.history?.imageSidecar);
|
|
274
274
|
const initialPiSettings = this.backend.settings.snapshot();
|
|
275
275
|
this.#programStatus = new ProgramStatusReporter(status => this.#terminal.setProgramStatus(status), () => this.view().status.footer?.sessionName ?? undefined);
|
|
276
276
|
this.#reloadPresentation = reloadPresentation;
|
|
@@ -802,7 +802,6 @@ export class OwnedUiSessionPresenter {
|
|
|
802
802
|
sessionFile: this.#currentSessionFile(),
|
|
803
803
|
kind: type,
|
|
804
804
|
text: input,
|
|
805
|
-
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
806
805
|
});
|
|
807
806
|
}
|
|
808
807
|
catch {
|
|
@@ -1213,11 +1212,13 @@ export class OwnedUiSessionPresenter {
|
|
|
1213
1212
|
this.#resolveStoppedLifecycle();
|
|
1214
1213
|
}
|
|
1215
1214
|
}
|
|
1216
|
-
#restoreResidentInput() {
|
|
1215
|
+
#restoreResidentInput(imageSidecar) {
|
|
1217
1216
|
const recovered = this.#residentTabOptions?.recovered;
|
|
1218
1217
|
if (recovered === undefined)
|
|
1219
1218
|
return;
|
|
1220
|
-
const prompts = recovered.prompts
|
|
1219
|
+
const prompts = recovered.prompts
|
|
1220
|
+
.map(prompt => this.root.rehydrateHistoryText(prompt.text, id => imageSidecar?.readAttachment(id) ?? null))
|
|
1221
|
+
.filter(Boolean);
|
|
1221
1222
|
if (prompts.length > 0) {
|
|
1222
1223
|
this.root.editor.setText(prompts[0]);
|
|
1223
1224
|
if (recovered.draft)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PiTuiPointerSurface } from "../../integrations/pi/tui-runtime/contracts.js";
|
|
2
2
|
import type { OwnedUiViewportSettings } from "../../contracts/owned-ui/index.js";
|
|
3
3
|
import { type TranscriptViewportFrame, type TranscriptViewportFrameInput } from "../../ui/components/transcript-viewport.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type SelectionCopySnapshot } from "../../ui/components/selection-copy.js";
|
|
5
5
|
import type { PiShellEditorPort } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
6
6
|
import type { PasteEvent } from "./paste-protocol.js";
|
|
7
7
|
export interface SessionViewportControllerOptions {
|
|
@@ -64,7 +64,7 @@ export declare class SessionViewportController {
|
|
|
64
64
|
setOverlaySurfaces(surfaces: readonly PiTuiPointerSurface[] | null): void;
|
|
65
65
|
get inputGeometryReady(): boolean;
|
|
66
66
|
compose(input: Omit<TranscriptViewportFrameInput, "pointerPosition">): TranscriptViewportFrame;
|
|
67
|
-
composeDockOnly(dockRows: readonly string[], width: number, height: number,
|
|
67
|
+
composeDockOnly(dockRows: readonly string[], width: number, height: number, copyableDockRows?: TranscriptViewportFrameInput["copyableDockRows"]): TranscriptViewportFrame | null;
|
|
68
68
|
/** Returns true when document wrapping may have changed. */
|
|
69
69
|
setConfig(config: OwnedUiViewportSettings): boolean;
|
|
70
70
|
resumeFollowing(): void;
|
|
@@ -3,6 +3,7 @@ import { TranscriptViewport } from "../../ui/components/transcript-viewport.js";
|
|
|
3
3
|
import { readVisibleHyperlinks } from "../../ui/components/visible-hyperlinks.js";
|
|
4
4
|
import { routeMouseInput } from "../../ui/components/mouse.js";
|
|
5
5
|
import { scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows } from "../../ui/components/scrollbar.js";
|
|
6
|
+
import { literalSelectionCopySnapshot } from "../../ui/components/selection-copy.js";
|
|
6
7
|
function withinTailControl(region, column, row) {
|
|
7
8
|
return row >= region.rowStart && row <= region.rowEnd && column >= region.columnStart && column <= region.columnEnd;
|
|
8
9
|
}
|
|
@@ -22,6 +23,7 @@ export class SessionViewportController {
|
|
|
22
23
|
#pasteDiagnosticRequest = 0;
|
|
23
24
|
#viewport = new TranscriptViewport();
|
|
24
25
|
#config = {
|
|
26
|
+
timeFormat: "8:00 PM",
|
|
25
27
|
scrollbarAppearance: "auto",
|
|
26
28
|
scrollbarStyle: "thin",
|
|
27
29
|
scrollbarSpeed: "normal",
|
|
@@ -165,16 +167,18 @@ export class SessionViewportController {
|
|
|
165
167
|
}
|
|
166
168
|
return frame;
|
|
167
169
|
}
|
|
168
|
-
composeDockOnly(dockRows, width, height,
|
|
169
|
-
return this.#viewport.composeDockOnly(dockRows, width, height,
|
|
170
|
+
composeDockOnly(dockRows, width, height, copyableDockRows = []) {
|
|
171
|
+
return this.#viewport.composeDockOnly(dockRows, width, height, copyableDockRows);
|
|
170
172
|
}
|
|
171
173
|
/** Returns true when document wrapping may have changed. */
|
|
172
174
|
setConfig(config) {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
const normalized = { ...config, timeFormat: config.timeFormat ?? "8:00 PM" };
|
|
176
|
+
const layoutChanged = normalized.scrollbarAppearance !== this.#config.scrollbarAppearance
|
|
177
|
+
|| normalized.timeFormat !== this.#config.timeFormat;
|
|
178
|
+
this.#config = normalized;
|
|
179
|
+
this.#viewport.setConfig(normalized);
|
|
176
180
|
this.#requestRender();
|
|
177
|
-
return
|
|
181
|
+
return layoutChanged;
|
|
178
182
|
}
|
|
179
183
|
resumeFollowing() {
|
|
180
184
|
if (!this.#enabled)
|
|
@@ -302,12 +306,10 @@ export class SessionViewportController {
|
|
|
302
306
|
});
|
|
303
307
|
}
|
|
304
308
|
if (data === "\u0003") {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
else if (this.#viewport.hasSelection) {
|
|
310
|
-
const copySelection = this.#viewport.captureSelectedText("explicit");
|
|
309
|
+
// Invariant: the newest complete-frame gesture owns explicit copy even if an
|
|
310
|
+
// older editor word/line selection remains painted beneath it.
|
|
311
|
+
if (this.#viewport.hasSelection) {
|
|
312
|
+
const copySelection = this.#viewport.captureSelectedText();
|
|
311
313
|
this.#viewport.clearSelection();
|
|
312
314
|
this.#stopSelectionAutoScroll();
|
|
313
315
|
this.#requestRender();
|
|
@@ -315,6 +317,10 @@ export class SessionViewportController {
|
|
|
315
317
|
? { data: "", consumed: true }
|
|
316
318
|
: { data: "", consumed: true, copySelection };
|
|
317
319
|
}
|
|
320
|
+
if (editorActive && this.#editor.hasSelection()) {
|
|
321
|
+
if (this.#viewport.clearSelection())
|
|
322
|
+
this.#requestRender();
|
|
323
|
+
}
|
|
318
324
|
}
|
|
319
325
|
const previousPrompt = SHIFT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
|
|
320
326
|
if (editorActive && allowWheel && (previousPrompt || SHIFT_DOWN_INPUTS.has(data))) {
|
|
@@ -541,6 +547,12 @@ export class SessionViewportController {
|
|
|
541
547
|
const editorRow = pendingEditorClick.row - this.#editorPointerFrame.rowStart + 1;
|
|
542
548
|
this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
543
549
|
this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
550
|
+
if (this.#copyOnSelect) {
|
|
551
|
+
const selected = this.#editor.selectedText?.();
|
|
552
|
+
if (selected !== undefined) {
|
|
553
|
+
completedCopy = literalSelectionCopySnapshot(selected, this.#viewport.selectionRevision) ?? undefined;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
544
556
|
}
|
|
545
557
|
forceRepaint = true;
|
|
546
558
|
repaint = true;
|
|
@@ -88,3 +88,4 @@ export declare function composeOwnedUi(options?: OwnedUiCompositionOptions): Pro
|
|
|
88
88
|
export declare function bindResidentTabBridge(tabBridge: TabBridgeClient, backend: Pick<ComposedSessionBackend, "identity" | "session" | "workflows" | "generateResidentTabName">, recovery?: Pick<ResidentTabRecovery, "settled"> | null, options?: {
|
|
89
89
|
readonly autoName?: boolean;
|
|
90
90
|
}): Promise<() => void>;
|
|
91
|
+
export declare function correlateResidentTabSubmission(recovery: Pick<ResidentTabRecovery, "correlated" | "settled">, backend: Pick<ComposedSessionBackend, "identity" | "session">, submissionId: string, entryId: string): Promise<void>;
|
|
@@ -12,6 +12,8 @@ import { createPiSessionPresenters } from "../integrations/pi/session-presenters
|
|
|
12
12
|
import { ClipboardDiagnosticCapture } from "../app/session-shell/clipboard-diagnostics.js";
|
|
13
13
|
import { createOwnedUiClipboardServices } from "../app/session-shell/clipboard-services.js";
|
|
14
14
|
import { createPiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
|
|
15
|
+
import { projectResidentTabShortcuts } from "../features/resident-tab/shortcut-projection.js";
|
|
16
|
+
import { readShortcutBindingsFile } from "../features/resident-tab/shortcut-bindings-file.js";
|
|
15
17
|
import { OwnedUiSessionPresenter, OwnedUiTerminalHost, sessionTerminalHostOptions, } from "../app/session-shell/session-shell.js";
|
|
16
18
|
import { OwnedSettingsManager } from "../ui/settings/manager.js";
|
|
17
19
|
import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentation-adapter.js";
|
|
@@ -254,7 +256,18 @@ export async function composeOwnedUi(options = {}) {
|
|
|
254
256
|
? new ClipboardDiagnosticCapture(clipboardDestination) : null;
|
|
255
257
|
// Invariant: process-wide services are created once here and borrowed by every session shell. The
|
|
256
258
|
// keybinding registry is applied before any presenter is built, so none of them reads Pi's defaults.
|
|
257
|
-
const
|
|
259
|
+
const keybindingsHost = createPiKeybindingsHost({ profile: ownedSurfaces ? "a1" : "pi", agentDir: backend.identity.agentDir });
|
|
260
|
+
// Invariant: the resident host dispatches and labels the tab shortcuts this child projects
|
|
261
|
+
// from its effective declarations, so a customization reload republishes the projection.
|
|
262
|
+
const publishTabShortcuts = () => tabBridge?.shortcuts(projectResidentTabShortcuts({
|
|
263
|
+
configured: readShortcutBindingsFile(backend.identity.agentDir),
|
|
264
|
+
reserved: keybindingsHost.manager.getEffectiveConfig(),
|
|
265
|
+
}).assignments);
|
|
266
|
+
const keybindings = tabBridge === null ? keybindingsHost : {
|
|
267
|
+
...keybindingsHost,
|
|
268
|
+
reload: () => { keybindingsHost.reload(); publishTabShortcuts(); },
|
|
269
|
+
};
|
|
270
|
+
publishTabShortcuts();
|
|
258
271
|
const clipboardServices = ownedSurfaces ? createOwnedUiClipboardServices() : null;
|
|
259
272
|
let createdTerminalHost;
|
|
260
273
|
let terminalHost;
|
|
@@ -284,7 +297,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
284
297
|
stallNoticeSeconds: 300,
|
|
285
298
|
admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
|
|
286
299
|
dispatched: () => tabRecovery.dispatched(),
|
|
287
|
-
correlated: (submissionId, entryId) => tabRecovery
|
|
300
|
+
correlated: (submissionId, entryId) => correlateResidentTabSubmission(tabRecovery, backend, submissionId, entryId),
|
|
288
301
|
checkpoint: text => tabRecovery.checkpoint(text),
|
|
289
302
|
gracefulStop: sessionFile => tabRecovery.gracefulStop(sessionFile),
|
|
290
303
|
...(recoveredTabInput === undefined ? {} : { recovered: recoveredTabInput }),
|
|
@@ -463,6 +476,9 @@ export async function bindResidentTabBridge(tabBridge, backend, recovery = null,
|
|
|
463
476
|
if (command.type === "rename") {
|
|
464
477
|
void backend.workflows.executeWorkflow({ command: "name", argument: command.name }).catch(() => { });
|
|
465
478
|
}
|
|
479
|
+
else if (command.type === "route") {
|
|
480
|
+
void backend.workflows.executeWorkflow({ command: command.route, argument: "" }).catch(() => { });
|
|
481
|
+
}
|
|
466
482
|
else if (command.type === "stop") {
|
|
467
483
|
void backend.session.dispose().catch(() => { });
|
|
468
484
|
}
|
|
@@ -502,6 +518,14 @@ export async function bindResidentTabBridge(tabBridge, backend, recovery = null,
|
|
|
502
518
|
unsubscribeEvents();
|
|
503
519
|
};
|
|
504
520
|
}
|
|
521
|
+
export async function correlateResidentTabSubmission(recovery, backend, submissionId, entryId) {
|
|
522
|
+
await recovery.correlated(submissionId, entryId);
|
|
523
|
+
// Concurrency: a normal prompt command can resolve after the ready transition already fired.
|
|
524
|
+
// Re-read lifecycle only after correlation is active; a concurrent ready observer and this path
|
|
525
|
+
// safely race because recovery consumes the active entry once and retirement is idempotent.
|
|
526
|
+
if (backend.session.view().lifecycle !== "busy")
|
|
527
|
+
await recovery.settled(currentBackendSessionFile(backend));
|
|
528
|
+
}
|
|
505
529
|
function currentBackendSessionFile(backend) {
|
|
506
530
|
const direct = backend.identity.currentSessionFile?.();
|
|
507
531
|
if (direct !== undefined && direct !== null)
|
|
@@ -523,6 +547,7 @@ function quitOutroSettingsSnapshot(settings) {
|
|
|
523
547
|
}
|
|
524
548
|
function viewportSettingsSnapshot(settings) {
|
|
525
549
|
return {
|
|
550
|
+
timeFormat: settings.value("timeFormat"),
|
|
526
551
|
scrollbarAppearance: settings.value("scrollbarAppearance"),
|
|
527
552
|
scrollbarStyle: settings.value("scrollbarStyle"),
|
|
528
553
|
scrollbarSpeed: settings.value("scrollbarSpeed"),
|
|
@@ -3,7 +3,7 @@ export type UiAccentColor = "purple" | "blue" | "cyan" | "green" | "orange" | "p
|
|
|
3
3
|
export { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiExtensionUiPort, } from "./extension-ui.js";
|
|
4
4
|
export type { OwnedUiExtensionAutocompleteItem, OwnedUiExtensionAutocompleteProvider, OwnedUiExtensionAutocompleteProviderFactory, OwnedUiExtensionAutocompleteSuggestions, OwnedUiExtensionComponent, OwnedUiExtensionComponentFactory, OwnedUiExtensionComponentFactoryContext, OwnedUiExtensionCustomFactory, OwnedUiExtensionCustomOptions, OwnedUiExtensionDialogOptions, OwnedUiExtensionEditorComponent, OwnedUiExtensionEditorFactory, OwnedUiExtensionEditorThemePort, OwnedUiExtensionEntryRenderInput, OwnedUiExtensionFooterDataPort, OwnedUiExtensionFooterFactory, OwnedUiExtensionInputListener, OwnedUiExtensionKeybindingsPort, OwnedUiExtensionMessageRenderInput, OwnedUiExtensionNotificationSeverity, OwnedUiExtensionOverlayAnchor, OwnedUiExtensionOverlayHandle, OwnedUiExtensionOverlayMargin, OwnedUiExtensionOverlayOptions, OwnedUiExtensionRenderCallback, OwnedUiExtensionRendererPort, OwnedUiExtensionRuntimePort, OwnedUiExtensionSelectListThemePort, OwnedUiExtensionSizeValue, OwnedUiExtensionTerminalPort, OwnedUiExtensionThemeBackground, OwnedUiExtensionThemeColor, OwnedUiExtensionThemeInfo, OwnedUiExtensionThemePort, OwnedUiExtensionToolRenderContext, OwnedUiExtensionUiCallback, OwnedUiExtensionUiPort, OwnedUiExtensionUiProperty, OwnedUiExtensionWidgetOptions, OwnedUiExtensionWidgetPlacement, OwnedUiExtensionWorkingIndicatorOptions, } from "./extension-ui.js";
|
|
5
5
|
export { OWNED_UI_CONTRACT_VERSION } from "./model.js";
|
|
6
|
-
export type { OwnedUiBackgroundSettingsPort, OwnedUiBackgroundStyle, OwnedUiBlockStatus, OwnedUiCommand, OwnedUiCommandOutcome, OwnedUiCorrelationId, OwnedUiCustomization, OwnedUiDiagnostics, OwnedUiDialog, OwnedUiEditorState, OwnedUiEntityId, OwnedUiEvent, OwnedUiFooterView, OwnedUiImageAttachment, OwnedUiLifecycle, OwnedUiModelInfo, OwnedUiOverlay, OwnedUiPromptSuggestionGeneratorPort, OwnedUiPromptSuggestionIdentity, OwnedUiPromptSuggestionReasoning, OwnedUiPromptSuggestionRequest, OwnedUiPromptSuggestionResult, OwnedUiPromptSuggestionState, OwnedUiQuitOutroSettings, OwnedUiQuitOutroSettingsPort, OwnedUiSessionId, OwnedUiSessionViewModel, OwnedUiSeverity, OwnedUiSlotId, OwnedUiSnapshot, OwnedUiStatusView, OwnedUiTerminalSurface, OwnedUiThinkingLevel, OwnedUiToolRendering, OwnedUiToolResultPart, OwnedUiToolState, OwnedUiTranscriptBlock, OwnedUiTranscriptBlockKind, OwnedUiTranscriptImageReference, OwnedUiUsageView, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, } from "./model.js";
|
|
6
|
+
export type { OwnedUiBackgroundSettingsPort, OwnedUiBackgroundStyle, OwnedUiBlockStatus, OwnedUiCommand, OwnedUiCommandOutcome, OwnedUiCorrelationId, OwnedUiCustomization, OwnedUiDiagnostics, OwnedUiDialog, OwnedUiEditorState, OwnedUiEntityId, OwnedUiEvent, OwnedUiFooterView, OwnedUiImageAttachment, OwnedUiLifecycle, OwnedUiModelInfo, OwnedUiOverlay, OwnedUiPromptSuggestionGeneratorPort, OwnedUiPromptSuggestionIdentity, OwnedUiPromptSuggestionReasoning, OwnedUiPromptSuggestionRequest, OwnedUiPromptSuggestionResult, OwnedUiPromptSuggestionState, OwnedUiQuitOutroSettings, OwnedUiQuitOutroSettingsPort, OwnedUiSessionId, OwnedUiSessionViewModel, OwnedUiSeverity, OwnedUiSlotId, OwnedUiSnapshot, OwnedUiStatusView, OwnedUiTerminalSurface, OwnedUiThinkingLevel, OwnedUiTimeFormat, OwnedUiToolRendering, OwnedUiToolResultPart, OwnedUiToolState, OwnedUiTranscriptBlock, OwnedUiTranscriptBlockKind, OwnedUiTranscriptImageReference, OwnedUiUsageView, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, } from "./model.js";
|
|
7
7
|
export type { OwnedUiExtensionPort, OwnedUiSessionBackend, OwnedUiSessionCatalogPort, OwnedUiSessionFactory, OwnedUiSessionIdentityPort, OwnedUiSessionPort, OwnedUiSessionRequest, OwnedUiSessionSettingsPort, OwnedUiWorkflowPort, } from "./session-backend.js";
|
|
8
8
|
export type { OwnedUiDialogHost, OwnedUiInputSurface, OwnedUiPresenterHost, OwnedUiSessionPresenter, OwnedUiSessionPresenters, OwnedUiShortcutDescription, OwnedUiTerminalHost, OwnedUiTranscriptRendererPort, } from "./session-presenters.js";
|
|
9
9
|
export { OWNED_UI_HIDDEN_COMMAND_NAMES, OWNED_UI_MODELS_COMMAND_NAME, OWNED_UI_WORKFLOW_COMMAND_NAMES, } from "./session-workflows.js";
|
|
@@ -53,7 +53,10 @@ export interface OwnedUiPromptSuggestionGeneratorPort {
|
|
|
53
53
|
generate(request: OwnedUiPromptSuggestionRequest): Promise<OwnedUiPromptSuggestionResult>;
|
|
54
54
|
}
|
|
55
55
|
export type OwnedUiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
56
|
+
export type OwnedUiTimeFormat = "20:00" | "8:00 PM";
|
|
56
57
|
export interface OwnedUiViewportSettings {
|
|
58
|
+
/** Omitted by older SDK callers; bare A1 composition always supplies the owned default. */
|
|
59
|
+
readonly timeFormat?: OwnedUiTimeFormat;
|
|
57
60
|
readonly scrollbarAppearance: "auto" | "always" | "hidden";
|
|
58
61
|
readonly scrollbarStyle: "thin" | "thick";
|
|
59
62
|
/** normal is baseline; fast is 2x normal; high is normal plus fast. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DIALOG_BACK_SHORTCUT_HINT, DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
|
|
2
|
-
import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, numericValues, RAIL_COLUMNS, renderDialogPanel, renderSteppedDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderShortcutHintsWithClose, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
|
|
2
|
+
import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, numericValues, RAIL_COLUMNS, renderDialogPanel, renderSteppedDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderShortcutHintsWithClose, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, formatLocalTime, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
|
|
3
3
|
import { SETTINGS_APP_ID } from "./settings-route.js";
|
|
4
4
|
export { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
|
|
5
5
|
const SCOPE = SETTINGS_APP_ID;
|
|
@@ -921,7 +921,7 @@ export class SettingsApp {
|
|
|
921
921
|
? CONFIGURE
|
|
922
922
|
: shown === null
|
|
923
923
|
? describeRaw(entry.rawValue)
|
|
924
|
-
:
|
|
924
|
+
: displaySettingValue(entry, shown);
|
|
925
925
|
const range = rangeOf(entry);
|
|
926
926
|
return {
|
|
927
927
|
key,
|
|
@@ -938,11 +938,11 @@ export class SettingsApp {
|
|
|
938
938
|
return lines;
|
|
939
939
|
}
|
|
940
940
|
const state = {
|
|
941
|
-
choices: menu.choices.map(choice =>
|
|
941
|
+
choices: menu.choices.map(choice => displaySettingValue(menu.entry, choice)),
|
|
942
942
|
...(menu.entry.id === "accentColor" ? {
|
|
943
943
|
previews: menu.choices.map(choice => theme.accentPreview?.(String(choice), "■") ?? ""),
|
|
944
944
|
} : {}),
|
|
945
|
-
current: this.#shownValue(menu.entry) === null ? null :
|
|
945
|
+
current: this.#shownValue(menu.entry) === null ? null : displaySettingValue(menu.entry, this.#shownValue(menu.entry)),
|
|
946
946
|
index: menu.index,
|
|
947
947
|
};
|
|
948
948
|
const frame = valueMenuFrame(state, {
|
|
@@ -1084,8 +1084,11 @@ function labelOf(entry) {
|
|
|
1084
1084
|
function rangeOf(entry) {
|
|
1085
1085
|
return { minimum: entry.minimum, maximum: entry.maximum, values: numericValues(entry.choices) };
|
|
1086
1086
|
}
|
|
1087
|
-
/** Booleans read as yes and no;
|
|
1088
|
-
function
|
|
1087
|
+
/** Booleans read as yes and no; time-format choices preview the current local time. */
|
|
1088
|
+
function displaySettingValue(entry, value) {
|
|
1089
|
+
if (entry.backend === "a1" && entry.id === "timeFormat" && (value === "20:00" || value === "8:00 PM")) {
|
|
1090
|
+
return formatLocalTime(new Date(), value) ?? value;
|
|
1091
|
+
}
|
|
1089
1092
|
if (typeof value === "boolean")
|
|
1090
1093
|
return value ? "yes" : "no";
|
|
1091
1094
|
return String(value);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { AUTO_NAME_MAX_LENGTH, AUTO_NAME_TIMEOUT_MS, AUTO_NAME_TOKEN_BUDGETS, fallbackAutoName, generateAutoName, normalizeAutoName, type AutoNameCompletion, } from "./auto-name.js";
|
|
2
|
-
export { RESIDENT_TAB_SHORTCUTS,
|
|
2
|
+
export { RESIDENT_TAB_SHORTCUTS, type ResidentTabAction, type ResidentTabShortcut, } from "./shortcut-declarations.js";
|
|
3
|
+
export { projectResidentTabShortcuts, RESIDENT_TAB_GLOBAL_SHORTCUTS, type ResidentTabGlobalAction, type ResidentTabShortcutConflict, type ResidentTabShortcutProjection, type ShortcutBindings, } from "./shortcut-projection.js";
|
|
4
|
+
export { readShortcutBindingsFile } from "./shortcut-bindings-file.js";
|
|
3
5
|
export { deriveResidentTabStatus, type ResidentTabDerivedStatus, type ResidentTabFacts, } from "./status-deriver.js";
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { AUTO_NAME_MAX_LENGTH, AUTO_NAME_TIMEOUT_MS, AUTO_NAME_TOKEN_BUDGETS, fallbackAutoName, generateAutoName, normalizeAutoName, } from "./auto-name.js";
|
|
2
|
-
|
|
2
|
+
// Performance: the registry builder lives in `shortcuts.ts`, outside this barrel, so startup
|
|
3
|
+
// presentation can read the declarations without loading the component registry.
|
|
4
|
+
export { RESIDENT_TAB_SHORTCUTS, } from "./shortcut-declarations.js";
|
|
5
|
+
export { projectResidentTabShortcuts, RESIDENT_TAB_GLOBAL_SHORTCUTS, } from "./shortcut-projection.js";
|
|
6
|
+
export { readShortcutBindingsFile } from "./shortcut-bindings-file.js";
|
|
3
7
|
export { deriveResidentTabStatus, } from "./status-deriver.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ShortcutBindings } from "./shortcut-projection.js";
|
|
2
|
+
/** Largest `keybindings.json` the projection reads; a larger file counts as absent. */
|
|
3
|
+
export declare const MAX_SHORTCUT_BINDINGS_BYTES: number;
|
|
4
|
+
/**
|
|
5
|
+
* Reads the user's `keybindings.json` as shortcut bindings, keeping `null` entries so an
|
|
6
|
+
* explicit unbind survives, and treating any unreadable or malformed file as empty.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readShortcutBindingsFile(agentDir: string): ShortcutBindings;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** Largest `keybindings.json` the projection reads; a larger file counts as absent. */
|
|
4
|
+
export const MAX_SHORTCUT_BINDINGS_BYTES = 256 * 1024;
|
|
5
|
+
/**
|
|
6
|
+
* Reads the user's `keybindings.json` as shortcut bindings, keeping `null` entries so an
|
|
7
|
+
* explicit unbind survives, and treating any unreadable or malformed file as empty.
|
|
8
|
+
*/
|
|
9
|
+
export function readShortcutBindingsFile(agentDir) {
|
|
10
|
+
let text;
|
|
11
|
+
try {
|
|
12
|
+
text = readFileSync(join(agentDir, "keybindings.json"), "utf8");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
if (text.length > MAX_SHORTCUT_BINDINGS_BYTES)
|
|
18
|
+
return {};
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(text.replace(/^/u, ""));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
27
|
+
return {};
|
|
28
|
+
const bindings = {};
|
|
29
|
+
for (const [action, value] of Object.entries(parsed)) {
|
|
30
|
+
if (action === "$schema")
|
|
31
|
+
continue;
|
|
32
|
+
if (value === null || typeof value === "string") {
|
|
33
|
+
bindings[action] = value;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
bindings[action] = value.filter((key) => typeof key === "string");
|
|
38
|
+
}
|
|
39
|
+
return bindings;
|
|
40
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ShortcutRegistry } from "../../ui/components/index.js";
|
|
2
1
|
export type ResidentTabAction = "tabs.new" | "tabs.close" | "tabs.rename" | "tabs.retry" | "tabs.fresh" | "tabs.next" | "tabs.previous" | "tabs.moveRight" | "tabs.moveLeft" | `tabs.jump${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}`;
|
|
3
2
|
export interface ResidentTabShortcut {
|
|
4
3
|
readonly id: ResidentTabAction;
|
|
@@ -6,5 +5,9 @@ export interface ResidentTabShortcut {
|
|
|
6
5
|
readonly scope: "global" | "failed-banner";
|
|
7
6
|
readonly description: string;
|
|
8
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The tab shortcut declarations, in the order that decides which action keeps a duplicated key.
|
|
10
|
+
* This module stays dependency-free so startup presentation and the resident projection can
|
|
11
|
+
* read it without loading the component registry.
|
|
12
|
+
*/
|
|
9
13
|
export declare const RESIDENT_TAB_SHORTCUTS: readonly ResidentTabShortcut[];
|
|
10
|
-
export declare function residentTabShortcutRegistry(assignments?: Readonly<Partial<Record<ResidentTabAction, string | null>>>): ShortcutRegistry<ResidentTabAction>;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { GLOBAL_SCOPE, ShortcutRegistry } from "../../ui/components/index.js";
|
|
2
1
|
const jumps = Array.from({ length: 10 }, (_, index) => ({
|
|
3
2
|
id: `tabs.jump${index + 1}`,
|
|
4
3
|
defaultKey: `alt+${(index + 1) % 10}`,
|
|
5
4
|
scope: "global",
|
|
6
5
|
description: `View A1 tab ${index + 1}`,
|
|
7
6
|
}));
|
|
7
|
+
/**
|
|
8
|
+
* The tab shortcut declarations, in the order that decides which action keeps a duplicated key.
|
|
9
|
+
* This module stays dependency-free so startup presentation and the resident projection can
|
|
10
|
+
* read it without loading the component registry.
|
|
11
|
+
*/
|
|
8
12
|
export const RESIDENT_TAB_SHORTCUTS = Object.freeze([
|
|
9
13
|
{ id: "tabs.new", defaultKey: "alt+a", scope: "global", description: "New A1 tab" },
|
|
10
14
|
{ id: "tabs.close", defaultKey: "alt+w", scope: "global", description: "Close A1 tab" },
|
|
@@ -17,19 +21,3 @@ export const RESIDENT_TAB_SHORTCUTS = Object.freeze([
|
|
|
17
21
|
{ id: "tabs.retry", defaultKey: "r", scope: "failed-banner", description: "Retry failed A1 tab" },
|
|
18
22
|
{ id: "tabs.fresh", defaultKey: "f", scope: "failed-banner", description: "Start failed A1 tab fresh" },
|
|
19
23
|
]);
|
|
20
|
-
export function residentTabShortcutRegistry(assignments = {}) {
|
|
21
|
-
const registry = new ShortcutRegistry();
|
|
22
|
-
for (const shortcut of RESIDENT_TAB_SHORTCUTS) {
|
|
23
|
-
const key = assignments[shortcut.id] === undefined ? shortcut.defaultKey : assignments[shortcut.id];
|
|
24
|
-
if (!key)
|
|
25
|
-
continue;
|
|
26
|
-
const declaration = {
|
|
27
|
-
key,
|
|
28
|
-
scope: shortcut.scope === "global" ? GLOBAL_SCOPE : shortcut.scope,
|
|
29
|
-
section: "Tabs",
|
|
30
|
-
description: shortcut.description,
|
|
31
|
-
};
|
|
32
|
-
registry.declare(declaration, shortcut.id);
|
|
33
|
-
}
|
|
34
|
-
return registry;
|
|
35
|
-
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ResidentTabAction } from "./shortcut-declarations.js";
|
|
2
|
+
/** Key assignments as `keybindings.json` and the keybinding manager express them. */
|
|
3
|
+
export type ShortcutBindings = Readonly<Record<string, string | readonly string[] | null | undefined>>;
|
|
4
|
+
export type ResidentTabGlobalAction = Exclude<ResidentTabAction, "tabs.retry" | "tabs.fresh">;
|
|
5
|
+
export interface ResidentTabShortcutConflict {
|
|
6
|
+
readonly id: ResidentTabGlobalAction;
|
|
7
|
+
readonly key: string;
|
|
8
|
+
/** `reserved` keys belong to an A1 action; `duplicate` keys were claimed by an earlier tab action. */
|
|
9
|
+
readonly reason: "reserved" | "duplicate";
|
|
10
|
+
}
|
|
11
|
+
/** The effective tab shortcuts the resident shell dispatches and every presentation shows. */
|
|
12
|
+
export interface ResidentTabShortcutProjection {
|
|
13
|
+
readonly assignments: Readonly<Record<ResidentTabGlobalAction, string | null>>;
|
|
14
|
+
readonly conflicts: readonly ResidentTabShortcutConflict[];
|
|
15
|
+
}
|
|
16
|
+
export interface ResidentTabShortcutInput {
|
|
17
|
+
/** The user's `keybindings.json`, including any `tabs.*` entries; `null` unbinds an action. */
|
|
18
|
+
readonly configured?: ShortcutBindings | undefined;
|
|
19
|
+
/** The effective A1 declarations (defaults plus customization) whose keys tab actions may not take. */
|
|
20
|
+
readonly reserved?: ShortcutBindings | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** The declarations the projection covers, in the order that decides duplicate claims. */
|
|
23
|
+
export declare const RESIDENT_TAB_GLOBAL_SHORTCUTS: import("./shortcut-declarations.js").ResidentTabShortcut[];
|
|
24
|
+
/**
|
|
25
|
+
* Projects the effective A1 declarations and the user's customization into one tab shortcut
|
|
26
|
+
* assignment per action. A key an A1 action holds after customization never becomes a tab
|
|
27
|
+
* shortcut; between tab actions the earlier declaration keeps a duplicated key.
|
|
28
|
+
*/
|
|
29
|
+
export declare function projectResidentTabShortcuts(input?: ResidentTabShortcutInput): ResidentTabShortcutProjection;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { RESIDENT_TAB_SHORTCUTS } from "./shortcut-declarations.js";
|
|
2
|
+
/** The declarations the projection covers, in the order that decides duplicate claims. */
|
|
3
|
+
export const RESIDENT_TAB_GLOBAL_SHORTCUTS = RESIDENT_TAB_SHORTCUTS.filter(shortcut => shortcut.scope === "global");
|
|
4
|
+
function normalizeKey(key) {
|
|
5
|
+
return key.trim().toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
function keysOf(value) {
|
|
8
|
+
if (value === null || value === undefined)
|
|
9
|
+
return value;
|
|
10
|
+
if (typeof value === "string")
|
|
11
|
+
return [value];
|
|
12
|
+
return value.filter((key) => typeof key === "string");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Projects the effective A1 declarations and the user's customization into one tab shortcut
|
|
16
|
+
* assignment per action. A key an A1 action holds after customization never becomes a tab
|
|
17
|
+
* shortcut; between tab actions the earlier declaration keeps a duplicated key.
|
|
18
|
+
*/
|
|
19
|
+
export function projectResidentTabShortcuts(input = {}) {
|
|
20
|
+
const configured = input.configured ?? {};
|
|
21
|
+
const claimedByOwnedActions = new Map();
|
|
22
|
+
for (const [action, value] of Object.entries(input.reserved ?? {})) {
|
|
23
|
+
if (action.startsWith("tabs."))
|
|
24
|
+
continue;
|
|
25
|
+
claimedByOwnedActions.set(action, keysOf(value) ?? []);
|
|
26
|
+
}
|
|
27
|
+
for (const [action, value] of Object.entries(configured)) {
|
|
28
|
+
if (action.startsWith("tabs.") || action === "$schema")
|
|
29
|
+
continue;
|
|
30
|
+
claimedByOwnedActions.set(action, keysOf(value) ?? []);
|
|
31
|
+
}
|
|
32
|
+
const reserved = new Set();
|
|
33
|
+
for (const keys of claimedByOwnedActions.values())
|
|
34
|
+
for (const key of keys)
|
|
35
|
+
reserved.add(normalizeKey(key));
|
|
36
|
+
const claimed = new Set();
|
|
37
|
+
const conflicts = [];
|
|
38
|
+
const assignments = {};
|
|
39
|
+
for (const shortcut of RESIDENT_TAB_GLOBAL_SHORTCUTS) {
|
|
40
|
+
const id = shortcut.id;
|
|
41
|
+
const custom = configured[id];
|
|
42
|
+
const candidates = custom === undefined
|
|
43
|
+
? (shortcut.defaultKey === null ? [] : [shortcut.defaultKey])
|
|
44
|
+
: (keysOf(custom) ?? []);
|
|
45
|
+
let assigned = null;
|
|
46
|
+
for (const candidate of candidates.map(normalizeKey)) {
|
|
47
|
+
if (candidate.length === 0)
|
|
48
|
+
continue;
|
|
49
|
+
if (reserved.has(candidate)) {
|
|
50
|
+
conflicts.push({ id, key: candidate, reason: "reserved" });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (claimed.has(candidate)) {
|
|
54
|
+
conflicts.push({ id, key: candidate, reason: "duplicate" });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
assigned = candidate;
|
|
58
|
+
claimed.add(candidate);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
assignments[id] = assigned;
|
|
62
|
+
}
|
|
63
|
+
return { assignments, conflicts };
|
|
64
|
+
}
|
|
@@ -66,6 +66,7 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
66
66
|
"attach", "--endpoint", resident.endpoint, "--cwd", process.cwd(), "--clear-exit-ms", String(CLEAR_EXIT_INTERVAL_MS),
|
|
67
67
|
"--tabs-max", String((await residentLaunchSettings).tabsMax),
|
|
68
68
|
"--profile", environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir,
|
|
69
|
+
"--settings-file", resolve(agentProfileDir, "settings.json"),
|
|
69
70
|
"--data-dir", paths.dataDir, "--runtime-dir", paths.runtimeDir,
|
|
70
71
|
"--release-root", release.releaseRoot, "--node-executable", process.execPath,
|
|
71
72
|
]),
|