botmux 3.4.1 → 3.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +6 -7
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/pi-initial-prompt-extension.d.ts +27 -0
- package/dist/adapters/cli/pi-initial-prompt-extension.d.ts.map +1 -0
- package/dist/adapters/cli/pi-initial-prompt-extension.js +38 -0
- package/dist/adapters/cli/pi-initial-prompt-extension.js.map +1 -0
- package/dist/adapters/cli/pi-initial-prompt.d.ts +21 -0
- package/dist/adapters/cli/pi-initial-prompt.d.ts.map +1 -0
- package/dist/adapters/cli/pi-initial-prompt.js +55 -0
- package/dist/adapters/cli/pi-initial-prompt.js.map +1 -0
- package/dist/adapters/cli/pi.d.ts.map +1 -1
- package/dist/adapters/cli/pi.js +15 -4
- package/dist/adapters/cli/pi.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +5 -0
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +8 -0
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +20 -0
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/cli.js +4 -5
- package/dist/cli.js.map +1 -1
- package/dist/core/inflight-input-tracker.d.ts +1 -0
- package/dist/core/inflight-input-tracker.d.ts.map +1 -1
- package/dist/core/inflight-input-tracker.js.map +1 -1
- package/dist/core/session-ready-handshake.d.ts +11 -0
- package/dist/core/session-ready-handshake.d.ts.map +1 -0
- package/dist/core/session-ready-handshake.js +41 -0
- package/dist/core/session-ready-handshake.js.map +1 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +9 -0
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +10 -2
- package/dist/daemon.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +1 -3
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/platform/secure-host-file.d.ts.map +1 -1
- package/dist/platform/secure-host-file.js +173 -54
- package/dist/platform/secure-host-file.js.map +1 -1
- package/dist/setup/open-platform-automation.d.ts +1 -1
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +0 -1
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/child-env.d.ts +1 -1
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +2 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/idle-detector.d.ts +9 -1
- package/dist/utils/idle-detector.d.ts.map +1 -1
- package/dist/utils/idle-detector.js +18 -5
- package/dist/utils/idle-detector.js.map +1 -1
- package/dist/utils/input-gate.d.ts +22 -3
- package/dist/utils/input-gate.d.ts.map +1 -1
- package/dist/utils/input-gate.js +22 -3
- package/dist/utils/input-gate.js.map +1 -1
- package/dist/utils/pending-input-queue.d.ts +19 -0
- package/dist/utils/pending-input-queue.d.ts.map +1 -1
- package/dist/utils/pending-input-queue.js +21 -1
- package/dist/utils/pending-input-queue.js.map +1 -1
- package/dist/utils/ready-gate.d.ts +6 -6
- package/dist/utils/ready-gate.js +6 -6
- package/dist/worker.js +178 -45
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-gate.js","sourceRoot":"","sources":["../../src/utils/input-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAAC,KAS9B;IACC,IAAI,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACzD,kEAAkE;IAClE,OAAO,KAAK,CAAC,iBAAiB,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,KAS/C;IACC,IAAI,CAAC,KAAK,CAAC,iCAAiC;QAAE,OAAO,IAAI,CAAC;IAC1D,IAAI,CAAC,KAAK,CAAC,eAAe;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,aAAa,CAAC;AAChD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"input-gate.js","sourceRoot":"","sources":["../../src/utils/input-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAAC,KAS9B;IACC,IAAI,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACzD,kEAAkE;IAClE,OAAO,KAAK,CAAC,iBAAiB,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2CAA2C,CAAC,KAM3D;IACC,OAAO,KAAK,CAAC,cAAc;WACtB,KAAK,CAAC,eAAe;WACrB,KAAK,CAAC,mBAAmB;WACzB,CAAC,KAAK,CAAC,aAAa;WACpB,CAAC,KAAK,CAAC,cAAc,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,KAS/C;IACC,IAAI,CAAC,KAAK,CAAC,iCAAiC;QAAE,OAAO,IAAI,CAAC;IAC1D,IAAI,CAAC,KAAK,CAAC,eAAe;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,aAAa,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAIrC;IACC,OAAO,KAAK,CAAC,sBAAsB,IAAI,KAAK,CAAC,+BAA+B,IAAI,KAAK,CAAC,0BAA0B,CAAC;AACnH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,iBAA0B;IAChE,OAAO,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;AACpD,CAAC"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { CodexAppTurnInput, VcMeetingImTurnOrigin } from '../types.js';
|
|
2
2
|
export interface PendingCliInput {
|
|
3
3
|
content: string;
|
|
4
|
+
/** The real user turn represented by `content` when delivery uses a short
|
|
5
|
+
* adapter command. Transcript bridges fingerprint this value, while the PTY
|
|
6
|
+
* receives `content`. */
|
|
7
|
+
logicalContent?: string;
|
|
4
8
|
turnId?: string;
|
|
5
9
|
dispatchAttempt?: number;
|
|
6
10
|
vcMeetingImTurnOrigin?: VcMeetingImTurnOrigin;
|
|
@@ -31,6 +35,21 @@ export declare function shouldDeferInitialPromptForArgLimit(opts: {
|
|
|
31
35
|
prompt?: string;
|
|
32
36
|
maxInitialPromptArgBytes?: number;
|
|
33
37
|
}): boolean;
|
|
38
|
+
/** Resolve the physical first-prompt transport after worker defer policy is
|
|
39
|
+
* known. A transformed argv prompt can provide a short deferred command while
|
|
40
|
+
* retaining the original text for bridge attribution. Other adapters keep the
|
|
41
|
+
* legacy behavior: argv when not deferred, original text in the queue when
|
|
42
|
+
* deferred. */
|
|
43
|
+
export declare function resolveInitialPromptDelivery(opts: {
|
|
44
|
+
originalPrompt?: string;
|
|
45
|
+
preparedArg?: string;
|
|
46
|
+
preparedDeferredContent?: string;
|
|
47
|
+
defer: boolean;
|
|
48
|
+
}): {
|
|
49
|
+
argvPrompt?: string;
|
|
50
|
+
queuedContent?: string;
|
|
51
|
+
logicalContent?: string;
|
|
52
|
+
};
|
|
34
53
|
/** Once either side of a queue boundary is durable, stop this batch and wait
|
|
35
54
|
* for the next reliable idle edge before writing the following turn. */
|
|
36
55
|
export declare function shouldStopPendingBatch(written: PendingCliInput, next: PendingCliInput | undefined): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending-input-queue.d.ts","sourceRoot":"","sources":["../../src/utils/pending-input-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE5E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EAAE,EAC1B,IAAI,EAAE,eAAe,GACpB,OAAO,
|
|
1
|
+
{"version":3,"file":"pending-input-queue.d.ts","sourceRoot":"","sources":["../../src/utils/pending-input-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE5E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB;;6BAEyB;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EAAE,EAC1B,IAAI,EAAE,eAAe,GACpB,OAAO,CAeT;AAED;;sEAEsE;AACtE,wBAAgB,2BAA2B,CACzC,wBAAwB,EAAE,OAAO,EACjC,mBAAmB,EAAE,OAAO,EAC5B,IAAI,EAAE,eAAe,GAAG,SAAS,GAChC,OAAO,CAKT;AAED;;;;sEAIsE;AACtE,wBAAgB,iCAAiC,CAAC,IAAI,EAAE;IACtD,0BAA0B,EAAE,OAAO,CAAC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAIV;AAED;;;;wDAIwD;AACxD,wBAAgB,mCAAmC,CAAC,IAAI,EAAE;IACxD,0BAA0B,EAAE,OAAO,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC,GAAG,OAAO,CAMV;AAED;;;;eAIe;AACf,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAYA;AAED;yEACyE;AACzE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,eAAe,GAAG,SAAS,GAChC,OAAO,CAKT;AAED;6EAC6E;AAC7E,wBAAgB,oBAAoB,CAAC,mBAAmB,EAAE,OAAO,GAAG,OAAO,CAE1E;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,EACtD,QAAQ,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,OAAO,CAIT"}
|
|
@@ -9,7 +9,8 @@ export function mergeQueuedCliInput(pending, next) {
|
|
|
9
9
|
// would drop or mis-attach the sidecar.
|
|
10
10
|
if (tail.dispatchAttempt !== undefined || next.dispatchAttempt !== undefined
|
|
11
11
|
|| tail.vcMeetingImTurnOrigin || next.vcMeetingImTurnOrigin
|
|
12
|
-
|| tail.codexAppInput || next.codexAppInput
|
|
12
|
+
|| tail.codexAppInput || next.codexAppInput
|
|
13
|
+
|| tail.logicalContent || next.logicalContent)
|
|
13
14
|
return false;
|
|
14
15
|
tail.content = `${tail.content}\n\n${next.content}`;
|
|
15
16
|
tail.turnId = next.turnId ?? tail.turnId;
|
|
@@ -49,6 +50,25 @@ export function shouldDeferInitialPromptForArgLimit(opts) {
|
|
|
49
50
|
return false;
|
|
50
51
|
return Buffer.byteLength(opts.prompt, 'utf8') > limit;
|
|
51
52
|
}
|
|
53
|
+
/** Resolve the physical first-prompt transport after worker defer policy is
|
|
54
|
+
* known. A transformed argv prompt can provide a short deferred command while
|
|
55
|
+
* retaining the original text for bridge attribution. Other adapters keep the
|
|
56
|
+
* legacy behavior: argv when not deferred, original text in the queue when
|
|
57
|
+
* deferred. */
|
|
58
|
+
export function resolveInitialPromptDelivery(opts) {
|
|
59
|
+
if (!opts.originalPrompt)
|
|
60
|
+
return {};
|
|
61
|
+
if (!opts.defer) {
|
|
62
|
+
return { argvPrompt: opts.preparedArg ?? opts.originalPrompt };
|
|
63
|
+
}
|
|
64
|
+
const queuedContent = opts.preparedDeferredContent ?? opts.originalPrompt;
|
|
65
|
+
return {
|
|
66
|
+
queuedContent,
|
|
67
|
+
...(queuedContent !== opts.originalPrompt
|
|
68
|
+
? { logicalContent: opts.originalPrompt }
|
|
69
|
+
: {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
52
72
|
/** Once either side of a queue boundary is durable, stop this batch and wait
|
|
53
73
|
* for the next reliable idle edge before writing the following turn. */
|
|
54
74
|
export function shouldStopPendingBatch(written, next) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending-input-queue.js","sourceRoot":"","sources":["../../src/utils/pending-input-queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pending-input-queue.js","sourceRoot":"","sources":["../../src/utils/pending-input-queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ready-gate state machine — holds the FIRST prompt for Claude-family CLIs until
|
|
3
|
-
* a SessionStart hook
|
|
3
|
+
* a SessionStart hook proves the outer startup selector has been passed.
|
|
4
4
|
*
|
|
5
5
|
* Why this exists: a custom launcher (e.g. `cjadk claude`) shows an interactive
|
|
6
6
|
* model/session selector at startup whose cursor is `❯` (U+276F). That glyph
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
* trailing Enter mis-selects a menu item). The selector clears before Claude's
|
|
11
11
|
* real input box renders, so the message is silently lost.
|
|
12
12
|
*
|
|
13
|
-
* Claude Code's `SessionStart` hook
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Claude Code's `SessionStart` hook crucially does NOT fire while the launcher
|
|
14
|
+
* is still on its selector. Claude can run multiple matching hooks in parallel,
|
|
15
|
+
* though, and renders the real prompt only after all finish. This gate therefore
|
|
16
|
+
* establishes the anti-selector boundary; the worker separately waits for fresh
|
|
17
|
+
* post-signal prompt evidence before it flushes Claude input.
|
|
18
18
|
*
|
|
19
19
|
* Lifecycle (recreated per CLI spawn in the worker):
|
|
20
20
|
* - `arm()` — call at spawn when the adapter injects the SessionStart
|
package/dist/utils/ready-gate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ready-gate state machine — holds the FIRST prompt for Claude-family CLIs until
|
|
3
|
-
* a SessionStart hook
|
|
3
|
+
* a SessionStart hook proves the outer startup selector has been passed.
|
|
4
4
|
*
|
|
5
5
|
* Why this exists: a custom launcher (e.g. `cjadk claude`) shows an interactive
|
|
6
6
|
* model/session selector at startup whose cursor is `❯` (U+276F). That glyph
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
* trailing Enter mis-selects a menu item). The selector clears before Claude's
|
|
11
11
|
* real input box renders, so the message is silently lost.
|
|
12
12
|
*
|
|
13
|
-
* Claude Code's `SessionStart` hook
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Claude Code's `SessionStart` hook crucially does NOT fire while the launcher
|
|
14
|
+
* is still on its selector. Claude can run multiple matching hooks in parallel,
|
|
15
|
+
* though, and renders the real prompt only after all finish. This gate therefore
|
|
16
|
+
* establishes the anti-selector boundary; the worker separately waits for fresh
|
|
17
|
+
* post-signal prompt evidence before it flushes Claude input.
|
|
18
18
|
*
|
|
19
19
|
* Lifecycle (recreated per CLI spawn in the worker):
|
|
20
20
|
* - `arm()` — call at spawn when the adapter injects the SessionStart
|
package/dist/worker.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 7. On 'restart', kills CLI and re-spawns with --resume
|
|
14
14
|
*/
|
|
15
15
|
import { randomBytes } from 'node:crypto';
|
|
16
|
-
import { mkdirSync, writeFileSync, unlinkSync, existsSync, statSync, lstatSync, readdirSync, readlinkSync, readFileSync, realpathSync, copyFileSync, watch as fsWatch, createWriteStream, openSync, closeSync, fstatSync, constants as fsConstants } from 'node:fs';
|
|
16
|
+
import { mkdirSync, writeFileSync, unlinkSync, rmdirSync, existsSync, statSync, lstatSync, readdirSync, readlinkSync, readFileSync, realpathSync, copyFileSync, watch as fsWatch, createWriteStream, openSync, closeSync, fstatSync, constants as fsConstants } from 'node:fs';
|
|
17
17
|
import { atomicWriteFileSync } from './utils/atomic-write.js';
|
|
18
18
|
import { join, basename, dirname } from 'node:path';
|
|
19
19
|
import { homedir, tmpdir } from 'node:os';
|
|
@@ -24,12 +24,12 @@ import { readProcessStartIdentity } from './core/session-marker.js';
|
|
|
24
24
|
import { drainTranscript, joinAssistantText, trailingAssistantText, findJsonlContainingFingerprint, findJsonlsContainingExactContent, findLatestJsonl, extractLastAssistantTurn, extractTurnStartText, splitTranscriptEventsByCutoff } from './services/claude-transcript.js';
|
|
25
25
|
import { BridgeTurnQueue, makeFingerprint, normaliseForFingerprint } from './services/bridge-turn-queue.js';
|
|
26
26
|
import { shouldSuppressBridgeEmit } from './services/bridge-fallback-gate.js';
|
|
27
|
-
import { decideHardTimeoutAction, decideSettleMarkReady, shouldReleaseFirstPromptTimeout, shouldWriteNow } from './utils/input-gate.js';
|
|
27
|
+
import { decideHardTimeoutAction, decideSettleMarkReady, shouldReleaseFirstPromptTimeout, shouldWaitForPostSessionStartPromptEvidence, shouldWriteNow, } from './utils/input-gate.js';
|
|
28
28
|
import { canStartInjectionFlush, shouldDeferUserFlush, shouldFlushInjectionsFirst } from './core/inject-queue-policy.js';
|
|
29
29
|
import { stripAnsiForLog, tailChars } from './utils/crash-log.js';
|
|
30
30
|
import { CodexUpdateDialogGuard } from './utils/codex-update-dialog.js';
|
|
31
31
|
import { installStdioEpipeGuard, isIgnorableStreamError } from './utils/stdio-epipe-guard.js';
|
|
32
|
-
import { mergeQueuedCliInput, pendingInputMayFlush, pendingInputAllowsTypeAhead, shouldDeferArgsBakedDurablePrompt, shouldDeferInitialPromptForArgLimit, shouldStopPendingBatch, terminalReleasesDurableTurn, } from './utils/pending-input-queue.js';
|
|
32
|
+
import { mergeQueuedCliInput, pendingInputMayFlush, pendingInputAllowsTypeAhead, resolveInitialPromptDelivery, shouldDeferArgsBakedDurablePrompt, shouldDeferInitialPromptForArgLimit, shouldStopPendingBatch, terminalReleasesDurableTurn, } from './utils/pending-input-queue.js';
|
|
33
33
|
import { ReadyGate, shouldArmReadyGate } from './utils/ready-gate.js';
|
|
34
34
|
import { shouldRunStartupCommandsOnSpawn, shouldDeferInitialPromptForStartup } from './core/startup-commands.js';
|
|
35
35
|
import { sanitizePerBotEnv } from './core/per-bot-env.js';
|
|
@@ -144,6 +144,36 @@ let remoteWsUrl;
|
|
|
144
144
|
let remoteThreadId;
|
|
145
145
|
let rpcDialogDismissTimer = null;
|
|
146
146
|
let rpcEnginePidMarker = null;
|
|
147
|
+
const piInitialPromptCleanupPaths = [];
|
|
148
|
+
const piInitialPromptCleanupDirs = [];
|
|
149
|
+
let piInitialPromptReadonlyRoots = [];
|
|
150
|
+
let piInitialPromptAdditionalArgs = [];
|
|
151
|
+
let piInitialPromptEnv = {};
|
|
152
|
+
function cleanupPiInitialPromptFiles() {
|
|
153
|
+
while (piInitialPromptCleanupPaths.length > 0) {
|
|
154
|
+
const p = piInitialPromptCleanupPaths.pop();
|
|
155
|
+
if (!p)
|
|
156
|
+
continue;
|
|
157
|
+
try {
|
|
158
|
+
unlinkSync(p);
|
|
159
|
+
}
|
|
160
|
+
catch { /* best effort */ }
|
|
161
|
+
}
|
|
162
|
+
while (piInitialPromptCleanupDirs.length > 0) {
|
|
163
|
+
const dir = piInitialPromptCleanupDirs.pop();
|
|
164
|
+
if (!dir)
|
|
165
|
+
continue;
|
|
166
|
+
// Non-recursive on purpose: remove only an empty session-owned directory,
|
|
167
|
+
// never a shared root or a path that unexpectedly gained other content.
|
|
168
|
+
try {
|
|
169
|
+
rmdirSync(dir);
|
|
170
|
+
}
|
|
171
|
+
catch { /* best effort */ }
|
|
172
|
+
}
|
|
173
|
+
piInitialPromptReadonlyRoots = [];
|
|
174
|
+
piInitialPromptAdditionalArgs = [];
|
|
175
|
+
piInitialPromptEnv = {};
|
|
176
|
+
}
|
|
147
177
|
function stopCodexRpcEngine() {
|
|
148
178
|
const engine = codexRpcEngine;
|
|
149
179
|
codexRpcEngine = undefined;
|
|
@@ -672,6 +702,9 @@ let reattachIdleProbeTimer = null;
|
|
|
672
702
|
* adapter's hermesBridgeAttach reads the correct mode. */
|
|
673
703
|
let lastSpawnEffectiveResume = false;
|
|
674
704
|
let lastSpawnEffectiveCliSessionId;
|
|
705
|
+
let lastSpawnDeferInitialPrompt = false;
|
|
706
|
+
let lastSpawnQueuedInitialPrompt;
|
|
707
|
+
let lastSpawnQueuedInitialPromptLogicalContent;
|
|
675
708
|
let idleDetector = null;
|
|
676
709
|
let isTmuxMode = false;
|
|
677
710
|
/** True once a crash diagnostic tmux shell (bmx-diag-<sid>) is live. */
|
|
@@ -794,17 +827,16 @@ let bareShellLaunchBlocked = false;
|
|
|
794
827
|
* shell. Reset per spawn in spawnCli. */
|
|
795
828
|
let bareShellChecked = false;
|
|
796
829
|
/** Ready-gate (Claude-family): holds the first prompt until the SessionStart
|
|
797
|
-
* hook
|
|
798
|
-
*
|
|
799
|
-
* per spawn
|
|
830
|
+
* hook proves a cjadk-style startup selector is behind us. Claude then needs
|
|
831
|
+
* fresh post-hook prompt evidence because sibling hooks may still be running.
|
|
832
|
+
* Recreated + armed per spawn; disarmed on signal or fallback timeout. */
|
|
800
833
|
let readyGate = new ReadyGate();
|
|
801
834
|
/** Fallback timer: if the SessionStart signal never arrives (hook injection
|
|
802
835
|
* failed / old CLI / launcher didn't pass --settings / adopt) release the gate
|
|
803
836
|
* and fall back to readyPattern + quiescence. */
|
|
804
837
|
let readySignalTimer = null;
|
|
805
838
|
/** How long the ready-gate waits for the SessionStart signal before falling
|
|
806
|
-
* back.
|
|
807
|
-
* pure insurance against a missing/failed hook — generous but bounded. */
|
|
839
|
+
* back. This is insurance against a missing/failed hook — generous but bounded. */
|
|
808
840
|
const READY_SIGNAL_TIMEOUT_MS = 45_000;
|
|
809
841
|
/** Soft fallback for CLIs that never emit an idle/ready signal during startup.
|
|
810
842
|
* Legacy adapters release queued first input here. Adapters that opt into
|
|
@@ -817,13 +849,13 @@ const FIRST_PROMPT_HARD_TIMEOUT_MS = 90_000;
|
|
|
817
849
|
/** Epoch ms of the most recent PTY output — used to settle for quiescence
|
|
818
850
|
* before the first flush (see settleThenFlush). */
|
|
819
851
|
let lastPtyOutputAtMs = 0;
|
|
820
|
-
/** After the SessionStart signal fires,
|
|
821
|
-
*
|
|
852
|
+
/** After the SessionStart signal fires, Ink's startup rendering or sibling
|
|
853
|
+
* hooks may still be active — typing immediately can trip Claude's
|
|
822
854
|
* paste-burst heuristic and the `\` soft-newline markers (claude-code
|
|
823
855
|
* writeInput) get kept literally. This is pronounced under wrapperCli launchers
|
|
824
856
|
* (e.g. `aiden x claude`) whose Claude renders more at startup. So we wait for
|
|
825
|
-
*
|
|
826
|
-
*
|
|
857
|
+
* PTY quiescence, while Claude additionally requires fresh prompt evidence
|
|
858
|
+
* after the SessionStart boundary. */
|
|
827
859
|
const READY_FLUSH_SETTLE_MS = 1_000;
|
|
828
860
|
/** Upper bound on the settle so a chatty startup (spinners, periodic redraw)
|
|
829
861
|
* can't stall the first prompt indefinitely. */
|
|
@@ -846,11 +878,18 @@ let promptReadyDetectedDuringSettle = false;
|
|
|
846
878
|
* spawn that renders ❯ but never fires BOTMUX_READY_COMMAND waits the full
|
|
847
879
|
* hard timeout (and previously never delivered at all). */
|
|
848
880
|
let readyPatternSeenDuringHold = false;
|
|
881
|
+
/** Claude's SessionStart hooks run in parallel. Its botmux hook proves the
|
|
882
|
+
* startup selector is behind us, but sibling project hooks may still be
|
|
883
|
+
* running. Hold type-ahead until a fresh PTY prompt is observed after the
|
|
884
|
+
* SessionStart signal. */
|
|
885
|
+
let awaitingPostSessionStartPromptEvidence = false;
|
|
886
|
+
/** Scoped marker set only by IdleDetector's screen-driven callback. */
|
|
887
|
+
let postSessionStartPromptEvidenceInFlight = false;
|
|
849
888
|
/** Wait until the PTY has been quiet for READY_FLUSH_SETTLE_MS (Ink render
|
|
850
889
|
* drained), capped at READY_FLUSH_SETTLE_CAP_MS, then flush the held prompt.
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
890
|
+
* An authoritative direct ready command (Hermes) can mark prompt readiness;
|
|
891
|
+
* Claude's SessionStart only opens the anti-selector boundary and its regular
|
|
892
|
+
* readyPattern/idle path must prove readiness afterward. */
|
|
854
893
|
function settleThenFlush(startedAtMs, promptReadyAfterSettle) {
|
|
855
894
|
readyFlushSettleTimer = null;
|
|
856
895
|
const now = Date.now();
|
|
@@ -4527,6 +4566,15 @@ function releaseRawInputRestartGate() {
|
|
|
4527
4566
|
rawInputRestartGate = false;
|
|
4528
4567
|
log('Replacement CLI prompt ready — releasing deferred passthrough commands');
|
|
4529
4568
|
}
|
|
4569
|
+
function markPromptReadyFromPty() {
|
|
4570
|
+
postSessionStartPromptEvidenceInFlight = true;
|
|
4571
|
+
try {
|
|
4572
|
+
markPromptReady();
|
|
4573
|
+
}
|
|
4574
|
+
finally {
|
|
4575
|
+
postSessionStartPromptEvidenceInFlight = false;
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4530
4578
|
function markPromptReady() {
|
|
4531
4579
|
if (isPromptReady)
|
|
4532
4580
|
return; // guard against duplicate calls
|
|
@@ -4546,6 +4594,14 @@ function markPromptReady() {
|
|
|
4546
4594
|
log('Idle detected but holding for SessionStart ready signal (startup selector guard)');
|
|
4547
4595
|
return;
|
|
4548
4596
|
}
|
|
4597
|
+
if (awaitingPostSessionStartPromptEvidence) {
|
|
4598
|
+
if (!postSessionStartPromptEvidenceInFlight) {
|
|
4599
|
+
log('Ignoring non-PTY ready source while waiting for post-SessionStart prompt evidence');
|
|
4600
|
+
return;
|
|
4601
|
+
}
|
|
4602
|
+
awaitingPostSessionStartPromptEvidence = false;
|
|
4603
|
+
log('Fresh prompt evidence observed after SessionStart hooks');
|
|
4604
|
+
}
|
|
4549
4605
|
if (isSettlingFirstFlush) {
|
|
4550
4606
|
promptReadyDetectedDuringSettle = true;
|
|
4551
4607
|
log('Idle detected during ready-gate settle; deferring prompt-ready until settle completes');
|
|
@@ -4577,6 +4633,7 @@ function markPromptReady() {
|
|
|
4577
4633
|
maybeEmitWorkflowTranscriptOutput();
|
|
4578
4634
|
if (awaitingFirstPrompt) {
|
|
4579
4635
|
awaitingFirstPrompt = false;
|
|
4636
|
+
awaitingPostSessionStartPromptEvidence = false;
|
|
4580
4637
|
renderer?.markNewTurn(); // exclude history replay from streaming card
|
|
4581
4638
|
}
|
|
4582
4639
|
send({ type: 'prompt_ready' });
|
|
@@ -4916,6 +4973,10 @@ async function flushPending() {
|
|
|
4916
4973
|
log(`Holding ${pendingMessages.length} pending message(s) until ready-gate settle completes`);
|
|
4917
4974
|
return;
|
|
4918
4975
|
}
|
|
4976
|
+
if (awaitingPostSessionStartPromptEvidence) {
|
|
4977
|
+
log(`Holding ${pendingMessages.length} pending message(s) until post-SessionStart prompt evidence`);
|
|
4978
|
+
return;
|
|
4979
|
+
}
|
|
4919
4980
|
// Type-ahead adapters flush even while the CLI is busy; others wait for
|
|
4920
4981
|
// idle. Claude bridge fallback used to also disable type-ahead because
|
|
4921
4982
|
// BridgeTurnQueue.ingest didn't recognise the `attachment(queued_command)`
|
|
@@ -5040,6 +5101,7 @@ async function flushPending() {
|
|
|
5040
5101
|
if (!codexRpcEngine)
|
|
5041
5102
|
inflightInputs.onWrite(item);
|
|
5042
5103
|
const msg = item.content;
|
|
5104
|
+
const logicalMsg = item.logicalContent ?? msg;
|
|
5043
5105
|
currentBotmuxTurnId = item.turnId;
|
|
5044
5106
|
currentBotmuxDispatchAttempt = item.dispatchAttempt;
|
|
5045
5107
|
currentVcMeetingImTurnOrigin = item.vcMeetingImTurnOrigin;
|
|
@@ -5059,14 +5121,14 @@ async function flushPending() {
|
|
|
5059
5121
|
bridgeIngest();
|
|
5060
5122
|
}
|
|
5061
5123
|
catch { /* best-effort */ }
|
|
5062
|
-
bridgeTurnId = bridgeMarkPendingTurn(
|
|
5124
|
+
bridgeTurnId = bridgeMarkPendingTurn(logicalMsg, item.turnId, item.dispatchAttempt);
|
|
5063
5125
|
}
|
|
5064
5126
|
else if (codexBridgeActive) {
|
|
5065
5127
|
// Codex mark works even before the rollout path is known: the
|
|
5066
5128
|
// queue is path-agnostic, and the late-attach below will start
|
|
5067
5129
|
// ingest from offset 0 so the user_message that lands shortly
|
|
5068
5130
|
// after still fingerprint-matches this turn.
|
|
5069
|
-
codexBridgeMarkPendingTurn(
|
|
5131
|
+
codexBridgeMarkPendingTurn(logicalMsg, item.turnId, item.dispatchAttempt);
|
|
5070
5132
|
}
|
|
5071
5133
|
if (durableWrite
|
|
5072
5134
|
&& cliAdapter.reliableTurnTerminal === true
|
|
@@ -5120,7 +5182,7 @@ async function flushPending() {
|
|
|
5120
5182
|
// do. Otherwise surface it as a submit failure so the message isn't
|
|
5121
5183
|
// silently lost.
|
|
5122
5184
|
if (backend)
|
|
5123
|
-
scheduleSubmitFailureNotify(
|
|
5185
|
+
scheduleSubmitFailureNotify(logicalMsg, undefined, '会话 JSONL', bridgeTurnId, undefined, turnSeq, item);
|
|
5124
5186
|
break;
|
|
5125
5187
|
}
|
|
5126
5188
|
// Persist any sessionId the adapter observed via authoritative sources
|
|
@@ -5139,7 +5201,7 @@ async function flushPending() {
|
|
|
5139
5201
|
// nulled backend) the user already got a "CLI exited" notice; don't also
|
|
5140
5202
|
// nag that the submit wasn't confirmed.
|
|
5141
5203
|
if (result && result.submitted === false && backend) {
|
|
5142
|
-
scheduleSubmitFailureNotify(
|
|
5204
|
+
scheduleSubmitFailureNotify(logicalMsg, result.recheck, '会话 JSONL', bridgeTurnId, result.failureReason, turnSeq, item);
|
|
5143
5205
|
}
|
|
5144
5206
|
// All structured bridges now drain every pending message in one flush:
|
|
5145
5207
|
// Claude's BridgeTurnQueue handles `attachment(queued_command)` events
|
|
@@ -6312,6 +6374,30 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6312
6374
|
// baking it into args would drop the message that triggered the resume.
|
|
6313
6375
|
// Finally, defer adapter-declared over-limit prompts to avoid backend command
|
|
6314
6376
|
// string limits (tmux "command too long") while preserving short argv prompts.
|
|
6377
|
+
let preparedInitialPrompt;
|
|
6378
|
+
let promptArgPreparationChanged = false;
|
|
6379
|
+
let preparedDeferredInput;
|
|
6380
|
+
if (cfg.prompt) {
|
|
6381
|
+
const prepared = cliAdapter.prepareInitialPromptArg?.({
|
|
6382
|
+
initialPrompt: cfg.prompt,
|
|
6383
|
+
sessionId: effectiveAdapterSessionId,
|
|
6384
|
+
sessionDataDir: process.env.SESSION_DATA_DIR,
|
|
6385
|
+
});
|
|
6386
|
+
if (prepared?.readonlyRoots?.length) {
|
|
6387
|
+
piInitialPromptReadonlyRoots = [
|
|
6388
|
+
...new Set([...piInitialPromptReadonlyRoots, ...prepared.readonlyRoots]),
|
|
6389
|
+
];
|
|
6390
|
+
}
|
|
6391
|
+
if (prepared?.cleanupPaths?.length) {
|
|
6392
|
+
piInitialPromptCleanupPaths.push(...prepared.cleanupPaths);
|
|
6393
|
+
}
|
|
6394
|
+
if (prepared?.cleanupDirs?.length) {
|
|
6395
|
+
piInitialPromptCleanupDirs.push(...prepared.cleanupDirs);
|
|
6396
|
+
}
|
|
6397
|
+
preparedDeferredInput = prepared?.deferredInput;
|
|
6398
|
+
preparedInitialPrompt = prepared?.initialPrompt ?? cfg.prompt;
|
|
6399
|
+
promptArgPreparationChanged = preparedInitialPrompt !== cfg.prompt;
|
|
6400
|
+
}
|
|
6315
6401
|
const deferInitialPrompt = shouldDeferInitialPromptForStartup({
|
|
6316
6402
|
hasStartupCommands: !!cfg.startupCommands?.length,
|
|
6317
6403
|
adoptMode: cfg.adoptMode === true,
|
|
@@ -6321,11 +6407,25 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6321
6407
|
adoptMode: cfg.adoptMode === true,
|
|
6322
6408
|
dispatchAttempt: cfg.dispatchAttempt,
|
|
6323
6409
|
}) || (effectiveResume && cliAdapter.initialPromptArgsIgnoredOnResume === true)
|
|
6324
|
-
|| shouldDeferInitialPromptForArgLimit({
|
|
6410
|
+
|| (!promptArgPreparationChanged && shouldDeferInitialPromptForArgLimit({
|
|
6325
6411
|
passesInitialPromptViaArgs: cliAdapter.passesInitialPromptViaArgs === true,
|
|
6326
6412
|
prompt: cfg.prompt,
|
|
6327
6413
|
maxInitialPromptArgBytes: cliAdapter.maxInitialPromptArgBytes,
|
|
6328
|
-
});
|
|
6414
|
+
}));
|
|
6415
|
+
const initialPromptDelivery = resolveInitialPromptDelivery({
|
|
6416
|
+
originalPrompt: cfg.prompt,
|
|
6417
|
+
preparedArg: preparedInitialPrompt,
|
|
6418
|
+
preparedDeferredContent: preparedDeferredInput?.content,
|
|
6419
|
+
defer: deferInitialPrompt,
|
|
6420
|
+
});
|
|
6421
|
+
preparedInitialPrompt = initialPromptDelivery.argvPrompt;
|
|
6422
|
+
lastSpawnQueuedInitialPrompt = initialPromptDelivery.queuedContent;
|
|
6423
|
+
lastSpawnQueuedInitialPromptLogicalContent = initialPromptDelivery.logicalContent;
|
|
6424
|
+
if (deferInitialPrompt && preparedDeferredInput) {
|
|
6425
|
+
piInitialPromptAdditionalArgs = [...(preparedDeferredInput.additionalArgs ?? [])];
|
|
6426
|
+
piInitialPromptEnv = { ...(preparedDeferredInput.env ?? {}) };
|
|
6427
|
+
}
|
|
6428
|
+
lastSpawnDeferInitialPrompt = deferInitialPrompt;
|
|
6329
6429
|
kiroSessionIdCaptureArmed = cfg.cliId === 'kiro-cli' && !effectiveCliSessionId && !willReattachPersistent;
|
|
6330
6430
|
kiroSessionIdCaptureBuffer = '';
|
|
6331
6431
|
// Per-bot local read isolation: assemble the Seatbelt profile context (the gate
|
|
@@ -6404,7 +6504,7 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6404
6504
|
resume: effectiveResume,
|
|
6405
6505
|
workingDir: cfg.workingDir,
|
|
6406
6506
|
resumeSessionId: effectiveCliSessionId,
|
|
6407
|
-
initialPrompt:
|
|
6507
|
+
initialPrompt: preparedInitialPrompt,
|
|
6408
6508
|
botName: cfg.botName,
|
|
6409
6509
|
botOpenId: cfg.botOpenId,
|
|
6410
6510
|
larkAppId: cfg.larkAppId,
|
|
@@ -6419,6 +6519,12 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6419
6519
|
remoteWsUrl,
|
|
6420
6520
|
remoteThreadId,
|
|
6421
6521
|
});
|
|
6522
|
+
// Pi's deferred long-first-prompt command is implemented by a session-scoped
|
|
6523
|
+
// extension. Keep its launch args across owned process restarts while the
|
|
6524
|
+
// queued/in-flight command may still need replay.
|
|
6525
|
+
if (piInitialPromptAdditionalArgs.length > 0) {
|
|
6526
|
+
args.unshift(...piInitialPromptAdditionalArgs);
|
|
6527
|
+
}
|
|
6422
6528
|
// Extra args from env (CLI_DISABLE_DEFAULT_ARGS is removed — adapters own their defaults)
|
|
6423
6529
|
const extra = (process.env.CLI_EXTRA_ARGS ?? '').trim();
|
|
6424
6530
|
if (extra)
|
|
@@ -6548,6 +6654,9 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6548
6654
|
// them past the server's global env.
|
|
6549
6655
|
if (cliAdapter.spawnEnv)
|
|
6550
6656
|
Object.assign(childEnv, cliAdapter.spawnEnv);
|
|
6657
|
+
if (Object.keys(piInitialPromptEnv).length > 0) {
|
|
6658
|
+
Object.assign(childEnv, piInitialPromptEnv);
|
|
6659
|
+
}
|
|
6551
6660
|
// v2 read isolation: point the CLI at its PER-BOT config dir (set AFTER spawnEnv
|
|
6552
6661
|
// so it overrides any adapter default). claude → CLAUDE_CONFIG_DIR, codex →
|
|
6553
6662
|
// CODEX_HOME. Both are in BOTMUX_INJECTED_ENV_KEYS so the tmux backend forwards
|
|
@@ -6634,6 +6743,9 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6634
6743
|
cliId: cliAdapter.id,
|
|
6635
6744
|
resolvedBin: canonical(cliAdapter.resolvedBin),
|
|
6636
6745
|
}),
|
|
6746
|
+
// buildV2DenyPaths masks the shared pi-initial-prompts root. Re-open
|
|
6747
|
+
// only this session's private child directory for Pi's @file/extension.
|
|
6748
|
+
...piInitialPromptReadonlyRoots.map(canonical),
|
|
6637
6749
|
];
|
|
6638
6750
|
finalDenyPaths = carve.finalDenyPaths.map(canonical);
|
|
6639
6751
|
traverseDirs = carve.traverseDirs.map(canonical);
|
|
@@ -6880,6 +6992,7 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6880
6992
|
extraExecPaths: cliAdapter.sandboxExtraExecPaths?.(),
|
|
6881
6993
|
readonlyRoots: [
|
|
6882
6994
|
...(cfg.skillReadonlyRoots ?? []),
|
|
6995
|
+
...piInitialPromptReadonlyRoots,
|
|
6883
6996
|
...(readIsoLinuxMasks?.ownReadOnlyPaths ?? []),
|
|
6884
6997
|
],
|
|
6885
6998
|
mcpGatewaySocketPath: sessionMcpGatewayHost?.socketPath,
|
|
@@ -7369,6 +7482,7 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
7369
7482
|
isSettlingFirstFlush = false;
|
|
7370
7483
|
promptReadyDetectedDuringSettle = false;
|
|
7371
7484
|
readyPatternSeenDuringHold = false;
|
|
7485
|
+
awaitingPostSessionStartPromptEvidence = false;
|
|
7372
7486
|
// Reset quiescence baseline so the settle measures silence from THIS spawn.
|
|
7373
7487
|
lastPtyOutputAtMs = Date.now();
|
|
7374
7488
|
const readyHookAvailable = effectiveReadyHookInstall
|
|
@@ -7411,7 +7525,7 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
7411
7525
|
// quiescence, repeatedly triggering markPromptReady() and duplicate cards.
|
|
7412
7526
|
if (effectiveBackendType !== 'riff') {
|
|
7413
7527
|
idleDetector = new IdleDetector(cliAdapter);
|
|
7414
|
-
idleDetector.onIdle(async () => {
|
|
7528
|
+
idleDetector.onIdle(async (evidenceSource) => {
|
|
7415
7529
|
log('Prompt detected (idle)');
|
|
7416
7530
|
// Bridge drain MUST run before markPromptReady() — the latter calls
|
|
7417
7531
|
// flushPending() which can immediately fire the next queued message
|
|
@@ -7433,7 +7547,12 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
7433
7547
|
log(`Codex bridge emit error: ${err.message}`);
|
|
7434
7548
|
}
|
|
7435
7549
|
}
|
|
7436
|
-
|
|
7550
|
+
if (evidenceSource === 'screen') {
|
|
7551
|
+
markPromptReadyFromPty();
|
|
7552
|
+
}
|
|
7553
|
+
else {
|
|
7554
|
+
markPromptReady();
|
|
7555
|
+
}
|
|
7437
7556
|
});
|
|
7438
7557
|
}
|
|
7439
7558
|
backend.onData(onPtyData);
|
|
@@ -7582,6 +7701,7 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
7582
7701
|
return;
|
|
7583
7702
|
}
|
|
7584
7703
|
awaitingFirstPrompt = false;
|
|
7704
|
+
awaitingPostSessionStartPromptEvidence = false;
|
|
7585
7705
|
renderer?.markNewTurn();
|
|
7586
7706
|
log(forced
|
|
7587
7707
|
? `WARN First prompt hard timeout — ${cliName()} readyPattern did not arrive; forcing queued message flush`
|
|
@@ -7626,6 +7746,8 @@ function killCli(opts = {}) {
|
|
|
7626
7746
|
currentCliCredentialIsolated = false;
|
|
7627
7747
|
stopSessionMcpGatewayHost();
|
|
7628
7748
|
stopCodexRpcEngine();
|
|
7749
|
+
if (!opts.preservePending)
|
|
7750
|
+
cleanupPiInitialPromptFiles();
|
|
7629
7751
|
destroyCrashDiagnosticTerminal('killCli');
|
|
7630
7752
|
idleDetector?.dispose();
|
|
7631
7753
|
idleDetector = null;
|
|
@@ -7643,6 +7765,7 @@ function killCli(opts = {}) {
|
|
|
7643
7765
|
isSettlingFirstFlush = false;
|
|
7644
7766
|
promptReadyDetectedDuringSettle = false;
|
|
7645
7767
|
readyPatternSeenDuringHold = false;
|
|
7768
|
+
awaitingPostSessionStartPromptEvidence = false;
|
|
7646
7769
|
stopScreenAnalyzer();
|
|
7647
7770
|
stopScreenUpdates();
|
|
7648
7771
|
backend?.kill();
|
|
@@ -9097,20 +9220,7 @@ process.on('message', async (raw) => {
|
|
|
9097
9220
|
// Tier-1/Tier-2 fresh demotion, which clears the flag). Adopt spawns
|
|
9098
9221
|
// return from spawnCli before that write — exclude them explicitly so
|
|
9099
9222
|
// the stale module-level value can't leak in.
|
|
9100
|
-
const deferInitialPrompt =
|
|
9101
|
-
hasStartupCommands: !!msg.startupCommands?.length,
|
|
9102
|
-
adoptMode: msg.adoptMode === true,
|
|
9103
|
-
passesInitialPromptViaArgs: cliAdapter?.passesInitialPromptViaArgs === true,
|
|
9104
|
-
}) || shouldDeferArgsBakedDurablePrompt({
|
|
9105
|
-
passesInitialPromptViaArgs: cliAdapter?.passesInitialPromptViaArgs === true,
|
|
9106
|
-
adoptMode: msg.adoptMode === true,
|
|
9107
|
-
dispatchAttempt: msg.dispatchAttempt,
|
|
9108
|
-
}) || (msg.adoptMode !== true && lastSpawnEffectiveResume && cliAdapter?.initialPromptArgsIgnoredOnResume === true)
|
|
9109
|
-
|| shouldDeferInitialPromptForArgLimit({
|
|
9110
|
-
passesInitialPromptViaArgs: cliAdapter?.passesInitialPromptViaArgs === true,
|
|
9111
|
-
prompt: msg.prompt,
|
|
9112
|
-
maxInitialPromptArgBytes: cliAdapter?.maxInitialPromptArgBytes,
|
|
9113
|
-
});
|
|
9223
|
+
const deferInitialPrompt = lastSpawnDeferInitialPrompt;
|
|
9114
9224
|
if (msg.prompt && cliAdapter?.passesInitialPromptViaArgs && !deferInitialPrompt && codexBridgeFallbackActive()) {
|
|
9115
9225
|
// Args-baked first prompts (notably Pi) never pass through the normal
|
|
9116
9226
|
// 'message' IPC path, so the structured bridge would otherwise see the
|
|
@@ -9135,7 +9245,10 @@ process.on('message', async (raw) => {
|
|
|
9135
9245
|
deferInitialPrompt,
|
|
9136
9246
|
})) {
|
|
9137
9247
|
pendingMessages.push({
|
|
9138
|
-
content: msg.prompt,
|
|
9248
|
+
content: lastSpawnQueuedInitialPrompt ?? msg.prompt,
|
|
9249
|
+
...(lastSpawnQueuedInitialPromptLogicalContent
|
|
9250
|
+
? { logicalContent: lastSpawnQueuedInitialPromptLogicalContent }
|
|
9251
|
+
: {}),
|
|
9139
9252
|
turnId: msg.turnId,
|
|
9140
9253
|
dispatchAttempt: msg.dispatchAttempt,
|
|
9141
9254
|
vcMeetingImTurnOrigin: msg.vcMeetingImTurnOrigin,
|
|
@@ -9454,24 +9567,43 @@ process.on('message', async (raw) => {
|
|
|
9454
9567
|
break;
|
|
9455
9568
|
}
|
|
9456
9569
|
case 'session_ready': {
|
|
9457
|
-
// Claude-family SessionStart
|
|
9458
|
-
//
|
|
9459
|
-
//
|
|
9460
|
-
//
|
|
9570
|
+
// Claude-family SessionStart hooks run in parallel. This signal proves
|
|
9571
|
+
// the startup selector is behind us, but a slower project hook can still
|
|
9572
|
+
// be running and Claude does not render its real prompt until ALL hooks
|
|
9573
|
+
// finish. Clear selector-era evidence and require a fresh PTY prompt after
|
|
9574
|
+
// the signal. Hermes keeps its authoritative ready-command behavior.
|
|
9461
9575
|
log(`SessionStart ready signal received (source=${msg.source ?? '?'})`);
|
|
9576
|
+
const waitForPostHookPrompt = shouldWaitForPostSessionStartPromptEvidence({
|
|
9577
|
+
isClaudeFamily: !!cliAdapter?.claudeDataDir,
|
|
9578
|
+
hasReadyPattern: !!cliAdapter?.readyPattern,
|
|
9579
|
+
awaitingFirstPrompt,
|
|
9580
|
+
isPromptReady,
|
|
9581
|
+
alreadyWaiting: awaitingPostSessionStartPromptEvidence,
|
|
9582
|
+
});
|
|
9583
|
+
if (waitForPostHookPrompt) {
|
|
9584
|
+
awaitingPostSessionStartPromptEvidence = true;
|
|
9585
|
+
promptReadyDetectedDuringSettle = false;
|
|
9586
|
+
readyPatternSeenDuringHold = false;
|
|
9587
|
+
idleDetector?.resetReadyEvidence();
|
|
9588
|
+
lastPtyOutputAtMs = Date.now();
|
|
9589
|
+
log('SessionStart boundary recorded — waiting for fresh post-hook prompt evidence');
|
|
9590
|
+
}
|
|
9462
9591
|
// 先记下 gate 是否已被 45s fallback 释放:ReadyGate.receive() 是一次性
|
|
9463
9592
|
// 语义,fallback 抢先后 releaseReadyGate 会整块跳过迟到的真信号。
|
|
9464
9593
|
const lateAfterFallback = readyGate.isArmed && readyGate.isReceived;
|
|
9465
|
-
releaseReadyGate('SessionStart hook', { promptReadyAfterSettle:
|
|
9594
|
+
releaseReadyGate('SessionStart hook', { promptReadyAfterSettle: !waitForPostHookPrompt });
|
|
9466
9595
|
// 冷启动超过 READY_SIGNAL_TIMEOUT_MS 的 CLI(Hermes 常态是 2-3 分钟)恰好
|
|
9467
9596
|
// 总落在 fallback 之后:fallback 只开闸不投递(非 type-ahead 的
|
|
9468
9597
|
// flushPending 是 no-op),真信号依然是权威就绪,这里直接兑现。仅限首轮
|
|
9469
9598
|
// (awaitingFirstPrompt)——首条 prompt 交付后 clear/compact 来源的
|
|
9470
9599
|
// SessionStart 保持原有 no-op 语义,绝不在会话中途误标就绪。
|
|
9471
|
-
if (lateAfterFallback && awaitingFirstPrompt && !isPromptReady) {
|
|
9600
|
+
if (lateAfterFallback && awaitingFirstPrompt && !isPromptReady && !waitForPostHookPrompt) {
|
|
9472
9601
|
log('Late ready signal after timeout fallback — marking prompt ready now');
|
|
9473
9602
|
markPromptReady();
|
|
9474
9603
|
}
|
|
9604
|
+
if (msg.requestId) {
|
|
9605
|
+
send({ type: 'session_ready_ack', requestId: msg.requestId });
|
|
9606
|
+
}
|
|
9475
9607
|
break;
|
|
9476
9608
|
}
|
|
9477
9609
|
case 'set_display_mode': {
|
|
@@ -9578,6 +9710,7 @@ process.on('message', async (raw) => {
|
|
|
9578
9710
|
});
|
|
9579
9711
|
// ─── Cleanup ─────────────────────────────────────────────────────────────────
|
|
9580
9712
|
function cleanup() {
|
|
9713
|
+
cleanupPiInitialPromptFiles();
|
|
9581
9714
|
stopSessionMcpGatewayHost();
|
|
9582
9715
|
if (tmuxRestartTimer) {
|
|
9583
9716
|
clearTimeout(tmuxRestartTimer);
|