taskplane 0.19.0 → 0.20.0
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/bin/taskplane.mjs +1 -1
- package/dashboard/public/app.js +41 -0
- package/dashboard/public/index.html +5 -2
- package/dashboard/public/style.css +194 -58
- package/dashboard/public/taskplane-word-color.svg +22 -0
- package/dashboard/public/taskplane-word-white.svg +22 -0
- package/dashboard/server.cjs +65 -0
- package/extensions/task-runner.ts +90 -69
- package/extensions/taskplane/engine-worker.ts +289 -0
- package/extensions/taskplane/execution.ts +298 -25
- package/extensions/taskplane/extension.ts +330 -94
- package/extensions/taskplane/merge.ts +242 -19
- package/extensions/taskplane/supervisor-primer.md +1 -1
- package/extensions/taskplane/supervisor.ts +161 -42
- package/package.json +1 -1
- package/skills/create-taskplane-task/references/prompt-template.md +0 -3
- package/templates/agents/task-worker.md +16 -1
- package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -99
- package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -73
- package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -98
- package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -73
|
@@ -3,50 +3,34 @@ import { Type } from "@mariozechner/pi-ai";
|
|
|
3
3
|
|
|
4
4
|
import { execSync, execFileSync } from "child_process";
|
|
5
5
|
import { writeFileSync, unlinkSync, mkdirSync, existsSync, readdirSync } from "fs";
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
freshOrchBatchState,
|
|
29
|
-
getCurrentBranch,
|
|
30
|
-
hasConfigFiles,
|
|
31
|
-
resolveConfigRoot,
|
|
32
|
-
listOrchSessions,
|
|
33
|
-
listWorktrees,
|
|
34
|
-
loadBatchState,
|
|
35
|
-
loadOrchestratorConfig,
|
|
36
|
-
loadSupervisorConfig,
|
|
37
|
-
loadTaskRunnerConfig,
|
|
38
|
-
parseOrchSessionNames,
|
|
39
|
-
resolveOperatorId,
|
|
40
|
-
resolveWorktreeBasePath,
|
|
41
|
-
resumeOrchBatch,
|
|
42
|
-
runDiscovery,
|
|
43
|
-
runGit,
|
|
44
|
-
runPreflight,
|
|
45
|
-
} from "./index.ts";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { Worker } from "worker_threads";
|
|
9
|
+
|
|
10
|
+
// Direct imports — avoid barrel (index.ts) to prevent loading the entire module graph.
|
|
11
|
+
// Each import targets the specific module where the symbol is defined.
|
|
12
|
+
import { DEFAULT_ORCHESTRATOR_CONFIG, DEFAULT_TASK_RUNNER_CONFIG, FATAL_DISCOVERY_CODES, StateFileError, WorkspaceConfigError, freshOrchBatchState } from "./types.ts";
|
|
13
|
+
import type { AbortMode, ExecutionContext, MonitorState, OrchestratorConfig, PersistedBatchState, TaskRunnerConfig } from "./types.ts";
|
|
14
|
+
import { ORCH_MESSAGES, computeIntegrateCleanupResult } from "./messages.ts";
|
|
15
|
+
import type { IntegrateCleanupRepoFindings } from "./messages.ts";
|
|
16
|
+
import { computeWaveAssignments } from "./waves.ts";
|
|
17
|
+
import { createOrchWidget, formatDependencyGraph, formatWavePlan } from "./formatting.ts";
|
|
18
|
+
import { deleteBatchState, loadBatchState, detectOrphanSessions, parseOrchSessionNames } from "./persistence.ts";
|
|
19
|
+
import { deleteStaleBranches, listWorktrees, resolveWorktreeBasePath, formatPreflightResults, runPreflight } from "./worktree.ts";
|
|
20
|
+
import { executeLane } from "./execution.ts";
|
|
21
|
+
import { executeOrchBatch } from "./engine.ts";
|
|
22
|
+
import { formatDiscoveryResults, runDiscovery } from "./discovery.ts";
|
|
23
|
+
import { formatOrchSessions, listOrchSessions } from "./sessions.ts";
|
|
24
|
+
import { getCurrentBranch, runGit } from "./git.ts";
|
|
25
|
+
import { hasConfigFiles, resolveConfigRoot, loadOrchestratorConfig, loadSupervisorConfig, loadTaskRunnerConfig } from "./config.ts";
|
|
26
|
+
import { resolveOperatorId } from "./naming.ts";
|
|
27
|
+
import { resumeOrchBatch } from "./resume.ts";
|
|
46
28
|
import { buildExecutionContext } from "./workspace.ts";
|
|
47
29
|
import { openSettingsTui } from "./settings-tui.ts";
|
|
48
30
|
import { loadProjectConfig } from "./config-loader.ts";
|
|
49
31
|
import { runMigrations } from "./migrations.ts";
|
|
32
|
+
import { serializeWorkspaceConfig, applySerializedState, deserializeWorkspaceConfig } from "./engine-worker.ts";
|
|
33
|
+
import type { EngineWorkerData, WorkerToMainMessage } from "./engine-worker.ts";
|
|
50
34
|
import { cleanupPostIntegrate, formatPostIntegrateCleanup, sweepStaleArtifacts, formatPreflightSweep, rotateSupervisorLogs, formatLogRotation } from "./cleanup.ts";
|
|
51
35
|
import {
|
|
52
36
|
activateSupervisor,
|
|
@@ -64,15 +48,6 @@ import {
|
|
|
64
48
|
resolveModelFromString,
|
|
65
49
|
} from "./supervisor.ts";
|
|
66
50
|
import type { SupervisorConfig, SupervisorRoutingContext, IntegrationExecutor, CiDeps, SummaryDeps } from "./supervisor.ts";
|
|
67
|
-
import type {
|
|
68
|
-
AbortMode,
|
|
69
|
-
ExecutionContext,
|
|
70
|
-
IntegrateCleanupRepoFindings,
|
|
71
|
-
MonitorState,
|
|
72
|
-
OrchestratorConfig,
|
|
73
|
-
PersistedBatchState,
|
|
74
|
-
TaskRunnerConfig,
|
|
75
|
-
} from "./index.ts";
|
|
76
51
|
|
|
77
52
|
// ── Integrate Args Parsing ────────────────────────────────────────────
|
|
78
53
|
|
|
@@ -905,6 +880,196 @@ export function startBatchAsync(
|
|
|
905
880
|
}, 0);
|
|
906
881
|
}
|
|
907
882
|
|
|
883
|
+
// ── TP-071: Engine Worker Thread ─────────────────────────────────────
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Resolve the absolute path to engine-worker.ts for Worker thread spawning.
|
|
887
|
+
*
|
|
888
|
+
* Uses import.meta.url to locate the file relative to this extension module.
|
|
889
|
+
* Falls back to __dirname for environments where import.meta.url is unavailable.
|
|
890
|
+
*
|
|
891
|
+
* @since TP-071
|
|
892
|
+
*/
|
|
893
|
+
function resolveEngineWorkerPath(): string {
|
|
894
|
+
let thisDir: string;
|
|
895
|
+
try {
|
|
896
|
+
thisDir = dirname(fileURLToPath(import.meta.url));
|
|
897
|
+
} catch {
|
|
898
|
+
thisDir = __dirname;
|
|
899
|
+
}
|
|
900
|
+
return join(thisDir, "engine-worker.ts");
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Launch the engine batch in a worker thread.
|
|
905
|
+
*
|
|
906
|
+
* Replaces `startBatchAsync()` for the worker-thread execution model (TP-071).
|
|
907
|
+
* The engine runs in a separate V8 isolate, keeping the main thread free
|
|
908
|
+
* for TUI interaction and supervisor agent LLM calls.
|
|
909
|
+
*
|
|
910
|
+
* If the worker fails to spawn (e.g., TypeScript not supported in workers),
|
|
911
|
+
* falls back to main-thread execution via `startBatchAsync()`.
|
|
912
|
+
*
|
|
913
|
+
* Communication:
|
|
914
|
+
* - Worker → Main: postMessage for notify, monitor-update, engine-event, state-sync, complete
|
|
915
|
+
* - Main → Worker: postMessage for pause/unpause control
|
|
916
|
+
*
|
|
917
|
+
* Terminal idempotency: a `settled` flag ensures that only the first terminal
|
|
918
|
+
* path (complete message, error event, or non-zero exit) triggers `onTerminal`.
|
|
919
|
+
* This prevents duplicate summary/integration/supervisor flows (R001 §3).
|
|
920
|
+
*
|
|
921
|
+
* @param wkData Serialized engine configuration and parameters
|
|
922
|
+
* @param batchState Main thread's batch state (updated via state-sync messages)
|
|
923
|
+
* @param ctx Extension context for UI notifications
|
|
924
|
+
* @param updateWidget Widget refresh callback
|
|
925
|
+
* @param onMonitorUpdate Optional callback for dashboard monitor updates
|
|
926
|
+
* @param onTerminal Callback when engine reaches terminal state
|
|
927
|
+
* @returns The Worker instance (for pause/abort control), or null if fallback was used
|
|
928
|
+
*
|
|
929
|
+
* @since TP-071
|
|
930
|
+
*/
|
|
931
|
+
export function startBatchInWorker(
|
|
932
|
+
wkData: EngineWorkerData,
|
|
933
|
+
batchState: import("./types.ts").OrchBatchRuntimeState,
|
|
934
|
+
ctx: ExtensionContext,
|
|
935
|
+
updateWidget: () => void,
|
|
936
|
+
onMonitorUpdate?: (state: import("./types.ts").MonitorState) => void,
|
|
937
|
+
onTerminal?: () => void,
|
|
938
|
+
): Worker | null {
|
|
939
|
+
const workerPath = resolveEngineWorkerPath();
|
|
940
|
+
|
|
941
|
+
let worker: Worker;
|
|
942
|
+
try {
|
|
943
|
+
worker = new Worker(workerPath, { workerData: wkData });
|
|
944
|
+
} catch (spawnErr: unknown) {
|
|
945
|
+
const errMsg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
946
|
+
ctx.ui.notify(
|
|
947
|
+
`⚠️ Worker thread spawn failed: ${errMsg}\n Falling back to main-thread execution.`,
|
|
948
|
+
"warning",
|
|
949
|
+
);
|
|
950
|
+
// Construct fallback engine function from workerData and run on main thread
|
|
951
|
+
const wsConfig = wkData.workspaceConfig
|
|
952
|
+
? deserializeWorkspaceConfig(wkData.workspaceConfig)
|
|
953
|
+
: undefined;
|
|
954
|
+
const fallbackFn = wkData.mode === "resume"
|
|
955
|
+
? () => resumeOrchBatch(
|
|
956
|
+
wkData.orchConfig,
|
|
957
|
+
wkData.runnerConfig,
|
|
958
|
+
wkData.cwd,
|
|
959
|
+
batchState,
|
|
960
|
+
(msg: string, lvl: "info" | "warning" | "error") => { ctx.ui.notify(msg, lvl); updateWidget(); },
|
|
961
|
+
(monState: import("./types.ts").MonitorState) => { onMonitorUpdate?.(monState); },
|
|
962
|
+
wsConfig,
|
|
963
|
+
wkData.workspaceRoot,
|
|
964
|
+
wkData.agentRoot,
|
|
965
|
+
wkData.force ?? false,
|
|
966
|
+
)
|
|
967
|
+
: () => executeOrchBatch(
|
|
968
|
+
wkData.args ?? "",
|
|
969
|
+
wkData.orchConfig,
|
|
970
|
+
wkData.runnerConfig,
|
|
971
|
+
wkData.cwd,
|
|
972
|
+
batchState,
|
|
973
|
+
(msg: string, lvl: "info" | "warning" | "error") => { ctx.ui.notify(msg, lvl); updateWidget(); },
|
|
974
|
+
(monState: import("./types.ts").MonitorState) => { onMonitorUpdate?.(monState); },
|
|
975
|
+
wsConfig,
|
|
976
|
+
wkData.workspaceRoot,
|
|
977
|
+
wkData.agentRoot,
|
|
978
|
+
);
|
|
979
|
+
startBatchAsync(fallbackFn, batchState, ctx, updateWidget, onTerminal);
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Terminal settlement guard (R001 §3): ensures onTerminal fires at most once.
|
|
984
|
+
// The worker can emit error + complete messages, followed by exit event.
|
|
985
|
+
// Without this guard, summary/integration/supervisor flows would fire multiple times.
|
|
986
|
+
let settled = false;
|
|
987
|
+
const settle = () => {
|
|
988
|
+
if (settled) return;
|
|
989
|
+
settled = true;
|
|
990
|
+
onTerminal?.();
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
worker.on("message", (msg: WorkerToMainMessage) => {
|
|
994
|
+
switch (msg.type) {
|
|
995
|
+
case "notify":
|
|
996
|
+
ctx.ui.notify(msg.msg, msg.level);
|
|
997
|
+
updateWidget();
|
|
998
|
+
break;
|
|
999
|
+
|
|
1000
|
+
case "monitor-update":
|
|
1001
|
+
onMonitorUpdate?.(msg.state);
|
|
1002
|
+
break;
|
|
1003
|
+
|
|
1004
|
+
case "engine-event":
|
|
1005
|
+
// Engine events are already persisted to events.jsonl by the worker.
|
|
1006
|
+
// No additional handling needed on the main thread.
|
|
1007
|
+
break;
|
|
1008
|
+
|
|
1009
|
+
case "state-sync":
|
|
1010
|
+
applySerializedState(batchState, msg.state);
|
|
1011
|
+
updateWidget();
|
|
1012
|
+
break;
|
|
1013
|
+
|
|
1014
|
+
case "complete":
|
|
1015
|
+
applySerializedState(batchState, msg.state);
|
|
1016
|
+
updateWidget();
|
|
1017
|
+
settle();
|
|
1018
|
+
break;
|
|
1019
|
+
|
|
1020
|
+
case "error":
|
|
1021
|
+
if (batchState.phase !== "completed" && batchState.phase !== "failed") {
|
|
1022
|
+
batchState.phase = "failed";
|
|
1023
|
+
batchState.endedAt = Date.now();
|
|
1024
|
+
batchState.errors.push(`Unhandled engine error: ${msg.message}`);
|
|
1025
|
+
}
|
|
1026
|
+
ctx.ui.notify(
|
|
1027
|
+
`❌ Engine crashed with unhandled error: ${msg.message}\n` +
|
|
1028
|
+
` Batch ${batchState.batchId} marked as failed.`,
|
|
1029
|
+
"error",
|
|
1030
|
+
);
|
|
1031
|
+
updateWidget();
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
worker.on("error", (err: Error) => {
|
|
1037
|
+
// Worker threw an uncaught exception
|
|
1038
|
+
if (batchState.phase !== "completed" && batchState.phase !== "failed") {
|
|
1039
|
+
batchState.phase = "failed";
|
|
1040
|
+
batchState.endedAt = Date.now();
|
|
1041
|
+
batchState.errors.push(`Worker thread error: ${err.message}`);
|
|
1042
|
+
}
|
|
1043
|
+
ctx.ui.notify(
|
|
1044
|
+
`❌ Engine worker thread error: ${err.message}\n` +
|
|
1045
|
+
` Batch ${batchState.batchId} marked as failed.`,
|
|
1046
|
+
"error",
|
|
1047
|
+
);
|
|
1048
|
+
updateWidget();
|
|
1049
|
+
settle();
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
worker.on("exit", (code: number) => {
|
|
1053
|
+
if (code !== 0 && !settled) {
|
|
1054
|
+
// Non-zero exit that wasn't handled by 'error' or 'complete'
|
|
1055
|
+
if (batchState.phase !== "completed" && batchState.phase !== "failed") {
|
|
1056
|
+
batchState.phase = "failed";
|
|
1057
|
+
batchState.endedAt = Date.now();
|
|
1058
|
+
batchState.errors.push(`Worker thread exited with code ${code}`);
|
|
1059
|
+
}
|
|
1060
|
+
ctx.ui.notify(
|
|
1061
|
+
`❌ Engine worker thread exited unexpectedly (code ${code}).`,
|
|
1062
|
+
"error",
|
|
1063
|
+
);
|
|
1064
|
+
updateWidget();
|
|
1065
|
+
}
|
|
1066
|
+
// Always settle on exit — ensures cleanup runs even on clean exit
|
|
1067
|
+
settle();
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
return worker;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
908
1073
|
// ── TP-043 R002-2: Integration Executor Builder ─────────────────────
|
|
909
1074
|
|
|
910
1075
|
/**
|
|
@@ -1219,6 +1384,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1219
1384
|
let orchWidgetCtx: ExtensionContext | undefined;
|
|
1220
1385
|
let latestMonitorState: MonitorState | null = null;
|
|
1221
1386
|
|
|
1387
|
+
// ── TP-071: Active engine worker thread ──────────────────────────
|
|
1388
|
+
// Tracked so pause/abort can post control messages to the worker.
|
|
1389
|
+
let activeWorker: Worker | null = null;
|
|
1390
|
+
|
|
1222
1391
|
// ── Supervisor State (TP-041) ────────────────────────────────────
|
|
1223
1392
|
let supervisorState = freshSupervisorState();
|
|
1224
1393
|
let supervisorConfig: SupervisorConfig = { ...DEFAULT_SUPERVISOR_CONFIG };
|
|
@@ -1620,37 +1789,36 @@ export default function (pi: ExtensionAPI) {
|
|
|
1620
1789
|
orchBatchState.startedAt = Date.now();
|
|
1621
1790
|
updateOrchWidget();
|
|
1622
1791
|
|
|
1623
|
-
// Non-blocking engine launch
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1792
|
+
// Non-blocking engine launch in worker thread (TP-071)
|
|
1793
|
+
activeWorker = startBatchInWorker(
|
|
1794
|
+
{
|
|
1795
|
+
engineWorker: true,
|
|
1796
|
+
mode: "execute",
|
|
1797
|
+
args: trimmedTarget,
|
|
1627
1798
|
orchConfig,
|
|
1628
1799
|
runnerConfig,
|
|
1629
|
-
repoRoot,
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
const changed = !latestMonitorState ||
|
|
1637
|
-
latestMonitorState.totalDone !== monState.totalDone ||
|
|
1638
|
-
latestMonitorState.totalFailed !== monState.totalFailed ||
|
|
1639
|
-
latestMonitorState.lanes.some((l, i) =>
|
|
1640
|
-
l.currentTaskId !== monState.lanes[i]?.currentTaskId ||
|
|
1641
|
-
l.currentStep !== monState.lanes[i]?.currentStep ||
|
|
1642
|
-
l.completedChecks !== monState.lanes[i]?.completedChecks,
|
|
1643
|
-
);
|
|
1644
|
-
latestMonitorState = monState;
|
|
1645
|
-
if (changed) updateOrchWidget();
|
|
1646
|
-
},
|
|
1647
|
-
execCtx!.workspaceConfig,
|
|
1648
|
-
execCtx!.workspaceRoot,
|
|
1649
|
-
execCtx!.pointer?.agentRoot,
|
|
1650
|
-
),
|
|
1800
|
+
cwd: repoRoot,
|
|
1801
|
+
workspaceConfig: execCtx!.workspaceConfig
|
|
1802
|
+
? serializeWorkspaceConfig(execCtx!.workspaceConfig)
|
|
1803
|
+
: null,
|
|
1804
|
+
workspaceRoot: execCtx!.workspaceRoot,
|
|
1805
|
+
agentRoot: execCtx!.pointer?.agentRoot,
|
|
1806
|
+
},
|
|
1651
1807
|
orchBatchState,
|
|
1652
1808
|
ctx,
|
|
1653
1809
|
updateOrchWidget,
|
|
1810
|
+
(monState: MonitorState) => {
|
|
1811
|
+
const changed = !latestMonitorState ||
|
|
1812
|
+
latestMonitorState.totalDone !== monState.totalDone ||
|
|
1813
|
+
latestMonitorState.totalFailed !== monState.totalFailed ||
|
|
1814
|
+
latestMonitorState.lanes.some((l, i) =>
|
|
1815
|
+
l.currentTaskId !== monState.lanes[i]?.currentTaskId ||
|
|
1816
|
+
l.currentStep !== monState.lanes[i]?.currentStep ||
|
|
1817
|
+
l.completedChecks !== monState.lanes[i]?.completedChecks,
|
|
1818
|
+
);
|
|
1819
|
+
latestMonitorState = monState;
|
|
1820
|
+
if (changed) updateOrchWidget();
|
|
1821
|
+
},
|
|
1654
1822
|
() => {
|
|
1655
1823
|
const mode = orchConfig.orchestrator.integration;
|
|
1656
1824
|
const opId = resolveOperatorId(orchConfig);
|
|
@@ -1723,6 +1891,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
1723
1891
|
};
|
|
1724
1892
|
transitionToRoutingMode(pi, supervisorState, postBatchContext);
|
|
1725
1893
|
},
|
|
1894
|
+
// Fallback: run engine on main thread if worker spawn fails (R001 §1)
|
|
1895
|
+
() => executeOrchBatch(
|
|
1896
|
+
trimmedTarget,
|
|
1897
|
+
orchConfig,
|
|
1898
|
+
runnerConfig,
|
|
1899
|
+
repoRoot,
|
|
1900
|
+
orchBatchState,
|
|
1901
|
+
(message, level) => {
|
|
1902
|
+
ctx.ui.notify(message, level);
|
|
1903
|
+
updateOrchWidget();
|
|
1904
|
+
},
|
|
1905
|
+
(monState: MonitorState) => {
|
|
1906
|
+
const changed = !latestMonitorState ||
|
|
1907
|
+
latestMonitorState.totalDone !== monState.totalDone ||
|
|
1908
|
+
latestMonitorState.totalFailed !== monState.totalFailed ||
|
|
1909
|
+
latestMonitorState.lanes.some((l, i) =>
|
|
1910
|
+
l.currentTaskId !== monState.lanes[i]?.currentTaskId ||
|
|
1911
|
+
l.currentStep !== monState.lanes[i]?.currentStep ||
|
|
1912
|
+
l.completedChecks !== monState.lanes[i]?.completedChecks,
|
|
1913
|
+
);
|
|
1914
|
+
latestMonitorState = monState;
|
|
1915
|
+
if (changed) updateOrchWidget();
|
|
1916
|
+
},
|
|
1917
|
+
execCtx!.workspaceConfig,
|
|
1918
|
+
execCtx!.workspaceRoot,
|
|
1919
|
+
execCtx!.pointer?.agentRoot,
|
|
1920
|
+
),
|
|
1726
1921
|
);
|
|
1727
1922
|
|
|
1728
1923
|
// Activate supervisor agent
|
|
@@ -1808,13 +2003,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
1808
2003
|
return ORCH_MESSAGES.pauseAlreadyPaused(orchBatchState.batchId);
|
|
1809
2004
|
}
|
|
1810
2005
|
orchBatchState.pauseSignal.paused = true;
|
|
2006
|
+
// TP-071: Forward pause to worker thread (its pauseSignal is separate)
|
|
2007
|
+
activeWorker?.postMessage({ type: "pause" });
|
|
1811
2008
|
updateOrchWidget();
|
|
1812
2009
|
return ORCH_MESSAGES.pauseActivated(orchBatchState.batchId);
|
|
1813
2010
|
}
|
|
1814
2011
|
|
|
1815
2012
|
/**
|
|
1816
2013
|
* Core logic for orch-resume. Returns an immediate status message.
|
|
1817
|
-
* The actual batch resume runs asynchronously via
|
|
2014
|
+
* The actual batch resume runs asynchronously via startBatchInWorker (TP-071).
|
|
1818
2015
|
* Returns null if execCtx is missing (caller must handle).
|
|
1819
2016
|
*/
|
|
1820
2017
|
function doOrchResume(force: boolean, ctx: ExtensionContext): { message: string; error?: boolean } {
|
|
@@ -1841,29 +2038,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
1841
2038
|
orchBatchState.startedAt = Date.now();
|
|
1842
2039
|
updateOrchWidget();
|
|
1843
2040
|
|
|
1844
|
-
// Fire-and-forget resume via
|
|
1845
|
-
|
|
1846
|
-
|
|
2041
|
+
// Fire-and-forget resume via worker thread (TP-071)
|
|
2042
|
+
activeWorker = startBatchInWorker(
|
|
2043
|
+
{
|
|
2044
|
+
engineWorker: true,
|
|
2045
|
+
mode: "resume",
|
|
2046
|
+
args: "",
|
|
1847
2047
|
orchConfig,
|
|
1848
2048
|
runnerConfig,
|
|
1849
|
-
execCtx!.repoRoot,
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
(monState: MonitorState) => {
|
|
1856
|
-
latestMonitorState = monState;
|
|
1857
|
-
updateOrchWidget();
|
|
1858
|
-
},
|
|
1859
|
-
execCtx!.workspaceConfig,
|
|
1860
|
-
execCtx!.workspaceRoot,
|
|
1861
|
-
execCtx!.pointer?.agentRoot,
|
|
2049
|
+
cwd: execCtx!.repoRoot,
|
|
2050
|
+
workspaceConfig: execCtx!.workspaceConfig
|
|
2051
|
+
? serializeWorkspaceConfig(execCtx!.workspaceConfig)
|
|
2052
|
+
: null,
|
|
2053
|
+
workspaceRoot: execCtx!.workspaceRoot,
|
|
2054
|
+
agentRoot: execCtx!.pointer?.agentRoot,
|
|
1862
2055
|
force,
|
|
1863
|
-
|
|
2056
|
+
},
|
|
1864
2057
|
orchBatchState,
|
|
1865
2058
|
ctx,
|
|
1866
2059
|
updateOrchWidget,
|
|
2060
|
+
(monState: MonitorState) => {
|
|
2061
|
+
latestMonitorState = monState;
|
|
2062
|
+
updateOrchWidget();
|
|
2063
|
+
},
|
|
1867
2064
|
() => {
|
|
1868
2065
|
const mode = orchConfig.orchestrator.integration;
|
|
1869
2066
|
const opId = resolveOperatorId(orchConfig);
|
|
@@ -1936,6 +2133,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
1936
2133
|
};
|
|
1937
2134
|
transitionToRoutingMode(pi, supervisorState, postBatchContext);
|
|
1938
2135
|
},
|
|
2136
|
+
// Fallback: run resume on main thread if worker spawn fails (R001 §1)
|
|
2137
|
+
() => resumeOrchBatch(
|
|
2138
|
+
orchConfig,
|
|
2139
|
+
runnerConfig,
|
|
2140
|
+
execCtx!.repoRoot,
|
|
2141
|
+
orchBatchState,
|
|
2142
|
+
(message, level) => {
|
|
2143
|
+
ctx.ui.notify(message, level);
|
|
2144
|
+
updateOrchWidget();
|
|
2145
|
+
},
|
|
2146
|
+
(monState: MonitorState) => {
|
|
2147
|
+
latestMonitorState = monState;
|
|
2148
|
+
updateOrchWidget();
|
|
2149
|
+
},
|
|
2150
|
+
execCtx!.workspaceConfig,
|
|
2151
|
+
execCtx!.workspaceRoot,
|
|
2152
|
+
execCtx!.pointer?.agentRoot,
|
|
2153
|
+
force,
|
|
2154
|
+
),
|
|
1939
2155
|
);
|
|
1940
2156
|
|
|
1941
2157
|
// Activate supervisor agent on resume
|
|
@@ -1973,11 +2189,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
1973
2189
|
messages.push(` ⚠ Failed to write abort signal file: ${err instanceof Error ? err.message : String(err)}`);
|
|
1974
2190
|
}
|
|
1975
2191
|
|
|
1976
|
-
// Step 2: Set pause signal
|
|
2192
|
+
// Step 2: Set pause signal and forward to worker
|
|
1977
2193
|
if (orchBatchState.pauseSignal) {
|
|
1978
2194
|
orchBatchState.pauseSignal.paused = true;
|
|
1979
2195
|
messages.push(" ✓ Pause signal set on in-memory batch state");
|
|
1980
2196
|
}
|
|
2197
|
+
// TP-071: Forward pause to worker and terminate on hard abort
|
|
2198
|
+
if (activeWorker) {
|
|
2199
|
+
activeWorker.postMessage({ type: "pause" });
|
|
2200
|
+
if (hard) {
|
|
2201
|
+
activeWorker.terminate();
|
|
2202
|
+
activeWorker = null;
|
|
2203
|
+
messages.push(" ✓ Engine worker thread terminated (hard abort)");
|
|
2204
|
+
} else {
|
|
2205
|
+
messages.push(" ✓ Pause signal forwarded to engine worker thread");
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
1981
2208
|
|
|
1982
2209
|
// Step 3: Check what we're aborting
|
|
1983
2210
|
const hasActiveBatch = orchBatchState.phase !== "idle" &&
|
|
@@ -3053,6 +3280,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
3053
3280
|
// Ensure supervisor lockfile/heartbeat are cleaned up on normal session exit.
|
|
3054
3281
|
// This avoids leaving a live-looking lock when the process exits cleanly.
|
|
3055
3282
|
pi.on("session_end", async () => {
|
|
3283
|
+
// TP-071: Terminate engine worker thread on session exit
|
|
3284
|
+
if (activeWorker) {
|
|
3285
|
+
try {
|
|
3286
|
+
activeWorker.terminate();
|
|
3287
|
+
activeWorker = null;
|
|
3288
|
+
} catch {
|
|
3289
|
+
// Best effort — worker may already be dead
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3056
3292
|
try {
|
|
3057
3293
|
await deactivateSupervisor(pi, supervisorState);
|
|
3058
3294
|
} catch {
|