@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.
- package/CHANGELOG.md +37 -0
- package/dist/bundled-resources/skills/n-plus-2-architecture/SKILL.md +13 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -20
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/envelope-enforcement.d.ts +3 -1
- package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -1
- package/dist/core/autonomy/envelope-enforcement.js +23 -12
- package/dist/core/autonomy/envelope-enforcement.js.map +1 -1
- package/dist/core/autonomy/gates.d.ts.map +1 -1
- package/dist/core/autonomy/gates.js +24 -26
- package/dist/core/autonomy/gates.js.map +1 -1
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +128 -3
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/session-shutdown.d.ts +9 -0
- package/dist/core/session-shutdown.d.ts.map +1 -0
- package/dist/core/session-shutdown.js +44 -0
- package/dist/core/session-shutdown.js.map +1 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skill-vault.d.ts +24 -16
- package/dist/core/skill-vault.d.ts.map +1 -1
- package/dist/core/skill-vault.js +159 -82
- package/dist/core/skill-vault.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +7 -18
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +3 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +15 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +86 -33
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-intent.d.ts +16 -0
- package/dist/core/tools/file-mutation-intent.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-intent.js +51 -2
- package/dist/core/tools/file-mutation-intent.js.map +1 -1
- package/dist/core/tools/persistent-process-coordinator.d.ts +8 -2
- package/dist/core/tools/persistent-process-coordinator.d.ts.map +1 -1
- package/dist/core/tools/persistent-process-coordinator.js +52 -4
- package/dist/core/tools/persistent-process-coordinator.js.map +1 -1
- package/dist/core/tools/shell-execution-session.d.ts +8 -1
- package/dist/core/tools/shell-execution-session.d.ts.map +1 -1
- package/dist/core/tools/shell-execution-session.js +54 -3
- package/dist/core/tools/shell-execution-session.js.map +1 -1
- package/dist/core/tools/shell-session.d.ts +5 -2
- package/dist/core/tools/shell-session.d.ts.map +1 -1
- package/dist/core/tools/shell-session.js +112 -17
- package/dist/core/tools/shell-session.js.map +1 -1
- package/dist/core/tools/skill.d.ts +2 -1
- package/dist/core/tools/skill.d.ts.map +1 -1
- package/dist/core/tools/skill.js +26 -12
- package/dist/core/tools/skill.js.map +1 -1
- package/dist/core/tools/windows-shell-engine.d.ts +1 -1
- package/dist/core/tools/windows-shell-engine.d.ts.map +1 -1
- package/dist/core/tools/windows-shell-engine.js +6 -2
- package/dist/core/tools/windows-shell-engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +2 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/utils/shell.d.ts +3 -5
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +4 -7
- package/dist/utils/shell.js.map +1 -1
- package/docs/settings.md +7 -7
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- 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
|
|
6
|
-
* event rejection, exit/close arbitration, whole-tree kill,
|
|
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
|
|
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
|
|
6
|
-
* event rejection, exit/close arbitration, whole-tree kill,
|
|
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
|
-
|
|
40
|
-
|
|
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":"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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 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
|
-
|
|
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;
|
|
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
|
-
|
|
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,
|
|
1
|
+
{"version":3,"file":"shell-execution-session.js","sourceRoot":"","sources":["../../../src/core/tools/shell-execution-session.ts"],"names":[],"mappings":"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","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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
271
|
-
if (
|
|
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
|
-
|
|
279
|
-
|
|
356
|
+
commandExitCode = parsed.exitCode;
|
|
357
|
+
commandCwd = parsed.cwd;
|
|
280
358
|
resolveWhenComplete();
|
|
281
359
|
return;
|
|
282
360
|
}
|
|
283
361
|
}
|
|
284
|
-
// Stream promptly but retain
|
|
285
|
-
emitStdoutPending(stdoutPending.length -
|
|
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
|
|
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
|