pi-crew 0.1.37 → 0.1.39
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/AGENTS.md +1 -1
- package/CHANGELOG.md +27 -0
- package/README.md +5 -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/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-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/resource-formats.md +10 -8
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +83 -83
- package/docs/usage.md +6 -0
- package/index.ts +6 -6
- package/package.json +3 -3
- package/schema.json +2 -2
- package/src/agents/agent-serializer.ts +34 -34
- package/src/config/config.ts +8 -4
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/import-index.ts +18 -2
- package/src/extension/register.ts +11 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +30 -6
- package/src/extension/registration/subagent-tools.ts +8 -3
- package/src/extension/result-watcher.ts +98 -98
- package/src/extension/run-import.ts +12 -2
- package/src/extension/run-index.ts +12 -2
- package/src/extension/run-maintenance.ts +24 -24
- package/src/extension/team-tool/api.ts +54 -14
- package/src/extension/team-tool/cancel.ts +31 -31
- package/src/extension/team-tool/doctor.ts +179 -179
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +79 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/status.ts +73 -73
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +54 -54
- package/src/observability/exporters/adapter.ts +24 -24
- package/src/observability/exporters/otlp-exporter.ts +65 -65
- package/src/observability/exporters/prometheus-exporter.ts +47 -47
- package/src/observability/metric-registry.ts +72 -72
- package/src/observability/metric-retention.ts +46 -46
- package/src/observability/metric-sink.ts +51 -51
- package/src/observability/metrics-primitives.ts +166 -166
- package/src/prompt/prompt-runtime.ts +68 -68
- package/src/runtime/agent-control.ts +64 -64
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -113
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/background-runner.ts +53 -53
- package/src/runtime/crash-recovery.ts +56 -56
- package/src/runtime/crew-agent-records.ts +54 -9
- package/src/runtime/crew-agent-runtime.ts +58 -58
- package/src/runtime/deadletter.ts +36 -36
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +88 -88
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +80 -80
- package/src/runtime/live-agent-control.ts +87 -78
- package/src/runtime/live-agent-manager.ts +85 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +299 -299
- package/src/runtime/manifest-cache.ts +248 -212
- package/src/runtime/model-fallback.ts +261 -261
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +99 -99
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +78 -78
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +56 -56
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +59 -59
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +28 -28
- package/src/runtime/subagent-manager.ts +80 -12
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +127 -106
- package/src/runtime/task-runner/live-executor.ts +98 -98
- package/src/runtime/task-runner/progress.ts +111 -111
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/team-runner.ts +1 -1
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/schema/config-schema.ts +21 -21
- package/src/schema/team-tool-schema.ts +100 -100
- package/src/state/artifact-store.ts +122 -108
- package/src/state/contracts.ts +105 -105
- package/src/state/jsonl-writer.ts +77 -77
- package/src/state/mailbox.ts +67 -22
- package/src/state/state-store.ts +36 -5
- package/src/state/task-claims.ts +42 -42
- 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/discover-teams.ts +27 -5
- package/src/teams/team-serializer.ts +38 -36
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- 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/loaders.ts +158 -158
- package/src/ui/mascot.ts +441 -441
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/run-dashboard.ts +5 -2
- package/src/ui/run-snapshot-cache.ts +19 -8
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +54 -54
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-viewer.ts +15 -1
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/file-coalescer.ts +84 -84
- package/src/utils/frontmatter.ts +36 -36
- package/src/utils/fs-watch.ts +31 -31
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +12 -12
- package/src/utils/names.ts +26 -26
- package/src/utils/paths.ts +3 -2
- package/src/utils/safe-paths.ts +34 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/timings.ts +31 -31
- package/src/utils/visual.ts +159 -159
- package/src/workflows/discover-workflows.ts +30 -3
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- 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,299 +1,299 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
4
|
-
import type { CrewRuntimeConfig } from "../config/config.ts";
|
|
5
|
-
import type { TeamRunManifest, TeamTaskState, UsageState } from "../state/types.ts";
|
|
6
|
-
import { buildMemoryBlock } from "./agent-memory.ts";
|
|
7
|
-
import { registerLiveAgent, updateLiveAgentStatus } from "./live-agent-manager.ts";
|
|
8
|
-
import { applyLiveAgentControlRequest, applyLiveAgentControlRequests, type LiveAgentControlCursor } from "./live-agent-control.ts";
|
|
9
|
-
import { subscribeLiveControlRealtime } from "./live-control-realtime.ts";
|
|
10
|
-
import { eventToSidechainType, sidechainOutputPath, writeSidechainEntry } from "./sidechain-output.ts";
|
|
11
|
-
import type { WorkflowStep } from "../workflows/workflow-config.ts";
|
|
12
|
-
import { isLiveSessionRuntimeAvailable } from "./runtime-resolver.ts";
|
|
13
|
-
|
|
14
|
-
export interface LiveSessionSpawnInput {
|
|
15
|
-
manifest: TeamRunManifest;
|
|
16
|
-
task: TeamTaskState;
|
|
17
|
-
step: WorkflowStep;
|
|
18
|
-
agent: AgentConfig;
|
|
19
|
-
prompt: string;
|
|
20
|
-
signal?: AbortSignal;
|
|
21
|
-
transcriptPath?: string;
|
|
22
|
-
onEvent?: (event: unknown) => void;
|
|
23
|
-
onOutput?: (text: string) => void;
|
|
24
|
-
runtimeConfig?: CrewRuntimeConfig;
|
|
25
|
-
parentContext?: string;
|
|
26
|
-
parentModel?: unknown;
|
|
27
|
-
modelRegistry?: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface LiveSessionRunResult {
|
|
31
|
-
available: true;
|
|
32
|
-
exitCode: number | null;
|
|
33
|
-
stdout: string;
|
|
34
|
-
stderr: string;
|
|
35
|
-
jsonEvents: number;
|
|
36
|
-
usage?: UsageState;
|
|
37
|
-
error?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface LiveSessionUnavailableResult {
|
|
41
|
-
available: false;
|
|
42
|
-
reason: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface LiveSessionPlannedResult {
|
|
46
|
-
available: true;
|
|
47
|
-
reason: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
type LiveSessionModule = Record<string, unknown> & {
|
|
51
|
-
createAgentSession?: (options?: Record<string, unknown>) => Promise<{ session: LiveSessionLike; modelFallbackMessage?: string }>;
|
|
52
|
-
DefaultResourceLoader?: new (options: Record<string, unknown>) => { reload?: () => Promise<void> };
|
|
53
|
-
SessionManager?: { inMemory?: (cwd?: string) => unknown; create?: (cwd?: string, sessionDir?: string) => unknown };
|
|
54
|
-
SettingsManager?: { create?: (cwd?: string, agentDir?: string) => unknown };
|
|
55
|
-
getAgentDir?: () => string;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type LiveSessionLike = {
|
|
59
|
-
subscribe?: (listener: (event: unknown) => void) => (() => void);
|
|
60
|
-
prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>;
|
|
61
|
-
steer?: (text: string) => Promise<void>;
|
|
62
|
-
abort?: () => Promise<void> | void;
|
|
63
|
-
getStats?: () => unknown;
|
|
64
|
-
stats?: unknown;
|
|
65
|
-
bindExtensions?: (bindings?: Record<string, unknown>) => Promise<void>;
|
|
66
|
-
getActiveToolNames?: () => string[];
|
|
67
|
-
setActiveToolsByName?: (names: string[]) => void;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
function appendTranscript(filePath: string | undefined, event: unknown): void {
|
|
71
|
-
if (!filePath) return;
|
|
72
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
73
|
-
fs.appendFileSync(filePath, `${JSON.stringify(event)}\n`, "utf-8");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
77
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function textFromContent(content: unknown): string[] {
|
|
81
|
-
if (typeof content === "string") return [content];
|
|
82
|
-
if (!Array.isArray(content)) return [];
|
|
83
|
-
return content.flatMap((part) => {
|
|
84
|
-
const obj = asRecord(part);
|
|
85
|
-
if (!obj) return [];
|
|
86
|
-
if (obj.type === "text" && typeof obj.text === "string") return [obj.text];
|
|
87
|
-
if (typeof obj.content === "string") return [obj.content];
|
|
88
|
-
return [];
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function eventText(event: unknown): string[] {
|
|
93
|
-
const obj = asRecord(event);
|
|
94
|
-
if (!obj) return [];
|
|
95
|
-
const text: string[] = [];
|
|
96
|
-
if (typeof obj.text === "string") text.push(obj.text);
|
|
97
|
-
text.push(...textFromContent(obj.content));
|
|
98
|
-
const message = asRecord(obj.message);
|
|
99
|
-
if (message) text.push(...textFromContent(message.content));
|
|
100
|
-
return text.filter((entry) => entry.trim());
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function finalAssistantText(event: unknown): string[] {
|
|
104
|
-
const obj = asRecord(event);
|
|
105
|
-
if (!obj || obj.type !== "message_end") return [];
|
|
106
|
-
const message = asRecord(obj.message);
|
|
107
|
-
if (message?.role !== "assistant") return [];
|
|
108
|
-
return textFromContent(message.content);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function numberField(obj: Record<string, unknown> | undefined, keys: string[]): number | undefined {
|
|
112
|
-
if (!obj) return undefined;
|
|
113
|
-
for (const key of keys) {
|
|
114
|
-
const value = obj[key];
|
|
115
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
116
|
-
}
|
|
117
|
-
return undefined;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function modelFromRegistry(modelRegistry: unknown, modelId: string | undefined): unknown {
|
|
121
|
-
if (!modelId || !modelId.includes("/")) return undefined;
|
|
122
|
-
const registry = asRecord(modelRegistry);
|
|
123
|
-
const find = registry?.find;
|
|
124
|
-
if (typeof find !== "function") return undefined;
|
|
125
|
-
const [provider, ...modelParts] = modelId.split("/");
|
|
126
|
-
const id = modelParts.join("/");
|
|
127
|
-
try {
|
|
128
|
-
return find.call(modelRegistry, provider, id);
|
|
129
|
-
} catch {
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function liveSystemPrompt(input: LiveSessionSpawnInput): string {
|
|
135
|
-
const memory = input.agent.memory ? buildMemoryBlock(input.agent.name, input.agent.memory, input.task.cwd, Boolean(input.agent.tools?.some((tool) => tool === "write" || tool === "edit"))) : "";
|
|
136
|
-
return [
|
|
137
|
-
"# pi-crew Live Subagent",
|
|
138
|
-
`Run ID: ${input.manifest.runId}`,
|
|
139
|
-
`Task ID: ${input.task.id}`,
|
|
140
|
-
`Role: ${input.task.role}`,
|
|
141
|
-
`Agent: ${input.agent.name}`,
|
|
142
|
-
`Working directory: ${input.task.cwd}`,
|
|
143
|
-
"",
|
|
144
|
-
input.agent.systemPrompt || "Follow the user task exactly and report verification evidence.",
|
|
145
|
-
memory ? `\n${memory}` : "",
|
|
146
|
-
].filter(Boolean).join("\n");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function filterActiveTools(session: LiveSessionLike, agent: AgentConfig): void {
|
|
150
|
-
if (typeof session.getActiveToolNames !== "function" || typeof session.setActiveToolsByName !== "function") return;
|
|
151
|
-
const recursiveTools = new Set(["team", "Team", "Agent", "get_subagent_result", "steer_subagent"]);
|
|
152
|
-
const allowed = agent.tools?.length ? new Set(agent.tools) : undefined;
|
|
153
|
-
const active = session.getActiveToolNames().filter((name) => !recursiveTools.has(name) && (!allowed || allowed.has(name)));
|
|
154
|
-
session.setActiveToolsByName(active);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function usageFromStats(stats: unknown): UsageState | undefined {
|
|
158
|
-
const obj = asRecord(stats);
|
|
159
|
-
if (!obj) return undefined;
|
|
160
|
-
const input = numberField(obj, ["input", "inputTokens", "input_tokens"]);
|
|
161
|
-
const output = numberField(obj, ["output", "outputTokens", "output_tokens"]);
|
|
162
|
-
const cacheRead = numberField(obj, ["cacheRead", "cache_read"]);
|
|
163
|
-
const cacheWrite = numberField(obj, ["cacheWrite", "cache_write"]);
|
|
164
|
-
const cost = numberField(obj, ["cost"]);
|
|
165
|
-
const turns = numberField(obj, ["turns", "turnCount", "turn_count"]);
|
|
166
|
-
return [input, output, cacheRead, cacheWrite, cost, turns].some((value) => value !== undefined) ? { input, output, cacheRead, cacheWrite, cost, turns } : undefined;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export async function probeLiveSessionRuntime(): Promise<LiveSessionUnavailableResult | LiveSessionPlannedResult> {
|
|
170
|
-
const availability = await isLiveSessionRuntimeAvailable();
|
|
171
|
-
if (!availability.available) return { available: false, reason: availability.reason ?? "Live-session runtime is unavailable." };
|
|
172
|
-
return { available: true, reason: "Live-session SDK exports are available and pi-crew can run experimental in-process live agents when runtime.mode=live-session." };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export async function runLiveSessionTask(input: LiveSessionSpawnInput): Promise<LiveSessionRunResult> {
|
|
176
|
-
if (process.env.PI_CREW_MOCK_LIVE_SESSION === "success") {
|
|
177
|
-
const agentId = `${input.manifest.runId}:${input.task.id}`;
|
|
178
|
-
const inherited = input.runtimeConfig?.inheritContext === true && input.parentContext ? ` with inherited context: ${input.parentContext}` : "";
|
|
179
|
-
const event = { type: "message_end", message: { role: "assistant", content: [{ type: "text", text: `Mock live-session success for ${input.agent.name}${inherited}` }] } };
|
|
180
|
-
const mockSession = { steer: async () => {}, prompt: async () => {}, abort: async () => {} };
|
|
181
|
-
registerLiveAgent({ agentId, runId: input.manifest.runId, taskId: input.task.id, session: mockSession, status: "running" });
|
|
182
|
-
appendTranscript(input.transcriptPath, event);
|
|
183
|
-
const sidechainPath = sidechainOutputPath(input.manifest.stateRoot, input.task.id);
|
|
184
|
-
writeSidechainEntry(sidechainPath, { agentId, type: "user", message: { role: "user", content: input.prompt }, cwd: input.task.cwd });
|
|
185
|
-
writeSidechainEntry(sidechainPath, { agentId, type: "message", message: event, cwd: input.task.cwd });
|
|
186
|
-
input.onEvent?.(event);
|
|
187
|
-
const stdout = `Mock live-session success for ${input.agent.name}${inherited}`;
|
|
188
|
-
input.onOutput?.(stdout);
|
|
189
|
-
updateLiveAgentStatus(agentId, "completed");
|
|
190
|
-
return { available: true, exitCode: 0, stdout, stderr: "", jsonEvents: 1 };
|
|
191
|
-
}
|
|
192
|
-
const availability = await isLiveSessionRuntimeAvailable();
|
|
193
|
-
if (!availability.available) return { available: true, exitCode: 1, stdout: "", stderr: availability.reason ?? "Live-session runtime unavailable.", jsonEvents: 0, error: availability.reason };
|
|
194
|
-
const mod = await import("@mariozechner/pi-coding-agent") as LiveSessionModule;
|
|
195
|
-
if (typeof mod.createAgentSession !== "function") return { available: true, exitCode: 1, stdout: "", stderr: "createAgentSession export is unavailable.", jsonEvents: 0, error: "createAgentSession export is unavailable." };
|
|
196
|
-
let session: LiveSessionLike | undefined;
|
|
197
|
-
let unsubscribe: (() => void) | undefined;
|
|
198
|
-
let unsubscribeControlRealtime: (() => void) | undefined;
|
|
199
|
-
let controlTimer: ReturnType<typeof setInterval> | undefined;
|
|
200
|
-
let stdout = "";
|
|
201
|
-
let jsonEvents = 0;
|
|
202
|
-
try {
|
|
203
|
-
const agentDir = typeof mod.getAgentDir === "function" ? mod.getAgentDir() : undefined;
|
|
204
|
-
let resourceLoader: unknown;
|
|
205
|
-
if (mod.DefaultResourceLoader && agentDir) {
|
|
206
|
-
resourceLoader = new mod.DefaultResourceLoader({
|
|
207
|
-
cwd: input.task.cwd,
|
|
208
|
-
agentDir,
|
|
209
|
-
noPromptTemplates: true,
|
|
210
|
-
noThemes: true,
|
|
211
|
-
noContextFiles: input.runtimeConfig?.inheritContext !== true,
|
|
212
|
-
systemPromptOverride: () => liveSystemPrompt(input),
|
|
213
|
-
appendSystemPromptOverride: () => [],
|
|
214
|
-
});
|
|
215
|
-
await (resourceLoader as { reload?: () => Promise<void> }).reload?.();
|
|
216
|
-
}
|
|
217
|
-
const resolvedModel = modelFromRegistry(input.modelRegistry, input.agent.model) ?? input.parentModel;
|
|
218
|
-
const created = await mod.createAgentSession({
|
|
219
|
-
cwd: input.task.cwd,
|
|
220
|
-
...(agentDir ? { agentDir } : {}),
|
|
221
|
-
...(resourceLoader ? { resourceLoader } : {}),
|
|
222
|
-
...(mod.SessionManager?.inMemory ? { sessionManager: mod.SessionManager.inMemory(input.task.cwd) } : {}),
|
|
223
|
-
...(mod.SettingsManager?.create && agentDir ? { settingsManager: mod.SettingsManager.create(input.task.cwd, agentDir) } : {}),
|
|
224
|
-
...(input.modelRegistry ? { modelRegistry: input.modelRegistry } : {}),
|
|
225
|
-
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
226
|
-
...(input.agent.thinking ? { thinkingLevel: input.agent.thinking } : {}),
|
|
227
|
-
});
|
|
228
|
-
session = created.session;
|
|
229
|
-
filterActiveTools(session, input.agent);
|
|
230
|
-
await session.bindExtensions?.({});
|
|
231
|
-
const agentId = `${input.manifest.runId}:${input.task.id}`;
|
|
232
|
-
registerLiveAgent({ agentId, runId: input.manifest.runId, taskId: input.task.id, session, status: "running" });
|
|
233
|
-
let controlCursor: LiveAgentControlCursor = { offset: 0 };
|
|
234
|
-
const seenControlRequestIds = new Set<string>();
|
|
235
|
-
let controlBusy = false;
|
|
236
|
-
const pollControl = async () => {
|
|
237
|
-
if (controlBusy || !session) return;
|
|
238
|
-
controlBusy = true;
|
|
239
|
-
try {
|
|
240
|
-
controlCursor = await applyLiveAgentControlRequests({ manifest: input.manifest, taskId: input.task.id, agentId, session, cursor: controlCursor, seenRequestIds: seenControlRequestIds });
|
|
241
|
-
} finally {
|
|
242
|
-
controlBusy = false;
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
unsubscribeControlRealtime = subscribeLiveControlRealtime((request) => {
|
|
246
|
-
if (request.runId !== input.manifest.runId || request.taskId !== input.task.id || !session) return;
|
|
247
|
-
void applyLiveAgentControlRequest({ request, taskId: input.task.id, agentId, session, seenRequestIds: seenControlRequestIds });
|
|
248
|
-
});
|
|
249
|
-
await pollControl();
|
|
250
|
-
controlTimer = setInterval(() => { void pollControl(); }, 500);
|
|
251
|
-
let turnCount = 0;
|
|
252
|
-
let softLimitReached = false;
|
|
253
|
-
const maxTurns = input.runtimeConfig?.maxTurns;
|
|
254
|
-
const graceTurns = input.runtimeConfig?.graceTurns ?? 5;
|
|
255
|
-
const sidechainPath = sidechainOutputPath(input.manifest.stateRoot, input.task.id);
|
|
256
|
-
writeSidechainEntry(sidechainPath, { agentId, type: "user", message: { role: "user", content: input.prompt }, cwd: input.task.cwd });
|
|
257
|
-
if (typeof session.subscribe === "function") {
|
|
258
|
-
unsubscribe = session.subscribe((event) => {
|
|
259
|
-
jsonEvents += 1;
|
|
260
|
-
appendTranscript(input.transcriptPath, event);
|
|
261
|
-
const sidechainType = eventToSidechainType(event);
|
|
262
|
-
if (sidechainType) writeSidechainEntry(sidechainPath, { agentId, type: sidechainType, message: event, cwd: input.task.cwd });
|
|
263
|
-
const obj = asRecord(event);
|
|
264
|
-
if (obj?.type === "turn_end") {
|
|
265
|
-
turnCount += 1;
|
|
266
|
-
if (maxTurns !== undefined && !softLimitReached && turnCount >= maxTurns) {
|
|
267
|
-
softLimitReached = true;
|
|
268
|
-
void session?.steer?.("You have reached your turn limit. Wrap up immediately — provide your final answer now.");
|
|
269
|
-
} else if (maxTurns !== undefined && softLimitReached && turnCount >= maxTurns + graceTurns) {
|
|
270
|
-
void session?.abort?.();
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
input.onEvent?.(event);
|
|
274
|
-
const text = [...eventText(event), ...finalAssistantText(event)].join("\n");
|
|
275
|
-
if (text.trim()) {
|
|
276
|
-
stdout += `${text}\n`;
|
|
277
|
-
input.onOutput?.(text);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (input.signal) {
|
|
282
|
-
if (input.signal.aborted) await session.abort?.();
|
|
283
|
-
else input.signal.addEventListener("abort", () => { void session?.abort?.(); }, { once: true });
|
|
284
|
-
}
|
|
285
|
-
const effectivePrompt = input.runtimeConfig?.inheritContext === true && input.parentContext ? `${input.parentContext}\n\n---\n# Live Subagent Task\n${input.prompt}` : input.prompt;
|
|
286
|
-
await session.prompt?.(effectivePrompt, { source: "api", expandPromptTemplates: false });
|
|
287
|
-
const usage = usageFromStats(typeof session.getStats === "function" ? session.getStats() : session.stats);
|
|
288
|
-
updateLiveAgentStatus(agentId, "completed");
|
|
289
|
-
return { available: true, exitCode: 0, stdout: stdout.trim(), stderr: created.modelFallbackMessage ?? "", jsonEvents, usage };
|
|
290
|
-
} catch (error) {
|
|
291
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
-
updateLiveAgentStatus(`${input.manifest.runId}:${input.task.id}`, "failed");
|
|
293
|
-
return { available: true, exitCode: 1, stdout: stdout.trim(), stderr: message, jsonEvents, error: message };
|
|
294
|
-
} finally {
|
|
295
|
-
if (controlTimer) clearInterval(controlTimer);
|
|
296
|
-
unsubscribeControlRealtime?.();
|
|
297
|
-
unsubscribe?.();
|
|
298
|
-
}
|
|
299
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
4
|
+
import type { CrewRuntimeConfig } from "../config/config.ts";
|
|
5
|
+
import type { TeamRunManifest, TeamTaskState, UsageState } from "../state/types.ts";
|
|
6
|
+
import { buildMemoryBlock } from "./agent-memory.ts";
|
|
7
|
+
import { registerLiveAgent, updateLiveAgentStatus } from "./live-agent-manager.ts";
|
|
8
|
+
import { applyLiveAgentControlRequest, applyLiveAgentControlRequests, type LiveAgentControlCursor } from "./live-agent-control.ts";
|
|
9
|
+
import { subscribeLiveControlRealtime } from "./live-control-realtime.ts";
|
|
10
|
+
import { eventToSidechainType, sidechainOutputPath, writeSidechainEntry } from "./sidechain-output.ts";
|
|
11
|
+
import type { WorkflowStep } from "../workflows/workflow-config.ts";
|
|
12
|
+
import { isLiveSessionRuntimeAvailable } from "./runtime-resolver.ts";
|
|
13
|
+
|
|
14
|
+
export interface LiveSessionSpawnInput {
|
|
15
|
+
manifest: TeamRunManifest;
|
|
16
|
+
task: TeamTaskState;
|
|
17
|
+
step: WorkflowStep;
|
|
18
|
+
agent: AgentConfig;
|
|
19
|
+
prompt: string;
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
transcriptPath?: string;
|
|
22
|
+
onEvent?: (event: unknown) => void;
|
|
23
|
+
onOutput?: (text: string) => void;
|
|
24
|
+
runtimeConfig?: CrewRuntimeConfig;
|
|
25
|
+
parentContext?: string;
|
|
26
|
+
parentModel?: unknown;
|
|
27
|
+
modelRegistry?: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LiveSessionRunResult {
|
|
31
|
+
available: true;
|
|
32
|
+
exitCode: number | null;
|
|
33
|
+
stdout: string;
|
|
34
|
+
stderr: string;
|
|
35
|
+
jsonEvents: number;
|
|
36
|
+
usage?: UsageState;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LiveSessionUnavailableResult {
|
|
41
|
+
available: false;
|
|
42
|
+
reason: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LiveSessionPlannedResult {
|
|
46
|
+
available: true;
|
|
47
|
+
reason: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type LiveSessionModule = Record<string, unknown> & {
|
|
51
|
+
createAgentSession?: (options?: Record<string, unknown>) => Promise<{ session: LiveSessionLike; modelFallbackMessage?: string }>;
|
|
52
|
+
DefaultResourceLoader?: new (options: Record<string, unknown>) => { reload?: () => Promise<void> };
|
|
53
|
+
SessionManager?: { inMemory?: (cwd?: string) => unknown; create?: (cwd?: string, sessionDir?: string) => unknown };
|
|
54
|
+
SettingsManager?: { create?: (cwd?: string, agentDir?: string) => unknown };
|
|
55
|
+
getAgentDir?: () => string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type LiveSessionLike = {
|
|
59
|
+
subscribe?: (listener: (event: unknown) => void) => (() => void);
|
|
60
|
+
prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>;
|
|
61
|
+
steer?: (text: string) => Promise<void>;
|
|
62
|
+
abort?: () => Promise<void> | void;
|
|
63
|
+
getStats?: () => unknown;
|
|
64
|
+
stats?: unknown;
|
|
65
|
+
bindExtensions?: (bindings?: Record<string, unknown>) => Promise<void>;
|
|
66
|
+
getActiveToolNames?: () => string[];
|
|
67
|
+
setActiveToolsByName?: (names: string[]) => void;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function appendTranscript(filePath: string | undefined, event: unknown): void {
|
|
71
|
+
if (!filePath) return;
|
|
72
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
73
|
+
fs.appendFileSync(filePath, `${JSON.stringify(event)}\n`, "utf-8");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
77
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function textFromContent(content: unknown): string[] {
|
|
81
|
+
if (typeof content === "string") return [content];
|
|
82
|
+
if (!Array.isArray(content)) return [];
|
|
83
|
+
return content.flatMap((part) => {
|
|
84
|
+
const obj = asRecord(part);
|
|
85
|
+
if (!obj) return [];
|
|
86
|
+
if (obj.type === "text" && typeof obj.text === "string") return [obj.text];
|
|
87
|
+
if (typeof obj.content === "string") return [obj.content];
|
|
88
|
+
return [];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function eventText(event: unknown): string[] {
|
|
93
|
+
const obj = asRecord(event);
|
|
94
|
+
if (!obj) return [];
|
|
95
|
+
const text: string[] = [];
|
|
96
|
+
if (typeof obj.text === "string") text.push(obj.text);
|
|
97
|
+
text.push(...textFromContent(obj.content));
|
|
98
|
+
const message = asRecord(obj.message);
|
|
99
|
+
if (message) text.push(...textFromContent(message.content));
|
|
100
|
+
return text.filter((entry) => entry.trim());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function finalAssistantText(event: unknown): string[] {
|
|
104
|
+
const obj = asRecord(event);
|
|
105
|
+
if (!obj || obj.type !== "message_end") return [];
|
|
106
|
+
const message = asRecord(obj.message);
|
|
107
|
+
if (message?.role !== "assistant") return [];
|
|
108
|
+
return textFromContent(message.content);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function numberField(obj: Record<string, unknown> | undefined, keys: string[]): number | undefined {
|
|
112
|
+
if (!obj) return undefined;
|
|
113
|
+
for (const key of keys) {
|
|
114
|
+
const value = obj[key];
|
|
115
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function modelFromRegistry(modelRegistry: unknown, modelId: string | undefined): unknown {
|
|
121
|
+
if (!modelId || !modelId.includes("/")) return undefined;
|
|
122
|
+
const registry = asRecord(modelRegistry);
|
|
123
|
+
const find = registry?.find;
|
|
124
|
+
if (typeof find !== "function") return undefined;
|
|
125
|
+
const [provider, ...modelParts] = modelId.split("/");
|
|
126
|
+
const id = modelParts.join("/");
|
|
127
|
+
try {
|
|
128
|
+
return find.call(modelRegistry, provider, id);
|
|
129
|
+
} catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function liveSystemPrompt(input: LiveSessionSpawnInput): string {
|
|
135
|
+
const memory = input.agent.memory ? buildMemoryBlock(input.agent.name, input.agent.memory, input.task.cwd, Boolean(input.agent.tools?.some((tool) => tool === "write" || tool === "edit"))) : "";
|
|
136
|
+
return [
|
|
137
|
+
"# pi-crew Live Subagent",
|
|
138
|
+
`Run ID: ${input.manifest.runId}`,
|
|
139
|
+
`Task ID: ${input.task.id}`,
|
|
140
|
+
`Role: ${input.task.role}`,
|
|
141
|
+
`Agent: ${input.agent.name}`,
|
|
142
|
+
`Working directory: ${input.task.cwd}`,
|
|
143
|
+
"",
|
|
144
|
+
input.agent.systemPrompt || "Follow the user task exactly and report verification evidence.",
|
|
145
|
+
memory ? `\n${memory}` : "",
|
|
146
|
+
].filter(Boolean).join("\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function filterActiveTools(session: LiveSessionLike, agent: AgentConfig): void {
|
|
150
|
+
if (typeof session.getActiveToolNames !== "function" || typeof session.setActiveToolsByName !== "function") return;
|
|
151
|
+
const recursiveTools = new Set(["team", "Team", "Agent", "get_subagent_result", "steer_subagent"]);
|
|
152
|
+
const allowed = agent.tools?.length ? new Set(agent.tools) : undefined;
|
|
153
|
+
const active = session.getActiveToolNames().filter((name) => !recursiveTools.has(name) && (!allowed || allowed.has(name)));
|
|
154
|
+
session.setActiveToolsByName(active);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function usageFromStats(stats: unknown): UsageState | undefined {
|
|
158
|
+
const obj = asRecord(stats);
|
|
159
|
+
if (!obj) return undefined;
|
|
160
|
+
const input = numberField(obj, ["input", "inputTokens", "input_tokens"]);
|
|
161
|
+
const output = numberField(obj, ["output", "outputTokens", "output_tokens"]);
|
|
162
|
+
const cacheRead = numberField(obj, ["cacheRead", "cache_read"]);
|
|
163
|
+
const cacheWrite = numberField(obj, ["cacheWrite", "cache_write"]);
|
|
164
|
+
const cost = numberField(obj, ["cost"]);
|
|
165
|
+
const turns = numberField(obj, ["turns", "turnCount", "turn_count"]);
|
|
166
|
+
return [input, output, cacheRead, cacheWrite, cost, turns].some((value) => value !== undefined) ? { input, output, cacheRead, cacheWrite, cost, turns } : undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function probeLiveSessionRuntime(): Promise<LiveSessionUnavailableResult | LiveSessionPlannedResult> {
|
|
170
|
+
const availability = await isLiveSessionRuntimeAvailable();
|
|
171
|
+
if (!availability.available) return { available: false, reason: availability.reason ?? "Live-session runtime is unavailable." };
|
|
172
|
+
return { available: true, reason: "Live-session SDK exports are available and pi-crew can run experimental in-process live agents when runtime.mode=live-session." };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function runLiveSessionTask(input: LiveSessionSpawnInput): Promise<LiveSessionRunResult> {
|
|
176
|
+
if (process.env.PI_CREW_MOCK_LIVE_SESSION === "success") {
|
|
177
|
+
const agentId = `${input.manifest.runId}:${input.task.id}`;
|
|
178
|
+
const inherited = input.runtimeConfig?.inheritContext === true && input.parentContext ? ` with inherited context: ${input.parentContext}` : "";
|
|
179
|
+
const event = { type: "message_end", message: { role: "assistant", content: [{ type: "text", text: `Mock live-session success for ${input.agent.name}${inherited}` }] } };
|
|
180
|
+
const mockSession = { steer: async () => {}, prompt: async () => {}, abort: async () => {} };
|
|
181
|
+
registerLiveAgent({ agentId, runId: input.manifest.runId, taskId: input.task.id, session: mockSession, status: "running" });
|
|
182
|
+
appendTranscript(input.transcriptPath, event);
|
|
183
|
+
const sidechainPath = sidechainOutputPath(input.manifest.stateRoot, input.task.id);
|
|
184
|
+
writeSidechainEntry(sidechainPath, { agentId, type: "user", message: { role: "user", content: input.prompt }, cwd: input.task.cwd });
|
|
185
|
+
writeSidechainEntry(sidechainPath, { agentId, type: "message", message: event, cwd: input.task.cwd });
|
|
186
|
+
input.onEvent?.(event);
|
|
187
|
+
const stdout = `Mock live-session success for ${input.agent.name}${inherited}`;
|
|
188
|
+
input.onOutput?.(stdout);
|
|
189
|
+
updateLiveAgentStatus(agentId, "completed");
|
|
190
|
+
return { available: true, exitCode: 0, stdout, stderr: "", jsonEvents: 1 };
|
|
191
|
+
}
|
|
192
|
+
const availability = await isLiveSessionRuntimeAvailable();
|
|
193
|
+
if (!availability.available) return { available: true, exitCode: 1, stdout: "", stderr: availability.reason ?? "Live-session runtime unavailable.", jsonEvents: 0, error: availability.reason };
|
|
194
|
+
const mod = await import("@mariozechner/pi-coding-agent") as LiveSessionModule;
|
|
195
|
+
if (typeof mod.createAgentSession !== "function") return { available: true, exitCode: 1, stdout: "", stderr: "createAgentSession export is unavailable.", jsonEvents: 0, error: "createAgentSession export is unavailable." };
|
|
196
|
+
let session: LiveSessionLike | undefined;
|
|
197
|
+
let unsubscribe: (() => void) | undefined;
|
|
198
|
+
let unsubscribeControlRealtime: (() => void) | undefined;
|
|
199
|
+
let controlTimer: ReturnType<typeof setInterval> | undefined;
|
|
200
|
+
let stdout = "";
|
|
201
|
+
let jsonEvents = 0;
|
|
202
|
+
try {
|
|
203
|
+
const agentDir = typeof mod.getAgentDir === "function" ? mod.getAgentDir() : undefined;
|
|
204
|
+
let resourceLoader: unknown;
|
|
205
|
+
if (mod.DefaultResourceLoader && agentDir) {
|
|
206
|
+
resourceLoader = new mod.DefaultResourceLoader({
|
|
207
|
+
cwd: input.task.cwd,
|
|
208
|
+
agentDir,
|
|
209
|
+
noPromptTemplates: true,
|
|
210
|
+
noThemes: true,
|
|
211
|
+
noContextFiles: input.runtimeConfig?.inheritContext !== true,
|
|
212
|
+
systemPromptOverride: () => liveSystemPrompt(input),
|
|
213
|
+
appendSystemPromptOverride: () => [],
|
|
214
|
+
});
|
|
215
|
+
await (resourceLoader as { reload?: () => Promise<void> }).reload?.();
|
|
216
|
+
}
|
|
217
|
+
const resolvedModel = modelFromRegistry(input.modelRegistry, input.agent.model) ?? input.parentModel;
|
|
218
|
+
const created = await mod.createAgentSession({
|
|
219
|
+
cwd: input.task.cwd,
|
|
220
|
+
...(agentDir ? { agentDir } : {}),
|
|
221
|
+
...(resourceLoader ? { resourceLoader } : {}),
|
|
222
|
+
...(mod.SessionManager?.inMemory ? { sessionManager: mod.SessionManager.inMemory(input.task.cwd) } : {}),
|
|
223
|
+
...(mod.SettingsManager?.create && agentDir ? { settingsManager: mod.SettingsManager.create(input.task.cwd, agentDir) } : {}),
|
|
224
|
+
...(input.modelRegistry ? { modelRegistry: input.modelRegistry } : {}),
|
|
225
|
+
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
226
|
+
...(input.agent.thinking ? { thinkingLevel: input.agent.thinking } : {}),
|
|
227
|
+
});
|
|
228
|
+
session = created.session;
|
|
229
|
+
filterActiveTools(session, input.agent);
|
|
230
|
+
await session.bindExtensions?.({});
|
|
231
|
+
const agentId = `${input.manifest.runId}:${input.task.id}`;
|
|
232
|
+
registerLiveAgent({ agentId, runId: input.manifest.runId, taskId: input.task.id, session, status: "running" });
|
|
233
|
+
let controlCursor: LiveAgentControlCursor = { offset: 0 };
|
|
234
|
+
const seenControlRequestIds = new Set<string>();
|
|
235
|
+
let controlBusy = false;
|
|
236
|
+
const pollControl = async () => {
|
|
237
|
+
if (controlBusy || !session) return;
|
|
238
|
+
controlBusy = true;
|
|
239
|
+
try {
|
|
240
|
+
controlCursor = await applyLiveAgentControlRequests({ manifest: input.manifest, taskId: input.task.id, agentId, session, cursor: controlCursor, seenRequestIds: seenControlRequestIds });
|
|
241
|
+
} finally {
|
|
242
|
+
controlBusy = false;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
unsubscribeControlRealtime = subscribeLiveControlRealtime((request) => {
|
|
246
|
+
if (request.runId !== input.manifest.runId || request.taskId !== input.task.id || !session) return;
|
|
247
|
+
void applyLiveAgentControlRequest({ request, taskId: input.task.id, agentId, session, seenRequestIds: seenControlRequestIds });
|
|
248
|
+
});
|
|
249
|
+
await pollControl();
|
|
250
|
+
controlTimer = setInterval(() => { void pollControl(); }, 500);
|
|
251
|
+
let turnCount = 0;
|
|
252
|
+
let softLimitReached = false;
|
|
253
|
+
const maxTurns = input.runtimeConfig?.maxTurns;
|
|
254
|
+
const graceTurns = input.runtimeConfig?.graceTurns ?? 5;
|
|
255
|
+
const sidechainPath = sidechainOutputPath(input.manifest.stateRoot, input.task.id);
|
|
256
|
+
writeSidechainEntry(sidechainPath, { agentId, type: "user", message: { role: "user", content: input.prompt }, cwd: input.task.cwd });
|
|
257
|
+
if (typeof session.subscribe === "function") {
|
|
258
|
+
unsubscribe = session.subscribe((event) => {
|
|
259
|
+
jsonEvents += 1;
|
|
260
|
+
appendTranscript(input.transcriptPath, event);
|
|
261
|
+
const sidechainType = eventToSidechainType(event);
|
|
262
|
+
if (sidechainType) writeSidechainEntry(sidechainPath, { agentId, type: sidechainType, message: event, cwd: input.task.cwd });
|
|
263
|
+
const obj = asRecord(event);
|
|
264
|
+
if (obj?.type === "turn_end") {
|
|
265
|
+
turnCount += 1;
|
|
266
|
+
if (maxTurns !== undefined && !softLimitReached && turnCount >= maxTurns) {
|
|
267
|
+
softLimitReached = true;
|
|
268
|
+
void session?.steer?.("You have reached your turn limit. Wrap up immediately — provide your final answer now.");
|
|
269
|
+
} else if (maxTurns !== undefined && softLimitReached && turnCount >= maxTurns + graceTurns) {
|
|
270
|
+
void session?.abort?.();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
input.onEvent?.(event);
|
|
274
|
+
const text = [...eventText(event), ...finalAssistantText(event)].join("\n");
|
|
275
|
+
if (text.trim()) {
|
|
276
|
+
stdout += `${text}\n`;
|
|
277
|
+
input.onOutput?.(text);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (input.signal) {
|
|
282
|
+
if (input.signal.aborted) await session.abort?.();
|
|
283
|
+
else input.signal.addEventListener("abort", () => { void session?.abort?.(); }, { once: true });
|
|
284
|
+
}
|
|
285
|
+
const effectivePrompt = input.runtimeConfig?.inheritContext === true && input.parentContext ? `${input.parentContext}\n\n---\n# Live Subagent Task\n${input.prompt}` : input.prompt;
|
|
286
|
+
await session.prompt?.(effectivePrompt, { source: "api", expandPromptTemplates: false });
|
|
287
|
+
const usage = usageFromStats(typeof session.getStats === "function" ? session.getStats() : session.stats);
|
|
288
|
+
updateLiveAgentStatus(agentId, "completed");
|
|
289
|
+
return { available: true, exitCode: 0, stdout: stdout.trim(), stderr: created.modelFallbackMessage ?? "", jsonEvents, usage };
|
|
290
|
+
} catch (error) {
|
|
291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
+
updateLiveAgentStatus(`${input.manifest.runId}:${input.task.id}`, "failed");
|
|
293
|
+
return { available: true, exitCode: 1, stdout: stdout.trim(), stderr: message, jsonEvents, error: message };
|
|
294
|
+
} finally {
|
|
295
|
+
if (controlTimer) clearInterval(controlTimer);
|
|
296
|
+
unsubscribeControlRealtime?.();
|
|
297
|
+
unsubscribe?.();
|
|
298
|
+
}
|
|
299
|
+
}
|