pi-crew 0.1.46 โ†’ 0.1.49

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 (253) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/analyst.md +11 -11
  3. package/agents/critic.md +11 -11
  4. package/agents/executor.md +11 -11
  5. package/agents/explorer.md +11 -11
  6. package/agents/planner.md +11 -11
  7. package/agents/reviewer.md +11 -11
  8. package/agents/security-reviewer.md +11 -11
  9. package/agents/test-engineer.md +11 -11
  10. package/agents/verifier.md +11 -11
  11. package/agents/writer.md +11 -11
  12. package/docs/next-upgrade-roadmap.md +117 -42
  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/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  18. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  19. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  20. package/docs/research/AUDIT_PI_CREW.md +457 -0
  21. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  22. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  23. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  24. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  25. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  26. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  27. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  28. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  29. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  30. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  31. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  32. package/docs/research-awesome-agent-skills-distillation.md +100 -100
  33. package/docs/research-extension-examples.md +297 -297
  34. package/docs/research-extension-system.md +324 -324
  35. package/docs/research-oh-my-pi-distillation.md +56 -9
  36. package/docs/research-optimization-plan.md +548 -548
  37. package/docs/research-phase10-distillation.md +198 -198
  38. package/docs/research-phase11-distillation.md +201 -201
  39. package/docs/research-pi-coding-agent.md +357 -357
  40. package/docs/research-source-pi-crew-reference.md +174 -174
  41. package/docs/runtime-flow.md +148 -148
  42. package/docs/source-runtime-refactor-map.md +107 -107
  43. package/index.ts +6 -6
  44. package/package.json +99 -98
  45. package/schema.json +8 -0
  46. package/skills/async-worker-recovery/SKILL.md +42 -42
  47. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  48. package/skills/delegation-patterns/SKILL.md +54 -54
  49. package/skills/mailbox-interactive/SKILL.md +40 -40
  50. package/skills/model-routing-context/SKILL.md +39 -39
  51. package/skills/multi-perspective-review/SKILL.md +58 -58
  52. package/skills/observability-reliability/SKILL.md +41 -41
  53. package/skills/orchestration/SKILL.md +157 -0
  54. package/skills/ownership-session-security/SKILL.md +41 -41
  55. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  56. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  57. package/skills/resource-discovery-config/SKILL.md +41 -41
  58. package/skills/runtime-state-reader/SKILL.md +44 -44
  59. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  60. package/skills/state-mutation-locking/SKILL.md +42 -42
  61. package/skills/systematic-debugging/SKILL.md +67 -67
  62. package/skills/ui-render-performance/SKILL.md +39 -39
  63. package/skills/verification-before-done/SKILL.md +57 -57
  64. package/skills/worktree-isolation/SKILL.md +39 -39
  65. package/src/agents/agent-config.ts +6 -0
  66. package/src/agents/agent-search.ts +98 -0
  67. package/src/agents/agent-serializer.ts +4 -0
  68. package/src/agents/discover-agents.ts +17 -4
  69. package/src/config/config.ts +24 -0
  70. package/src/config/defaults.ts +11 -0
  71. package/src/extension/autonomous-policy.ts +26 -33
  72. package/src/extension/cross-extension-rpc.ts +82 -82
  73. package/src/extension/help.ts +1 -0
  74. package/src/extension/management.ts +5 -0
  75. package/src/extension/register.ts +58 -13
  76. package/src/extension/registration/commands.ts +33 -1
  77. package/src/extension/registration/compaction-guard.ts +125 -125
  78. package/src/extension/registration/team-tool.ts +6 -4
  79. package/src/extension/run-bundle-schema.ts +89 -89
  80. package/src/extension/run-index.ts +24 -18
  81. package/src/extension/run-maintenance.ts +68 -62
  82. package/src/extension/team-tool/api.ts +23 -2
  83. package/src/extension/team-tool/cancel.ts +86 -11
  84. package/src/extension/team-tool/context.ts +3 -0
  85. package/src/extension/team-tool/handle-settings.ts +188 -188
  86. package/src/extension/team-tool/inspect.ts +41 -41
  87. package/src/extension/team-tool/intent-policy.ts +42 -0
  88. package/src/extension/team-tool/lifecycle-actions.ts +47 -18
  89. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  90. package/src/extension/team-tool/plan.ts +19 -19
  91. package/src/extension/team-tool/respond.ts +10 -2
  92. package/src/extension/team-tool/run.ts +3 -2
  93. package/src/extension/team-tool/status.ts +1 -1
  94. package/src/extension/team-tool-types.ts +1 -0
  95. package/src/extension/team-tool.ts +13 -3
  96. package/src/hooks/registry.ts +61 -0
  97. package/src/hooks/types.ts +41 -0
  98. package/src/i18n.ts +184 -184
  99. package/src/observability/exporters/otlp-exporter.ts +77 -77
  100. package/src/prompt/prompt-runtime.ts +72 -72
  101. package/src/runtime/agent-control.ts +108 -2
  102. package/src/runtime/agent-memory.ts +72 -72
  103. package/src/runtime/agent-observability.ts +114 -114
  104. package/src/runtime/async-marker.ts +26 -26
  105. package/src/runtime/async-runner.ts +3 -1
  106. package/src/runtime/attention-events.ts +28 -28
  107. package/src/runtime/background-runner.ts +19 -0
  108. package/src/runtime/cancellation-token.ts +89 -0
  109. package/src/runtime/cancellation.ts +61 -51
  110. package/src/runtime/capability-inventory.ts +116 -0
  111. package/src/runtime/child-pi.ts +2 -1
  112. package/src/runtime/code-summary.ts +247 -0
  113. package/src/runtime/completion-guard.ts +190 -190
  114. package/src/runtime/crash-recovery.ts +181 -0
  115. package/src/runtime/crew-agent-records.ts +35 -7
  116. package/src/runtime/crew-agent-runtime.ts +1 -0
  117. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  118. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  119. package/src/runtime/delivery-coordinator.ts +3 -1
  120. package/src/runtime/direct-run.ts +35 -35
  121. package/src/runtime/effectiveness.ts +81 -76
  122. package/src/runtime/event-stream-bridge.ts +90 -0
  123. package/src/runtime/foreground-control.ts +82 -82
  124. package/src/runtime/green-contract.ts +46 -46
  125. package/src/runtime/group-join.ts +106 -106
  126. package/src/runtime/heartbeat-gradient.ts +28 -28
  127. package/src/runtime/heartbeat-watcher.ts +124 -124
  128. package/src/runtime/live-agent-control.ts +88 -88
  129. package/src/runtime/live-agent-manager.ts +78 -2
  130. package/src/runtime/live-control-realtime.ts +36 -36
  131. package/src/runtime/live-extension-bridge.ts +150 -0
  132. package/src/runtime/live-irc.ts +92 -0
  133. package/src/runtime/live-session-health.ts +100 -0
  134. package/src/runtime/live-session-runtime.ts +297 -7
  135. package/src/runtime/mcp-proxy.ts +113 -0
  136. package/src/runtime/notebook-helpers.ts +90 -0
  137. package/src/runtime/orphan-sentinel.ts +7 -0
  138. package/src/runtime/output-validator.ts +187 -0
  139. package/src/runtime/parallel-research.ts +44 -44
  140. package/src/runtime/parallel-utils.ts +57 -0
  141. package/src/runtime/parent-guard.ts +80 -0
  142. package/src/runtime/pi-json-output.ts +111 -111
  143. package/src/runtime/policy-engine.ts +79 -79
  144. package/src/runtime/progress-event-coalescer.ts +43 -43
  145. package/src/runtime/prose-compressor.ts +164 -0
  146. package/src/runtime/recovery-recipes.ts +74 -74
  147. package/src/runtime/result-extractor.ts +121 -0
  148. package/src/runtime/role-permission.ts +39 -39
  149. package/src/runtime/runtime-resolver.ts +1 -4
  150. package/src/runtime/semaphore.ts +131 -0
  151. package/src/runtime/sensitive-paths.ts +92 -0
  152. package/src/runtime/session-resources.ts +25 -25
  153. package/src/runtime/session-snapshot.ts +59 -59
  154. package/src/runtime/session-usage.ts +79 -79
  155. package/src/runtime/sidechain-output.ts +29 -29
  156. package/src/runtime/stream-preview.ts +177 -0
  157. package/src/runtime/subagent-manager.ts +3 -2
  158. package/src/runtime/subprocess-tool-registry.ts +67 -0
  159. package/src/runtime/supervisor-contact.ts +59 -59
  160. package/src/runtime/task-display.ts +38 -38
  161. package/src/runtime/task-output-context.ts +59 -9
  162. package/src/runtime/task-runner/capabilities.ts +78 -78
  163. package/src/runtime/task-runner/live-executor.ts +2 -0
  164. package/src/runtime/task-runner/progress.ts +119 -119
  165. package/src/runtime/task-runner/prompt-builder.ts +70 -8
  166. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  167. package/src/runtime/task-runner/result-utils.ts +14 -14
  168. package/src/runtime/task-runner/run-projection.ts +104 -0
  169. package/src/runtime/task-runner/state-helpers.ts +22 -22
  170. package/src/runtime/task-runner.ts +75 -4
  171. package/src/runtime/team-runner.ts +60 -8
  172. package/src/runtime/worker-heartbeat.ts +21 -21
  173. package/src/runtime/worker-startup.ts +57 -57
  174. package/src/runtime/workspace-tree.ts +298 -0
  175. package/src/runtime/yield-handler.ts +189 -0
  176. package/src/schema/config-schema.ts +6 -0
  177. package/src/schema/team-tool-schema.ts +11 -1
  178. package/src/skills/discover-skills.ts +67 -0
  179. package/src/state/active-run-registry.ts +4 -2
  180. package/src/state/artifact-store.ts +4 -1
  181. package/src/state/atomic-write.ts +50 -1
  182. package/src/state/blob-store.ts +117 -0
  183. package/src/state/contracts.ts +1 -0
  184. package/src/state/event-log-rotation.ts +158 -0
  185. package/src/state/event-log.ts +52 -2
  186. package/src/state/mailbox.ts +87 -7
  187. package/src/state/state-store.ts +24 -4
  188. package/src/state/task-claims.ts +44 -44
  189. package/src/state/types.ts +20 -0
  190. package/src/state/usage.ts +29 -29
  191. package/src/subagents/async-entry.ts +1 -1
  192. package/src/subagents/index.ts +3 -3
  193. package/src/subagents/live/control.ts +1 -1
  194. package/src/subagents/live/manager.ts +1 -1
  195. package/src/subagents/live/realtime.ts +1 -1
  196. package/src/subagents/live/session-runtime.ts +1 -1
  197. package/src/subagents/manager.ts +1 -1
  198. package/src/subagents/spawn.ts +1 -1
  199. package/src/teams/team-serializer.ts +38 -38
  200. package/src/types/diff.d.ts +18 -18
  201. package/src/ui/agent-management-overlay.ts +144 -0
  202. package/src/ui/crew-footer.ts +101 -101
  203. package/src/ui/crew-select-list.ts +111 -111
  204. package/src/ui/crew-widget.ts +11 -2
  205. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  206. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  207. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  208. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  209. package/src/ui/dynamic-border.ts +25 -25
  210. package/src/ui/layout-primitives.ts +106 -106
  211. package/src/ui/live-run-sidebar.ts +4 -0
  212. package/src/ui/loaders.ts +158 -158
  213. package/src/ui/powerbar-publisher.ts +77 -15
  214. package/src/ui/render-coalescer.ts +51 -0
  215. package/src/ui/render-diff.ts +119 -119
  216. package/src/ui/render-scheduler.ts +143 -143
  217. package/src/ui/run-dashboard.ts +4 -0
  218. package/src/ui/run-event-bus.ts +209 -0
  219. package/src/ui/run-snapshot-cache.ts +68 -16
  220. package/src/ui/snapshot-types.ts +8 -0
  221. package/src/ui/spinner.ts +17 -17
  222. package/src/ui/status-colors.ts +58 -58
  223. package/src/ui/syntax-highlight.ts +116 -116
  224. package/src/ui/transcript-entries.ts +258 -0
  225. package/src/utils/atomic-write.ts +33 -33
  226. package/src/utils/completion-dedupe.ts +63 -63
  227. package/src/utils/frontmatter.ts +68 -68
  228. package/src/utils/git.ts +262 -262
  229. package/src/utils/ids.ts +17 -12
  230. package/src/utils/incremental-reader.ts +104 -0
  231. package/src/utils/names.ts +27 -27
  232. package/src/utils/redaction.ts +44 -44
  233. package/src/utils/safe-paths.ts +47 -47
  234. package/src/utils/scan-cache.ts +137 -0
  235. package/src/utils/sleep.ts +32 -32
  236. package/src/utils/sse-parser.ts +134 -0
  237. package/src/utils/task-name-generator.ts +337 -0
  238. package/src/utils/visual.ts +33 -2
  239. package/src/workflows/validate-workflow.ts +40 -40
  240. package/src/worktree/branch-freshness.ts +45 -45
  241. package/src/worktree/cleanup.ts +2 -1
  242. package/teams/default.team.md +12 -12
  243. package/teams/fast-fix.team.md +11 -11
  244. package/teams/implementation.team.md +18 -18
  245. package/teams/parallel-research.team.md +14 -14
  246. package/teams/research.team.md +11 -11
  247. package/teams/review.team.md +12 -12
  248. package/workflows/default.workflow.md +29 -29
  249. package/workflows/fast-fix.workflow.md +22 -22
  250. package/workflows/implementation.workflow.md +38 -38
  251. package/workflows/parallel-research.workflow.md +46 -46
  252. package/workflows/research.workflow.md +22 -22
  253. package/workflows/review.workflow.md +30 -30
