aimux-cli 0.1.19 → 0.1.20
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 +13 -4
- package/bin/aimux +4 -0
- package/bin/aimux-dev +2 -6
- package/dist/agent-output-parser-audit.d.ts +23 -0
- package/dist/agent-output-parser-audit.js +187 -0
- package/dist/agent-output-parser-contract.d.ts +9 -0
- package/dist/agent-output-parser-contract.js +33 -0
- package/dist/agent-output-parser-fixtures.d.ts +15 -0
- package/dist/agent-output-parser-fixtures.js +593 -0
- package/dist/agent-output-parser-harness.d.ts +21 -0
- package/dist/agent-output-parser-harness.js +43 -0
- package/dist/agent-output-parser-test-utils.d.ts +1 -0
- package/dist/agent-output-parser-test-utils.js +7 -0
- package/dist/agent-output-parser.js +215 -35
- package/dist/atomic-write.d.ts +15 -0
- package/dist/atomic-write.js +69 -4
- package/dist/attachment-store.d.ts +7 -0
- package/dist/attachment-store.js +64 -5
- package/dist/backend-session-discovery.d.ts +17 -0
- package/dist/backend-session-discovery.js +57 -0
- package/dist/config.js +9 -4
- package/dist/connection-targets.js +20 -1
- package/dist/context/context-bridge.js +4 -1
- package/dist/credentials.js +3 -6
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +16 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.js +1 -0
- package/dist/dashboard/targets.js +14 -2
- package/dist/dashboard/ui-state-store.js +4 -3
- package/dist/last-used.js +3 -2
- package/dist/launcher-env.d.ts +4 -0
- package/dist/launcher-env.js +70 -0
- package/dist/main.js +16 -1
- package/dist/metadata-server.d.ts +13 -2
- package/dist/metadata-server.js +60 -4
- package/dist/metadata-store.js +4 -3
- package/dist/mobile-push-bridge.d.ts +8 -0
- package/dist/mobile-push-bridge.js +22 -0
- package/dist/mobile-push-throttle.d.ts +23 -0
- package/dist/mobile-push-throttle.js +53 -0
- package/dist/multiplexer/dashboard-model.js +3 -2
- package/dist/multiplexer/dashboard-ops.d.ts +3 -2
- package/dist/multiplexer/dashboard-ops.js +2 -2
- package/dist/multiplexer/dashboard-tail-methods.d.ts +3 -2
- package/dist/multiplexer/dashboard-tail-methods.js +2 -2
- package/dist/multiplexer/dashboard-view-methods.js +2 -0
- package/dist/multiplexer/index.d.ts +1 -1
- package/dist/multiplexer/index.js +4 -4
- package/dist/multiplexer/persistence-methods.js +2 -1
- package/dist/multiplexer/runtime-lifecycle-methods.js +6 -2
- package/dist/multiplexer/runtime-state.js +13 -1
- package/dist/multiplexer/service-state-snapshot.js +4 -2
- package/dist/multiplexer/services.js +5 -4
- package/dist/multiplexer/session-launch.d.ts +1 -1
- package/dist/multiplexer/session-launch.js +18 -6
- package/dist/multiplexer/session-runtime-core.js +9 -2
- package/dist/multiplexer/tool-picker.d.ts +2 -1
- package/dist/multiplexer/tool-picker.js +29 -21
- package/dist/notify.d.ts +1 -1
- package/dist/notify.js +8 -5
- package/dist/paths.js +50 -4
- package/dist/project-takeover.d.ts +1 -0
- package/dist/project-takeover.js +117 -0
- package/dist/relay-client.d.ts +10 -0
- package/dist/relay-client.js +5 -0
- package/dist/runtime-core/backend-id-reconcile.d.ts +13 -0
- package/dist/runtime-core/backend-id-reconcile.js +23 -0
- package/dist/runtime-core/exchange-store.js +3 -8
- package/dist/runtime-core/topology-store.js +3 -8
- package/dist/runtime-owner.d.ts +3 -0
- package/dist/runtime-owner.js +10 -0
- package/dist/shell-args.d.ts +13 -0
- package/dist/shell-args.js +25 -0
- package/dist/shell-hooks.d.ts +1 -0
- package/dist/shell-hooks.js +1 -0
- package/dist/team.js +4 -3
- package/dist/tmux/runtime-manager.js +2 -0
- package/dist/tui/screens/dashboard-renderers.js +6 -6
- package/dist/vitest.setup.d.ts +1 -0
- package/dist/vitest.setup.js +9 -0
- package/dist-ui/_expo/static/css/web-8782287775683e5a944b821b854d0f60.css +1 -0
- package/dist-ui/_expo/static/js/web/{entry-477c745b2adc79367a4380ecf07d9ff6.js → entry-90d00d223eefabe5cc21e4329b274fa5.js} +260 -252
- package/dist-ui/index.html +2 -2
- package/package.json +3 -1
- package/dist-ui/_expo/static/css/web-30453ede1678c16acb08b97e83e8646d.css +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const DEDUPE_TTL_MS = 60_000;
|
|
2
|
+
const SESSION_WINDOW_MS = 60_000;
|
|
3
|
+
const SESSION_LIMIT = 5;
|
|
4
|
+
/**
|
|
5
|
+
* In-memory guard for outbound mobile pushes. Collapses identical alerts
|
|
6
|
+
* re-emitted within a TTL window (chatty idle/needs_input polling) and caps the
|
|
7
|
+
* push rate per session so one runaway agent cannot flood the device.
|
|
8
|
+
*/
|
|
9
|
+
export class MobilePushThrottle {
|
|
10
|
+
dedupeTtlMs;
|
|
11
|
+
sessionLimit;
|
|
12
|
+
sessionWindowMs;
|
|
13
|
+
now;
|
|
14
|
+
lastByKey = new Map();
|
|
15
|
+
sessionHits = new Map();
|
|
16
|
+
constructor(dedupeTtlMs = DEDUPE_TTL_MS, sessionLimit = SESSION_LIMIT, sessionWindowMs = SESSION_WINDOW_MS, now = () => Date.now()) {
|
|
17
|
+
this.dedupeTtlMs = dedupeTtlMs;
|
|
18
|
+
this.sessionLimit = sessionLimit;
|
|
19
|
+
this.sessionWindowMs = sessionWindowMs;
|
|
20
|
+
this.now = now;
|
|
21
|
+
}
|
|
22
|
+
allow(input) {
|
|
23
|
+
const ts = this.now();
|
|
24
|
+
this.prune(ts);
|
|
25
|
+
const key = input.dedupeKey?.trim() || [input.sessionId, input.kind, input.title, input.body].map((p) => p ?? "").join("|");
|
|
26
|
+
const last = this.lastByKey.get(key);
|
|
27
|
+
if (last !== undefined && ts - last < this.dedupeTtlMs)
|
|
28
|
+
return false;
|
|
29
|
+
const session = input.sessionId?.trim() || "_global";
|
|
30
|
+
const hits = (this.sessionHits.get(session) ?? []).filter((t) => ts - t < this.sessionWindowMs);
|
|
31
|
+
if (hits.length >= this.sessionLimit) {
|
|
32
|
+
this.sessionHits.set(session, hits);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
hits.push(ts);
|
|
36
|
+
this.sessionHits.set(session, hits);
|
|
37
|
+
this.lastByKey.set(key, ts);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
prune(ts) {
|
|
41
|
+
for (const [key, t] of this.lastByKey) {
|
|
42
|
+
if (ts - t >= this.dedupeTtlMs)
|
|
43
|
+
this.lastByKey.delete(key);
|
|
44
|
+
}
|
|
45
|
+
for (const [session, hits] of this.sessionHits) {
|
|
46
|
+
const fresh = hits.filter((t) => ts - t < this.sessionWindowMs);
|
|
47
|
+
if (fresh.length === 0)
|
|
48
|
+
this.sessionHits.delete(session);
|
|
49
|
+
else
|
|
50
|
+
this.sessionHits.set(session, fresh);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -819,7 +819,7 @@ export async function startProjectServices(host) {
|
|
|
819
819
|
targetSessionId: input.sessionId,
|
|
820
820
|
targetWorktreePath: input.worktreePath,
|
|
821
821
|
open: input.open ?? false,
|
|
822
|
-
|
|
822
|
+
launchOverride: input.launchOverride,
|
|
823
823
|
}), input.sessionId
|
|
824
824
|
? buildMetadataPendingSessionSeed({
|
|
825
825
|
sessionId: input.sessionId,
|
|
@@ -862,7 +862,7 @@ export async function startProjectServices(host) {
|
|
|
862
862
|
instruction: input.instruction,
|
|
863
863
|
targetWorktreePath: input.worktreePath,
|
|
864
864
|
open: input.open ?? false,
|
|
865
|
-
|
|
865
|
+
launchOverride: input.launchOverride,
|
|
866
866
|
}), input.targetSessionId
|
|
867
867
|
? buildMetadataPendingSessionSeed({
|
|
868
868
|
sessionId: input.targetSessionId,
|
|
@@ -876,6 +876,7 @@ export async function startProjectServices(host) {
|
|
|
876
876
|
renameAgent: (input) => host.renameAgent(input.sessionId, input.label),
|
|
877
877
|
migrateAgent: (input) => withMetadataSessionPending(host, input.sessionId, "migrating", () => host.migrateAgent(input.sessionId, input.worktreePath), findDashboardSessionSeed(host, input.sessionId)),
|
|
878
878
|
killAgent: (input) => withMetadataSessionPending(host, input.sessionId, "graveyarding", () => host.sendAgentToGraveyard(input.sessionId), findDashboardSessionSeed(host, input.sessionId)),
|
|
879
|
+
recordBackendSessionId: (input) => host.recordSessionBackendSessionId(input.sessionId, input.backendSessionId),
|
|
879
880
|
sendAgentInput: (input) => host.sendAgentInput(input.sessionId, input.text),
|
|
880
881
|
readAgentOutput: (input) => host.readAgentOutput(input.sessionId, input.startLine),
|
|
881
882
|
},
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { DashboardService, DashboardSession } from "../dashboard/index.js";
|
|
2
2
|
import type { PendingServiceActionKind, PendingSessionActionKind } from "../pending-actions.js";
|
|
3
|
+
import type { LaunchOverride } from "../shell-args.js";
|
|
3
4
|
type DashboardOpsHost = any;
|
|
4
5
|
export declare function runDashboardOperation<T>(host: DashboardOpsHost, title: string, lines: string[], work: () => Promise<T> | T, errorTitle?: string): Promise<T | undefined>;
|
|
5
6
|
export declare function spawnDashboardAgentWithFeedback(host: DashboardOpsHost, input: {
|
|
6
7
|
sessionId: string;
|
|
7
8
|
tool: string;
|
|
8
9
|
worktreePath?: string;
|
|
9
|
-
|
|
10
|
+
launchOverride?: LaunchOverride;
|
|
10
11
|
}): Promise<void>;
|
|
11
12
|
export declare function forkDashboardAgentWithFeedback(host: DashboardOpsHost, input: {
|
|
12
13
|
sourceSessionId: string;
|
|
@@ -14,7 +15,7 @@ export declare function forkDashboardAgentWithFeedback(host: DashboardOpsHost, i
|
|
|
14
15
|
tool: string;
|
|
15
16
|
instruction?: string;
|
|
16
17
|
worktreePath?: string;
|
|
17
|
-
|
|
18
|
+
launchOverride?: LaunchOverride;
|
|
18
19
|
}): Promise<void>;
|
|
19
20
|
export declare function setPendingDashboardSessionAction(host: DashboardOpsHost, sessionId: string, kind: PendingSessionActionKind | null, opts?: {
|
|
20
21
|
sessionSeed?: DashboardSession;
|
|
@@ -300,7 +300,7 @@ export async function spawnDashboardAgentWithFeedback(host, input) {
|
|
|
300
300
|
tool: input.tool,
|
|
301
301
|
sessionId: input.sessionId,
|
|
302
302
|
worktreePath: input.worktreePath,
|
|
303
|
-
|
|
303
|
+
launchOverride: input.launchOverride,
|
|
304
304
|
open: false,
|
|
305
305
|
}, { timeoutMs: 10_000 });
|
|
306
306
|
},
|
|
@@ -330,7 +330,7 @@ export async function forkDashboardAgentWithFeedback(host, input) {
|
|
|
330
330
|
tool: input.tool,
|
|
331
331
|
instruction: input.instruction,
|
|
332
332
|
worktreePath: input.worktreePath,
|
|
333
|
-
|
|
333
|
+
launchOverride: input.launchOverride,
|
|
334
334
|
open: false,
|
|
335
335
|
}, { timeoutMs: 10_000 });
|
|
336
336
|
},
|
|
@@ -3,6 +3,7 @@ import type { Multiplexer, SessionState } from "./index.js";
|
|
|
3
3
|
import { dashboardSessionActionDeps as dashboardSessionActionDepsImpl } from "./dashboard-ops.js";
|
|
4
4
|
import type { PendingServiceActionKind, PendingSessionActionKind } from "../pending-actions.js";
|
|
5
5
|
import type { SessionRuntime } from "../session-runtime.js";
|
|
6
|
+
import type { LaunchOverride } from "../shell-args.js";
|
|
6
7
|
export type DashboardTailMethods = {
|
|
7
8
|
forkAgent(this: Multiplexer, opts: {
|
|
8
9
|
sourceSessionId: string;
|
|
@@ -11,7 +12,7 @@ export type DashboardTailMethods = {
|
|
|
11
12
|
instruction?: string;
|
|
12
13
|
targetWorktreePath?: string;
|
|
13
14
|
open?: boolean;
|
|
14
|
-
|
|
15
|
+
launchOverride?: LaunchOverride;
|
|
15
16
|
}): Promise<{
|
|
16
17
|
sessionId: string;
|
|
17
18
|
threadId: string;
|
|
@@ -21,7 +22,7 @@ export type DashboardTailMethods = {
|
|
|
21
22
|
targetSessionId?: string;
|
|
22
23
|
targetWorktreePath?: string;
|
|
23
24
|
open?: boolean;
|
|
24
|
-
|
|
25
|
+
launchOverride?: LaunchOverride;
|
|
25
26
|
}): Promise<{
|
|
26
27
|
sessionId: string;
|
|
27
28
|
}>;
|
|
@@ -55,7 +55,7 @@ function refreshLifecycleViews(host) {
|
|
|
55
55
|
}
|
|
56
56
|
export const dashboardTailMethods = {
|
|
57
57
|
async forkAgent(opts) {
|
|
58
|
-
const result = await this.forkSessionFromSource(opts.sourceSessionId, opts.targetToolConfigKey, opts.targetSessionId, opts.instruction, opts.targetWorktreePath, opts.
|
|
58
|
+
const result = await this.forkSessionFromSource(opts.sourceSessionId, opts.targetToolConfigKey, opts.targetSessionId, opts.instruction, opts.targetWorktreePath, opts.launchOverride);
|
|
59
59
|
if (!result) {
|
|
60
60
|
throw new Error(`Unable to fork agent "${opts.sourceSessionId}"`);
|
|
61
61
|
}
|
|
@@ -71,7 +71,7 @@ export const dashboardTailMethods = {
|
|
|
71
71
|
throw new Error(`Unknown tool config: ${opts.toolConfigKey}`);
|
|
72
72
|
}
|
|
73
73
|
const sessionId = opts.targetSessionId ?? this.generateDashboardSessionId?.(tool.command);
|
|
74
|
-
const transport = this.createSession(
|
|
74
|
+
const transport = this.createSession(opts.launchOverride?.command ?? tool.command, opts.launchOverride?.args ?? tool.args, tool.preambleFlag, opts.toolConfigKey, undefined, tool.sessionIdFlag, opts.targetWorktreePath, undefined, sessionId, !opts.open, false, undefined, opts.launchOverride?.env);
|
|
75
75
|
if (opts.open) {
|
|
76
76
|
this.openLiveTmuxWindowForEntry({ id: transport.id });
|
|
77
77
|
}
|
|
@@ -2,6 +2,7 @@ import { closeNotificationPanel as closeNotificationPanelImpl, handleNotificatio
|
|
|
2
2
|
import { beginWorktreeRemoval as beginWorktreeRemovalImpl, finishWorktreeRemoval as finishWorktreeRemovalImpl, handleWorktreeInputKey as handleWorktreeInputKeyImpl, handleWorktreeRemoveConfirmKey as handleWorktreeRemoveConfirmKeyImpl, handleWorktreeListKey as handleWorktreeListKeyImpl, renderWorktreeInput as renderWorktreeInputImpl, renderWorktreeList as renderWorktreeListImpl, renderWorktreeRemoveConfirm as renderWorktreeRemoveConfirmImpl, showWorktreeCreatePrompt as showWorktreeCreatePromptImpl, showWorktreeList as showWorktreeListImpl, } from "./worktrees.js";
|
|
3
3
|
import { createService as createServiceImpl, removeOfflineService as removeOfflineServiceImpl, resumeOfflineService as resumeOfflineServiceImpl, resumeOfflineServiceById as resumeOfflineServiceByIdImpl, serviceLabelForCommand as serviceLabelForCommandImpl, stopService as stopServiceImpl, } from "./services.js";
|
|
4
4
|
import { derivedStatusLabel } from "../dashboard/index.js";
|
|
5
|
+
import { isDevelopmentRuntime } from "../connection-targets.js";
|
|
5
6
|
import { selectDashboardTeammates } from "../dashboard/session-registry.js";
|
|
6
7
|
import { hasRuntimeEvidence, isAttachableDashboardSessionEntry } from "../dashboard/runtime-evidence.js";
|
|
7
8
|
export const dashboardViewMethods = {
|
|
@@ -113,6 +114,7 @@ export const dashboardViewMethods = {
|
|
|
113
114
|
selectedServiceId: selectedService,
|
|
114
115
|
selectedTeammates: selectDashboardTeammates(dashTeammates, selectedSessionEntry),
|
|
115
116
|
runtimeLabel: "tmux",
|
|
117
|
+
isDevRuntime: isDevelopmentRuntime(),
|
|
116
118
|
mainCheckout: mainCheckoutInfo,
|
|
117
119
|
operationFailures: this.dashboardOperationFailuresCache ?? [],
|
|
118
120
|
worktreeRemoval: this.worktreeRemovalJob
|
|
@@ -185,7 +185,7 @@ export declare class Multiplexer {
|
|
|
185
185
|
* Starts fresh sessions but with context from the previous conversation.
|
|
186
186
|
*/
|
|
187
187
|
restoreSessions(toolFilter?: string): Promise<number>;
|
|
188
|
-
createSession(command: string, args: string[], preambleFlag?: string[], toolConfigKey?: string, extraPreamble?: string, sessionIdFlag?: string[], worktreePath?: string, backendSessionIdOverride?: string, sessionIdOverride?: string, detachedInTmux?: boolean, suppressStartupPreamble?: boolean, team?: SessionTeamMetadata): SessionTransport;
|
|
188
|
+
createSession(command: string, args: string[], preambleFlag?: string[], toolConfigKey?: string, extraPreamble?: string, sessionIdFlag?: string[], worktreePath?: string, backendSessionIdOverride?: string, sessionIdOverride?: string, detachedInTmux?: boolean, suppressStartupPreamble?: boolean, team?: SessionTeamMetadata, launchEnv?: Record<string, string>): SessionTransport;
|
|
189
189
|
recordSessionBackendSessionId(sessionId: string, backendSessionId: string): {
|
|
190
190
|
sessionId: string;
|
|
191
191
|
backendSessionId: string;
|
|
@@ -317,8 +317,8 @@ export class Multiplexer {
|
|
|
317
317
|
async restoreSessions(toolFilter) {
|
|
318
318
|
return restoreSessionsImpl(this, toolFilter);
|
|
319
319
|
}
|
|
320
|
-
createSession(command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux = false, suppressStartupPreamble = false, team) {
|
|
321
|
-
return createSessionImpl(this, command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux, suppressStartupPreamble, team);
|
|
320
|
+
createSession(command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux = false, suppressStartupPreamble = false, team, launchEnv) {
|
|
321
|
+
return createSessionImpl(this, command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux, suppressStartupPreamble, team, launchEnv);
|
|
322
322
|
}
|
|
323
323
|
recordSessionBackendSessionId(sessionId, backendSessionId) {
|
|
324
324
|
return runtimeLifecycleMethods.recordSessionBackendSessionId.call(this, sessionId, backendSessionId);
|
|
@@ -348,7 +348,7 @@ export class Multiplexer {
|
|
|
348
348
|
handleAction(action) {
|
|
349
349
|
handleActionImpl(this, action);
|
|
350
350
|
}
|
|
351
|
-
async forkSessionFromSource(sourceSessionId, targetToolConfigKey, requestedTargetSessionId, instruction, targetWorktreePath,
|
|
351
|
+
async forkSessionFromSource(sourceSessionId, targetToolConfigKey, requestedTargetSessionId, instruction, targetWorktreePath, launchOverride) {
|
|
352
352
|
const sourceSession = this.sessions.find((session) => session.id === sourceSessionId);
|
|
353
353
|
if (!sourceSession) {
|
|
354
354
|
this.showDashboardError("Cannot fork missing session", [`Source session ${sourceSessionId} not found.`]);
|
|
@@ -402,7 +402,7 @@ export class Multiplexer {
|
|
|
402
402
|
]
|
|
403
403
|
.filter(Boolean)
|
|
404
404
|
.join("\n\n");
|
|
405
|
-
const transport = this.createSession(toolCfg.command,
|
|
405
|
+
const transport = this.createSession(launchOverride?.command ?? toolCfg.command, launchOverride?.args ?? toolCfg.args, toolCfg.preambleFlag, targetToolConfigKey, extraPreamble, toolCfg.sessionIdFlag, targetWorktree, undefined, targetSessionId, !toolCfg.preambleFlag, false, undefined, launchOverride?.env);
|
|
406
406
|
this.agentTracker.emit(sourceSessionId, {
|
|
407
407
|
kind: "status",
|
|
408
408
|
message: `Forked ${targetSessionId} from this session`,
|
|
@@ -5,6 +5,7 @@ import { addDashboardOperationFailure, clearDashboardOperationFailures, listDash
|
|
|
5
5
|
import { composeDashboardWorktreeGroups } from "./dashboard-model.js";
|
|
6
6
|
import { loadDaemonInfo } from "../daemon.js";
|
|
7
7
|
import { getProjectStateDir, getStatePath } from "../paths.js";
|
|
8
|
+
import { writeJsonAtomic } from "../atomic-write.js";
|
|
8
9
|
import { loadMetadataState } from "../metadata-store.js";
|
|
9
10
|
import { createRuntimeExchangeStore } from "../runtime-core/exchange-store.js";
|
|
10
11
|
import { renderCurrentDashboardView as renderCurrentDashboardViewImpl } from "./runtime-state.js";
|
|
@@ -855,7 +856,7 @@ function removePersistedServicesForWorktree(path) {
|
|
|
855
856
|
const nextServices = (state.services ?? []).filter((service) => service.worktreePath !== path);
|
|
856
857
|
if (nextServices.length === (state.services ?? []).length)
|
|
857
858
|
return;
|
|
858
|
-
|
|
859
|
+
writeJsonAtomic(statePath, { ...state, services: nextServices });
|
|
859
860
|
}
|
|
860
861
|
catch { }
|
|
861
862
|
}
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { closeDebug, debug } from "../debug.js";
|
|
5
5
|
import { loadConfig } from "../config.js";
|
|
6
6
|
import { getStatePath } from "../paths.js";
|
|
7
|
+
import { quarantineCorruptFile, writeJsonAtomic } from "../atomic-write.js";
|
|
7
8
|
import { buildAimuxAgentInstructions } from "../session-bootstrap.js";
|
|
8
9
|
import { listTopologySessionStates, saveRuntimeTopologySessions } from "../runtime-core/topology-sessions.js";
|
|
9
10
|
import { adjustAfterRemove as adjustAfterRemoveImpl, buildLiveServiceStates as buildLiveServiceStatesImpl, evictZombieSession as evictZombieSessionImpl, graveyardSession as graveyardSessionImpl, isSessionRuntimeLive as isSessionRuntimeLiveImpl, loadOfflineServices as loadOfflineServicesImpl, loadOfflineTopologySessions as loadOfflineTopologySessionsImpl, restoreTmuxSessionsFromTopology as restoreTmuxSessionsFromTopologyImpl, recordSessionBackendSessionId as recordSessionBackendSessionIdImpl, resumeOfflineSession as resumeOfflineSessionImpl, startHeartbeat as startHeartbeatImpl, startProjectServiceRefresh as startProjectServiceRefreshImpl, startStatusRefresh as startStatusRefreshImpl, stopHeartbeat as stopHeartbeatImpl, stopProjectServiceRefresh as stopProjectServiceRefreshImpl, stopSessionToOffline as stopSessionToOfflineImpl, stopStatusRefresh as stopStatusRefreshImpl, syncSessionsFromTopology as syncSessionsFromTopologyImpl, } from "./runtime-state.js";
|
|
@@ -92,6 +93,7 @@ export function loadStateStatic() {
|
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
catch {
|
|
96
|
+
quarantineCorruptFile(statePath);
|
|
95
97
|
return null;
|
|
96
98
|
}
|
|
97
99
|
}
|
|
@@ -261,7 +263,9 @@ export const runtimeLifecycleMethods = {
|
|
|
261
263
|
});
|
|
262
264
|
mergedServices = [...otherServices, ...myServices];
|
|
263
265
|
}
|
|
264
|
-
catch {
|
|
266
|
+
catch {
|
|
267
|
+
quarantineCorruptFile(statePath);
|
|
268
|
+
}
|
|
265
269
|
}
|
|
266
270
|
saveRuntimeTopologySessions({ sessions: mergedSessions });
|
|
267
271
|
unpreservedExitedIds.clear();
|
|
@@ -270,7 +274,7 @@ export const runtimeLifecycleMethods = {
|
|
|
270
274
|
cwd: process.cwd(),
|
|
271
275
|
services: mergedServices,
|
|
272
276
|
};
|
|
273
|
-
|
|
277
|
+
writeJsonAtomic(statePath, state);
|
|
274
278
|
this.invalidateDesktopStateSnapshot();
|
|
275
279
|
},
|
|
276
280
|
teardown() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { discoverBackendSessionId } from "../backend-session-discovery.js";
|
|
2
3
|
import { loadConfig } from "../config.js";
|
|
3
4
|
import { loadMetadataState, updateSessionMetadata } from "../metadata-store.js";
|
|
4
5
|
import { isToolInternalWorktree, listWorktrees as listAllWorktrees } from "../worktree.js";
|
|
@@ -419,7 +420,18 @@ export function resumeOfflineSession(host, session) {
|
|
|
419
420
|
return;
|
|
420
421
|
const derived = loadMetadataState().sessions[session.id]?.derived;
|
|
421
422
|
const relaunchFresh = derived?.activity === "error" || derived?.attention === "error";
|
|
422
|
-
|
|
423
|
+
let backendSessionId = session.backendSessionId;
|
|
424
|
+
if (!backendSessionId && !relaunchFresh) {
|
|
425
|
+
// The durable backend id can be lost if a crash killed the tmux pane before
|
|
426
|
+
// it was captured. Recover it from the tool's on-disk session store so the
|
|
427
|
+
// agent stays resumable instead of being stranded.
|
|
428
|
+
const discovered = discoverBackendSessionId(session.toolConfigKey, session.worktreePath);
|
|
429
|
+
if (discovered) {
|
|
430
|
+
backendSessionId = discovered;
|
|
431
|
+
session.backendSessionId = discovered;
|
|
432
|
+
host.debug?.(`reconciled backend session id for ${session.id} from disk: ${discovered}`, "session");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
423
435
|
const useBackendResume = !relaunchFresh && host.sessionBootstrap.canResumeWithBackendSessionId(toolCfg, backendSessionId);
|
|
424
436
|
let actionArgs;
|
|
425
437
|
if (useBackendResume) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { getStatePath } from "../paths.js";
|
|
3
|
+
import { quarantineCorruptFile, writeJsonAtomic } from "../atomic-write.js";
|
|
3
4
|
import { buildServiceStateFromMetadata } from "./services.js";
|
|
4
5
|
import { listWorktreeGraveyardPaths } from "./worktree-graveyard.js";
|
|
5
6
|
function isAvailableSnapshotWorktree(worktreePath, graveyardPaths = listWorktreeGraveyardPaths()) {
|
|
@@ -61,11 +62,12 @@ export function persistProjectRuntimeSnapshotsBeforeTmuxStop(projectRoot, tmux)
|
|
|
61
62
|
existing = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
62
63
|
}
|
|
63
64
|
catch {
|
|
65
|
+
quarantineCorruptFile(statePath);
|
|
64
66
|
existing = null;
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
const nextState = mergeRuntimeSnapshots(existing, { services }, projectRoot);
|
|
68
|
-
|
|
70
|
+
writeJsonAtomic(statePath, nextState);
|
|
69
71
|
return { sessions: [], services };
|
|
70
72
|
}
|
|
71
73
|
export function persistProjectServiceSnapshotsBeforeRuntimeStop(projectRoot, tmux) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { findMainRepo } from "../worktree.js";
|
|
4
4
|
import { getStatePath } from "../paths.js";
|
|
5
|
+
import { writeJsonAtomic } from "../atomic-write.js";
|
|
5
6
|
import { wrapCommandWithShellIntegration, wrapInteractiveShellWithIntegration } from "../shell-hooks.js";
|
|
6
7
|
import { markLastUsed } from "../last-used.js";
|
|
7
8
|
import { removeTopologyService, upsertTopologyService, } from "../runtime-core/topology-services.js";
|
|
@@ -119,7 +120,7 @@ export function createService(host, commandLine, worktreePath, opts) {
|
|
|
119
120
|
const command = wrapped.command;
|
|
120
121
|
const args = wrapped.args;
|
|
121
122
|
const label = serviceLabelForCommand(trimmed);
|
|
122
|
-
const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(
|
|
123
|
+
const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(projectRoot);
|
|
123
124
|
const shouldRenderPending = host.startedInDashboard && host.mode === "dashboard";
|
|
124
125
|
if (shouldRenderPending) {
|
|
125
126
|
host.setPendingDashboardServiceAction(serviceId, "creating", {
|
|
@@ -237,7 +238,7 @@ export function removeOfflineService(host, serviceId) {
|
|
|
237
238
|
try {
|
|
238
239
|
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
239
240
|
state.services = (state.services ?? []).filter((service) => service.id !== serviceId);
|
|
240
|
-
|
|
241
|
+
writeJsonAtomic(statePath, state);
|
|
241
242
|
}
|
|
242
243
|
catch { }
|
|
243
244
|
}
|
|
@@ -299,7 +300,7 @@ export function resumeOfflineService(host, service) {
|
|
|
299
300
|
const command = wrapped.command;
|
|
300
301
|
const args = wrapped.args;
|
|
301
302
|
const label = service.label ?? serviceLabelForCommand(launchCommandLine);
|
|
302
|
-
const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(
|
|
303
|
+
const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(projectRoot);
|
|
303
304
|
const retainedTarget = service.tmuxTarget && host.tmuxRuntimeManager.hasWindow?.(service.tmuxTarget) ? service.tmuxTarget : undefined;
|
|
304
305
|
const target = retainedTarget ??
|
|
305
306
|
host.tmuxRuntimeManager.createWindow(tmuxSession.sessionName, label, resumeCwd, command, args, {
|
|
@@ -10,7 +10,7 @@ export declare function runDashboard(host: SessionLaunchHost): Promise<number>;
|
|
|
10
10
|
export declare function runProjectService(host: SessionLaunchHost): Promise<number>;
|
|
11
11
|
export declare function resumeSessions(host: SessionLaunchHost, toolFilter?: string): Promise<number>;
|
|
12
12
|
export declare function restoreSessions(host: SessionLaunchHost, toolFilter?: string): Promise<number>;
|
|
13
|
-
export declare function createSession(host: SessionLaunchHost, command: string, args: string[], preambleFlag?: string[], toolConfigKey?: string, extraPreamble?: string, sessionIdFlag?: string[], worktreePath?: string, backendSessionIdOverride?: string, sessionIdOverride?: string, detachedInTmux?: boolean, suppressStartupPreamble?: boolean, team?: SessionTeamMetadata): any;
|
|
13
|
+
export declare function createSession(host: SessionLaunchHost, command: string, args: string[], preambleFlag?: string[], toolConfigKey?: string, extraPreamble?: string, sessionIdFlag?: string[], worktreePath?: string, backendSessionIdOverride?: string, sessionIdOverride?: string, detachedInTmux?: boolean, suppressStartupPreamble?: boolean, team?: SessionTeamMetadata, launchEnv?: Record<string, string>): any;
|
|
14
14
|
export declare function migrateAgent(host: SessionLaunchHost, sessionId: string, targetWorktreePath: string): Promise<void>;
|
|
15
15
|
export declare function getSessionWorktreePath(host: SessionLaunchHost, sessionId: string): string | undefined;
|
|
16
16
|
export declare function getSessionsByWorktree(host: SessionLaunchHost): Map<string | undefined, any[]>;
|
|
@@ -306,7 +306,7 @@ export async function restoreSessions(host, toolFilter) {
|
|
|
306
306
|
host.openTmuxDashboardTarget();
|
|
307
307
|
return 0;
|
|
308
308
|
}
|
|
309
|
-
export function createSession(host, command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux = false, suppressStartupPreamble = false, team) {
|
|
309
|
+
export function createSession(host, command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux = false, suppressStartupPreamble = false, team, launchEnv) {
|
|
310
310
|
const cols = process.stdout.columns ?? 80;
|
|
311
311
|
const sessionId = sessionIdOverride ?? `${command}-${Math.random().toString(36).slice(2, 8)}`;
|
|
312
312
|
if (host.sessions.some((session) => session.id === sessionId)) {
|
|
@@ -314,6 +314,8 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
|
|
|
314
314
|
}
|
|
315
315
|
const config = loadConfig();
|
|
316
316
|
const toolCfg = toolConfigKey ? config.tools[toolConfigKey] : undefined;
|
|
317
|
+
// A launch override may swap the binary; aimux flags/preamble only apply to the tool's own command.
|
|
318
|
+
const isConfiguredToolCommand = Boolean(toolCfg && toolCfg.command === command);
|
|
317
319
|
const isClaudeResumeStyleLaunch = Boolean(toolCfg && toolConfigKey === "claude" && toolCfg.command === command) &&
|
|
318
320
|
shouldSkipClaudeSessionIdInjection(args);
|
|
319
321
|
const explicitClaudeBackendSessionId = toolCfg && toolConfigKey === "claude" && toolCfg.command === command
|
|
@@ -323,7 +325,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
|
|
|
323
325
|
? extractCodexBackendSessionIdFromArgs(args)
|
|
324
326
|
: undefined;
|
|
325
327
|
const effectiveSuppressStartupPreamble = suppressStartupPreamble;
|
|
326
|
-
const effectiveSessionIdFlag = isClaudeResumeStyleLaunch ?
|
|
328
|
+
const effectiveSessionIdFlag = isConfiguredToolCommand && !isClaudeResumeStyleLaunch ? sessionIdFlag : undefined;
|
|
327
329
|
const backendSessionId = backendSessionIdOverride ??
|
|
328
330
|
explicitClaudeBackendSessionId ??
|
|
329
331
|
explicitCodexBackendSessionId ??
|
|
@@ -339,7 +341,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
|
|
|
339
341
|
includeAimuxPreamble: automaticPreambleEnabled,
|
|
340
342
|
team,
|
|
341
343
|
});
|
|
342
|
-
const shouldInjectLaunchPreamble = Boolean(!effectiveSuppressStartupPreamble && preambleFlag && preamble.trim());
|
|
344
|
+
const shouldInjectLaunchPreamble = Boolean(isConfiguredToolCommand && !effectiveSuppressStartupPreamble && preambleFlag && preamble.trim());
|
|
343
345
|
const shouldInjectCodexDeveloperInstructions = Boolean(!effectiveSuppressStartupPreamble &&
|
|
344
346
|
toolCfg?.command === command &&
|
|
345
347
|
command === "codex" &&
|
|
@@ -375,6 +377,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
|
|
|
375
377
|
command: launchCommand,
|
|
376
378
|
args: finalArgs,
|
|
377
379
|
extraEnv: {
|
|
380
|
+
...(launchEnv ?? {}),
|
|
378
381
|
AIMUX_SESSION_ID: sessionId,
|
|
379
382
|
AIMUX_TOOL: toolConfigKey ?? command,
|
|
380
383
|
},
|
|
@@ -382,13 +385,23 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
|
|
|
382
385
|
launchCommand = wrapped.command;
|
|
383
386
|
finalArgs = wrapped.args;
|
|
384
387
|
}
|
|
385
|
-
else if (
|
|
388
|
+
else if (isConfiguredToolCommand) {
|
|
386
389
|
const wrapped = wrapCommandWithShellIntegration({
|
|
387
390
|
projectRoot,
|
|
388
391
|
sessionId,
|
|
389
392
|
tool: toolConfigKey ?? command,
|
|
390
393
|
command: launchCommand,
|
|
391
394
|
args: finalArgs,
|
|
395
|
+
extraEnv: launchEnv,
|
|
396
|
+
});
|
|
397
|
+
launchCommand = wrapped.command;
|
|
398
|
+
finalArgs = wrapped.args;
|
|
399
|
+
}
|
|
400
|
+
else if (launchEnv && Object.keys(launchEnv).length > 0) {
|
|
401
|
+
const wrapped = wrapCommandWithManagedLaunchEnv({
|
|
402
|
+
command: launchCommand,
|
|
403
|
+
args: finalArgs,
|
|
404
|
+
extraEnv: launchEnv,
|
|
392
405
|
});
|
|
393
406
|
launchCommand = wrapped.command;
|
|
394
407
|
finalArgs = wrapped.args;
|
|
@@ -399,7 +412,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
|
|
|
399
412
|
debug(`creating session: ${command} (configKey=${toolConfigKey ?? "cli"}, backendId=${backendSessionId ?? "none"}, cwd=${worktreePath ?? process.cwd()}, args=${finalArgs.length})`, "session");
|
|
400
413
|
debug(`spawn args: ${JSON.stringify(summarizeLaunchArgs(finalArgs))}`, "session");
|
|
401
414
|
const sessionStartTime = Date.now();
|
|
402
|
-
const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(
|
|
415
|
+
const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(projectRoot);
|
|
403
416
|
const target = host.tmuxRuntimeManager.createWindow(tmuxSession.sessionName, host.getSessionLabel(sessionId) ?? command, worktreePath ?? process.cwd(), launchCommand, finalArgs, { detached: detachedInTmux });
|
|
404
417
|
const tmuxTransport = new TmuxSessionTransport(sessionId, command, target, host.tmuxRuntimeManager, cols, process.stdout.rows ?? 24);
|
|
405
418
|
host.sessionTmuxTargets.set(sessionId, target);
|
|
@@ -409,7 +422,6 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
|
|
|
409
422
|
if (session instanceof TmuxSessionTransport) {
|
|
410
423
|
host.syncTmuxWindowMetadata(sessionId);
|
|
411
424
|
}
|
|
412
|
-
void projectRoot;
|
|
413
425
|
host.activeIndex = host.sessions.length - 1;
|
|
414
426
|
if (host.startedInDashboard && host.mode === "dashboard") {
|
|
415
427
|
host.invalidateDesktopStateSnapshot();
|
|
@@ -8,6 +8,7 @@ import { SessionRuntime } from "../session-runtime.js";
|
|
|
8
8
|
import { TmuxSessionTransport } from "../tmux/session-transport.js";
|
|
9
9
|
import { loadMetadataState } from "../metadata-store.js";
|
|
10
10
|
import { parseAgentOutput } from "../agent-output-parser.js";
|
|
11
|
+
import { normalizeSubmittedPrompt, waitForTmuxPromptSubmit } from "../agent-prompt-delivery.js";
|
|
11
12
|
import { captureGitContext } from "../context/context-bridge.js";
|
|
12
13
|
export function getSessionLabel(host, sessionId) {
|
|
13
14
|
return (host.sessionLabels.get(sessionId) ?? host.offlineSessions.find((session) => session.id === sessionId)?.label);
|
|
@@ -166,8 +167,14 @@ export async function sendAgentInput(host, sessionId, text) {
|
|
|
166
167
|
if (!target)
|
|
167
168
|
throw new Error(`Session "${sessionId}" does not have a live tmux target`);
|
|
168
169
|
session.transport.retarget(target);
|
|
169
|
-
|
|
170
|
-
session.transport.write(
|
|
170
|
+
const prompt = normalizeSubmittedPrompt(host.sessionToolKeys.get(sessionId), text, true);
|
|
171
|
+
session.transport.write(prompt);
|
|
172
|
+
await waitForTmuxPromptSubmit({
|
|
173
|
+
tmuxRuntimeManager: host.tmuxRuntimeManager,
|
|
174
|
+
target,
|
|
175
|
+
draft: prompt,
|
|
176
|
+
isTargetCurrent: () => resolveLiveSessionTmuxTarget(host, sessionId, target)?.windowId === target.windowId,
|
|
177
|
+
});
|
|
171
178
|
}
|
|
172
179
|
else {
|
|
173
180
|
session.write(text);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type ToolConfig } from "../config.js";
|
|
2
|
+
import { type LaunchOverride } from "../shell-args.js";
|
|
2
3
|
type ToolPickerHost = any;
|
|
3
4
|
export declare function buildToolPickerOverlayOutput(host: ToolPickerHost): string;
|
|
4
5
|
export declare function buildToolOptionsOverlayOutput(host: ToolPickerHost): string;
|
|
5
6
|
export declare function renderToolPicker(host: ToolPickerHost): void;
|
|
6
7
|
export declare function runSelectedTool(host: ToolPickerHost, toolKey: string, tool: ToolConfig, opts?: {
|
|
7
|
-
|
|
8
|
+
override?: LaunchOverride;
|
|
8
9
|
}): void;
|
|
9
10
|
export declare function showToolPicker(host: ToolPickerHost, sourceSessionId?: string): void;
|
|
10
11
|
export declare function handleToolPickerKey(host: ToolPickerHost, data: Buffer): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadConfig } from "../config.js";
|
|
2
2
|
import { parseKeys } from "../key-parser.js";
|
|
3
|
-
import {
|
|
3
|
+
import { parseLaunchCommandLine } from "../shell-args.js";
|
|
4
4
|
import { forkDashboardAgentWithFeedback, spawnDashboardAgentWithFeedback } from "./dashboard-ops.js";
|
|
5
5
|
function enabledTools() {
|
|
6
6
|
const config = loadConfig();
|
|
@@ -67,28 +67,37 @@ export function buildToolOptionsOverlayOutput(host) {
|
|
|
67
67
|
}
|
|
68
68
|
const [toolKey, tool] = selected;
|
|
69
69
|
const buffer = host.toolOptionsBuffer ?? "";
|
|
70
|
-
let
|
|
70
|
+
let override;
|
|
71
71
|
let parseError = host.toolOptionsError;
|
|
72
72
|
if (!parseError) {
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
override = parseLaunchCommandLine(buffer);
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
77
|
parseError = error instanceof Error ? error.message : String(error);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
const defaultArgs = tool.args.length ? tool.args.map(quoteShellArg).join(" ") : "(none)";
|
|
81
|
-
const preview = commandPreview(tool.command, [...tool.args, ...parsedExtraArgs]);
|
|
82
80
|
const lines = [
|
|
83
81
|
host.pickerMode === "fork" && host.forkSourceSessionId
|
|
84
|
-
? `Fork ${toolKey}: launch
|
|
85
|
-
: `${toolKey}: launch
|
|
82
|
+
? `Fork ${toolKey}: launch command`
|
|
83
|
+
: `${toolKey}: launch command`,
|
|
86
84
|
"",
|
|
87
|
-
`
|
|
88
|
-
` Extra args: ${buffer}_`,
|
|
89
|
-
"",
|
|
90
|
-
` Command: ${preview}`,
|
|
85
|
+
` ${buffer}_`,
|
|
91
86
|
];
|
|
87
|
+
if (override) {
|
|
88
|
+
lines.push("");
|
|
89
|
+
if (override.env) {
|
|
90
|
+
const envStr = Object.entries(override.env)
|
|
91
|
+
.map(([key, value]) => `${key}=${quoteShellArg(value)}`)
|
|
92
|
+
.join(" ");
|
|
93
|
+
lines.push(` Env: ${envStr}`);
|
|
94
|
+
}
|
|
95
|
+
lines.push(` Launch: ${commandPreview(override.command, override.args)}`);
|
|
96
|
+
lines.push("");
|
|
97
|
+
lines.push(override.command === tool.command
|
|
98
|
+
? " aimux hooks + session tracking applied"
|
|
99
|
+
: " custom binary — launched without aimux hooks");
|
|
100
|
+
}
|
|
92
101
|
if (parseError) {
|
|
93
102
|
lines.push("");
|
|
94
103
|
lines.push(` Error: ${parseError}`);
|
|
@@ -106,8 +115,7 @@ export function renderToolPicker(host) {
|
|
|
106
115
|
}
|
|
107
116
|
export function runSelectedTool(host, toolKey, tool, opts = {}) {
|
|
108
117
|
const wtPath = host.mode === "dashboard" ? host.dashboardState.focusedWorktreePath : undefined;
|
|
109
|
-
const
|
|
110
|
-
const launchArgs = [...tool.args, ...extraArgs];
|
|
118
|
+
const override = opts.override;
|
|
111
119
|
if (host.pickerMode === "fork") {
|
|
112
120
|
const sourceSessionId = host.forkSourceSessionId;
|
|
113
121
|
host.pickerMode = "create";
|
|
@@ -127,7 +135,7 @@ export function runSelectedTool(host, toolKey, tool, opts = {}) {
|
|
|
127
135
|
targetSessionId,
|
|
128
136
|
tool: toolKey,
|
|
129
137
|
worktreePath: wtPath,
|
|
130
|
-
|
|
138
|
+
launchOverride: override,
|
|
131
139
|
});
|
|
132
140
|
return;
|
|
133
141
|
}
|
|
@@ -137,7 +145,7 @@ export function runSelectedTool(host, toolKey, tool, opts = {}) {
|
|
|
137
145
|
targetSessionId,
|
|
138
146
|
targetWorktreePath: wtPath,
|
|
139
147
|
open: false,
|
|
140
|
-
|
|
148
|
+
launchOverride: override,
|
|
141
149
|
});
|
|
142
150
|
return;
|
|
143
151
|
}
|
|
@@ -153,11 +161,11 @@ export function runSelectedTool(host, toolKey, tool, opts = {}) {
|
|
|
153
161
|
sessionId,
|
|
154
162
|
tool: toolKey,
|
|
155
163
|
worktreePath: wtPath,
|
|
156
|
-
|
|
164
|
+
launchOverride: override,
|
|
157
165
|
});
|
|
158
166
|
return;
|
|
159
167
|
}
|
|
160
|
-
host.createSession(tool.command,
|
|
168
|
+
host.createSession(override?.command ?? tool.command, override?.args ?? tool.args, tool.preambleFlag, toolKey, undefined, tool.sessionIdFlag, wtPath, undefined, sessionId, false, false, undefined, override?.env);
|
|
161
169
|
}
|
|
162
170
|
export function showToolPicker(host, sourceSessionId) {
|
|
163
171
|
host.pickerMode = sourceSessionId ? "fork" : "create";
|
|
@@ -204,7 +212,7 @@ export function handleToolPickerKey(host, data) {
|
|
|
204
212
|
return;
|
|
205
213
|
}
|
|
206
214
|
host.toolOptionsToolKey = picked[0];
|
|
207
|
-
host.toolOptionsBuffer =
|
|
215
|
+
host.toolOptionsBuffer = commandPreview(picked[1].command, picked[1].args);
|
|
208
216
|
host.toolOptionsError = null;
|
|
209
217
|
host.openDashboardOverlay("tool-options");
|
|
210
218
|
if (typeof host.redrawDashboardWithOverlay === "function") {
|
|
@@ -271,9 +279,9 @@ export function handleToolOptionsKey(host, data) {
|
|
|
271
279
|
return;
|
|
272
280
|
}
|
|
273
281
|
const [toolKey, tool] = selected;
|
|
274
|
-
let
|
|
282
|
+
let override;
|
|
275
283
|
try {
|
|
276
|
-
|
|
284
|
+
override = parseLaunchCommandLine(host.toolOptionsBuffer ?? "");
|
|
277
285
|
}
|
|
278
286
|
catch (error) {
|
|
279
287
|
host.toolOptionsError = error instanceof Error ? error.message : String(error);
|
|
@@ -286,7 +294,7 @@ export function handleToolOptionsKey(host, data) {
|
|
|
286
294
|
return;
|
|
287
295
|
}
|
|
288
296
|
host.clearDashboardOverlay();
|
|
289
|
-
runSelectedTool(host, toolKey, tool, {
|
|
297
|
+
runSelectedTool(host, toolKey, tool, { override });
|
|
290
298
|
return;
|
|
291
299
|
}
|
|
292
300
|
if (event.name === "paste" || event.char) {
|
package/dist/notify.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare function notifyPrompt(sessionId: string): void;
|
|
|
7
7
|
export declare function notifyError(sessionId: string, message?: string): void;
|
|
8
8
|
/** Notify that an agent completed (exited cleanly) */
|
|
9
9
|
export declare function notifyComplete(sessionId: string): void;
|
|
10
|
-
export declare function notifyAlert(event: AlertEvent):
|
|
10
|
+
export declare function notifyAlert(event: AlertEvent): boolean;
|
|
11
11
|
export declare function notifyRemoteClientConnected(input: {
|
|
12
12
|
title?: unknown;
|
|
13
13
|
body?: unknown;
|