@caupulican/pi-adaptative 0.93.4 → 0.93.7

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 (78) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/bundled-resources/skills/n-plus-2-architecture/SKILL.md +13 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +5 -20
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/autonomy/envelope-enforcement.d.ts +3 -1
  7. package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -1
  8. package/dist/core/autonomy/envelope-enforcement.js +23 -12
  9. package/dist/core/autonomy/envelope-enforcement.js.map +1 -1
  10. package/dist/core/autonomy/gates.d.ts.map +1 -1
  11. package/dist/core/autonomy/gates.js +24 -26
  12. package/dist/core/autonomy/gates.js.map +1 -1
  13. package/dist/core/context-gc.d.ts.map +1 -1
  14. package/dist/core/context-gc.js +128 -3
  15. package/dist/core/context-gc.js.map +1 -1
  16. package/dist/core/session-shutdown.d.ts +9 -0
  17. package/dist/core/session-shutdown.d.ts.map +1 -0
  18. package/dist/core/session-shutdown.js +44 -0
  19. package/dist/core/session-shutdown.js.map +1 -0
  20. package/dist/core/settings-manager.js.map +1 -1
  21. package/dist/core/skill-vault.d.ts +24 -16
  22. package/dist/core/skill-vault.d.ts.map +1 -1
  23. package/dist/core/skill-vault.js +159 -82
  24. package/dist/core/skill-vault.js.map +1 -1
  25. package/dist/core/system-prompt.d.ts.map +1 -1
  26. package/dist/core/system-prompt.js +7 -18
  27. package/dist/core/system-prompt.js.map +1 -1
  28. package/dist/core/tools/bash.d.ts +3 -1
  29. package/dist/core/tools/bash.d.ts.map +1 -1
  30. package/dist/core/tools/bash.js +15 -7
  31. package/dist/core/tools/bash.js.map +1 -1
  32. package/dist/core/tools/edit.d.ts.map +1 -1
  33. package/dist/core/tools/edit.js +86 -33
  34. package/dist/core/tools/edit.js.map +1 -1
  35. package/dist/core/tools/file-mutation-intent.d.ts +16 -0
  36. package/dist/core/tools/file-mutation-intent.d.ts.map +1 -1
  37. package/dist/core/tools/file-mutation-intent.js +51 -2
  38. package/dist/core/tools/file-mutation-intent.js.map +1 -1
  39. package/dist/core/tools/persistent-process-coordinator.d.ts +8 -2
  40. package/dist/core/tools/persistent-process-coordinator.d.ts.map +1 -1
  41. package/dist/core/tools/persistent-process-coordinator.js +52 -4
  42. package/dist/core/tools/persistent-process-coordinator.js.map +1 -1
  43. package/dist/core/tools/shell-execution-session.d.ts +8 -1
  44. package/dist/core/tools/shell-execution-session.d.ts.map +1 -1
  45. package/dist/core/tools/shell-execution-session.js +54 -3
  46. package/dist/core/tools/shell-execution-session.js.map +1 -1
  47. package/dist/core/tools/shell-session.d.ts +5 -2
  48. package/dist/core/tools/shell-session.d.ts.map +1 -1
  49. package/dist/core/tools/shell-session.js +112 -17
  50. package/dist/core/tools/shell-session.js.map +1 -1
  51. package/dist/core/tools/skill.d.ts +2 -1
  52. package/dist/core/tools/skill.d.ts.map +1 -1
  53. package/dist/core/tools/skill.js +26 -12
  54. package/dist/core/tools/skill.js.map +1 -1
  55. package/dist/core/tools/windows-shell-engine.d.ts +1 -1
  56. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -1
  57. package/dist/core/tools/windows-shell-engine.js +6 -2
  58. package/dist/core/tools/windows-shell-engine.js.map +1 -1
  59. package/dist/index.d.ts +1 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/modes/interactive/components/settings-selector.js +2 -2
  64. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  65. package/dist/utils/shell.d.ts +3 -5
  66. package/dist/utils/shell.d.ts.map +1 -1
  67. package/dist/utils/shell.js +4 -7
  68. package/dist/utils/shell.js.map +1 -1
  69. package/docs/settings.md +7 -7
  70. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  71. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  72. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  73. package/examples/extensions/sandbox/package-lock.json +2 -2
  74. package/examples/extensions/sandbox/package.json +1 -1
  75. package/examples/extensions/with-deps/package-lock.json +2 -2
  76. package/examples/extensions/with-deps/package.json +1 -1
  77. package/npm-shrinkwrap.json +12 -12
  78. package/package.json +4 -4
@@ -2,8 +2,9 @@
2
2
  * Shared lifecycle owner for one serialized, persistent child process.
3
3
  *
4
4
  * Wire protocols, state projection, and respawn policy stay with their concrete shell adapters.
5
- * This boundary owns only the invariant OS mechanics: one active task, one tracked child, stale
6
- * event rejection, exit/close arbitration, whole-tree kill, loop references, and parent exit.
5
+ * This boundary owns only the invariant OS mechanics: one active task, one current child, every
6
+ * outstanding child terminal, stale event rejection, exit/close arbitration, whole-tree kill,
7
+ * loop references, and parent exit.
7
8
  */
8
9
  import type { ChildProcess } from "node:child_process";
