taskplane 0.26.1 → 0.28.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/README.md +4 -1
- package/bin/taskplane.mjs +12 -5
- package/dashboard/public/app.js +162 -13
- package/extensions/taskplane/abort.ts +2 -1
- package/extensions/taskplane/agent-host.ts +100 -1
- package/extensions/taskplane/cleanup.ts +272 -10
- package/extensions/taskplane/discovery.ts +1818 -1508
- package/extensions/taskplane/engine.ts +182 -47
- package/extensions/taskplane/execution.ts +172 -51
- package/extensions/taskplane/extension.ts +5125 -5125
- package/extensions/taskplane/formatting.ts +70 -11
- package/extensions/taskplane/git.ts +34 -0
- package/extensions/taskplane/lane-runner.ts +586 -46
- package/extensions/taskplane/merge.ts +3128 -2917
- package/extensions/taskplane/persistence.ts +3 -0
- package/extensions/taskplane/resume.ts +86 -30
- package/extensions/taskplane/supervisor-primer.md +55 -0
- package/extensions/taskplane/types.ts +52 -3
- package/package.json +1 -1
- package/skills/create-taskplane-task/SKILL.md +58 -0
- package/skills/create-taskplane-task/references/prompt-template.md +39 -0
- package/templates/agents/task-worker-segment.md +44 -0
- package/templates/agents/task-worker.md +429 -387
|
@@ -367,22 +367,71 @@ export function buildDashboardViewModel(
|
|
|
367
367
|
// Build lane cards from monitor state (if available) or current lanes
|
|
368
368
|
const laneCards: OrchLaneCardData[] = [];
|
|
369
369
|
|
|
370
|
-
|
|
370
|
+
// TP-170: Detect stale monitor data from prior waves.
|
|
371
|
+
// When wave N+1 starts, batchState.currentLanes is updated to wave N+1's
|
|
372
|
+
// lanes, but monitorState may still hold wave N's data until the first
|
|
373
|
+
// poll of wave N+1's monitor. Detect this mismatch by checking whether
|
|
374
|
+
// the monitor's lane numbers match the current allocation.
|
|
375
|
+
const monitorIsFresh = monitorState && monitorState.lanes.length > 0 && (
|
|
376
|
+
// If no current allocation, monitor data is the best we have
|
|
377
|
+
// (covers terminal phases like completed/failed/stopped)
|
|
378
|
+
batchState.currentLanes.length === 0 ||
|
|
379
|
+
// If allocated lanes exist, verify monitor lanes match them
|
|
380
|
+
monitorState.lanes.some(ml =>
|
|
381
|
+
batchState.currentLanes.some(cl => cl.laneNumber === ml.laneNumber),
|
|
382
|
+
)
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// TP-170: Build a laneNumber → AllocatedLane index for identity reconciliation.
|
|
386
|
+
// In workspace mode, the monitor’s sessionName (e.g., "orch-henry-api-lane-1")
|
|
387
|
+
// may differ from the V2 registry agentId ("orch-henry-lane-3-worker").
|
|
388
|
+
// Cross-referencing with the current allocation ensures the displayed session
|
|
389
|
+
// name matches the authoritative laneSessionId for the current wave.
|
|
390
|
+
const allocatedByLaneNumber = new Map<number, { laneSessionId: string; laneId: string }>();
|
|
391
|
+
for (const cl of batchState.currentLanes) {
|
|
392
|
+
allocatedByLaneNumber.set(cl.laneNumber, { laneSessionId: cl.laneSessionId, laneId: cl.laneId });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (monitorIsFresh && monitorState) {
|
|
371
396
|
// Sort lanes by laneNumber (deterministic)
|
|
372
397
|
const sortedLanes = [...monitorState.lanes].sort((a, b) => a.laneNumber - b.laneNumber);
|
|
373
398
|
|
|
374
399
|
for (const lane of sortedLanes) {
|
|
375
400
|
const snap = lane.currentTaskSnapshot;
|
|
401
|
+
const alloc = allocatedByLaneNumber.get(lane.laneNumber);
|
|
402
|
+
|
|
403
|
+
// TP-170: Reconcile task-level vs lane-level sessionAlive.
|
|
404
|
+
// resolveTaskMonitorState may derive sessionAlive from the lane
|
|
405
|
+
// snapshot file (snap.status === "running") while the lane-level
|
|
406
|
+
// sessionAlive comes from isV2AgentAlive (PID check). When the
|
|
407
|
+
// task snapshot says "running" but the lane session is confirmed
|
|
408
|
+
// dead, the task is effectively failed — not still running.
|
|
376
409
|
let status: OrchLaneCardData["status"] = "idle";
|
|
377
|
-
if (lane.failedTasks.length > 0)
|
|
378
|
-
|
|
379
|
-
else if (snap?.status === "
|
|
380
|
-
|
|
410
|
+
if (lane.failedTasks.length > 0) {
|
|
411
|
+
status = "failed";
|
|
412
|
+
} else if (snap?.status === "stalled") {
|
|
413
|
+
status = "stalled";
|
|
414
|
+
} else if (snap?.status === "running") {
|
|
415
|
+
// TP-170: TOCTOU guard — if lane session is dead but task snapshot
|
|
416
|
+
// still says "running", treat as failed instead of showing
|
|
417
|
+
// "session dead" in the card. This prevents the false positive
|
|
418
|
+
// where the lane snapshot file lags behind the PID liveness check.
|
|
419
|
+
status = lane.sessionAlive ? "running" : "failed";
|
|
420
|
+
} else if (
|
|
421
|
+
lane.completedTasks.length > 0 &&
|
|
422
|
+
lane.remainingTasks.length === 0 &&
|
|
423
|
+
!lane.currentTaskId
|
|
424
|
+
) {
|
|
425
|
+
status = "succeeded";
|
|
426
|
+
}
|
|
381
427
|
|
|
382
428
|
laneCards.push({
|
|
383
429
|
laneNumber: lane.laneNumber,
|
|
384
|
-
laneId: lane.laneId,
|
|
385
|
-
|
|
430
|
+
laneId: alloc?.laneId || lane.laneId,
|
|
431
|
+
// TP-170: Prefer the allocation’s laneSessionId (current-wave authority)
|
|
432
|
+
// over the monitor’s sessionName which may use a stale or workspace-local
|
|
433
|
+
// name that doesn’t match the V2 registry.
|
|
434
|
+
sessionName: alloc?.laneSessionId || lane.sessionName,
|
|
386
435
|
sessionAlive: lane.sessionAlive,
|
|
387
436
|
currentTaskId: lane.currentTaskId,
|
|
388
437
|
currentStepName: snap?.currentStepName || null,
|
|
@@ -395,7 +444,9 @@ export function buildDashboardViewModel(
|
|
|
395
444
|
});
|
|
396
445
|
}
|
|
397
446
|
} else if (batchState.currentLanes.length > 0) {
|
|
398
|
-
// No monitor data
|
|
447
|
+
// No fresh monitor data — show lanes from allocation.
|
|
448
|
+
// This covers both initial startup (monitor hasn't polled yet)
|
|
449
|
+
// and wave transitions (monitor data is stale from prior wave).
|
|
399
450
|
const sortedLanes = [...batchState.currentLanes].sort((a, b) => a.laneNumber - b.laneNumber);
|
|
400
451
|
for (const lane of sortedLanes) {
|
|
401
452
|
laneCards.push({
|
|
@@ -495,7 +546,11 @@ export function renderLaneCard(card: OrchLaneCardData, colWidth: number, theme:
|
|
|
495
546
|
if (card.currentStepName) {
|
|
496
547
|
stepInfo = trunc(card.currentStepName, w - 2);
|
|
497
548
|
} else if (card.currentTaskId && card.totalItems === 0) {
|
|
498
|
-
|
|
549
|
+
// TP-170: Distinguish startup-grace (no STATUS.md yet) from
|
|
550
|
+
// genuine stale data. During startup, the lane is alive but
|
|
551
|
+
// hasn’t written STATUS.md yet — show "starting..." instead of
|
|
552
|
+
// the misleading "waiting for data..." which implies a problem.
|
|
553
|
+
stepInfo = card.sessionAlive ? "starting..." : "no status data";
|
|
499
554
|
} else if (!card.currentTaskId && card.status !== "idle") {
|
|
500
555
|
stepInfo = `${card.completedTasks}/${card.totalLaneTasks} tasks`;
|
|
501
556
|
}
|
|
@@ -512,8 +567,12 @@ export function renderLaneCard(card: OrchLaneCardData, colWidth: number, theme:
|
|
|
512
567
|
extraInfo = `${card.totalChecked}/${card.totalItems} ✓`;
|
|
513
568
|
extraColor = card.totalChecked === card.totalItems ? "success" : "muted";
|
|
514
569
|
} else if (!card.sessionAlive && card.status === "running") {
|
|
515
|
-
|
|
516
|
-
|
|
570
|
+
// TP-170: With the TOCTOU guard in buildDashboardViewModel, a lane
|
|
571
|
+
// with a dead session and task snapshot "running" now gets status
|
|
572
|
+
// "failed" instead. This branch guards any remaining edge cases
|
|
573
|
+
// (e.g., allocation-fallback lane assumed alive but actually dead).
|
|
574
|
+
extraInfo = "session ended";
|
|
575
|
+
extraColor = "warning";
|
|
517
576
|
}
|
|
518
577
|
const extraStr = theme.fg(extraColor, trunc(extraInfo, w));
|
|
519
578
|
const extraVis = Math.min(extraInfo.length, w);
|
|
@@ -54,3 +54,37 @@ export function runGit(
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Run a git command with custom environment variables.
|
|
59
|
+
*
|
|
60
|
+
* Used by TP-169 to create commits on the orch branch without
|
|
61
|
+
* modifying HEAD, via GIT_INDEX_FILE for alternate index manipulation.
|
|
62
|
+
*
|
|
63
|
+
* @param args - Git command arguments
|
|
64
|
+
* @param cwd - Working directory
|
|
65
|
+
* @param env - Additional environment variables to set
|
|
66
|
+
*/
|
|
67
|
+
export function runGitWithEnv(
|
|
68
|
+
args: string[],
|
|
69
|
+
cwd: string,
|
|
70
|
+
env: Record<string, string>,
|
|
71
|
+
): { ok: boolean; stdout: string; stderr: string } {
|
|
72
|
+
try {
|
|
73
|
+
const stdout = execFileSync("git", args, {
|
|
74
|
+
encoding: "utf-8",
|
|
75
|
+
timeout: 30_000,
|
|
76
|
+
cwd,
|
|
77
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
78
|
+
env: { ...process.env, ...env },
|
|
79
|
+
}).trim();
|
|
80
|
+
return { ok: true, stdout, stderr: "" };
|
|
81
|
+
} catch (err: unknown) {
|
|
82
|
+
const e = err as { stdout?: string; stderr?: string; message?: string };
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
stdout: (e.stdout ?? "").toString().trim(),
|
|
86
|
+
stderr: (e.stderr ?? e.message ?? "unknown error").toString().trim(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|