pi-crew 0.1.45 → 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/README.md +5 -5
- 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 +808 -0
- 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 -0
- package/docs/research-oh-my-pi-distillation.md +369 -0
- package/docs/source-runtime-refactor-map.md +24 -0
- package/docs/usage.md +3 -3
- package/install.mjs +52 -8
- package/package.json +99 -98
- package/schema.json +10 -1
- package/skills/async-worker-recovery/SKILL.md +42 -0
- package/skills/context-artifact-hygiene/SKILL.md +52 -0
- package/skills/delegation-patterns/SKILL.md +54 -0
- package/skills/mailbox-interactive/SKILL.md +40 -0
- package/skills/model-routing-context/SKILL.md +39 -0
- package/skills/multi-perspective-review/SKILL.md +58 -0
- package/skills/observability-reliability/SKILL.md +41 -0
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -0
- package/skills/pi-extension-lifecycle/SKILL.md +39 -0
- package/skills/requirements-to-task-packet/SKILL.md +63 -0
- package/skills/resource-discovery-config/SKILL.md +41 -0
- package/skills/runtime-state-reader/SKILL.md +44 -0
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
- package/skills/state-mutation-locking/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +67 -0
- package/skills/ui-render-performance/SKILL.md +39 -0
- package/skills/verification-before-done/SKILL.md +57 -0
- package/skills/worktree-isolation/SKILL.md +39 -0
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -34
- package/src/agents/discover-agents.ts +29 -15
- package/src/config/config.ts +72 -24
- package/src/config/defaults.ts +25 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/project-init.ts +62 -2
- package/src/extension/register.ts +69 -22
- package/src/extension/registration/commands.ts +64 -25
- package/src/extension/registration/compaction-guard.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +8 -0
- package/src/extension/registration/subagent-tools.ts +149 -148
- package/src/extension/registration/team-tool.ts +14 -10
- package/src/extension/run-index.ts +35 -21
- package/src/extension/run-maintenance.ts +30 -5
- package/src/extension/team-tool/api.ts +47 -9
- package/src/extension/team-tool/cancel.ts +109 -5
- package/src/extension/team-tool/context.ts +8 -0
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +120 -79
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/respond.ts +46 -18
- package/src/extension/team-tool/run.ts +55 -12
- package/src/extension/team-tool/status.ts +13 -2
- package/src/extension/team-tool-types.ts +3 -0
- package/src/extension/team-tool.ts +45 -14
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/observability/event-to-metric.ts +8 -1
- package/src/runtime/agent-control.ts +169 -63
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/background-runner.ts +78 -53
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -0
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +458 -444
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/crash-recovery.ts +182 -0
- package/src/runtime/crew-agent-records.ts +70 -10
- 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/deadletter.ts +1 -0
- package/src/runtime/delivery-coordinator.ts +48 -25
- package/src/runtime/effectiveness.ts +81 -0
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/live-agent-control.ts +2 -1
- package/src/runtime/live-agent-manager.ts +179 -85
- package/src/runtime/live-control-realtime.ts +1 -1
- 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 +599 -305
- package/src/runtime/manifest-cache.ts +17 -2
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/model-fallback.ts +6 -4
- 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-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-args.ts +18 -3
- package/src/runtime/process-status.ts +5 -1
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/retry-executor.ts +81 -64
- package/src/runtime/runtime-resolver.ts +23 -10
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/skill-instructions.ts +222 -0
- package/src/runtime/stale-reconciler.ts +4 -14
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +6 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/task-output-context.ts +177 -127
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/live-executor.ts +107 -101
- package/src/runtime/task-runner/prompt-builder.ts +72 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner.ts +115 -5
- package/src/runtime/team-runner.ts +134 -19
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +7 -0
- package/src/schema/team-tool-schema.ts +14 -4
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +167 -0
- 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 +2 -1
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +129 -9
- package/src/state/state-store.ts +32 -5
- package/src/state/types.ts +64 -2
- package/src/teams/team-config.ts +1 -0
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-widget.ts +15 -5
- 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/progress-pane.ts +2 -0
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +78 -18
- package/src/ui/snapshot-types.ts +10 -0
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/ids.ts +5 -0
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/paths.ts +4 -2
- package/src/utils/scan-cache.ts +137 -0
- 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/workflow-config.ts +1 -0
- package/src/worktree/cleanup.ts +2 -1
|
@@ -1,85 +1,179 @@
|
|
|
1
|
-
import type { CrewAgentRecord } from "./crew-agent-runtime.ts";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
await handle.session.
|
|
67
|
-
handle.
|
|
68
|
-
handle
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
handle.
|
|
79
|
-
handle.updatedAt = new Date().toISOString();
|
|
80
|
-
return handle;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function
|
|
84
|
-
|
|
85
|
-
}
|
|
1
|
+
import type { CrewAgentRecord } from "./crew-agent-runtime.ts";
|
|
2
|
+
import type { IrcMessage } from "./live-irc.ts";
|
|
3
|
+
|
|
4
|
+
type LiveSessionHandle = {
|
|
5
|
+
steer?: (text: string) => Promise<void>;
|
|
6
|
+
prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>;
|
|
7
|
+
abort?: () => Promise<void> | void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface LiveAgentHandle {
|
|
11
|
+
agentId: string;
|
|
12
|
+
taskId: string;
|
|
13
|
+
runId: string;
|
|
14
|
+
session: LiveSessionHandle;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
status: CrewAgentRecord["status"];
|
|
18
|
+
pendingSteers: string[];
|
|
19
|
+
pendingFollowUps: string[];
|
|
20
|
+
/** Phase 7: Pending IRC messages for this agent. */
|
|
21
|
+
pendingMessages: IrcMessage[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const liveAgents = new Map<string, LiveAgentHandle>();
|
|
25
|
+
|
|
26
|
+
export function registerLiveAgent(input: Omit<LiveAgentHandle, "createdAt" | "updatedAt" | "pendingSteers" | "pendingFollowUps" | "pendingMessages">): LiveAgentHandle {
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const existing = liveAgents.get(input.agentId);
|
|
29
|
+
const handle: LiveAgentHandle = { ...input, createdAt: existing?.createdAt ?? now, updatedAt: now, pendingSteers: existing?.pendingSteers ?? [], pendingFollowUps: existing?.pendingFollowUps ?? [], pendingMessages: existing?.pendingMessages ?? [] };
|
|
30
|
+
liveAgents.set(input.agentId, handle);
|
|
31
|
+
if (handle.pendingSteers.length && typeof handle.session.steer === "function") {
|
|
32
|
+
const pending = [...handle.pendingSteers];
|
|
33
|
+
handle.pendingSteers.length = 0;
|
|
34
|
+
for (const message of pending) void handle.session.steer(message).catch(() => {});
|
|
35
|
+
}
|
|
36
|
+
if (handle.pendingFollowUps.length && typeof handle.session.prompt === "function") {
|
|
37
|
+
const pending = [...handle.pendingFollowUps];
|
|
38
|
+
handle.pendingFollowUps.length = 0;
|
|
39
|
+
for (const message of pending) void handle.session.prompt(message, { source: "api", expandPromptTemplates: false }).catch(() => {});
|
|
40
|
+
}
|
|
41
|
+
return handle;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function updateLiveAgentStatus(agentId: string, status: CrewAgentRecord["status"]): void {
|
|
45
|
+
const handle = liveAgents.get(agentId);
|
|
46
|
+
if (!handle) return;
|
|
47
|
+
handle.status = status;
|
|
48
|
+
handle.updatedAt = new Date().toISOString();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getLiveAgent(agentIdOrTaskId: string): LiveAgentHandle | undefined {
|
|
52
|
+
return liveAgents.get(agentIdOrTaskId) ?? [...liveAgents.values()].find((entry) => entry.taskId === agentIdOrTaskId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function listLiveAgents(): LiveAgentHandle[] {
|
|
56
|
+
return [...liveAgents.values()].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function steerLiveAgent(agentIdOrTaskId: string, message: string): Promise<LiveAgentHandle> {
|
|
60
|
+
const handle = getLiveAgent(agentIdOrTaskId);
|
|
61
|
+
if (!handle) throw new Error(`Live agent '${agentIdOrTaskId}' is not registered in this process.`);
|
|
62
|
+
if (typeof handle.session.steer !== "function") {
|
|
63
|
+
handle.pendingSteers.push(message);
|
|
64
|
+
return handle;
|
|
65
|
+
}
|
|
66
|
+
await handle.session.steer(message);
|
|
67
|
+
handle.updatedAt = new Date().toISOString();
|
|
68
|
+
return handle;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function followUpLiveAgent(agentIdOrTaskId: string, prompt: string): Promise<LiveAgentHandle> {
|
|
72
|
+
const handle = getLiveAgent(agentIdOrTaskId);
|
|
73
|
+
if (!handle) throw new Error(`Live agent '${agentIdOrTaskId}' is not registered in this process.`);
|
|
74
|
+
if (typeof handle.session.prompt !== "function") {
|
|
75
|
+
handle.pendingFollowUps.push(prompt);
|
|
76
|
+
return handle;
|
|
77
|
+
}
|
|
78
|
+
await handle.session.prompt(prompt, { source: "api", expandPromptTemplates: false });
|
|
79
|
+
handle.updatedAt = new Date().toISOString();
|
|
80
|
+
return handle;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function stopLiveAgent(agentIdOrTaskId: string): Promise<LiveAgentHandle> {
|
|
84
|
+
const handle = getLiveAgent(agentIdOrTaskId);
|
|
85
|
+
if (!handle) throw new Error(`Live agent '${agentIdOrTaskId}' is not registered in this process.`);
|
|
86
|
+
if (typeof handle.session.abort !== "function") throw new Error(`Live agent '${agentIdOrTaskId}' does not expose abort().`);
|
|
87
|
+
await handle.session.abort();
|
|
88
|
+
handle.status = "stopped";
|
|
89
|
+
handle.updatedAt = new Date().toISOString();
|
|
90
|
+
return handle;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function resumeLiveAgent(agentIdOrTaskId: string, prompt: string): Promise<LiveAgentHandle> {
|
|
94
|
+
const handle = getLiveAgent(agentIdOrTaskId);
|
|
95
|
+
if (!handle) throw new Error(`Live agent '${agentIdOrTaskId}' is not registered in this process.`);
|
|
96
|
+
if (typeof handle.session.prompt !== "function") throw new Error(`Live agent '${agentIdOrTaskId}' does not expose prompt().`);
|
|
97
|
+
handle.status = "running";
|
|
98
|
+
await handle.session.prompt(prompt, { source: "api", expandPromptTemplates: false });
|
|
99
|
+
handle.status = "completed";
|
|
100
|
+
handle.updatedAt = new Date().toISOString();
|
|
101
|
+
return handle;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function clearLiveAgentsForTest(): void {
|
|
105
|
+
liveAgents.clear();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Phase 7/G4: Send an IRC message to a specific live agent (DM).
|
|
109
|
+
* Uses non-blocking delivery via sendCustomMessage when available.
|
|
110
|
+
* Falls back to session.prompt (blocking) when not.
|
|
111
|
+
*/
|
|
112
|
+
export function sendIrcMessage(targetAgentId: string, message: IrcMessage): void {
|
|
113
|
+
const handle = getLiveAgent(targetAgentId);
|
|
114
|
+
if (!handle) return;
|
|
115
|
+
handle.pendingMessages.push(message);
|
|
116
|
+
handle.updatedAt = new Date().toISOString();
|
|
117
|
+
// G4: Try non-blocking delivery via sendCustomMessage
|
|
118
|
+
const session = handle.session as Record<string, unknown>;
|
|
119
|
+
if (typeof session.sendCustomMessage === "function") {
|
|
120
|
+
try {
|
|
121
|
+
(session.sendCustomMessage as (msg: unknown, opts?: unknown) => void)(
|
|
122
|
+
{ customType: "irc", content: `[DM from ${message.from}] ${message.content}`, display: "collapsed" },
|
|
123
|
+
{ deliverAs: "followUp", triggerTurn: false },
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
} catch {
|
|
127
|
+
// Fall through to prompt-based delivery
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Fallback: inject as prompt (blocking)
|
|
131
|
+
if (typeof handle.session.prompt === "function") {
|
|
132
|
+
const ircPrompt = `[Message from ${message.from}] ${message.content}`;
|
|
133
|
+
void handle.session.prompt(ircPrompt, { source: "api", expandPromptTemplates: false }).catch(() => {});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Phase 7/G4: Broadcast an IRC message to all live agents except the sender.
|
|
138
|
+
* Uses non-blocking delivery via sendCustomMessage when available.
|
|
139
|
+
* Returns recipient IDs.
|
|
140
|
+
*/
|
|
141
|
+
export function broadcastIrcMessage(fromAgentId: string, message: IrcMessage): string[] {
|
|
142
|
+
const recipients: string[] = [];
|
|
143
|
+
for (const handle of liveAgents.values()) {
|
|
144
|
+
if (handle.agentId === fromAgentId) continue;
|
|
145
|
+
if (handle.status !== "running" && handle.status !== "queued") continue;
|
|
146
|
+
handle.pendingMessages.push(message);
|
|
147
|
+
handle.updatedAt = new Date().toISOString();
|
|
148
|
+
// G4: Try non-blocking delivery
|
|
149
|
+
const session = handle.session as Record<string, unknown>;
|
|
150
|
+
if (typeof session.sendCustomMessage === "function") {
|
|
151
|
+
try {
|
|
152
|
+
(session.sendCustomMessage as (msg: unknown, opts?: unknown) => void)(
|
|
153
|
+
{ customType: "irc", content: `[Broadcast from ${message.from}] ${message.content}`, display: "collapsed" },
|
|
154
|
+
{ deliverAs: "followUp", triggerTurn: false },
|
|
155
|
+
);
|
|
156
|
+
recipients.push(handle.agentId);
|
|
157
|
+
continue;
|
|
158
|
+
} catch {
|
|
159
|
+
// Fall through to prompt-based delivery
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Fallback: inject as prompt
|
|
163
|
+
if (typeof handle.session.prompt === "function") {
|
|
164
|
+
const ircPrompt = `[Broadcast from ${message.from}] ${message.content}`;
|
|
165
|
+
void handle.session.prompt(ircPrompt, { source: "api", expandPromptTemplates: false }).catch(() => {});
|
|
166
|
+
}
|
|
167
|
+
recipients.push(handle.agentId);
|
|
168
|
+
}
|
|
169
|
+
return recipients;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Phase 7: Get pending IRC messages for an agent (and clear them). */
|
|
173
|
+
export function drainIrcMessages(agentIdOrTaskId: string): IrcMessage[] {
|
|
174
|
+
const handle = getLiveAgent(agentIdOrTaskId);
|
|
175
|
+
if (!handle) return [];
|
|
176
|
+
const messages = [...handle.pendingMessages];
|
|
177
|
+
handle.pendingMessages.length = 0;
|
|
178
|
+
return messages;
|
|
179
|
+
}
|
|
@@ -28,7 +28,7 @@ export function parseLiveControlRealtimeMessage(raw: unknown): LiveAgentControlR
|
|
|
28
28
|
const message = raw as { type?: unknown; version?: unknown; request?: unknown };
|
|
29
29
|
if (message.type !== "live-control" || message.version !== 1 || !message.request || typeof message.request !== "object" || Array.isArray(message.request)) return undefined;
|
|
30
30
|
const request = message.request as Partial<LiveAgentControlRequest>;
|
|
31
|
-
return typeof request.id === "string" && typeof request.runId === "string" && typeof request.taskId === "string" && (request.operation === "steer" || request.operation === "stop" || request.operation === "resume") && typeof request.createdAt === "string" ? request as LiveAgentControlRequest : undefined;
|
|
31
|
+
return typeof request.id === "string" && typeof request.runId === "string" && typeof request.taskId === "string" && (request.operation === "steer" || request.operation === "follow-up" || request.operation === "stop" || request.operation === "resume") && typeof request.createdAt === "string" ? request as LiveAgentControlRequest : undefined;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export function clearLiveControlRealtimeForTest(): void {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G5: Extension runner bridge for live-session workers.
|
|
3
|
+
*
|
|
4
|
+
* Bridges pi-crew's extension lifecycle with the Pi SDK session's
|
|
5
|
+
* extension runner. Verified against actual SDK API surface:
|
|
6
|
+
*
|
|
7
|
+
* Session methods:
|
|
8
|
+
* - sendCustomMessage(message, options?)
|
|
9
|
+
* - sendUserMessage(content, options?)
|
|
10
|
+
* - getActiveToolNames() / setActiveToolsByName()
|
|
11
|
+
* - getAllTools() / getToolDefinition()
|
|
12
|
+
* - steer(text) / prompt(text, options?)
|
|
13
|
+
* - abort()
|
|
14
|
+
* - getContextUsage()
|
|
15
|
+
* - bindExtensions()
|
|
16
|
+
* - compact()
|
|
17
|
+
* - getSessionStats()
|
|
18
|
+
*
|
|
19
|
+
* ExtensionRunner methods:
|
|
20
|
+
* - initialize(apis, host)
|
|
21
|
+
* - emit(event)
|
|
22
|
+
* - hasHandlers(eventType)
|
|
23
|
+
* - getAllRegisteredTools()
|
|
24
|
+
* - onError(listener)
|
|
25
|
+
* - shutdown()
|
|
26
|
+
*
|
|
27
|
+
* ExtensionContext actions (via registerTool):
|
|
28
|
+
* - sendMessage / sendUserMessage / appendEntry / setLabel
|
|
29
|
+
* - setActiveTools / getActiveTools / getAllTools
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type { YieldResult } from "./yield-handler.ts";
|
|
33
|
+
|
|
34
|
+
export interface ExtensionBridgeApis {
|
|
35
|
+
sendMessage: (message: unknown, options?: Record<string, unknown>) => void;
|
|
36
|
+
sendUserMessage: (content: unknown, options?: Record<string, unknown>) => void;
|
|
37
|
+
appendEntry: (customType: string, data: unknown) => void;
|
|
38
|
+
setLabel: (targetId: string, label: string) => void;
|
|
39
|
+
getActiveTools: () => string[];
|
|
40
|
+
getAllTools: () => string[];
|
|
41
|
+
setActiveTools: (toolNames: string[]) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ExtensionHostApis {
|
|
45
|
+
getModel: () => unknown;
|
|
46
|
+
isIdle: () => boolean;
|
|
47
|
+
abort: () => void;
|
|
48
|
+
hasPendingMessages: () => boolean;
|
|
49
|
+
shutdown: () => void;
|
|
50
|
+
getContextUsage: () => unknown;
|
|
51
|
+
getSystemPrompt: () => string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Pi SDK session-like object with the methods we need.
|
|
56
|
+
* Verified against actual `createAgentSession().session` prototype.
|
|
57
|
+
*/
|
|
58
|
+
interface PiSdkSession {
|
|
59
|
+
sendCustomMessage: (message: unknown, options?: Record<string, unknown>) => void;
|
|
60
|
+
sendUserMessage: (content: unknown, options?: Record<string, unknown>) => void;
|
|
61
|
+
getActiveToolNames: () => string[];
|
|
62
|
+
getAllTools: () => string[];
|
|
63
|
+
setActiveToolsByName: (toolNames: string[]) => void;
|
|
64
|
+
steer: (text: string) => Promise<void>;
|
|
65
|
+
prompt: (text: string, options?: Record<string, unknown>) => Promise<void>;
|
|
66
|
+
abort: () => void | Promise<void>;
|
|
67
|
+
getContextUsage: () => unknown;
|
|
68
|
+
subscribe: (listener: (event: unknown) => void) => () => void;
|
|
69
|
+
bindExtensions: (bindings?: Record<string, unknown>) => Promise<void>;
|
|
70
|
+
compact: (options?: unknown) => void;
|
|
71
|
+
getSessionStats: () => unknown;
|
|
72
|
+
isStreaming?: boolean;
|
|
73
|
+
model?: unknown;
|
|
74
|
+
systemPrompt?: string;
|
|
75
|
+
pendingMessageCount?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build extension bridge APIs from a Pi SDK session.
|
|
80
|
+
* Returns null if the session doesn't support extension running.
|
|
81
|
+
*/
|
|
82
|
+
export function buildExtensionBridge(session: PiSdkSession): { apis: ExtensionBridgeApis; host: ExtensionHostApis } | null {
|
|
83
|
+
if (typeof session.sendCustomMessage !== "function") return null;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
apis: {
|
|
87
|
+
sendMessage: (message, options) => {
|
|
88
|
+
try {
|
|
89
|
+
session.sendCustomMessage(message, options);
|
|
90
|
+
} catch {
|
|
91
|
+
/* non-blocking */
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
sendUserMessage: (content, options) => {
|
|
95
|
+
try {
|
|
96
|
+
session.sendUserMessage(content, options);
|
|
97
|
+
} catch {
|
|
98
|
+
/* non-blocking */
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
appendEntry: () => {
|
|
102
|
+
// appendEntry requires sessionManager access which isn't directly on session
|
|
103
|
+
// This is a no-op placeholder; extensions that rely on it will gracefully degrade
|
|
104
|
+
},
|
|
105
|
+
setLabel: () => {
|
|
106
|
+
// setLabel requires sessionManager access — no-op placeholder
|
|
107
|
+
},
|
|
108
|
+
getActiveTools: () => {
|
|
109
|
+
try {
|
|
110
|
+
return session.getActiveToolNames();
|
|
111
|
+
} catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
getAllTools: () => {
|
|
116
|
+
try {
|
|
117
|
+
return session.getAllTools();
|
|
118
|
+
} catch {
|
|
119
|
+
return session.getActiveToolNames();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
setActiveTools: (toolNames) => {
|
|
123
|
+
try {
|
|
124
|
+
session.setActiveToolsByName(toolNames);
|
|
125
|
+
} catch {
|
|
126
|
+
/* ignore */
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
host: {
|
|
131
|
+
getModel: () => session.model,
|
|
132
|
+
isIdle: () => !session.isStreaming,
|
|
133
|
+
abort: () => {
|
|
134
|
+
void session.abort();
|
|
135
|
+
},
|
|
136
|
+
hasPendingMessages: () => (session.pendingMessageCount ?? 0) > 0,
|
|
137
|
+
shutdown: () => {
|
|
138
|
+
/* no-op for live-session — caller manages session lifecycle */
|
|
139
|
+
},
|
|
140
|
+
getContextUsage: () => {
|
|
141
|
+
try {
|
|
142
|
+
return session.getContextUsage();
|
|
143
|
+
} catch {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
getSystemPrompt: () => session.systemPrompt ?? "",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 7: Inter-agent communication for live-session workers.
|
|
3
|
+
*
|
|
4
|
+
* Provides IRC-like messaging between live-session workers, adapted from
|
|
5
|
+
* oh-my-pi's IrcTool pattern. Uses the existing LiveAgentHandle manager
|
|
6
|
+
* for message routing.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - DM: Send a message to a specific agent
|
|
10
|
+
* - Broadcast: Send a message to all live agents
|
|
11
|
+
* - Side-channel: Non-blocking message injection (via pendingFollowUps)
|
|
12
|
+
*
|
|
13
|
+
* For child-process workers, messages fall back to file-based mailbox.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type IrcOperation = "send" | "list";
|
|
17
|
+
|
|
18
|
+
export interface IrcMessage {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
content: string;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
/** Whether the sender expects a reply. */
|
|
24
|
+
awaitReply?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IrcSendMessage {
|
|
28
|
+
op: IrcOperation;
|
|
29
|
+
/** Target agent ID or "all" for broadcast. */
|
|
30
|
+
to: string;
|
|
31
|
+
/** Message content. */
|
|
32
|
+
message: string;
|
|
33
|
+
/** Whether to wait for a reply (default: true for DM, false for broadcast). */
|
|
34
|
+
awaitReply?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface IrcListResult {
|
|
38
|
+
peers: Array<{ id: string; name: string; status: string }>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build IRC peer roster for injection into system prompt.
|
|
43
|
+
* Lists all currently live agents except the caller.
|
|
44
|
+
*/
|
|
45
|
+
export function renderIrcPeerRoster(selfId: string, peers: Array<{ agentId: string; status: string }>): string {
|
|
46
|
+
const visible = peers.filter((p) => p.agentId !== selfId && (p.status === "running" || p.status === "idle"));
|
|
47
|
+
if (visible.length === 0) return "- (no other live agents)";
|
|
48
|
+
return visible.map((peer) => `- \`${peer.agentId}\` (${peer.status})`).join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the IRC system prompt section for a live-session worker.
|
|
53
|
+
*/
|
|
54
|
+
export function buildIrcSystemSection(selfId: string, peers: Array<{ agentId: string; status: string }>): string {
|
|
55
|
+
const roster = renderIrcPeerRoster(selfId, peers);
|
|
56
|
+
return [
|
|
57
|
+
"## Inter-Agent Communication",
|
|
58
|
+
`Your agent ID: \`${selfId}\``,
|
|
59
|
+
"You can send messages to other live agents via the `irc` tool.",
|
|
60
|
+
"Available peers:",
|
|
61
|
+
roster,
|
|
62
|
+
].join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Route an IRC message to the appropriate agent(s).
|
|
67
|
+
* Returns the list of agent IDs that received the message.
|
|
68
|
+
*/
|
|
69
|
+
export function routeIrcMessage(
|
|
70
|
+
message: IrcSendMessage,
|
|
71
|
+
selfId: string,
|
|
72
|
+
routing: {
|
|
73
|
+
sendDm: (agentId: string, content: string) => void;
|
|
74
|
+
broadcast: (content: string, excludeId: string) => string[];
|
|
75
|
+
},
|
|
76
|
+
): { deliveredTo: string[]; error?: string } {
|
|
77
|
+
if (!message.to || !message.message?.trim()) {
|
|
78
|
+
return { deliveredTo: [], error: "Missing 'to' (agent ID or 'all') and 'message' fields." };
|
|
79
|
+
}
|
|
80
|
+
if (message.to === selfId) {
|
|
81
|
+
return { deliveredTo: [], error: "Cannot send a message to yourself." };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (message.to === "all") {
|
|
85
|
+
const recipients = routing.broadcast(message.message, selfId);
|
|
86
|
+
return { deliveredTo: recipients };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// DM to specific agent
|
|
90
|
+
routing.sendDm(message.to, message.message);
|
|
91
|
+
return { deliveredTo: [message.to] };
|
|
92
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 8: Monitoring and observability for live-session workers.
|
|
3
|
+
*
|
|
4
|
+
* Provides health checks, metrics collection, and diagnostics
|
|
5
|
+
* for live-session workers running in-process.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface LiveSessionHealth {
|
|
9
|
+
/** Total number of registered live agents. */
|
|
10
|
+
totalAgents: number;
|
|
11
|
+
/** Number of agents currently running. */
|
|
12
|
+
runningAgents: number;
|
|
13
|
+
/** Number of agents in idle state. */
|
|
14
|
+
idleAgents: number;
|
|
15
|
+
/** Number of agents that have completed. */
|
|
16
|
+
completedAgents: number;
|
|
17
|
+
/** Number of agents that have failed. */
|
|
18
|
+
failedAgents: number;
|
|
19
|
+
/** Total tokens consumed across all live sessions. */
|
|
20
|
+
totalTokens: number;
|
|
21
|
+
/** Timestamp of this health snapshot. */
|
|
22
|
+
timestamp: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LiveSessionMetrics {
|
|
26
|
+
agentId: string;
|
|
27
|
+
taskId: string;
|
|
28
|
+
status: string;
|
|
29
|
+
/** Accumulated usage from session stats. */
|
|
30
|
+
usage?: {
|
|
31
|
+
input?: number;
|
|
32
|
+
output?: number;
|
|
33
|
+
cacheRead?: number;
|
|
34
|
+
cacheWrite?: number;
|
|
35
|
+
cost?: number;
|
|
36
|
+
turns?: number;
|
|
37
|
+
};
|
|
38
|
+
/** Session duration in milliseconds. */
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
/** Number of IRC messages received. */
|
|
41
|
+
ircMessagesReceived?: number;
|
|
42
|
+
/** Number of yield reminders sent. */
|
|
43
|
+
yieldReminders?: number;
|
|
44
|
+
/** Whether yield was called. */
|
|
45
|
+
yieldCalled: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Collect health snapshot from live agent handles.
|
|
50
|
+
*/
|
|
51
|
+
export function collectLiveSessionHealth(
|
|
52
|
+
agents: Array<{ status: string }>,
|
|
53
|
+
getUsage: (agentId: string) => { input?: number; output?: number; turns?: number } | undefined,
|
|
54
|
+
): LiveSessionHealth {
|
|
55
|
+
let running = 0;
|
|
56
|
+
let idle = 0;
|
|
57
|
+
let completed = 0;
|
|
58
|
+
let failed = 0;
|
|
59
|
+
let totalTokens = 0;
|
|
60
|
+
|
|
61
|
+
for (const agent of agents) {
|
|
62
|
+
switch (agent.status) {
|
|
63
|
+
case "running": running++; break;
|
|
64
|
+
case "idle": idle++; break;
|
|
65
|
+
case "completed": completed++; break;
|
|
66
|
+
case "failed": failed++; break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Sum tokens from usage data
|
|
71
|
+
for (const agent of agents) {
|
|
72
|
+
const agentAny = agent as Record<string, unknown>;
|
|
73
|
+
const agentId = agentAny.agentId as string | undefined;
|
|
74
|
+
if (agentId) {
|
|
75
|
+
const usage = getUsage(agentId);
|
|
76
|
+
if (usage) {
|
|
77
|
+
totalTokens += (usage.input ?? 0) + (usage.output ?? 0);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
totalAgents: agents.length,
|
|
84
|
+
runningAgents: running,
|
|
85
|
+
idleAgents: idle,
|
|
86
|
+
completedAgents: completed,
|
|
87
|
+
failedAgents: failed,
|
|
88
|
+
totalTokens,
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build a diagnostic summary string for logging.
|
|
95
|
+
*/
|
|
96
|
+
export function formatLiveSessionDiagnostics(health: LiveSessionHealth): string {
|
|
97
|
+
return [
|
|
98
|
+
`[Live-Session Health] agents=${health.totalAgents} running=${health.runningAgents} idle=${health.idleAgents} completed=${health.completedAgents} failed=${health.failedAgents} tokens=${health.totalTokens}`,
|
|
99
|
+
].join("\n");
|
|
100
|
+
}
|