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,119 +1,119 @@
|
|
|
1
|
-
import type { UsageState } from "../../state/types.ts";
|
|
2
|
-
import type { CrewAgentProgress } from "../crew-agent-runtime.ts";
|
|
3
|
-
import { emptyCrewAgentProgress } from "../crew-agent-records.ts";
|
|
4
|
-
import type { ProgressEventSummary } from "../progress-event-coalescer.ts";
|
|
5
|
-
import type { TeamTaskState } from "../../state/types.ts";
|
|
6
|
-
|
|
7
|
-
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
8
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function safeNum(v: number | undefined): number {
|
|
12
|
-
return Number.isFinite(v) ? v! : 0;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function textFromContent(content: unknown): string[] {
|
|
16
|
-
if (typeof content === "string") return [content];
|
|
17
|
-
if (!Array.isArray(content)) return [];
|
|
18
|
-
const text: string[] = [];
|
|
19
|
-
for (const part of content) {
|
|
20
|
-
const obj = asRecord(part);
|
|
21
|
-
if (!obj) continue;
|
|
22
|
-
if (obj.type === "text" && typeof obj.text === "string") text.push(obj.text);
|
|
23
|
-
else if (typeof obj.content === "string") text.push(obj.content);
|
|
24
|
-
}
|
|
25
|
-
return text;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function eventText(event: unknown): string[] {
|
|
29
|
-
const obj = asRecord(event);
|
|
30
|
-
if (!obj) return [];
|
|
31
|
-
const text: string[] = [];
|
|
32
|
-
if (typeof obj.text === "string") text.push(obj.text);
|
|
33
|
-
if (typeof obj.output === "string") text.push(obj.output);
|
|
34
|
-
text.push(...textFromContent(obj.content));
|
|
35
|
-
const message = asRecord(obj.message);
|
|
36
|
-
if (message) text.push(...textFromContent(message.content));
|
|
37
|
-
return text.filter((entry) => entry.trim());
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
|
|
41
|
-
for (const key of keys) {
|
|
42
|
-
const value = obj[key];
|
|
43
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
44
|
-
}
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function eventUsage(event: unknown): { input?: number; output?: number; turns?: number } | undefined {
|
|
49
|
-
const obj = asRecord(event);
|
|
50
|
-
if (!obj) return undefined;
|
|
51
|
-
const direct = { input: numberField(obj, ["input", "inputTokens", "input_tokens"]), output: numberField(obj, ["output", "outputTokens", "output_tokens"]), turns: numberField(obj, ["turns", "turnCount", "turn_count"]) };
|
|
52
|
-
if (Object.values(direct).some((value) => value !== undefined)) return direct;
|
|
53
|
-
for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
|
|
54
|
-
const nested = eventUsage(obj[key]);
|
|
55
|
-
if (nested) return nested;
|
|
56
|
-
}
|
|
57
|
-
const message = asRecord(obj.message);
|
|
58
|
-
return message ? eventUsage(message.usage) : undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function previewArgs(args: unknown): string | undefined {
|
|
62
|
-
if (!args) return undefined;
|
|
63
|
-
try {
|
|
64
|
-
const text = typeof args === "string" ? args : JSON.stringify(args);
|
|
65
|
-
return text.length > 240 ? `${text.slice(0, 240)}…` : text;
|
|
66
|
-
} catch {
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function applyUsageToProgress(progress: CrewAgentProgress | undefined, usage: UsageState | undefined): CrewAgentProgress | undefined {
|
|
72
|
-
if (!usage) return progress;
|
|
73
|
-
const base = progress ?? emptyCrewAgentProgress();
|
|
74
|
-
const tokens = safeNum(usage.input) + safeNum(usage.output) + safeNum(usage.cacheRead) + safeNum(usage.cacheWrite);
|
|
75
|
-
return { ...base, tokens, turns: usage.turns ?? base.turns };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function shouldFlushProgressEvent(event: unknown): boolean {
|
|
79
|
-
const type = asRecord(event)?.type;
|
|
80
|
-
return type === "tool_execution_start" || type === "tool_execution_end" || type === "message_end" || type === "tool_result_end";
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function progressEventSummary(task: TeamTaskState, event: unknown): ProgressEventSummary {
|
|
84
|
-
const type = asRecord(event)?.type;
|
|
85
|
-
return { eventType: typeof type === "string" ? type : "event", currentTool: task.agentProgress?.currentTool, toolCount: task.agentProgress?.toolCount, tokens: task.agentProgress?.tokens, turns: task.agentProgress?.turns, activityState: task.agentProgress?.activityState, lastActivityAt: task.agentProgress?.lastActivityAt };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function applyAgentProgressEvent(progress: CrewAgentProgress, event: unknown, startedAt: string | undefined): CrewAgentProgress {
|
|
89
|
-
const obj = asRecord(event);
|
|
90
|
-
const now = new Date().toISOString();
|
|
91
|
-
const next: CrewAgentProgress = { ...progress, recentTools: [...progress.recentTools], recentOutput: [...progress.recentOutput], lastActivityAt: now, activityState: "active" };
|
|
92
|
-
if (startedAt) {
|
|
93
|
-
const startMs = new Date(startedAt).getTime();
|
|
94
|
-
next.durationMs = Number.isFinite(startMs) ? Date.now() - startMs : undefined;
|
|
95
|
-
}
|
|
96
|
-
if (obj?.type === "tool_execution_start") {
|
|
97
|
-
next.toolCount += 1;
|
|
98
|
-
next.currentTool = typeof obj.toolName === "string" ? obj.toolName : typeof obj.name === "string" ? obj.name : "tool";
|
|
99
|
-
next.currentToolArgs = previewArgs(obj.args);
|
|
100
|
-
next.currentToolStartedAt = now;
|
|
101
|
-
}
|
|
102
|
-
if (obj?.type === "tool_execution_end") {
|
|
103
|
-
if (next.currentTool) next.recentTools.push({ tool: next.currentTool, args: next.currentToolArgs, endedAt: now });
|
|
104
|
-
next.currentTool = undefined;
|
|
105
|
-
next.currentToolArgs = undefined;
|
|
106
|
-
next.currentToolStartedAt = undefined;
|
|
107
|
-
}
|
|
108
|
-
if ((obj?.type === "tool_execution_error" || obj?.type === "tool_execution_failed") && next.currentTool) next.failedTool = next.currentTool;
|
|
109
|
-
const usage = eventUsage(event);
|
|
110
|
-
if (usage) {
|
|
111
|
-
next.tokens = safeNum(usage.input) + safeNum(usage.output);
|
|
112
|
-
next.turns = usage.turns ?? next.turns;
|
|
113
|
-
}
|
|
114
|
-
const text = eventText(event);
|
|
115
|
-
if (text.length > 0) next.recentOutput.push(...text.flatMap((entry) => entry.split(/\r?\n/)).filter(Boolean).slice(-10));
|
|
116
|
-
if (next.recentTools.length > 25) next.recentTools.splice(0, next.recentTools.length - 25);
|
|
117
|
-
if (next.recentOutput.length > 50) next.recentOutput.splice(0, next.recentOutput.length - 50);
|
|
118
|
-
return next;
|
|
119
|
-
}
|
|
1
|
+
import type { UsageState } from "../../state/types.ts";
|
|
2
|
+
import type { CrewAgentProgress } from "../crew-agent-runtime.ts";
|
|
3
|
+
import { emptyCrewAgentProgress } from "../crew-agent-records.ts";
|
|
4
|
+
import type { ProgressEventSummary } from "../progress-event-coalescer.ts";
|
|
5
|
+
import type { TeamTaskState } from "../../state/types.ts";
|
|
6
|
+
|
|
7
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
8
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function safeNum(v: number | undefined): number {
|
|
12
|
+
return Number.isFinite(v) ? v! : 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function textFromContent(content: unknown): string[] {
|
|
16
|
+
if (typeof content === "string") return [content];
|
|
17
|
+
if (!Array.isArray(content)) return [];
|
|
18
|
+
const text: string[] = [];
|
|
19
|
+
for (const part of content) {
|
|
20
|
+
const obj = asRecord(part);
|
|
21
|
+
if (!obj) continue;
|
|
22
|
+
if (obj.type === "text" && typeof obj.text === "string") text.push(obj.text);
|
|
23
|
+
else if (typeof obj.content === "string") text.push(obj.content);
|
|
24
|
+
}
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function eventText(event: unknown): string[] {
|
|
29
|
+
const obj = asRecord(event);
|
|
30
|
+
if (!obj) return [];
|
|
31
|
+
const text: string[] = [];
|
|
32
|
+
if (typeof obj.text === "string") text.push(obj.text);
|
|
33
|
+
if (typeof obj.output === "string") text.push(obj.output);
|
|
34
|
+
text.push(...textFromContent(obj.content));
|
|
35
|
+
const message = asRecord(obj.message);
|
|
36
|
+
if (message) text.push(...textFromContent(message.content));
|
|
37
|
+
return text.filter((entry) => entry.trim());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
const value = obj[key];
|
|
43
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function eventUsage(event: unknown): { input?: number; output?: number; turns?: number } | undefined {
|
|
49
|
+
const obj = asRecord(event);
|
|
50
|
+
if (!obj) return undefined;
|
|
51
|
+
const direct = { input: numberField(obj, ["input", "inputTokens", "input_tokens"]), output: numberField(obj, ["output", "outputTokens", "output_tokens"]), turns: numberField(obj, ["turns", "turnCount", "turn_count"]) };
|
|
52
|
+
if (Object.values(direct).some((value) => value !== undefined)) return direct;
|
|
53
|
+
for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
|
|
54
|
+
const nested = eventUsage(obj[key]);
|
|
55
|
+
if (nested) return nested;
|
|
56
|
+
}
|
|
57
|
+
const message = asRecord(obj.message);
|
|
58
|
+
return message ? eventUsage(message.usage) : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function previewArgs(args: unknown): string | undefined {
|
|
62
|
+
if (!args) return undefined;
|
|
63
|
+
try {
|
|
64
|
+
const text = typeof args === "string" ? args : JSON.stringify(args);
|
|
65
|
+
return text.length > 240 ? `${text.slice(0, 240)}…` : text;
|
|
66
|
+
} catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function applyUsageToProgress(progress: CrewAgentProgress | undefined, usage: UsageState | undefined): CrewAgentProgress | undefined {
|
|
72
|
+
if (!usage) return progress;
|
|
73
|
+
const base = progress ?? emptyCrewAgentProgress();
|
|
74
|
+
const tokens = safeNum(usage.input) + safeNum(usage.output) + safeNum(usage.cacheRead) + safeNum(usage.cacheWrite);
|
|
75
|
+
return { ...base, tokens, turns: usage.turns ?? base.turns };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function shouldFlushProgressEvent(event: unknown): boolean {
|
|
79
|
+
const type = asRecord(event)?.type;
|
|
80
|
+
return type === "tool_execution_start" || type === "tool_execution_end" || type === "message_end" || type === "tool_result_end";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function progressEventSummary(task: TeamTaskState, event: unknown): ProgressEventSummary {
|
|
84
|
+
const type = asRecord(event)?.type;
|
|
85
|
+
return { eventType: typeof type === "string" ? type : "event", currentTool: task.agentProgress?.currentTool, toolCount: task.agentProgress?.toolCount, tokens: task.agentProgress?.tokens, turns: task.agentProgress?.turns, activityState: task.agentProgress?.activityState, lastActivityAt: task.agentProgress?.lastActivityAt };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function applyAgentProgressEvent(progress: CrewAgentProgress, event: unknown, startedAt: string | undefined): CrewAgentProgress {
|
|
89
|
+
const obj = asRecord(event);
|
|
90
|
+
const now = new Date().toISOString();
|
|
91
|
+
const next: CrewAgentProgress = { ...progress, recentTools: [...progress.recentTools], recentOutput: [...progress.recentOutput], lastActivityAt: now, activityState: "active" };
|
|
92
|
+
if (startedAt) {
|
|
93
|
+
const startMs = new Date(startedAt).getTime();
|
|
94
|
+
next.durationMs = Number.isFinite(startMs) ? Date.now() - startMs : undefined;
|
|
95
|
+
}
|
|
96
|
+
if (obj?.type === "tool_execution_start") {
|
|
97
|
+
next.toolCount += 1;
|
|
98
|
+
next.currentTool = typeof obj.toolName === "string" ? obj.toolName : typeof obj.name === "string" ? obj.name : "tool";
|
|
99
|
+
next.currentToolArgs = previewArgs(obj.args);
|
|
100
|
+
next.currentToolStartedAt = now;
|
|
101
|
+
}
|
|
102
|
+
if (obj?.type === "tool_execution_end") {
|
|
103
|
+
if (next.currentTool) next.recentTools.push({ tool: next.currentTool, args: next.currentToolArgs, endedAt: now });
|
|
104
|
+
next.currentTool = undefined;
|
|
105
|
+
next.currentToolArgs = undefined;
|
|
106
|
+
next.currentToolStartedAt = undefined;
|
|
107
|
+
}
|
|
108
|
+
if ((obj?.type === "tool_execution_error" || obj?.type === "tool_execution_failed") && next.currentTool) next.failedTool = next.currentTool;
|
|
109
|
+
const usage = eventUsage(event);
|
|
110
|
+
if (usage) {
|
|
111
|
+
next.tokens = safeNum(usage.input) + safeNum(usage.output);
|
|
112
|
+
next.turns = usage.turns ?? next.turns;
|
|
113
|
+
}
|
|
114
|
+
const text = eventText(event);
|
|
115
|
+
if (text.length > 0) next.recentOutput.push(...text.flatMap((entry) => entry.split(/\r?\n/)).filter(Boolean).slice(-10));
|
|
116
|
+
if (next.recentTools.length > 25) next.recentTools.splice(0, next.recentTools.length - 25);
|
|
117
|
+
if (next.recentOutput.length > 50) next.recentOutput.splice(0, next.recentOutput.length - 50);
|
|
118
|
+
return next;
|
|
119
|
+
}
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import type { AgentConfig } from "../../agents/agent-config.ts";
|
|
2
|
-
import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
|
+
import type { TeamRunManifest, TeamTaskState, TaskOutputSchema } from "../../state/types.ts";
|
|
3
3
|
import type { WorkflowStep } from "../../workflows/workflow-config.ts";
|
|
4
4
|
import { buildMemoryBlock } from "../agent-memory.ts";
|
|
5
5
|
import { permissionForRole } from "../role-permission.ts";
|
|
6
6
|
import { renderTaskPacket } from "../task-packet.ts";
|
|
7
|
+
import { buildWorkspaceTree } from "../workspace-tree.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* When loadMode is "lean", emit a tool guidance block that tells the worker
|
|
11
|
+
* which tools to prefer. This is a prompt-level hint only — actual tool
|
|
12
|
+
* filtering at the Pi level is a future optimisation (Phase 3.2+).
|
|
13
|
+
*/
|
|
14
|
+
export function toolGuidanceBlock(agent?: AgentConfig): string {
|
|
15
|
+
if (!agent || agent.loadMode !== "lean" || !agent.defaultTools?.length) return "";
|
|
16
|
+
return [
|
|
17
|
+
"# Tool Guidance",
|
|
18
|
+
`This role uses a focused tool set. Preferred tools: ${agent.defaultTools.join(", ")}.`,
|
|
19
|
+
"Other tools are available but should only be used when explicitly needed for the task.",
|
|
20
|
+
].join("\n");
|
|
21
|
+
}
|
|
7
22
|
|
|
8
23
|
function readOnlyRoleInstructions(role: string): string {
|
|
9
24
|
if (permissionForRole(role) !== "read_only") return "";
|
|
@@ -36,9 +51,45 @@ function inputDependencyContext(task: TeamTaskState): string {
|
|
|
36
51
|
return (task as TeamTaskState & { dependencyContextText?: string }).dependencyContextText ?? "";
|
|
37
52
|
}
|
|
38
53
|
|
|
39
|
-
export function
|
|
54
|
+
export function renderOutputSchemaBlock(outputSchema: TaskOutputSchema): string {
|
|
55
|
+
const lines: string[] = ["## Expected Output Format"];
|
|
56
|
+
lines.push(`Your final output must be ${outputSchema.format}.`);
|
|
57
|
+
if (outputSchema.description) {
|
|
58
|
+
lines.push(outputSchema.description);
|
|
59
|
+
}
|
|
60
|
+
if (outputSchema.format === "json" && outputSchema.schema) {
|
|
61
|
+
lines.push("The output must match this schema:");
|
|
62
|
+
lines.push("```json");
|
|
63
|
+
lines.push(JSON.stringify(outputSchema.schema, null, 2));
|
|
64
|
+
lines.push("```");
|
|
65
|
+
}
|
|
66
|
+
if (outputSchema.example) {
|
|
67
|
+
lines.push("Example output:");
|
|
68
|
+
lines.push("```");
|
|
69
|
+
lines.push(outputSchema.example);
|
|
70
|
+
lines.push("```");
|
|
71
|
+
}
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface RenderedTaskPrompt {
|
|
76
|
+
/** Stable sections that rarely change between tasks of the same role/cwd. */
|
|
77
|
+
stablePrefix: string;
|
|
78
|
+
/** Dynamic sections that change per-task (goal, task packet, skills, dependency context). */
|
|
79
|
+
dynamicSuffix: string;
|
|
80
|
+
/** Full rendered prompt (stablePrefix + dynamicSuffix). */
|
|
81
|
+
full: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function renderTaskPrompt(manifest: TeamRunManifest, step: WorkflowStep, task: TeamTaskState, agent?: AgentConfig, skillBlock = ""): Promise<RenderedTaskPrompt> {
|
|
40
85
|
const memoryBlock = agent?.memory ? buildMemoryBlock(agent.name, agent.memory, task.cwd, Boolean(agent.tools?.some((tool) => tool === "write" || tool === "edit"))) : "";
|
|
41
|
-
|
|
86
|
+
|
|
87
|
+
// Build workspace tree for stable context
|
|
88
|
+
const tree = await buildWorkspaceTree(task.cwd);
|
|
89
|
+
const treeBlock = tree.rendered ? `# Workspace Structure\n${tree.rendered}` : "";
|
|
90
|
+
|
|
91
|
+
// Stable prefix: role instructions, coordination, workspace tree — rarely changes
|
|
92
|
+
const stablePrefix = [
|
|
42
93
|
"# pi-crew Worker Runtime Context",
|
|
43
94
|
`Run ID: ${manifest.runId}`,
|
|
44
95
|
`Team: ${manifest.team}`,
|
|
@@ -50,11 +101,6 @@ export function renderTaskPrompt(manifest: TeamRunManifest, step: WorkflowStep,
|
|
|
50
101
|
`Task cwd: ${task.cwd}`,
|
|
51
102
|
`Workspace mode: ${manifest.workspaceMode}`,
|
|
52
103
|
"",
|
|
53
|
-
`Goal:\n${manifest.goal}`,
|
|
54
|
-
"",
|
|
55
|
-
`Step: ${step.id}`,
|
|
56
|
-
`Role: ${step.role}`,
|
|
57
|
-
"",
|
|
58
104
|
"Protocol:",
|
|
59
105
|
"- Stay within the task scope unless the prompt explicitly says otherwise.",
|
|
60
106
|
"- Report blockers and verification evidence in the final result.",
|
|
@@ -65,13 +111,29 @@ export function renderTaskPrompt(manifest: TeamRunManifest, step: WorkflowStep,
|
|
|
65
111
|
"",
|
|
66
112
|
coordinationBridgeInstructions(task),
|
|
67
113
|
"",
|
|
114
|
+
treeBlock,
|
|
115
|
+
"",
|
|
116
|
+
toolGuidanceBlock(agent),
|
|
117
|
+
].filter(Boolean).join("\n");
|
|
118
|
+
|
|
119
|
+
// Dynamic suffix: goal, step, skills, task packet, dependency context, memory — changes per task
|
|
120
|
+
const dynamicSuffix = [
|
|
121
|
+
`Goal:\n${manifest.goal}`,
|
|
122
|
+
"",
|
|
123
|
+
`Step: ${step.id}`,
|
|
124
|
+
`Role: ${step.role}`,
|
|
125
|
+
"",
|
|
68
126
|
skillBlock,
|
|
69
127
|
"",
|
|
70
128
|
task.taskPacket ? renderTaskPacket(task.taskPacket) : "",
|
|
71
129
|
"",
|
|
72
130
|
(inputDependencyContext(task) || ""),
|
|
73
131
|
memoryBlock,
|
|
132
|
+
task.taskPacket?.outputSchema ? renderOutputSchemaBlock(task.taskPacket.outputSchema) : "",
|
|
74
133
|
"Task:",
|
|
75
134
|
step.task.replaceAll("{goal}", manifest.goal),
|
|
76
135
|
].join("\n");
|
|
136
|
+
|
|
137
|
+
const full = [stablePrefix, "", dynamicSuffix].join("\n");
|
|
138
|
+
return { stablePrefix, dynamicSuffix, full };
|
|
77
139
|
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
|
-
import type { ArtifactDescriptor } from "../../state/types.ts";
|
|
3
|
-
|
|
4
|
-
export type WorkerPromptPipelineStageName =
|
|
5
|
-
| "task-packet-built"
|
|
6
|
-
| "dependency-context-collected"
|
|
7
|
-
| "skills-rendered-or-disabled"
|
|
8
|
-
| "capability-inventory-recorded"
|
|
9
|
-
| "coordination-bridge-attached"
|
|
10
|
-
| "prompt-rendered"
|
|
11
|
-
| "prompt-artifact-written";
|
|
12
|
-
|
|
13
|
-
export interface WorkerPromptPipelineStage {
|
|
14
|
-
name: WorkerPromptPipelineStageName;
|
|
15
|
-
references: string[];
|
|
16
|
-
details?: Record<string, string | number | boolean>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface WorkerPromptPipelineArtifact {
|
|
20
|
-
schemaVersion: 1;
|
|
21
|
-
taskId: string;
|
|
22
|
-
stages: WorkerPromptPipelineStage[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function artifactReference(artifactsRoot: string, artifact?: ArtifactDescriptor): string | undefined {
|
|
26
|
-
if (!artifact) return undefined;
|
|
27
|
-
const root = path.resolve(artifactsRoot);
|
|
28
|
-
const target = path.resolve(artifact.path);
|
|
29
|
-
const relative = path.relative(root, target);
|
|
30
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return undefined;
|
|
31
|
-
return relative.replaceAll("\\", "/");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface BuildWorkerPromptPipelineInput {
|
|
35
|
-
artifactsRoot: string;
|
|
36
|
-
taskId: string;
|
|
37
|
-
promptArtifact: ArtifactDescriptor;
|
|
38
|
-
inputsArtifact: ArtifactDescriptor;
|
|
39
|
-
skillArtifact?: ArtifactDescriptor;
|
|
40
|
-
capabilityArtifact: ArtifactDescriptor;
|
|
41
|
-
coordinationArtifact: ArtifactDescriptor;
|
|
42
|
-
skillInstructionCount: number;
|
|
43
|
-
skillsDisabled: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function buildWorkerPromptPipeline(input: BuildWorkerPromptPipelineInput): WorkerPromptPipelineArtifact {
|
|
47
|
-
return {
|
|
48
|
-
schemaVersion: 1,
|
|
49
|
-
taskId: input.taskId,
|
|
50
|
-
stages: [
|
|
51
|
-
{ name: "task-packet-built", references: [`metadata/${input.taskId}.task-packet.json`] },
|
|
52
|
-
{ name: "dependency-context-collected", references: [artifactReference(input.artifactsRoot, input.inputsArtifact) ?? `metadata/${input.taskId}.inputs.json`] },
|
|
53
|
-
{
|
|
54
|
-
name: "skills-rendered-or-disabled",
|
|
55
|
-
references: input.skillArtifact ? [artifactReference(input.artifactsRoot, input.skillArtifact) ?? `metadata/${input.taskId}.skills.md`] : [],
|
|
56
|
-
details: { disabled: input.skillsDisabled, skillInstructionCount: input.skillInstructionCount },
|
|
57
|
-
},
|
|
58
|
-
{ name: "capability-inventory-recorded", references: [artifactReference(input.artifactsRoot, input.capabilityArtifact) ?? `metadata/${input.taskId}.capabilities.json`] },
|
|
59
|
-
{ name: "coordination-bridge-attached", references: [artifactReference(input.artifactsRoot, input.coordinationArtifact) ?? `metadata/${input.taskId}.coordination-bridge.md`] },
|
|
60
|
-
{ name: "prompt-rendered", references: [] },
|
|
61
|
-
{ name: "prompt-artifact-written", references: [artifactReference(input.artifactsRoot, input.promptArtifact) ?? `prompts/${input.taskId}.md`] },
|
|
62
|
-
],
|
|
63
|
-
};
|
|
64
|
-
}
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { ArtifactDescriptor } from "../../state/types.ts";
|
|
3
|
+
|
|
4
|
+
export type WorkerPromptPipelineStageName =
|
|
5
|
+
| "task-packet-built"
|
|
6
|
+
| "dependency-context-collected"
|
|
7
|
+
| "skills-rendered-or-disabled"
|
|
8
|
+
| "capability-inventory-recorded"
|
|
9
|
+
| "coordination-bridge-attached"
|
|
10
|
+
| "prompt-rendered"
|
|
11
|
+
| "prompt-artifact-written";
|
|
12
|
+
|
|
13
|
+
export interface WorkerPromptPipelineStage {
|
|
14
|
+
name: WorkerPromptPipelineStageName;
|
|
15
|
+
references: string[];
|
|
16
|
+
details?: Record<string, string | number | boolean>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WorkerPromptPipelineArtifact {
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
taskId: string;
|
|
22
|
+
stages: WorkerPromptPipelineStage[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function artifactReference(artifactsRoot: string, artifact?: ArtifactDescriptor): string | undefined {
|
|
26
|
+
if (!artifact) return undefined;
|
|
27
|
+
const root = path.resolve(artifactsRoot);
|
|
28
|
+
const target = path.resolve(artifact.path);
|
|
29
|
+
const relative = path.relative(root, target);
|
|
30
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return undefined;
|
|
31
|
+
return relative.replaceAll("\\", "/");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BuildWorkerPromptPipelineInput {
|
|
35
|
+
artifactsRoot: string;
|
|
36
|
+
taskId: string;
|
|
37
|
+
promptArtifact: ArtifactDescriptor;
|
|
38
|
+
inputsArtifact: ArtifactDescriptor;
|
|
39
|
+
skillArtifact?: ArtifactDescriptor;
|
|
40
|
+
capabilityArtifact: ArtifactDescriptor;
|
|
41
|
+
coordinationArtifact: ArtifactDescriptor;
|
|
42
|
+
skillInstructionCount: number;
|
|
43
|
+
skillsDisabled: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildWorkerPromptPipeline(input: BuildWorkerPromptPipelineInput): WorkerPromptPipelineArtifact {
|
|
47
|
+
return {
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
taskId: input.taskId,
|
|
50
|
+
stages: [
|
|
51
|
+
{ name: "task-packet-built", references: [`metadata/${input.taskId}.task-packet.json`] },
|
|
52
|
+
{ name: "dependency-context-collected", references: [artifactReference(input.artifactsRoot, input.inputsArtifact) ?? `metadata/${input.taskId}.inputs.json`] },
|
|
53
|
+
{
|
|
54
|
+
name: "skills-rendered-or-disabled",
|
|
55
|
+
references: input.skillArtifact ? [artifactReference(input.artifactsRoot, input.skillArtifact) ?? `metadata/${input.taskId}.skills.md`] : [],
|
|
56
|
+
details: { disabled: input.skillsDisabled, skillInstructionCount: input.skillInstructionCount },
|
|
57
|
+
},
|
|
58
|
+
{ name: "capability-inventory-recorded", references: [artifactReference(input.artifactsRoot, input.capabilityArtifact) ?? `metadata/${input.taskId}.capabilities.json`] },
|
|
59
|
+
{ name: "coordination-bridge-attached", references: [artifactReference(input.artifactsRoot, input.coordinationArtifact) ?? `metadata/${input.taskId}.coordination-bridge.md`] },
|
|
60
|
+
{ name: "prompt-rendered", references: [] },
|
|
61
|
+
{ name: "prompt-artifact-written", references: [artifactReference(input.artifactsRoot, input.promptArtifact) ?? `prompts/${input.taskId}.md`] },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
export function cleanResultText(text: string | undefined): string | undefined {
|
|
2
|
-
const trimmed = text?.trim();
|
|
3
|
-
if (!trimmed) return undefined;
|
|
4
|
-
const doneIndex = trimmed.lastIndexOf("\nDONE\n");
|
|
5
|
-
if (doneIndex >= 0) return trimmed.slice(doneIndex + 1).trim();
|
|
6
|
-
if (trimmed === "DONE" || trimmed.startsWith("DONE\n")) return trimmed;
|
|
7
|
-
const fencedPromptIndex = trimmed.lastIndexOf("</file>");
|
|
8
|
-
if (fencedPromptIndex >= 0 && fencedPromptIndex < trimmed.length - 7) return trimmed.slice(fencedPromptIndex + 7).trim() || trimmed;
|
|
9
|
-
return trimmed;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function isFinalChildEvent(event: unknown): boolean {
|
|
13
|
-
return Boolean(event && typeof event === "object" && !Array.isArray(event) && (event as Record<string, unknown>).type === "message_end");
|
|
14
|
-
}
|
|
1
|
+
export function cleanResultText(text: string | undefined): string | undefined {
|
|
2
|
+
const trimmed = text?.trim();
|
|
3
|
+
if (!trimmed) return undefined;
|
|
4
|
+
const doneIndex = trimmed.lastIndexOf("\nDONE\n");
|
|
5
|
+
if (doneIndex >= 0) return trimmed.slice(doneIndex + 1).trim();
|
|
6
|
+
if (trimmed === "DONE" || trimmed.startsWith("DONE\n")) return trimmed;
|
|
7
|
+
const fencedPromptIndex = trimmed.lastIndexOf("</file>");
|
|
8
|
+
if (fencedPromptIndex >= 0 && fencedPromptIndex < trimmed.length - 7) return trimmed.slice(fencedPromptIndex + 7).trim() || trimmed;
|
|
9
|
+
return trimmed;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isFinalChildEvent(event: unknown): boolean {
|
|
13
|
+
return Boolean(event && typeof event === "object" && !Array.isArray(event) && (event as Record<string, unknown>).type === "message_end");
|
|
14
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
|
+
import type { MailboxMessage } from "../../state/mailbox.ts";
|
|
3
|
+
import type { ArtifactDescriptor } from "../../state/types.ts";
|
|
4
|
+
|
|
5
|
+
export interface RunProjectionSource {
|
|
6
|
+
kind: "events" | "mailbox" | "artifacts" | "ui_metadata" | "runtime_metadata";
|
|
7
|
+
bounded: boolean;
|
|
8
|
+
reference?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RunProjectionResult {
|
|
12
|
+
sources: RunProjectionSource[];
|
|
13
|
+
summary: string;
|
|
14
|
+
injectedAsContext: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transform run context before a worker starts.
|
|
19
|
+
* Builds a bounded projection of durable history that will be available
|
|
20
|
+
* to the worker as reference context, not as instructions.
|
|
21
|
+
*
|
|
22
|
+
* Rules:
|
|
23
|
+
* - Durable history retains events, mailbox, artifacts, UI/runtime metadata.
|
|
24
|
+
* - Worker prompt gets a bounded projection (truncated/summarized).
|
|
25
|
+
* - UI/runtime events are not prompt text unless explicitly selected.
|
|
26
|
+
*/
|
|
27
|
+
export function transformRunContextBeforeWorkerStart(input: {
|
|
28
|
+
manifest: TeamRunManifest;
|
|
29
|
+
tasks: TeamTaskState[];
|
|
30
|
+
pendingMailbox: MailboxMessage[];
|
|
31
|
+
artifacts: ArtifactDescriptor[];
|
|
32
|
+
maxEvents?: number;
|
|
33
|
+
maxMailboxMessages?: number;
|
|
34
|
+
maxArtifactRefs?: number;
|
|
35
|
+
}): RunProjectionResult {
|
|
36
|
+
const maxEvents = input.maxEvents ?? 20;
|
|
37
|
+
const maxMailbox = input.maxMailboxMessages ?? 10;
|
|
38
|
+
const maxArtifacts = input.maxArtifactRefs ?? 15;
|
|
39
|
+
|
|
40
|
+
const sources: RunProjectionSource[] = [];
|
|
41
|
+
const lines: string[] = [];
|
|
42
|
+
|
|
43
|
+
// Project a bounded slice of task history
|
|
44
|
+
const completedTasks = input.tasks.filter((t) => t.status === "completed" || t.status === "failed");
|
|
45
|
+
if (completedTasks.length > 0) {
|
|
46
|
+
const tasks = completedTasks.slice(0, maxEvents);
|
|
47
|
+
sources.push({ kind: "events", bounded: true, reference: `tasks:${tasks.length}/${completedTasks.length}` });
|
|
48
|
+
lines.push(`Previous tasks (${tasks.length}/${completedTasks.length}):`);
|
|
49
|
+
for (const task of tasks) {
|
|
50
|
+
lines.push(`- ${task.id}: ${task.status}${task.error ? ` (${task.error})` : ""}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Project pending mailbox that is relevant to this worker
|
|
55
|
+
if (input.pendingMailbox.length > 0) {
|
|
56
|
+
const messages = input.pendingMailbox.slice(0, maxMailbox);
|
|
57
|
+
sources.push({ kind: "mailbox", bounded: true, reference: `mailbox:${messages.length}/${input.pendingMailbox.length}` });
|
|
58
|
+
lines.push(`Pending messages (${messages.length}/${input.pendingMailbox.length}):`);
|
|
59
|
+
for (const msg of messages) {
|
|
60
|
+
lines.push(`- ${msg.kind ?? "message"}: ${msg.body.slice(0, 100)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Project artifact references (not content)
|
|
65
|
+
if (input.artifacts.length > 0) {
|
|
66
|
+
const artifacts = input.artifacts.slice(0, maxArtifacts);
|
|
67
|
+
sources.push({ kind: "artifacts", bounded: true, reference: `artifacts:${artifacts.length}/${input.artifacts.length}` });
|
|
68
|
+
lines.push(`Available artifacts (${artifacts.length}/${input.artifacts.length}):`);
|
|
69
|
+
for (const art of artifacts) {
|
|
70
|
+
lines.push(`- ${art.kind} (${art.producer})`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Metadata markers — not injected as prompt instructions
|
|
75
|
+
sources.push({ kind: "ui_metadata", bounded: false, reference: "excluded_from_prompt" });
|
|
76
|
+
sources.push({ kind: "runtime_metadata", bounded: false, reference: "excluded_from_prompt" });
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
sources,
|
|
80
|
+
summary: lines.join("\n"),
|
|
81
|
+
injectedAsContext: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Convert run history to a bounded worker prompt section.
|
|
87
|
+
* Same logic as transformRunContextBeforeWorkerStart but returns
|
|
88
|
+
* the prompt text directly for embedding in the worker prompt.
|
|
89
|
+
*/
|
|
90
|
+
export function convertRunHistoryToWorkerPrompt(input: {
|
|
91
|
+
manifest: TeamRunManifest;
|
|
92
|
+
tasks: TeamTaskState[];
|
|
93
|
+
pendingMailbox: MailboxMessage[];
|
|
94
|
+
artifacts: ArtifactDescriptor[];
|
|
95
|
+
}): string {
|
|
96
|
+
const projection = transformRunContextBeforeWorkerStart(input);
|
|
97
|
+
if (!projection.summary) return "";
|
|
98
|
+
return [
|
|
99
|
+
"## Run Context (bounded projection)",
|
|
100
|
+
projection.summary,
|
|
101
|
+
"",
|
|
102
|
+
`Projection sources: ${projection.sources.map((s) => s.kind).join(", ")}`,
|
|
103
|
+
].join("\n");
|
|
104
|
+
}
|