taskplane 0.22.18 → 0.23.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/dashboard/public/app.js +365 -7
- package/dashboard/public/index.html +16 -0
- package/dashboard/public/style.css +105 -0
- package/dashboard/server.cjs +199 -0
- package/extensions/task-runner.ts +40 -286
- package/extensions/taskplane/abort.ts +11 -1
- package/extensions/taskplane/agent-bridge-extension.ts +159 -0
- package/extensions/taskplane/agent-host.ts +686 -0
- package/extensions/taskplane/engine.ts +75 -3
- package/extensions/taskplane/execution.ts +403 -9
- package/extensions/taskplane/extension.ts +322 -28
- package/extensions/taskplane/lane-runner.ts +567 -0
- package/extensions/taskplane/mailbox.ts +349 -1
- package/extensions/taskplane/merge.ts +208 -51
- package/extensions/taskplane/process-registry.ts +345 -0
- package/extensions/taskplane/resume.ts +185 -47
- package/extensions/taskplane/supervisor.ts +16 -12
- package/extensions/taskplane/task-executor-core.ts +553 -0
- package/extensions/taskplane/types.ts +517 -1
- package/package.json +1 -1
- package/skills/create-taskplane-task/SKILL.md +41 -33
- package/skills/create-taskplane-task/references/prompt-template.md +3 -3
|
@@ -1895,13 +1895,14 @@ export type EngineEventCallback = (event: EngineEvent) => void;
|
|
|
1895
1895
|
* - `task-failure`: A task failed after deterministic recovery was exhausted
|
|
1896
1896
|
* - `merge-failure`: Wave merge failed and batch paused
|
|
1897
1897
|
* - `batch-complete`: Batch finished (all waves done)
|
|
1898
|
+
* - `agent-message`: Runtime mailbox reply/escalation from a running agent
|
|
1898
1899
|
*
|
|
1899
1900
|
* Note: `stall` detection is deferred to a future phase (requires
|
|
1900
1901
|
* last-activity tracking not yet built).
|
|
1901
1902
|
*
|
|
1902
1903
|
* @since TP-076
|
|
1903
1904
|
*/
|
|
1904
|
-
export type SupervisorAlertCategory = "task-failure" | "merge-failure" | "batch-complete";
|
|
1905
|
+
export type SupervisorAlertCategory = "task-failure" | "merge-failure" | "batch-complete" | "agent-message";
|
|
1905
1906
|
|
|
1906
1907
|
/**
|
|
1907
1908
|
* Structured context payload for supervisor alerts.
|
|
@@ -1922,6 +1923,10 @@ export interface SupervisorAlertContext {
|
|
|
1922
1923
|
waveIndex?: number;
|
|
1923
1924
|
/** Exit reason string (for task-failure alerts) */
|
|
1924
1925
|
exitReason?: string;
|
|
1926
|
+
/** Agent ID (for agent-message alerts) */
|
|
1927
|
+
agentId?: string;
|
|
1928
|
+
/** Mailbox message ID (for agent-message alerts) */
|
|
1929
|
+
messageId?: string;
|
|
1925
1930
|
/** Whether partial progress was preserved (for task-failure alerts) */
|
|
1926
1931
|
partialProgress?: boolean;
|
|
1927
1932
|
/** Batch progress summary */
|
|
@@ -3451,3 +3456,514 @@ export interface WriteMailboxMessageOpts {
|
|
|
3451
3456
|
replyTo?: string | null;
|
|
3452
3457
|
}
|
|
3453
3458
|
|
|
3459
|
+
// ── Runtime V2 Contracts (TP-102) ────────────────────────────────────
|
|
3460
|
+
//
|
|
3461
|
+
// These types define the foundational contracts for the no-TMUX Runtime V2
|
|
3462
|
+
// architecture. They are additive — existing runtime paths continue to work
|
|
3463
|
+
// while Runtime V2 is incrementally adopted.
|
|
3464
|
+
//
|
|
3465
|
+
// Design principles:
|
|
3466
|
+
// 1. Agent identity is a stable runtime ID, not a TMUX session name.
|
|
3467
|
+
// 2. Packet-path authority is explicit, never inferred from cwd.
|
|
3468
|
+
// 3. Process ownership uses a registry, not terminal session discovery.
|
|
3469
|
+
// 4. Normalized events flow directly from child to parent.
|
|
3470
|
+
//
|
|
3471
|
+
// See: docs/specifications/framework/taskplane-runtime-v2/
|
|
3472
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
3473
|
+
|
|
3474
|
+
/**
|
|
3475
|
+
* Canonical agent roles in the Runtime V2 process model.
|
|
3476
|
+
*
|
|
3477
|
+
* Every spawned agent process has exactly one role. The role determines
|
|
3478
|
+
* the process's responsibilities, tools, and lifecycle semantics.
|
|
3479
|
+
*
|
|
3480
|
+
* @since TP-102
|
|
3481
|
+
*/
|
|
3482
|
+
export type RuntimeAgentRole = "worker" | "reviewer" | "merger" | "lane-runner";
|
|
3483
|
+
|
|
3484
|
+
/**
|
|
3485
|
+
* Agent lifecycle states in the process registry.
|
|
3486
|
+
*
|
|
3487
|
+
* State machine:
|
|
3488
|
+
* spawning → running → wrapping_up → exited
|
|
3489
|
+
* → crashed
|
|
3490
|
+
* → timed_out
|
|
3491
|
+
* → killed
|
|
3492
|
+
*
|
|
3493
|
+
* @since TP-102
|
|
3494
|
+
*/
|
|
3495
|
+
export type RuntimeAgentStatus =
|
|
3496
|
+
| "spawning"
|
|
3497
|
+
| "running"
|
|
3498
|
+
| "wrapping_up"
|
|
3499
|
+
| "exited"
|
|
3500
|
+
| "crashed"
|
|
3501
|
+
| "timed_out"
|
|
3502
|
+
| "killed";
|
|
3503
|
+
|
|
3504
|
+
/** Set of terminal agent statuses (process is no longer alive). @since TP-102 */
|
|
3505
|
+
export const TERMINAL_AGENT_STATUSES: ReadonlySet<RuntimeAgentStatus> = new Set([
|
|
3506
|
+
"exited", "crashed", "timed_out", "killed",
|
|
3507
|
+
]);
|
|
3508
|
+
|
|
3509
|
+
/**
|
|
3510
|
+
* Stable agent identity for Runtime V2.
|
|
3511
|
+
*
|
|
3512
|
+
* This replaces TMUX session names as the canonical identifier for a
|
|
3513
|
+
* spawned agent process. The string format is deliberately compatible
|
|
3514
|
+
* with existing naming conventions (e.g., "orch-henrylach-lane-1-worker")
|
|
3515
|
+
* to minimize churn in supervisor tools, dashboard, and mailbox addressing.
|
|
3516
|
+
*
|
|
3517
|
+
* The key semantic change: this is a **runtime process ID**, not a terminal
|
|
3518
|
+
* session name. Code must not assume `tmuxHasSession(agentId)` is valid.
|
|
3519
|
+
*
|
|
3520
|
+
* @since TP-102
|
|
3521
|
+
*/
|
|
3522
|
+
export type RuntimeAgentId = string;
|
|
3523
|
+
|
|
3524
|
+
/**
|
|
3525
|
+
* Explicit packet-path authority for a task execution.
|
|
3526
|
+
*
|
|
3527
|
+
* In workspace mode, the packet home (where PROMPT.md / STATUS.md / .DONE
|
|
3528
|
+
* live) may differ from the execution cwd (the active segment repo worktree).
|
|
3529
|
+
* Runtime V2 requires these paths to be resolved explicitly and passed
|
|
3530
|
+
* through the execution chain — never inferred from cwd.
|
|
3531
|
+
*
|
|
3532
|
+
* In repo mode (single repo), all paths point into the same filesystem tree.
|
|
3533
|
+
* The contract is the same; the values just happen to be co-located.
|
|
3534
|
+
*
|
|
3535
|
+
* @since TP-102
|
|
3536
|
+
*/
|
|
3537
|
+
export interface PacketPaths {
|
|
3538
|
+
/** Absolute path to the task's PROMPT.md */
|
|
3539
|
+
promptPath: string;
|
|
3540
|
+
/** Absolute path to the task's STATUS.md */
|
|
3541
|
+
statusPath: string;
|
|
3542
|
+
/** Absolute path to the task's .DONE marker */
|
|
3543
|
+
donePath: string;
|
|
3544
|
+
/** Absolute path to the task's .reviews/ directory */
|
|
3545
|
+
reviewsDir: string;
|
|
3546
|
+
/** Absolute path to the task folder containing packet files */
|
|
3547
|
+
taskFolder: string;
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
/**
|
|
3551
|
+
* Resolve a PacketPaths object from a task folder path.
|
|
3552
|
+
*
|
|
3553
|
+
* This is a pure helper — it does not check whether the files exist.
|
|
3554
|
+
* Consumers should use this to build authoritative paths from an
|
|
3555
|
+
* already-resolved task folder location.
|
|
3556
|
+
*
|
|
3557
|
+
* @param taskFolder - Absolute path to the task folder
|
|
3558
|
+
* @returns Complete PacketPaths with all derived paths
|
|
3559
|
+
*
|
|
3560
|
+
* @since TP-102
|
|
3561
|
+
*/
|
|
3562
|
+
export function resolvePacketPaths(taskFolder: string): PacketPaths {
|
|
3563
|
+
return {
|
|
3564
|
+
promptPath: `${taskFolder}/PROMPT.md`,
|
|
3565
|
+
statusPath: `${taskFolder}/STATUS.md`,
|
|
3566
|
+
donePath: `${taskFolder}/.DONE`,
|
|
3567
|
+
reviewsDir: `${taskFolder}/.reviews`,
|
|
3568
|
+
taskFolder,
|
|
3569
|
+
};
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
/**
|
|
3573
|
+
* A single execution unit in Runtime V2.
|
|
3574
|
+
*
|
|
3575
|
+
* Represents one unit of work to be executed in one lane: either a whole
|
|
3576
|
+
* task (repo mode / single-segment workspace mode) or one segment of a
|
|
3577
|
+
* multi-repo task.
|
|
3578
|
+
*
|
|
3579
|
+
* This is the contract between the engine (which decides what to run) and
|
|
3580
|
+
* the lane-runner (which runs it). It carries everything the lane-runner
|
|
3581
|
+
* needs without requiring it to re-derive paths from cwd or session state.
|
|
3582
|
+
*
|
|
3583
|
+
* @since TP-102
|
|
3584
|
+
*/
|
|
3585
|
+
export interface ExecutionUnit {
|
|
3586
|
+
/** Unique identifier: taskId for whole-task units, `taskId::repoId` for segments */
|
|
3587
|
+
id: string;
|
|
3588
|
+
/** Parent task identifier */
|
|
3589
|
+
taskId: string;
|
|
3590
|
+
/** Segment identifier (null for whole-task execution) */
|
|
3591
|
+
segmentId: string | null;
|
|
3592
|
+
/** Repo ID where execution happens (cwd of the worker) */
|
|
3593
|
+
executionRepoId: string;
|
|
3594
|
+
/** Repo ID that owns the packet files (may differ in workspace mode) */
|
|
3595
|
+
packetHomeRepoId: string;
|
|
3596
|
+
/** Absolute path to the execution worktree */
|
|
3597
|
+
worktreePath: string;
|
|
3598
|
+
/** Authoritative packet file paths */
|
|
3599
|
+
packet: PacketPaths;
|
|
3600
|
+
/** Full parsed task metadata */
|
|
3601
|
+
task: ParsedTask;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
/**
|
|
3605
|
+
* Per-agent process manifest for the runtime registry.
|
|
3606
|
+
*
|
|
3607
|
+
* Written by the agent's parent process (lane-runner or engine) before
|
|
3608
|
+
* the agent is considered visible. Updated on status transitions and
|
|
3609
|
+
* cleaned up on batch completion.
|
|
3610
|
+
*
|
|
3611
|
+
* Replaces TMUX session discovery as the source of truth for agent
|
|
3612
|
+
* liveness, identity, and attribution.
|
|
3613
|
+
*
|
|
3614
|
+
* File location: `.pi/runtime/{batchId}/agents/{agentId}/manifest.json`
|
|
3615
|
+
*
|
|
3616
|
+
* @since TP-102
|
|
3617
|
+
*/
|
|
3618
|
+
export interface RuntimeAgentManifest {
|
|
3619
|
+
/** Batch this agent belongs to */
|
|
3620
|
+
batchId: string;
|
|
3621
|
+
/** Stable agent identity (e.g., "orch-henrylach-lane-1-worker") */
|
|
3622
|
+
agentId: RuntimeAgentId;
|
|
3623
|
+
/** Agent role */
|
|
3624
|
+
role: RuntimeAgentRole;
|
|
3625
|
+
/** Lane number (null for merge agents) */
|
|
3626
|
+
laneNumber: number | null;
|
|
3627
|
+
/** Current task ID being executed (null before first assignment) */
|
|
3628
|
+
taskId: string | null;
|
|
3629
|
+
/** Repo ID the agent is operating in */
|
|
3630
|
+
repoId: string;
|
|
3631
|
+
/** OS process ID of the agent host process */
|
|
3632
|
+
pid: number;
|
|
3633
|
+
/** OS process ID of the parent (lane-runner or engine) */
|
|
3634
|
+
parentPid: number;
|
|
3635
|
+
/** Epoch ms when the agent was spawned */
|
|
3636
|
+
startedAt: number;
|
|
3637
|
+
/** Current lifecycle status */
|
|
3638
|
+
status: RuntimeAgentStatus;
|
|
3639
|
+
/** Absolute path to the agent's working directory */
|
|
3640
|
+
cwd: string;
|
|
3641
|
+
/** Authoritative packet paths (null for merge agents or pre-assignment) */
|
|
3642
|
+
packet: PacketPaths | null;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
/**
|
|
3646
|
+
* Batch-level runtime registry snapshot.
|
|
3647
|
+
*
|
|
3648
|
+
* Contains all active and recently-exited agents for one batch.
|
|
3649
|
+
* The authoritative source of truth for which agents exist, replacing
|
|
3650
|
+
* TMUX session discovery.
|
|
3651
|
+
*
|
|
3652
|
+
* File location: `.pi/runtime/{batchId}/registry.json`
|
|
3653
|
+
*
|
|
3654
|
+
* @since TP-102
|
|
3655
|
+
*/
|
|
3656
|
+
export interface RuntimeRegistry {
|
|
3657
|
+
/** Batch ID this registry belongs to */
|
|
3658
|
+
batchId: string;
|
|
3659
|
+
/** Epoch ms when the registry was last updated */
|
|
3660
|
+
updatedAt: number;
|
|
3661
|
+
/** All known agents (keyed by agentId for fast lookup in JSON form) */
|
|
3662
|
+
agents: Record<RuntimeAgentId, RuntimeAgentManifest>;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
/**
|
|
3666
|
+
* Lane execution snapshot emitted by the lane-runner.
|
|
3667
|
+
*
|
|
3668
|
+
* Replaces the current `lane-state-*.json` sidecar with a first-class
|
|
3669
|
+
* contract. Written by the lane-runner directly (not by tailing sidecar
|
|
3670
|
+
* files from a sibling process).
|
|
3671
|
+
*
|
|
3672
|
+
* File location: `.pi/runtime/{batchId}/lanes/lane-{N}.json`
|
|
3673
|
+
*
|
|
3674
|
+
* @since TP-102
|
|
3675
|
+
*/
|
|
3676
|
+
export interface RuntimeLaneSnapshot {
|
|
3677
|
+
/** Batch this lane belongs to */
|
|
3678
|
+
batchId: string;
|
|
3679
|
+
/** Lane number (1-indexed) */
|
|
3680
|
+
laneNumber: number;
|
|
3681
|
+
/** Lane identifier (e.g., "lane-1") */
|
|
3682
|
+
laneId: string;
|
|
3683
|
+
/** Repo ID this lane targets */
|
|
3684
|
+
repoId: string;
|
|
3685
|
+
/** Current task ID being executed */
|
|
3686
|
+
taskId: string | null;
|
|
3687
|
+
/** Current segment ID (null for whole-task execution) */
|
|
3688
|
+
segmentId: string | null;
|
|
3689
|
+
/** Lane execution status */
|
|
3690
|
+
status: "idle" | "running" | "complete" | "failed";
|
|
3691
|
+
/** Worker agent snapshot (null when no worker is active) */
|
|
3692
|
+
worker: RuntimeAgentTelemetrySnapshot | null;
|
|
3693
|
+
/** Reviewer agent snapshot (null when no reviewer is active) */
|
|
3694
|
+
reviewer: RuntimeAgentTelemetrySnapshot | null;
|
|
3695
|
+
/** Task progress derived from STATUS.md */
|
|
3696
|
+
progress: RuntimeTaskProgress | null;
|
|
3697
|
+
/** Epoch ms when this snapshot was last updated */
|
|
3698
|
+
updatedAt: number;
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
/**
|
|
3702
|
+
* Telemetry snapshot for a single agent within a lane.
|
|
3703
|
+
*
|
|
3704
|
+
* @since TP-102
|
|
3705
|
+
*/
|
|
3706
|
+
export interface RuntimeAgentTelemetrySnapshot {
|
|
3707
|
+
/** Agent ID */
|
|
3708
|
+
agentId: RuntimeAgentId;
|
|
3709
|
+
/** Agent lifecycle status */
|
|
3710
|
+
status: RuntimeAgentStatus;
|
|
3711
|
+
/** Elapsed time in milliseconds */
|
|
3712
|
+
elapsedMs: number;
|
|
3713
|
+
/** Number of tool calls made */
|
|
3714
|
+
toolCalls: number;
|
|
3715
|
+
/** Context window utilization percentage (0-100) */
|
|
3716
|
+
contextPct: number;
|
|
3717
|
+
/** Cumulative cost in USD */
|
|
3718
|
+
costUsd: number;
|
|
3719
|
+
/** Last tool call description */
|
|
3720
|
+
lastTool: string;
|
|
3721
|
+
/** Input tokens consumed */
|
|
3722
|
+
inputTokens: number;
|
|
3723
|
+
/** Output tokens generated */
|
|
3724
|
+
outputTokens: number;
|
|
3725
|
+
/** Cache read tokens */
|
|
3726
|
+
cacheReadTokens: number;
|
|
3727
|
+
/** Cache write tokens */
|
|
3728
|
+
cacheWriteTokens: number;
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
/**
|
|
3732
|
+
* Task progress derived from STATUS.md parsing.
|
|
3733
|
+
*
|
|
3734
|
+
* @since TP-102
|
|
3735
|
+
*/
|
|
3736
|
+
export interface RuntimeTaskProgress {
|
|
3737
|
+
/** Human-readable current step label */
|
|
3738
|
+
currentStep: string;
|
|
3739
|
+
/** Number of checked checkboxes across all steps */
|
|
3740
|
+
checked: number;
|
|
3741
|
+
/** Total number of checkboxes across all steps */
|
|
3742
|
+
total: number;
|
|
3743
|
+
/** Current worker iteration number */
|
|
3744
|
+
iteration: number;
|
|
3745
|
+
/** Number of reviews performed */
|
|
3746
|
+
reviews: number;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
/**
|
|
3750
|
+
* Normalized event emitted by an agent host.
|
|
3751
|
+
*
|
|
3752
|
+
* The canonical telemetry/conversation event shape for Runtime V2.
|
|
3753
|
+
* Agent hosts write these to per-agent event logs and stream them
|
|
3754
|
+
* to their parent process via IPC.
|
|
3755
|
+
*
|
|
3756
|
+
* File location: `.pi/runtime/{batchId}/agents/{agentId}/events.jsonl`
|
|
3757
|
+
*
|
|
3758
|
+
* @since TP-102
|
|
3759
|
+
*/
|
|
3760
|
+
export interface RuntimeAgentEvent {
|
|
3761
|
+
/** Batch ID */
|
|
3762
|
+
batchId: string;
|
|
3763
|
+
/** Agent that produced this event */
|
|
3764
|
+
agentId: RuntimeAgentId;
|
|
3765
|
+
/** Agent role */
|
|
3766
|
+
role: RuntimeAgentRole;
|
|
3767
|
+
/** Lane number (null for merge agents) */
|
|
3768
|
+
laneNumber: number | null;
|
|
3769
|
+
/** Task ID being executed when the event was produced */
|
|
3770
|
+
taskId: string | null;
|
|
3771
|
+
/** Repo ID */
|
|
3772
|
+
repoId: string;
|
|
3773
|
+
/** Epoch ms timestamp */
|
|
3774
|
+
ts: number;
|
|
3775
|
+
/** Event type */
|
|
3776
|
+
type: RuntimeAgentEventType;
|
|
3777
|
+
/** Event-specific payload */
|
|
3778
|
+
payload: Record<string, unknown>;
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* Normalized event types for the Runtime V2 agent event stream.
|
|
3783
|
+
*
|
|
3784
|
+
* @since TP-102
|
|
3785
|
+
*/
|
|
3786
|
+
export type RuntimeAgentEventType =
|
|
3787
|
+
// Lifecycle
|
|
3788
|
+
| "agent_started"
|
|
3789
|
+
| "agent_exited"
|
|
3790
|
+
| "agent_killed"
|
|
3791
|
+
| "agent_crashed"
|
|
3792
|
+
| "agent_timeout"
|
|
3793
|
+
// Conversation
|
|
3794
|
+
| "prompt_sent"
|
|
3795
|
+
| "assistant_message"
|
|
3796
|
+
| "tool_call"
|
|
3797
|
+
| "tool_result"
|
|
3798
|
+
// Telemetry
|
|
3799
|
+
| "usage_delta"
|
|
3800
|
+
| "context_usage"
|
|
3801
|
+
| "retry_started"
|
|
3802
|
+
| "retry_finished"
|
|
3803
|
+
| "compaction_started"
|
|
3804
|
+
| "compaction_finished"
|
|
3805
|
+
// Steering
|
|
3806
|
+
| "message_delivered"
|
|
3807
|
+
| "reply_sent"
|
|
3808
|
+
| "escalation_sent"
|
|
3809
|
+
// Review / bridge
|
|
3810
|
+
| "review_requested"
|
|
3811
|
+
| "review_completed"
|
|
3812
|
+
| "review_failed";
|
|
3813
|
+
|
|
3814
|
+
// ── Runtime V2 Path Helpers (TP-102) ─────────────────────────────────
|
|
3815
|
+
|
|
3816
|
+
/**
|
|
3817
|
+
* Resolve the root directory for Runtime V2 artifacts for a given batch.
|
|
3818
|
+
*
|
|
3819
|
+
* @param stateRoot - Root directory containing .pi/ (workspace root or repo root)
|
|
3820
|
+
* @param batchId - Batch identifier
|
|
3821
|
+
* @returns Absolute path: `{stateRoot}/.pi/runtime/{batchId}/`
|
|
3822
|
+
*
|
|
3823
|
+
* @since TP-102
|
|
3824
|
+
*/
|
|
3825
|
+
export function runtimeRoot(stateRoot: string, batchId: string): string {
|
|
3826
|
+
return `${stateRoot}/.pi/runtime/${batchId}`;
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
/**
|
|
3830
|
+
* Resolve the path for a specific agent's runtime directory.
|
|
3831
|
+
*
|
|
3832
|
+
* @param stateRoot - Root directory containing .pi/
|
|
3833
|
+
* @param batchId - Batch identifier
|
|
3834
|
+
* @param agentId - Runtime agent identifier
|
|
3835
|
+
* @returns Absolute path: `{stateRoot}/.pi/runtime/{batchId}/agents/{agentId}/`
|
|
3836
|
+
*
|
|
3837
|
+
* @since TP-102
|
|
3838
|
+
*/
|
|
3839
|
+
export function runtimeAgentDir(stateRoot: string, batchId: string, agentId: RuntimeAgentId): string {
|
|
3840
|
+
return `${stateRoot}/.pi/runtime/${batchId}/agents/${agentId}`;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
/**
|
|
3844
|
+
* Resolve the path for a specific agent's manifest file.
|
|
3845
|
+
*
|
|
3846
|
+
* @since TP-102
|
|
3847
|
+
*/
|
|
3848
|
+
export function runtimeManifestPath(stateRoot: string, batchId: string, agentId: RuntimeAgentId): string {
|
|
3849
|
+
return `${runtimeAgentDir(stateRoot, batchId, agentId)}/manifest.json`;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
/**
|
|
3853
|
+
* Resolve the path for a specific agent's event log.
|
|
3854
|
+
*
|
|
3855
|
+
* @since TP-102
|
|
3856
|
+
*/
|
|
3857
|
+
export function runtimeAgentEventsPath(stateRoot: string, batchId: string, agentId: RuntimeAgentId): string {
|
|
3858
|
+
return `${runtimeAgentDir(stateRoot, batchId, agentId)}/events.jsonl`;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
/**
|
|
3862
|
+
* Resolve the path for a lane snapshot file.
|
|
3863
|
+
*
|
|
3864
|
+
* @since TP-102
|
|
3865
|
+
*/
|
|
3866
|
+
export function runtimeLaneSnapshotPath(stateRoot: string, batchId: string, laneNumber: number): string {
|
|
3867
|
+
return `${stateRoot}/.pi/runtime/${batchId}/lanes/lane-${laneNumber}.json`;
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
/**
|
|
3871
|
+
* Resolve the path for the batch runtime registry.
|
|
3872
|
+
*
|
|
3873
|
+
* @since TP-102
|
|
3874
|
+
*/
|
|
3875
|
+
export function runtimeRegistryPath(stateRoot: string, batchId: string): string {
|
|
3876
|
+
return `${stateRoot}/.pi/runtime/${batchId}/registry.json`;
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
/**
|
|
3880
|
+
* Build a canonical RuntimeAgentId from components.
|
|
3881
|
+
*
|
|
3882
|
+
* Produces IDs compatible with the existing naming convention
|
|
3883
|
+
* (e.g., "orch-henrylach-lane-1-worker") while semantically
|
|
3884
|
+
* decoupling them from TMUX session names.
|
|
3885
|
+
*
|
|
3886
|
+
* @param prefix - Operator/batch prefix (e.g., "orch-henrylach")
|
|
3887
|
+
* @param laneNumber - Lane number (null for merge agents)
|
|
3888
|
+
* @param role - Agent role
|
|
3889
|
+
* @param mergeIndex - Merge wave index (only for merge agents)
|
|
3890
|
+
* @returns Canonical agent ID string
|
|
3891
|
+
*
|
|
3892
|
+
* @since TP-102
|
|
3893
|
+
*/
|
|
3894
|
+
export function buildRuntimeAgentId(
|
|
3895
|
+
prefix: string,
|
|
3896
|
+
laneNumber: number | null,
|
|
3897
|
+
role: RuntimeAgentRole,
|
|
3898
|
+
mergeIndex?: number,
|
|
3899
|
+
): RuntimeAgentId {
|
|
3900
|
+
if (role === "merger" && mergeIndex != null) {
|
|
3901
|
+
return `${prefix}-merge-${mergeIndex}`;
|
|
3902
|
+
}
|
|
3903
|
+
if (role === "lane-runner" && laneNumber != null) {
|
|
3904
|
+
return `${prefix}-lane-${laneNumber}`;
|
|
3905
|
+
}
|
|
3906
|
+
if (laneNumber != null) {
|
|
3907
|
+
return `${prefix}-lane-${laneNumber}-${role}`;
|
|
3908
|
+
}
|
|
3909
|
+
return `${prefix}-${role}`;
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
/**
|
|
3913
|
+
* Validate that a RuntimeAgentManifest has required fields and sane values.
|
|
3914
|
+
*
|
|
3915
|
+
* Returns an array of validation error strings (empty = valid).
|
|
3916
|
+
*
|
|
3917
|
+
* @since TP-102
|
|
3918
|
+
*/
|
|
3919
|
+
export function validateAgentManifest(manifest: unknown): string[] {
|
|
3920
|
+
const errors: string[] = [];
|
|
3921
|
+
if (!manifest || typeof manifest !== "object") {
|
|
3922
|
+
return ["manifest must be a non-null object"];
|
|
3923
|
+
}
|
|
3924
|
+
const m = manifest as Record<string, unknown>;
|
|
3925
|
+
|
|
3926
|
+
if (typeof m.batchId !== "string" || !m.batchId) errors.push("batchId must be a non-empty string");
|
|
3927
|
+
if (typeof m.agentId !== "string" || !m.agentId) errors.push("agentId must be a non-empty string");
|
|
3928
|
+
if (typeof m.role !== "string") errors.push("role must be a string");
|
|
3929
|
+
else {
|
|
3930
|
+
const validRoles: ReadonlySet<string> = new Set(["worker", "reviewer", "merger", "lane-runner"]);
|
|
3931
|
+
if (!validRoles.has(m.role as string)) errors.push(`role must be one of: ${[...validRoles].join(", ")}`);
|
|
3932
|
+
}
|
|
3933
|
+
if (typeof m.pid !== "number" || !Number.isFinite(m.pid) || m.pid <= 0) errors.push("pid must be a positive finite number");
|
|
3934
|
+
if (typeof m.parentPid !== "number" || !Number.isFinite(m.parentPid) || m.parentPid <= 0) errors.push("parentPid must be a positive finite number");
|
|
3935
|
+
if (typeof m.startedAt !== "number" || !Number.isFinite(m.startedAt)) errors.push("startedAt must be a finite number");
|
|
3936
|
+
if (typeof m.status !== "string") errors.push("status must be a string");
|
|
3937
|
+
else {
|
|
3938
|
+
const validStatuses: ReadonlySet<string> = new Set(["spawning", "running", "wrapping_up", "exited", "crashed", "timed_out", "killed"]);
|
|
3939
|
+
if (!validStatuses.has(m.status as string)) errors.push(`status must be one of: ${[...validStatuses].join(", ")}`);
|
|
3940
|
+
}
|
|
3941
|
+
if (typeof m.cwd !== "string" || !m.cwd) errors.push("cwd must be a non-empty string");
|
|
3942
|
+
if (typeof m.repoId !== "string") errors.push("repoId must be a string");
|
|
3943
|
+
|
|
3944
|
+
return errors;
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
/**
|
|
3948
|
+
* Validate that a PacketPaths object has all required fields.
|
|
3949
|
+
*
|
|
3950
|
+
* Returns an array of validation error strings (empty = valid).
|
|
3951
|
+
*
|
|
3952
|
+
* @since TP-102
|
|
3953
|
+
*/
|
|
3954
|
+
export function validatePacketPaths(packet: unknown): string[] {
|
|
3955
|
+
const errors: string[] = [];
|
|
3956
|
+
if (!packet || typeof packet !== "object") {
|
|
3957
|
+
return ["packet must be a non-null object"];
|
|
3958
|
+
}
|
|
3959
|
+
const p = packet as Record<string, unknown>;
|
|
3960
|
+
|
|
3961
|
+
for (const field of ["promptPath", "statusPath", "donePath", "reviewsDir", "taskFolder"] as const) {
|
|
3962
|
+
if (typeof p[field] !== "string" || !(p[field] as string)) {
|
|
3963
|
+
errors.push(`${field} must be a non-empty string`);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
return errors;
|
|
3968
|
+
}
|
|
3969
|
+
|