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.
Files changed (160) hide show
  1. package/AGENTS.md +83 -0
  2. package/CHANGELOG.md +31 -0
  3. package/CONTRIBUTING.md +36 -0
  4. package/README.md +44 -168
  5. package/README.tr.md +84 -0
  6. package/RELEASE.md +68 -0
  7. package/agents/AGENTS.md +91 -0
  8. package/agents/auditor.md +67 -23
  9. package/agents/{worker.md → coder.md} +24 -17
  10. package/agents/docmaster.md +91 -0
  11. package/agents/executor.md +63 -79
  12. package/agents/planner.md +78 -58
  13. package/agents/reviewer.md +31 -15
  14. package/agents/scout.md +25 -17
  15. package/agents/sec-coder.md +83 -0
  16. package/agents/ui-coder.md +77 -0
  17. package/commands/board.md +17 -0
  18. package/commands/execute.md +9 -7
  19. package/commands/init-deep.md +7 -6
  20. package/commands/init.md +5 -5
  21. package/commands/inspect.md +6 -5
  22. package/commands/plan.md +8 -6
  23. package/commands/quality.md +4 -3
  24. package/commands/review.md +5 -3
  25. package/commands/status.md +5 -3
  26. package/defaults/AGENTS.md +48 -0
  27. package/defaults/opencode-multiagent.json +180 -0
  28. package/defaults/opencode-multiagent.schema.json +265 -0
  29. package/dist/control-plane.d.ts +4 -0
  30. package/dist/control-plane.d.ts.map +1 -0
  31. package/dist/index.d.ts +5 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +1916 -0
  34. package/dist/opencode-multiagent/compiler.d.ts +25 -0
  35. package/dist/opencode-multiagent/compiler.d.ts.map +1 -0
  36. package/dist/opencode-multiagent/constants.d.ts +128 -0
  37. package/dist/opencode-multiagent/constants.d.ts.map +1 -0
  38. package/dist/opencode-multiagent/correlation.d.ts +21 -0
  39. package/dist/opencode-multiagent/correlation.d.ts.map +1 -0
  40. package/dist/opencode-multiagent/defaults.d.ts +10 -0
  41. package/dist/opencode-multiagent/defaults.d.ts.map +1 -0
  42. package/dist/opencode-multiagent/hooks.d.ts +62 -0
  43. package/dist/opencode-multiagent/hooks.d.ts.map +1 -0
  44. package/dist/opencode-multiagent/log.d.ts +2 -0
  45. package/dist/opencode-multiagent/log.d.ts.map +1 -0
  46. package/dist/opencode-multiagent/markdown.d.ts +8 -0
  47. package/dist/opencode-multiagent/markdown.d.ts.map +1 -0
  48. package/dist/opencode-multiagent/mcp.d.ts +3 -0
  49. package/dist/opencode-multiagent/mcp.d.ts.map +1 -0
  50. package/dist/opencode-multiagent/policy.d.ts +5 -0
  51. package/dist/opencode-multiagent/policy.d.ts.map +1 -0
  52. package/dist/opencode-multiagent/quality.d.ts +18 -0
  53. package/dist/opencode-multiagent/quality.d.ts.map +1 -0
  54. package/dist/opencode-multiagent/runtime.d.ts +7 -0
  55. package/dist/opencode-multiagent/runtime.d.ts.map +1 -0
  56. package/dist/opencode-multiagent/session-tracker.d.ts +32 -0
  57. package/dist/opencode-multiagent/session-tracker.d.ts.map +1 -0
  58. package/dist/opencode-multiagent/skills.d.ts +17 -0
  59. package/dist/opencode-multiagent/skills.d.ts.map +1 -0
  60. package/dist/opencode-multiagent/supervision.d.ts +26 -0
  61. package/dist/opencode-multiagent/supervision.d.ts.map +1 -0
  62. package/dist/opencode-multiagent/task-manager.d.ts +54 -0
  63. package/dist/opencode-multiagent/task-manager.d.ts.map +1 -0
  64. package/dist/opencode-multiagent/telemetry.d.ts +28 -0
  65. package/dist/opencode-multiagent/telemetry.d.ts.map +1 -0
  66. package/dist/opencode-multiagent/tools.d.ts +87 -0
  67. package/dist/opencode-multiagent/tools.d.ts.map +1 -0
  68. package/dist/opencode-multiagent/types.d.ts +36 -0
  69. package/dist/opencode-multiagent/types.d.ts.map +1 -0
  70. package/dist/opencode-multiagent/utils.d.ts +9 -0
  71. package/dist/opencode-multiagent/utils.d.ts.map +1 -0
  72. package/docs/agents.md +148 -0
  73. package/docs/agents.tr.md +149 -0
  74. package/docs/configuration.md +244 -0
  75. package/docs/configuration.tr.md +244 -0
  76. package/docs/usage-guide.md +224 -0
  77. package/docs/usage-guide.tr.md +225 -0
  78. package/examples/opencode.with-overrides.json +3 -7
  79. package/package.json +23 -13
  80. package/skills/AGENTS.md +51 -0
  81. package/skills/advanced-evaluation/SKILL.md +37 -21
  82. package/skills/advanced-evaluation/manifest.json +2 -13
  83. package/skills/cek-context-engineering/SKILL.md +159 -87
  84. package/skills/cek-context-engineering/manifest.json +1 -3
  85. package/skills/cek-prompt-engineering/SKILL.md +13 -10
  86. package/skills/cek-prompt-engineering/manifest.json +1 -3
  87. package/skills/cek-test-prompt/SKILL.md +38 -28
  88. package/skills/cek-test-prompt/manifest.json +1 -3
  89. package/skills/cek-thought-based-reasoning/SKILL.md +75 -21
  90. package/skills/cek-thought-based-reasoning/manifest.json +1 -3
  91. package/skills/context-degradation/SKILL.md +14 -13
  92. package/skills/context-degradation/manifest.json +1 -3
  93. package/skills/debate/SKILL.md +23 -78
  94. package/skills/debate/manifest.json +2 -12
  95. package/skills/design-first/manifest.json +2 -13
  96. package/skills/dispatching-parallel-agents/SKILL.md +14 -3
  97. package/skills/dispatching-parallel-agents/manifest.json +1 -4
  98. package/skills/drift-analysis/SKILL.md +50 -29
  99. package/skills/drift-analysis/manifest.json +2 -12
  100. package/skills/evaluation/manifest.json +2 -12
  101. package/skills/executing-plans/SKILL.md +15 -8
  102. package/skills/executing-plans/manifest.json +1 -3
  103. package/skills/handoff-protocols/manifest.json +2 -12
  104. package/skills/parallel-investigation/SKILL.md +25 -12
  105. package/skills/parallel-investigation/manifest.json +1 -4
  106. package/skills/reflexion-critique/SKILL.md +21 -10
  107. package/skills/reflexion-critique/manifest.json +1 -3
  108. package/skills/reflexion-reflect/SKILL.md +36 -34
  109. package/skills/reflexion-reflect/manifest.json +2 -10
  110. package/skills/root-cause-analysis/manifest.json +2 -13
  111. package/skills/sadd-judge-with-debate/SKILL.md +50 -26
  112. package/skills/sadd-judge-with-debate/manifest.json +1 -3
  113. package/skills/structured-code-review/manifest.json +2 -11
  114. package/skills/task-decomposition/manifest.json +2 -13
  115. package/skills/verification-before-completion/manifest.json +2 -15
  116. package/skills/verification-gates/SKILL.md +27 -19
  117. package/skills/verification-gates/manifest.json +2 -12
  118. package/agents/advisor.md +0 -57
  119. package/agents/critic.md +0 -127
  120. package/agents/deep-worker.md +0 -65
  121. package/agents/devil.md +0 -36
  122. package/agents/heavy-worker.md +0 -68
  123. package/agents/lead.md +0 -155
  124. package/agents/librarian.md +0 -62
  125. package/agents/qa.md +0 -50
  126. package/agents/quick.md +0 -65
  127. package/agents/scribe.md +0 -78
  128. package/agents/strategist.md +0 -63
  129. package/agents/ui-heavy-worker.md +0 -62
  130. package/agents/ui-worker.md +0 -69
  131. package/agents/validator.md +0 -47
  132. package/defaults/agent-settings.json +0 -102
  133. package/defaults/agent-settings.schema.json +0 -25
  134. package/defaults/flags.json +0 -35
  135. package/defaults/flags.schema.json +0 -119
  136. package/defaults/mcp-defaults.json +0 -47
  137. package/defaults/mcp-defaults.schema.json +0 -38
  138. package/defaults/profiles.json +0 -53
  139. package/defaults/profiles.schema.json +0 -60
  140. package/defaults/team-profiles.json +0 -83
  141. package/src/control-plane.ts +0 -21
  142. package/src/index.ts +0 -8
  143. package/src/opencode-multiagent/compiler.ts +0 -168
  144. package/src/opencode-multiagent/constants.ts +0 -178
  145. package/src/opencode-multiagent/file-lock.ts +0 -90
  146. package/src/opencode-multiagent/hooks.ts +0 -599
  147. package/src/opencode-multiagent/log.ts +0 -12
  148. package/src/opencode-multiagent/mailbox.ts +0 -287
  149. package/src/opencode-multiagent/markdown.ts +0 -99
  150. package/src/opencode-multiagent/mcp.ts +0 -35
  151. package/src/opencode-multiagent/policy.ts +0 -67
  152. package/src/opencode-multiagent/quality.ts +0 -140
  153. package/src/opencode-multiagent/runtime.ts +0 -55
  154. package/src/opencode-multiagent/skills.ts +0 -144
  155. package/src/opencode-multiagent/supervision.ts +0 -156
  156. package/src/opencode-multiagent/task-manager.ts +0 -148
  157. package/src/opencode-multiagent/team-manager.ts +0 -219
  158. package/src/opencode-multiagent/team-tools.ts +0 -359
  159. package/src/opencode-multiagent/telemetry.ts +0 -124
  160. 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
- };