@@ -27,12 +27,15 @@ interface ManifestCacheEntry {
27
27
  manifestSize: number;
28
28
  tasksMtimeMs: number;
29
29
  tasksSize: number;
30
+ cachedAt?: number;
30
31
  }
31
32
 
33
+ const MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
32
34
  const manifestCache = new Map<string, ManifestCacheEntry>();
33
35
 
34
36
  function setManifestCache(stateRoot: string, entry: ManifestCacheEntry): void {
35
37
  if (manifestCache.has(stateRoot)) manifestCache.delete(stateRoot);
38
+ entry.cachedAt = Date.now();
36
39
  manifestCache.set(stateRoot, entry);
37
40
  while (manifestCache.size > DEFAULT_CACHE.manifestMaxEntries) {
38
41
  const oldest = manifestCache.keys().next().value;
@@ -196,6 +199,15 @@ export async function saveRunTasksAsync(manifest: TeamRunManifest, tasks: TeamTa
196
199
  invalidateRunCache(manifest.stateRoot);
197
200
  }
198
201
 
202
+ /** M8: Atomically save manifest + tasks and invalidate cache once to prevent stale reads between saves */
203
+ export async function saveManifestAndTasksAtomic(manifest: TeamRunManifest, tasks: TeamTaskState[]): Promise<void> {
204
+ await Promise.all([
205
+ atomicWriteJsonAsync(path.join(manifest.stateRoot, "manifest.json"), manifest),
206
+ atomicWriteJsonAsync(manifest.tasksPath, tasks),
207
+ ]);
208
+ invalidateRunCache(manifest.stateRoot);
209
+ }
210
+
199
211
  export interface UpdateRunStatusOptions {
200
212
  data?: Record<string, unknown>;
201
213
  metadata?: Parameters<typeof appendEvent>[1]["metadata"];
@@ -266,11 +278,15 @@ export function loadRunManifestById(cwd: string, runId: string): { manifest: Tea
266
278
  && cached.tasksMtimeMs === tasksMtimeMs
267
279
  && cached.tasksSize === (tasksStat?.size ?? 0)
268
280
  ) {
269
- if (!validateRunManifestPaths(cwd, runId, cached.manifest, stateRoot, tasksPath)) {
281
+ // TTL eviction: expire stale entries even if mtime matches
282
+ if (cached.cachedAt && Date.now() - cached.cachedAt > MANIFEST_CACHE_TTL_MS) {
283
+ manifestCache.delete(stateRoot);
284
+ } else if (!validateRunManifestPaths(cwd, runId, cached.manifest, stateRoot, tasksPath)) {
270
285
  manifestCache.delete(stateRoot);
271
286
  return undefined;
287
+ } else {
288
+ return { manifest: cached.manifest, tasks: cached.tasks };
272
289
  }
273
- return { manifest: cached.manifest, tasks: cached.tasks };
274
290
  }
275
291
 
276
292
  const manifest = readJsonFile<TeamRunManifest>(manifestPath);
@@ -307,11 +323,15 @@ export async function loadRunManifestByIdAsync(cwd: string, runId: string): Prom
307
323
  }
308
324
  const tasksMtimeMs = tasksStat?.mtimeMs ?? 0;
309
325
  if (cached && cached.manifestMtimeMs === manifestStat.mtimeMs && cached.manifestSize === manifestStat.size && cached.tasksMtimeMs === tasksMtimeMs && cached.tasksSize === (tasksStat?.size ?? 0)) {
310
- if (!validateRunManifestPaths(cwd, runId, cached.manifest, stateRoot, tasksPath)) {
326
+ // TTL eviction: expire stale entries even if mtime matches
327
+ if (cached.cachedAt && Date.now() - cached.cachedAt > MANIFEST_CACHE_TTL_MS) {
328
+ manifestCache.delete(stateRoot);
329
+ } else if (!validateRunManifestPaths(cwd, runId, cached.manifest, stateRoot, tasksPath)) {
311
330
  manifestCache.delete(stateRoot);
312
331
  return undefined;
332
+ } else {
333
+ return { manifest: cached.manifest, tasks: cached.tasks };
313
334
  }
314
- return { manifest: cached.manifest, tasks: cached.tasks };
315
335
  }
316
336
  const manifest = await readJsonFileAsync<TeamRunManifest>(manifestPath);
317
337
  if (!manifest || !validateRunManifestPaths(cwd, runId, manifest, stateRoot, tasksPath)) return undefined;
@@ -1,44 +1,44 @@
1
- import { randomUUID } from "node:crypto";
2
- import type { TeamTaskState } from "./types.ts";
3
-
4
- export interface TaskClaimState {
5
- owner: string;
6
- token: string;
7
- leasedUntil: string;
8
- }
9
-
10
- export function createTaskClaim(owner: string, leaseMs = 5 * 60_000, now = new Date()): TaskClaimState {
11
- return { owner, token: randomUUID(), leasedUntil: new Date(now.getTime() + leaseMs).toISOString() };
12
- }
13
-
14
- export function isTaskClaimExpired(claim: TaskClaimState | undefined, now = new Date()): boolean {
15
- if (!claim) return false;
16
- const parsed = Date.parse(claim.leasedUntil);
17
- // Corrupt or invalid date strings produce NaN โ€” treat as expired immediately.
18
- return Number.isFinite(parsed) ? parsed <= now.getTime() : true;
19
- }
20
-
21
- export function canUseTaskClaim(task: Pick<TeamTaskState, "claim">, owner: string, token: string, now = new Date()): boolean {
22
- return task.claim?.owner === owner && task.claim.token === token && !isTaskClaimExpired(task.claim, now);
23
- }
24
-
25
- export function claimTask<T extends TeamTaskState>(task: T, owner: string, leaseMs?: number, now = new Date()): T {
26
- if (task.claim && !isTaskClaimExpired(task.claim, now)) {
27
- throw new Error(`Task '${task.id}' is already claimed by '${task.claim.owner}'.`);
28
- }
29
- return { ...task, claim: createTaskClaim(owner, leaseMs, now) };
30
- }
31
-
32
- export function releaseTaskClaim<T extends TeamTaskState>(task: T, owner: string, token: string, now = new Date()): T {
33
- if (!canUseTaskClaim(task, owner, token, now)) {
34
- throw new Error(`Task '${task.id}' claim is not held by '${owner}' or has expired.`);
35
- }
36
- return { ...task, claim: undefined };
37
- }
38
-
39
- export function transitionClaimedTaskStatus<T extends TeamTaskState>(task: T, owner: string, token: string, status: T["status"], now = new Date()): T {
40
- if (!canUseTaskClaim(task, owner, token, now)) {
41
- throw new Error(`Task '${task.id}' claim is not held by '${owner}' or has expired.`);
42
- }
43
- return { ...task, status };
44
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import type { TeamTaskState } from "./types.ts";
3
+
4
+ export interface TaskClaimState {
5
+ owner: string;
6
+ token: string;
7
+ leasedUntil: string;
8
+ }
9
+
10
+ export function createTaskClaim(owner: string, leaseMs = 5 * 60_000, now = new Date()): TaskClaimState {
11
+ return { owner, token: randomUUID(), leasedUntil: new Date(now.getTime() + leaseMs).toISOString() };
12
+ }
13
+
14
+ export function isTaskClaimExpired(claim: TaskClaimState | undefined, now = new Date()): boolean {
15
+ if (!claim) return false;
16
+ const parsed = Date.parse(claim.leasedUntil);
17
+ // Corrupt or invalid date strings produce NaN โ€” treat as expired immediately.
18
+ return Number.isFinite(parsed) ? parsed <= now.getTime() : true;
19
+ }
20
+
21
+ export function canUseTaskClaim(task: Pick<TeamTaskState, "claim">, owner: string, token: string, now = new Date()): boolean {
22
+ return task.claim?.owner === owner && task.claim.token === token && !isTaskClaimExpired(task.claim, now);
23
+ }
24
+
25
+ export function claimTask<T extends TeamTaskState>(task: T, owner: string, leaseMs?: number, now = new Date()): T {
26
+ if (task.claim && !isTaskClaimExpired(task.claim, now)) {
27
+ throw new Error(`Task '${task.id}' is already claimed by '${task.claim.owner}'.`);
28
+ }
29
+ return { ...task, claim: createTaskClaim(owner, leaseMs, now) };
30
+ }
31
+
32
+ export function releaseTaskClaim<T extends TeamTaskState>(task: T, owner: string, token: string, now = new Date()): T {
33
+ if (!canUseTaskClaim(task, owner, token, now)) {
34
+ throw new Error(`Task '${task.id}' claim is not held by '${owner}' or has expired.`);
35
+ }
36
+ return { ...task, claim: undefined };
37
+ }
38
+
39
+ export function transitionClaimedTaskStatus<T extends TeamTaskState>(task: T, owner: string, token: string, status: T["status"], now = new Date()): T {
40
+ if (!canUseTaskClaim(task, owner, token, now)) {
41
+ throw new Error(`Task '${task.id}' claim is not held by '${owner}' or has expired.`);
42
+ }
43
+ return { ...task, status };
44
+ }
@@ -39,6 +39,17 @@ export interface VerificationEvidence {
39
39
  notes?: string;
40
40
  }
41
41
 
42
+ export interface TaskOutputSchema {
43
+ /** Output format expected from the worker */
44
+ format: "json" | "markdown" | "text";
45
+ /** JTD or JSON Schema for validating JSON output (only when format="json") */
46
+ schema?: Record<string, unknown>;
47
+ /** Human-readable description of expected output */
48
+ description?: string;
49
+ /** Example of valid output (for prompt guidance) */
50
+ example?: string;
51
+ }
52
+
42
53
  export interface TaskPacket {
43
54
  objective: string;
44
55
  scope: TaskScope;
@@ -53,6 +64,7 @@ export interface TaskPacket {
53
64
  constraints: string[];
54
65
  expectedArtifacts: string[];
55
66
  verification: VerificationContract;
67
+ outputSchema?: TaskOutputSchema;
56
68
  }
57
69
 
58
70
  export type PolicyDecisionAction = "retry" | "reassign" | "escalate" | "block" | "notify" | "cleanup" | "closeout" | "fail";
@@ -218,6 +230,7 @@ export interface TeamTaskState {
218
230
  role: string;
219
231
  agent: string;
220
232
  title: string;
233
+ displayName?: string;
221
234
  status: TeamTaskStatus;
222
235
  dependsOn: string[];
223
236
  cwd: string;
@@ -253,4 +266,11 @@ export interface TeamTaskState {
253
266
  retryCount?: number;
254
267
  lastDecision?: PolicyDecision;
255
268
  };
269
+ controlReservation?: ControlReservation;
270
+ }
271
+
272
+ export interface ControlReservation {
273
+ reservedAt: string;
274
+ controllerId: string;
275
+ acceptsControlEvents: boolean;
256
276
  }
@@ -1,29 +1,29 @@
1
- import type { TeamTaskState, UsageState } from "./types.ts";
2
-
3
- export function aggregateUsage(tasks: TeamTaskState[]): UsageState | undefined {
4
- const total: UsageState = {};
5
- let found = false;
6
- for (const task of tasks) {
7
- if (!task.usage) continue;
8
- found = true;
9
- total.input = (total.input ?? 0) + (task.usage.input ?? 0);
10
- total.output = (total.output ?? 0) + (task.usage.output ?? 0);
11
- total.cacheRead = (total.cacheRead ?? 0) + (task.usage.cacheRead ?? 0);
12
- total.cacheWrite = (total.cacheWrite ?? 0) + (task.usage.cacheWrite ?? 0);
13
- total.cost = (total.cost ?? 0) + (task.usage.cost ?? 0);
14
- total.turns = (total.turns ?? 0) + (task.usage.turns ?? 0);
15
- }
16
- return found ? total : undefined;
17
- }
18
-
19
- export function formatUsage(usage: UsageState | undefined): string {
20
- if (!usage) return "(none)";
21
- const parts: string[] = [];
22
- if (usage.input !== undefined) parts.push(`input=${usage.input}`);
23
- if (usage.output !== undefined) parts.push(`output=${usage.output}`);
24
- if (usage.cacheRead !== undefined) parts.push(`cacheRead=${usage.cacheRead}`);
25
- if (usage.cacheWrite !== undefined) parts.push(`cacheWrite=${usage.cacheWrite}`);
26
- if (usage.cost !== undefined && Number.isFinite(usage.cost)) parts.push(`cost=${usage.cost.toFixed(6)}`);
27
- if (usage.turns !== undefined) parts.push(`turns=${usage.turns}`);
28
- return parts.join(", ") || "(none)";
29
- }
1
+ import type { TeamTaskState, UsageState } from "./types.ts";
2
+
3
+ export function aggregateUsage(tasks: TeamTaskState[]): UsageState | undefined {
4
+ const total: UsageState = {};
5
+ let found = false;
6
+ for (const task of tasks) {
7
+ if (!task.usage) continue;
8
+ found = true;
9
+ total.input = (total.input ?? 0) + (task.usage.input ?? 0);
10
+ total.output = (total.output ?? 0) + (task.usage.output ?? 0);
11
+ total.cacheRead = (total.cacheRead ?? 0) + (task.usage.cacheRead ?? 0);
12
+ total.cacheWrite = (total.cacheWrite ?? 0) + (task.usage.cacheWrite ?? 0);
13
+ total.cost = (total.cost ?? 0) + (task.usage.cost ?? 0);
14
+ total.turns = (total.turns ?? 0) + (task.usage.turns ?? 0);
15
+ }
16
+ return found ? total : undefined;
17
+ }
18
+
19
+ export function formatUsage(usage: UsageState | undefined): string {
20
+ if (!usage) return "(none)";
21
+ const parts: string[] = [];
22
+ if (usage.input !== undefined) parts.push(`input=${usage.input}`);
23
+ if (usage.output !== undefined) parts.push(`output=${usage.output}`);
24
+ if (usage.cacheRead !== undefined) parts.push(`cacheRead=${usage.cacheRead}`);
25
+ if (usage.cacheWrite !== undefined) parts.push(`cacheWrite=${usage.cacheWrite}`);
26
+ if (usage.cost !== undefined && Number.isFinite(usage.cost)) parts.push(`cost=${usage.cost.toFixed(6)}`);
27
+ if (usage.turns !== undefined) parts.push(`turns=${usage.turns}`);
28
+ return parts.join(", ") || "(none)";
29
+ }
@@ -1 +1 @@
1
- export * from "../runtime/async-runner.ts";
1
+ export * from "../runtime/async-runner.ts";
@@ -1,3 +1,3 @@
1
- export * from "./spawn.ts";
2
- export * from "./manager.ts";
3
- export * from "./async-entry.ts";
1
+ export * from "./spawn.ts";
2
+ export * from "./manager.ts";
3
+ export * from "./async-entry.ts";
@@ -1 +1 @@
1
- export * from "../../runtime/live-agent-control.ts";
1
+ export * from "../../runtime/live-agent-control.ts";
@@ -1 +1 @@
1
- export * from "../../runtime/live-agent-manager.ts";
1
+ export * from "../../runtime/live-agent-manager.ts";
@@ -1 +1 @@
1
- export * from "../../runtime/live-control-realtime.ts";
1
+ export * from "../../runtime/live-control-realtime.ts";
@@ -1 +1 @@
1
- export * from "../../runtime/live-session-runtime.ts";
1
+ export * from "../../runtime/live-session-runtime.ts";
@@ -1 +1 @@
1
- export * from "../runtime/subagent-manager.ts";
1
+ export * from "../runtime/subagent-manager.ts";
@@ -1 +1 @@
1
- export * from "../runtime/child-pi.ts";
1
+ export * from "../runtime/child-pi.ts";
@@ -1,38 +1,38 @@
1
- import type { TeamConfig, TeamRole } from "./team-config.ts";
2
-
3
- function line(key: string, value: string | string[] | undefined): string | undefined {
4
- if (value === undefined) return undefined;
5
- if (Array.isArray(value)) return `${key}: ${value.join(", ")}`;
6
- return `${key}: ${value}`;
7
- }
8
-
9
- function serializeRole(role: TeamRole): string {
10
- const parts = [`agent=${role.agent}`];
11
- if (role.model) parts.push(`model=${role.model}`);
12
- if (role.skills === false) parts.push("skills=false");
13
- else if (role.skills?.length) parts.push(`skills=${role.skills.join(",")}`);
14
- if (role.maxConcurrency !== undefined) parts.push(`maxConcurrency=${role.maxConcurrency}`);
15
- if (role.description) parts.push(role.description);
16
- return `- ${role.name}: ${parts.join(" ")}`;
17
- }
18
-
19
- export function serializeTeam(team: TeamConfig): string {
20
- const lines = [
21
- "---",
22
- `name: ${team.name}`,
23
- `description: ${team.description}`,
24
- team.defaultWorkflow ? `defaultWorkflow: ${team.defaultWorkflow}` : undefined,
25
- team.workspaceMode ? `workspaceMode: ${team.workspaceMode}` : undefined,
26
- team.maxConcurrency !== undefined ? `maxConcurrency: ${team.maxConcurrency}` : undefined,
27
- line("triggers", team.routing?.triggers),
28
- line("useWhen", team.routing?.useWhen),
29
- line("avoidWhen", team.routing?.avoidWhen),
30
- line("cost", team.routing?.cost),
31
- line("category", team.routing?.category),
32
- "---",
33
- "",
34
- ...team.roles.map(serializeRole),
35
- "",
36
- ].filter((entry): entry is string => entry !== undefined);
37
- return lines.join("\n");
38
- }
1
+ import type { TeamConfig, TeamRole } from "./team-config.ts";
2
+
3
+ function line(key: string, value: string | string[] | undefined): string | undefined {
4
+ if (value === undefined) return undefined;
5
+ if (Array.isArray(value)) return `${key}: ${value.join(", ")}`;
6
+ return `${key}: ${value}`;
7
+ }
8
+
9
+ function serializeRole(role: TeamRole): string {
10
+ const parts = [`agent=${role.agent}`];
11
+ if (role.model) parts.push(`model=${role.model}`);
12
+ if (role.skills === false) parts.push("skills=false");
13
+ else if (role.skills?.length) parts.push(`skills=${role.skills.join(",")}`);
14
+ if (role.maxConcurrency !== undefined) parts.push(`maxConcurrency=${role.maxConcurrency}`);
15
+ if (role.description) parts.push(role.description);
16
+ return `- ${role.name}: ${parts.join(" ")}`;
17
+ }
18
+
19
+ export function serializeTeam(team: TeamConfig): string {
20
+ const lines = [
21
+ "---",
22
+ `name: ${team.name}`,
23
+ `description: ${team.description}`,
24
+ team.defaultWorkflow ? `defaultWorkflow: ${team.defaultWorkflow}` : undefined,
25
+ team.workspaceMode ? `workspaceMode: ${team.workspaceMode}` : undefined,
26
+ team.maxConcurrency !== undefined ? `maxConcurrency: ${team.maxConcurrency}` : undefined,
27
+ line("triggers", team.routing?.triggers),
28
+ line("useWhen", team.routing?.useWhen),
29
+ line("avoidWhen", team.routing?.avoidWhen),
30
+ line("cost", team.routing?.cost),
31
+ line("category", team.routing?.category),
32
+ "---",
33
+ "",
34
+ ...team.roles.map(serializeRole),
35
+ "",
36
+ ].filter((entry): entry is string => entry !== undefined);
37
+ return lines.join("\n");
38
+ }
@@ -1,18 +1,18 @@
1
- declare module "diff" {
2
- export interface Change {
3
- value: string;
4
- count?: number;
5
- added?: boolean;
6
- removed?: boolean;
7
- }
8
-
9
- export interface DiffOptions {
10
- ignoreCase?: boolean;
11
- newlineIsToken?: boolean;
12
- ignoreWhitespace?: boolean;
13
- stripTrailingCr?: boolean;
14
- oneChangePerToken?: boolean;
15
- }
16
-
17
- export function diffWords(oldStr: string, newStr: string, options?: DiffOptions): Change[];
18
- }
1
+ declare module "diff" {
2
+ export interface Change {
3
+ value: string;
4
+ count?: number;
5
+ added?: boolean;
6
+ removed?: boolean;
7
+ }
8
+
9
+ export interface DiffOptions {
10
+ ignoreCase?: boolean;
11
+ newlineIsToken?: boolean;
12
+ ignoreWhitespace?: boolean;
13
+ stripTrailingCr?: boolean;
14
+ oneChangePerToken?: boolean;
15
+ }
16
+
17
+ export function diffWords(oldStr: string, newStr: string, options?: DiffOptions): Change[];
18
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Agent Management Overlay โ€” displays discovered agents with their configuration.
3
+ * Read-only view of agent definitions from builtin/user/project sources.
4
+ * Future: enable/disable toggle, model override editing.
5
+ */
6
+ import type { AgentConfig, ResourceSource } from "../agents/agent-config.ts";
7
+ import { truncate } from "../utils/visual.ts";
8
+
9
+ export interface AgentEntry {
10
+ name: string;
11
+ description: string;
12
+ source: ResourceSource;
13
+ model?: string;
14
+ thinking?: string;
15
+ loadMode?: string;
16
+ contextMode?: string;
17
+ disabled?: boolean;
18
+ filePath: string;
19
+ }
20
+
21
+ export function agentToEntry(agent: AgentConfig): AgentEntry {
22
+ return {
23
+ name: agent.name,
24
+ description: agent.description,
25
+ source: agent.source,
26
+ model: agent.model,
27
+ thinking: agent.thinking,
28
+ loadMode: agent.loadMode,
29
+ contextMode: agent.contextMode,
30
+ disabled: agent.disabled,
31
+ filePath: agent.filePath,
32
+ };
33
+ }
34
+
35
+ function sourceIcon(source: ResourceSource): string {
36
+ switch (source) {
37
+ case "builtin": return "๐Ÿ“ฆ";
38
+ case "user": return "๐Ÿ‘ค";
39
+ case "project": return "๐Ÿ“‚";
40
+ case "git": return "๐Ÿ”—";
41
+ }
42
+ }
43
+
44
+ function sourceLabel(source: ResourceSource): string {
45
+ switch (source) {
46
+ case "builtin": return "builtin";
47
+ case "user": return "user";
48
+ case "project": return "project";
49
+ case "git": return "git";
50
+ }
51
+ }
52
+
53
+ export interface AgentOverlayState {
54
+ entries: AgentEntry[];
55
+ selectedIndex: number;
56
+ scrollOffset: number;
57
+ expanded: Set<number>;
58
+ maxVisible: number;
59
+ }
60
+
61
+ export function createAgentOverlayState(entries: AgentEntry[], maxVisible = 20): AgentOverlayState {
62
+ return {
63
+ entries: entries.sort((a, b) => {
64
+ const order: Record<ResourceSource, number> = { project: 0, user: 1, git: 2, builtin: 3 };
65
+ const diff = (order[a.source] ?? 4) - (order[b.source] ?? 4);
66
+ return diff !== 0 ? diff : a.name.localeCompare(b.name);
67
+ }),
68
+ selectedIndex: 0,
69
+ scrollOffset: 0,
70
+ expanded: new Set(),
71
+ maxVisible,
72
+ };
73
+ }
74
+
75
+ export function moveSelection(state: AgentOverlayState, direction: -1 | 1): AgentOverlayState {
76
+ const next = Math.max(0, Math.min(state.entries.length - 1, state.selectedIndex + direction));
77
+ const visibleStart = state.scrollOffset;
78
+ const visibleEnd = state.scrollOffset + state.maxVisible;
79
+ const newScroll = next < visibleStart
80
+ ? next
81
+ : next >= visibleEnd
82
+ ? Math.max(0, next - state.maxVisible + 1)
83
+ : state.scrollOffset;
84
+ return { ...state, selectedIndex: next, scrollOffset: newScroll };
85
+ }
86
+
87
+ export function toggleExpand(state: AgentOverlayState): AgentOverlayState {
88
+ const expanded = new Set(state.expanded);
89
+ if (expanded.has(state.selectedIndex)) {
90
+ expanded.delete(state.selectedIndex);
91
+ } else {
92
+ expanded.add(state.selectedIndex);
93
+ }
94
+ return { ...state, expanded };
95
+ }
96
+
97
+ export function renderAgentOverlay(state: AgentOverlayState, width: number): string[] {
98
+ const lines: string[] = [];
99
+ const header = ` Agent Configuration (${state.entries.length} agents)`;
100
+ lines.push(truncate(header, width));
101
+ lines.push(truncate("โ”€".repeat(Math.min(width, 60)), width));
102
+
103
+ if (state.entries.length === 0) {
104
+ lines.push(truncate(" No agents discovered.", width));
105
+ return lines;
106
+ }
107
+
108
+ const visible = state.entries.slice(
109
+ state.scrollOffset,
110
+ state.scrollOffset + state.maxVisible,
111
+ );
112
+
113
+ for (const [i, entry] of visible.entries()) {
114
+ const globalIndex = state.scrollOffset + i;
115
+ const isSelected = globalIndex === state.selectedIndex;
116
+ const isExpanded = state.expanded.has(globalIndex);
117
+ const cursor = isSelected ? "โ–ธ" : " ";
118
+ const disabled = entry.disabled ? " [disabled]" : "";
119
+ const model = entry.model ? ` (${entry.model})` : "";
120
+
121
+ const summary = `${cursor} ${sourceIcon(entry.source)} ${entry.name}${model}${disabled}`;
122
+ lines.push(truncate(summary, width));
123
+
124
+ if (isExpanded) {
125
+ const desc = ` ${entry.description}`;
126
+ lines.push(truncate(desc, width));
127
+ const meta: string[] = [` source: ${sourceLabel(entry.source)}`];
128
+ if (entry.model) meta.push(`model: ${entry.model}`);
129
+ if (entry.thinking) meta.push(`thinking: ${entry.thinking}`);
130
+ if (entry.loadMode) meta.push(`loadMode: ${entry.loadMode}`);
131
+ if (entry.contextMode) meta.push(`context: ${entry.contextMode}`);
132
+ meta.push(`file: ${entry.filePath}`);
133
+ lines.push(truncate(meta.join(" ยท "), width));
134
+ lines.push(truncate("โ”€".repeat(Math.min(width - 4, 50)), width));
135
+ }
136
+ }
137
+
138
+ if (state.scrollOffset + state.maxVisible < state.entries.length) {
139
+ const remaining = state.entries.length - state.scrollOffset - state.maxVisible;
140
+ lines.push(truncate(` โ€ฆ +${remaining} more`, width));
141
+ }
142
+
143
+ return lines;
144
+ }