pi-crew 0.1.45 → 0.1.46

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.
Files changed (198) hide show
  1. package/README.md +5 -5
  2. package/agents/analyst.md +1 -1
  3. package/agents/critic.md +1 -1
  4. package/agents/executor.md +1 -1
  5. package/agents/explorer.md +1 -1
  6. package/agents/planner.md +1 -1
  7. package/agents/reviewer.md +1 -1
  8. package/agents/security-reviewer.md +1 -1
  9. package/agents/test-engineer.md +1 -1
  10. package/agents/verifier.md +1 -1
  11. package/agents/writer.md +1 -1
  12. package/docs/next-upgrade-roadmap.md +733 -0
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research-awesome-agent-skills-distillation.md +100 -0
  18. package/docs/research-extension-examples.md +297 -297
  19. package/docs/research-extension-system.md +324 -324
  20. package/docs/research-oh-my-pi-distillation.md +322 -0
  21. package/docs/research-optimization-plan.md +548 -548
  22. package/docs/research-phase10-distillation.md +198 -198
  23. package/docs/research-phase11-distillation.md +201 -201
  24. package/docs/research-pi-coding-agent.md +357 -357
  25. package/docs/research-source-pi-crew-reference.md +174 -174
  26. package/docs/runtime-flow.md +148 -148
  27. package/docs/source-runtime-refactor-map.md +107 -83
  28. package/docs/usage.md +3 -3
  29. package/index.ts +6 -6
  30. package/install.mjs +52 -8
  31. package/package.json +1 -1
  32. package/schema.json +2 -1
  33. package/skills/async-worker-recovery/SKILL.md +42 -0
  34. package/skills/context-artifact-hygiene/SKILL.md +52 -0
  35. package/skills/delegation-patterns/SKILL.md +54 -0
  36. package/skills/mailbox-interactive/SKILL.md +40 -0
  37. package/skills/model-routing-context/SKILL.md +39 -0
  38. package/skills/multi-perspective-review/SKILL.md +58 -0
  39. package/skills/observability-reliability/SKILL.md +41 -0
  40. package/skills/ownership-session-security/SKILL.md +41 -0
  41. package/skills/pi-extension-lifecycle/SKILL.md +39 -0
  42. package/skills/requirements-to-task-packet/SKILL.md +63 -0
  43. package/skills/resource-discovery-config/SKILL.md +41 -0
  44. package/skills/runtime-state-reader/SKILL.md +44 -0
  45. package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
  46. package/skills/state-mutation-locking/SKILL.md +42 -0
  47. package/skills/systematic-debugging/SKILL.md +67 -0
  48. package/skills/ui-render-performance/SKILL.md +39 -0
  49. package/skills/verification-before-done/SKILL.md +57 -0
  50. package/skills/worktree-isolation/SKILL.md +39 -0
  51. package/src/agents/agent-serializer.ts +34 -34
  52. package/src/agents/discover-agents.ts +12 -11
  53. package/src/config/config.ts +48 -24
  54. package/src/config/defaults.ts +14 -0
  55. package/src/extension/cross-extension-rpc.ts +82 -82
  56. package/src/extension/project-init.ts +62 -2
  57. package/src/extension/register.ts +11 -9
  58. package/src/extension/registration/commands.ts +32 -25
  59. package/src/extension/registration/compaction-guard.ts +125 -125
  60. package/src/extension/registration/subagent-helpers.ts +8 -0
  61. package/src/extension/registration/subagent-tools.ts +149 -148
  62. package/src/extension/registration/team-tool.ts +8 -6
  63. package/src/extension/run-bundle-schema.ts +89 -89
  64. package/src/extension/run-index.ts +13 -5
  65. package/src/extension/run-maintenance.ts +62 -43
  66. package/src/extension/team-tool/api.ts +25 -8
  67. package/src/extension/team-tool/cancel.ts +33 -4
  68. package/src/extension/team-tool/context.ts +5 -0
  69. package/src/extension/team-tool/handle-settings.ts +188 -188
  70. package/src/extension/team-tool/inspect.ts +41 -41
  71. package/src/extension/team-tool/lifecycle-actions.ts +91 -79
  72. package/src/extension/team-tool/plan.ts +19 -19
  73. package/src/extension/team-tool/respond.ts +37 -17
  74. package/src/extension/team-tool/run.ts +52 -10
  75. package/src/extension/team-tool/status.ts +12 -1
  76. package/src/extension/team-tool-types.ts +2 -0
  77. package/src/extension/team-tool.ts +32 -11
  78. package/src/i18n.ts +184 -184
  79. package/src/observability/event-to-metric.ts +8 -1
  80. package/src/observability/exporters/otlp-exporter.ts +77 -77
  81. package/src/prompt/prompt-runtime.ts +72 -72
  82. package/src/runtime/agent-control.ts +63 -63
  83. package/src/runtime/agent-memory.ts +72 -72
  84. package/src/runtime/agent-observability.ts +114 -114
  85. package/src/runtime/async-marker.ts +26 -26
  86. package/src/runtime/attention-events.ts +28 -28
  87. package/src/runtime/background-runner.ts +59 -53
  88. package/src/runtime/cancellation.ts +51 -0
  89. package/src/runtime/child-pi.ts +457 -444
  90. package/src/runtime/completion-guard.ts +190 -190
  91. package/src/runtime/crash-recovery.ts +1 -0
  92. package/src/runtime/crew-agent-records.ts +38 -6
  93. package/src/runtime/deadletter.ts +1 -0
  94. package/src/runtime/delivery-coordinator.ts +46 -25
  95. package/src/runtime/direct-run.ts +35 -35
  96. package/src/runtime/effectiveness.ts +76 -0
  97. package/src/runtime/foreground-control.ts +82 -82
  98. package/src/runtime/green-contract.ts +46 -46
  99. package/src/runtime/group-join.ts +106 -106
  100. package/src/runtime/heartbeat-gradient.ts +28 -28
  101. package/src/runtime/heartbeat-watcher.ts +124 -124
  102. package/src/runtime/live-agent-control.ts +88 -87
  103. package/src/runtime/live-agent-manager.ts +103 -85
  104. package/src/runtime/live-control-realtime.ts +36 -36
  105. package/src/runtime/live-session-runtime.ts +309 -305
  106. package/src/runtime/manifest-cache.ts +17 -2
  107. package/src/runtime/model-fallback.ts +6 -4
  108. package/src/runtime/parallel-research.ts +44 -44
  109. package/src/runtime/pi-args.ts +18 -3
  110. package/src/runtime/pi-json-output.ts +111 -111
  111. package/src/runtime/policy-engine.ts +79 -79
  112. package/src/runtime/process-status.ts +5 -1
  113. package/src/runtime/progress-event-coalescer.ts +43 -43
  114. package/src/runtime/recovery-recipes.ts +74 -74
  115. package/src/runtime/retry-executor.ts +81 -64
  116. package/src/runtime/role-permission.ts +39 -39
  117. package/src/runtime/runtime-resolver.ts +22 -6
  118. package/src/runtime/session-resources.ts +25 -25
  119. package/src/runtime/session-snapshot.ts +59 -59
  120. package/src/runtime/session-usage.ts +79 -79
  121. package/src/runtime/sidechain-output.ts +29 -29
  122. package/src/runtime/skill-instructions.ts +222 -0
  123. package/src/runtime/stale-reconciler.ts +4 -14
  124. package/src/runtime/subagent-manager.ts +3 -0
  125. package/src/runtime/supervisor-contact.ts +59 -59
  126. package/src/runtime/task-display.ts +38 -38
  127. package/src/runtime/task-output-context.ts +127 -127
  128. package/src/runtime/task-runner/capabilities.ts +78 -0
  129. package/src/runtime/task-runner/live-executor.ts +105 -101
  130. package/src/runtime/task-runner/progress.ts +119 -119
  131. package/src/runtime/task-runner/prompt-builder.ts +3 -1
  132. package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
  133. package/src/runtime/task-runner/result-utils.ts +14 -14
  134. package/src/runtime/task-runner/state-helpers.ts +22 -22
  135. package/src/runtime/task-runner.ts +44 -5
  136. package/src/runtime/team-runner.ts +78 -15
  137. package/src/runtime/worker-heartbeat.ts +21 -21
  138. package/src/runtime/worker-startup.ts +57 -57
  139. package/src/schema/config-schema.ts +1 -0
  140. package/src/schema/team-tool-schema.ts +3 -3
  141. package/src/state/active-run-registry.ts +165 -0
  142. package/src/state/contracts.ts +1 -1
  143. package/src/state/mailbox.ts +44 -4
  144. package/src/state/state-store.ts +8 -1
  145. package/src/state/task-claims.ts +44 -44
  146. package/src/state/types.ts +44 -2
  147. package/src/state/usage.ts +29 -29
  148. package/src/subagents/async-entry.ts +1 -1
  149. package/src/subagents/index.ts +3 -3
  150. package/src/subagents/live/control.ts +1 -1
  151. package/src/subagents/live/manager.ts +1 -1
  152. package/src/subagents/live/realtime.ts +1 -1
  153. package/src/subagents/live/session-runtime.ts +1 -1
  154. package/src/subagents/manager.ts +1 -1
  155. package/src/subagents/spawn.ts +1 -1
  156. package/src/teams/team-config.ts +1 -0
  157. package/src/teams/team-serializer.ts +38 -38
  158. package/src/types/diff.d.ts +18 -18
  159. package/src/ui/crew-footer.ts +101 -101
  160. package/src/ui/crew-select-list.ts +111 -111
  161. package/src/ui/crew-widget.ts +4 -3
  162. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  163. package/src/ui/dashboard-panes/progress-pane.ts +2 -0
  164. package/src/ui/dynamic-border.ts +25 -25
  165. package/src/ui/layout-primitives.ts +106 -106
  166. package/src/ui/loaders.ts +158 -158
  167. package/src/ui/render-diff.ts +119 -119
  168. package/src/ui/render-scheduler.ts +143 -143
  169. package/src/ui/run-snapshot-cache.ts +10 -2
  170. package/src/ui/snapshot-types.ts +2 -0
  171. package/src/ui/spinner.ts +17 -17
  172. package/src/ui/status-colors.ts +58 -58
  173. package/src/ui/syntax-highlight.ts +116 -116
  174. package/src/utils/atomic-write.ts +33 -33
  175. package/src/utils/completion-dedupe.ts +63 -63
  176. package/src/utils/frontmatter.ts +68 -68
  177. package/src/utils/git.ts +262 -262
  178. package/src/utils/ids.ts +12 -12
  179. package/src/utils/names.ts +27 -27
  180. package/src/utils/paths.ts +4 -2
  181. package/src/utils/redaction.ts +44 -44
  182. package/src/utils/safe-paths.ts +47 -47
  183. package/src/utils/sleep.ts +32 -32
  184. package/src/workflows/validate-workflow.ts +40 -40
  185. package/src/workflows/workflow-config.ts +1 -0
  186. package/src/worktree/branch-freshness.ts +45 -45
  187. package/teams/default.team.md +12 -12
  188. package/teams/fast-fix.team.md +11 -11
  189. package/teams/implementation.team.md +18 -18
  190. package/teams/parallel-research.team.md +14 -14
  191. package/teams/research.team.md +11 -11
  192. package/teams/review.team.md +12 -12
  193. package/workflows/default.workflow.md +29 -29
  194. package/workflows/fast-fix.workflow.md +22 -22
  195. package/workflows/implementation.workflow.md +38 -38
  196. package/workflows/parallel-research.workflow.md +46 -46
  197. package/workflows/research.workflow.md +22 -22
  198. package/workflows/review.workflow.md +30 -30
