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.
Files changed (70) hide show
  1. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  2. package/dist/adapters/cli/claude-code.js +6 -7
  3. package/dist/adapters/cli/claude-code.js.map +1 -1
  4. package/dist/adapters/cli/pi-initial-prompt-extension.d.ts +27 -0
  5. package/dist/adapters/cli/pi-initial-prompt-extension.d.ts.map +1 -0
  6. package/dist/adapters/cli/pi-initial-prompt-extension.js +38 -0
  7. package/dist/adapters/cli/pi-initial-prompt-extension.js.map +1 -0
  8. package/dist/adapters/cli/pi-initial-prompt.d.ts +21 -0
  9. package/dist/adapters/cli/pi-initial-prompt.d.ts.map +1 -0
  10. package/dist/adapters/cli/pi-initial-prompt.js +55 -0
  11. package/dist/adapters/cli/pi-initial-prompt.js.map +1 -0
  12. package/dist/adapters/cli/pi.d.ts.map +1 -1
  13. package/dist/adapters/cli/pi.js +15 -4
  14. package/dist/adapters/cli/pi.js.map +1 -1
  15. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  16. package/dist/adapters/cli/read-isolation.js +5 -0
  17. package/dist/adapters/cli/read-isolation.js.map +1 -1
  18. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  19. package/dist/adapters/cli/shared-hints.js +8 -0
  20. package/dist/adapters/cli/shared-hints.js.map +1 -1
  21. package/dist/adapters/cli/types.d.ts +20 -0
  22. package/dist/adapters/cli/types.d.ts.map +1 -1
  23. package/dist/cli.js +4 -5
  24. package/dist/cli.js.map +1 -1
  25. package/dist/core/inflight-input-tracker.d.ts +1 -0
  26. package/dist/core/inflight-input-tracker.d.ts.map +1 -1
  27. package/dist/core/inflight-input-tracker.js.map +1 -1
  28. package/dist/core/session-ready-handshake.d.ts +11 -0
  29. package/dist/core/session-ready-handshake.d.ts.map +1 -0
  30. package/dist/core/session-ready-handshake.js +41 -0
  31. package/dist/core/session-ready-handshake.js.map +1 -0
  32. package/dist/core/worker-pool.d.ts.map +1 -1
  33. package/dist/core/worker-pool.js +9 -0
  34. package/dist/core/worker-pool.js.map +1 -1
  35. package/dist/daemon.d.ts.map +1 -1
  36. package/dist/daemon.js +10 -2
  37. package/dist/daemon.js.map +1 -1
  38. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  39. package/dist/im/lark/event-dispatcher.js +1 -3
  40. package/dist/im/lark/event-dispatcher.js.map +1 -1
  41. package/dist/platform/secure-host-file.d.ts.map +1 -1
  42. package/dist/platform/secure-host-file.js +173 -54
  43. package/dist/platform/secure-host-file.js.map +1 -1
  44. package/dist/setup/open-platform-automation.d.ts +1 -1
  45. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  46. package/dist/setup/open-platform-automation.js +0 -1
  47. package/dist/setup/open-platform-automation.js.map +1 -1
  48. package/dist/types.d.ts +7 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/utils/child-env.d.ts +1 -1
  51. package/dist/utils/child-env.d.ts.map +1 -1
  52. package/dist/utils/child-env.js +2 -0
  53. package/dist/utils/child-env.js.map +1 -1
  54. package/dist/utils/idle-detector.d.ts +9 -1
  55. package/dist/utils/idle-detector.d.ts.map +1 -1
  56. package/dist/utils/idle-detector.js +18 -5
  57. package/dist/utils/idle-detector.js.map +1 -1
  58. package/dist/utils/input-gate.d.ts +22 -3
  59. package/dist/utils/input-gate.d.ts.map +1 -1
  60. package/dist/utils/input-gate.js +22 -3
  61. package/dist/utils/input-gate.js.map +1 -1
  62. package/dist/utils/pending-input-queue.d.ts +19 -0
  63. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  64. package/dist/utils/pending-input-queue.js +21 -1
  65. package/dist/utils/pending-input-queue.js.map +1 -1
  66. package/dist/utils/ready-gate.d.ts +6 -6
  67. package/dist/utils/ready-gate.js +6 -6
  68. package/dist/worker.js +178 -45
  69. package/dist/worker.js.map +1 -1
  70. 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;;;;;;;;;;;;;;;;;;;;GAoBG;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
