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,88 +1,88 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type { TeamRunManifest } from "../state/types.ts";
|
|
4
|
-
import { agentStateFile, ensureAgentStateDir } from "./crew-agent-records.ts";
|
|
5
|
-
|
|
6
|
-
export type LiveAgentControlOperation = "steer" | "follow-up" | "stop" | "resume";
|
|
7
|
-
|
|
8
|
-
export interface LiveAgentControlRequest {
|
|
9
|
-
id: string;
|
|
10
|
-
runId: string;
|
|
11
|
-
taskId: string;
|
|
12
|
-
agentId?: string;
|
|
13
|
-
operation: LiveAgentControlOperation;
|
|
14
|
-
message?: string;
|
|
15
|
-
createdAt: string;
|
|
16
|
-
processedAt?: string;
|
|
17
|
-
error?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface LiveAgentControlCursor {
|
|
21
|
-
offset: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function liveAgentControlPath(manifest: TeamRunManifest, taskId: string): string {
|
|
25
|
-
return path.join(ensureAgentStateDir(manifest, taskId), "live-control.jsonl");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function liveAgentControlFile(manifest: TeamRunManifest, taskId: string): string {
|
|
29
|
-
return agentStateFile(manifest, taskId, "live-control.jsonl");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function requestId(): string {
|
|
33
|
-
return `ctrl_${Date.now().toString(36)}_${Math.random().toString(16).slice(2, 10)}`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function appendLiveAgentControlRequest(manifest: TeamRunManifest, input: { taskId: string; agentId?: string; operation: LiveAgentControlOperation; message?: string }): LiveAgentControlRequest {
|
|
37
|
-
const request: LiveAgentControlRequest = {
|
|
38
|
-
id: requestId(),
|
|
39
|
-
runId: manifest.runId,
|
|
40
|
-
taskId: input.taskId,
|
|
41
|
-
agentId: input.agentId,
|
|
42
|
-
operation: input.operation,
|
|
43
|
-
message: input.message,
|
|
44
|
-
createdAt: new Date().toISOString(),
|
|
45
|
-
};
|
|
46
|
-
const filePath = liveAgentControlFile(manifest, input.taskId);
|
|
47
|
-
fs.appendFileSync(filePath, `${JSON.stringify(request)}\n`, "utf-8");
|
|
48
|
-
return request;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function readLiveAgentControlRequests(manifest: TeamRunManifest, taskId: string, cursor: LiveAgentControlCursor = { offset: 0 }): { requests: LiveAgentControlRequest[]; cursor: LiveAgentControlCursor } {
|
|
52
|
-
let filePath: string;
|
|
53
|
-
try {
|
|
54
|
-
filePath = liveAgentControlFile(manifest, taskId);
|
|
55
|
-
} catch {
|
|
56
|
-
return { requests: [], cursor };
|
|
57
|
-
}
|
|
58
|
-
if (!fs.existsSync(filePath)) return { requests: [], cursor };
|
|
59
|
-
const text = fs.readFileSync(filePath, "utf-8");
|
|
60
|
-
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
61
|
-
const requests = lines.slice(cursor.offset).flatMap((line) => {
|
|
62
|
-
try {
|
|
63
|
-
const parsed = JSON.parse(line) as LiveAgentControlRequest;
|
|
64
|
-
return parsed && parsed.runId === manifest.runId && parsed.taskId === taskId ? [parsed] : [];
|
|
65
|
-
} catch {
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
return { requests, cursor: { offset: lines.length } };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function applyLiveAgentControlRequest(input: { request: LiveAgentControlRequest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; seenRequestIds?: Set<string> }): Promise<boolean> {
|
|
73
|
-
const { request, taskId, agentId, session, seenRequestIds } = input;
|
|
74
|
-
if (seenRequestIds?.has(request.id)) return false;
|
|
75
|
-
if (request.agentId && request.agentId !== agentId && request.agentId !== taskId) return false;
|
|
76
|
-
seenRequestIds?.add(request.id);
|
|
77
|
-
if (request.operation === "steer") await session.steer?.(request.message ?? "Please report current status and wrap up if possible.");
|
|
78
|
-
else if (request.operation === "follow-up") await session.prompt?.(request.message ?? "Please continue with the follow-up request.", { source: "api", expandPromptTemplates: false });
|
|
79
|
-
else if (request.operation === "resume") await session.prompt?.(request.message ?? "Please resume and report final status.", { source: "api", expandPromptTemplates: false });
|
|
80
|
-
else if (request.operation === "stop") await session.abort?.();
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function applyLiveAgentControlRequests(input: { manifest: TeamRunManifest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; cursor: LiveAgentControlCursor; seenRequestIds?: Set<string> }): Promise<LiveAgentControlCursor> {
|
|
85
|
-
const batch = readLiveAgentControlRequests(input.manifest, input.taskId, input.cursor);
|
|
86
|
-
for (const request of batch.requests) await applyLiveAgentControlRequest({ request, taskId: input.taskId, agentId: input.agentId, session: input.session, seenRequestIds: input.seenRequestIds });
|
|
87
|
-
return batch.cursor;
|
|
88
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
4
|
+
import { agentStateFile, ensureAgentStateDir } from "./crew-agent-records.ts";
|
|
5
|
+
|
|
6
|
+
export type LiveAgentControlOperation = "steer" | "follow-up" | "stop" | "resume";
|
|
7
|
+
|
|
8
|
+
export interface LiveAgentControlRequest {
|
|
9
|
+
id: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
taskId: string;
|
|
12
|
+
agentId?: string;
|
|
13
|
+
operation: LiveAgentControlOperation;
|
|
14
|
+
message?: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
processedAt?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LiveAgentControlCursor {
|
|
21
|
+
offset: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function liveAgentControlPath(manifest: TeamRunManifest, taskId: string): string {
|
|
25
|
+
return path.join(ensureAgentStateDir(manifest, taskId), "live-control.jsonl");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function liveAgentControlFile(manifest: TeamRunManifest, taskId: string): string {
|
|
29
|
+
return agentStateFile(manifest, taskId, "live-control.jsonl");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function requestId(): string {
|
|
33
|
+
return `ctrl_${Date.now().toString(36)}_${Math.random().toString(16).slice(2, 10)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function appendLiveAgentControlRequest(manifest: TeamRunManifest, input: { taskId: string; agentId?: string; operation: LiveAgentControlOperation; message?: string }): LiveAgentControlRequest {
|
|
37
|
+
const request: LiveAgentControlRequest = {
|
|
38
|
+
id: requestId(),
|
|
39
|
+
runId: manifest.runId,
|
|
40
|
+
taskId: input.taskId,
|
|
41
|
+
agentId: input.agentId,
|
|
42
|
+
operation: input.operation,
|
|
43
|
+
message: input.message,
|
|
44
|
+
createdAt: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
const filePath = liveAgentControlFile(manifest, input.taskId);
|
|
47
|
+
fs.appendFileSync(filePath, `${JSON.stringify(request)}\n`, "utf-8");
|
|
48
|
+
return request;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function readLiveAgentControlRequests(manifest: TeamRunManifest, taskId: string, cursor: LiveAgentControlCursor = { offset: 0 }): { requests: LiveAgentControlRequest[]; cursor: LiveAgentControlCursor } {
|
|
52
|
+
let filePath: string;
|
|
53
|
+
try {
|
|
54
|
+
filePath = liveAgentControlFile(manifest, taskId);
|
|
55
|
+
} catch {
|
|
56
|
+
return { requests: [], cursor };
|
|
57
|
+
}
|
|
58
|
+
if (!fs.existsSync(filePath)) return { requests: [], cursor };
|
|
59
|
+
const text = fs.readFileSync(filePath, "utf-8");
|
|
60
|
+
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
61
|
+
const requests = lines.slice(cursor.offset).flatMap((line) => {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(line) as LiveAgentControlRequest;
|
|
64
|
+
return parsed && parsed.runId === manifest.runId && parsed.taskId === taskId ? [parsed] : [];
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return { requests, cursor: { offset: lines.length } };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function applyLiveAgentControlRequest(input: { request: LiveAgentControlRequest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; seenRequestIds?: Set<string> }): Promise<boolean> {
|
|
73
|
+
const { request, taskId, agentId, session, seenRequestIds } = input;
|
|
74
|
+
if (seenRequestIds?.has(request.id)) return false;
|
|
75
|
+
if (request.agentId && request.agentId !== agentId && request.agentId !== taskId) return false;
|
|
76
|
+
seenRequestIds?.add(request.id);
|
|
77
|
+
if (request.operation === "steer") await session.steer?.(request.message ?? "Please report current status and wrap up if possible.");
|
|
78
|
+
else if (request.operation === "follow-up") await session.prompt?.(request.message ?? "Please continue with the follow-up request.", { source: "api", expandPromptTemplates: false });
|
|
79
|
+
else if (request.operation === "resume") await session.prompt?.(request.message ?? "Please resume and report final status.", { source: "api", expandPromptTemplates: false });
|
|
80
|
+
else if (request.operation === "stop") await session.abort?.();
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function applyLiveAgentControlRequests(input: { manifest: TeamRunManifest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; cursor: LiveAgentControlCursor; seenRequestIds?: Set<string> }): Promise<LiveAgentControlCursor> {
|
|
85
|
+
const batch = readLiveAgentControlRequests(input.manifest, input.taskId, input.cursor);
|
|
86
|
+
for (const request of batch.requests) await applyLiveAgentControlRequest({ request, taskId: input.taskId, agentId: input.agentId, session: input.session, seenRequestIds: input.seenRequestIds });
|
|
87
|
+
return batch.cursor;
|
|
88
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CrewAgentRecord } from "./crew-agent-runtime.ts";
|
|
2
|
+
import type { IrcMessage } from "./live-irc.ts";
|
|
2
3
|
|
|
3
4
|
type LiveSessionHandle = {
|
|
4
5
|
steer?: (text: string) => Promise<void>;
|
|
@@ -16,14 +17,16 @@ export interface LiveAgentHandle {
|
|
|
16
17
|
status: CrewAgentRecord["status"];
|
|
17
18
|
pendingSteers: string[];
|
|
18
19
|
pendingFollowUps: string[];
|
|
20
|
+
/** Phase 7: Pending IRC messages for this agent. */
|
|
21
|
+
pendingMessages: IrcMessage[];
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const liveAgents = new Map<string, LiveAgentHandle>();
|
|
22
25
|
|
|
23
|
-
export function registerLiveAgent(input: Omit<LiveAgentHandle, "createdAt" | "updatedAt" | "pendingSteers" | "pendingFollowUps">): LiveAgentHandle {
|
|
26
|
+
export function registerLiveAgent(input: Omit<LiveAgentHandle, "createdAt" | "updatedAt" | "pendingSteers" | "pendingFollowUps" | "pendingMessages">): LiveAgentHandle {
|
|
24
27
|
const now = new Date().toISOString();
|
|
25
28
|
const existing = liveAgents.get(input.agentId);
|
|
26
|
-
const handle: LiveAgentHandle = { ...input, createdAt: existing?.createdAt ?? now, updatedAt: now, pendingSteers: existing?.pendingSteers ?? [], pendingFollowUps: existing?.pendingFollowUps ?? [] };
|
|
29
|
+
const handle: LiveAgentHandle = { ...input, createdAt: existing?.createdAt ?? now, updatedAt: now, pendingSteers: existing?.pendingSteers ?? [], pendingFollowUps: existing?.pendingFollowUps ?? [], pendingMessages: existing?.pendingMessages ?? [] };
|
|
27
30
|
liveAgents.set(input.agentId, handle);
|
|
28
31
|
if (handle.pendingSteers.length && typeof handle.session.steer === "function") {
|
|
29
32
|
const pending = [...handle.pendingSteers];
|
|
@@ -101,3 +104,76 @@ export async function resumeLiveAgent(agentIdOrTaskId: string, prompt: string):
|
|
|
101
104
|
export function clearLiveAgentsForTest(): void {
|
|
102
105
|
liveAgents.clear();
|
|
103
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
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import type { LiveAgentControlRequest } from "./live-agent-control.ts";
|
|
2
|
-
|
|
3
|
-
export interface LiveControlRealtimeMessage {
|
|
4
|
-
type: "live-control";
|
|
5
|
-
version: 1;
|
|
6
|
-
request: LiveAgentControlRequest;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
type Listener = (request: LiveAgentControlRequest) => void | Promise<void>;
|
|
10
|
-
|
|
11
|
-
const listeners = new Set<Listener>();
|
|
12
|
-
|
|
13
|
-
export function publishLiveControlRealtime(request: LiveAgentControlRequest): void {
|
|
14
|
-
for (const listener of [...listeners]) void listener(request);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function subscribeLiveControlRealtime(listener: Listener): () => void {
|
|
18
|
-
listeners.add(listener);
|
|
19
|
-
return () => listeners.delete(listener);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function liveControlRealtimeMessage(request: LiveAgentControlRequest): LiveControlRealtimeMessage {
|
|
23
|
-
return { type: "live-control", version: 1, request };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function parseLiveControlRealtimeMessage(raw: unknown): LiveAgentControlRequest | undefined {
|
|
27
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
28
|
-
const message = raw as { type?: unknown; version?: unknown; request?: unknown };
|
|
29
|
-
if (message.type !== "live-control" || message.version !== 1 || !message.request || typeof message.request !== "object" || Array.isArray(message.request)) return undefined;
|
|
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 === "follow-up" || request.operation === "stop" || request.operation === "resume") && typeof request.createdAt === "string" ? request as LiveAgentControlRequest : undefined;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function clearLiveControlRealtimeForTest(): void {
|
|
35
|
-
listeners.clear();
|
|
36
|
-
}
|
|
1
|
+
import type { LiveAgentControlRequest } from "./live-agent-control.ts";
|
|
2
|
+
|
|
3
|
+
export interface LiveControlRealtimeMessage {
|
|
4
|
+
type: "live-control";
|
|
5
|
+
version: 1;
|
|
6
|
+
request: LiveAgentControlRequest;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type Listener = (request: LiveAgentControlRequest) => void | Promise<void>;
|
|
10
|
+
|
|
11
|
+
const listeners = new Set<Listener>();
|
|
12
|
+
|
|
13
|
+
export function publishLiveControlRealtime(request: LiveAgentControlRequest): void {
|
|
14
|
+
for (const listener of [...listeners]) void listener(request);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function subscribeLiveControlRealtime(listener: Listener): () => void {
|
|
18
|
+
listeners.add(listener);
|
|
19
|
+
return () => listeners.delete(listener);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function liveControlRealtimeMessage(request: LiveAgentControlRequest): LiveControlRealtimeMessage {
|
|
23
|
+
return { type: "live-control", version: 1, request };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseLiveControlRealtimeMessage(raw: unknown): LiveAgentControlRequest | undefined {
|
|
27
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
28
|
+
const message = raw as { type?: unknown; version?: unknown; request?: unknown };
|
|
29
|
+
if (message.type !== "live-control" || message.version !== 1 || !message.request || typeof message.request !== "object" || Array.isArray(message.request)) return undefined;
|
|
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 === "follow-up" || request.operation === "stop" || request.operation === "resume") && typeof request.createdAt === "string" ? request as LiveAgentControlRequest : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clearLiveControlRealtimeForTest(): void {
|
|
35
|
+
listeners.clear();
|
|
36
|
+
}
|
|
@@ -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
|
+
}
|