@ferris1225/pi-subagents 4.1.24 → 4.2.1

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/src/monitor.ts CHANGED
@@ -20,7 +20,6 @@ import type { IsolationMode, WorktreeFinalizationStatus } from "./worktree.ts";
20
20
 
21
21
  export type RunStatus = "queued" | "running" | "interrupting" | "parked" | "done" | "failed";
22
22
  export type ContinuationKind = "resume-retained" | "resume-appended";
23
- export type WorkflowStageStatus = "done" | "active" | "pending" | "changes" | "failed";
24
23
 
25
24
  /** Why a queued run has produced no output yet. Three genuinely different
26
25
  * situations used to be reported as one "queued": waiting for a free process
@@ -32,19 +31,6 @@ export type WorkflowStageStatus = "done" | "active" | "pending" | "changes" | "f
32
31
  * "queued"; cleared on every transition out of it. */
33
32
  export type RunWaitReason = "process-slot" | "repository-lane" | "starting";
34
33
 
35
- /** Ephemeral projection of one real or currently planned managed stage. It is
36
- * live monitor state only; durable results remain the per-run chain records. */
37
- export interface WorkflowStage {
38
- agent: string;
39
- relation: string;
40
- status: WorkflowStageStatus;
41
- /** Telemetry snapshot frozen when the stage settled (the live child row
42
- * leaves the monitor at that moment); the active stage reads its live child. */
43
- model?: string;
44
- usage?: UsageStats;
45
- elapsedMs?: number;
46
- }
47
-
48
34
  export function isRunActiveStatus(status: RunStatus): boolean {
49
35
  return status === "queued" || status === "running" || status === "interrupting";
50
36
  }
@@ -88,45 +74,19 @@ export interface RunView {
88
74
  endedAt?: number;
89
75
  /** Why this generation reused retained context, shown in the widget/status. */
90
76
  continuationKind?: ContinuationKind;
91
- /** When set, this is an internal managed-workflow step. */
92
- groupId?: string;
93
- /** Human-readable role within a workflow, e.g. "final review" or "final documentation sync". */
94
- relationLabel?: string;
95
- /** Stable owning run whose row represents the whole managed workflow. */
96
- parentRunId?: number;
97
- /** This stable top-level row currently owns a multi-stage managed workflow.
98
- * Its elapsed time is workflow-wide; active child rows own stage telemetry. */
99
- managedWorkflow?: boolean;
100
- /** Live-only stage timeline retained on the parent while completed internal
101
- * child rows leave the monitor. */
102
- workflowStages?: WorkflowStage[];
103
77
  }
104
78
 
105
- /** Optional metadata for documenter/reviewer/fix children of a stable parent run. */
79
+ /** Extra metadata for a run whose row must carry isolation or resume context. */
106
80
  export interface RunChainMeta {
107
- groupId?: string;
108
- relationLabel?: string;
109
- parentRunId?: number;
110
81
  isolation?: IsolationMode;
111
82
  worktreeId?: string;
112
83
  continuationKind?: ContinuationKind;
113
84
  /** Initial wait reason; defaults to "process-slot" (a fresh dispatch enters
114
- * the process queue). Workflow-internal children pass "starting" because
115
- * they spawn immediately and never wait for a slot. */
85
+ * the process queue). Children spawned outside the queue pass "starting"
86
+ * because they never wait for a slot. */
116
87
  waitReason?: RunWaitReason;
117
88
  }
118
89
 
119
- /** Ephemeral activity of the parent pi model while its agent loop runs: the
120
- * live model/thinking ref and a one-line "what is it doing now". Not a run —
121
- * no id, usage, or chain machinery; the view disappears when the loop settles. */
122
- export interface MainActivity {
123
- model?: string;
124
- thinking?: string;
125
- activity?: string;
126
- /** Epoch ms when the current agent loop started. */
127
- activeSince: number;
128
- }
129
-
130
90
  // ---------------------------------------------------------------------------
131
91
  // Formatting helpers
132
92
  // ---------------------------------------------------------------------------