+ {"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,CAcT;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;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"}
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":"AAUA,MAAM,UAAU,mBAAmB,CACjC,OAA0B,EAC1B,IAAqB;IAErB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,0EAA0E;IAC1E,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,wCAAwC;IACxC,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS;WACvE,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAAC,qBAAqB;WACxD,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,CAAC,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACpD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;IACzC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;sEAEsE;AACtE,MAAM,UAAU,2BAA2B,CACzC,wBAAiC,EACjC,mBAA4B,EAC5B,IAAiC;IAEjC,OAAO,wBAAwB;WAC1B,CAAC,mBAAmB;WACpB,IAAI,EAAE,eAAe,KAAK,SAAS;WACnC,CAAC,IAAI,EAAE,qBAAqB,CAAC;AACpC,CAAC;AAED;;;;sEAIsE;AACtE,MAAM,UAAU,iCAAiC,CAAC,IAIjD;IACC,OAAO,IAAI,CAAC,0BAA0B;WACjC,CAAC,IAAI,CAAC,SAAS;WACf,IAAI,CAAC,eAAe,KAAK,SAAS,CAAC;AAC1C,CAAC;AAED;;;;wDAIwD;AACxD,MAAM,UAAU,mCAAmC,CAAC,IAInD;IACC,IAAI,CAAC,IAAI,CAAC,0BAA0B;QAAE,OAAO,KAAK,CAAC;IACnD,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,wBAAwB,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACpF,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;AACxD,CAAC;AAED;yEACyE;AACzE,MAAM,UAAU,sBAAsB,CACpC,OAAwB,EACxB,IAAiC;IAEjC,OAAO,OAAO,CAAC,eAAe,KAAK,SAAS;WACvC,IAAI,EAAE,eAAe,KAAK,SAAS;WACnC,CAAC,CAAC,OAAO,CAAC,qBAAqB;WAC/B,CAAC,CAAC,IAAI,EAAE,qBAAqB,CAAC;AACrC,CAAC;AAED;6EAC6E;AAC7E,MAAM,UAAU,oBAAoB,CAAC,mBAA4B;IAC/D,OAAO,CAAC,mBAAmB,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,OAAsD,EACtD,QAAsD;IAEtD,OAAO,QAAQ,CAAC,eAAe,KAAK,SAAS;WACxC,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;WAClC,OAAO,CAAC,eAAe,KAAK,QAAQ,CAAC,eAAe,CAAC;AAC5D,CAAC"}
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 fires a "真就绪" (true-ready) signal.
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 fires within ~3ms of the real input box
14
- * rendering, and crucially does NOT fire while the launcher is still on its
15
- * selector so it's an unambiguous "the CLI is genuinely ready" signal. This
16
- * gate suspends the first flush until that signal (or a fallback timeout) so the
17
- * selector can never eat the first message.
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
@@ -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 fires a "真就绪" (true-ready) signal.
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 fires within ~3ms of the real input box
14
- * rendering, and crucially does NOT fire while the launcher is still on its
15
- * selector so it's an unambiguous "the CLI is genuinely ready" signal. This
16
- * gate suspends the first flush until that signal (or a fallback timeout) so the
17
- * selector can never eat the first message.
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 fires a true-ready signal, so a cjadk-style startup selector's (which
798
- * falsely matches readyPattern) can't eat the first message. Recreated + armed
799
- * per spawn in spawnCli; disarmed on signal or fallback timeout. */
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. The real signal lands within ~ms of the input box rendering, so this is
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, the input box has appeared but Ink's
821
- * startup render isn't fully drained yet — typing immediately trips Claude's
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
- * the PTY to fall quiet for SETTLE_MS before the first flush — the signal still
826
- * gates readiness (anti-selector), the settle just lets the render drain. */
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
- * A real SessionStart/BOTMUX_READY_COMMAND signal is itself authoritative
852
- * prompt readiness; the timeout fallback only opens the gate and lets the
853
- * regular readyPattern/idle path prove readiness later. */
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(msg, item.turnId, item.dispatchAttempt);
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(msg, item.turnId, item.dispatchAttempt);
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(msg, undefined, '会话 JSONL', bridgeTurnId, undefined, turnSeq, item);
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(msg, result.recheck, '会话 JSONL', bridgeTurnId, result.failureReason, turnSeq, item);
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: deferInitialPrompt ? undefined : (cfg.prompt || undefined),
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
- markPromptReady();
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 = shouldDeferInitialPromptForStartup({
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 hook fired (via `botmux session-ready`
9458
- // daemon). The CLI's input box is genuinely rendered release the
9459
- // ready-gate and deliver any held first prompt. Idempotent: a later
9460
- // duplicate (clear/compact source) is a no-op.
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: true });
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);