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.
- package/CHANGELOG.md +97 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +117 -42
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -100
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +56 -9
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -107
- package/index.ts +6 -6
- package/package.json +99 -98
- package/schema.json +8 -0
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/discover-agents.ts +17 -4
- package/src/config/config.ts +24 -0
- package/src/config/defaults.ts +11 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/register.ts +58 -13
- package/src/extension/registration/commands.ts +33 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/team-tool.ts +6 -4
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +24 -18
- package/src/extension/run-maintenance.ts +68 -62
- package/src/extension/team-tool/api.ts +23 -2
- package/src/extension/team-tool/cancel.ts +86 -11
- package/src/extension/team-tool/context.ts +3 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +47 -18
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +10 -2
- package/src/extension/team-tool/run.ts +3 -2
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +1 -0
- package/src/extension/team-tool.ts +13 -3
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +108 -2
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +19 -0
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -51
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +181 -0
- package/src/runtime/crew-agent-records.ts +35 -7
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/delivery-coordinator.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +81 -76
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +78 -2
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +297 -7
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +1 -4
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +3 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +59 -9
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +2 -0
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +70 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +75 -4
- package/src/runtime/team-runner.ts +60 -8
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +6 -0
- package/src/schema/team-tool-schema.ts +11 -1
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +4 -2
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +1 -0
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +87 -7
- package/src/state/state-store.ts +24 -4
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +20 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +11 -2
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +68 -16
- package/src/ui/snapshot-types.ts +8 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -12
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +2 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import * as fs from "node:fs";
|
|
2
3
|
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
3
4
|
import { appendEvent, scanSequence } from "../state/event-log.ts";
|
|
4
5
|
import { withRunLockSync } from "../state/locks.ts";
|
|
@@ -8,6 +9,8 @@ import { isWorkerHeartbeatStale } from "./worker-heartbeat.ts";
|
|
|
8
9
|
import type { ManifestCache } from "./manifest-cache.ts";
|
|
9
10
|
import { checkProcessLiveness } from "./process-status.ts";
|
|
10
11
|
import { reconcileStaleRun, type ReconcileResult } from "./stale-reconciler.ts";
|
|
12
|
+
import { executeHook, appendHookEvent } from "../hooks/registry.ts";
|
|
13
|
+
import { activeRunEntries, unregisterActiveRun, readActiveRunRegistry } from "../state/active-run-registry.ts";
|
|
11
14
|
|
|
12
15
|
export interface RecoveryPlan {
|
|
13
16
|
runId: string;
|
|
@@ -43,6 +46,14 @@ export function detectInterruptedRuns(cwd: string, manifestCache: ManifestCache,
|
|
|
43
46
|
export async function applyRecoveryPlan(plan: RecoveryPlan, ctx: Pick<ExtensionContext, "cwd">, registry?: MetricRegistry): Promise<void> {
|
|
44
47
|
const loaded = loadRunManifestById(ctx.cwd, plan.runId);
|
|
45
48
|
if (!loaded) throw new Error(`Run '${plan.runId}' not found.`);
|
|
49
|
+
|
|
50
|
+
const hookReport = await executeHook("run_recovery", { runId: plan.runId, cwd: ctx.cwd });
|
|
51
|
+
appendHookEvent(loaded.manifest, hookReport);
|
|
52
|
+
if (hookReport.outcome === "block") {
|
|
53
|
+
appendEvent(loaded.manifest.eventsPath, { type: "crew.run.recovery_blocked", runId: plan.runId, message: `Recovery blocked by hook: ${hookReport.reason ?? "run_recovery hook blocked the operation."}`, data: { hookOutcome: "block", reason: hookReport.reason } });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
const reset = new Set(plan.resumableTasks);
|
|
47
58
|
const tasks = loaded.tasks.map((task) => reset.has(task.id) ? { ...task, status: "queued" as const, startedAt: undefined, finishedAt: undefined, error: undefined, heartbeat: undefined } : task);
|
|
48
59
|
saveRunTasks(loaded.manifest, tasks);
|
|
@@ -62,6 +73,176 @@ export function declineRecoveryPlan(plan: RecoveryPlan, ctx: Pick<ExtensionConte
|
|
|
62
73
|
* Run 3-phase stale reconciliation on all active runs.
|
|
63
74
|
* Returns results for each reconciled run.
|
|
64
75
|
*/
|
|
76
|
+
/**
|
|
77
|
+
* Auto-cancel orphaned runs whose owner session no longer exists.
|
|
78
|
+
*
|
|
79
|
+
* When a Pi session dies (crash, force-close, Ctrl+C), `session_shutdown`
|
|
80
|
+
* does not fire and child workers are not terminated. The next Pi session
|
|
81
|
+
* must detect these orphaned runs and cancel them.
|
|
82
|
+
*
|
|
83
|
+
* Criteria for orphan detection:
|
|
84
|
+
* 1. Manifest status is "running"
|
|
85
|
+
* 2. Manifest has an `ownerSessionId` that is NOT the current session
|
|
86
|
+
* 3. The owner session's process is no longer alive (PID check)
|
|
87
|
+
* 4. No recent heartbeat activity (task heartbeat or agent progress within threshold)
|
|
88
|
+
*
|
|
89
|
+
* Returns the number of runs cancelled.
|
|
90
|
+
*/
|
|
91
|
+
export function cancelOrphanedRuns(
|
|
92
|
+
cwd: string,
|
|
93
|
+
manifestCache: ManifestCache,
|
|
94
|
+
currentSessionId: string,
|
|
95
|
+
staleThresholdMs = 300_000,
|
|
96
|
+
now = Date.now(),
|
|
97
|
+
): { cancelled: string[]; skipped: string[] } {
|
|
98
|
+
const cancelled: string[] = [];
|
|
99
|
+
const skipped: string[] = [];
|
|
100
|
+
|
|
101
|
+
// Phase 1: Scan project-level manifests via manifestCache
|
|
102
|
+
for (const manifest of manifestCache.list(50)) {
|
|
103
|
+
if (manifest.status !== "running") continue;
|
|
104
|
+
|
|
105
|
+
// Only consider runs owned by a different session
|
|
106
|
+
const ownerId = manifest.ownerSessionId;
|
|
107
|
+
if (!ownerId || ownerId === currentSessionId) continue;
|
|
108
|
+
|
|
109
|
+
// Check if the owner process is still alive
|
|
110
|
+
const ownerPid = manifest.async?.pid;
|
|
111
|
+
if (ownerPid !== undefined && checkProcessLiveness(ownerPid).alive) {
|
|
112
|
+
skipped.push(manifest.runId);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for recent heartbeat activity
|
|
117
|
+
const loaded = loadRunManifestById(cwd, manifest.runId);
|
|
118
|
+
if (!loaded) continue;
|
|
119
|
+
|
|
120
|
+
const hasRecentActivity = loaded.tasks.some((task) => {
|
|
121
|
+
if (task.status !== "running" && task.status !== "waiting") return false;
|
|
122
|
+
const heartbeatAt = task.heartbeat?.lastSeenAt ? new Date(task.heartbeat.lastSeenAt).getTime() : Number.NaN;
|
|
123
|
+
if (task.heartbeat?.alive !== false && Number.isFinite(heartbeatAt) && now - heartbeatAt <= staleThresholdMs) return true;
|
|
124
|
+
const activityAt = task.agentProgress?.lastActivityAt ? new Date(task.agentProgress.lastActivityAt).getTime() : Number.NaN;
|
|
125
|
+
return Number.isFinite(activityAt) && now - activityAt <= staleThresholdMs;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (hasRecentActivity) {
|
|
129
|
+
skipped.push(manifest.runId);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Orphan confirmed — cancel all running tasks
|
|
134
|
+
withRunLockSync(loaded.manifest, () => {
|
|
135
|
+
const fresh = loadRunManifestById(cwd, manifest.runId);
|
|
136
|
+
if (!fresh || fresh.manifest.status !== "running") return;
|
|
137
|
+
|
|
138
|
+
const now_iso = new Date(now).toISOString();
|
|
139
|
+
const repairedTasks = fresh.tasks.map((task) => {
|
|
140
|
+
if (task.status === "running" || task.status === "queued" || task.status === "waiting") {
|
|
141
|
+
return { ...task, status: "cancelled" as const, finishedAt: now_iso, error: `Orphaned run: owner session ${ownerId} no longer exists` };
|
|
142
|
+
}
|
|
143
|
+
return task;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
saveRunTasks(fresh.manifest, repairedTasks);
|
|
147
|
+
updateRunStatus(fresh.manifest, "cancelled", `Orphaned run: owner session ${ownerId} no longer exists`);
|
|
148
|
+
appendEvent(fresh.manifest.eventsPath, { type: "crew.run.orphan_cancelled", runId: manifest.runId, message: `Auto-cancelled orphaned run (owner: ${ownerId})`, data: { ownerSessionId: ownerId, cancelledTasks: repairedTasks.filter((t) => t.status === "cancelled").length } });
|
|
149
|
+
cancelled.push(manifest.runId);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { cancelled, skipped };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Purge the global active-run-index of entries whose manifest is no longer active.
|
|
158
|
+
*
|
|
159
|
+
* This scans every entry in active-run-index.json and removes any whose:
|
|
160
|
+
* - manifest file no longer exists, OR
|
|
161
|
+
* - manifest status is terminal (completed/failed/cancelled/blocked), OR
|
|
162
|
+
* - manifest cwd directory no longer exists (e.g. temp test dirs)
|
|
163
|
+
*
|
|
164
|
+
* Also removes entries where the manifest is still "running" but:
|
|
165
|
+
* - The cwd has been deleted (temp dir cleanup)
|
|
166
|
+
* - The async worker PID is dead AND no heartbeat for > threshold
|
|
167
|
+
*
|
|
168
|
+
* This is the **global** cleanup that cancelOrphanedRuns (project-scoped)
|
|
169
|
+
* cannot reach.
|
|
170
|
+
*/
|
|
171
|
+
export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.now()): { purged: string[]; kept: string[] } {
|
|
172
|
+
const purged: string[] = [];
|
|
173
|
+
const kept: string[] = [];
|
|
174
|
+
const entries = readActiveRunRegistry();
|
|
175
|
+
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
// 1. Manifest file gone → definitely stale
|
|
178
|
+
if (!fs.existsSync(entry.manifestPath)) {
|
|
179
|
+
unregisterActiveRun(entry.runId);
|
|
180
|
+
purged.push(entry.runId);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 2. CWD gone → temp dir cleaned up
|
|
185
|
+
if (!fs.existsSync(entry.cwd)) {
|
|
186
|
+
unregisterActiveRun(entry.runId);
|
|
187
|
+
purged.push(entry.runId);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 3. Read manifest status
|
|
192
|
+
let manifest: { status?: string; async?: { pid?: number }; ownerSessionId?: string } | undefined;
|
|
193
|
+
try {
|
|
194
|
+
manifest = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8"));
|
|
195
|
+
} catch {
|
|
196
|
+
unregisterActiveRun(entry.runId);
|
|
197
|
+
purged.push(entry.runId);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 4. Terminal status → no longer active
|
|
202
|
+
const terminalStatuses = new Set(["completed", "failed", "cancelled", "blocked"]);
|
|
203
|
+
if (manifest && terminalStatuses.has(manifest.status ?? "")) {
|
|
204
|
+
unregisterActiveRun(entry.runId);
|
|
205
|
+
purged.push(entry.runId);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 5. Still "running" — check if worker PID is dead and no heartbeat
|
|
210
|
+
if (manifest?.status === "running" && manifest.async?.pid !== undefined) {
|
|
211
|
+
const pidAlive = checkProcessLiveness(manifest.async.pid).alive;
|
|
212
|
+
if (!pidAlive) {
|
|
213
|
+
// Check age — if manifest hasn't been updated in > threshold, it's stale
|
|
214
|
+
const updatedAt = new Date(entry.updatedAt).getTime();
|
|
215
|
+
if (Number.isFinite(updatedAt) && now - updatedAt > staleThresholdMs) {
|
|
216
|
+
// Dead PID + stale update → cancel the manifest and unregister
|
|
217
|
+
try {
|
|
218
|
+
const fullLoaded = loadRunManifestById(entry.cwd, entry.runId);
|
|
219
|
+
if (fullLoaded) {
|
|
220
|
+
const now_iso = new Date(now).toISOString();
|
|
221
|
+
const repairedTasks = fullLoaded.tasks.map((task) => {
|
|
222
|
+
if (task.status === "running" || task.status === "queued" || task.status === "waiting") {
|
|
223
|
+
return { ...task, status: "cancelled" as const, finishedAt: now_iso, error: "Orphaned run: worker process dead and no recent activity" };
|
|
224
|
+
}
|
|
225
|
+
return task;
|
|
226
|
+
});
|
|
227
|
+
saveRunTasks(fullLoaded.manifest, repairedTasks);
|
|
228
|
+
updateRunStatus(fullLoaded.manifest, "cancelled", "Orphaned run: worker process dead and no recent activity");
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Best-effort manifest cleanup
|
|
232
|
+
}
|
|
233
|
+
unregisterActiveRun(entry.runId);
|
|
234
|
+
purged.push(entry.runId);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
kept.push(entry.runId);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { purged, kept };
|
|
244
|
+
}
|
|
245
|
+
|
|
65
246
|
export function reconcileAllStaleRuns(cwd: string, manifestCache: ManifestCache, now = Date.now()): ReconcileResult[] {
|
|
66
247
|
const results: ReconcileResult[] = [];
|
|
67
248
|
for (const manifest of manifestCache.list(50)) {
|
|
@@ -81,7 +81,20 @@ function setAsyncAgentReaderCache(filePath: string, entry: { expiresAt: number;
|
|
|
81
81
|
|
|
82
82
|
export function readCrewAgents(manifest: TeamRunManifest): CrewAgentRecord[] {
|
|
83
83
|
try {
|
|
84
|
-
|
|
84
|
+
const records = readJsonFileCoalesced(agentsPath(manifest), AGENT_READER_TTL_MS, () => readJsonFile<CrewAgentRecord[]>(agentsPath(manifest)) ?? []);
|
|
85
|
+
// Validate schema and deduplicate by id to handle concurrent write conflicts
|
|
86
|
+
const seen = new Set<string>();
|
|
87
|
+
const deduped = records.filter((r) => {
|
|
88
|
+
if (!r || typeof r.id !== "string" || typeof r.taskId !== "string") return false;
|
|
89
|
+
if (seen.has(r.id)) return false;
|
|
90
|
+
seen.add(r.id);
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
if (deduped.length !== records.length) {
|
|
94
|
+
// Schema mismatch or duplicates detected — save corrected state
|
|
95
|
+
saveCrewAgents(manifest, deduped);
|
|
96
|
+
}
|
|
97
|
+
return deduped;
|
|
85
98
|
} catch {
|
|
86
99
|
return [];
|
|
87
100
|
}
|
|
@@ -96,9 +109,20 @@ export async function readCrewAgentsAsync(manifest: TeamRunManifest): Promise<Cr
|
|
|
96
109
|
const inFlight = (async (): Promise<CrewAgentRecord[]> => {
|
|
97
110
|
try {
|
|
98
111
|
const parsed = JSON.parse(await fs.promises.readFile(filePath, "utf-8")) as unknown;
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
const raw = Array.isArray(parsed) ? redactSecrets(parsed) as CrewAgentRecord[] : [];
|
|
113
|
+
// Deduplicate by id to handle concurrent write conflicts
|
|
114
|
+
const seen = new Set<string>();
|
|
115
|
+
const deduped = raw.filter((r) => {
|
|
116
|
+
if (!r || typeof r.id !== "string" || typeof r.taskId !== "string") return false;
|
|
117
|
+
if (seen.has(r.id)) return false;
|
|
118
|
+
seen.add(r.id);
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
if (deduped.length !== raw.length) {
|
|
122
|
+
try { saveCrewAgents(manifest, deduped); } catch { /* best-effort */ }
|
|
123
|
+
}
|
|
124
|
+
setAsyncAgentReaderCache(filePath, { expiresAt: Date.now() + AGENT_READER_TTL_MS, records: deduped });
|
|
125
|
+
return deduped;
|
|
102
126
|
} catch {
|
|
103
127
|
setAsyncAgentReaderCache(filePath, { expiresAt: Date.now() + AGENT_READER_TTL_MS, records: [] });
|
|
104
128
|
return [];
|
|
@@ -117,9 +141,13 @@ export function saveCrewAgents(manifest: TeamRunManifest, records: CrewAgentReco
|
|
|
117
141
|
}
|
|
118
142
|
|
|
119
143
|
export function upsertCrewAgent(manifest: TeamRunManifest, record: CrewAgentRecord): void {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
// Read current state
|
|
145
|
+
const existing = readCrewAgents(manifest);
|
|
146
|
+
// Deduplicate by id: keep newer record when same id appears
|
|
147
|
+
const idIndex = new Map(existing.map((item, i) => [item.id, i]));
|
|
148
|
+
const merged: CrewAgentRecord[] = existing.map((item) => item.id === record.id ? record : item);
|
|
149
|
+
if (!idIndex.has(record.id)) merged.push(record);
|
|
150
|
+
saveCrewAgents(manifest, merged);
|
|
123
151
|
writeCrewAgentStatus(manifest, record);
|
|
124
152
|
}
|
|
125
153
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G1: Custom tool — irc.
|
|
3
|
+
*
|
|
4
|
+
* Registers a real `irc` tool in the Pi SDK session so that
|
|
5
|
+
* live-session workers can send messages to other live agents.
|
|
6
|
+
*
|
|
7
|
+
* Operations:
|
|
8
|
+
* - `list`: List currently visible peer agents
|
|
9
|
+
* - `send`: Send a message to a specific agent or broadcast to all
|
|
10
|
+
*
|
|
11
|
+
* Adapted from oh-my-pi's `IrcTool` pattern. Uses the live-agent-manager
|
|
12
|
+
* for routing messages between in-process workers.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { defineTool, type ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
16
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
17
|
+
import { listLiveAgents, sendIrcMessage, broadcastIrcMessage } from "../live-agent-manager.ts";
|
|
18
|
+
import type { IrcMessage } from "../live-irc.ts";
|
|
19
|
+
|
|
20
|
+
const IrcParams = Type.Object({
|
|
21
|
+
op: Type.Union(
|
|
22
|
+
[
|
|
23
|
+
Type.Literal("send", { description: "Send a message to one peer or to all peers." }),
|
|
24
|
+
Type.Literal("list", { description: "List currently visible peers." }),
|
|
25
|
+
],
|
|
26
|
+
{ description: "IRC operation." },
|
|
27
|
+
),
|
|
28
|
+
to: Type.Optional(
|
|
29
|
+
Type.String({
|
|
30
|
+
description: 'Recipient agent ID or "all" to broadcast.',
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
message: Type.Optional(
|
|
34
|
+
Type.String({
|
|
35
|
+
description: "Message body to deliver.",
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
awaitReply: Type.Optional(
|
|
39
|
+
Type.Boolean({
|
|
40
|
+
description: "Wait for a reply (default: true for DM, false for broadcast). Not yet supported — messages are fire-and-forget.",
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
type IrcParams = Static<typeof IrcParams>;
|
|
46
|
+
|
|
47
|
+
interface IrcDetails {
|
|
48
|
+
op: "send" | "list";
|
|
49
|
+
from?: string;
|
|
50
|
+
to?: string;
|
|
51
|
+
delivered?: string[];
|
|
52
|
+
notFound?: string[];
|
|
53
|
+
peers?: Array<{ id: string; status: string }>;
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create an `irc` tool definition for a specific agent.
|
|
59
|
+
*
|
|
60
|
+
* @param selfId — This agent's ID (runId:taskId format)
|
|
61
|
+
*/
|
|
62
|
+
export function createIrcTool(
|
|
63
|
+
selfId: string,
|
|
64
|
+
): ToolDefinition<typeof IrcParams, IrcDetails> {
|
|
65
|
+
return defineTool({
|
|
66
|
+
name: "irc",
|
|
67
|
+
label: "IRC",
|
|
68
|
+
description:
|
|
69
|
+
"Send messages to other live agents in same team. " +
|
|
70
|
+
'Use `op: "list"` to see peers, `op: "send"` with `to` (agent ID or "all") and `message` to communicate.',
|
|
71
|
+
parameters: IrcParams,
|
|
72
|
+
promptSnippet: "Send messages to other live agents via the irc tool",
|
|
73
|
+
promptGuidelines: [
|
|
74
|
+
"Use irc to coordinate with other agents when you need information or want to share findings.",
|
|
75
|
+
'Use `op: "list"` first to discover available peers.',
|
|
76
|
+
],
|
|
77
|
+
async execute(
|
|
78
|
+
_toolCallId: string,
|
|
79
|
+
params: IrcParams,
|
|
80
|
+
_signal: AbortSignal | undefined,
|
|
81
|
+
_onUpdate: unknown,
|
|
82
|
+
_ctx: unknown,
|
|
83
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; details: IrcDetails }> {
|
|
84
|
+
if (params.op === "list") {
|
|
85
|
+
return executeList(selfId);
|
|
86
|
+
}
|
|
87
|
+
if (params.op === "send") {
|
|
88
|
+
return executeSend(selfId, params);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text: "Unknown irc op." }],
|
|
92
|
+
details: { op: params.op, from: selfId, error: "Unknown operation." },
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function executeList(selfId: string): { content: Array<{ type: "text"; text: string }>; details: IrcDetails } {
|
|
99
|
+
const agents = listLiveAgents();
|
|
100
|
+
const peers = agents
|
|
101
|
+
.filter((a) => a.agentId !== selfId && (a.status === "running" || a.status === "queued"))
|
|
102
|
+
.map((a) => ({ id: a.agentId, status: a.status }));
|
|
103
|
+
|
|
104
|
+
const lines: string[] = [];
|
|
105
|
+
if (peers.length === 0) {
|
|
106
|
+
lines.push("No other live agents.");
|
|
107
|
+
} else {
|
|
108
|
+
lines.push(`${peers.length} peer(s):`);
|
|
109
|
+
for (const peer of peers) {
|
|
110
|
+
lines.push(`- ${peer.id} (${peer.status})`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
116
|
+
details: { op: "list", from: selfId, peers },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function executeSend(
|
|
121
|
+
selfId: string,
|
|
122
|
+
params: IrcParams,
|
|
123
|
+
): { content: Array<{ type: "text"; text: string }>; details: IrcDetails } {
|
|
124
|
+
const to = params.to?.trim();
|
|
125
|
+
const message = params.message?.trim();
|
|
126
|
+
|
|
127
|
+
if (!to) {
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: "text", text: '`to` is required for op="send".' }],
|
|
130
|
+
details: { op: "send", from: selfId, error: "Missing 'to' field." },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (!message) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: '`message` is required for op="send".' }],
|
|
136
|
+
details: { op: "send", from: selfId, to, error: "Missing 'message' field." },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (to === selfId) {
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: "text", text: "Cannot send a message to yourself." }],
|
|
142
|
+
details: { op: "send", from: selfId, to, error: "Self-message not allowed." },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ircMessage: IrcMessage = {
|
|
147
|
+
from: selfId,
|
|
148
|
+
to,
|
|
149
|
+
content: message,
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
awaitReply: params.awaitReply,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const notFound: string[] = [];
|
|
155
|
+
const delivered: string[] = [];
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
if (to === "all") {
|
|
159
|
+
const recipients = broadcastIrcMessage(selfId, ircMessage);
|
|
160
|
+
delivered.push(...recipients);
|
|
161
|
+
} else {
|
|
162
|
+
// DM to specific agent
|
|
163
|
+
const agents = listLiveAgents();
|
|
164
|
+
const target = agents.find((a) => a.agentId === to);
|
|
165
|
+
if (!target || (target.status !== "running" && target.status !== "queued")) {
|
|
166
|
+
notFound.push(to);
|
|
167
|
+
} else {
|
|
168
|
+
try {
|
|
169
|
+
sendIrcMessage(to, ircMessage);
|
|
170
|
+
delivered.push(to);
|
|
171
|
+
} catch {
|
|
172
|
+
notFound.push(to);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
// Agent deregistered during routing — treat as not found
|
|
178
|
+
notFound.push(to);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const lines: string[] = [];
|
|
182
|
+
if (delivered.length > 0) {
|
|
183
|
+
lines.push(`Delivered to ${delivered.length} peer(s): ${delivered.join(", ")}`);
|
|
184
|
+
} else {
|
|
185
|
+
lines.push("No recipients received the message.");
|
|
186
|
+
}
|
|
187
|
+
if (notFound.length > 0) {
|
|
188
|
+
lines.push(`Unknown / unavailable peers: ${notFound.join(", ")}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
193
|
+
details: {
|
|
194
|
+
op: "send",
|
|
195
|
+
from: selfId,
|
|
196
|
+
to,
|
|
197
|
+
delivered: delivered.length > 0 ? delivered : undefined,
|
|
198
|
+
notFound: notFound.length > 0 ? notFound : undefined,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G1: Custom tool — submit_result.
|
|
3
|
+
*
|
|
4
|
+
* Registers a real `submit_result` tool in the Pi SDK session so that
|
|
5
|
+
* live-session workers can yield their result by calling a tool (instead of
|
|
6
|
+
* relying solely on prompt-based reminders).
|
|
7
|
+
*
|
|
8
|
+
* Adapted from oh-my-pi's `YieldTool` pattern. Uses Pi SDK's `defineTool()`
|
|
9
|
+
* and TypeBox schemas for validation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineTool, type ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
13
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
14
|
+
import type { YieldResult } from "../yield-handler.ts";
|
|
15
|
+
|
|
16
|
+
const SubmitResultParams = Type.Object({
|
|
17
|
+
summary: Type.String({ description: "Summary of completed work." }),
|
|
18
|
+
artifacts: Type.Optional(
|
|
19
|
+
Type.Record(Type.String(), Type.String(), {
|
|
20
|
+
description: "Key-value map of artifact labels to file paths or content.",
|
|
21
|
+
}),
|
|
22
|
+
),
|
|
23
|
+
structuredData: Type.Optional(
|
|
24
|
+
Type.Record(Type.String(), Type.Unknown(), {
|
|
25
|
+
description: "Structured key-value data to pass back to the orchestrator.",
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
type SubmitResultParams = Static<typeof SubmitResultParams>;
|
|
31
|
+
|
|
32
|
+
interface SubmitResultDetails {
|
|
33
|
+
summary: string;
|
|
34
|
+
artifacts?: Record<string, string>;
|
|
35
|
+
structuredData?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a `submit_result` tool definition that calls `onYield` when invoked.
|
|
40
|
+
*
|
|
41
|
+
* The tool is injected into the session via `createAgentSession({ customTools: [...] })`.
|
|
42
|
+
* When the model calls it, the result is captured via the `onYield` callback
|
|
43
|
+
* and the yield enforcement loop terminates.
|
|
44
|
+
*/
|
|
45
|
+
export function createSubmitResultTool(
|
|
46
|
+
onYield: (result: YieldResult) => void,
|
|
47
|
+
): ToolDefinition<typeof SubmitResultParams, SubmitResultDetails> {
|
|
48
|
+
return defineTool({
|
|
49
|
+
name: "submit_result",
|
|
50
|
+
label: "Submit Result",
|
|
51
|
+
description:
|
|
52
|
+
"Submit final task result. Call when task complete. " +
|
|
53
|
+
"Provide summary, optional artifacts (file paths/content), optional structured data.",
|
|
54
|
+
parameters: SubmitResultParams,
|
|
55
|
+
promptSnippet: "Submit your task result when done using submit_result",
|
|
56
|
+
promptGuidelines: [
|
|
57
|
+
"Always call submit_result when your task is complete, even if you were unable to finish.",
|
|
58
|
+
"Include a clear summary of what was accomplished.",
|
|
59
|
+
],
|
|
60
|
+
async execute(
|
|
61
|
+
toolCallId: string,
|
|
62
|
+
params: SubmitResultParams,
|
|
63
|
+
_signal: AbortSignal | undefined,
|
|
64
|
+
_onUpdate: unknown,
|
|
65
|
+
_ctx: unknown,
|
|
66
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SubmitResultDetails }> {
|
|
67
|
+
const result: YieldResult = {
|
|
68
|
+
summary: params.summary,
|
|
69
|
+
toolCallId,
|
|
70
|
+
...(params.artifacts ? { artifacts: params.artifacts } : {}),
|
|
71
|
+
...(params.structuredData ? { structuredData: params.structuredData } : {}),
|
|
72
|
+
};
|
|
73
|
+
// Build response first so the model always gets confirmation
|
|
74
|
+
const response: { content: Array<{ type: "text"; text: string }>; details: SubmitResultDetails } = {
|
|
75
|
+
content: [{ type: "text", text: "Result submitted successfully. Thank you." }],
|
|
76
|
+
details: {
|
|
77
|
+
summary: params.summary,
|
|
78
|
+
artifacts: params.artifacts,
|
|
79
|
+
structuredData: params.structuredData,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
onYield(result);
|
|
84
|
+
} catch {
|
|
85
|
+
// Yield handler failure should not prevent tool response
|
|
86
|
+
}
|
|
87
|
+
return response;
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -102,8 +102,10 @@ export class DeliveryCoordinator {
|
|
|
102
102
|
|
|
103
103
|
flushQueuedResults(): void {
|
|
104
104
|
if (!this.active || this.pending.length === 0) return;
|
|
105
|
-
|
|
105
|
+
// H7: Set flushing BEFORE splice to prevent re-entrancy
|
|
106
|
+
if (this.flushing) return;
|
|
106
107
|
this.flushing = true;
|
|
108
|
+
const batch = this.pending.splice(0);
|
|
107
109
|
try {
|
|
108
110
|
const retryLater: PendingDelivery[] = [];
|
|
109
111
|
for (const delivery of batch) {
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
2
|
-
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
3
|
-
import type { TeamConfig } from "../teams/team-config.ts";
|
|
4
|
-
import type { WorkflowConfig } from "../workflows/workflow-config.ts";
|
|
5
|
-
|
|
6
|
-
export function isDirectRun(manifest: Pick<TeamRunManifest, "team" | "workflow">): boolean {
|
|
7
|
-
return manifest.workflow === "direct-agent";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function directTeamAndWorkflowFromRun(manifest: TeamRunManifest, tasks: TeamTaskState[], agents: AgentConfig[]): { team: TeamConfig; workflow: WorkflowConfig } | undefined {
|
|
11
|
-
if (!isDirectRun(manifest)) return undefined;
|
|
12
|
-
const firstTask = tasks[0];
|
|
13
|
-
const agentName = firstTask?.agent ?? (manifest.team.replace(/^direct-/, "") || "executor");
|
|
14
|
-
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
15
|
-
const role = firstTask?.role ?? "agent";
|
|
16
|
-
const stepId = firstTask?.stepId ?? "01_agent";
|
|
17
|
-
return {
|
|
18
|
-
team: {
|
|
19
|
-
name: manifest.team,
|
|
20
|
-
description: `Direct subagent run for ${agentName}`,
|
|
21
|
-
source: "builtin",
|
|
22
|
-
filePath: "<generated>",
|
|
23
|
-
roles: [{ name: role, agent: agentName, description: agent?.description }],
|
|
24
|
-
defaultWorkflow: "direct-agent",
|
|
25
|
-
workspaceMode: manifest.workspaceMode,
|
|
26
|
-
},
|
|
27
|
-
workflow: {
|
|
28
|
-
name: manifest.workflow ?? "direct-agent",
|
|
29
|
-
description: `Direct task for ${agentName}`,
|
|
30
|
-
source: "builtin",
|
|
31
|
-
filePath: "<generated>",
|
|
32
|
-
steps: [{ id: stepId, role, task: "{goal}", model: firstTask?.model }],
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
}
|
|
1
|
+
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
2
|
+
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
3
|
+
import type { TeamConfig } from "../teams/team-config.ts";
|
|
4
|
+
import type { WorkflowConfig } from "../workflows/workflow-config.ts";
|
|
5
|
+
|
|
6
|
+
export function isDirectRun(manifest: Pick<TeamRunManifest, "team" | "workflow">): boolean {
|
|
7
|
+
return manifest.workflow === "direct-agent";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function directTeamAndWorkflowFromRun(manifest: TeamRunManifest, tasks: TeamTaskState[], agents: AgentConfig[]): { team: TeamConfig; workflow: WorkflowConfig } | undefined {
|
|
11
|
+
if (!isDirectRun(manifest)) return undefined;
|
|
12
|
+
const firstTask = tasks[0];
|
|
13
|
+
const agentName = firstTask?.agent ?? (manifest.team.replace(/^direct-/, "") || "executor");
|
|
14
|
+
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
15
|
+
const role = firstTask?.role ?? "agent";
|
|
16
|
+
const stepId = firstTask?.stepId ?? "01_agent";
|
|
17
|
+
return {
|
|
18
|
+
team: {
|
|
19
|
+
name: manifest.team,
|
|
20
|
+
description: `Direct subagent run for ${agentName}`,
|
|
21
|
+
source: "builtin",
|
|
22
|
+
filePath: "<generated>",
|
|
23
|
+
roles: [{ name: role, agent: agentName, description: agent?.description }],
|
|
24
|
+
defaultWorkflow: "direct-agent",
|
|
25
|
+
workspaceMode: manifest.workspaceMode,
|
|
26
|
+
},
|
|
27
|
+
workflow: {
|
|
28
|
+
name: manifest.workflow ?? "direct-agent",
|
|
29
|
+
description: `Direct task for ${agentName}`,
|
|
30
|
+
source: "builtin",
|
|
31
|
+
filePath: "<generated>",
|
|
32
|
+
steps: [{ id: stepId, role, task: "{goal}", model: firstTask?.model }],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|