@@ -1,43 +1,62 @@
1
- import * as fs from "node:fs";
2
- import type { TeamRunManifest } from "../state/types.ts";
3
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
4
- import { projectCrewRoot } from "../utils/paths.ts";
5
- import { listRuns } from "./run-index.ts";
6
- import { logInternalError } from "../utils/internal-error.ts";
7
-
8
- export interface PruneRunsResult {
9
- kept: string[];
10
- removed: string[];
11
- }
12
-
13
- function isFinished(run: TeamRunManifest): boolean {
14
- return run.status === "completed" || run.status === "failed" || run.status === "cancelled" || run.status === "blocked";
15
- }
16
-
17
- function isSafeToPrune(cwd: string, run: TeamRunManifest): boolean {
18
- try {
19
- const crewRoot = projectCrewRoot(cwd);
20
- resolveRealContainedPath(crewRoot, run.stateRoot);
21
- resolveRealContainedPath(crewRoot, run.artifactsRoot);
22
- return true;
23
- } catch {
24
- return false;
25
- }
26
- }
27
-
28
- export function pruneFinishedRuns(cwd: string, keep: number): PruneRunsResult {
29
- const crewRoot = projectCrewRoot(cwd);
30
- const finished = listRuns(cwd).filter((run) => run.cwd === cwd && isFinished(run)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
31
- const kept = finished.slice(0, keep).map((run) => run.runId);
32
- const removed: string[] = [];
33
- for (const run of finished.slice(keep)) {
34
- if (!isSafeToPrune(cwd, run)) {
35
- logInternalError("prune.path-unsafe", new Error(`Skipping unsafe prune: stateRoot=${run.stateRoot}, artifactsRoot=${run.artifactsRoot}`), `runId=${run.runId}`);
36
- continue;
37
- }
38
- fs.rmSync(run.stateRoot, { recursive: true, force: true });
39
- fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
40
- removed.push(run.runId);
41
- }
42
- return { kept, removed };
43
- }
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { TeamRunManifest } from "../state/types.ts";
4
+ import { resolveRealContainedPath } from "../utils/safe-paths.ts";
5
+ import { projectCrewRoot } from "../utils/paths.ts";
6
+ import { listRuns } from "./run-index.ts";
7
+ import { logInternalError } from "../utils/internal-error.ts";
8
+ import { redactSecrets } from "../utils/redaction.ts";
9
+
10
+ export interface PruneRunsResult {
11
+ kept: string[];
12
+ removed: string[];
13
+ auditPath?: string;
14
+ }
15
+
16
+ export interface PruneRunsOptions {
17
+ intent?: string;
18
+ }
19
+
20
+ function isFinished(run: TeamRunManifest): boolean {
21
+ return run.status === "completed" || run.status === "failed" || run.status === "cancelled" || run.status === "blocked";
22
+ }
23
+
24
+ function isSafeToPrune(cwd: string, run: TeamRunManifest): boolean {
25
+ try {
26
+ const crewRoot = projectCrewRoot(cwd);
27
+ resolveRealContainedPath(crewRoot, run.stateRoot);
28
+ resolveRealContainedPath(crewRoot, run.artifactsRoot);
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function appendPruneAudit(cwd: string, payload: Record<string, unknown>): string | undefined {
36
+ try {
37
+ const filePath = path.join(projectCrewRoot(cwd), "audit", "prune.jsonl");
38
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
39
+ fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ ...payload, auditedAt: new Date().toISOString() }))}\n`, "utf-8");
40
+ return filePath;
41
+ } catch (error) {
42
+ logInternalError("prune.audit-write", error, `cwd=${cwd}`);
43
+ return undefined;
44
+ }
45
+ }
46
+
47
+ export function pruneFinishedRuns(cwd: string, keep: number, options: PruneRunsOptions = {}): PruneRunsResult {
48
+ const finished = listRuns(cwd).filter((run) => run.cwd === cwd && isFinished(run)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
49
+ const kept = finished.slice(0, keep).map((run) => run.runId);
50
+ const removed: string[] = [];
51
+ for (const run of finished.slice(keep)) {
52
+ if (!isSafeToPrune(cwd, run)) {
53
+ logInternalError("prune.path-unsafe", new Error(`Skipping unsafe prune: stateRoot=${run.stateRoot}, artifactsRoot=${run.artifactsRoot}`), `runId=${run.runId}`);
54
+ continue;
55
+ }
56
+ fs.rmSync(run.stateRoot, { recursive: true, force: true });
57
+ fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
58
+ removed.push(run.runId);
59
+ }
60
+ const auditPath = appendPruneAudit(cwd, { action: "prune", keep, intent: options.intent, kept, removed });
61
+ return { kept, removed, auditPath };
62
+ }
@@ -5,7 +5,7 @@ import { loadRunManifestById, saveRunManifest, saveRunTasks, updateRunStatus } f
5
5
  import { withRunLockSync } from "../../state/locks.ts";
6
6
  import { canTransitionTaskStatus, isTeamTaskStatus } from "../../state/contracts.ts";
7
7
  import { claimTask, releaseTaskClaim, transitionClaimedTaskStatus } from "../../state/task-claims.ts";
8
- import { acknowledgeMailboxMessage, appendMailboxMessage, readDeliveryState, readMailbox, readMailboxMessage, validateMailbox, type MailboxDirection } from "../../state/mailbox.ts";
8
+ import { acknowledgeMailboxMessage, appendFollowUpMessage, appendMailboxMessage, appendSteeringMessage, readDeliveryState, readMailbox, readMailboxMessage, validateMailbox, type MailboxDirection } from "../../state/mailbox.ts";
9
9
  import { appendEvent, readEvents, readEventsCursor } from "../../state/event-log.ts";
10
10
  import { resolveCrewRuntime } from "../../runtime/runtime-resolver.ts";
11
11
  import { probeLiveSessionRuntime } from "../../subagents/live/session-runtime.ts";
@@ -14,7 +14,7 @@ import { touchWorkerHeartbeat } from "../../runtime/worker-heartbeat.ts";
14
14
  import { agentOutputPath, readCrewAgentEventsCursor, readCrewAgentStatus, readCrewAgents } from "../../runtime/crew-agent-records.ts";
15
15
  import { buildAgentDashboard, readAgentOutput } from "../../runtime/agent-observability.ts";
16
16
  import { readForegroundControlStatus, writeForegroundInterruptRequest } from "../../runtime/foreground-control.ts";
17
- import { getLiveAgent, listLiveAgents, resumeLiveAgent, steerLiveAgent, stopLiveAgent } from "../../subagents/live/manager.ts";
17
+ import { followUpLiveAgent, getLiveAgent, listLiveAgents, resumeLiveAgent, steerLiveAgent, stopLiveAgent } from "../../subagents/live/manager.ts";
18
18
  import { appendLiveAgentControlRequest } from "../../subagents/live/control.ts";
19
19
  import { liveControlRealtimeMessage, publishLiveControlRealtime } from "../../subagents/live/realtime.ts";
20
20
  import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
@@ -212,7 +212,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
212
212
  const agent = readCrewAgents(loaded.manifest).find((item) => item.id === agentId || item.taskId === agentId);
213
213
  if (!agent) return result("API nudge-agent requires config.agentId matching an agent id or task id.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
214
214
  const messageText = typeof cfg.message === "string" && cfg.message.trim() ? cfg.message.trim() : "Please report your current status, blocker, or smallest next step.";
215
- const message = appendMailboxMessage(loaded.manifest, { direction: "inbox", from: "leader", to: agent.taskId, taskId: agent.taskId, body: messageText });
215
+ const message = appendSteeringMessage(loaded.manifest, { taskId: agent.taskId, to: agent.taskId, body: messageText, priority: "normal", data: { source: "nudge-agent" } });
216
216
  appendEvent(loaded.manifest.eventsPath, { type: "agent.nudged", runId: loaded.manifest.runId, taskId: agent.taskId, message: messageText, data: { agentId: agent.id, mailboxMessageId: message.id } });
217
217
  ctx.events?.emit?.("crew.mailbox.message", { runId: loaded.manifest.runId, id: message.id, direction: message.direction, from: message.from, to: message.to, taskId: message.taskId, source: "nudge-agent" });
218
218
  return result(JSON.stringify({ agentId: agent.id, mailboxMessage: message }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
@@ -220,7 +220,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
220
220
  if (operation === "list-live-agents") {
221
221
  return result(JSON.stringify(listLiveAgents().filter((agent) => agent.runId === loaded.manifest.runId), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
222
222
  }
223
- if (operation === "steer-agent" || operation === "stop-agent" || operation === "resume-agent" || operation === "interrupt-agent") {
223
+ if (operation === "steer-agent" || operation === "follow-up-agent" || operation === "stop-agent" || operation === "resume-agent" || operation === "interrupt-agent") {
224
224
  const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
225
225
  if (!agentId) return result(`API ${operation} requires config.agentId.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
226
226
  const message = typeof cfg.message === "string" && cfg.message.trim() ? cfg.message.trim() : undefined;
@@ -228,7 +228,22 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
228
228
  try {
229
229
  const live = getLiveAgent(agentId);
230
230
  if (live && live.runId !== loaded.manifest.runId) return result(`Live agent '${agentId}' does not belong to run ${loaded.manifest.runId}.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
231
- if (operation === "steer-agent") return result(JSON.stringify(await steerLiveAgent(agentId, message ?? "Please report current status and wrap up if possible."), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
231
+ if (!live && (operation === "steer-agent" || operation === "follow-up-agent")) throw new Error(`Live agent '${agentId}' not found.`);
232
+ const liveTaskId = live?.taskId;
233
+ if ((operation === "steer-agent" || operation === "follow-up-agent") && !liveTaskId) throw new Error(`Live agent '${agentId}' not found.`);
234
+ const targetTaskId = liveTaskId ?? agentId;
235
+ if (operation === "steer-agent") {
236
+ const text = message ?? "Please report current status and wrap up if possible.";
237
+ const realtime = await steerLiveAgent(agentId, text);
238
+ const mailboxMessage = appendSteeringMessage(loaded.manifest, { taskId: targetTaskId, body: text, status: "delivered", data: { source: "steer-agent", realtime: true } });
239
+ return result(JSON.stringify({ realtime, mailboxMessage }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
240
+ }
241
+ if (operation === "follow-up-agent") {
242
+ if (!prompt) return result("API follow-up-agent requires config.prompt or config.message.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
243
+ const realtime = await followUpLiveAgent(agentId, prompt);
244
+ const mailboxMessage = appendFollowUpMessage(loaded.manifest, { taskId: targetTaskId, body: prompt, status: "delivered", data: { source: "follow-up-agent", realtime: true } });
245
+ return result(JSON.stringify({ realtime, mailboxMessage }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
246
+ }
232
247
  if (operation === "resume-agent") {
233
248
  if (!prompt) return result("API resume-agent requires config.prompt or config.message.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
234
249
  return result(JSON.stringify(await resumeLiveAgent(agentId, prompt), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
@@ -243,12 +258,14 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
243
258
  const task = loaded.tasks.find((item) => item.id === agent.taskId);
244
259
  if (!task) return result(`API ${operation} agent '${agentId}' does not match a run task.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
245
260
  if (operation === "resume-agent" && !prompt) return result("API resume-agent requires config.prompt or config.message.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
261
+ if (operation === "follow-up-agent" && !prompt) return result("API follow-up-agent requires config.prompt or config.message.", { action: "api", status: "error", runId: loaded.manifest.runId }, true);
246
262
  try {
247
- const request = appendLiveAgentControlRequest(loaded.manifest, { taskId: task.id, agentId: agent.id, operation: operation === "resume-agent" ? "resume" : operation === "steer-agent" ? "steer" : "stop", message: operation === "resume-agent" ? prompt : message });
263
+ const request = appendLiveAgentControlRequest(loaded.manifest, { taskId: task.id, agentId: agent.id, operation: operation === "resume-agent" ? "resume" : operation === "follow-up-agent" ? "follow-up" : operation === "steer-agent" ? "steer" : "stop", message: operation === "resume-agent" || operation === "follow-up-agent" ? prompt : message });
264
+ const mailboxMessage = operation === "steer-agent" ? appendSteeringMessage(loaded.manifest, { taskId: task.id, to: agent.id, body: message ?? "Please report current status and wrap up if possible.", status: "delivered", data: { source: "steer-agent", liveControlRequestId: request.id } }) : operation === "follow-up-agent" && prompt ? appendFollowUpMessage(loaded.manifest, { taskId: task.id, to: agent.id, body: prompt, status: "delivered", data: { source: "follow-up-agent", liveControlRequestId: request.id } }) : undefined;
248
265
  publishLiveControlRealtime(request);
249
266
  ctx.events?.emit?.("pi-crew:live-control", liveControlRealtimeMessage(request));
250
- appendEvent(loaded.manifest.eventsPath, { type: "agent.control.queued", runId: loaded.manifest.runId, taskId: agent.taskId, message: `Queued ${request.operation} control request for live agent.`, data: { request, realtime: true } });
251
- return result(JSON.stringify({ queued: true, request }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
267
+ appendEvent(loaded.manifest.eventsPath, { type: "agent.control.queued", runId: loaded.manifest.runId, taskId: agent.taskId, message: `Queued ${request.operation} control request for live agent.`, data: { request, mailboxMessageId: mailboxMessage?.id, realtime: true } });
268
+ return result(JSON.stringify({ queued: true, request, mailboxMessage }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
252
269
  } catch (queueError) {
253
270
  const message = queueError instanceof Error ? queueError.message : String(queueError);
254
271
  return result(message, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
@@ -3,6 +3,8 @@ import { withRunLockSync } from "../../state/locks.ts";
3
3
  import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
4
4
  import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
5
5
  import { writeForegroundInterruptRequest } from "../../runtime/foreground-control.ts";
6
+ import { cancellationReasonFromUnknown } from "../../runtime/cancellation.ts";
7
+ import { appendEvent } from "../../state/event-log.ts";
6
8
  import { logInternalError } from "../../utils/internal-error.ts";
7
9
  import type { PiTeamsToolResult } from "../tool-result.ts";
8
10
  import { result, type TeamContext } from "./context.ts";
@@ -55,6 +57,25 @@ export function abortOwned(
55
57
  return result;
56
58
  }
57
59
 
60
+ function configFromParams(params: TeamToolParamsValue): Record<string, unknown> | undefined {
61
+ return params.config && typeof params.config === "object" && !Array.isArray(params.config) ? params.config : undefined;
62
+ }
63
+
64
+ function cancelReasonFromParams(params: TeamToolParamsValue): { code: string; message: string } {
65
+ const config = configFromParams(params);
66
+ const rawReason = config?.reason ?? config?.cancelReason;
67
+ const reason = rawReason === undefined ? { code: "caller_cancelled" as const, message: "Run cancelled by user request." } : cancellationReasonFromUnknown(rawReason);
68
+ return { code: reason.code, message: reason.message };
69
+ }
70
+
71
+ function intentFromParams(params: TeamToolParamsValue): string | undefined {
72
+ const config = configFromParams(params);
73
+ const rawIntent = config?.intent ?? config?._intent;
74
+ if (typeof rawIntent !== "string") return undefined;
75
+ const intent = rawIntent.replace(/\s+/g, " ").trim();
76
+ return intent ? intent.slice(0, 500) : undefined;
77
+ }
78
+
58
79
  export function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
59
80
  if (!params.runId) return result("Cancel requires runId.", { action: "cancel", status: "error" }, true);
60
81
  const loaded = loadRunManifestById(ctx.cwd, params.runId);
@@ -65,13 +86,17 @@ export function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): PiT
65
86
  // Classify tasks for foreign-aware cancellation
66
87
  const abortResult = abortOwned(loaded.manifest.runId, undefined, ctx);
67
88
  if (abortResult.abortedIds.length === 0 && abortResult.foreignIds.length > 0) {
68
- return result(`Run ${loaded.manifest.runId} belongs to another session; not cancelled.`, { action: "cancel", status: "error", runId: loaded.manifest.runId, foreignIds: abortResult.foreignIds } as never, true);
89
+ return result(`Run ${loaded.manifest.runId} belongs to another session; not cancelled.`, { action: "cancel", status: "error", runId: loaded.manifest.runId, foreignIds: abortResult.foreignIds }, true);
69
90
  }
70
91
  const cancellableIds = new Set(abortResult.abortedIds);
92
+ const cancelReason = cancelReasonFromParams(params);
93
+ const cancelIntent = intentFromParams(params);
94
+ const cancelData = cancelIntent ? { reason: cancelReason.code, intent: cancelIntent } : { reason: cancelReason.code };
95
+ const cancelMessage = `${cancelReason.message} (${cancelReason.code})`;
71
96
 
72
97
  const tasks = loaded.tasks.map((task) => {
73
98
  if (cancellableIds.has(task.id) && (task.status === "queued" || task.status === "running" || task.status === "waiting")) {
74
- return { ...task, status: "cancelled" as const, finishedAt: new Date().toISOString(), error: "Run cancelled by user request." };
99
+ return { ...task, status: "cancelled" as const, finishedAt: new Date().toISOString(), error: cancelMessage };
75
100
  }
76
101
  return task;
77
102
  });
@@ -82,11 +107,14 @@ export function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): PiT
82
107
  logInternalError("team-tool.handleCancel.crewAgents", error, `runId=${loaded.manifest.runId}`);
83
108
  }
84
109
  try {
85
- writeForegroundInterruptRequest(loaded.manifest, "Run cancelled by user request.");
110
+ writeForegroundInterruptRequest(loaded.manifest, cancelMessage);
86
111
  } catch (error) {
87
112
  logInternalError("team-tool.handleCancel.interruptRequest", error, `runId=${loaded.manifest.runId}`);
88
113
  }
89
- const updated = updateRunStatus(loaded.manifest, "cancelled", "Run cancelled by user request. Already-finished worker processes are not retroactively changed.");
114
+ for (const taskId of abortResult.abortedIds) {
115
+ appendEvent(loaded.manifest.eventsPath, { type: "task.cancelled", runId: loaded.manifest.runId, taskId, message: cancelMessage, data: cancelData });
116
+ }
117
+ const updated = updateRunStatus(loaded.manifest, "cancelled", `${cancelMessage} Already-finished worker processes are not retroactively changed.`, { data: cancelData });
90
118
 
91
119
  // Build descriptive message including foreign/missing info
92
120
  const parts = [`Cancelled run ${updated.runId}.`];
@@ -101,6 +129,7 @@ export function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): PiT
101
129
  abortedIds: abortResult.abortedIds,
102
130
  missingIds: abortResult.missingIds,
103
131
  foreignIds: abortResult.foreignIds,
132
+ intent: cancelIntent,
104
133
  });
105
134
  });
106
135
  }
@@ -15,6 +15,11 @@ export type TeamContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<Extension
15
15
  onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
16
16
  };
17
17
 
18
+ export function withSessionId<T extends Pick<ExtensionContext, "sessionManager">>(ctx: T): T & { sessionId?: string } {
19
+ const sessionId = ctx.sessionManager.getSessionId();
20
+ return sessionId ? { ...ctx, sessionId } : { ...ctx };
21
+ }
22
+
18
23
  export function result(text: string, details: TeamToolDetails, isError = false): PiTeamsToolResult {
19
24
  return toolResult(text, details, isError);
20
25
  }