taskplane 0.20.5 → 0.20.6
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.
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork entry point for the engine child process.
|
|
3
|
+
*
|
|
4
|
+
* Node v25 blocks .ts files inside node_modules regardless of flags.
|
|
5
|
+
* This .mjs file loads cleanly (no TypeScript processing needed), then
|
|
6
|
+
* uses jiti to import engine-worker.ts — bypassing Node's restriction.
|
|
7
|
+
*
|
|
8
|
+
* jiti is the same TypeScript runtime loader that Pi uses to load
|
|
9
|
+
* extensions. It transforms .ts files itself, independent of Node's
|
|
10
|
+
* --experimental-strip-types support.
|
|
11
|
+
*/
|
|
12
|
+
import { createJiti } from "jiti";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const jiti = createJiti(import.meta.url);
|
|
18
|
+
await jiti.import(join(__dirname, "engine-worker.ts"));
|
|
@@ -5,8 +5,7 @@ import { execSync, execFileSync } from "child_process";
|
|
|
5
5
|
import { writeFileSync, unlinkSync, mkdirSync, existsSync, readdirSync } from "fs";
|
|
6
6
|
import { join, dirname } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
|
|
9
|
-
// Re-enable when engine-worker ships as pre-compiled .js bundle.
|
|
8
|
+
import { fork, type ChildProcess } from "child_process";
|
|
10
9
|
|
|
11
10
|
// Direct imports — avoid barrel (index.ts) to prevent loading the entire module graph.
|
|
12
11
|
// Each import targets the specific module where the symbol is defined.
|
|
@@ -898,7 +897,7 @@ function resolveEngineWorkerPath(): string {
|
|
|
898
897
|
} catch {
|
|
899
898
|
thisDir = __dirname;
|
|
900
899
|
}
|
|
901
|
-
return join(thisDir, "engine-worker.
|
|
900
|
+
return join(thisDir, "engine-worker-entry.mjs");
|
|
902
901
|
}
|
|
903
902
|
|
|
904
903
|
/**
|
|
@@ -936,14 +935,24 @@ export function startBatchInWorker(
|
|
|
936
935
|
updateWidget: () => void,
|
|
937
936
|
onMonitorUpdate?: (state: import("./types.ts").MonitorState) => void,
|
|
938
937
|
onTerminal?: () => void,
|
|
939
|
-
): null {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
938
|
+
): ChildProcess | null {
|
|
939
|
+
const workerPath = resolveEngineWorkerPath();
|
|
940
|
+
|
|
941
|
+
let child: ChildProcess;
|
|
942
|
+
try {
|
|
943
|
+
// Fork a child process to run the engine in a separate isolate.
|
|
944
|
+
// The entry point is a .mjs file that uses jiti to load .ts files,
|
|
945
|
+
// bypassing Node v25's restriction on .ts in node_modules.
|
|
946
|
+
child = fork(workerPath, [], {
|
|
947
|
+
env: { ...process.env, TASKPLANE_ENGINE_FORK: "1" },
|
|
948
|
+
serialization: "advanced",
|
|
949
|
+
});
|
|
950
|
+
} catch (spawnErr: unknown) {
|
|
951
|
+
const errMsg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
952
|
+
ctx.ui.notify(
|
|
953
|
+
`⚠️ Engine process spawn failed: ${errMsg}\n Falling back to main-thread execution.`,
|
|
954
|
+
"warning",
|
|
955
|
+
);
|
|
947
956
|
// Construct fallback engine function from workerData and run on main thread
|
|
948
957
|
const wsConfig = wkData.workspaceConfig
|
|
949
958
|
? deserializeWorkspaceConfig(wkData.workspaceConfig)
|
|
@@ -976,6 +985,91 @@ export function startBatchInWorker(
|
|
|
976
985
|
startBatchAsync(fallbackFn, batchState, ctx, updateWidget, onTerminal);
|
|
977
986
|
return null;
|
|
978
987
|
}
|
|
988
|
+
|
|
989
|
+
// Send workerData as first IPC message
|
|
990
|
+
child.send({ type: "init", data: wkData });
|
|
991
|
+
|
|
992
|
+
// Terminal settlement guard (R001 §3): ensures onTerminal fires at most once.
|
|
993
|
+
let settled = false;
|
|
994
|
+
const settle = () => {
|
|
995
|
+
if (settled) return;
|
|
996
|
+
settled = true;
|
|
997
|
+
onTerminal?.();
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
child.on("message", (msg: WorkerToMainMessage) => {
|
|
1001
|
+
switch (msg.type) {
|
|
1002
|
+
case "notify":
|
|
1003
|
+
ctx.ui.notify(msg.msg, msg.level);
|
|
1004
|
+
updateWidget();
|
|
1005
|
+
break;
|
|
1006
|
+
|
|
1007
|
+
case "monitor-update":
|
|
1008
|
+
onMonitorUpdate?.(msg.state);
|
|
1009
|
+
break;
|
|
1010
|
+
|
|
1011
|
+
case "engine-event":
|
|
1012
|
+
break;
|
|
1013
|
+
|
|
1014
|
+
case "state-sync":
|
|
1015
|
+
applySerializedState(batchState, msg.state);
|
|
1016
|
+
updateWidget();
|
|
1017
|
+
break;
|
|
1018
|
+
|
|
1019
|
+
case "complete":
|
|
1020
|
+
applySerializedState(batchState, msg.state);
|
|
1021
|
+
updateWidget();
|
|
1022
|
+
settle();
|
|
1023
|
+
break;
|
|
1024
|
+
|
|
1025
|
+
case "error":
|
|
1026
|
+
if (batchState.phase !== "completed" && batchState.phase !== "failed") {
|
|
1027
|
+
batchState.phase = "failed";
|
|
1028
|
+
batchState.endedAt = Date.now();
|
|
1029
|
+
batchState.errors.push(`Unhandled engine error: ${msg.message}`);
|
|
1030
|
+
}
|
|
1031
|
+
ctx.ui.notify(
|
|
1032
|
+
`❌ Engine crashed with unhandled error: ${msg.message}\n` +
|
|
1033
|
+
` Batch ${batchState.batchId} marked as failed.`,
|
|
1034
|
+
"error",
|
|
1035
|
+
);
|
|
1036
|
+
updateWidget();
|
|
1037
|
+
break;
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
child.on("error", (err: Error) => {
|
|
1042
|
+
if (batchState.phase !== "completed" && batchState.phase !== "failed") {
|
|
1043
|
+
batchState.phase = "failed";
|
|
1044
|
+
batchState.endedAt = Date.now();
|
|
1045
|
+
batchState.errors.push(`Engine process error: ${err.message}`);
|
|
1046
|
+
}
|
|
1047
|
+
ctx.ui.notify(
|
|
1048
|
+
`❌ Engine process error: ${err.message}\n` +
|
|
1049
|
+
` Batch ${batchState.batchId} marked as failed.`,
|
|
1050
|
+
"error",
|
|
1051
|
+
);
|
|
1052
|
+
updateWidget();
|
|
1053
|
+
settle();
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
child.on("exit", (code: number | null) => {
|
|
1057
|
+
if (code !== 0 && !settled) {
|
|
1058
|
+
if (batchState.phase !== "completed" && batchState.phase !== "failed") {
|
|
1059
|
+
batchState.phase = "failed";
|
|
1060
|
+
batchState.endedAt = Date.now();
|
|
1061
|
+
batchState.errors.push(`Engine process exited with code ${code}`);
|
|
1062
|
+
}
|
|
1063
|
+
ctx.ui.notify(
|
|
1064
|
+
`❌ Engine process exited unexpectedly (code ${code}).`,
|
|
1065
|
+
"error",
|
|
1066
|
+
);
|
|
1067
|
+
updateWidget();
|
|
1068
|
+
}
|
|
1069
|
+
settle();
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
return child;
|
|
979
1073
|
}
|
|
980
1074
|
|
|
981
1075
|
// ── TP-043 R002-2: Integration Executor Builder ─────────────────────
|
|
@@ -1292,9 +1386,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1292
1386
|
let orchWidgetCtx: ExtensionContext | undefined;
|
|
1293
1387
|
let latestMonitorState: MonitorState | null = null;
|
|
1294
1388
|
|
|
1295
|
-
// ── TP-071: Active engine
|
|
1296
|
-
//
|
|
1297
|
-
let activeWorker: null = null;
|
|
1389
|
+
// ── TP-071: Active engine child process ──────────────────────────
|
|
1390
|
+
// Tracked so pause/abort can send control messages to the engine.
|
|
1391
|
+
let activeWorker: ChildProcess | null = null;
|
|
1298
1392
|
|
|
1299
1393
|
// ── Supervisor State (TP-041) ────────────────────────────────────
|
|
1300
1394
|
let supervisorState = freshSupervisorState();
|
|
@@ -1911,8 +2005,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1911
2005
|
return ORCH_MESSAGES.pauseAlreadyPaused(orchBatchState.batchId);
|
|
1912
2006
|
}
|
|
1913
2007
|
orchBatchState.pauseSignal.paused = true;
|
|
1914
|
-
// TP-071: Forward pause to engine process (
|
|
1915
|
-
|
|
2008
|
+
// TP-071: Forward pause to engine process (its pauseSignal is separate)
|
|
2009
|
+
activeWorker?.send({ type: "pause" });
|
|
1916
2010
|
updateOrchWidget();
|
|
1917
2011
|
return ORCH_MESSAGES.pauseActivated(orchBatchState.batchId);
|
|
1918
2012
|
}
|
|
@@ -2102,10 +2196,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
2102
2196
|
orchBatchState.pauseSignal.paused = true;
|
|
2103
2197
|
messages.push(" ✓ Pause signal set on in-memory batch state");
|
|
2104
2198
|
}
|
|
2105
|
-
// TP-071: Forward pause to engine
|
|
2106
|
-
if (
|
|
2107
|
-
|
|
2108
|
-
|
|
2199
|
+
// TP-071: Forward pause to engine and kill on hard abort
|
|
2200
|
+
if (activeWorker) {
|
|
2201
|
+
activeWorker.send({ type: "pause" });
|
|
2202
|
+
if (hard) {
|
|
2203
|
+
activeWorker.kill();
|
|
2204
|
+
activeWorker = null;
|
|
2205
|
+
messages.push(" ✓ Engine process killed (hard abort)");
|
|
2206
|
+
} else {
|
|
2207
|
+
messages.push(" ✓ Pause signal forwarded to engine process");
|
|
2208
|
+
}
|
|
2109
2209
|
}
|
|
2110
2210
|
|
|
2111
2211
|
// Step 3: Check what we're aborting
|
|
@@ -3182,8 +3282,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
3182
3282
|
// Ensure supervisor lockfile/heartbeat are cleaned up on normal session exit.
|
|
3183
3283
|
// This avoids leaving a live-looking lock when the process exits cleanly.
|
|
3184
3284
|
pi.on("session_end", async () => {
|
|
3185
|
-
// TP-071: Kill engine process on session exit
|
|
3186
|
-
|
|
3285
|
+
// TP-071: Kill engine process on session exit
|
|
3286
|
+
if (activeWorker) {
|
|
3287
|
+
try {
|
|
3288
|
+
activeWorker.kill();
|
|
3289
|
+
activeWorker = null;
|
|
3290
|
+
} catch {
|
|
3291
|
+
// Best effort — process may already be dead
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3187
3294
|
try {
|
|
3188
3295
|
await deactivateSupervisor(pi, supervisorState);
|
|
3189
3296
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taskplane",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.6",
|
|
4
4
|
"description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@sinclair/typebox": "*"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"jiti": "^2.6.1",
|
|
46
47
|
"yaml": "^2.4.0"
|
|
47
48
|
},
|
|
48
49
|
"license": "MIT",
|