@@ -494,71 +454,6 @@ export class MonitorStore {
494
454
  private runs: RunView[] = [];
495
455
  private nextId = 1;
496
456
  private subscribers = new Set<() => void>();
497
- private mainModel?: string;
498
- private mainThinking?: string;
499
- private mainActivity?: string;
500
- private mainActiveSince?: number;
501
-
502
- // --- parent pi model activity ------------------------------------------
503
- // Fed by the parent session's extension events (agent loop, streaming,
504
- // tool executions); rendered as the widget's first line. Change-guarded so
505
- // per-token streaming deltas do not flood subscribers.
506
-
507
- setMainModel(model?: string): void {
508
- if (!model || this.mainModel === model) return;
509
- this.mainModel = model;
510
- this.notify();
511
- }
512
-
513
- setMainThinking(thinking?: string): void {
514
- if (!thinking || this.mainThinking === thinking) return;
515
- this.mainThinking = thinking;
516
- this.notify();
517
- }
518
-
519
- setMainActivity(text: string): void {
520
- const activity = sanitizeActivityText(text) || undefined;
521
- if (!activity || this.mainActivity === activity) return;
522
- this.mainActivity = activity;
523
- this.notify();
524
- }
525
-
526
- /** Record the main model starting a tool; the activity shows the tool's
527
- * most telling argument, same vocabulary as subagent rows. */
528
- recordMainToolStart(toolName: string, activity: string): void {
529
- const safeToolName = sanitizeActivityText(toolName) || "tool";
530
- this.setMainActivity(activity || safeToolName);
531
- }
532
-
533
- /** Record a failed main-model tool; successful completions keep their last
534
- * activity until the next model event supplies a better description. */
535
- recordMainToolEnd(toolName: string, isError: boolean): void {
536
- if (isError) this.setMainActivity(`✗ ${sanitizeActivityText(toolName) || "tool"} failed`);
537
- }
538
-
539
- /** Track the parent agent loop: started at agent_start, cleared when the
540
- * loop settles (agent_end / agent_settled). */
541
- setMainAgentActive(active: boolean): void {
542
- if ((this.mainActiveSince !== undefined) === active) return;
543
- this.mainActiveSince = active ? Date.now() : undefined;
544
- this.mainActivity = undefined;
545
- this.notify();
546
- }
547
-
548
- /** Live view of the parent model while its agent loop runs; undefined when idle. */
549
- getMainActivity(): MainActivity | undefined {
550
- if (this.mainActiveSince === undefined) return undefined;
551
- return {
552
- ...(this.mainModel ? { model: this.mainModel } : {}),
553
- ...(this.mainThinking ? { thinking: this.mainThinking } : {}),
554
- ...(this.mainActivity ? { activity: this.mainActivity } : {}),
555
- activeSince: this.mainActiveSince,
556
- };
557
- }
558
-
559
- isMainAgentActive(): boolean {
560
- return this.mainActiveSince !== undefined;
561
- }
562
457
 
563
458
  beginTurn(): void {
564
459
  // Clear finished runs from a previous turn, but keep active and parked
@@ -608,9 +503,6 @@ export class MonitorStore {
608
503
  waitReason: meta?.waitReason ?? "process-slot",
609
504
  usage: emptyUsage(),
610
505
  elapsedMs: 0,
611
- ...(meta?.groupId ? { groupId: meta.groupId } : {}),
612
- ...(meta?.relationLabel ? { relationLabel: meta.relationLabel } : {}),
613
- ...(meta?.parentRunId !== undefined ? { parentRunId: meta.parentRunId } : {}),
614
506
  ...(meta?.isolation ? { isolation: meta.isolation, integrationStatus: meta.isolation === "worktree" ? "pending" : undefined, ...(meta.worktreeId ? { worktreeId: meta.worktreeId } : {}) } : {}),
615
507
  ...(meta?.continuationKind ? { continuationKind: meta.continuationKind } : {}),
616
508
  });
@@ -649,27 +541,6 @@ export class MonitorStore {
649
541
  this.notify();
650
542
  }
651
543
 
652
- /** Switch a stable top-level row from one model run to workflow ownership.
653
- * The original role remains for identity; child rows show stage telemetry. */
654
- setManagedWorkflow(id: number, active: boolean): void {
655
- const run = this.find(id);
656
- if (!run) return;
657
- run.managedWorkflow = active || undefined;
658
- if (!active) run.workflowStages = undefined;
659
- this.notify();
660
- }
661
-
662
- /** Replace the live workflow projection atomically so renderers never observe
663
- * a half-updated fix/re-review plan. */
664
- setWorkflowStages(id: number, stages: readonly WorkflowStage[]): void {
665
- const run = this.find(id);
666
- if (!run) return;
667
- run.workflowStages = stages.length > 0
668
- ? stages.map((stage) => ({ ...stage }))
669
- : undefined;
670
- this.notify();
671
- }
672
-
673
544
  setUsage(id: number, usage: UsageStats, model?: string): void {
674
545
  const run = this.find(id);
675
546
  if (!run) return;
@@ -806,8 +677,6 @@ export class MonitorStore {
806
677
  run.waitReason = "process-slot";
807
678
  run.usage = emptyUsage();
808
679
  run.activity = undefined;
809
- run.managedWorkflow = undefined;
810
- run.workflowStages = undefined;
811
680
  run.activeSince = undefined;
812
681
  run.endedAt = undefined;
813
682
  run.elapsedMs = Math.max(run.elapsedMs, meta?.elapsedMs ?? 0);
@@ -825,10 +694,6 @@ export class MonitorStore {
825
694
  * finishRun calls from the old session remain safe no-ops. */
826
695
  clear(): void {
827
696
  this.runs = [];
828
- this.mainModel = undefined;
829
- this.mainThinking = undefined;
830
- this.mainActivity = undefined;
831
- this.mainActiveSince = undefined;
832
697
  this.notify();
833
698
  }
834
699
 
@@ -854,14 +719,13 @@ export class MonitorStore {
854
719
 
855
720
  summarize(run: RunView): string {
856
721
  const usage = formatUsageCompact(run.usage);
857
- const parts = [run.managedWorkflow ? `${run.agent} workflow` : run.agent];
722
+ const parts = [run.agent];
858
723
  const continuation = continuationLabel(run.continuationKind);
859
724
  if (continuation) parts.push(continuation);
860
- if (run.relationLabel) parts.push(run.relationLabel);
861
- if (!run.managedWorkflow && run.model) parts.push(run.model);
862
- if (!run.managedWorkflow && run.thinking) parts.push(`thinking ${run.thinking}`);
725
+ if (run.model) parts.push(run.model);
726
+ if (run.thinking) parts.push(`thinking ${run.thinking}`);
863
727
  if (run.isolation === "worktree") parts.push(`worktree ${run.integrationStatus ?? "active"}`);
864
- if (!run.managedWorkflow && usage) parts.push(usage);
728
+ if (usage) parts.push(usage);
865
729
  const elapsed = formatElapsed(run);
866
730
  if (elapsed) parts.push(elapsed);
867
731
  return parts.join(" · ");
package/src/prompt.ts CHANGED
@@ -20,38 +20,21 @@ export function buildDelegationDirective(
20
20
 
21
21
  const catalog = agents.map(formatCatalogEntry).join("\n");
22
22
  const hasExplorer = agents.some((agent) => agent.name === "explorer");
23
- const hasWorker = agents.some((agent) => agent.name === "worker");
24
- const hasCleaner = agents.some((agent) => agent.name === "cleaner");
25
- const hasDocumenter = agents.some((agent) => agent.name === "documenter");
26
- const hasReviewer = agents.some((agent) => agent.name === "reviewer");
27
- const hasSynthesizer = agents.some((agent) => agent.name === "synthesizer");
28
- const codeWriterNames = [
29
- ...(hasWorker ? ["worker"] : []),
30
- ...(hasCleaner ? ["cleaner"] : []),
31
- ];
23
+ const hasExecutor = agents.some((agent) => agent.name === "executor");
32
24
 
33
25
  const dispatchRules = [
34
- `Delegate aggressively: child contexts are cheap, yours is scarce. Inline only trivial work — a lookup, a single focused edit, an answer already in context${hasWorker ? "; default every non-trivial implementation, fix, refactor, or test task to `worker`" : ""}.`,
26
+ `Delegate aggressively: child contexts are cheap, yours is scarce. Inline only trivial work — a lookup, a single focused edit, an answer already in context${hasExecutor ? "; default every non-trivial delegated task (implementation, fix, refactor, test, cleanup, docs sync, result merging) to `executor`" : ""}.`,
35
27
  ...(hasExplorer
36
28
  ? [
37
29
  "`explorer`: split a broad question into parallel explorers with disjoint scopes. Its findings are leads, never proof — re-read load-bearing files before acting yourself (a child you brief re-verifies).",
38
30
  ]
39
31
  : []),
40
- ...(hasCleaner
41
- ? ["`cleaner`: dispatch for requested cleanup AND proactively when finished work leaves dead code or duplication. Your brief is its edit authorization — every safe proven cut applies without per-item approval; never a gate. Scope: the uncommitted diff, or whatever your brief names (Git range, directory)."]
42
- : []),
43
- ...(hasDocumenter
44
- ? ["`documenter`: standalone docs/comment work; dispatch it proactively when a change — yours or a child's — leaves README/docs/comment drift no writer already synced; cheap and may make zero edits."]
45
- : []),
46
- ...(hasReviewer
32
+ ...(hasExecutor
47
33
  ? [
48
- `\`reviewer\`: read-only assessments and gates${codeWriterNames.length > 0 ? `; successful ${codeWriterNames.join("/")} runs get one fresh gate, and failing gates are fixed by the reviewer itself in bounded fix/re-review rounds (a still-failing gate returns to you). Pass \`review: "none"\` for mechanical, low-risk edits you verify yourself; keep the default gate whenever behavior can change` : ""}. Advisory output has no VERDICT and cannot authorize edits.`,
34
+ "`executor`: brief it as the edit authorization. For cleanup, name the scope (uncommitted diff, Git range, directory) every safe proven cut applies without per-item approval; finding no safe cut is a valid result. After a wide fan-out, pass the result-artifact paths to one executor and read its merged brief instead of every result yourself.",
49
35
  ]
50
36
  : []),
51
- ...(hasSynthesizer
52
- ? ["`synthesizer`: after a wide fan-out, pass the result-artifact paths to one synthesizer and read its brief instead of every result yourself."]
53
- : []),
54
- `Parallelize by default: map the todo list onto ONE \`tasks\` dispatch. One child owns one deliverable and its files; only genuinely dependent work waits for its prerequisite.`,
37
+ "Parallelize by default: map the todo list onto ONE `tasks` dispatch. One child owns one deliverable and its files; only genuinely dependent work waits for its prerequisite.",
55
38
  "Brief each child completely — goal, exact paths, constraints, expected output; it has no conversation memory and cannot delegate. Resume parked threads with `subagent_control resume`.",
56
39
  ];
57
40
 
@@ -63,13 +46,7 @@ export function buildDelegationDirective(
63
46
 
64
47
  const verificationRules = [
65
48
  "Never report an unrun check as passed; surface unavailable checks and pre-existing failures, and inspect actual changes before reporting completion.",
66
- ...(hasReviewer
67
- ? [
68
- "A REVIEW_FAIL from a gate you dispatched directly returns its findings to you: fix them inline or via a briefed worker without waiting for the user, then re-verify ONCE. If the gate still fails, report the remaining findings and move on — never loop gate dispatches.",
69
- "Multi-model cross-review only when explicitly requested or for high-risk security, FFI, migration, or concurrency changes.",
70
- ]
71
- : []),
72
- "Commit or push only when explicitly requested, applicable checks pass, and no review finding remains unresolved.",
49
+ "Commit or push only when explicitly requested and applicable checks pass.",
73
50
  ];
74
51
 
75
52
  return `
@@ -86,6 +63,6 @@ ${bullets(dispatchRules)}
86
63
  Result handoff:
87
64
  ${bullets(handoffRules)}
88
65
 
89
- Review and verification:
66
+ Verification:
90
67
  ${bullets(verificationRules)}`;
91
68
  }