9
10
  export interface PersistentChildHandlers {
@@ -14,18 +15,23 @@ export interface PersistentChildHandlers {
14
15
  }
15
16
  export declare class PersistentProcessCoordinator {
16
17
  private currentChild;
18
+ private readonly pendingChildTerminals;
17
19
  private queue;
18
20
  private disposed;
19
21
  constructor();
20
22
  get child(): ChildProcess | null;
23
+ get terminalPromise(): Promise<void>;
21
24
  runSerialized<T>(task: () => Promise<T>): Promise<T>;
22
25
  attach(child: ChildProcess, handlers: PersistentChildHandlers): void;
23
26
  kill(): void;
27
+ private terminateChild;
24
28
  dispose(): void;
25
29
  /** Synchronous best-effort tree kill for the process exit hook. */
26
30
  killForProcessExit(): void;
27
31
  /** Idle coordinators must not keep one-shot Node modes alive; active commands must. */
28
32
  setLoopRef(active: boolean): void;
33
+ private waitForTerminalRelease;
34
+ private trackTerminal;
29
35
  private clear;
30
36
  }
31
37
  //# sourceMappingURL=persistent-process-coordinator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistent-process-coordinator.d.ts","sourceRoot":"","sources":["../../../src/core/tools/persistent-process-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;CACnC;AAaD,qBAAa,4BAA4B;IACxC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAS;IAEzB,cAGC;IAED,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,CAE/B;IAED,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOnD;IAED,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CAuCnE;IAED,IAAI,IAAI,IAAI,CAKX;IAED,OAAO,IAAI,IAAI,CAKd;IAED,mEAAmE;IACnE,kBAAkB,IAAI,IAAI,CAEzB;IAED,uFAAuF;IACvF,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAIhC;IAED,OAAO,CAAC,KAAK;CAMb"}
1
+ {"version":3,"file":"persistent-process-coordinator.d.ts","sourceRoot":"","sources":["../../../src/core/tools/persistent-process-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;CACnC;AAaD,qBAAa,4BAA4B;IACxC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA4B;IAClE,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAS;IAEzB,cAGC;IAED,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,CAE/B;IAED,IAAI,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAED,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOnD;IAED,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CAyCnE;IAED,IAAI,IAAI,IAAI,CAKX;IAED,OAAO,CAAC,cAAc;IAatB,OAAO,IAAI,IAAI,CAKd;IAED,mEAAmE;IACnE,kBAAkB,IAAI,IAAI,CAEzB;IAED,uFAAuF;IACvF,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAIhC;YAEa,sBAAsB;IAOpC,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,KAAK;CAMb"}
@@ -2,8 +2,9 @@
2
2
  * Shared lifecycle owner for one serialized, persistent child process.
3
3
  *
4
4
  * Wire protocols, state projection, and respawn policy stay with their concrete shell adapters.
5
- * This boundary owns only the invariant OS mechanics: one active task, one tracked child, stale
6
- * event rejection, exit/close arbitration, whole-tree kill, loop references, and parent exit.
5
+ * This boundary owns only the invariant OS mechanics: one active task, one current child, every
6
+ * outstanding child terminal, stale event rejection, exit/close arbitration, whole-tree kill,
7
+ * loop references, and parent exit.
7
8
  */
8
9
  import { setChildProcessLoopRef } from "../../utils/child-process-ref.js";
9
10
  import { killProcessTree, trackDetachedChildPid, untrackDetachedChildPid } from "../../utils/shell.js";
@@ -21,6 +22,7 @@ function installExitHook() {
21
22
  export class PersistentProcessCoordinator {
22
23
  constructor() {
23
24
  this.currentChild = null;
25
+ this.pendingChildTerminals = new Set();
24
26
  this.queue = Promise.resolve();
25
27
  this.disposed = false;
26
28
  installExitHook();
@@ -29,6 +31,9 @@ export class PersistentProcessCoordinator {
29
31
  get child() {
30
32
  return this.currentChild;
31
33
  }
34
+ get terminalPromise() {
35
+ return this.waitForTerminalRelease();
36
+ }
32
37
  runSerialized(task) {
33
38
  const run = this.queue.then(task);
34
39
  this.queue = run.then(() => undefined, () => undefined);
@@ -36,8 +41,8 @@ export class PersistentProcessCoordinator {
36
41
  }
37
42
  attach(child, handlers) {
38
43
  if (this.disposed) {
39
- if (child.pid)
40
- killProcessTree(child.pid);
44
+ this.trackTerminal(child);
45
+ this.terminateChild(child);
41
46
  throw new Error("Persistent process coordinator is disposed");
42
47
  }
43
48
  if (this.currentChild)
@@ -45,6 +50,7 @@ export class PersistentProcessCoordinator {
45
50
  this.currentChild = child;
46
51
  if (child.pid)
47
52
  trackDetachedChildPid(child.pid);
53
+ this.trackTerminal(child);
48
54
  child.stdout?.on("data", (data) => {
49
55
  if (this.currentChild === child)
50
56
  handlers.onStdout(data);
@@ -86,8 +92,21 @@ export class PersistentProcessCoordinator {
86
92
  if (!child)
87
93
  return;
88
94
  this.clear(child);
95
+ this.terminateChild(child);
96
+ }
97
+ terminateChild(child) {
98
+ // Bun's Windows child-process bridge can stop publishing terminal events after every
99
+ // handle has been unref'd. Re-arm the already-installed close observer before killing so
100
+ // strict teardown can distinguish physical handle release from process exit.
101
+ setChildProcessLoopRef(child, true);
89
102
  if (child.pid)
90
103
  killProcessTree(child.pid);
104
+ try {
105
+ child.kill();
106
+ }
107
+ catch {
108
+ // Process already dead
109
+ }
91
110
  }
92
111
  dispose() {
93
112
  if (this.disposed)
@@ -107,6 +126,35 @@ export class PersistentProcessCoordinator {
107
126
  return;
108
127
  setChildProcessLoopRef(child, active);
109
128
  }
129
+ async waitForTerminalRelease() {
130
+ await this.queue;
131
+ while (this.pendingChildTerminals.size > 0) {
132
+ await Promise.all(Array.from(this.pendingChildTerminals));
133
+ }
134
+ }
135
+ trackTerminal(child) {
136
+ let settled = false;
137
+ let resolveTerminal;
138
+ const terminalPromise = new Promise((resolve) => {
139
+ resolveTerminal = resolve;
140
+ });
141
+ const settleTerminal = () => {
142
+ if (settled)
143
+ return;
144
+ settled = true;
145
+ child.off("error", handleTerminalError);
146
+ child.off("close", settleTerminal);
147
+ this.pendingChildTerminals.delete(terminalPromise);
148
+ resolveTerminal();
149
+ };
150
+ const handleTerminalError = () => {
151
+ if (!child.pid)
152
+ settleTerminal();
153
+ };
154
+ this.pendingChildTerminals.add(terminalPromise);
155
+ child.on("error", handleTerminalError);
156
+ child.once("close", settleTerminal);
157
+ }
110
158
  clear(child) {
111
159
  if (this.currentChild !== child)
112
160
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"persistent-process-coordinator.js","sourceRoot":"","sources":["../../../src/core/tools/persistent-process-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AASvG,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAgC,CAAC;AACjE,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B,SAAS,eAAe;IACvB,IAAI,iBAAiB;QAAE,OAAO;IAC9B,iBAAiB,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACvB,KAAK,MAAM,WAAW,IAAI,gBAAgB;YAAE,WAAW,CAAC,kBAAkB,EAAE,CAAC;IAC9E,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,4BAA4B;IAKxC;QAJQ,iBAAY,GAAwB,IAAI,CAAC;QACzC,UAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;QACzC,aAAQ,GAAG,KAAK,CAAC;QAGxB,eAAe,EAAE,CAAC;QAClB,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,KAAK;QACR,OAAO,IAAI,CAAC,YAAY,CAAC;IAC1B,CAAC;IAED,aAAa,CAAI,IAAsB;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,IAAI,CACpB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CACf,CAAC;QACF,OAAO,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,CAAC,KAAmB,EAAE,QAAiC;QAC5D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,KAAK,CAAC,GAAG;gBAAE,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,IAAI,CAAC,YAAY;YAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QAC9F,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC1B,IAAI,KAAK,CAAC,GAAG;YAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEhD,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;YACzC,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK;gBAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;YACzC,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK;gBAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAAE,OAAO;YAC/B,QAAQ,CAAC,OAAO,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;QACH,IAAI,QAAQ,GAAkB,IAAI,CAAC;QACnC,IAAI,aAAwD,CAAC;QAE7D,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK;gBAAE,OAAO;YACxC,QAAQ,GAAG,IAAI,CAAC;YAChB,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC/B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;oBAAE,OAAO;gBAC/B,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC,EAAE,KAAK,CAAC,CAAC;YACV,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,IAAI,OAAO,IAAI,aAAa,EAAE,CAAC;gBACpF,aAAa,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;QACF,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC1B,IAAI,aAAa;gBAAE,YAAY,CAAC,aAAa,CAAC,CAAC;YAC/C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAAE,OAAO;YAC/B,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,IAAI;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,KAAK,CAAC,GAAG;YAAE,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO;QACN,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;IAED,mEAAmE;IACnE,kBAAkB;QACjB,IAAI,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;IAED,uFAAuF;IACvF,UAAU,CAAC,MAAe;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAEO,KAAK,CAAC,KAAmB;QAChC,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,KAAK,CAAC,GAAG;YAAE,uBAAuB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,OAAO,IAAI,CAAC;IACb,CAAC;CACD","sourcesContent":["/**\n * Shared lifecycle owner for one serialized, persistent child process.\n *\n * Wire protocols, state projection, and respawn policy stay with their concrete shell adapters.\n * This boundary owns only the invariant OS mechanics: one active task, one tracked child, stale\n * event rejection, exit/close arbitration, whole-tree kill, loop references, and parent exit.\n */\n\nimport type { ChildProcess } from \"node:child_process\";\nimport { setChildProcessLoopRef } from \"../../utils/child-process-ref.ts\";\nimport { killProcessTree, trackDetachedChildPid, untrackDetachedChildPid } from \"../../utils/shell.ts\";\n\nexport interface PersistentChildHandlers {\n\tonStdout(data: Buffer): void;\n\tonStderr(data: Buffer): void;\n\tonError(error: Error): void;\n\tonClose(code: number | null): void;\n}\n\nconst liveCoordinators = new Set<PersistentProcessCoordinator>();\nlet exitHookInstalled = false;\n\nfunction installExitHook(): void {\n\tif (exitHookInstalled) return;\n\texitHookInstalled = true;\n\tprocess.on(\"exit\", () => {\n\t\tfor (const coordinator of liveCoordinators) coordinator.killForProcessExit();\n\t});\n}\n\nexport class PersistentProcessCoordinator {\n\tprivate currentChild: ChildProcess | null = null;\n\tprivate queue: Promise<void> = Promise.resolve();\n\tprivate disposed = false;\n\n\tconstructor() {\n\t\tinstallExitHook();\n\t\tliveCoordinators.add(this);\n\t}\n\n\tget child(): ChildProcess | null {\n\t\treturn this.currentChild;\n\t}\n\n\trunSerialized<T>(task: () => Promise<T>): Promise<T> {\n\t\tconst run = this.queue.then(task);\n\t\tthis.queue = run.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn run;\n\t}\n\n\tattach(child: ChildProcess, handlers: PersistentChildHandlers): void {\n\t\tif (this.disposed) {\n\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\tthrow new Error(\"Persistent process coordinator is disposed\");\n\t\t}\n\t\tif (this.currentChild) throw new Error(\"Persistent process coordinator already owns a child\");\n\t\tthis.currentChild = child;\n\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\n\t\tchild.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\tif (this.currentChild === child) handlers.onStdout(data);\n\t\t});\n\t\tchild.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tif (this.currentChild === child) handlers.onStderr(data);\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tif (!this.clear(child)) return;\n\t\t\thandlers.onError(error instanceof Error ? error : new Error(String(error)));\n\t\t});\n\t\tlet exitCode: number | null = null;\n\t\tlet fallbackTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t\tchild.on(\"exit\", (code) => {\n\t\t\tif (this.currentChild !== child) return;\n\t\t\texitCode = code;\n\t\t\tfallbackTimer = setTimeout(() => {\n\t\t\t\tif (!this.clear(child)) return;\n\t\t\t\thandlers.onClose(exitCode);\n\t\t\t}, 2_000);\n\t\t\tif (typeof fallbackTimer === \"object\" && fallbackTimer && \"unref\" in fallbackTimer) {\n\t\t\t\tfallbackTimer.unref();\n\t\t\t}\n\t\t});\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (fallbackTimer) clearTimeout(fallbackTimer);\n\t\t\tif (!this.clear(child)) return;\n\t\t\thandlers.onClose(code ?? exitCode);\n\t\t});\n\t}\n\n\tkill(): void {\n\t\tconst child = this.currentChild;\n\t\tif (!child) return;\n\t\tthis.clear(child);\n\t\tif (child.pid) killProcessTree(child.pid);\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tliveCoordinators.delete(this);\n\t\tthis.kill();\n\t}\n\n\t/** Synchronous best-effort tree kill for the process exit hook. */\n\tkillForProcessExit(): void {\n\t\tthis.kill();\n\t}\n\n\t/** Idle coordinators must not keep one-shot Node modes alive; active commands must. */\n\tsetLoopRef(active: boolean): void {\n\t\tconst child = this.currentChild;\n\t\tif (!child) return;\n\t\tsetChildProcessLoopRef(child, active);\n\t}\n\n\tprivate clear(child: ChildProcess): boolean {\n\t\tif (this.currentChild !== child) return false;\n\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\tthis.currentChild = null;\n\t\treturn true;\n\t}\n}\n"]}
1
+ {"version":3,"file":"persistent-process-coordinator.js","sourceRoot":"","sources":["../../../src/core/tools/persistent-process-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared lifecycle owner for one serialized, persistent child process.\n *\n * Wire protocols, state projection, and respawn policy stay with their concrete shell adapters.\n * This boundary owns only the invariant OS mechanics: one active task, one current child, every\n * outstanding child terminal, stale event rejection, exit/close arbitration, whole-tree kill,\n * loop references, and parent exit.\n */\n\nimport type { ChildProcess } from \"node:child_process\";\nimport { setChildProcessLoopRef } from \"../../utils/child-process-ref.ts\";\nimport { killProcessTree, trackDetachedChildPid, untrackDetachedChildPid } from \"../../utils/shell.ts\";\n\nexport interface PersistentChildHandlers {\n\tonStdout(data: Buffer): void;\n\tonStderr(data: Buffer): void;\n\tonError(error: Error): void;\n\tonClose(code: number | null): void;\n}\n\nconst liveCoordinators = new Set<PersistentProcessCoordinator>();\nlet exitHookInstalled = false;\n\nfunction installExitHook(): void {\n\tif (exitHookInstalled) return;\n\texitHookInstalled = true;\n\tprocess.on(\"exit\", () => {\n\t\tfor (const coordinator of liveCoordinators) coordinator.killForProcessExit();\n\t});\n}\n\nexport class PersistentProcessCoordinator {\n\tprivate currentChild: ChildProcess | null = null;\n\tprivate readonly pendingChildTerminals = new Set<Promise<void>>();\n\tprivate queue: Promise<void> = Promise.resolve();\n\tprivate disposed = false;\n\n\tconstructor() {\n\t\tinstallExitHook();\n\t\tliveCoordinators.add(this);\n\t}\n\n\tget child(): ChildProcess | null {\n\t\treturn this.currentChild;\n\t}\n\n\tget terminalPromise(): Promise<void> {\n\t\treturn this.waitForTerminalRelease();\n\t}\n\n\trunSerialized<T>(task: () => Promise<T>): Promise<T> {\n\t\tconst run = this.queue.then(task);\n\t\tthis.queue = run.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn run;\n\t}\n\n\tattach(child: ChildProcess, handlers: PersistentChildHandlers): void {\n\t\tif (this.disposed) {\n\t\t\tthis.trackTerminal(child);\n\t\t\tthis.terminateChild(child);\n\t\t\tthrow new Error(\"Persistent process coordinator is disposed\");\n\t\t}\n\t\tif (this.currentChild) throw new Error(\"Persistent process coordinator already owns a child\");\n\t\tthis.currentChild = child;\n\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\tthis.trackTerminal(child);\n\n\t\tchild.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\tif (this.currentChild === child) handlers.onStdout(data);\n\t\t});\n\t\tchild.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tif (this.currentChild === child) handlers.onStderr(data);\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tif (!this.clear(child)) return;\n\t\t\thandlers.onError(error instanceof Error ? error : new Error(String(error)));\n\t\t});\n\t\tlet exitCode: number | null = null;\n\t\tlet fallbackTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t\tchild.on(\"exit\", (code) => {\n\t\t\tif (this.currentChild !== child) return;\n\t\t\texitCode = code;\n\t\t\tfallbackTimer = setTimeout(() => {\n\t\t\t\tif (!this.clear(child)) return;\n\t\t\t\thandlers.onClose(exitCode);\n\t\t\t}, 2_000);\n\t\t\tif (typeof fallbackTimer === \"object\" && fallbackTimer && \"unref\" in fallbackTimer) {\n\t\t\t\tfallbackTimer.unref();\n\t\t\t}\n\t\t});\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (fallbackTimer) clearTimeout(fallbackTimer);\n\t\t\tif (!this.clear(child)) return;\n\t\t\thandlers.onClose(code ?? exitCode);\n\t\t});\n\t}\n\n\tkill(): void {\n\t\tconst child = this.currentChild;\n\t\tif (!child) return;\n\t\tthis.clear(child);\n\t\tthis.terminateChild(child);\n\t}\n\n\tprivate terminateChild(child: ChildProcess): void {\n\t\t// Bun's Windows child-process bridge can stop publishing terminal events after every\n\t\t// handle has been unref'd. Re-arm the already-installed close observer before killing so\n\t\t// strict teardown can distinguish physical handle release from process exit.\n\t\tsetChildProcessLoopRef(child, true);\n\t\tif (child.pid) killProcessTree(child.pid);\n\t\ttry {\n\t\t\tchild.kill();\n\t\t} catch {\n\t\t\t// Process already dead\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tliveCoordinators.delete(this);\n\t\tthis.kill();\n\t}\n\n\t/** Synchronous best-effort tree kill for the process exit hook. */\n\tkillForProcessExit(): void {\n\t\tthis.kill();\n\t}\n\n\t/** Idle coordinators must not keep one-shot Node modes alive; active commands must. */\n\tsetLoopRef(active: boolean): void {\n\t\tconst child = this.currentChild;\n\t\tif (!child) return;\n\t\tsetChildProcessLoopRef(child, active);\n\t}\n\n\tprivate async waitForTerminalRelease(): Promise<void> {\n\t\tawait this.queue;\n\t\twhile (this.pendingChildTerminals.size > 0) {\n\t\t\tawait Promise.all(Array.from(this.pendingChildTerminals));\n\t\t}\n\t}\n\n\tprivate trackTerminal(child: ChildProcess): void {\n\t\tlet settled = false;\n\t\tlet resolveTerminal: () => void;\n\t\tconst terminalPromise = new Promise<void>((resolve) => {\n\t\t\tresolveTerminal = resolve;\n\t\t});\n\t\tconst settleTerminal = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tchild.off(\"error\", handleTerminalError);\n\t\t\tchild.off(\"close\", settleTerminal);\n\t\t\tthis.pendingChildTerminals.delete(terminalPromise);\n\t\t\tresolveTerminal();\n\t\t};\n\t\tconst handleTerminalError = () => {\n\t\t\tif (!child.pid) settleTerminal();\n\t\t};\n\n\t\tthis.pendingChildTerminals.add(terminalPromise);\n\t\tchild.on(\"error\", handleTerminalError);\n\t\tchild.once(\"close\", settleTerminal);\n\t}\n\n\tprivate clear(child: ChildProcess): boolean {\n\t\tif (this.currentChild !== child) return false;\n\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\tthis.currentChild = null;\n\t\treturn true;\n\t}\n}\n"]}
@@ -1,4 +1,11 @@
1
1
  /** One production lifecycle boundary for every process/state resource owned by a shell session. */
2
- /** Idempotently dispose both execution tiers and their shared authoritative Windows state. */
2
+ export declare const SHELL_EXECUTION_DISPOSAL_WATCHDOG_MS = 5000;
3
+ /** Synchronously dispose both execution tiers and their shared authoritative Windows state. */
3
4
  export declare function disposeShellExecutionSession(sessionKey: string): void;
5
+ /**
6
+ * Awaitable disposal that guarantees child processes in both execution tiers have reached
7
+ * physical terminal close before resolving. Concurrent callers for the same sessionKey share
8
+ * the identical pending wait promise.
9
+ */
10
+ export declare function disposeShellExecutionSessionAndWait(sessionKey: string, timeoutMs?: number): Promise<void>;
4
11
  //# sourceMappingURL=shell-execution-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell-execution-session.d.ts","sourceRoot":"","sources":["../../../src/core/tools/shell-execution-session.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAMnG,8FAA8F;AAC9F,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAIrE"}
1
+ {"version":3,"file":"shell-execution-session.d.ts","sourceRoot":"","sources":["../../../src/core/tools/shell-execution-session.ts"],"names":[],"mappings":"AAAA,mGAAmG;AASnG,eAAO,MAAM,oCAAoC,OAAQ,CAAC;AAE1D,+FAA+F;AAC/F,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAerE;AAUD;;;;GAIG;AACH,wBAAgB,mCAAmC,CAClD,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,MAA6C,GACtD,OAAO,CAAC,IAAI,CAAC,CA8Bf"}
@@ -2,10 +2,61 @@
2
2
  import { disposePersistentShellSession } from "./shell-session.js";
3
3
  import { disposeWindowsShellEngineSession } from "./windows-shell-engine.js";
4
4
  import { disposeWindowsShellState } from "./windows-shell-state.js";
5
- /** Idempotently dispose both execution tiers and their shared authoritative Windows state. */
5
+ const inFlightTerminalPromises = new Map();
6
+ const inFlightDisposalWaits = new Map();
7
+ export const SHELL_EXECUTION_DISPOSAL_WATCHDOG_MS = 5_000;
8
+ /** Synchronously dispose both execution tiers and their shared authoritative Windows state. */
6
9
  export function disposeShellExecutionSession(sessionKey) {
7
- disposeWindowsShellEngineSession(sessionKey);
8
- disposePersistentShellSession(sessionKey);
9
10
  disposeWindowsShellState(sessionKey);
11
+ const p1 = disposeWindowsShellEngineSession(sessionKey);
12
+ const p2 = disposePersistentShellSession(sessionKey);
13
+ const previous = inFlightTerminalPromises.get(sessionKey);
14
+ const combined = Promise.all([previous ?? Promise.resolve(), p1, p2])
15
+ .then(() => undefined)
16
+ .finally(() => {
17
+ if (inFlightTerminalPromises.get(sessionKey) === combined) {
18
+ inFlightTerminalPromises.delete(sessionKey);
19
+ }
20
+ });
21
+ inFlightTerminalPromises.set(sessionKey, combined);
22
+ }
23
+ async function drainTerminalPromises(sessionKey) {
24
+ while (true) {
25
+ const terminalPromise = inFlightTerminalPromises.get(sessionKey);
26
+ if (!terminalPromise)
27
+ return;
28
+ await terminalPromise;
29
+ }
30
+ }
31
+ /**
32
+ * Awaitable disposal that guarantees child processes in both execution tiers have reached
33
+ * physical terminal close before resolving. Concurrent callers for the same sessionKey share
34
+ * the identical pending wait promise.
35
+ */
36
+ export function disposeShellExecutionSessionAndWait(sessionKey, timeoutMs = SHELL_EXECUTION_DISPOSAL_WATCHDOG_MS) {
37
+ disposeShellExecutionSession(sessionKey);
38
+ const existing = inFlightDisposalWaits.get(sessionKey);
39
+ if (existing)
40
+ return existing;
41
+ let watchdogTimer;
42
+ const watchdogPromise = new Promise((_, reject) => {
43
+ watchdogTimer = setTimeout(() => {
44
+ reject(new Error(`Shell execution session terminal release timed out after ${timeoutMs}ms before child close event`));
45
+ }, timeoutMs);
46
+ if (typeof watchdogTimer === "object" && watchdogTimer && "unref" in watchdogTimer) {
47
+ watchdogTimer.unref();
48
+ }
49
+ });
50
+ const waitPromise = Promise.race([drainTerminalPromises(sessionKey), watchdogPromise])
51
+ .then(() => undefined)
52
+ .finally(() => {
53
+ if (watchdogTimer)
54
+ clearTimeout(watchdogTimer);
55
+ if (inFlightDisposalWaits.get(sessionKey) === waitPromise) {
56
+ inFlightDisposalWaits.delete(sessionKey);
57
+ }
58
+ });
59
+ inFlightDisposalWaits.set(sessionKey, waitPromise);
60
+ return waitPromise;
10
61
  }
11
62
  //# sourceMappingURL=shell-execution-session.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell-execution-session.js","sourceRoot":"","sources":["../../../src/core/tools/shell-execution-session.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,8FAA8F;AAC9F,MAAM,UAAU,4BAA4B,CAAC,UAAkB;IAC9D,gCAAgC,CAAC,UAAU,CAAC,CAAC;IAC7C,6BAA6B,CAAC,UAAU,CAAC,CAAC;IAC1C,wBAAwB,CAAC,UAAU,CAAC,CAAC;AACtC,CAAC","sourcesContent":["/** One production lifecycle boundary for every process/state resource owned by a shell session. */\n\nimport { disposePersistentShellSession } from \"./shell-session.ts\";\nimport { disposeWindowsShellEngineSession } from \"./windows-shell-engine.ts\";\nimport { disposeWindowsShellState } from \"./windows-shell-state.ts\";\n\n/** Idempotently dispose both execution tiers and their shared authoritative Windows state. */\nexport function disposeShellExecutionSession(sessionKey: string): void {\n\tdisposeWindowsShellEngineSession(sessionKey);\n\tdisposePersistentShellSession(sessionKey);\n\tdisposeWindowsShellState(sessionKey);\n}\n"]}
1
+ {"version":3,"file":"shell-execution-session.js","sourceRoot":"","sources":["../../../src/core/tools/shell-execution-session.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAyB,CAAC;AAClE,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAAyB,CAAC;AAE/D,MAAM,CAAC,MAAM,oCAAoC,GAAG,KAAK,CAAC;AAE1D,+FAA+F;AAC/F,MAAM,UAAU,4BAA4B,CAAC,UAAkB;IAC9D,wBAAwB,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,EAAE,GAAG,gCAAgC,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,EAAE,GAAG,6BAA6B,CAAC,UAAU,CAAC,CAAC;IAErD,MAAM,QAAQ,GAAG,wBAAwB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;SACnE,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;SACrB,OAAO,CAAC,GAAG,EAAE;QACb,IAAI,wBAAwB,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC3D,wBAAwB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC,CAAC,CAAC;IAEJ,wBAAwB,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACpD,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,UAAkB;IACtD,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,eAAe,GAAG,wBAAwB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACjE,IAAI,CAAC,eAAe;YAAE,OAAO;QAC7B,MAAM,eAAe,CAAC;IACvB,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mCAAmC,CAClD,UAAkB,EAClB,SAAS,GAAW,oCAAoC;IAExD,4BAA4B,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,IAAI,aAAwD,CAAC;IAC7D,MAAM,eAAe,GAAG,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QACvD,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,MAAM,CACL,IAAI,KAAK,CACR,4DAA4D,SAAS,6BAA6B,CAClG,CACD,CAAC;QACH,CAAC,EAAE,SAAS,CAAC,CAAC;QACd,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,IAAI,OAAO,IAAI,aAAa,EAAE,CAAC;YACpF,aAAa,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,qBAAqB,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC,CAAC;SACpF,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;SACrB,OAAO,CAAC,GAAG,EAAE;QACb,IAAI,aAAa;YAAE,YAAY,CAAC,aAAa,CAAC,CAAC;QAC/C,IAAI,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,WAAW,EAAE,CAAC;YAC3D,qBAAqB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC,CAAC,CAAC;IAEJ,qBAAqB,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACnD,OAAO,WAAW,CAAC;AACpB,CAAC","sourcesContent":["/** One production lifecycle boundary for every process/state resource owned by a shell session. */\n\nimport { disposePersistentShellSession } from \"./shell-session.ts\";\nimport { disposeWindowsShellEngineSession } from \"./windows-shell-engine.ts\";\nimport { disposeWindowsShellState } from \"./windows-shell-state.ts\";\n\nconst inFlightTerminalPromises = new Map<string, Promise<void>>();\nconst inFlightDisposalWaits = new Map<string, Promise<void>>();\n\nexport const SHELL_EXECUTION_DISPOSAL_WATCHDOG_MS = 5_000;\n\n/** Synchronously dispose both execution tiers and their shared authoritative Windows state. */\nexport function disposeShellExecutionSession(sessionKey: string): void {\n\tdisposeWindowsShellState(sessionKey);\n\tconst p1 = disposeWindowsShellEngineSession(sessionKey);\n\tconst p2 = disposePersistentShellSession(sessionKey);\n\n\tconst previous = inFlightTerminalPromises.get(sessionKey);\n\tconst combined = Promise.all([previous ?? Promise.resolve(), p1, p2])\n\t\t.then(() => undefined)\n\t\t.finally(() => {\n\t\t\tif (inFlightTerminalPromises.get(sessionKey) === combined) {\n\t\t\t\tinFlightTerminalPromises.delete(sessionKey);\n\t\t\t}\n\t\t});\n\n\tinFlightTerminalPromises.set(sessionKey, combined);\n}\n\nasync function drainTerminalPromises(sessionKey: string): Promise<void> {\n\twhile (true) {\n\t\tconst terminalPromise = inFlightTerminalPromises.get(sessionKey);\n\t\tif (!terminalPromise) return;\n\t\tawait terminalPromise;\n\t}\n}\n\n/**\n * Awaitable disposal that guarantees child processes in both execution tiers have reached\n * physical terminal close before resolving. Concurrent callers for the same sessionKey share\n * the identical pending wait promise.\n */\nexport function disposeShellExecutionSessionAndWait(\n\tsessionKey: string,\n\ttimeoutMs: number = SHELL_EXECUTION_DISPOSAL_WATCHDOG_MS,\n): Promise<void> {\n\tdisposeShellExecutionSession(sessionKey);\n\tconst existing = inFlightDisposalWaits.get(sessionKey);\n\tif (existing) return existing;\n\n\tlet watchdogTimer: ReturnType<typeof setTimeout> | undefined;\n\tconst watchdogPromise = new Promise<void>((_, reject) => {\n\t\twatchdogTimer = setTimeout(() => {\n\t\t\treject(\n\t\t\t\tnew Error(\n\t\t\t\t\t`Shell execution session terminal release timed out after ${timeoutMs}ms before child close event`,\n\t\t\t\t),\n\t\t\t);\n\t\t}, timeoutMs);\n\t\tif (typeof watchdogTimer === \"object\" && watchdogTimer && \"unref\" in watchdogTimer) {\n\t\t\twatchdogTimer.unref();\n\t\t}\n\t});\n\n\tconst waitPromise = Promise.race([drainTerminalPromises(sessionKey), watchdogPromise])\n\t\t.then(() => undefined)\n\t\t.finally(() => {\n\t\t\tif (watchdogTimer) clearTimeout(watchdogTimer);\n\t\t\tif (inFlightDisposalWaits.get(sessionKey) === waitPromise) {\n\t\t\t\tinFlightDisposalWaits.delete(sessionKey);\n\t\t\t}\n\t\t});\n\n\tinFlightDisposalWaits.set(sessionKey, waitPromise);\n\treturn waitPromise;\n}\n"]}
@@ -7,7 +7,8 @@
7
7
  * commands; each key gets an isolated session so concurrently running agents never share state.
8
8
  *
9
9
  * Protocol: commands stream to the session over stdin and are terminated by a per-command
10
- * sentinel carrying a random nonce and the exit code. Bash wraps commands in an eval of a quoted
10
+ * sentinel carrying a random nonce, the exit code, and (bash) the shell's `$PWD` so failures can
11
+ * report where the command actually ran. Bash wraps commands in an eval of a quoted
11
12
  * heredoc (arbitrary content stays data; syntax errors stay contained in eval); PowerShell runs a
12
13
  * ReadLine loop decoding base64 lines (no external binaries involved). On bash, command stderr is
13
14
  * merged into stdout at the shell so sentinel ordering is guaranteed on one pipe.
@@ -76,9 +77,11 @@ export declare class PersistentShellSession {
76
77
  /** Serialized: one command at a time per session, later calls queue behind earlier ones. */
77
78
  exec(command: string, cwd: string, options: ShellSessionExecOptions): Promise<{
78
79
  exitCode: number | null;
80
+ cwd?: string;
79
81
  }>;
80
82
  /** Start and validate the long-lived shell before the first user command. Idempotent per session. */
81
83
  prewarm(cwd: string, env?: NodeJS.ProcessEnv): Promise<void>;
84
+ get terminalPromise(): Promise<void>;
82
85
  dispose(): void;
83
86
  private execNow;
84
87
  private spawnChild;
@@ -91,5 +94,5 @@ export declare class PersistentShellSession {
91
94
  /** Get or lazily create the persistent session for a key. A kind change replaces the session. */
92
95
  export declare function acquirePersistentShellSession(key: string, kind: PlatformShellToolName): PersistentShellSession;
93
96
  /** Kill and forget a session (agent teardown). Safe to call for keys that never spawned. */
94
- export declare function disposePersistentShellSession(key: string): void;
97
+ export declare function disposePersistentShellSession(key: string): Promise<void>;
95
98
  //# sourceMappingURL=shell-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell-session.d.ts","sourceRoot":"","sources":["../../../src/core/tools/shell-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAKH,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAS,MAAM,eAAe,CAAC;AAS5E,OAAO,EAIN,KAAK,qBAAqB,EAE1B,KAAK,WAAW,EAChB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EACN,+BAA+B,EAC/B,sCAAsC,GACtC,MAAM,4CAA4C,CAAC;AASpD,MAAM,WAAW,uBAAuB;IACvC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,kGAAkG;IAClG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC7C,2BAA2B,CAAC,EAAE,MAAM,WAAW,EAAE,CAAC;IAClD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,YAAY,KAAK,YAAY,CAAC;IACjF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAUD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAUzF;AAED,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK/F;AA8CD,qBAAa,sBAAsB;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAsB;IAClE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA2E;IACxG,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,aAAa,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,GAAE,6BAAkC,EAMhG;IAED,IAAI,WAAW,IAAI,qBAAqB,CAEvC;IAED,4FAA4F;IAC5F,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAEzG;IAED,qGAAqG;IACrG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B1E;IAED,OAAO,IAAI,IAAI,CAOd;YAEa,OAAO;YAwLP,UAAU;YAqBV,oBAAoB;IA2BlC,OAAO,CAAC,wBAAwB;IA2FhC,OAAO,CAAC,gBAAgB;IAexB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,SAAS;CAIjB;AAID,iGAAiG;AACjG,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,sBAAsB,CAO9G;AAED,4FAA4F;AAC5F,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK/D"}
1
+ {"version":3,"file":"shell-session.d.ts","sourceRoot":"","sources":["../../../src/core/tools/shell-session.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,8 @@
7
7
  * commands; each key gets an isolated session so concurrently running agents never share state.
8
8
  *
9
9
  * Protocol: commands stream to the session over stdin and are terminated by a per-command
10
- * sentinel carrying a random nonce and the exit code. Bash wraps commands in an eval of a quoted
10
+ * sentinel carrying a random nonce, the exit code, and (bash) the shell's `$PWD` so failures can
11
+ * report where the command actually ran. Bash wraps commands in an eval of a quoted
11
12
  * heredoc (arbitrary content stays data; syntax errors stay contained in eval); PowerShell runs a
12
13
  * ReadLine loop decoding base64 lines (no external binaries involved). On bash, command stderr is
13
14
  * merged into stdout at the shell so sentinel ordering is guaranteed on one pipe.
@@ -43,8 +44,7 @@ import { claimCliPowerShellWarmStart } from "./early-powershell-session.js";
43
44
  import { PersistentProcessCoordinator } from "./persistent-process-coordinator.js";
44
45
  export { POWERSHELL_SESSION_READY_MARKER, POWERSHELL_SESSION_STDERR_READY_MARKER, } from "../../utils/powershell-session-protocol.js";
45
46
  const SENTINEL_BYTE = 0x1e;
46
- /** Longest possible sentinel: "\n" + 0x1e + 16-hex nonce + ":" + exit code digits + 0x1e. */
47
- const SENTINEL_HOLDBACK_BYTES = 64;
47
+ const BASH_SENTINEL_PAYLOAD_PREFIX = Buffer.from("v1:", "latin1");
48
48
  const MAX_STARTUP_DIAGNOSTIC_BYTES = 16 * 1024;
49
49
  const POWERSHELL_SESSION_READY_BYTES = Buffer.from(POWERSHELL_SESSION_READY_MARKER, "latin1");
50
50
  const POWERSHELL_SESSION_STDERR_READY_BYTES = Buffer.from(POWERSHELL_SESSION_STDERR_READY_MARKER, "latin1");
@@ -67,10 +67,70 @@ export function buildBashWire(command, nonce, cdTo) {
67
67
  body,
68
68
  `PI_EOF_${nonce}`,
69
69
  `)"; } < /dev/null 2>&1`,
70
- `printf '\\n\\036%s:%s\\036' '${nonce}' "$?"`,
70
+ // Version and byte length make the frame unambiguous even when a legal POSIX path contains
71
+ // the record-separator byte used to terminate the sentinel. The subshell keeps its status
72
+ // scratch variable and LC_ALL change out of the persistent caller session.
73
+ `(__pi_status=$?; LC_ALL=C; printf '\\n\\036%s:v1:%s:%s:%s\\036' '${nonce}' "$__pi_status" "\${#PWD}" "$PWD")`,
71
74
  "",
72
75
  ].join("\n");
73
76
  }
77
+ /** Parse the versioned bash frame plus the legacy exit-only/exit+cwd PowerShell test frames. */
78
+ function parseShellSentinel(buffer, payloadStart) {
79
+ const available = buffer.length - payloadStart;
80
+ const prefixComparisonLength = Math.min(available, BASH_SENTINEL_PAYLOAD_PREFIX.length);
81
+ const couldBeVersioned = prefixComparisonLength > 0 &&
82
+ buffer.compare(BASH_SENTINEL_PAYLOAD_PREFIX, 0, prefixComparisonLength, payloadStart, payloadStart + prefixComparisonLength) === 0;
83
+ if (couldBeVersioned && available < BASH_SENTINEL_PAYLOAD_PREFIX.length)
84
+ return undefined;
85
+ if (available >= BASH_SENTINEL_PAYLOAD_PREFIX.length &&
86
+ buffer.compare(BASH_SENTINEL_PAYLOAD_PREFIX, 0, BASH_SENTINEL_PAYLOAD_PREFIX.length, payloadStart, payloadStart + BASH_SENTINEL_PAYLOAD_PREFIX.length) === 0) {
87
+ const exitStart = payloadStart + BASH_SENTINEL_PAYLOAD_PREFIX.length;
88
+ const exitEnd = buffer.indexOf(0x3a, exitStart);
89
+ if (exitEnd === -1)
90
+ return undefined;
91
+ const lengthEnd = buffer.indexOf(0x3a, exitEnd + 1);
92
+ if (lengthEnd === -1)
93
+ return undefined;
94
+ const exitCodeText = buffer.subarray(exitStart, exitEnd).toString("latin1");
95
+ const cwdLengthText = buffer.subarray(exitEnd + 1, lengthEnd).toString("latin1");
96
+ const cwdByteLength = /^\d+$/.test(cwdLengthText) ? Number.parseInt(cwdLengthText, 10) : Number.NaN;
97
+ if (Number.isSafeInteger(cwdByteLength)) {
98
+ const cwdStart = lengthEnd + 1;
99
+ const closeIndex = cwdStart + cwdByteLength;
100
+ if (buffer.length <= closeIndex)
101
+ return undefined;
102
+ if (buffer[closeIndex] === SENTINEL_BYTE) {
103
+ const parsedExitCode = Number.parseInt(exitCodeText, 10);
104
+ return {
105
+ closeIndex,
106
+ exitCode: Number.isNaN(parsedExitCode) ? null : parsedExitCode,
107
+ ...(cwdByteLength > 0 ? { cwd: buffer.subarray(cwdStart, closeIndex).toString("utf8") } : {}),
108
+ };
109
+ }
110
+ }
111
+ // A corrupt versioned payload must still settle like the legacy parser: retain the exit
112
+ // classification when possible and degrade only the cwd field.
113
+ const closeIndex = buffer.indexOf(SENTINEL_BYTE, lengthEnd + 1);
114
+ if (closeIndex === -1)
115
+ return undefined;
116
+ const parsedExitCode = Number.parseInt(exitCodeText, 10);
117
+ return { closeIndex, exitCode: Number.isNaN(parsedExitCode) ? null : parsedExitCode };
118
+ }
119
+ const closeIndex = buffer.indexOf(SENTINEL_BYTE, payloadStart);
120
+ if (closeIndex === -1)
121
+ return undefined;
122
+ const payload = buffer.subarray(payloadStart, closeIndex);
123
+ const separatorIndex = payload.indexOf(0x3a);
124
+ const exitCodeText = (separatorIndex === -1 ? payload : payload.subarray(0, separatorIndex)).toString("latin1");
125
+ const parsedExitCode = Number.parseInt(exitCodeText, 10);
126
+ return {
127
+ closeIndex,
128
+ exitCode: Number.isNaN(parsedExitCode) ? null : parsedExitCode,
129
+ ...(separatorIndex !== -1 && separatorIndex < payload.length - 1
130
+ ? { cwd: payload.subarray(separatorIndex + 1).toString("utf8") }
131
+ : {}),
132
+ };
133
+ }
74
134
  /** One protocol line: `<nonce> <base64(utf8 command)>`. Base64 keeps arbitrary multi-line commands line-safe. */
75
135
  export function buildPowerShellWire(command, nonce, cdTo) {
76
136
  const body = cdTo
@@ -155,6 +215,9 @@ export class PersistentShellSession {
155
215
  this.coordinator.setLoopRef(false);
156
216
  });
157
217
  }
218
+ get terminalPromise() {
219
+ return this.coordinator.terminalPromise;
220
+ }
158
221
  dispose() {
159
222
  if (this.disposed)
160
223
  return;
@@ -212,6 +275,7 @@ export class PersistentShellSession {
212
275
  let stdoutPending = Buffer.alloc(0);
213
276
  let stderrPending = Buffer.alloc(0);
214
277
  let commandExitCode;
278
+ let commandCwd;
215
279
  let stderrBarrierSeen = this.kind !== "powershell";
216
280
  let timeoutTimer;
217
281
  const silenceWatchdog = timeoutSeconds === undefined && silenceMs !== undefined && silenceMs > 0
@@ -255,7 +319,24 @@ export class PersistentShellSession {
255
319
  const exitCode = commandExitCode;
256
320
  if (exitCode === undefined || !stderrBarrierSeen)
257
321
  return;
258
- settle(() => resolve({ exitCode }));
322
+ settle(() => resolve({ exitCode, cwd: commandCwd }));
323
+ };
324
+ // Retain only a tail that could still be a sentinel in progress: a sentinel starts
325
+ // with "\n" + 0x1e + nonce, so anything whose suffix is inconsistent with that
326
+ // prefix streams through immediately. Once the random prefix matches, retain the
327
+ // complete length-framed cwd rather than imposing a false filesystem path bound.
328
+ const sentinelHoldback = () => {
329
+ for (let index = 0; index < stdoutPending.length; index++) {
330
+ if (stdoutPending[index] !== 0x0a)
331
+ continue;
332
+ if (index + 1 < stdoutPending.length && stdoutPending[index + 1] !== SENTINEL_BYTE)
333
+ continue;
334
+ const length = Math.min(stdoutPending.length - index, sentinelPrefix.length);
335
+ if (stdoutPending.compare(sentinelPrefix, 0, length, index, index + length) === 0) {
336
+ return stdoutPending.length - index;
337
+ }
338
+ }
339
+ return 0;
259
340
  };
260
341
  this.activeExec = {
261
342
  onStdout: (data) => {
@@ -267,22 +348,19 @@ export class PersistentShellSession {
267
348
  stdoutPending = stdoutPending.length === 0 ? data : Buffer.concat([stdoutPending, data]);
268
349
  const prefixIndex = stdoutPending.indexOf(sentinelPrefix);
269
350
  if (prefixIndex !== -1) {
270
- const closeIndex = stdoutPending.indexOf(SENTINEL_BYTE, prefixIndex + sentinelPrefix.length);
271
- if (closeIndex !== -1) {
272
- const codeText = stdoutPending
273
- .subarray(prefixIndex + sentinelPrefix.length, closeIndex)
274
- .toString("latin1");
351
+ const parsed = parseShellSentinel(stdoutPending, prefixIndex + sentinelPrefix.length);
352
+ if (parsed) {
275
353
  emitStdoutPending(prefixIndex);
276
- stdoutPending = stdoutPending.subarray(closeIndex - prefixIndex + 1);
354
+ stdoutPending = stdoutPending.subarray(parsed.closeIndex - prefixIndex + 1);
277
355
  emitStdoutPending(stdoutPending.length);
278
- const parsed = Number.parseInt(codeText, 10);
279
- commandExitCode = Number.isNaN(parsed) ? null : parsed;
356
+ commandExitCode = parsed.exitCode;
357
+ commandCwd = parsed.cwd;
280
358
  resolveWhenComplete();
281
359
  return;
282
360
  }
283
361
  }
284
- // Stream promptly but retain a tail large enough to hold any split sentinel.
285
- emitStdoutPending(stdoutPending.length - SENTINEL_HOLDBACK_BYTES);
362
+ // Stream promptly but retain any tail that could be a split sentinel.
363
+ emitStdoutPending(stdoutPending.length - sentinelHoldback());
286
364
  },
287
365
  onStderr: (data) => {
288
366
  silenceWatchdog?.touch();
@@ -505,12 +583,28 @@ export class PersistentShellSession {
505
583
  }
506
584
  }
507
585
  const shellSessions = new Map();
586
+ const retiredShellSessionTerminals = new Map();
587
+ function retainShellSessionTerminal(key, terminalPromise) {
588
+ const previous = retiredShellSessionTerminals.get(key);
589
+ const combined = Promise.all([previous ?? Promise.resolve(), terminalPromise])
590
+ .then(() => undefined)
591
+ .finally(() => {
592
+ if (retiredShellSessionTerminals.get(key) === combined) {
593
+ retiredShellSessionTerminals.delete(key);
594
+ }
595
+ });
596
+ retiredShellSessionTerminals.set(key, combined);
597
+ return combined;
598
+ }
508
599
  /** Get or lazily create the persistent session for a key. A kind change replaces the session. */
509
600
  export function acquirePersistentShellSession(key, kind) {
510
601
  const existing = shellSessions.get(key);
511
602
  if (existing && existing.sessionKind === kind)
512
603
  return existing;
513
- existing?.dispose();
604
+ if (existing) {
605
+ existing.dispose();
606
+ retainShellSessionTerminal(key, existing.terminalPromise);
607
+ }
514
608
  const session = new PersistentShellSession(key, kind);
515
609
  shellSessions.set(key, session);
516
610
  return session;
@@ -519,8 +613,9 @@ export function acquirePersistentShellSession(key, kind) {
519
613
  export function disposePersistentShellSession(key) {
520
614
  const session = shellSessions.get(key);
521
615
  if (!session)
522
- return;
616
+ return retiredShellSessionTerminals.get(key) ?? Promise.resolve();
523
617
  shellSessions.delete(key);
524
618
  session.dispose();
619
+ return retainShellSessionTerminal(key, session.terminalPromise);
525
620
  }
526
621
  //# sourceMappingURL=shell-session.js.map