pi-ui-extend 1.0.41 → 1.0.45
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 +50 -13
- package/dist/app/app.d.ts +13 -0
- package/dist/app/app.js +158 -8
- package/dist/app/cli/install.js +2 -2
- package/dist/app/commands/command-controller.d.ts +4 -0
- package/dist/app/commands/command-controller.js +11 -0
- package/dist/app/commands/command-git-actions.d.ts +25 -0
- package/dist/app/commands/command-git-actions.js +381 -0
- package/dist/app/commands/command-host.d.ts +4 -0
- package/dist/app/commands/command-host.js +5 -2
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +57 -35
- package/dist/app/commands/command-navigation-actions.d.ts +1 -0
- package/dist/app/commands/command-navigation-actions.js +16 -3
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +15 -1
- package/dist/app/commands/command-session-actions.js +9 -3
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +70 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- package/dist/app/input/autocomplete-controller.js +61 -34
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- package/dist/app/input/input-controller.d.ts +2 -1
- package/dist/app/input/input-controller.js +7 -2
- package/dist/app/input/prompt-enhancer-controller.js +53 -35
- package/dist/app/input/voice-controller.d.ts +41 -45
- package/dist/app/input/voice-controller.js +351 -419
- package/dist/app/model/model-usage-controller.js +17 -7
- package/dist/app/model/model-usage-status.d.ts +4 -1
- package/dist/app/model/model-usage-status.js +270 -46
- package/dist/app/popup/menu-items-controller.d.ts +13 -3
- package/dist/app/popup/menu-items-controller.js +37 -21
- package/dist/app/popup/popup-action-controller.d.ts +12 -2
- package/dist/app/popup/popup-action-controller.js +77 -24
- package/dist/app/popup/popup-menu-controller.d.ts +36 -14
- package/dist/app/popup/popup-menu-controller.js +239 -69
- package/dist/app/rendering/dcp-stats.d.ts +9 -4
- package/dist/app/rendering/dcp-stats.js +40 -425
- package/dist/app/rendering/editor-panels.js +10 -4
- package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
- package/dist/app/rendering/popup-menu-renderer.js +42 -37
- package/dist/app/rendering/render-controller.js +23 -2
- package/dist/app/rendering/status-line-renderer.d.ts +5 -0
- package/dist/app/rendering/status-line-renderer.js +41 -5
- package/dist/app/rendering/tab-line-renderer.js +26 -20
- package/dist/app/runtime.d.ts +12 -1
- package/dist/app/runtime.js +120 -12
- package/dist/app/screen/mouse-controller.d.ts +2 -0
- package/dist/app/screen/mouse-controller.js +17 -7
- package/dist/app/screen/status-controller.d.ts +4 -0
- package/dist/app/screen/status-controller.js +5 -0
- package/dist/app/session/lazy-session-manager.js +34 -0
- package/dist/app/session/session-event-controller.d.ts +1 -0
- package/dist/app/session/session-event-controller.js +10 -1
- package/dist/app/session/session-history.d.ts +1 -0
- package/dist/app/session/session-history.js +12 -1
- package/dist/app/session/session-lifecycle-controller.d.ts +7 -1
- package/dist/app/session/session-lifecycle-controller.js +16 -1
- package/dist/app/session/tabs-controller.d.ts +26 -1
- package/dist/app/session/tabs-controller.js +454 -147
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +1 -0
- package/dist/app/subagents/subagents-model.js +18 -2
- package/dist/app/subagents/subagents-widget-controller.d.ts +1 -0
- package/dist/app/subagents/subagents-widget-controller.js +6 -0
- package/dist/app/types.d.ts +16 -1
- package/dist/app/workspace/workspace-actions-controller.js +10 -2
- package/dist/app/workspace/workspace-undo.d.ts +1 -0
- package/dist/app/workspace/workspace-undo.js +1 -0
- package/dist/bundled-extensions/question/index.js +9 -1
- package/dist/bundled-extensions/question/remote.d.ts +4 -0
- package/dist/bundled-extensions/question/remote.js +33 -0
- package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
- package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
- package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
- package/dist/bundled-extensions/telegram-connector/config.js +87 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
- package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
- package/dist/bundled-extensions/telegram-connector/index.js +195 -0
- package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
- package/dist/bundled-extensions/terminal-bell/index.js +0 -76
- package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
- package/dist/bundled-extensions/workspace-undo/index.js +191 -0
- package/dist/config.d.ts +21 -2
- package/dist/config.js +159 -32
- package/dist/default-pix-config.js +25 -5
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +88 -62
- package/dist/schemas/pi-tools-suite-schema.js +54 -83
- package/dist/schemas/pix-schema.d.ts +19 -2
- package/dist/schemas/pix-schema.js +47 -5
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +9 -1
- package/docs/desktop-mvp.md +22 -6
- package/docs/desktop-task-manager.md +148 -84
- package/docs/release.md +30 -6
- package/external/pi-tools-suite/README.md +197 -84
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +47 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +48 -30
- package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
- package/external/pi-tools-suite/package.json +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
- package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
- package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
- package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
- package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
- package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/browser.mjs +346 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
- package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
- package/external/pi-tools-suite/src/async-subagents/commands.ts +15 -71
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +8 -5
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +160 -327
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +21 -5
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +40 -26
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +16 -9
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -5
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
- package/external/pi-tools-suite/src/config.ts +55 -1
- package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +77 -3
- package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +222 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +206 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +7 -7
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +16 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +128 -49
- package/external/pi-tools-suite/src/dcp/commands.ts +32 -156
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +177 -46
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
- package/external/pi-tools-suite/src/dcp/config.ts +114 -17
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
- package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
- package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
- package/external/pi-tools-suite/src/dcp/index.ts +338 -66
- package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
- package/external/pi-tools-suite/src/dcp/prompts.ts +83 -192
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +93 -3
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
- package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -0
- package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
- package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +78 -22
- package/external/pi-tools-suite/src/index.ts +7 -1
- package/external/pi-tools-suite/src/lib/project.ts +36 -1
- package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
- package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
- package/external/pi-tools-suite/src/resource-registry/index.ts +2551 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/todo/index.ts +21 -0
- package/external/pi-tools-suite/src/todo/todo.ts +19 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +47 -19
- package/package.json +10 -9
- package/schemas/pi-tools-suite.json +466 -287
- package/schemas/pix.json +129 -11
- package/schemas/tasks.json +131 -0
- package/skills/simplify/SKILL.md +33 -5
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -177
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
- package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
- package/skills/playwright-cli/SKILL.md +0 -420
- package/skills/playwright-cli/references/element-attributes.md +0 -23
- package/skills/playwright-cli/references/playwright-tests.md +0 -50
- package/skills/playwright-cli/references/request-mocking.md +0 -87
- package/skills/playwright-cli/references/running-code.md +0 -241
- package/skills/playwright-cli/references/session-management.md +0 -273
- package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
- package/skills/playwright-cli/references/storage-state.md +0 -290
- package/skills/playwright-cli/references/test-generation.md +0 -142
- package/skills/playwright-cli/references/tracing.md +0 -154
- package/skills/playwright-cli/references/video-recording.md +0 -147
- package/skills/spec-lite/SKILL.md +0 -140
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.mjs +0 -0
|
@@ -32,6 +32,7 @@ export class AppTabsController {
|
|
|
32
32
|
tabIdsNeedingHistoryReload = new Set();
|
|
33
33
|
historyInvalidationGenerationByTabId = new Map();
|
|
34
34
|
runtimeOwnershipGenerationByTabId = new Map();
|
|
35
|
+
touchedDraftTabIds = new Set();
|
|
35
36
|
orphanRuntimeDisposals = new WeakSet();
|
|
36
37
|
lifecycleMutationQueue = [];
|
|
37
38
|
activeTabId;
|
|
@@ -42,6 +43,9 @@ export class AppTabsController {
|
|
|
42
43
|
lifecycleGeneration = 0;
|
|
43
44
|
lifecycleMutationRunning = false;
|
|
44
45
|
saveTabsWriteTail = Promise.resolve();
|
|
46
|
+
startupSavedTabs;
|
|
47
|
+
startupPrepared = false;
|
|
48
|
+
startupRuntimePlan;
|
|
45
49
|
restored = false;
|
|
46
50
|
retentionCleanupRunning = false;
|
|
47
51
|
retentionCleanupScheduled = false;
|
|
@@ -127,6 +131,22 @@ export class AppTabsController {
|
|
|
127
131
|
this.syncActiveTabFromRuntime({ save: false });
|
|
128
132
|
return this.activeTabId;
|
|
129
133
|
}
|
|
134
|
+
isDraftTabActive() {
|
|
135
|
+
return this.activeTab()?.draft === true;
|
|
136
|
+
}
|
|
137
|
+
openSessionPaths() {
|
|
138
|
+
return this.tabItems.flatMap((tab) => tab.sessionPath ? [resolve(tab.sessionPath)] : []);
|
|
139
|
+
}
|
|
140
|
+
noteActiveDraftInputChanged() {
|
|
141
|
+
const tab = this.activeTab();
|
|
142
|
+
if (!tab?.draft)
|
|
143
|
+
return;
|
|
144
|
+
const input = this.host.captureInputState();
|
|
145
|
+
if (input.text.length === 0 && (input.attachments?.length ?? 0) === 0)
|
|
146
|
+
return;
|
|
147
|
+
this.touchedDraftTabIds.add(tab.id);
|
|
148
|
+
this.host.closeDraftSessionSelector?.();
|
|
149
|
+
}
|
|
130
150
|
inputStateForTab(tabId) {
|
|
131
151
|
if (!tabId)
|
|
132
152
|
return this.host.captureInputState();
|
|
@@ -233,6 +253,26 @@ export class AppTabsController {
|
|
|
233
253
|
if (options.save !== false)
|
|
234
254
|
void this.saveTabs();
|
|
235
255
|
}
|
|
256
|
+
async prepareStartupRuntime() {
|
|
257
|
+
if (this.startupPrepared)
|
|
258
|
+
return this.startupRuntimePlan ?? null;
|
|
259
|
+
this.startupPrepared = true;
|
|
260
|
+
if (this.host.options.noSession) {
|
|
261
|
+
this.startupRuntimePlan = {};
|
|
262
|
+
return this.startupRuntimePlan;
|
|
263
|
+
}
|
|
264
|
+
if (this.host.options.sessionPath) {
|
|
265
|
+
this.startupRuntimePlan = { sessionPath: resolve(this.host.options.sessionPath) };
|
|
266
|
+
return this.startupRuntimePlan;
|
|
267
|
+
}
|
|
268
|
+
const saved = await this.loadTabs();
|
|
269
|
+
this.startupSavedTabs = saved ?? null;
|
|
270
|
+
const paths = saved?.tabs.map((tab) => resolve(tab.path)).filter((path) => existsSync(path)) ?? [];
|
|
271
|
+
const activePath = saved?.activePath ? resolve(saved.activePath) : undefined;
|
|
272
|
+
const sessionPath = activePath && paths.includes(activePath) ? activePath : paths[0];
|
|
273
|
+
this.startupRuntimePlan = sessionPath ? { sessionPath } : null;
|
|
274
|
+
return this.startupRuntimePlan;
|
|
275
|
+
}
|
|
236
276
|
restoreAfterStartup() {
|
|
237
277
|
return this.runLifecycleMutation((generation) => this.restoreAfterStartupMutation(generation));
|
|
238
278
|
}
|
|
@@ -241,15 +281,24 @@ export class AppTabsController {
|
|
|
241
281
|
return;
|
|
242
282
|
this.restored = true;
|
|
243
283
|
const runtime = this.host.runtime();
|
|
244
|
-
|
|
284
|
+
const saved = this.startupSavedTabs === undefined
|
|
285
|
+
? await this.loadTabs()
|
|
286
|
+
: this.startupSavedTabs ?? undefined;
|
|
287
|
+
if (!runtime) {
|
|
288
|
+
if (this.host.options.noSession)
|
|
289
|
+
return;
|
|
290
|
+
const draft = this.createDraftTab();
|
|
291
|
+
this.tabItems.splice(0, this.tabItems.length, draft);
|
|
292
|
+
this.activateDraftTab(draft, { resetInput: true, openSelector: true });
|
|
293
|
+
await this.saveTabs();
|
|
245
294
|
return;
|
|
295
|
+
}
|
|
246
296
|
const startupSession = runtime.session;
|
|
247
297
|
this.syncActiveTabFromRuntime({ save: false });
|
|
248
298
|
if (this.host.options.noSession) {
|
|
249
299
|
this.clearStartupTabPlaceholders();
|
|
250
300
|
return;
|
|
251
301
|
}
|
|
252
|
-
const saved = await this.loadTabs();
|
|
253
302
|
if (!this.isLifecycleOwner(generation, runtime, startupSession)) {
|
|
254
303
|
this.syncActiveTabFromRuntime({ save: false, force: true });
|
|
255
304
|
return;
|
|
@@ -350,6 +399,12 @@ export class AppTabsController {
|
|
|
350
399
|
this.scheduleProjectSessionRetention();
|
|
351
400
|
this.scheduleTabPrewarm();
|
|
352
401
|
await this.loadActiveSessionHistory(restoredRuntime, generation);
|
|
402
|
+
if (restoredRuntime.session.messages.length === 0) {
|
|
403
|
+
await this.host.awaitCurrentSessionExtensions?.(restoredRuntime);
|
|
404
|
+
const text = this.host.contextInventoryText?.(restoredRuntime, `Opened an empty tab. cwd=${restoredRuntime.cwd}`);
|
|
405
|
+
if (text)
|
|
406
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
407
|
+
}
|
|
353
408
|
this.scheduleRestoredTabTitleRefresh(restoredSessionPaths, generation);
|
|
354
409
|
}
|
|
355
410
|
openNewTab() {
|
|
@@ -360,133 +415,181 @@ export class AppTabsController {
|
|
|
360
415
|
this.host.showToast("Wait for the tab to finish loading", "info");
|
|
361
416
|
return;
|
|
362
417
|
}
|
|
363
|
-
|
|
364
|
-
|
|
418
|
+
if (this.host.options.noSession) {
|
|
419
|
+
this.host.showToast("/new_tab is unavailable with --no-session", "warning");
|
|
365
420
|
return;
|
|
366
|
-
|
|
421
|
+
}
|
|
422
|
+
const existingDraft = this.tabItems.find((tab) => tab.draft === true);
|
|
423
|
+
if (existingDraft) {
|
|
424
|
+
await this.switchToTabMutation(existingDraft.id, generation);
|
|
367
425
|
return;
|
|
426
|
+
}
|
|
427
|
+
const runtime = this.host.runtime();
|
|
368
428
|
this.cancelHistoryLoad();
|
|
369
|
-
|
|
370
|
-
|
|
429
|
+
if (runtime) {
|
|
430
|
+
this.syncActiveTabFromRuntime({ save: false });
|
|
431
|
+
this.storeActiveRuntime(runtime);
|
|
432
|
+
this.storeActiveSessionView();
|
|
433
|
+
}
|
|
371
434
|
this.storeActiveInputState();
|
|
372
435
|
this.storeActiveDeferredUserMessages();
|
|
373
|
-
const
|
|
374
|
-
const previousRuntime = runtime;
|
|
375
|
-
const previousSession = runtime.session;
|
|
376
|
-
const tab = {
|
|
377
|
-
id: createId("tab"),
|
|
378
|
-
title: "new",
|
|
379
|
-
titlePlaceholder: "new",
|
|
380
|
-
status: "active",
|
|
381
|
-
activity: "thinking",
|
|
382
|
-
};
|
|
436
|
+
const tab = this.createDraftTab();
|
|
383
437
|
this.tabItems.push(tab);
|
|
384
|
-
this.
|
|
438
|
+
this.activateDraftTab(tab, { resetInput: true, openSelector: true });
|
|
439
|
+
await this.saveTabs();
|
|
440
|
+
}
|
|
441
|
+
materializeActiveDraftTab() {
|
|
442
|
+
return this.runLifecycleMutation((generation) => this.materializeActiveDraftTabMutation(generation));
|
|
443
|
+
}
|
|
444
|
+
async materializeActiveDraftTabMutation(generation) {
|
|
445
|
+
if (this.pendingActiveTabId)
|
|
446
|
+
return undefined;
|
|
447
|
+
const tab = this.activeTab();
|
|
448
|
+
if (!tab?.draft)
|
|
449
|
+
return this.host.runtime();
|
|
450
|
+
if (this.host.options.noSession) {
|
|
451
|
+
this.host.showToast("A persisted session is unavailable with --no-session", "warning");
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
this.storeActiveInputState();
|
|
455
|
+
this.storeActiveDeferredUserMessages();
|
|
385
456
|
this.pendingActiveTabId = tab.id;
|
|
386
|
-
this.
|
|
387
|
-
this.
|
|
388
|
-
this.host.closeMenusForTabSwitch?.();
|
|
389
|
-
this.host.resetSessionView();
|
|
390
|
-
this.restoreDeferredUserMessages(tab.id);
|
|
457
|
+
this.host.closeDraftSessionSelector?.();
|
|
458
|
+
this.host.setStatus("starting new session");
|
|
391
459
|
this.host.setSessionActivity("thinking");
|
|
392
|
-
this.host.setStatus("starting new tab");
|
|
393
460
|
this.host.render();
|
|
394
461
|
let newRuntime;
|
|
395
462
|
try {
|
|
396
|
-
newRuntime = await this.host.createRuntimeForNewSession();
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.
|
|
402
|
-
this.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
463
|
+
newRuntime = await this.host.createRuntimeForNewSession(this.host.draftModelOverrideRef?.());
|
|
464
|
+
if (!this.ownsDraftLifecycle(tab.id, generation)) {
|
|
465
|
+
await this.disposeRuntimeIfOrphan(newRuntime);
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
469
|
+
if (!this.ownsDraftLifecycle(tab.id, generation) || this.host.runtime() !== newRuntime) {
|
|
470
|
+
if (this.host.runtime() === newRuntime)
|
|
471
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
472
|
+
await this.disposeRuntimeIfOrphan(newRuntime);
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
await this.host.awaitCurrentSessionExtensions?.(newRuntime);
|
|
476
|
+
if (!this.ownsDraftLifecycle(tab.id, generation) || this.host.runtime() !== newRuntime) {
|
|
477
|
+
if (this.host.runtime() === newRuntime)
|
|
478
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
479
|
+
await this.disposeRuntimeIfOrphan(newRuntime);
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
this.pendingActiveTabId = undefined;
|
|
483
|
+
delete tab.draft;
|
|
484
|
+
delete tab.titlePlaceholder;
|
|
485
|
+
this.touchedDraftTabIds.delete(tab.id);
|
|
486
|
+
this.updateTabFromSession(tab, newRuntime.session);
|
|
487
|
+
this.setRuntimeForTab(tab.id, newRuntime);
|
|
488
|
+
this.host.closeDraftSessionSelector?.();
|
|
406
489
|
this.host.resetSessionView();
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
this.host.
|
|
410
|
-
this.
|
|
411
|
-
this.
|
|
490
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
491
|
+
this.host.setSessionStatus(newRuntime.session);
|
|
492
|
+
this.host.setSessionActivity(this.sessionActivity(newRuntime.session));
|
|
493
|
+
void this.saveTabs();
|
|
494
|
+
this.scheduleProjectSessionRetention();
|
|
412
495
|
this.host.render();
|
|
413
|
-
|
|
496
|
+
return newRuntime;
|
|
414
497
|
}
|
|
415
|
-
|
|
498
|
+
catch {
|
|
499
|
+
if (newRuntime && this.host.runtime() === newRuntime)
|
|
500
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
501
|
+
if (newRuntime)
|
|
502
|
+
await this.disposeRuntimeIfOrphan(newRuntime);
|
|
416
503
|
if (this.pendingActiveTabId === tab.id)
|
|
417
504
|
this.pendingActiveTabId = undefined;
|
|
418
|
-
this.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
return;
|
|
505
|
+
if (this.tabItems.some((item) => item.id === tab.id) && this.activeTabId === tab.id) {
|
|
506
|
+
this.activateDraftTab(tab, { openSelector: !this.touchedDraftTabIds.has(tab.id) });
|
|
507
|
+
this.host.showToast("Could not start new conversation", "error");
|
|
508
|
+
}
|
|
509
|
+
return undefined;
|
|
423
510
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
this.pendingActiveTabId
|
|
430
|
-
|
|
431
|
-
this.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
this.
|
|
511
|
+
}
|
|
512
|
+
openSessionInActiveDraftTab(sessionPath) {
|
|
513
|
+
return this.runLifecycleMutation((generation) => this.openSessionInActiveDraftTabMutation(sessionPath, generation));
|
|
514
|
+
}
|
|
515
|
+
async openSessionInActiveDraftTabMutation(sessionPath, generation) {
|
|
516
|
+
if (this.pendingActiveTabId)
|
|
517
|
+
return false;
|
|
518
|
+
const draft = this.activeTab();
|
|
519
|
+
if (!draft?.draft)
|
|
520
|
+
return false;
|
|
521
|
+
const resolvedSessionPath = resolve(this.host.options.cwd, sessionPath);
|
|
522
|
+
const existing = this.findTabBySessionPath(resolvedSessionPath);
|
|
523
|
+
if (existing) {
|
|
524
|
+
await this.switchDraftToPersistedTab(existing, generation);
|
|
525
|
+
if (this.activeTabId !== existing.id)
|
|
526
|
+
return false;
|
|
527
|
+
this.removeTab(draft.id);
|
|
528
|
+
this.inputStatesByTabId.delete(draft.id);
|
|
529
|
+
this.autoUserMessagesByTabId.delete(draft.id);
|
|
530
|
+
this.deferredUserMessagesByTabId.delete(draft.id);
|
|
531
|
+
this.touchedDraftTabIds.delete(draft.id);
|
|
532
|
+
void this.saveTabs();
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
this.cancelHistoryLoad();
|
|
536
|
+
this.storeActiveInputState();
|
|
537
|
+
this.pendingActiveTabId = draft.id;
|
|
538
|
+
this.host.closeDraftSessionSelector?.();
|
|
539
|
+
this.host.setStatus("opening session");
|
|
435
540
|
this.host.setSessionActivity("thinking");
|
|
436
541
|
this.host.render();
|
|
542
|
+
let targetRuntime;
|
|
437
543
|
try {
|
|
438
|
-
await this.host.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.pendingActiveTabId = undefined;
|
|
443
|
-
if (targetTab === tab)
|
|
444
|
-
this.removeTab(tab.id);
|
|
445
|
-
this.activeTabId = previousTabId;
|
|
446
|
-
if (this.host.runtime() !== previousRuntime) {
|
|
447
|
-
try {
|
|
448
|
-
await this.host.activateRuntime(previousRuntime, { awaitExtensions: false });
|
|
449
|
-
}
|
|
450
|
-
catch {
|
|
451
|
-
// Keep the best available runtime and preserve the original activation error.
|
|
452
|
-
}
|
|
544
|
+
targetRuntime = await this.host.createRuntimeForSession(resolvedSessionPath);
|
|
545
|
+
if (!this.ownsDraftLifecycle(draft.id, generation)) {
|
|
546
|
+
await this.disposeRuntimeIfOrphan(targetRuntime);
|
|
547
|
+
return false;
|
|
453
548
|
}
|
|
454
|
-
await this.
|
|
455
|
-
if (this.
|
|
456
|
-
this.host.
|
|
457
|
-
|
|
458
|
-
this.
|
|
459
|
-
|
|
549
|
+
await this.host.activateRuntime(targetRuntime, { awaitExtensions: false });
|
|
550
|
+
if (!this.ownsDraftLifecycle(draft.id, generation) || this.host.runtime() !== targetRuntime) {
|
|
551
|
+
if (this.host.runtime() === targetRuntime)
|
|
552
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
553
|
+
await this.disposeRuntimeIfOrphan(targetRuntime);
|
|
554
|
+
return false;
|
|
460
555
|
}
|
|
461
|
-
|
|
462
|
-
|
|
556
|
+
this.pendingActiveTabId = undefined;
|
|
557
|
+
delete draft.draft;
|
|
558
|
+
delete draft.titlePlaceholder;
|
|
559
|
+
this.touchedDraftTabIds.delete(draft.id);
|
|
560
|
+
this.updateTabFromSession(draft, targetRuntime.session);
|
|
561
|
+
this.setRuntimeForTab(draft.id, targetRuntime);
|
|
562
|
+
this.inputStatesByTabId.delete(draft.id);
|
|
563
|
+
this.autoUserMessagesByTabId.delete(draft.id);
|
|
564
|
+
this.deferredUserMessagesByTabId.delete(draft.id);
|
|
565
|
+
this.restoreInputState(draft.id);
|
|
463
566
|
this.host.closeMenusForTabSwitch?.();
|
|
464
|
-
this.
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
this.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
567
|
+
void this.saveTabs();
|
|
568
|
+
this.scheduleProjectSessionRetention();
|
|
569
|
+
await this.loadActiveSessionHistory(targetRuntime, generation);
|
|
570
|
+
this.scheduleDelayedHistoryReload(draft.id, targetRuntime, generation);
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
if (targetRuntime && this.host.runtime() === targetRuntime)
|
|
575
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
576
|
+
if (targetRuntime)
|
|
577
|
+
await this.disposeRuntimeIfOrphan(targetRuntime);
|
|
578
|
+
if (this.pendingActiveTabId === draft.id)
|
|
579
|
+
this.pendingActiveTabId = undefined;
|
|
580
|
+
if (this.tabItems.some((item) => item.id === draft.id) && this.activeTabId === draft.id) {
|
|
581
|
+
this.activateDraftTab(draft, { openSelector: true });
|
|
582
|
+
this.host.showToast("Could not open conversation", "warning");
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
472
585
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
this.
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
this.host.addEntry({ id: createId("system"), kind: "system", text: `Opened a new tab. cwd=${newRuntime.cwd}` });
|
|
481
|
-
if (newRuntime.modelFallbackMessage)
|
|
482
|
-
this.host.addEntry({ id: createId("system"), kind: "system", text: newRuntime.modelFallbackMessage });
|
|
483
|
-
for (const diag of newRuntime.diagnostics ?? []) {
|
|
484
|
-
const kind = diag.type === "error" ? "error" : "system";
|
|
485
|
-
this.host.addEntry({ id: createId("system"), kind, text: `[${diag.type}] ${diag.message}` });
|
|
486
|
-
}
|
|
487
|
-
this.host.setSessionStatus(newRuntime.session);
|
|
488
|
-
this.host.setSessionActivity(this.sessionActivity(newRuntime.session));
|
|
489
|
-
this.host.render();
|
|
586
|
+
}
|
|
587
|
+
ownsDraftLifecycle(tabId, generation) {
|
|
588
|
+
return this.host.isRunning()
|
|
589
|
+
&& generation === this.lifecycleGeneration
|
|
590
|
+
&& this.activeTabId === tabId
|
|
591
|
+
&& this.pendingActiveTabId === tabId
|
|
592
|
+
&& this.tabItems.some((tab) => tab.id === tabId && tab.draft === true);
|
|
490
593
|
}
|
|
491
594
|
openSessionInNewTab(sessionPath) {
|
|
492
595
|
return this.runLifecycleMutation((generation) => this.openSessionInNewTabMutation(sessionPath, generation));
|
|
@@ -571,6 +674,7 @@ export class AppTabsController {
|
|
|
571
674
|
this.host.render();
|
|
572
675
|
try {
|
|
573
676
|
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
677
|
+
await this.host.awaitCurrentSessionExtensions?.(newRuntime);
|
|
574
678
|
}
|
|
575
679
|
catch {
|
|
576
680
|
this.pendingActiveTabId = undefined;
|
|
@@ -615,6 +719,11 @@ export class AppTabsController {
|
|
|
615
719
|
void this.saveTabs();
|
|
616
720
|
this.scheduleTabPrewarm();
|
|
617
721
|
await this.loadActiveSessionHistory(newRuntime, generation);
|
|
722
|
+
if (newRuntime.session.messages.length === 0) {
|
|
723
|
+
const text = this.host.contextInventoryText?.(newRuntime, `Opened an empty tab. cwd=${newRuntime.cwd}`);
|
|
724
|
+
if (text)
|
|
725
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
726
|
+
}
|
|
618
727
|
return true;
|
|
619
728
|
}
|
|
620
729
|
forkSessionEntryInNewTab(entryId) {
|
|
@@ -763,17 +872,34 @@ export class AppTabsController {
|
|
|
763
872
|
}
|
|
764
873
|
if (tabId === this.activeTabId)
|
|
765
874
|
return;
|
|
766
|
-
const runtime = this.runtimeForCommand("tab switch");
|
|
767
|
-
if (!runtime)
|
|
768
|
-
return;
|
|
769
|
-
this.syncActiveTabFromRuntime({ save: false });
|
|
770
875
|
const target = this.tabItems.find((tab) => tab.id === tabId);
|
|
771
876
|
if (!target)
|
|
772
877
|
return;
|
|
878
|
+
if (target.draft) {
|
|
879
|
+
const runtime = this.host.runtime();
|
|
880
|
+
if (runtime) {
|
|
881
|
+
this.syncActiveTabFromRuntime({ save: false });
|
|
882
|
+
this.storeActiveRuntime(runtime);
|
|
883
|
+
this.storeActiveSessionView();
|
|
884
|
+
}
|
|
885
|
+
this.storeActiveInputState();
|
|
886
|
+
this.storeActiveDeferredUserMessages();
|
|
887
|
+
this.activateDraftTab(target, { openSelector: !this.touchedDraftTabIds.has(target.id) });
|
|
888
|
+
await this.saveTabs();
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
773
891
|
if (!target.sessionPath) {
|
|
774
892
|
this.host.showToast("Tab has no persisted session path", "warning");
|
|
775
893
|
return;
|
|
776
894
|
}
|
|
895
|
+
if (this.activeTab()?.draft) {
|
|
896
|
+
await this.switchDraftToPersistedTab(target, generation);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
const runtime = this.runtimeForCommand("tab switch");
|
|
900
|
+
if (!runtime)
|
|
901
|
+
return;
|
|
902
|
+
this.syncActiveTabFromRuntime({ save: false });
|
|
777
903
|
this.cancelHistoryLoad();
|
|
778
904
|
const previousTabId = this.activeTabId;
|
|
779
905
|
const previousRuntime = runtime;
|
|
@@ -858,6 +984,84 @@ export class AppTabsController {
|
|
|
858
984
|
}
|
|
859
985
|
this.scheduleDelayedHistoryReload(target.id, targetRuntime, generation);
|
|
860
986
|
}
|
|
987
|
+
async switchDraftToPersistedTab(target, generation) {
|
|
988
|
+
const draft = this.activeTab();
|
|
989
|
+
if (!draft?.draft || !target.sessionPath)
|
|
990
|
+
return;
|
|
991
|
+
this.cancelHistoryLoad();
|
|
992
|
+
this.storeActiveInputState();
|
|
993
|
+
this.storeActiveDeferredUserMessages();
|
|
994
|
+
this.pendingActiveTabId = target.id;
|
|
995
|
+
const previousTargetActivity = target.activity;
|
|
996
|
+
target.activity = "thinking";
|
|
997
|
+
this.clearTabAttention(target);
|
|
998
|
+
this.host.closeDraftSessionSelector?.();
|
|
999
|
+
this.host.setStatus("switching tab");
|
|
1000
|
+
this.host.setSessionActivity("thinking");
|
|
1001
|
+
this.host.render();
|
|
1002
|
+
let targetRuntime;
|
|
1003
|
+
try {
|
|
1004
|
+
targetRuntime = await this.runtimeForTab(target);
|
|
1005
|
+
if (!targetRuntime)
|
|
1006
|
+
throw new Error("Could not load tab runtime");
|
|
1007
|
+
if (generation !== this.lifecycleGeneration
|
|
1008
|
+
|| this.activeTabId !== draft.id
|
|
1009
|
+
|| this.pendingActiveTabId !== target.id)
|
|
1010
|
+
throw new Error("Tab ownership changed while loading the runtime");
|
|
1011
|
+
await this.host.activateRuntime(targetRuntime, { awaitExtensions: false });
|
|
1012
|
+
}
|
|
1013
|
+
catch {
|
|
1014
|
+
if (this.pendingActiveTabId === target.id)
|
|
1015
|
+
this.pendingActiveTabId = undefined;
|
|
1016
|
+
if (previousTargetActivity === undefined)
|
|
1017
|
+
delete target.activity;
|
|
1018
|
+
else
|
|
1019
|
+
target.activity = previousTargetActivity;
|
|
1020
|
+
if (targetRuntime && this.host.runtime() === targetRuntime)
|
|
1021
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
1022
|
+
if (targetRuntime)
|
|
1023
|
+
await this.disposeRuntimeIfOrphan(targetRuntime);
|
|
1024
|
+
this.activateDraftTab(draft, { openSelector: !this.touchedDraftTabIds.has(draft.id) });
|
|
1025
|
+
this.host.showToast("Could not switch tab", "warning");
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (this.pendingActiveTabId === target.id)
|
|
1029
|
+
this.pendingActiveTabId = undefined;
|
|
1030
|
+
this.activeTabId = target.id;
|
|
1031
|
+
this.clearTabAttention(target);
|
|
1032
|
+
this.updateTabFromSession(target, targetRuntime.session);
|
|
1033
|
+
this.setRuntimeForTab(target.id, targetRuntime);
|
|
1034
|
+
this.restoreInputState(target.id);
|
|
1035
|
+
this.host.closeMenusForTabSwitch?.();
|
|
1036
|
+
void this.saveTabs();
|
|
1037
|
+
this.scheduleTabPrewarm();
|
|
1038
|
+
const cachedView = this.sessionViewsByTabId.get(target.id);
|
|
1039
|
+
const cachedViewNeedsHistoryReload = this.tabIdsNeedingHistoryReload.has(target.id);
|
|
1040
|
+
const restoreCachedView = () => {
|
|
1041
|
+
if (!cachedView || !this.host.restoreSessionView)
|
|
1042
|
+
return false;
|
|
1043
|
+
if (this.activeTabId !== target.id || this.host.runtime() !== targetRuntime)
|
|
1044
|
+
return false;
|
|
1045
|
+
this.host.restoreSessionView(cachedView);
|
|
1046
|
+
this.restoreDeferredUserMessages(target.id);
|
|
1047
|
+
this.host.setSessionStatus(targetRuntime.session);
|
|
1048
|
+
this.host.setSessionActivity(this.sessionActivity(targetRuntime.session));
|
|
1049
|
+
this.host.render();
|
|
1050
|
+
return true;
|
|
1051
|
+
};
|
|
1052
|
+
if (cachedView && !cachedViewNeedsHistoryReload) {
|
|
1053
|
+
restoreCachedView();
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
const loaded = await this.loadActiveSessionHistory(targetRuntime, generation);
|
|
1057
|
+
// Returning from a transient draft must never strand a real tab on the
|
|
1058
|
+
// blank view used by the draft. If a stale-history refresh is cancelled
|
|
1059
|
+
// or fails, fall back to the snapshot captured when the draft opened.
|
|
1060
|
+
if (!loaded)
|
|
1061
|
+
restoreCachedView();
|
|
1062
|
+
}
|
|
1063
|
+
this.scheduleDelayedHistoryReload(target.id, targetRuntime, generation);
|
|
1064
|
+
}
|
|
861
1065
|
closeTab(tabId) {
|
|
862
1066
|
return this.runLifecycleMutation((generation) => this.closeTabMutation(tabId, generation));
|
|
863
1067
|
}
|
|
@@ -869,6 +1073,13 @@ export class AppTabsController {
|
|
|
869
1073
|
const index = this.tabItems.findIndex((tab) => tab.id === tabId);
|
|
870
1074
|
if (index < 0)
|
|
871
1075
|
return;
|
|
1076
|
+
const closingTab = this.tabItems[index];
|
|
1077
|
+
if (closingTab?.draft) {
|
|
1078
|
+
if (this.tabItems.length <= 1)
|
|
1079
|
+
return;
|
|
1080
|
+
await this.closeDraftTabMutation(closingTab, index, generation);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
872
1083
|
this.cancelHistoryLoad();
|
|
873
1084
|
if (this.tabItems.length <= 1) {
|
|
874
1085
|
await this.replaceLastTabWithNewSession(tabId, generation);
|
|
@@ -956,6 +1167,48 @@ export class AppTabsController {
|
|
|
956
1167
|
this.scheduleTabPrewarm();
|
|
957
1168
|
await this.loadActiveSessionHistory(nextRuntime, generation);
|
|
958
1169
|
}
|
|
1170
|
+
async closeDraftTabMutation(tab, index, generation) {
|
|
1171
|
+
this.cancelHistoryLoad();
|
|
1172
|
+
if (tab.id !== this.activeTabId) {
|
|
1173
|
+
this.removeTab(tab.id);
|
|
1174
|
+
this.inputStatesByTabId.delete(tab.id);
|
|
1175
|
+
this.autoUserMessagesByTabId.delete(tab.id);
|
|
1176
|
+
this.deferredUserMessagesByTabId.delete(tab.id);
|
|
1177
|
+
this.touchedDraftTabIds.delete(tab.id);
|
|
1178
|
+
await this.saveTabs();
|
|
1179
|
+
this.host.render();
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const nextTab = this.tabItems[index + 1] ?? this.tabItems[index - 1];
|
|
1183
|
+
if (nextTab?.sessionPath) {
|
|
1184
|
+
await this.switchDraftToPersistedTab(nextTab, generation);
|
|
1185
|
+
if (this.activeTabId !== nextTab.id)
|
|
1186
|
+
return;
|
|
1187
|
+
this.removeTab(tab.id);
|
|
1188
|
+
this.inputStatesByTabId.delete(tab.id);
|
|
1189
|
+
this.autoUserMessagesByTabId.delete(tab.id);
|
|
1190
|
+
this.deferredUserMessagesByTabId.delete(tab.id);
|
|
1191
|
+
this.touchedDraftTabIds.delete(tab.id);
|
|
1192
|
+
await this.saveTabs();
|
|
1193
|
+
this.host.render();
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
this.removeTab(tab.id);
|
|
1197
|
+
this.inputStatesByTabId.delete(tab.id);
|
|
1198
|
+
this.autoUserMessagesByTabId.delete(tab.id);
|
|
1199
|
+
this.deferredUserMessagesByTabId.delete(tab.id);
|
|
1200
|
+
this.touchedDraftTabIds.delete(tab.id);
|
|
1201
|
+
this.activeTabId = undefined;
|
|
1202
|
+
this.host.closeDraftSessionSelector?.();
|
|
1203
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
1204
|
+
this.host.restoreInputState({ text: "", cursor: 0 });
|
|
1205
|
+
this.host.resetSessionView();
|
|
1206
|
+
this.host.setSessionStatus(undefined);
|
|
1207
|
+
this.host.setSessionActivity("idle");
|
|
1208
|
+
this.host.setStatus("no conversation");
|
|
1209
|
+
await this.saveTabs();
|
|
1210
|
+
this.host.render();
|
|
1211
|
+
}
|
|
959
1212
|
async replaceLastTabWithNewSession(tabId, generation) {
|
|
960
1213
|
const tab = this.tabItems.find((item) => item.id === tabId);
|
|
961
1214
|
if (!tab)
|
|
@@ -963,52 +1216,66 @@ export class AppTabsController {
|
|
|
963
1216
|
const runtime = this.idleRuntime("new");
|
|
964
1217
|
if (!runtime)
|
|
965
1218
|
return;
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1219
|
+
if (this.host.options.noSession) {
|
|
1220
|
+
const session = runtime.session;
|
|
1221
|
+
this.activeTabId = tab.id;
|
|
1222
|
+
this.pendingActiveTabId = tab.id;
|
|
1223
|
+
this.host.setStatus("starting new session");
|
|
1224
|
+
this.host.render();
|
|
1225
|
+
let result;
|
|
1226
|
+
try {
|
|
1227
|
+
await this.host.awaitCurrentSessionExtensions?.(runtime);
|
|
1228
|
+
if (!this.isLifecycleOwner(generation, runtime, session))
|
|
1229
|
+
return;
|
|
1230
|
+
result = await runtime.newSession();
|
|
1231
|
+
}
|
|
1232
|
+
finally {
|
|
1233
|
+
if (this.pendingActiveTabId === tab.id)
|
|
1234
|
+
this.pendingActiveTabId = undefined;
|
|
1235
|
+
}
|
|
1236
|
+
if (result.cancelled) {
|
|
1237
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: "New session cancelled." });
|
|
1238
|
+
this.host.setSessionStatus(runtime.session);
|
|
1239
|
+
this.host.setSessionActivity(this.sessionActivity(runtime.session));
|
|
1240
|
+
this.host.render();
|
|
975
1241
|
return;
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
this.
|
|
1242
|
+
}
|
|
1243
|
+
this.clearTabAttention(tab);
|
|
1244
|
+
this.updateTabFromSession(tab, runtime.session);
|
|
1245
|
+
this.setRuntimeForTab(tab.id, runtime);
|
|
1246
|
+
this.inputStatesByTabId.delete(tab.id);
|
|
1247
|
+
this.autoUserMessagesByTabId.delete(tab.id);
|
|
1248
|
+
this.deferredUserMessagesByTabId.delete(tab.id);
|
|
1249
|
+
this.restoreInputState(tab.id);
|
|
1250
|
+
this.host.closeMenusForTabSwitch?.();
|
|
1251
|
+
this.stopAttentionBlinkIfIdle();
|
|
1252
|
+
this.host.resetSessionView();
|
|
1253
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
1254
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: `Started a new session. cwd=${runtime.cwd}` });
|
|
984
1255
|
this.host.setSessionStatus(runtime.session);
|
|
985
1256
|
this.host.setSessionActivity(this.sessionActivity(runtime.session));
|
|
1257
|
+
void this.saveTabs();
|
|
986
1258
|
this.host.render();
|
|
987
1259
|
return;
|
|
988
1260
|
}
|
|
989
|
-
this.
|
|
990
|
-
|
|
991
|
-
this.
|
|
1261
|
+
if (!this.isLifecycleOwner(generation, runtime, runtime.session))
|
|
1262
|
+
return;
|
|
1263
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
1264
|
+
this.deleteRuntimeForTab(tab.id);
|
|
1265
|
+
await this.host.disposeRuntime(runtime).catch(() => undefined);
|
|
1266
|
+
tab.title = "new";
|
|
1267
|
+
tab.titlePlaceholder = "new";
|
|
1268
|
+
tab.draft = true;
|
|
1269
|
+
delete tab.isFork;
|
|
1270
|
+
delete tab.sessionPath;
|
|
1271
|
+
tab.activity = "idle";
|
|
1272
|
+
this.touchedDraftTabIds.delete(tab.id);
|
|
992
1273
|
this.inputStatesByTabId.delete(tab.id);
|
|
993
1274
|
this.autoUserMessagesByTabId.delete(tab.id);
|
|
994
1275
|
this.deferredUserMessagesByTabId.delete(tab.id);
|
|
995
|
-
this.restoreInputState(tab.id);
|
|
996
|
-
this.host.closeMenusForTabSwitch?.();
|
|
997
1276
|
this.stopAttentionBlinkIfIdle();
|
|
998
|
-
this.
|
|
999
|
-
this.
|
|
1000
|
-
this.host.addEntry({ id: createId("system"), kind: "system", text: `Started a new session. cwd=${runtime.cwd}` });
|
|
1001
|
-
if (runtime.modelFallbackMessage)
|
|
1002
|
-
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
1003
|
-
for (const diag of runtime.diagnostics ?? []) {
|
|
1004
|
-
const kind = diag.type === "error" ? "error" : "system";
|
|
1005
|
-
this.host.addEntry({ id: createId("system"), kind, text: `[${diag.type}] ${diag.message}` });
|
|
1006
|
-
}
|
|
1007
|
-
this.host.setSessionStatus(runtime.session);
|
|
1008
|
-
this.host.setSessionActivity(this.sessionActivity(runtime.session));
|
|
1009
|
-
void this.saveTabs();
|
|
1010
|
-
this.scheduleProjectSessionRetention();
|
|
1011
|
-
this.host.render();
|
|
1277
|
+
this.activateDraftTab(tab, { resetInput: true, openSelector: true });
|
|
1278
|
+
await this.saveTabs();
|
|
1012
1279
|
}
|
|
1013
1280
|
async loadActiveSessionHistory(runtime, lifecycleGeneration) {
|
|
1014
1281
|
const tabId = this.activeTabId;
|
|
@@ -1087,6 +1354,48 @@ export class AppTabsController {
|
|
|
1087
1354
|
activeTab() {
|
|
1088
1355
|
return this.activeTabId ? this.tabItems.find((tab) => tab.id === this.activeTabId) : undefined;
|
|
1089
1356
|
}
|
|
1357
|
+
createDraftTab() {
|
|
1358
|
+
return {
|
|
1359
|
+
id: createId("tab"),
|
|
1360
|
+
title: "new",
|
|
1361
|
+
titlePlaceholder: "new",
|
|
1362
|
+
draft: true,
|
|
1363
|
+
status: "active",
|
|
1364
|
+
activity: "idle",
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
activateDraftTab(tab, options = {}) {
|
|
1368
|
+
this.cancelHistoryLoad();
|
|
1369
|
+
// Draft activation must be atomic from the renderer's point of view.
|
|
1370
|
+
// Host cleanup (extension UI / voice / runtime teardown) may synchronously
|
|
1371
|
+
// render. While that is happening, keep the draft as the pending target so
|
|
1372
|
+
// tabs() cannot resync the old still-attached runtime back into activeTabId.
|
|
1373
|
+
this.pendingActiveTabId = tab.id;
|
|
1374
|
+
this.activeTabId = tab.id;
|
|
1375
|
+
this.clearTabAttention(tab);
|
|
1376
|
+
if (options.resetInput) {
|
|
1377
|
+
this.inputStatesByTabId.delete(tab.id);
|
|
1378
|
+
this.autoUserMessagesByTabId.delete(tab.id);
|
|
1379
|
+
this.deferredUserMessagesByTabId.delete(tab.id);
|
|
1380
|
+
this.touchedDraftTabIds.delete(tab.id);
|
|
1381
|
+
this.host.resetDraftModelSelection?.();
|
|
1382
|
+
}
|
|
1383
|
+
this.restoreInputState(tab.id);
|
|
1384
|
+
this.host.closeMenusForTabSwitch?.();
|
|
1385
|
+
this.host.resetSessionView();
|
|
1386
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
1387
|
+
this.host.deactivateRuntimeForDraft?.();
|
|
1388
|
+
this.host.setSessionStatus(undefined);
|
|
1389
|
+
this.host.setSessionActivity("idle");
|
|
1390
|
+
this.host.setStatus("new conversation");
|
|
1391
|
+
if (options.openSelector !== false && !this.touchedDraftTabIds.has(tab.id))
|
|
1392
|
+
this.host.openDraftSessionSelector?.();
|
|
1393
|
+
else
|
|
1394
|
+
this.host.closeDraftSessionSelector?.();
|
|
1395
|
+
if (this.pendingActiveTabId === tab.id)
|
|
1396
|
+
this.pendingActiveTabId = undefined;
|
|
1397
|
+
this.host.render();
|
|
1398
|
+
}
|
|
1090
1399
|
clearStartupTabPlaceholders() {
|
|
1091
1400
|
for (const tab of this.tabItems) {
|
|
1092
1401
|
delete tab.titlePlaceholder;
|
|
@@ -1832,8 +2141,6 @@ export class AppTabsController {
|
|
|
1832
2141
|
}
|
|
1833
2142
|
tabs.push(persistedTab);
|
|
1834
2143
|
}
|
|
1835
|
-
if (tabs.length === 0)
|
|
1836
|
-
return Promise.resolve();
|
|
1837
2144
|
const activePath = this.activeTab()?.sessionPath;
|
|
1838
2145
|
const payload = JSON.stringify({
|
|
1839
2146
|
version: TAB_STATE_VERSION,
|