@timurproko/a1 0.2.7-dev.754 → 0.2.7-dev.772
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 +30 -0
- package/bin/cli.js +4 -0
- package/bin/guardian.js +5 -0
- package/bin/tab-launch-context.d.ts +26 -0
- package/bin/tab-launch-context.js +94 -0
- package/bin/ui.js +24 -6
- package/dist/app/session-shell/helper-pool.d.ts +3 -5
- package/dist/app/session-shell/helper-pool.js +3 -5
- package/dist/app/session-shell/session-shell-root.d.ts +28 -1
- package/dist/app/session-shell/session-shell-root.js +4 -3
- package/dist/app/session-shell/session-shell.js +149 -31
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -1
- package/dist/app/session-shell/session-viewport-controller.js +3 -3
- package/dist/app/session-shell/shell-host.d.ts +2 -0
- package/dist/app/session-shell/shell-host.js +20 -1
- package/dist/cli/dispatch.d.ts +11 -1
- package/dist/cli/dispatch.js +79 -5
- package/dist/cli/tabs-request.d.ts +7 -0
- package/dist/cli/tabs-request.js +1 -0
- package/dist/cli/tabs.d.ts +14 -0
- package/dist/cli/tabs.js +140 -0
- package/dist/composition/owned-ui.d.ts +26 -0
- package/dist/composition/owned-ui.js +176 -6
- package/dist/contracts/owned-ui/session-backend.d.ts +1 -0
- package/dist/features/launch/index.d.ts +1 -1
- package/dist/features/launch/intent.d.ts +3 -1
- package/dist/features/launch/intent.js +7 -4
- package/dist/features/owned-ui/settings-app.js +8 -35
- package/dist/features/resident-tab/auto-name.d.ts +11 -0
- package/dist/features/resident-tab/auto-name.js +42 -0
- package/dist/features/resident-tab/index.d.ts +3 -0
- package/dist/features/resident-tab/index.js +3 -0
- package/dist/features/resident-tab/shortcuts.d.ts +10 -0
- package/dist/features/resident-tab/shortcuts.js +35 -0
- package/dist/features/resident-tab/status-deriver.d.ts +9 -0
- package/dist/features/resident-tab/status-deriver.js +15 -0
- package/dist/foundation/launch-context/index.d.ts +10 -0
- package/dist/foundation/launch-context/index.js +25 -0
- package/dist/foundation/launch-guardian/main.d.ts +5 -0
- package/dist/foundation/launch-guardian/main.js +2 -1
- package/dist/foundation/release/bootstrap.d.ts +6 -0
- package/dist/foundation/release/bootstrap.js +51 -10
- package/dist/foundation/release/cohort-state.d.ts +1 -1
- package/dist/foundation/release/cohort-state.js +1 -1
- package/dist/foundation/release/dependency-certification.js +54 -19
- package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
- package/dist/foundation/release/progress-palette.generated.js +2 -2
- package/dist/foundation/release/release-gc.d.ts +3 -0
- package/dist/foundation/release/release-gc.js +44 -8
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +16 -0
- package/dist/foundation/resident-tabs/bounded-host-command.js +35 -0
- package/dist/foundation/resident-tabs/bridge-client.d.ts +56 -0
- package/dist/foundation/resident-tabs/bridge-client.js +279 -0
- package/dist/foundation/resident-tabs/doctor.d.ts +29 -0
- package/dist/foundation/resident-tabs/doctor.js +166 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +27 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.js +145 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +31 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +147 -0
- package/dist/foundation/resident-tabs/fault-injection.d.ts +8 -0
- package/dist/foundation/resident-tabs/fault-injection.js +26 -0
- package/dist/foundation/resident-tabs/index.d.ts +12 -0
- package/dist/foundation/resident-tabs/index.js +6 -0
- package/dist/foundation/resident-tabs/maintenance.d.ts +42 -0
- package/dist/foundation/resident-tabs/maintenance.js +78 -0
- package/dist/foundation/resident-tabs/prompt-journal.d.ts +65 -0
- package/dist/foundation/resident-tabs/prompt-journal.js +214 -0
- package/dist/foundation/resident-tabs/protocol.d.ts +12 -0
- package/dist/foundation/resident-tabs/protocol.js +195 -0
- package/dist/foundation/resident-tabs/recovery.d.ts +26 -0
- package/dist/foundation/resident-tabs/recovery.js +205 -0
- package/dist/foundation/resident-tabs/release-holds.d.ts +27 -0
- package/dist/foundation/resident-tabs/release-holds.js +77 -0
- package/dist/foundation/resident-tabs/routing.d.ts +11 -0
- package/dist/foundation/resident-tabs/routing.js +10 -0
- package/dist/foundation/resident-tabs/session-sync.d.ts +14 -0
- package/dist/foundation/resident-tabs/session-sync.js +37 -0
- package/dist/foundation/resident-tabs/settings.d.ts +14 -0
- package/dist/foundation/resident-tabs/settings.js +66 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +18 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +53 -0
- package/dist/foundation/resident-tabs/zip-archive.d.ts +9 -0
- package/dist/foundation/resident-tabs/zip-archive.js +56 -0
- package/dist/foundation/session-writer-lock/index.d.ts +32 -0
- package/dist/foundation/session-writer-lock/index.js +201 -0
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/foundation/terminal-cleanup/index.d.ts +1 -1
- package/dist/foundation/terminal-cleanup/index.js +1 -1
- package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +1 -0
- package/dist/foundation/terminal-cleanup/terminal-reset.js +1 -0
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +4 -4
- package/dist/integrations/pi/components/resources/builtin-themes.js +4 -4
- package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +4 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +38 -4
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +17 -4
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +3 -3
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +0 -2
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +41 -20
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/history/editor-core.js +5 -5
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.d.ts +3 -0
- package/dist/integrations/pi/components/upstream/theme/theme.js +24 -1
- package/dist/integrations/pi/engine/adapter.d.ts +1 -0
- package/dist/integrations/pi/engine/adapter.js +4 -0
- package/dist/integrations/pi/engine/pi-settings-metadata.json +2 -2
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
- package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/runtime-integration.js +102 -29
- package/dist/integrations/pi/engine/session-runtime.d.ts +0 -6
- package/dist/integrations/pi/engine/session-runtime.js +8 -3
- package/dist/integrations/pi/engine/session-selection.d.ts +3 -0
- package/dist/integrations/pi/engine/session-selection.js +45 -9
- package/dist/integrations/pi/engine/workflow-contexts.js +10 -1
- package/dist/integrations/pi/startup-public.js +152 -222
- package/dist/integrations/pi/startup-public.manifest.json +266 -177
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +10500 -0
- package/dist/native/darwin-arm64/terminal-host.manifest.json +44 -0
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/manifest.json +20 -0
- 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-THIRD-PARTY-NOTICES.txt +10737 -0
- package/dist/native/darwin-x64/terminal-host.manifest.json +44 -0
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +11226 -0
- package/dist/native/linux-x64/terminal-host.manifest.json +44 -0
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10757 -0
- package/dist/native/win32-x64/terminal-host.exe +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +44 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +5 -0
- package/dist/runtime-payload-inventory.json +5349 -6035
- package/dist/ui/components/line-input.d.ts +1 -1
- package/dist/ui/components/prompt-input.d.ts +7 -5
- package/dist/ui/components/prompt-input.js +7 -7
- package/dist/ui/components/transcript-viewport.d.ts +6 -3
- package/dist/ui/components/transcript-viewport.js +13 -15
- package/dist/ui/settings/declarations.d.ts +25 -1
- package/dist/ui/settings/declarations.js +21 -1
- package/dist/ui/settings/index.d.ts +4 -2
- package/dist/ui/settings/index.js +2 -1
- package/dist/ui/settings/manager.d.ts +10 -3
- package/dist/ui/settings/manager.js +90 -30
- package/dist/ui/settings/migrations.d.ts +6 -0
- package/dist/ui/settings/migrations.js +44 -0
- package/dist/ui/settings/profile-document.d.ts +26 -0
- package/dist/ui/settings/profile-document.js +164 -0
- package/docs/architecture/evidence/resident-tabs/linux-x64.md +37 -0
- package/docs/architecture/evidence/resident-tabs/macos-arm64.md +36 -0
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +35 -0
- package/docs/architecture/evidence/resident-tabs/windows-x64.md +36 -0
- package/docs/architecture/internal-naming.md +3 -1
- package/docs/architecture/process-guardian-provenance.md +2 -0
- package/docs/architecture/resident-tabs-requirements.md +18 -0
- package/docs/architecture/resident-tabs.md +594 -0
- package/docs/architecture/terminal-host-proof-gate.md +2 -0
- package/docs/architecture/terminal-host-provenance.md +13 -3
- package/docs/architecture/toolchain.md +1 -1
- package/docs/ci-release-runbook.md +2 -2
- package/docs/features/launch-profiles.md +11 -8
- package/docs/manual-resident-tabs-acceptance.md +51 -0
- package/docs/openspec-archive-automation.md +17 -16
- package/docs/validation.md +9 -1
- package/package.json +10 -4
|
@@ -3,7 +3,7 @@ import { createResponseCopyExecutor, hasAsyncClipboardOutput, responseCopyDestin
|
|
|
3
3
|
import { MAX_COPY_CONTROL_BYTES } from "./response-copy-protocol.js";
|
|
4
4
|
import { PromptHistoryController } from "./prompt-history-controller.js";
|
|
5
5
|
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
6
|
-
import { boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
6
|
+
import { CLEAR_EXIT_INTERVAL_MS, boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
7
7
|
import { assertOwnedUiCommand, OWNED_UI_HIDDEN_COMMAND_NAMES } from "../../contracts/owned-ui/index.js";
|
|
8
8
|
import { assertPromptImages, ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
9
9
|
import { ContextualPromptSuggestionController } from "./prompt-suggestion-controller.js";
|
|
@@ -34,6 +34,7 @@ export class OwnedUiSessionPresenter {
|
|
|
34
34
|
#host;
|
|
35
35
|
#terminal;
|
|
36
36
|
#cwd;
|
|
37
|
+
#residentTabOptions;
|
|
37
38
|
#listeners = new Set();
|
|
38
39
|
#unsubscribe;
|
|
39
40
|
#unsubscribePromptSuggestions;
|
|
@@ -53,6 +54,9 @@ export class OwnedUiSessionPresenter {
|
|
|
53
54
|
#routeHost;
|
|
54
55
|
#startupRoute;
|
|
55
56
|
#startupRouteTimer;
|
|
57
|
+
#residentProgressTimer;
|
|
58
|
+
#residentWorking = false;
|
|
59
|
+
#residentStalled = false;
|
|
56
60
|
#dialogHandle;
|
|
57
61
|
#sequence = 0;
|
|
58
62
|
#editorRevision = 0;
|
|
@@ -104,6 +108,7 @@ export class OwnedUiSessionPresenter {
|
|
|
104
108
|
this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
|
|
105
109
|
this.#suggestionModelKey = modelKey(this.backend.session.view());
|
|
106
110
|
this.#cwd = cwd;
|
|
111
|
+
this.#residentTabOptions = options.engine.residentTab;
|
|
107
112
|
this.#routeHost = routeHost ?? null;
|
|
108
113
|
this.#startupRoute = options.engine.startupRoute;
|
|
109
114
|
this.#customViewport = sessionLayout === "custom-viewport";
|
|
@@ -202,6 +207,7 @@ export class OwnedUiSessionPresenter {
|
|
|
202
207
|
onDequeue: () => this.restoreQueuedInput(),
|
|
203
208
|
onEditorChange: text => {
|
|
204
209
|
this.#editorRevision++;
|
|
210
|
+
this.#residentTabOptions?.checkpoint?.(text);
|
|
205
211
|
promptSuggestionController?.abortPending();
|
|
206
212
|
if (text.length === 0) {
|
|
207
213
|
// Concurrency: the editor synchronizes deletion-owned autocomplete after onChange.
|
|
@@ -218,9 +224,9 @@ export class OwnedUiSessionPresenter {
|
|
|
218
224
|
promptSuggestionController?.invalidate();
|
|
219
225
|
this.#promptHistory?.synchronize();
|
|
220
226
|
},
|
|
221
|
-
onCopyText: text => {
|
|
227
|
+
onCopyText: (text, cut) => {
|
|
222
228
|
if (this.#responseCopy !== null) {
|
|
223
|
-
|
|
229
|
+
this.#trackCopy(this.#responseCopy.submitText(text), cut ? undefined : text.length);
|
|
224
230
|
return;
|
|
225
231
|
}
|
|
226
232
|
const write = () => {
|
|
@@ -263,6 +269,7 @@ export class OwnedUiSessionPresenter {
|
|
|
263
269
|
// Invariant: the presenter owns its root (theme subscription and helper clients) and disposes it once the
|
|
264
270
|
// terminal no longer renders it.
|
|
265
271
|
constructed.push(() => this.root.dispose());
|
|
272
|
+
this.#restoreResidentInput();
|
|
266
273
|
const initialPiSettings = this.backend.settings.snapshot();
|
|
267
274
|
this.#programStatus = new ProgramStatusReporter(status => this.#terminal.setProgramStatus(status), () => this.view().status.footer?.sessionName ?? undefined);
|
|
268
275
|
this.#reloadPresentation = reloadPresentation;
|
|
@@ -474,6 +481,7 @@ export class OwnedUiSessionPresenter {
|
|
|
474
481
|
this.#settleStoppedLifecycle();
|
|
475
482
|
return;
|
|
476
483
|
}
|
|
484
|
+
this.#observeResidentProgress(event);
|
|
477
485
|
// Performance: a streamed chunk names one block, and touching only that block is what keeps the
|
|
478
486
|
// cost of a chunk the same in a long session as in a new one. Everything else
|
|
479
487
|
// resynchronizes the view, which is cheap next to re-reading the transcript.
|
|
@@ -626,21 +634,21 @@ export class OwnedUiSessionPresenter {
|
|
|
626
634
|
const routed = this.root.handleViewportPreInput(data, true, Date.now(), this.root.usesDefaultInputSurface() && !this.#terminal.hasFocusedOverlay());
|
|
627
635
|
if (routed.copySelection !== undefined) {
|
|
628
636
|
const snapshot = routed.copySelection;
|
|
629
|
-
|
|
630
|
-
const intent = ++this.#copyIntentSequence;
|
|
631
|
-
void this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite).then(result => {
|
|
632
|
-
if (this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
633
|
-
|| result.outcome !== "delivered" && result.outcome !== "submitted-unverified")
|
|
634
|
-
return;
|
|
635
|
-
if (acknowledgement.hasNonWhitespace) {
|
|
636
|
-
this.root.showCopyAcknowledgement(`copied ${acknowledgement.characters} chars to clipboard`);
|
|
637
|
-
}
|
|
638
|
-
});
|
|
637
|
+
this.#trackCopy(this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite), copyCharacters(snapshot));
|
|
639
638
|
}
|
|
640
639
|
if (!routed.consumed)
|
|
641
640
|
return routed.data === data ? undefined : { data: routed.data };
|
|
642
641
|
return routed.data.length === 0 ? { consume: true } : { data: routed.data };
|
|
643
642
|
}
|
|
643
|
+
#trackCopy(delivery, count) {
|
|
644
|
+
const intent = ++this.#copyIntentSequence;
|
|
645
|
+
void delivery?.then(({ outcome }) => {
|
|
646
|
+
if (count === undefined || this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
647
|
+
|| outcome !== "delivered" && outcome !== "submitted-unverified")
|
|
648
|
+
return;
|
|
649
|
+
this.root.showCopyAcknowledgement(`copied ${count} chars to clipboard`);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
644
652
|
#promptSuggestionPresentationBlockReason(identity) {
|
|
645
653
|
const view = this.view();
|
|
646
654
|
if (this.#disposed)
|
|
@@ -779,15 +787,38 @@ export class OwnedUiSessionPresenter {
|
|
|
779
787
|
const type = this.view().lifecycle === "busy" ? "steer" : "prompt";
|
|
780
788
|
this.#rememberInput(this.root.omitUnsentPromptImages(displayInput).trim(), type);
|
|
781
789
|
this.root.resumeViewportFollowing();
|
|
782
|
-
const
|
|
790
|
+
const command = {
|
|
791
|
+
type,
|
|
792
|
+
correlationId: this.#correlation(type),
|
|
793
|
+
sessionId: this.backend.identity.sessionId,
|
|
794
|
+
text: input,
|
|
795
|
+
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
796
|
+
};
|
|
797
|
+
let submission;
|
|
783
798
|
try {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
799
|
+
submission = await this.#residentTabOptions?.admit?.({
|
|
800
|
+
sessionId: command.sessionId,
|
|
801
|
+
sessionFile: this.#currentSessionFile(),
|
|
802
|
+
kind: type,
|
|
788
803
|
text: input,
|
|
789
804
|
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
790
|
-
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
if (this.root.editor.getText().length === 0)
|
|
809
|
+
this.root.editor.setText(displayInput);
|
|
810
|
+
const message = "Prompt was not sent because its recovery journal could not be synchronized.";
|
|
811
|
+
this.#reportSubmissionError(undefined, message);
|
|
812
|
+
return rejected(message);
|
|
813
|
+
}
|
|
814
|
+
const finishSending = prepared.images.length === 0 ? () => { } : this.root.beginImageSubmissionStatus();
|
|
815
|
+
try {
|
|
816
|
+
const result = await this.#execute(command, displayInput);
|
|
817
|
+
if (submission !== undefined && result.outcome !== "rejected") {
|
|
818
|
+
this.#residentTabOptions?.dispatched?.();
|
|
819
|
+
await this.#residentTabOptions?.correlated?.(submission.id, command.correlationId).catch(() => undefined);
|
|
820
|
+
}
|
|
821
|
+
return result;
|
|
791
822
|
}
|
|
792
823
|
finally {
|
|
793
824
|
finishSending();
|
|
@@ -795,7 +826,7 @@ export class OwnedUiSessionPresenter {
|
|
|
795
826
|
}
|
|
796
827
|
async clearOrExit(now = Date.now()) {
|
|
797
828
|
this.#promptSuggestions?.abortPending();
|
|
798
|
-
if (now - this.#lastClearTime <
|
|
829
|
+
if (now - this.#lastClearTime < CLEAR_EXIT_INTERVAL_MS)
|
|
799
830
|
return this.shutdown();
|
|
800
831
|
this.root.editor.setText("");
|
|
801
832
|
this.#lastClearTime = now;
|
|
@@ -1149,10 +1180,23 @@ export class OwnedUiSessionPresenter {
|
|
|
1149
1180
|
this.#presenters.openSettingsSelector(this.#dialogHost);
|
|
1150
1181
|
}
|
|
1151
1182
|
shutdown() {
|
|
1152
|
-
this.#shutdownPromise ??= this.#shutdown()
|
|
1183
|
+
this.#shutdownPromise ??= this.#shutdown().finally(() => {
|
|
1184
|
+
// Invariant: a rejected resident detach is retryable; ordinary shutdown has disposed the shell already.
|
|
1185
|
+
if (!this.#disposed)
|
|
1186
|
+
this.#shutdownPromise = undefined;
|
|
1187
|
+
});
|
|
1153
1188
|
return this.#shutdownPromise;
|
|
1154
1189
|
}
|
|
1155
1190
|
async #shutdown() {
|
|
1191
|
+
const residentTab = this.#residentTab();
|
|
1192
|
+
if (residentTab !== undefined) {
|
|
1193
|
+
const detached = await residentTab.requestDetach().catch(() => false);
|
|
1194
|
+
if (detached)
|
|
1195
|
+
return { outcome: "completed", diagnostic: null };
|
|
1196
|
+
this.root.appendWorkflowStatus(residentTab.unavailableNotice ?? "Press Ctrl+C twice to leave; this agent keeps running.");
|
|
1197
|
+
this.#terminal.requestRender();
|
|
1198
|
+
return rejected("resident tab detach is unavailable");
|
|
1199
|
+
}
|
|
1156
1200
|
try {
|
|
1157
1201
|
const quit = this.backend.workflows.executeWorkflow({ command: "quit", argument: "" });
|
|
1158
1202
|
// Invariant: a slow or hung engine quit never holds the terminal past the cleanup deadline. The
|
|
@@ -1168,6 +1212,75 @@ export class OwnedUiSessionPresenter {
|
|
|
1168
1212
|
this.#resolveStoppedLifecycle();
|
|
1169
1213
|
}
|
|
1170
1214
|
}
|
|
1215
|
+
#restoreResidentInput() {
|
|
1216
|
+
const recovered = this.#residentTabOptions?.recovered;
|
|
1217
|
+
if (recovered === undefined)
|
|
1218
|
+
return;
|
|
1219
|
+
const prompts = recovered.prompts.map(prompt => prompt.text).filter(Boolean);
|
|
1220
|
+
if (prompts.length > 0) {
|
|
1221
|
+
this.root.editor.setText(prompts[0]);
|
|
1222
|
+
if (recovered.draft)
|
|
1223
|
+
this.root.editor.addToHistory(recovered.draft);
|
|
1224
|
+
for (const additional of prompts.slice(1))
|
|
1225
|
+
this.root.editor.addToHistory(additional);
|
|
1226
|
+
this.root.appendWorkflowStatus(recovered.draft
|
|
1227
|
+
? `Recovered an unfinished prompt; the prior draft and ${Math.max(0, prompts.length - 1)} additional prompt${prompts.length === 2 ? "" : "s"} are in editor history.`
|
|
1228
|
+
: `Recovered an unfinished prompt${prompts.length > 1 ? `; ${prompts.length - 1} additional prompts are in editor history` : ""}.`);
|
|
1229
|
+
}
|
|
1230
|
+
else if (recovered.draft) {
|
|
1231
|
+
this.root.editor.setText(recovered.draft);
|
|
1232
|
+
this.root.appendWorkflowStatus("Recovered the editor draft from the previous tab process.");
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
#currentSessionFile() {
|
|
1236
|
+
const direct = this.backend.identity.currentSessionFile?.();
|
|
1237
|
+
if (direct !== undefined && direct !== null)
|
|
1238
|
+
return direct;
|
|
1239
|
+
const resume = this.backend.identity.currentSessionResumeMetadata();
|
|
1240
|
+
return resume === null ? null : `${resume.sessionDir}/${resume.sessionId}`;
|
|
1241
|
+
}
|
|
1242
|
+
#residentTab() {
|
|
1243
|
+
return this.#residentTabOptions;
|
|
1244
|
+
}
|
|
1245
|
+
#observeResidentProgress(event) {
|
|
1246
|
+
const resident = this.#residentTabOptions;
|
|
1247
|
+
const seconds = resident?.stallNoticeSeconds;
|
|
1248
|
+
if (resident === undefined || seconds === undefined)
|
|
1249
|
+
return;
|
|
1250
|
+
if (event.type === "session-lifecycle") {
|
|
1251
|
+
this.#residentWorking = event.lifecycle === "busy";
|
|
1252
|
+
if (!this.#residentWorking) {
|
|
1253
|
+
if (this.#residentProgressTimer !== undefined)
|
|
1254
|
+
clearTimeout(this.#residentProgressTimer);
|
|
1255
|
+
this.#residentProgressTimer = undefined;
|
|
1256
|
+
this.#residentStalled = false;
|
|
1257
|
+
resident.reportStatus?.("idle");
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
else if (!this.#residentWorking || ![
|
|
1262
|
+
"transcript-block", "session-view", "assistant-message-completed", "agent-run-started",
|
|
1263
|
+
].includes(event.type)) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (this.#residentStalled) {
|
|
1267
|
+
this.#residentStalled = false;
|
|
1268
|
+
resident.reportStatus?.("working");
|
|
1269
|
+
}
|
|
1270
|
+
if (this.#residentProgressTimer !== undefined)
|
|
1271
|
+
clearTimeout(this.#residentProgressTimer);
|
|
1272
|
+
this.#residentProgressTimer = setTimeout(() => {
|
|
1273
|
+
this.#residentProgressTimer = undefined;
|
|
1274
|
+
if (this.#disposed || !this.#residentWorking)
|
|
1275
|
+
return;
|
|
1276
|
+
this.#residentStalled = true;
|
|
1277
|
+
resident.reportStatus?.("stalled");
|
|
1278
|
+
const minutes = Math.max(1, Math.round(seconds / 60));
|
|
1279
|
+
this.root.appendWorkflowStatus(`No progress for ${minutes} min — Esc interrupts · use the tab restart action to restart`);
|
|
1280
|
+
this.#terminal.requestRender();
|
|
1281
|
+
}, seconds * 1_000);
|
|
1282
|
+
this.#residentProgressTimer.unref?.();
|
|
1283
|
+
}
|
|
1171
1284
|
async runWorkflow(request) {
|
|
1172
1285
|
if (request.command === "quit")
|
|
1173
1286
|
return this.shutdown();
|
|
@@ -1425,6 +1538,9 @@ export class OwnedUiSessionPresenter {
|
|
|
1425
1538
|
if (this.#startupRouteTimer !== undefined)
|
|
1426
1539
|
clearTimeout(this.#startupRouteTimer);
|
|
1427
1540
|
this.#startupRouteTimer = undefined;
|
|
1541
|
+
if (this.#residentProgressTimer !== undefined)
|
|
1542
|
+
clearTimeout(this.#residentProgressTimer);
|
|
1543
|
+
this.#residentProgressTimer = undefined;
|
|
1428
1544
|
});
|
|
1429
1545
|
attempt(() => this.#promptSuggestions?.dispose());
|
|
1430
1546
|
attempt(() => this.#unsubscribePromptSuggestions());
|
|
@@ -1461,6 +1577,8 @@ export class OwnedUiSessionPresenter {
|
|
|
1461
1577
|
}
|
|
1462
1578
|
await historyCleanup.catch(() => false); // Security: background durability outcomes never enter terminal output.
|
|
1463
1579
|
await boundedCleanup(() => pasteCleanup).catch(error => settled.push(error));
|
|
1580
|
+
await boundedCleanup(() => this.#residentTabOptions?.gracefulStop?.(this.#currentSessionFile()) ?? Promise.resolve())
|
|
1581
|
+
.catch(error => settled.push(error));
|
|
1464
1582
|
await boundedCleanup(() => this.backend.extensions.unbindExtensionUi()).catch(error => settled.push(error));
|
|
1465
1583
|
return settled;
|
|
1466
1584
|
};
|
|
@@ -1641,7 +1759,8 @@ export class OwnedUiSessionPresenter {
|
|
|
1641
1759
|
this.#dialogId = dialog.id;
|
|
1642
1760
|
}
|
|
1643
1761
|
hotkeysPresentation() {
|
|
1644
|
-
|
|
1762
|
+
const presentation = this.root.hotkeysPresentation();
|
|
1763
|
+
return this.#residentTabOptions === undefined ? presentation : { ...presentation, residentTabs: true };
|
|
1645
1764
|
}
|
|
1646
1765
|
async #slashCommand(text) {
|
|
1647
1766
|
const body = text.slice(1).trim();
|
|
@@ -1920,16 +2039,15 @@ export class OwnedUiSessionShell extends OwnedUiSessionPresenter {
|
|
|
1920
2039
|
function isCoalescedStreamBlock(kind) {
|
|
1921
2040
|
return kind === "assistant" || kind === "thinking" || kind === "tool-call" || kind === "tool-result";
|
|
1922
2041
|
}
|
|
1923
|
-
function
|
|
2042
|
+
function copyCharacters(snapshot) {
|
|
1924
2043
|
if (snapshot.literal !== true)
|
|
1925
|
-
return
|
|
1926
|
-
let
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
return { characters, hasNonWhitespace };
|
|
2044
|
+
return snapshot.sourceUnits;
|
|
2045
|
+
let count = Math.max(0, snapshot.rows.length - 1), hasContent = false;
|
|
2046
|
+
for (const { text } of snapshot.rows) {
|
|
2047
|
+
count += text.length;
|
|
2048
|
+
hasContent ||= /\S/u.test(text);
|
|
2049
|
+
}
|
|
2050
|
+
return hasContent ? count : undefined;
|
|
1933
2051
|
}
|
|
1934
2052
|
export function formatSessionResumeCommand(metadata) {
|
|
1935
2053
|
const args = [PRODUCT_TEXT.commandName];
|
|
@@ -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): TranscriptViewportFrame | null;
|
|
67
|
+
composeDockOnly(dockRows: readonly string[], width: number, height: number, explicitCopyDockRows?: number): TranscriptViewportFrame | null;
|
|
68
68
|
/** Returns true when document wrapping may have changed. */
|
|
69
69
|
setConfig(config: OwnedUiViewportSettings): boolean;
|
|
70
70
|
resumeFollowing(): void;
|
|
@@ -165,8 +165,8 @@ export class SessionViewportController {
|
|
|
165
165
|
}
|
|
166
166
|
return frame;
|
|
167
167
|
}
|
|
168
|
-
composeDockOnly(dockRows, width, height) {
|
|
169
|
-
return this.#viewport.composeDockOnly(dockRows, width, height);
|
|
168
|
+
composeDockOnly(dockRows, width, height, explicitCopyDockRows = 0) {
|
|
169
|
+
return this.#viewport.composeDockOnly(dockRows, width, height, explicitCopyDockRows);
|
|
170
170
|
}
|
|
171
171
|
/** Returns true when document wrapping may have changed. */
|
|
172
172
|
setConfig(config) {
|
|
@@ -307,7 +307,7 @@ export class SessionViewportController {
|
|
|
307
307
|
this.#requestRender();
|
|
308
308
|
}
|
|
309
309
|
else if (this.#viewport.hasSelection) {
|
|
310
|
-
const copySelection = this.#viewport.captureSelectedText();
|
|
310
|
+
const copySelection = this.#viewport.captureSelectedText("explicit");
|
|
311
311
|
this.#viewport.clearSelection();
|
|
312
312
|
this.#stopSelectionAutoScroll();
|
|
313
313
|
this.#requestRender();
|
|
@@ -98,6 +98,8 @@ export declare class OwnedUiTerminalHost implements OwnedUiTerminalHostContract<
|
|
|
98
98
|
active(): OwnedUiHostedPresenter | null;
|
|
99
99
|
requestRender(from: OwnedUiHostedPresenter, force?: boolean): void;
|
|
100
100
|
start(): void;
|
|
101
|
+
/** Hidden resident tabs retain engine/status work but stop producing terminal animation frames. */
|
|
102
|
+
setPresentationVisible(visible: boolean): void;
|
|
101
103
|
/** Closes one presenter. Closing the last one ends the terminal. */
|
|
102
104
|
close(presenter: OwnedUiHostedPresenter): Promise<void>;
|
|
103
105
|
dispose(): Promise<void>;
|
|
@@ -42,6 +42,8 @@ export class OwnedUiTerminalHost {
|
|
|
42
42
|
#focused = false;
|
|
43
43
|
#pointerReporting = false;
|
|
44
44
|
#started = false;
|
|
45
|
+
#presentationVisible = true;
|
|
46
|
+
#releaseVisibilityHold;
|
|
45
47
|
#ending;
|
|
46
48
|
constructor(options) {
|
|
47
49
|
this.#customViewport = options.layout === "custom-viewport";
|
|
@@ -147,7 +149,7 @@ export class OwnedUiTerminalHost {
|
|
|
147
149
|
return this.#active;
|
|
148
150
|
}
|
|
149
151
|
requestRender(from, force = false) {
|
|
150
|
-
if (from === this.#active)
|
|
152
|
+
if (this.#presentationVisible && from === this.#active)
|
|
151
153
|
this.runtime.requestRender(force);
|
|
152
154
|
}
|
|
153
155
|
start() {
|
|
@@ -155,9 +157,26 @@ export class OwnedUiTerminalHost {
|
|
|
155
157
|
return;
|
|
156
158
|
this.#started = true;
|
|
157
159
|
this.runtime.start();
|
|
160
|
+
if (!this.#presentationVisible)
|
|
161
|
+
this.#releaseVisibilityHold = this.runtime.beginPresentationHold();
|
|
158
162
|
if (this.#customViewport)
|
|
159
163
|
this.#setPointerReporting(true);
|
|
160
164
|
}
|
|
165
|
+
/** Hidden resident tabs retain engine/status work but stop producing terminal animation frames. */
|
|
166
|
+
setPresentationVisible(visible) {
|
|
167
|
+
if (this.#presentationVisible === visible)
|
|
168
|
+
return;
|
|
169
|
+
this.#presentationVisible = visible;
|
|
170
|
+
if (!this.#started || this.#ending !== undefined)
|
|
171
|
+
return;
|
|
172
|
+
if (visible) {
|
|
173
|
+
this.#releaseVisibilityHold?.();
|
|
174
|
+
this.#releaseVisibilityHold = undefined;
|
|
175
|
+
}
|
|
176
|
+
else if (this.#releaseVisibilityHold === undefined) {
|
|
177
|
+
this.#releaseVisibilityHold = this.runtime.beginPresentationHold();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
161
180
|
/** Closes one presenter. Closing the last one ends the terminal. */
|
|
162
181
|
close(presenter) {
|
|
163
182
|
if (this.#ending !== undefined || !this.#presenters.has(presenter))
|
package/dist/cli/dispatch.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type InteractiveLaunchIntent, type LaunchProfileId, type SessionSelection } from "../features/launch/index.js";
|
|
1
|
+
import { type InteractiveLaunchIntent, type InteractiveLaunchMode, type LaunchProfileId, type SessionSelection } from "../features/launch/index.js";
|
|
2
2
|
import type { CliCapabilities } from "./capabilities.js";
|
|
3
3
|
import type { PackageCommandRequest } from "./packages.js";
|
|
4
4
|
import type { SessionContextRequest } from "./session-context.js";
|
|
5
|
+
import type { TabsRequest } from "./tabs-request.js";
|
|
5
6
|
import { type PackageCliVerb, type PackageCommandNamespace, type PackageSyntaxDiagnostic } from "./package-messages.js";
|
|
6
7
|
export type UpdateChannel = "stable" | "next";
|
|
7
8
|
export interface CliHandlers {
|
|
@@ -10,6 +11,7 @@ export interface CliHandlers {
|
|
|
10
11
|
readonly update: (channel: UpdateChannel, target?: string) => Promise<number>;
|
|
11
12
|
readonly packages: (request: PackageCommandRequest) => Promise<number>;
|
|
12
13
|
readonly sessionContext: (request: SessionContextRequest) => Promise<number>;
|
|
14
|
+
readonly tabs: (request: TabsRequest) => Promise<number>;
|
|
13
15
|
}
|
|
14
16
|
export interface CliOutput {
|
|
15
17
|
readonly stdout: (message: string) => void;
|
|
@@ -17,6 +19,8 @@ export interface CliOutput {
|
|
|
17
19
|
}
|
|
18
20
|
export declare function cliUsage(capabilities: CliCapabilities): string;
|
|
19
21
|
export declare function cliHelp(capabilities: CliCapabilities): string;
|
|
22
|
+
/** Focused help for the resident-tab maintenance forms; printing it contacts no host. */
|
|
23
|
+
export declare function tabsHelp(): string;
|
|
20
24
|
export declare function dispatchCli(arguments_: readonly string[], handlers: CliHandlers, output: CliOutput, capabilities: CliCapabilities): Promise<number>;
|
|
21
25
|
export type CliCommand = {
|
|
22
26
|
readonly kind: "noop";
|
|
@@ -33,6 +37,7 @@ export type CliCommand = {
|
|
|
33
37
|
} | {
|
|
34
38
|
readonly kind: "launch";
|
|
35
39
|
readonly profileId: LaunchProfileId;
|
|
40
|
+
readonly launchMode: InteractiveLaunchMode;
|
|
36
41
|
readonly sessionSelection?: SessionSelection;
|
|
37
42
|
} | {
|
|
38
43
|
readonly kind: "version";
|
|
@@ -46,6 +51,11 @@ export type CliCommand = {
|
|
|
46
51
|
} | {
|
|
47
52
|
readonly kind: "session-context";
|
|
48
53
|
readonly request: SessionContextRequest;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: "tabs";
|
|
56
|
+
readonly request: TabsRequest;
|
|
57
|
+
} | {
|
|
58
|
+
readonly kind: "tabs-help";
|
|
49
59
|
} | {
|
|
50
60
|
readonly kind: "error";
|
|
51
61
|
readonly message: string;
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -6,6 +6,7 @@ export function cliUsage(capabilities) {
|
|
|
6
6
|
"",
|
|
7
7
|
"--session <path|id>",
|
|
8
8
|
"--session-dir <dir> --session <path|id>",
|
|
9
|
+
"solo",
|
|
9
10
|
...(capabilities.developmentComparison ? ["pi"] : []),
|
|
10
11
|
"help",
|
|
11
12
|
"version",
|
|
@@ -21,6 +22,10 @@ export function cliUsage(capabilities) {
|
|
|
21
22
|
"session worktrees",
|
|
22
23
|
"session link-worktree <path>",
|
|
23
24
|
"session unlink-worktree",
|
|
25
|
+
"tabs",
|
|
26
|
+
"tabs stop <id>|--all",
|
|
27
|
+
"tabs host status|stop",
|
|
28
|
+
"tabs doctor",
|
|
24
29
|
]);
|
|
25
30
|
}
|
|
26
31
|
export function cliHelp(capabilities) {
|
|
@@ -30,6 +35,7 @@ export function cliHelp(capabilities) {
|
|
|
30
35
|
` ${command}`,
|
|
31
36
|
` ${command} --session <path|id>`,
|
|
32
37
|
` ${command} --session-dir <dir> --session <path|id>`,
|
|
38
|
+
` ${command} solo`,
|
|
33
39
|
...(capabilities.developmentComparison ? [` ${command} pi`] : []),
|
|
34
40
|
` ${command} help`,
|
|
35
41
|
` ${command} version`,
|
|
@@ -52,9 +58,32 @@ export function cliHelp(capabilities) {
|
|
|
52
58
|
` ${command} session link-worktree <path>`,
|
|
53
59
|
` ${command} session unlink-worktree`,
|
|
54
60
|
"",
|
|
61
|
+
"Tabs:",
|
|
62
|
+
` ${command} tabs`,
|
|
63
|
+
` ${command} tabs stop <id>|--all`,
|
|
64
|
+
` ${command} tabs host status|stop`,
|
|
65
|
+
` ${command} tabs doctor`,
|
|
66
|
+
"",
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
69
|
+
/** Focused help for the resident-tab maintenance forms; printing it contacts no host. */
|
|
70
|
+
export function tabsHelp() {
|
|
71
|
+
const command = PRODUCT_TEXT.commandName;
|
|
72
|
+
return [
|
|
73
|
+
"Resident tabs:",
|
|
74
|
+
` ${command} tabs List running tabs`,
|
|
75
|
+
` ${command} tabs stop <id> Stop one tab; its session stays resumable`,
|
|
76
|
+
` ${command} tabs stop --all Stop every tab and keep the host`,
|
|
77
|
+
` ${command} tabs host status Show host identity, limits, and health counters`,
|
|
78
|
+
` ${command} tabs host stop Stop every tab and the host`,
|
|
79
|
+
` ${command} tabs doctor Write a redacted diagnostic bundle`,
|
|
80
|
+
"",
|
|
81
|
+
"Options:",
|
|
82
|
+
" --json Print the versioned machine-readable report",
|
|
83
|
+
"",
|
|
55
84
|
].join("\n");
|
|
56
85
|
}
|
|
57
|
-
const PROFILE_WORDS = new Set(["pi"]);
|
|
86
|
+
const PROFILE_WORDS = new Set(["pi", "solo"]);
|
|
58
87
|
export async function dispatchCli(arguments_, handlers, output, capabilities) {
|
|
59
88
|
const command = parseCliCommand(arguments_, capabilities);
|
|
60
89
|
if (command.kind === "noop")
|
|
@@ -76,23 +105,29 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
|
|
|
76
105
|
return 2;
|
|
77
106
|
}
|
|
78
107
|
if (command.kind === "launch")
|
|
79
|
-
return await handlers.launch(interactiveLaunchIntent(command.profileId, command.sessionSelection));
|
|
108
|
+
return await handlers.launch(interactiveLaunchIntent(command.profileId, command.sessionSelection, command.launchMode));
|
|
80
109
|
if (command.kind === "version")
|
|
81
110
|
return await handlers.version();
|
|
82
111
|
if (command.kind === "packages")
|
|
83
112
|
return await handlers.packages(command.request);
|
|
84
113
|
if (command.kind === "session-context")
|
|
85
114
|
return await handlers.sessionContext(command.request);
|
|
115
|
+
if (command.kind === "tabs-help") {
|
|
116
|
+
output.stdout(tabsHelp());
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
if (command.kind === "tabs")
|
|
120
|
+
return await handlers.tabs(command.request);
|
|
86
121
|
return await handlers.update(command.channel, command.target);
|
|
87
122
|
}
|
|
88
123
|
export function parseCliCommand(arguments_, capabilities) {
|
|
89
124
|
if (arguments_.length === 0)
|
|
90
|
-
return { kind: "launch", profileId: "a1" };
|
|
125
|
+
return { kind: "launch", profileId: "a1", launchMode: "tabs" };
|
|
91
126
|
const [command, ...rest] = arguments_;
|
|
92
127
|
if (command === "--session" || command === "--session-dir") {
|
|
93
128
|
try {
|
|
94
129
|
const sessionSelection = parseSessionSelection(arguments_);
|
|
95
|
-
return { kind: "launch", profileId: "a1", ...(sessionSelection === undefined ? {} : { sessionSelection }) };
|
|
130
|
+
return { kind: "launch", profileId: "a1", launchMode: "tabs", ...(sessionSelection === undefined ? {} : { sessionSelection }) };
|
|
96
131
|
}
|
|
97
132
|
catch (error) {
|
|
98
133
|
return { kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse session launch: ${error instanceof Error ? error.message : String(error)}`) };
|
|
@@ -108,18 +143,57 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
108
143
|
if (command === "pi") {
|
|
109
144
|
if (rest.length > 0)
|
|
110
145
|
return parsePackageCommand(rest, "pi");
|
|
111
|
-
return capabilities.developmentComparison ? { kind: "launch", profileId: "pi" } : { kind: "noop" };
|
|
146
|
+
return capabilities.developmentComparison ? { kind: "launch", profileId: "pi", launchMode: "solo" } : { kind: "noop" };
|
|
112
147
|
}
|
|
148
|
+
if (command === "solo")
|
|
149
|
+
return withoutArguments(rest, { kind: "launch", profileId: "a1", launchMode: "solo" });
|
|
113
150
|
if (command === "update")
|
|
114
151
|
return parseUpdate(rest);
|
|
115
152
|
if (command === "session")
|
|
116
153
|
return parseSessionContext(rest);
|
|
154
|
+
if (command === "tabs")
|
|
155
|
+
return parseTabs(rest);
|
|
117
156
|
if (command?.startsWith("update:"))
|
|
118
157
|
return { kind: "noop" };
|
|
119
158
|
// Rationale: unsupported and reserved command spaces are deliberately quiet. Help is
|
|
120
159
|
// explicit, and a typo must never start an interactive or maintenance path.
|
|
121
160
|
return { kind: "noop" };
|
|
122
161
|
}
|
|
162
|
+
/** Parses `a1 tabs` before any I/O; a malformed form fails with one line and contacts no host. */
|
|
163
|
+
function parseTabs(rest) {
|
|
164
|
+
if (rest.some(value => value === "--help" || value === "-h"))
|
|
165
|
+
return { kind: "tabs-help" };
|
|
166
|
+
const json = rest.includes("--json");
|
|
167
|
+
const words = rest.filter(value => value !== "--json");
|
|
168
|
+
const fail = (detail) => ({ kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse tabs: ${detail}`) });
|
|
169
|
+
const [action, value, extra] = words;
|
|
170
|
+
if (action === undefined)
|
|
171
|
+
return { kind: "tabs", request: { action: "list", json } };
|
|
172
|
+
if (action === "stop") {
|
|
173
|
+
if (value === undefined)
|
|
174
|
+
return fail("stop requires a tab id or --all.");
|
|
175
|
+
if (extra !== undefined)
|
|
176
|
+
return fail("stop accepts one tab id or --all.");
|
|
177
|
+
if (value === "--all")
|
|
178
|
+
return { kind: "tabs", request: { action: "stop-all", json } };
|
|
179
|
+
if (value.startsWith("-"))
|
|
180
|
+
return fail(`unknown option ${value}.`);
|
|
181
|
+
return { kind: "tabs", request: { action: "stop", tabId: value, json } };
|
|
182
|
+
}
|
|
183
|
+
if (action === "host") {
|
|
184
|
+
if (extra !== undefined || (value !== "status" && value !== "stop"))
|
|
185
|
+
return fail("host accepts status or stop.");
|
|
186
|
+
return { kind: "tabs", request: { action: value === "status" ? "host-status" : "host-stop", json } };
|
|
187
|
+
}
|
|
188
|
+
if (action === "doctor") {
|
|
189
|
+
if (value !== undefined)
|
|
190
|
+
return fail("doctor does not accept arguments.");
|
|
191
|
+
return { kind: "tabs", request: { action: "doctor", json } };
|
|
192
|
+
}
|
|
193
|
+
if (action.startsWith("-"))
|
|
194
|
+
return fail(`unknown option ${action}.`);
|
|
195
|
+
return fail(`unknown subcommand ${action}; use list, stop, host, or doctor.`);
|
|
196
|
+
}
|
|
123
197
|
function parseSessionContext(rest) {
|
|
124
198
|
const [action, value, extra] = rest;
|
|
125
199
|
if (action === "link-worktree") {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type TabsAction = "list" | "stop" | "stop-all" | "host-status" | "host-stop" | "doctor";
|
|
2
|
+
/** One parsed `a1 tabs` form. Parsing finishes before any host is contacted. */
|
|
3
|
+
export interface TabsRequest {
|
|
4
|
+
readonly action: TabsAction;
|
|
5
|
+
readonly tabId?: string;
|
|
6
|
+
readonly json: boolean;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { runTerminalHostMaintenance, writeDoctorBundle } from "../foundation/resident-tabs/index.js";
|
|
2
|
+
import type { TabsRequest } from "./tabs-request.js";
|
|
3
|
+
export type { TabsRequest } from "./tabs-request.js";
|
|
4
|
+
export interface TabsCommandOptions {
|
|
5
|
+
/** The running package root; its terminal host is a candidate after the active release. */
|
|
6
|
+
readonly packageRoot?: string;
|
|
7
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
8
|
+
readonly stdout?: (message: string) => void;
|
|
9
|
+
readonly stderr?: (message: string) => void;
|
|
10
|
+
readonly maintenance?: typeof runTerminalHostMaintenance;
|
|
11
|
+
readonly doctor?: typeof writeDoctorBundle;
|
|
12
|
+
}
|
|
13
|
+
/** `a1 tabs`: inspect or stop resident tabs without attaching, and never start a host to report state. */
|
|
14
|
+
export declare function runTabsCommand(request: TabsRequest, options?: TabsCommandOptions): Promise<number>;
|