opencode-multiagent 0.2.1 → 0.4.0
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 +83 -0
- package/CHANGELOG.md +31 -0
- package/CONTRIBUTING.md +36 -0
- package/README.md +44 -168
- package/README.tr.md +84 -0
- package/RELEASE.md +68 -0
- package/agents/AGENTS.md +91 -0
- package/agents/auditor.md +67 -23
- package/agents/{worker.md → coder.md} +24 -17
- package/agents/docmaster.md +91 -0
- package/agents/executor.md +63 -79
- package/agents/planner.md +78 -58
- package/agents/reviewer.md +31 -15
- package/agents/scout.md +25 -17
- package/agents/sec-coder.md +83 -0
- package/agents/ui-coder.md +77 -0
- package/commands/board.md +17 -0
- package/commands/execute.md +9 -7
- package/commands/init-deep.md +7 -6
- package/commands/init.md +5 -5
- package/commands/inspect.md +6 -5
- package/commands/plan.md +8 -6
- package/commands/quality.md +4 -3
- package/commands/review.md +5 -3
- package/commands/status.md +5 -3
- package/defaults/AGENTS.md +48 -0
- package/defaults/opencode-multiagent.json +180 -0
- package/defaults/opencode-multiagent.schema.json +265 -0
- package/dist/control-plane.d.ts +4 -0
- package/dist/control-plane.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1916 -0
- package/dist/opencode-multiagent/compiler.d.ts +25 -0
- package/dist/opencode-multiagent/compiler.d.ts.map +1 -0
- package/dist/opencode-multiagent/constants.d.ts +128 -0
- package/dist/opencode-multiagent/constants.d.ts.map +1 -0
- package/dist/opencode-multiagent/correlation.d.ts +21 -0
- package/dist/opencode-multiagent/correlation.d.ts.map +1 -0
- package/dist/opencode-multiagent/defaults.d.ts +10 -0
- package/dist/opencode-multiagent/defaults.d.ts.map +1 -0
- package/dist/opencode-multiagent/hooks.d.ts +62 -0
- package/dist/opencode-multiagent/hooks.d.ts.map +1 -0
- package/dist/opencode-multiagent/log.d.ts +2 -0
- package/dist/opencode-multiagent/log.d.ts.map +1 -0
- package/dist/opencode-multiagent/markdown.d.ts +8 -0
- package/dist/opencode-multiagent/markdown.d.ts.map +1 -0
- package/dist/opencode-multiagent/mcp.d.ts +3 -0
- package/dist/opencode-multiagent/mcp.d.ts.map +1 -0
- package/dist/opencode-multiagent/policy.d.ts +5 -0
- package/dist/opencode-multiagent/policy.d.ts.map +1 -0
- package/dist/opencode-multiagent/quality.d.ts +18 -0
- package/dist/opencode-multiagent/quality.d.ts.map +1 -0
- package/dist/opencode-multiagent/runtime.d.ts +7 -0
- package/dist/opencode-multiagent/runtime.d.ts.map +1 -0
- package/dist/opencode-multiagent/session-tracker.d.ts +32 -0
- package/dist/opencode-multiagent/session-tracker.d.ts.map +1 -0
- package/dist/opencode-multiagent/skills.d.ts +17 -0
- package/dist/opencode-multiagent/skills.d.ts.map +1 -0
- package/dist/opencode-multiagent/supervision.d.ts +26 -0
- package/dist/opencode-multiagent/supervision.d.ts.map +1 -0
- package/dist/opencode-multiagent/task-manager.d.ts +54 -0
- package/dist/opencode-multiagent/task-manager.d.ts.map +1 -0
- package/dist/opencode-multiagent/telemetry.d.ts +28 -0
- package/dist/opencode-multiagent/telemetry.d.ts.map +1 -0
- package/dist/opencode-multiagent/tools.d.ts +87 -0
- package/dist/opencode-multiagent/tools.d.ts.map +1 -0
- package/dist/opencode-multiagent/types.d.ts +36 -0
- package/dist/opencode-multiagent/types.d.ts.map +1 -0
- package/dist/opencode-multiagent/utils.d.ts +9 -0
- package/dist/opencode-multiagent/utils.d.ts.map +1 -0
- package/docs/agents.md +148 -0
- package/docs/agents.tr.md +149 -0
- package/docs/configuration.md +244 -0
- package/docs/configuration.tr.md +244 -0
- package/docs/usage-guide.md +224 -0
- package/docs/usage-guide.tr.md +225 -0
- package/examples/opencode.with-overrides.json +3 -7
- package/package.json +23 -13
- package/skills/AGENTS.md +51 -0
- package/skills/advanced-evaluation/SKILL.md +37 -21
- package/skills/advanced-evaluation/manifest.json +2 -13
- package/skills/cek-context-engineering/SKILL.md +159 -87
- package/skills/cek-context-engineering/manifest.json +1 -3
- package/skills/cek-prompt-engineering/SKILL.md +13 -10
- package/skills/cek-prompt-engineering/manifest.json +1 -3
- package/skills/cek-test-prompt/SKILL.md +38 -28
- package/skills/cek-test-prompt/manifest.json +1 -3
- package/skills/cek-thought-based-reasoning/SKILL.md +75 -21
- package/skills/cek-thought-based-reasoning/manifest.json +1 -3
- package/skills/context-degradation/SKILL.md +14 -13
- package/skills/context-degradation/manifest.json +1 -3
- package/skills/debate/SKILL.md +23 -78
- package/skills/debate/manifest.json +2 -12
- package/skills/design-first/manifest.json +2 -13
- package/skills/dispatching-parallel-agents/SKILL.md +14 -3
- package/skills/dispatching-parallel-agents/manifest.json +1 -4
- package/skills/drift-analysis/SKILL.md +50 -29
- package/skills/drift-analysis/manifest.json +2 -12
- package/skills/evaluation/manifest.json +2 -12
- package/skills/executing-plans/SKILL.md +15 -8
- package/skills/executing-plans/manifest.json +1 -3
- package/skills/handoff-protocols/manifest.json +2 -12
- package/skills/parallel-investigation/SKILL.md +25 -12
- package/skills/parallel-investigation/manifest.json +1 -4
- package/skills/reflexion-critique/SKILL.md +21 -10
- package/skills/reflexion-critique/manifest.json +1 -3
- package/skills/reflexion-reflect/SKILL.md +36 -34
- package/skills/reflexion-reflect/manifest.json +2 -10
- package/skills/root-cause-analysis/manifest.json +2 -13
- package/skills/sadd-judge-with-debate/SKILL.md +50 -26
- package/skills/sadd-judge-with-debate/manifest.json +1 -3
- package/skills/structured-code-review/manifest.json +2 -11
- package/skills/task-decomposition/manifest.json +2 -13
- package/skills/verification-before-completion/manifest.json +2 -15
- package/skills/verification-gates/SKILL.md +27 -19
- package/skills/verification-gates/manifest.json +2 -12
- package/agents/advisor.md +0 -57
- package/agents/critic.md +0 -127
- package/agents/deep-worker.md +0 -65
- package/agents/devil.md +0 -36
- package/agents/heavy-worker.md +0 -68
- package/agents/lead.md +0 -155
- package/agents/librarian.md +0 -62
- package/agents/qa.md +0 -50
- package/agents/quick.md +0 -65
- package/agents/scribe.md +0 -78
- package/agents/strategist.md +0 -63
- package/agents/ui-heavy-worker.md +0 -62
- package/agents/ui-worker.md +0 -69
- package/agents/validator.md +0 -47
- package/defaults/agent-settings.json +0 -102
- package/defaults/agent-settings.schema.json +0 -25
- package/defaults/flags.json +0 -35
- package/defaults/flags.schema.json +0 -119
- package/defaults/mcp-defaults.json +0 -47
- package/defaults/mcp-defaults.schema.json +0 -38
- package/defaults/profiles.json +0 -53
- package/defaults/profiles.schema.json +0 -60
- package/defaults/team-profiles.json +0 -83
- package/src/control-plane.ts +0 -21
- package/src/index.ts +0 -8
- package/src/opencode-multiagent/compiler.ts +0 -168
- package/src/opencode-multiagent/constants.ts +0 -178
- package/src/opencode-multiagent/file-lock.ts +0 -90
- package/src/opencode-multiagent/hooks.ts +0 -599
- package/src/opencode-multiagent/log.ts +0 -12
- package/src/opencode-multiagent/mailbox.ts +0 -287
- package/src/opencode-multiagent/markdown.ts +0 -99
- package/src/opencode-multiagent/mcp.ts +0 -35
- package/src/opencode-multiagent/policy.ts +0 -67
- package/src/opencode-multiagent/quality.ts +0 -140
- package/src/opencode-multiagent/runtime.ts +0 -55
- package/src/opencode-multiagent/skills.ts +0 -144
- package/src/opencode-multiagent/supervision.ts +0 -156
- package/src/opencode-multiagent/task-manager.ts +0 -148
- package/src/opencode-multiagent/team-manager.ts +0 -219
- package/src/opencode-multiagent/team-tools.ts +0 -359
- package/src/opencode-multiagent/telemetry.ts +0 -124
- package/src/opencode-multiagent/utils.ts +0 -54
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inter-agent communication and team orchestration tools for opencode-multiagent.
|
|
3
|
-
*
|
|
4
|
-
* Exports `createTeamTools()` — a factory that returns a `{ [key: string]: ToolDefinition }`
|
|
5
|
-
* object ready to be spread into the `tool` field of the plugin `Hooks`.
|
|
6
|
-
*
|
|
7
|
-
* Integration (hooks.ts):
|
|
8
|
-
* ```ts
|
|
9
|
-
* import { createMailbox, createMailboxEventHandler } from "./mailbox.ts";
|
|
10
|
-
* import { createTeamManager, loadTeamProfiles } from "./team-manager.ts";
|
|
11
|
-
* import { createTeamTools } from "./team-tools.ts";
|
|
12
|
-
*
|
|
13
|
-
* const mailbox = createMailbox();
|
|
14
|
-
* const teamManager = createTeamManager();
|
|
15
|
-
*
|
|
16
|
-
* return {
|
|
17
|
-
* ...createPluginHooks({ client, flags, ... }),
|
|
18
|
-
* tool: createTeamTools(mailbox, teamManager, client, loadTeamProfiles),
|
|
19
|
-
* };
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { tool } from "@opencode-ai/plugin";
|
|
24
|
-
|
|
25
|
-
import { note } from "./log.ts";
|
|
26
|
-
import type { Mailbox } from "./mailbox.ts";
|
|
27
|
-
import type { TeamManager, TeamProfileRegistry } from "./team-manager.ts";
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Client type for team orchestration
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Minimal OpenCode client interface required by team tools.
|
|
35
|
-
* Defines session.create and a richer session.prompt (with optional `agent` field)
|
|
36
|
-
* in addition to the mailbox delivery fallback (promptAsync).
|
|
37
|
-
*
|
|
38
|
-
* The parts array intentionally uses a loose element type so this interface is
|
|
39
|
-
* structurally compatible with MailboxClient (required for mailbox.deliverPending).
|
|
40
|
-
*/
|
|
41
|
-
export type TeamClient = {
|
|
42
|
-
session?: {
|
|
43
|
-
/** Create a new child session. Returns an object with `data.id`. */
|
|
44
|
-
create?(options?: {
|
|
45
|
-
body?: { parentID?: string; title?: string };
|
|
46
|
-
}): Promise<{ data?: { id: string } | null } | null | undefined>;
|
|
47
|
-
/** Send a message to a session (typed SDK single-object form). */
|
|
48
|
-
prompt?(options: {
|
|
49
|
-
path: { id: string };
|
|
50
|
-
body: {
|
|
51
|
-
agent?: string;
|
|
52
|
-
parts: Array<{ type: string; text?: string; name?: string }>;
|
|
53
|
-
noReply?: boolean;
|
|
54
|
-
};
|
|
55
|
-
}): Promise<unknown>;
|
|
56
|
-
/** Runtime-only fallback used elsewhere in the plugin. */
|
|
57
|
-
promptAsync?(input: {
|
|
58
|
-
sessionID: string;
|
|
59
|
-
noReply?: boolean;
|
|
60
|
-
parts: Array<{ type: string; text: string }>;
|
|
61
|
-
}): Promise<unknown>;
|
|
62
|
-
};
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// Factory
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Build all team-related custom tools.
|
|
71
|
-
*
|
|
72
|
-
* @param mailbox Shared mailbox instance (from `createMailbox()`).
|
|
73
|
-
* @param teamManager Shared team manager (from `createTeamManager()`).
|
|
74
|
-
* @param client OpenCode client — used for session lifecycle and delivery.
|
|
75
|
-
* @param loadTeamProfiles Async loader for `defaults/team-profiles.json`.
|
|
76
|
-
*/
|
|
77
|
-
export const createTeamTools = (
|
|
78
|
-
mailbox: Mailbox,
|
|
79
|
-
teamManager: TeamManager,
|
|
80
|
-
client: TeamClient,
|
|
81
|
-
loadTeamProfiles: () => Promise<TeamProfileRegistry>,
|
|
82
|
-
) => ({
|
|
83
|
-
// -------------------------------------------------------------------------
|
|
84
|
-
// Mailbox: send / read
|
|
85
|
-
// -------------------------------------------------------------------------
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Send a message to a teammate agent by name or session ID.
|
|
89
|
-
* Immediate delivery is attempted; falls back to queued delivery on next idle.
|
|
90
|
-
*/
|
|
91
|
-
team_send_message: tool({
|
|
92
|
-
description:
|
|
93
|
-
"Send a message to a teammate agent by agent name or session ID. " +
|
|
94
|
-
"Use this to communicate task results, status updates, or requests to other agents.",
|
|
95
|
-
args: {
|
|
96
|
-
to: tool.schema
|
|
97
|
-
.string()
|
|
98
|
-
.describe("Target agent name (e.g. 'backend-dev') or raw session ID"),
|
|
99
|
-
message: tool.schema.string().describe("Message content to deliver to the target agent"),
|
|
100
|
-
},
|
|
101
|
-
async execute(args, ctx) {
|
|
102
|
-
if (!ctx.sessionID) throw new Error("[opencode-multiagent] team_send_message: missing session ID");
|
|
103
|
-
|
|
104
|
-
const targetSessionID = teamManager.resolveSessionID(args.to) ?? args.to;
|
|
105
|
-
const msg = mailbox.send(ctx.sessionID, targetSessionID, args.message);
|
|
106
|
-
|
|
107
|
-
const delivered = await mailbox.deliverPending(targetSessionID, client);
|
|
108
|
-
|
|
109
|
-
await note("mailbox_send", {
|
|
110
|
-
observation: true,
|
|
111
|
-
from: ctx.sessionID,
|
|
112
|
-
fromAgent: ctx.agent,
|
|
113
|
-
to: args.to,
|
|
114
|
-
targetSessionID,
|
|
115
|
-
messageID: msg.id,
|
|
116
|
-
delivered,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
return delivered > 0
|
|
120
|
-
? `Message delivered to ${args.to}.`
|
|
121
|
-
: `Message queued for ${args.to}. It will be delivered when that agent is next idle.`;
|
|
122
|
-
},
|
|
123
|
-
}),
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Read incoming messages. By default returns only unread messages and
|
|
127
|
-
* marks them as read. Pass `unreadOnly: false` to see the full inbox.
|
|
128
|
-
*/
|
|
129
|
-
team_read_messages: tool({
|
|
130
|
-
description:
|
|
131
|
-
"Read incoming messages from other agents. " +
|
|
132
|
-
"Returns messages sent to this session and marks them as read.",
|
|
133
|
-
args: {
|
|
134
|
-
unreadOnly: tool.schema
|
|
135
|
-
.boolean()
|
|
136
|
-
.optional()
|
|
137
|
-
.default(true)
|
|
138
|
-
.describe("When true (default) return only unread messages; false returns all messages"),
|
|
139
|
-
},
|
|
140
|
-
async execute(args, ctx) {
|
|
141
|
-
const msgs = args.unreadOnly
|
|
142
|
-
? mailbox.getUnread(ctx.sessionID)
|
|
143
|
-
: mailbox.getAll(ctx.sessionID);
|
|
144
|
-
|
|
145
|
-
msgs.forEach((m) => mailbox.markRead(m.id));
|
|
146
|
-
|
|
147
|
-
await note("mailbox_read", {
|
|
148
|
-
observation: true,
|
|
149
|
-
sessionID: ctx.sessionID,
|
|
150
|
-
agent: ctx.agent,
|
|
151
|
-
unreadOnly: args.unreadOnly,
|
|
152
|
-
count: msgs.length,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (msgs.length === 0) return "No messages.";
|
|
156
|
-
|
|
157
|
-
return JSON.stringify(
|
|
158
|
-
msgs.map((m) => ({
|
|
159
|
-
id: m.id,
|
|
160
|
-
from: m.from,
|
|
161
|
-
content: m.content,
|
|
162
|
-
timestamp: m.timestamp,
|
|
163
|
-
delivered: m.delivered,
|
|
164
|
-
})),
|
|
165
|
-
null,
|
|
166
|
-
2,
|
|
167
|
-
);
|
|
168
|
-
},
|
|
169
|
-
}),
|
|
170
|
-
|
|
171
|
-
// -------------------------------------------------------------------------
|
|
172
|
-
// Team orchestration: create / status / spawn profile
|
|
173
|
-
// -------------------------------------------------------------------------
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Create a new teammate agent session as a child of the current session.
|
|
177
|
-
*
|
|
178
|
-
* This tool:
|
|
179
|
-
* 1. Creates a child session via `client.session.create()`.
|
|
180
|
-
* 2. Registers the member in the team manager.
|
|
181
|
-
* 3. Sends the initial prompt to the new session.
|
|
182
|
-
*/
|
|
183
|
-
team_create: tool({
|
|
184
|
-
description:
|
|
185
|
-
"Create a new teammate agent session. " +
|
|
186
|
-
"The teammate runs as a child of the current session and receives an initial prompt.",
|
|
187
|
-
args: {
|
|
188
|
-
name: tool.schema
|
|
189
|
-
.string()
|
|
190
|
-
.describe("Logical team name for this member (e.g. 'backend-dev', 'tester')"),
|
|
191
|
-
agent: tool.schema
|
|
192
|
-
.string()
|
|
193
|
-
.describe("OpenCode agent ID to use for this session (e.g. 'backend-dev')"),
|
|
194
|
-
role: tool.schema
|
|
195
|
-
.string()
|
|
196
|
-
.optional()
|
|
197
|
-
.describe("Human-readable role description for status display"),
|
|
198
|
-
prompt: tool.schema.string().describe("Initial prompt to send to the new agent session"),
|
|
199
|
-
},
|
|
200
|
-
async execute(args, ctx) {
|
|
201
|
-
if (!client.session?.create) {
|
|
202
|
-
throw new Error("[opencode-multiagent] team_create: client.session.create is unavailable");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Set this session as the lead (idempotent).
|
|
206
|
-
teamManager.setLead(ctx.sessionID);
|
|
207
|
-
|
|
208
|
-
// 1. Create child session.
|
|
209
|
-
const result = await client.session.create({
|
|
210
|
-
body: { parentID: ctx.sessionID, title: `[team] ${args.name}` },
|
|
211
|
-
});
|
|
212
|
-
const sessionID = result?.data?.id;
|
|
213
|
-
if (!sessionID) {
|
|
214
|
-
throw new Error(`[opencode-multiagent] team_create: failed to create session for "${args.name}"`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 2. Register in team manager.
|
|
218
|
-
teamManager.register(args.name, {
|
|
219
|
-
sessionID,
|
|
220
|
-
agent: args.agent,
|
|
221
|
-
role: args.role,
|
|
222
|
-
status: "spawning",
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// 3. Send initial prompt to the new session.
|
|
226
|
-
if (client.session?.prompt) {
|
|
227
|
-
await client.session.prompt({
|
|
228
|
-
path: { id: sessionID },
|
|
229
|
-
body: {
|
|
230
|
-
agent: args.agent,
|
|
231
|
-
parts: [{ type: "text", text: args.prompt }],
|
|
232
|
-
noReply: false,
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
await note("team_create", {
|
|
238
|
-
observation: true,
|
|
239
|
-
leadSessionID: ctx.sessionID,
|
|
240
|
-
memberName: args.name,
|
|
241
|
-
memberSessionID: sessionID,
|
|
242
|
-
agent: args.agent,
|
|
243
|
-
role: args.role,
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
return `Teammate "${args.name}" created (session: ${sessionID}).`;
|
|
247
|
-
},
|
|
248
|
-
}),
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Return the current team composition and member statuses.
|
|
252
|
-
*/
|
|
253
|
-
team_status: tool({
|
|
254
|
-
description:
|
|
255
|
-
"Show the current team status: lead session, member list, and each member's status.",
|
|
256
|
-
args: {},
|
|
257
|
-
async execute(_args, ctx) {
|
|
258
|
-
const status = teamManager.getStatus();
|
|
259
|
-
|
|
260
|
-
await note("team_status", {
|
|
261
|
-
observation: true,
|
|
262
|
-
sessionID: ctx.sessionID,
|
|
263
|
-
memberCount: status.memberCount,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
return JSON.stringify(status, null, 2);
|
|
267
|
-
},
|
|
268
|
-
}),
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Activate a pre-defined team profile from `defaults/team-profiles.json`.
|
|
272
|
-
*
|
|
273
|
-
* Members with `auto_spawn: true` are created immediately with their
|
|
274
|
-
* `initial_prompt`. Members with `auto_spawn: false` are listed in the
|
|
275
|
-
* return value so the lead can spawn them manually with `team_create`.
|
|
276
|
-
*/
|
|
277
|
-
team_spawn_profile: tool({
|
|
278
|
-
description:
|
|
279
|
-
"Activate a pre-defined team profile. " +
|
|
280
|
-
"Members marked auto_spawn:true are created immediately; others are listed for manual spawn.",
|
|
281
|
-
args: {
|
|
282
|
-
profile: tool.schema
|
|
283
|
-
.string()
|
|
284
|
-
.describe(
|
|
285
|
-
"Profile name to activate. Available profiles: standard, quick, review, fullstack.",
|
|
286
|
-
),
|
|
287
|
-
},
|
|
288
|
-
async execute(args, ctx) {
|
|
289
|
-
const profiles = await loadTeamProfiles();
|
|
290
|
-
const profileData = profiles[args.profile];
|
|
291
|
-
if (!profileData) {
|
|
292
|
-
const available = Object.keys(profiles).join(", ") || "(none)";
|
|
293
|
-
return `Profile "${args.profile}" not found. Available: ${available}.`;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
teamManager.setLead(ctx.sessionID);
|
|
297
|
-
|
|
298
|
-
const spawned: string[] = [];
|
|
299
|
-
const pending: string[] = [];
|
|
300
|
-
|
|
301
|
-
for (const member of profileData.members) {
|
|
302
|
-
if (member.auto_spawn && member.initial_prompt) {
|
|
303
|
-
if (!client.session?.create || !client.session?.prompt) {
|
|
304
|
-
pending.push(member.name);
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
try {
|
|
308
|
-
const result = await client.session.create({
|
|
309
|
-
body: { parentID: ctx.sessionID, title: `[team] ${member.name}` },
|
|
310
|
-
});
|
|
311
|
-
const sessionID = result?.data?.id;
|
|
312
|
-
if (!sessionID) {
|
|
313
|
-
pending.push(member.name);
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
teamManager.register(member.name, {
|
|
317
|
-
sessionID,
|
|
318
|
-
agent: member.agent,
|
|
319
|
-
role: member.role,
|
|
320
|
-
status: "spawning",
|
|
321
|
-
});
|
|
322
|
-
await client.session.prompt({
|
|
323
|
-
path: { id: sessionID },
|
|
324
|
-
body: {
|
|
325
|
-
agent: member.agent,
|
|
326
|
-
parts: [{ type: "text", text: member.initial_prompt }],
|
|
327
|
-
noReply: false,
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
spawned.push(member.name);
|
|
331
|
-
} catch {
|
|
332
|
-
pending.push(member.name);
|
|
333
|
-
}
|
|
334
|
-
} else {
|
|
335
|
-
pending.push(member.name);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
await note("team_spawn_profile", {
|
|
340
|
-
observation: true,
|
|
341
|
-
leadSessionID: ctx.sessionID,
|
|
342
|
-
profile: args.profile,
|
|
343
|
-
spawned,
|
|
344
|
-
pending,
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const lines: string[] = [`Profile "${args.profile}" activated.`];
|
|
348
|
-
if (spawned.length > 0) lines.push(`Auto-spawned: ${spawned.join(", ")}.`);
|
|
349
|
-
if (pending.length > 0) {
|
|
350
|
-
lines.push(
|
|
351
|
-
`Pending manual spawn (use team_create): ${pending.join(", ")}.`,
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
return lines.join("\n");
|
|
355
|
-
},
|
|
356
|
-
}),
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
export type TeamTools = ReturnType<typeof createTeamTools>;
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { note } from "./log.ts";
|
|
2
|
-
|
|
3
|
-
const cleanupIntervalMs = 5 * 60 * 1000;
|
|
4
|
-
const staleSessionTtlMs = 30 * 60 * 1000;
|
|
5
|
-
const maxTrackedSessions = 200;
|
|
6
|
-
const evictionFraction = 0.2;
|
|
7
|
-
|
|
8
|
-
type SessionTelemetryState = {
|
|
9
|
-
sessionID: string;
|
|
10
|
-
agent?: string;
|
|
11
|
-
model?: string;
|
|
12
|
-
startedAt: number;
|
|
13
|
-
lastActivityAt: number;
|
|
14
|
-
toolCalls: number;
|
|
15
|
-
toolErrors: number;
|
|
16
|
-
filesEdited: number;
|
|
17
|
-
tasksDispatched: number;
|
|
18
|
-
permissionDenied: number;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const createTelemetryController = ({ flags }: { flags: Record<string, any> }) => {
|
|
22
|
-
const sessions = new Map<string, SessionTelemetryState>();
|
|
23
|
-
|
|
24
|
-
const ensureSession = (sessionID: string | undefined, extra: Partial<SessionTelemetryState> = {}): SessionTelemetryState | null => {
|
|
25
|
-
if (!sessionID) return null;
|
|
26
|
-
const now = Date.now();
|
|
27
|
-
if (!sessions.has(sessionID)) {
|
|
28
|
-
sessions.set(sessionID, {
|
|
29
|
-
sessionID,
|
|
30
|
-
agent: undefined,
|
|
31
|
-
model: undefined,
|
|
32
|
-
startedAt: now,
|
|
33
|
-
lastActivityAt: now,
|
|
34
|
-
toolCalls: 0,
|
|
35
|
-
toolErrors: 0,
|
|
36
|
-
filesEdited: 0,
|
|
37
|
-
tasksDispatched: 0,
|
|
38
|
-
permissionDenied: 0,
|
|
39
|
-
...extra,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
const state = sessions.get(sessionID)!;
|
|
43
|
-
Object.assign(state, extra);
|
|
44
|
-
state.lastActivityAt = now;
|
|
45
|
-
return state;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const cleanupStaleSessions = (now = Date.now()): void => {
|
|
49
|
-
for (const [sessionID, state] of sessions.entries()) {
|
|
50
|
-
if (now - state.lastActivityAt > staleSessionTtlMs) sessions.delete(sessionID);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const enforceSessionLimit = (): void => {
|
|
55
|
-
if (sessions.size <= maxTrackedSessions) return;
|
|
56
|
-
const toRemove = [...sessions.values()]
|
|
57
|
-
.sort((left, right) => left.lastActivityAt - right.lastActivityAt)
|
|
58
|
-
.slice(0, Math.max(1, Math.ceil(sessions.size * evictionFraction)));
|
|
59
|
-
for (const state of toRemove) sessions.delete(state.sessionID);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
let interval: ReturnType<typeof setInterval> | null = null;
|
|
63
|
-
if (flags.telemetry) {
|
|
64
|
-
interval = setInterval(() => cleanupStaleSessions(), cleanupIntervalMs);
|
|
65
|
-
interval.unref?.();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const cleanup = (): void => {
|
|
69
|
-
if (!interval) return;
|
|
70
|
-
clearInterval(interval);
|
|
71
|
-
interval = null;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
cleanup,
|
|
76
|
-
rememberSession(sessionID: string | undefined, extra: Partial<SessionTelemetryState> = {}): void {
|
|
77
|
-
ensureSession(sessionID, extra);
|
|
78
|
-
enforceSessionLimit();
|
|
79
|
-
},
|
|
80
|
-
trackEdit(sessionID: string | undefined): void {
|
|
81
|
-
const state = ensureSession(sessionID);
|
|
82
|
-
if (!state) return;
|
|
83
|
-
state.filesEdited += 1;
|
|
84
|
-
},
|
|
85
|
-
trackToolCall(sessionID: string | undefined, extra: Partial<SessionTelemetryState> = {}): void {
|
|
86
|
-
const state = ensureSession(sessionID, extra);
|
|
87
|
-
if (!state) return;
|
|
88
|
-
state.toolCalls += 1;
|
|
89
|
-
},
|
|
90
|
-
trackToolError(sessionID: string | undefined, extra: Partial<SessionTelemetryState> = {}): void {
|
|
91
|
-
const state = ensureSession(sessionID, extra);
|
|
92
|
-
if (!state) return;
|
|
93
|
-
state.toolErrors += 1;
|
|
94
|
-
},
|
|
95
|
-
trackTaskDispatch(sessionID: string | undefined, extra: Partial<SessionTelemetryState> = {}): void {
|
|
96
|
-
const state = ensureSession(sessionID, extra);
|
|
97
|
-
if (!state) return;
|
|
98
|
-
state.tasksDispatched += 1;
|
|
99
|
-
},
|
|
100
|
-
trackPermissionDenied(sessionID: string | undefined, extra: Partial<SessionTelemetryState> = {}): void {
|
|
101
|
-
const state = ensureSession(sessionID, extra);
|
|
102
|
-
if (!state) return;
|
|
103
|
-
state.permissionDenied += 1;
|
|
104
|
-
},
|
|
105
|
-
async flushSession(sessionID: string, reason = "session_deleted"): Promise<void> {
|
|
106
|
-
const state = sessions.get(sessionID);
|
|
107
|
-
if (!state) return;
|
|
108
|
-
sessions.delete(sessionID);
|
|
109
|
-
await note("session_metrics", {
|
|
110
|
-
observation: true,
|
|
111
|
-
sessionID,
|
|
112
|
-
agent: state.agent,
|
|
113
|
-
model: state.model,
|
|
114
|
-
duration_ms: Math.max(0, Date.now() - state.startedAt),
|
|
115
|
-
tool_calls: state.toolCalls,
|
|
116
|
-
tool_errors: state.toolErrors,
|
|
117
|
-
files_edited: state.filesEdited,
|
|
118
|
-
tasks_dispatched: state.tasksDispatched,
|
|
119
|
-
permission_denied: state.permissionDenied,
|
|
120
|
-
reason,
|
|
121
|
-
});
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
export const compact = <T extends Record<string, unknown>>(value: T): Partial<T> =>
|
|
2
|
-
Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as Partial<T>;
|
|
3
|
-
|
|
4
|
-
export const clip = (value: unknown, size = 240): string | undefined => {
|
|
5
|
-
if (typeof value !== "string") return undefined;
|
|
6
|
-
if (value.length <= size) return value;
|
|
7
|
-
return `${value.slice(0, size)}...`;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type TextPart = { type?: string; text?: string } | null | undefined;
|
|
11
|
-
|
|
12
|
-
export const text = (parts: unknown): string =>
|
|
13
|
-
Array.isArray(parts)
|
|
14
|
-
? (parts as TextPart[])
|
|
15
|
-
.filter((part): part is { type: string; text: string } => Boolean(part) && part?.type === "text" && typeof part?.text === "string")
|
|
16
|
-
.map((part) => part.text)
|
|
17
|
-
.join("\n")
|
|
18
|
-
: "";
|
|
19
|
-
|
|
20
|
-
export const label = (value: unknown, keys = ["name", "id", "modelID", "providerID"]): string | undefined => {
|
|
21
|
-
if (typeof value === "string") return value;
|
|
22
|
-
if (!value || typeof value !== "object") return undefined;
|
|
23
|
-
const objectValue = value as Record<string, unknown>;
|
|
24
|
-
for (const key of keys) {
|
|
25
|
-
if (typeof objectValue[key] === "string") return objectValue[key] as string;
|
|
26
|
-
}
|
|
27
|
-
return undefined;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const own = (value: unknown, key: string): boolean =>
|
|
31
|
-
Boolean(value) && Object.prototype.hasOwnProperty.call(value, key);
|
|
32
|
-
|
|
33
|
-
export const clone = <T>(value: T): T => {
|
|
34
|
-
if (value === undefined) return value;
|
|
35
|
-
return JSON.parse(JSON.stringify(value)) as T;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const merge = <T>(base: T, extra: unknown): T => {
|
|
39
|
-
if (!extra || typeof extra !== "object" || Array.isArray(extra)) {
|
|
40
|
-
return clone((extra !== undefined ? extra : base) as T);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const result: Record<string, unknown> = { ...((base && typeof base === "object" && !Array.isArray(base) ? base : {}) as Record<string, unknown>) };
|
|
44
|
-
|
|
45
|
-
for (const [key, value] of Object.entries(extra as Record<string, unknown>)) {
|
|
46
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
47
|
-
result[key] = value;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
result[key] = merge(result[key], value);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return result as T;
|
|
54
|
-
};
|