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
package/dist/index.js ADDED
@@ -0,0 +1,1916 @@
1
+ // @bun
2
+ // src/opencode-multiagent/hooks.ts
3
+ import { readdir as readdir2, readFile as readFile5 } from "fs/promises";
4
+ import { join as join4 } from "path";
5
+
6
+ // src/opencode-multiagent/constants.ts
7
+ import { existsSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { dirname, join, resolve } from "path";
10
+ import { fileURLToPath } from "url";
11
+ var hasBundledAssets = (directory) => {
12
+ return ["package.json", "agents", "commands", "defaults", "skills"].every((entry) => {
13
+ return existsSync(join(directory, entry));
14
+ });
15
+ };
16
+ var detectPackageRoot = () => {
17
+ let current = dirname(fileURLToPath(import.meta.url));
18
+ while (true) {
19
+ if (hasBundledAssets(current)) {
20
+ return current;
21
+ }
22
+ const parent = resolve(current, "..");
23
+ if (parent === current) {
24
+ return resolve(dirname(fileURLToPath(import.meta.url)), "../..");
25
+ }
26
+ current = parent;
27
+ }
28
+ };
29
+ var pluginName = "opencode-multiagent";
30
+ var pluginMode = "stable";
31
+ var opencodeDir = join(homedir(), ".config", "opencode");
32
+ var pluginDir = join(opencodeDir, "plugins");
33
+ var globalAgentsDir = join(opencodeDir, "agents");
34
+ var globalCommandsDir = join(opencodeDir, "commands");
35
+ var logDirPath = join(opencodeDir, "logs");
36
+ var logFilePath = join(logDirPath, `${pluginName}.jsonl`);
37
+ var settingsPath = join(pluginDir, `${pluginName}.json`);
38
+ var packageRoot = detectPackageRoot();
39
+ var bundledDefaultsPath = join(packageRoot, "defaults", "opencode-multiagent.json");
40
+ var bundledAgentsDir = join(packageRoot, "agents");
41
+ var bundledCommandsDir = join(packageRoot, "commands");
42
+ var bundledSkillsDir = join(packageRoot, "skills");
43
+ var trackedEventTypes = new Set([
44
+ "permission.updated",
45
+ "permission.replied",
46
+ "session.created",
47
+ "session.status",
48
+ "session.diff",
49
+ "file.edited",
50
+ "session.deleted",
51
+ "session.idle",
52
+ "message.updated",
53
+ "command.executed"
54
+ ]);
55
+ var supervisionEventTypes = new Set([
56
+ "session.created",
57
+ "message.updated",
58
+ "session.idle",
59
+ "session.deleted"
60
+ ]);
61
+ var qualityEventTypes = new Set(["file.edited", "session.idle", "session.deleted"]);
62
+ var suspiciousTerms = ["blocked", "failed", "error", "permission", "refused"];
63
+ var qualitySignalRegex = /\b(test|tests|lint|typecheck|tsc|build|pytest|vitest|jest|cargo test|go test|ruff|mypy|eslint|biome|prettier)\b/i;
64
+ var mcpToolPrefixes = [
65
+ "exa_",
66
+ "context7_",
67
+ "gh_grep_",
68
+ "github_",
69
+ "code_index_",
70
+ "repo_git_"
71
+ ];
72
+ var defaultProfiles = {
73
+ minimal: {
74
+ enforcement: true,
75
+ observation: false,
76
+ prompt_controls: false,
77
+ agent_compilation: true,
78
+ command_compilation: true,
79
+ mcp_compilation: true,
80
+ telemetry: false,
81
+ supervision: false,
82
+ quality_gate: false,
83
+ task_lifecycle: false,
84
+ quality_gate_enforcement: false,
85
+ concurrency_limit: 0,
86
+ experimental: {
87
+ chat_system_transform: false,
88
+ chat_messages_transform: false,
89
+ session_compacting: false,
90
+ text_complete: false
91
+ }
92
+ },
93
+ standard: {
94
+ enforcement: true,
95
+ observation: true,
96
+ prompt_controls: true,
97
+ agent_compilation: true,
98
+ command_compilation: true,
99
+ mcp_compilation: true,
100
+ telemetry: true,
101
+ supervision: true,
102
+ quality_gate: true,
103
+ task_lifecycle: true,
104
+ quality_gate_enforcement: false,
105
+ concurrency_limit: 5,
106
+ skill_sources: [
107
+ bundledSkillsDir,
108
+ join(homedir(), ".agents", "skills"),
109
+ join(homedir(), "skills")
110
+ ],
111
+ skill_injection: false,
112
+ experimental: {
113
+ chat_system_transform: false,
114
+ chat_messages_transform: false,
115
+ session_compacting: false,
116
+ text_complete: false
117
+ }
118
+ },
119
+ strict: {
120
+ enforcement: true,
121
+ observation: true,
122
+ prompt_controls: true,
123
+ agent_compilation: true,
124
+ command_compilation: true,
125
+ mcp_compilation: true,
126
+ telemetry: true,
127
+ supervision: true,
128
+ quality_gate: true,
129
+ task_lifecycle: true,
130
+ quality_gate_enforcement: true,
131
+ concurrency_limit: 5,
132
+ experimental: {
133
+ chat_system_transform: true,
134
+ chat_messages_transform: true,
135
+ session_compacting: true,
136
+ text_complete: true
137
+ }
138
+ }
139
+ };
140
+ var defaultFlags = {
141
+ profile: "standard",
142
+ enforcement: true,
143
+ observation: true,
144
+ prompt_controls: true,
145
+ agent_compilation: true,
146
+ command_compilation: true,
147
+ mcp_compilation: true,
148
+ telemetry: true,
149
+ supervision: true,
150
+ quality_gate: true,
151
+ task_lifecycle: true,
152
+ quality_gate_enforcement: false,
153
+ concurrency_limit: 5,
154
+ skill_sources: [
155
+ bundledSkillsDir,
156
+ join(homedir(), ".agents", "skills"),
157
+ join(homedir(), "skills")
158
+ ],
159
+ skill_injection: false,
160
+ compiler: {
161
+ permission_compilation: true
162
+ },
163
+ experimental: {
164
+ chat_system_transform: false,
165
+ chat_messages_transform: false,
166
+ session_compacting: false,
167
+ text_complete: false
168
+ },
169
+ supervision_config: {
170
+ idle_timeout_ms: 180000,
171
+ cooldown_ms: 300000
172
+ },
173
+ quality_config: {
174
+ reminder_idle_ms: 120000,
175
+ reminder_cooldown_ms: 300000
176
+ }
177
+ };
178
+ var disabledNativeAgents = ["build", "plan", "general", "explore"];
179
+ var blockedPathPrefixRegex = /^(\/etc|\/root|\/boot|\/proc|\/sys|\/dev)(?:\/|$)/;
180
+ var blockedPathFragments = [
181
+ "/.ssh/",
182
+ "/credentials",
183
+ "/secrets",
184
+ "/.aws/",
185
+ "/.gcloud/",
186
+ "/.kube/"
187
+ ];
188
+ var blockedPathSuffixes = ["/.ssh", "/credentials"];
189
+ var destructiveBashFragments = [
190
+ "git reset --" + "hard",
191
+ "git che" + "ckout --",
192
+ "r" + "m -rf /",
193
+ "r" + "m -rf ~",
194
+ "su" + "do ",
195
+ "shut" + "down",
196
+ "re" + "boot",
197
+ "mk" + "fs",
198
+ "dd i" + "f=",
199
+ "cu" + "rl | ba" + "sh",
200
+ "cu" + "rl | sh",
201
+ "wg" + "et | ba" + "sh",
202
+ "wg" + "et | sh",
203
+ "ev" + "al $(",
204
+ "> /de" + "v/",
205
+ ":" + "(){",
206
+ "ch" + "mod 777",
207
+ "ki" + "ll -9 -1",
208
+ "no" + "hup "
209
+ ];
210
+ var sensitiveMentions = [
211
+ "/etc",
212
+ "/root",
213
+ "/boot",
214
+ "/proc",
215
+ "/sys",
216
+ "/dev",
217
+ "/.ssh",
218
+ "~/.ssh"
219
+ ];
220
+ var experimentalText = "[opencode-multiagent experimental] Experimental control-plane transforms are active for this session.";
221
+
222
+ // src/opencode-multiagent/log.ts
223
+ import { appendFile, mkdir } from "fs/promises";
224
+ var _initPromise = null;
225
+ var ensureLogDir = () => {
226
+ if (_initPromise === null) {
227
+ _initPromise = mkdir(logDirPath, { recursive: true }).then(() => {
228
+ return;
229
+ });
230
+ }
231
+ return _initPromise;
232
+ };
233
+ var note = async (kind, payload) => {
234
+ try {
235
+ await ensureLogDir();
236
+ await appendFile(logFilePath, `${JSON.stringify({ ts: Date.now(), kind, ...payload })}
237
+ `);
238
+ } catch {}
239
+ };
240
+
241
+ // src/opencode-multiagent/markdown.ts
242
+ import { readFile, readdir } from "fs/promises";
243
+ import { join as join2, resolve as resolve2 } from "path";
244
+ var unquote = (value) => {
245
+ if (typeof value !== "string" || value.length < 2)
246
+ return value;
247
+ const first = value[0];
248
+ const last = value[value.length - 1];
249
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
250
+ return value.slice(1, -1);
251
+ }
252
+ return value;
253
+ };
254
+ var parseScalar = (value) => {
255
+ const trimmed = value.trim();
256
+ if (trimmed === "")
257
+ return "";
258
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
259
+ return unquote(trimmed);
260
+ }
261
+ if (trimmed === "true")
262
+ return true;
263
+ if (trimmed === "false")
264
+ return false;
265
+ if (/^-?\d+(?:\.\d+)?$/.test(trimmed))
266
+ return Number(trimmed);
267
+ return trimmed;
268
+ };
269
+ function parseFrontmatter(value) {
270
+ if (typeof value !== "string" || !value.startsWith("---")) {
271
+ return { data: {}, body: value };
272
+ }
273
+ const openingMatch = value.match(/^---\r?\n/);
274
+ if (!openingMatch)
275
+ return { data: {}, body: value };
276
+ const rest = value.slice(openingMatch[0].length);
277
+ const closingIndex = rest.search(/\r?\n---(?:\r?\n|$)/);
278
+ if (closingIndex < 0)
279
+ return { data: {}, body: value };
280
+ const rawFrontmatter = rest.slice(0, closingIndex);
281
+ const closingSlice = rest.slice(closingIndex);
282
+ const closingMatch = closingSlice.match(/^\r?\n---(?:\r?\n|$)/);
283
+ if (!closingMatch)
284
+ return { data: {}, body: value };
285
+ const body = closingSlice.slice(closingMatch[0].length);
286
+ const data = {};
287
+ const stack = [{ indent: -1, obj: data }];
288
+ for (const line of rawFrontmatter.split(/\r?\n/)) {
289
+ if (!line.trim())
290
+ continue;
291
+ const indent = line.match(/^ */)?.[0].length ?? 0;
292
+ const trimmed = line.trim();
293
+ const colonIndex = trimmed.indexOf(":");
294
+ if (colonIndex < 0)
295
+ continue;
296
+ const key = String(unquote(trimmed.slice(0, colonIndex).trim()));
297
+ const rawValue = trimmed.slice(colonIndex + 1).trim();
298
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
299
+ stack.pop();
300
+ }
301
+ const parent = stack[stack.length - 1].obj;
302
+ if (rawValue === "") {
303
+ parent[key] = {};
304
+ stack.push({ indent, obj: parent[key] });
305
+ continue;
306
+ }
307
+ parent[key] = parseScalar(rawValue);
308
+ }
309
+ return { data, body };
310
+ }
311
+ var dedupe = (values) => [
312
+ ...new Set(values.filter(Boolean).map((value) => resolve2(value)))
313
+ ];
314
+ async function loadMarkdownDefs(dirs) {
315
+ const defs = new Map;
316
+ for (const dir of dedupe(dirs)) {
317
+ const files = await readdir(dir).catch(() => []);
318
+ for (const file of files.filter((name) => name.endsWith(".md")).sort()) {
319
+ try {
320
+ const value = await readFile(join2(dir, file), "utf8");
321
+ const { data, body } = parseFrontmatter(value);
322
+ defs.set(file.slice(0, -3), { data, body, source: join2(dir, file) });
323
+ } catch {}
324
+ }
325
+ }
326
+ return defs;
327
+ }
328
+
329
+ // src/opencode-multiagent/utils.ts
330
+ import { readFile as readFile2 } from "fs/promises";
331
+ var compact = (value) => Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
332
+ var clip = (value, size = 240) => {
333
+ if (typeof value !== "string")
334
+ return;
335
+ if (value.length <= size)
336
+ return value;
337
+ return `${value.slice(0, size)}...`;
338
+ };
339
+ var text = (parts) => Array.isArray(parts) ? parts.filter((part) => Boolean(part) && part?.type === "text" && typeof part?.text === "string").map((part) => part.text).join(`
340
+ `) : "";
341
+ var label = (value, keys = ["name", "id", "modelID", "providerID"]) => {
342
+ if (typeof value === "string")
343
+ return value;
344
+ if (!value || typeof value !== "object")
345
+ return;
346
+ const objectValue = value;
347
+ for (const key of keys) {
348
+ if (typeof objectValue[key] === "string")
349
+ return objectValue[key];
350
+ }
351
+ return;
352
+ };
353
+ var own = (value, key) => Boolean(value) && Object.prototype.hasOwnProperty.call(value, key);
354
+ var clone = (value) => {
355
+ if (value === undefined)
356
+ return value;
357
+ return JSON.parse(JSON.stringify(value));
358
+ };
359
+ var readJSON = async (path, fallback) => {
360
+ try {
361
+ return JSON.parse(await readFile2(path, "utf8"));
362
+ } catch {
363
+ return clone(fallback);
364
+ }
365
+ };
366
+ var merge = (base, extra) => {
367
+ if (!extra || typeof extra !== "object" || Array.isArray(extra)) {
368
+ return clone(extra !== undefined ? extra : base);
369
+ }
370
+ const result = {
371
+ ...base && typeof base === "object" && !Array.isArray(base) ? base : {}
372
+ };
373
+ for (const [key, value] of Object.entries(extra)) {
374
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
375
+ result[key] = value;
376
+ continue;
377
+ }
378
+ result[key] = merge(result[key], value);
379
+ }
380
+ return result;
381
+ };
382
+
383
+ // src/opencode-multiagent/compiler.ts
384
+ var normalizeDefinitionData = (data) => {
385
+ const result = clone(data) ?? {};
386
+ delete result.variant;
387
+ return result;
388
+ };
389
+ var isMcpPermissionKey = (key) => mcpToolPrefixes.some((prefix) => key === prefix || key.startsWith(prefix));
390
+ var buildTaskRoutingAllowSet = (permission = {}) => {
391
+ const allowed = new Set;
392
+ const taskPerm = permission.task;
393
+ if (taskPerm === "allow" || taskPerm === true)
394
+ return new Set(["*"]);
395
+ if (taskPerm === "deny" || taskPerm === false || !taskPerm || typeof taskPerm !== "object")
396
+ return allowed;
397
+ const taskObj = taskPerm;
398
+ for (const [key, value] of Object.entries(taskObj)) {
399
+ if (key === "*")
400
+ continue;
401
+ if (value === "allow" || value === true)
402
+ allowed.add(key);
403
+ }
404
+ return allowed;
405
+ };
406
+ var buildMcpPermissionRegistry = (permission = {}) => {
407
+ const allowed = [];
408
+ const denied = [];
409
+ for (const [key, value] of Object.entries(permission)) {
410
+ if (!isMcpPermissionKey(key))
411
+ continue;
412
+ if (value === "allow")
413
+ allowed.push(key);
414
+ if (value === "deny")
415
+ denied.push(key);
416
+ }
417
+ return { allowed, denied, fallback: permission["*"] === "allow" ? "allow" : "deny" };
418
+ };
419
+ async function compileAgents(cfg, dirs, agentSettings = {}) {
420
+ const defs = await loadMarkdownDefs(dirs);
421
+ if (!cfg.agent || typeof cfg.agent !== "object")
422
+ cfg.agent = {};
423
+ const mcpRegistry = new Map;
424
+ const taskRouting = new Map;
425
+ for (const [name, raw] of defs.entries()) {
426
+ const data = normalizeDefinitionData(raw.data);
427
+ if (!own(cfg.agent, name)) {
428
+ cfg.agent[name] = {};
429
+ } else if (!cfg.agent[name] || typeof cfg.agent[name] !== "object") {
430
+ continue;
431
+ }
432
+ const target = cfg.agent[name];
433
+ const explicitFields = new Set(Object.keys(target));
434
+ for (const [field, value] of Object.entries(data)) {
435
+ if (own(target, field))
436
+ continue;
437
+ target[field] = clone(value);
438
+ }
439
+ const overrides = agentSettings?.[name];
440
+ if (overrides && typeof overrides === "object") {
441
+ for (const [field, value] of Object.entries(overrides)) {
442
+ if (["model", "temperature", "steps"].includes(field) && !explicitFields.has(field)) {
443
+ target[field] = clone(value);
444
+ }
445
+ }
446
+ }
447
+ if (!own(target, "prompt") && typeof raw.body === "string") {
448
+ target.prompt = raw.body.trim();
449
+ }
450
+ const perm = target.permission ?? {};
451
+ mcpRegistry.set(name, buildMcpPermissionRegistry(perm));
452
+ taskRouting.set(name, buildTaskRoutingAllowSet(perm));
453
+ }
454
+ return { mcpRegistry, taskRouting };
455
+ }
456
+ var isTaskRoutingAllowed = (agent, target, routing) => {
457
+ const allowed = routing.get(agent);
458
+ if (!allowed)
459
+ return true;
460
+ if (allowed.has("*"))
461
+ return true;
462
+ return allowed.has(target);
463
+ };
464
+ async function compileCommands(cfg, dirs) {
465
+ const defs = await loadMarkdownDefs(dirs);
466
+ if (!cfg.command || typeof cfg.command !== "object")
467
+ cfg.command = {};
468
+ for (const [name, raw] of defs.entries()) {
469
+ const data = normalizeDefinitionData(raw.data);
470
+ if (!own(cfg.command, name)) {
471
+ cfg.command[name] = {};
472
+ } else if (!cfg.command[name] || typeof cfg.command[name] !== "object") {
473
+ continue;
474
+ }
475
+ const target = cfg.command[name];
476
+ for (const [field, value] of Object.entries(data)) {
477
+ if (own(target, field))
478
+ continue;
479
+ target[field] = clone(value);
480
+ }
481
+ if (!own(target, "template") && typeof raw.body === "string") {
482
+ target.template = raw.body.trim();
483
+ }
484
+ }
485
+ }
486
+ var applyBuiltInAgentPolicy = async (cfg) => {
487
+ if (!cfg.agent || typeof cfg.agent !== "object")
488
+ cfg.agent = {};
489
+ for (const name of disabledNativeAgents) {
490
+ cfg.agent[name] = merge(cfg.agent[name], { disable: true });
491
+ }
492
+ const current = typeof cfg.default_agent === "string" ? cfg.default_agent : undefined;
493
+ const currentAgent = current ? cfg.agent?.[current] : undefined;
494
+ const preferred = cfg.agent.planner && cfg.agent.planner.disable !== true ? "planner" : cfg.agent.executor && cfg.agent.executor.disable !== true ? "executor" : undefined;
495
+ const invalidCurrent = !current || disabledNativeAgents.includes(current) || !currentAgent || currentAgent.disable === true || currentAgent.hidden === true || currentAgent.mode === "subagent" || currentAgent.mode === "sub" || preferred === "planner" && current !== "planner";
496
+ if (invalidCurrent && preferred) {
497
+ const previous = current;
498
+ cfg.default_agent = preferred;
499
+ await note("config_warning", compact({
500
+ observation: true,
501
+ warning: "default_agent_reset",
502
+ previous,
503
+ next: preferred
504
+ }));
505
+ }
506
+ };
507
+ var applyPermissionDefaults = (cfg) => {
508
+ if (!cfg.permission || typeof cfg.permission !== "object")
509
+ cfg.permission = {};
510
+ for (const [key, value] of Object.entries({
511
+ bash: "allow",
512
+ read: "allow",
513
+ edit: "allow",
514
+ glob: "allow",
515
+ grep: "allow",
516
+ list: "allow"
517
+ })) {
518
+ if (!own(cfg.permission, key)) {
519
+ cfg.permission[key] = value;
520
+ }
521
+ }
522
+ };
523
+ var matchesMcpPermission = (tool, registry) => {
524
+ if (!registry)
525
+ return true;
526
+ for (const pattern of registry.denied ?? []) {
527
+ if (pattern.endsWith("*") && tool.startsWith(pattern.slice(0, -1)))
528
+ return false;
529
+ if (pattern === tool)
530
+ return false;
531
+ }
532
+ for (const pattern of registry.allowed ?? []) {
533
+ if (pattern.endsWith("*") && tool.startsWith(pattern.slice(0, -1)))
534
+ return true;
535
+ if (pattern === tool)
536
+ return true;
537
+ }
538
+ return registry.fallback === "allow";
539
+ };
540
+
541
+ // src/opencode-multiagent/correlation.ts
542
+ var INTENT_TTL_MS = 60000;
543
+ var createCorrelationController = () => {
544
+ const intentQueues = new Map;
545
+ const links = new Map;
546
+ const taskIndex = new Map;
547
+ const recordIntent = (parentSessionID, taskID, targetAgent) => {
548
+ if (!intentQueues.has(parentSessionID)) {
549
+ intentQueues.set(parentSessionID, []);
550
+ }
551
+ intentQueues.get(parentSessionID).push({
552
+ taskID,
553
+ parentSessionID,
554
+ targetAgent,
555
+ createdAt: Date.now()
556
+ });
557
+ };
558
+ const tryCorrelate = (parentSessionID, childSessionID, agentHint) => {
559
+ const queue = intentQueues.get(parentSessionID);
560
+ if (!queue || queue.length === 0)
561
+ return;
562
+ const now = Date.now();
563
+ const fresh = queue.filter((i) => now - i.createdAt < INTENT_TTL_MS);
564
+ if (fresh.length === 0) {
565
+ intentQueues.delete(parentSessionID);
566
+ return;
567
+ }
568
+ intentQueues.set(parentSessionID, fresh);
569
+ let matchIdx = -1;
570
+ if (agentHint) {
571
+ matchIdx = fresh.findIndex((i) => i.targetAgent === agentHint);
572
+ }
573
+ if (matchIdx < 0) {
574
+ matchIdx = 0;
575
+ }
576
+ const intent = fresh.splice(matchIdx, 1)[0];
577
+ if (fresh.length === 0)
578
+ intentQueues.delete(parentSessionID);
579
+ const link = {
580
+ taskID: intent.taskID,
581
+ childSessionID,
582
+ parentSessionID,
583
+ linkedAt: now
584
+ };
585
+ links.set(childSessionID, link);
586
+ taskIndex.set(intent.taskID, childSessionID);
587
+ return link;
588
+ };
589
+ const getLink = (childSessionID) => {
590
+ return links.get(childSessionID);
591
+ };
592
+ const getLinkByTask = (taskID) => {
593
+ const childID = taskIndex.get(taskID);
594
+ if (!childID)
595
+ return;
596
+ return links.get(childID);
597
+ };
598
+ const removeLink = (childSessionID) => {
599
+ const link = links.get(childSessionID);
600
+ if (!link)
601
+ return;
602
+ links.delete(childSessionID);
603
+ taskIndex.delete(link.taskID);
604
+ return link;
605
+ };
606
+ return { recordIntent, tryCorrelate, getLink, getLinkByTask, removeLink };
607
+ };
608
+
609
+ // src/opencode-multiagent/defaults.ts
610
+ import { readFile as readFile3 } from "fs/promises";
611
+ var asRecord = (value) => {
612
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
613
+ return {};
614
+ }
615
+ return value;
616
+ };
617
+ var cachedDefaults;
618
+ var loadBundledDefaults = async () => {
619
+ if (cachedDefaults) {
620
+ return clone(cachedDefaults);
621
+ }
622
+ const parsed = JSON.parse(await readFile3(bundledDefaultsPath, "utf8"));
623
+ cachedDefaults = asRecord(parsed);
624
+ return clone(cachedDefaults);
625
+ };
626
+ var readBundledDefaultsSection = (defaults, section, fallback) => {
627
+ const value = defaults[section];
628
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
629
+ return clone(fallback);
630
+ }
631
+ return clone(value);
632
+ };
633
+
634
+ // src/opencode-multiagent/mcp.ts
635
+ var cachedDefaults2;
636
+ async function loadMcpDefaults() {
637
+ if (cachedDefaults2)
638
+ return clone(cachedDefaults2);
639
+ try {
640
+ const defaults = await loadBundledDefaults();
641
+ cachedDefaults2 = readBundledDefaultsSection(defaults, "mcpDefaults", {});
642
+ } catch {
643
+ cachedDefaults2 = {};
644
+ await note("config_warning", {
645
+ observation: true,
646
+ warning: "mcp_defaults_unreadable",
647
+ path: bundledDefaultsPath
648
+ });
649
+ }
650
+ return clone(cachedDefaults2);
651
+ }
652
+ function applyMcpDefaults(cfg, defaults) {
653
+ if (!cfg.mcp || typeof cfg.mcp !== "object")
654
+ cfg.mcp = {};
655
+ const mcp = cfg.mcp;
656
+ for (const [name, definition] of Object.entries(defaults ?? {})) {
657
+ if (own(mcp, name))
658
+ continue;
659
+ mcp[name] = clone(definition);
660
+ }
661
+ }
662
+
663
+ // src/opencode-multiagent/policy.ts
664
+ var normalize = (value) => String(value ?? "").toLowerCase();
665
+ var hasBlockedEnvName = (value) => {
666
+ const name = value.split("/").pop() ?? "";
667
+ if (name === ".env.example")
668
+ return false;
669
+ return name.endsWith(".env") || name.includes(".env.");
670
+ };
671
+ var blocked = (value) => {
672
+ const normalized = normalize(value);
673
+ return blockedPathPrefixRegex.test(normalized) || blockedPathFragments.some((fragment) => normalized.includes(fragment)) || blockedPathSuffixes.some((suffix) => normalized.endsWith(suffix)) || hasBlockedEnvName(normalized);
674
+ };
675
+ var tokenizedBashBlocked = (command) => {
676
+ const normalized = normalize(command).replace(/\s+/g, " ").trim();
677
+ if (normalized.length === 0)
678
+ return false;
679
+ const forkBombPattern = /(?:^|[;&|])\s*(?::|[a-z_][a-z0-9_]*)\s*\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*(?::|[a-z_][a-z0-9_]*)\s*(?:$|[;&|])/;
680
+ if (forkBombPattern.test(normalized))
681
+ return true;
682
+ const segments = normalized.split(/&&|\||;/).map((segment) => segment.trim()).filter(Boolean);
683
+ if (segments.some((segment) => destructiveBashFragments.some((fragment) => segment.includes(fragment)))) {
684
+ return true;
685
+ }
686
+ for (let index = 0;index < segments.length - 1; index += 1) {
687
+ if (!/^(curl|wget)(?:\s|$)/.test(segments[index]))
688
+ continue;
689
+ if (/\b(?:bash|sh)\b/.test(segments[index + 1]))
690
+ return true;
691
+ }
692
+ return false;
693
+ };
694
+ var mentions = (value) => sensitiveMentions.some((fragment) => value.includes(fragment));
695
+ var flagged = (value) => suspiciousTerms.filter((term) => value.includes(term));
696
+ var risky = (value) => {
697
+ const lower = normalize(value);
698
+ return mentions(lower) || blocked(lower) || tokenizedBashBlocked(lower);
699
+ };
700
+
701
+ // src/opencode-multiagent/session-tracker.ts
702
+ function createSessionTracker(config) {
703
+ const {
704
+ getActivityTime,
705
+ onRemove,
706
+ cleanupIntervalMs = 5 * 60 * 1000,
707
+ staleTtlMs = 30 * 60 * 1000,
708
+ maxTracked = 200,
709
+ evictionFraction = 0.2,
710
+ enabled
711
+ } = config;
712
+ const entries = new Map;
713
+ const remove = (key) => {
714
+ entries.delete(key);
715
+ onRemove?.(key);
716
+ };
717
+ const cleanupStale = (now = Date.now()) => {
718
+ for (const [key, entry] of entries.entries()) {
719
+ if (now - getActivityTime(entry) > staleTtlMs)
720
+ remove(key);
721
+ }
722
+ };
723
+ const enforceLimit = () => {
724
+ if (entries.size <= maxTracked)
725
+ return;
726
+ const toEvict = [...entries.entries()].sort((a, b) => getActivityTime(a[1]) - getActivityTime(b[1])).slice(0, Math.max(1, Math.ceil(entries.size * evictionFraction)));
727
+ for (const [key] of toEvict)
728
+ remove(key);
729
+ };
730
+ let interval = null;
731
+ if (enabled) {
732
+ interval = setInterval(() => cleanupStale(), cleanupIntervalMs);
733
+ interval.unref?.();
734
+ }
735
+ const cleanup = () => {
736
+ if (!interval)
737
+ return;
738
+ clearInterval(interval);
739
+ interval = null;
740
+ };
741
+ return { entries, cleanupStale, enforceLimit, cleanup };
742
+ }
743
+
744
+ // src/opencode-multiagent/quality.ts
745
+ var createQualityController = ({
746
+ flags,
747
+ client
748
+ }) => {
749
+ const tracker = createSessionTracker({
750
+ getActivityTime: (entry) => entry.lastActivityAt,
751
+ enabled: Boolean(flags.quality_gate)
752
+ });
753
+ const getSessionState = (sessionID) => {
754
+ if (!tracker.entries.has(sessionID)) {
755
+ tracker.entries.set(sessionID, {
756
+ editedFiles: new Set,
757
+ lastEditAt: 0,
758
+ qualityRemindedAt: 0,
759
+ lastQualityEvidenceAt: 0,
760
+ lastActivityAt: Date.now()
761
+ });
762
+ }
763
+ return tracker.entries.get(sessionID);
764
+ };
765
+ const trackEdit = (sessionID, filePath) => {
766
+ if (!sessionID || !filePath)
767
+ return;
768
+ const state = getSessionState(sessionID);
769
+ state.editedFiles.add(filePath);
770
+ state.lastEditAt = Date.now();
771
+ state.lastActivityAt = Date.now();
772
+ tracker.enforceLimit();
773
+ };
774
+ const recordQualityEvidence = (sessionID, command) => {
775
+ if (!sessionID || typeof command !== "string")
776
+ return;
777
+ if (!qualitySignalRegex.test(command))
778
+ return;
779
+ const state = getSessionState(sessionID);
780
+ state.lastQualityEvidenceAt = Date.now();
781
+ state.lastActivityAt = Date.now();
782
+ };
783
+ const handleQualityEvent = async (event) => {
784
+ if (!flags.quality_gate || !qualityEventTypes.has(event?.type ?? ""))
785
+ return;
786
+ const props = event.properties ?? {};
787
+ if (event.type === "file.edited") {
788
+ const sessionID = typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.sessionID === "string" ? props.info.sessionID : undefined;
789
+ const file = typeof props.file === "string" ? props.file : typeof props.info?.file === "string" ? props.info.file : undefined;
790
+ trackEdit(sessionID, file);
791
+ return;
792
+ }
793
+ if (event.type === "session.idle") {
794
+ const sessionID = typeof props.sessionID === "string" ? props.sessionID : undefined;
795
+ if (!sessionID || !client?.session?.prompt)
796
+ return;
797
+ const state = tracker.entries.get(sessionID);
798
+ if (!state || state.editedFiles.size === 0)
799
+ return;
800
+ state.lastActivityAt = Date.now();
801
+ if (state.lastQualityEvidenceAt >= state.lastEditAt)
802
+ return;
803
+ const now = Date.now();
804
+ const idleMs = flags.quality_config?.reminder_idle_ms ?? 120000;
805
+ const cooldownMs = flags.quality_config?.reminder_cooldown_ms ?? 300000;
806
+ if (now - state.lastEditAt < idleMs || now - state.qualityRemindedAt < cooldownMs)
807
+ return;
808
+ state.qualityRemindedAt = now;
809
+ const files = [...state.editedFiles].slice(0, 6).join(", ");
810
+ const reminder = "[opencode-multiagent quality] This session edited files and no later verification signal was observed. " + "Run `/quality` or equivalent repo-native checks before treating the work as done." + (files ? ` Tracked files: ${files}` : "");
811
+ try {
812
+ await client.session.prompt({
813
+ path: { id: sessionID },
814
+ body: { parts: [{ type: "text", text: reminder }], noReply: true }
815
+ });
816
+ } catch {
817
+ return;
818
+ }
819
+ await note("quality_gate", {
820
+ event: "idle_reminder",
821
+ sessionID,
822
+ files: [...state.editedFiles].slice(0, 12)
823
+ });
824
+ return;
825
+ }
826
+ if (event.type === "session.deleted") {
827
+ const sessionID = typeof props.info?.id === "string" ? props.info.id : typeof props.sessionID === "string" ? props.sessionID : undefined;
828
+ if (sessionID)
829
+ tracker.entries.delete(sessionID);
830
+ }
831
+ };
832
+ const hasQualityEvidence = (sessionID) => {
833
+ const state = tracker.entries.get(sessionID);
834
+ if (!state || state.editedFiles.size === 0)
835
+ return { passed: true };
836
+ if (state.lastQualityEvidenceAt >= state.lastEditAt)
837
+ return { passed: true };
838
+ return {
839
+ passed: false,
840
+ reason: `${state.editedFiles.size} file(s) edited, no verification evidence found`
841
+ };
842
+ };
843
+ return {
844
+ handleQualityEvent,
845
+ recordQualityEvidence,
846
+ trackEdit,
847
+ hasQualityEvidence,
848
+ cleanup: tracker.cleanup
849
+ };
850
+ };
851
+
852
+ // src/opencode-multiagent/supervision.ts
853
+ var createSupervisionController = ({
854
+ flags,
855
+ client
856
+ }) => {
857
+ const childMap = new Map;
858
+ const cleanupChildMap = (childID) => {
859
+ const info = tracker.entries.get(childID);
860
+ if (!info)
861
+ return;
862
+ const children = childMap.get(info.parentID);
863
+ if (!children)
864
+ return;
865
+ children.delete(childID);
866
+ if (children.size === 0)
867
+ childMap.delete(info.parentID);
868
+ };
869
+ const tracker = createSessionTracker({
870
+ getActivityTime: (entry) => entry.lastActivity,
871
+ maxTracked: 100,
872
+ onRemove: cleanupChildMap,
873
+ enabled: true
874
+ });
875
+ const removeChild = (childID) => {
876
+ const info = tracker.entries.get(childID);
877
+ if (!info)
878
+ return false;
879
+ const children = childMap.get(info.parentID);
880
+ if (children) {
881
+ children.delete(childID);
882
+ if (children.size === 0)
883
+ childMap.delete(info.parentID);
884
+ }
885
+ tracker.entries.delete(childID);
886
+ return true;
887
+ };
888
+ const handleSupervision = async (event) => {
889
+ if (!supervisionEventTypes.has(event?.type ?? ""))
890
+ return;
891
+ const props = event.properties ?? {};
892
+ if (event.type === "session.created") {
893
+ const parentID = typeof props.info?.parentID === "string" ? props.info.parentID : undefined;
894
+ const childID = typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : undefined;
895
+ if (!parentID || !childID)
896
+ return;
897
+ removeChild(childID);
898
+ if (!childMap.has(parentID))
899
+ childMap.set(parentID, new Set);
900
+ childMap.get(parentID)?.add(childID);
901
+ tracker.entries.set(childID, {
902
+ parentID,
903
+ agentName: null,
904
+ lastActivity: Date.now(),
905
+ remindedAt: 0,
906
+ lastToolErrorAt: 0,
907
+ hasUnverifiedEdits: false
908
+ });
909
+ tracker.enforceLimit();
910
+ await note("supervision", { event: "child_tracked", parentID, childID });
911
+ return;
912
+ }
913
+ if (event.type === "session.deleted") {
914
+ const sessionID = typeof props.info?.id === "string" ? props.info.id : typeof props.sessionID === "string" ? props.sessionID : undefined;
915
+ if (!sessionID || !removeChild(sessionID))
916
+ return;
917
+ await note("supervision", { event: "child_removed", sessionID });
918
+ return;
919
+ }
920
+ if (!flags.supervision)
921
+ return;
922
+ if (["message.updated", "message.part.updated", "message.part.delta"].includes(event.type ?? "")) {
923
+ const sessionID = event.type === "message.updated" ? typeof props.info?.sessionID === "string" ? props.info.sessionID : undefined : event.type === "message.part.updated" ? typeof props.part?.sessionID === "string" ? props.part.sessionID : undefined : typeof props.sessionID === "string" ? props.sessionID : undefined;
924
+ if (!sessionID || !tracker.entries.has(sessionID))
925
+ return;
926
+ const info = tracker.entries.get(sessionID);
927
+ info.lastActivity = Date.now();
928
+ if (event.type === "message.updated" && props.info?.role === "assistant" && typeof props.info?.agent === "string") {
929
+ info.agentName = props.info.agent;
930
+ }
931
+ return;
932
+ }
933
+ if (event.type === "session.idle") {
934
+ const sessionID = typeof props.sessionID === "string" ? props.sessionID : undefined;
935
+ if (!sessionID || !tracker.entries.has(sessionID) || !client?.session?.prompt)
936
+ return;
937
+ const info = tracker.entries.get(sessionID);
938
+ const now = Date.now();
939
+ const idleTimeout = flags.supervision_config?.idle_timeout_ms ?? 180000;
940
+ const cooldown = flags.supervision_config?.cooldown_ms ?? 300000;
941
+ if (now - info.lastActivity < idleTimeout || now - info.remindedAt < cooldown)
942
+ return;
943
+ info.remindedAt = now;
944
+ const reminderText = `[opencode-multiagent supervision] Child session ${sessionID} ` + `(agent: ${info.agentName ?? "unknown"}) has been idle. Check status or resume if needed.`;
945
+ try {
946
+ await client.session.prompt({
947
+ path: { id: info.parentID },
948
+ body: { parts: [{ type: "text", text: reminderText }], noReply: true }
949
+ });
950
+ } catch {
951
+ return;
952
+ }
953
+ await note("supervision", {
954
+ event: "idle_reminder",
955
+ parentID: info.parentID,
956
+ childID: sessionID,
957
+ agentName: info.agentName
958
+ });
959
+ return;
960
+ }
961
+ };
962
+ const recordToolError = (sessionID) => {
963
+ const info = tracker.entries.get(sessionID);
964
+ if (info)
965
+ info.lastToolErrorAt = Date.now();
966
+ };
967
+ const recordVerification = (sessionID) => {
968
+ const info = tracker.entries.get(sessionID);
969
+ if (info)
970
+ info.hasUnverifiedEdits = false;
971
+ };
972
+ const getActiveChildCount = (parentID) => {
973
+ return childMap.get(parentID)?.size ?? 0;
974
+ };
975
+ return {
976
+ handleSupervision,
977
+ cleanup: tracker.cleanup,
978
+ recordToolError,
979
+ recordVerification,
980
+ getActiveChildCount,
981
+ getChildInfo(sessionID) {
982
+ return tracker.entries.get(sessionID);
983
+ },
984
+ getParentID(sessionID) {
985
+ return tracker.entries.get(sessionID)?.parentID;
986
+ }
987
+ };
988
+ };
989
+
990
+ // src/opencode-multiagent/task-manager.ts
991
+ import { mkdir as mkdir2, readFile as readFile4, writeFile } from "fs/promises";
992
+ import { join as join3 } from "path";
993
+ var createTaskManager = (projectRoot) => {
994
+ let taskCounter = 0;
995
+ const generateTaskID = () => {
996
+ taskCounter += 1;
997
+ return `T-${Date.now()}-${taskCounter.toString().padStart(4, "0")}`;
998
+ };
999
+ const tasks = new Map;
1000
+ const boardPath = projectRoot ? join3(projectRoot, ".magent", "board.json") : undefined;
1001
+ const legacyBoardPath = projectRoot ? join3(projectRoot, ".opencode", "tasks", "taskboard.json") : undefined;
1002
+ let persistTimer = null;
1003
+ let persistInFlight = false;
1004
+ let persistQueued = false;
1005
+ const doPersist = async () => {
1006
+ if (!boardPath)
1007
+ return;
1008
+ if (persistInFlight) {
1009
+ persistQueued = true;
1010
+ return;
1011
+ }
1012
+ persistInFlight = true;
1013
+ try {
1014
+ await mkdir2(join3(boardPath, ".."), { recursive: true });
1015
+ await writeFile(boardPath, JSON.stringify([...tasks.values()], null, 2), "utf8");
1016
+ } catch {} finally {
1017
+ persistInFlight = false;
1018
+ if (persistQueued) {
1019
+ persistQueued = false;
1020
+ doPersist();
1021
+ }
1022
+ }
1023
+ };
1024
+ const schedulePersist = () => {
1025
+ if (persistTimer)
1026
+ clearTimeout(persistTimer);
1027
+ persistTimer = setTimeout(() => {
1028
+ persistTimer = null;
1029
+ doPersist();
1030
+ }, 100);
1031
+ };
1032
+ const load = async () => {
1033
+ if (!boardPath)
1034
+ return;
1035
+ let loaded = false;
1036
+ try {
1037
+ const raw = await readFile4(boardPath, "utf8");
1038
+ const items = JSON.parse(raw);
1039
+ for (const item of items)
1040
+ tasks.set(item.id, item);
1041
+ loaded = true;
1042
+ } catch {}
1043
+ if (!loaded && legacyBoardPath) {
1044
+ try {
1045
+ const raw = await readFile4(legacyBoardPath, "utf8");
1046
+ const items = JSON.parse(raw);
1047
+ for (const item of items)
1048
+ tasks.set(item.id, item);
1049
+ loaded = true;
1050
+ schedulePersist();
1051
+ } catch {}
1052
+ }
1053
+ if (loaded) {
1054
+ for (const task of tasks.values()) {
1055
+ if (task.status === "in_progress" || task.status === "claimed") {
1056
+ task.status = "pending";
1057
+ task.updatedAt = Date.now();
1058
+ task.result = (task.result ? task.result + " | " : "") + "Reset from stale state on load";
1059
+ }
1060
+ }
1061
+ if (tasks.size > 0)
1062
+ schedulePersist();
1063
+ }
1064
+ };
1065
+ const areDependenciesMet = (taskID) => {
1066
+ const task = tasks.get(taskID);
1067
+ if (!task || task.dependencies.length === 0)
1068
+ return { met: true, unmet: [] };
1069
+ const unmet = [];
1070
+ for (const depID of task.dependencies) {
1071
+ const dep = tasks.get(depID);
1072
+ if (!dep || dep.status !== "completed") {
1073
+ unmet.push(depID);
1074
+ }
1075
+ }
1076
+ return { met: unmet.length === 0, unmet };
1077
+ };
1078
+ const create = (input) => {
1079
+ const task = {
1080
+ id: generateTaskID(),
1081
+ title: input.title,
1082
+ description: input.description,
1083
+ status: "pending",
1084
+ priority: input.priority ?? "medium",
1085
+ assignedAgent: input.assignedAgent,
1086
+ createdBy: input.createdBy,
1087
+ dependencies: input.dependencies ?? [],
1088
+ createdAt: Date.now(),
1089
+ updatedAt: Date.now()
1090
+ };
1091
+ tasks.set(task.id, task);
1092
+ schedulePersist();
1093
+ return task;
1094
+ };
1095
+ const update = (taskID, input) => {
1096
+ const task = tasks.get(taskID);
1097
+ if (!task)
1098
+ return { error: `Task ${taskID} not found` };
1099
+ if (input.status && (input.status === "in_progress" || input.status === "claimed") && task.dependencies.length > 0) {
1100
+ const { met, unmet } = areDependenciesMet(taskID);
1101
+ if (!met) {
1102
+ return {
1103
+ error: `Cannot transition ${taskID} to ${input.status}: unmet dependencies [${unmet.join(", ")}]`
1104
+ };
1105
+ }
1106
+ }
1107
+ if (input.status !== undefined)
1108
+ task.status = input.status;
1109
+ if (input.result !== undefined)
1110
+ task.result = input.result;
1111
+ if (input.assignedAgent !== undefined)
1112
+ task.assignedAgent = input.assignedAgent;
1113
+ task.updatedAt = Date.now();
1114
+ schedulePersist();
1115
+ return task;
1116
+ };
1117
+ const get = (taskID) => {
1118
+ const task = tasks.get(taskID);
1119
+ if (!task)
1120
+ return { error: `Task ${taskID} not found` };
1121
+ return task;
1122
+ };
1123
+ const list = (filter) => {
1124
+ const all = [...tasks.values()];
1125
+ if (!filter)
1126
+ return all;
1127
+ return all.filter((task) => {
1128
+ if (filter.status && task.status !== filter.status)
1129
+ return false;
1130
+ if (filter.assignedAgent && task.assignedAgent !== filter.assignedAgent)
1131
+ return false;
1132
+ return true;
1133
+ });
1134
+ };
1135
+ const getActiveSummary = () => {
1136
+ const active = [...tasks.values()].filter((t) => t.status === "pending" || t.status === "claimed" || t.status === "in_progress");
1137
+ if (active.length === 0)
1138
+ return "";
1139
+ const lines = active.slice(0, 10).map((t) => `- [${t.status}] ${t.title}${t.assignedAgent ? ` (${t.assignedAgent})` : ""}`);
1140
+ return `Active tasks (${active.length}):
1141
+ ${lines.join(`
1142
+ `)}`;
1143
+ };
1144
+ const linkSession = (taskID, sessionID) => {
1145
+ const task = tasks.get(taskID);
1146
+ if (!task)
1147
+ return false;
1148
+ task.sessionID = sessionID;
1149
+ task.updatedAt = Date.now();
1150
+ schedulePersist();
1151
+ return true;
1152
+ };
1153
+ return { create, update, get, list, load, getActiveSummary, linkSession };
1154
+ };
1155
+
1156
+ // src/opencode-multiagent/telemetry.ts
1157
+ var createTelemetryController = ({ flags }) => {
1158
+ const tracker = createSessionTracker({
1159
+ getActivityTime: (entry) => entry.lastActivityAt,
1160
+ enabled: Boolean(flags.telemetry)
1161
+ });
1162
+ const ensureSession = (sessionID, extra = {}) => {
1163
+ if (!sessionID)
1164
+ return null;
1165
+ const now = Date.now();
1166
+ if (!tracker.entries.has(sessionID)) {
1167
+ tracker.entries.set(sessionID, {
1168
+ sessionID,
1169
+ agent: undefined,
1170
+ model: undefined,
1171
+ startedAt: now,
1172
+ lastActivityAt: now,
1173
+ toolCalls: 0,
1174
+ toolErrors: 0,
1175
+ filesEdited: 0,
1176
+ tasksDispatched: 0,
1177
+ permissionDenied: 0,
1178
+ flushed: false,
1179
+ ...extra
1180
+ });
1181
+ }
1182
+ const state = tracker.entries.get(sessionID);
1183
+ const safeExtra = Object.fromEntries(Object.entries(extra).filter(([, v]) => v !== undefined));
1184
+ Object.assign(state, safeExtra);
1185
+ state.lastActivityAt = now;
1186
+ return state;
1187
+ };
1188
+ return {
1189
+ cleanup: tracker.cleanup,
1190
+ rememberSession(sessionID, extra = {}) {
1191
+ ensureSession(sessionID, extra);
1192
+ tracker.enforceLimit();
1193
+ },
1194
+ trackEdit(sessionID) {
1195
+ const state = ensureSession(sessionID);
1196
+ if (!state)
1197
+ return;
1198
+ state.filesEdited += 1;
1199
+ },
1200
+ trackToolCall(sessionID, extra = {}) {
1201
+ const state = ensureSession(sessionID, extra);
1202
+ if (!state)
1203
+ return;
1204
+ state.toolCalls += 1;
1205
+ },
1206
+ trackToolError(sessionID, extra = {}) {
1207
+ const state = ensureSession(sessionID, extra);
1208
+ if (!state)
1209
+ return;
1210
+ state.toolErrors += 1;
1211
+ },
1212
+ trackTaskDispatch(sessionID, extra = {}) {
1213
+ const state = ensureSession(sessionID, extra);
1214
+ if (!state)
1215
+ return;
1216
+ state.tasksDispatched += 1;
1217
+ },
1218
+ trackPermissionDenied(sessionID, extra = {}) {
1219
+ const state = ensureSession(sessionID, extra);
1220
+ if (!state)
1221
+ return;
1222
+ state.permissionDenied += 1;
1223
+ },
1224
+ async flushSession(sessionID, reason = "session_deleted") {
1225
+ const state = tracker.entries.get(sessionID);
1226
+ if (!state || state.flushed)
1227
+ return;
1228
+ state.flushed = true;
1229
+ tracker.entries.delete(sessionID);
1230
+ await note("session_metrics", {
1231
+ observation: true,
1232
+ sessionID,
1233
+ agent: state.agent,
1234
+ model: state.model,
1235
+ duration_ms: Math.max(0, Date.now() - state.startedAt),
1236
+ tool_calls: state.toolCalls,
1237
+ tool_errors: state.toolErrors,
1238
+ files_edited: state.filesEdited,
1239
+ tasks_dispatched: state.tasksDispatched,
1240
+ permission_denied: state.permissionDenied,
1241
+ reason
1242
+ });
1243
+ },
1244
+ async flushAll() {
1245
+ const ids = [...tracker.entries.keys()];
1246
+ for (const id of ids) {
1247
+ await this.flushSession(id, "cleanup");
1248
+ }
1249
+ }
1250
+ };
1251
+ };
1252
+
1253
+ // src/opencode-multiagent/tools.ts
1254
+ import { tool as pluginTool } from "@opencode-ai/plugin";
1255
+ var createTaskTools = (taskManager, taskManagerReady, correlation, quality, flags) => ({
1256
+ task_create: pluginTool({
1257
+ description: "Create a new task on the shared task board",
1258
+ args: {
1259
+ title: pluginTool.schema.string().describe("Short title for the task"),
1260
+ description: pluginTool.schema.string().describe("Detailed description of what needs to be done"),
1261
+ assignedAgent: pluginTool.schema.string().optional().describe("Name of the agent to assign this task to"),
1262
+ dependencies: pluginTool.schema.array(pluginTool.schema.string()).optional().describe("IDs of tasks this task depends on"),
1263
+ priority: pluginTool.schema.enum(["high", "medium", "low"]).optional().describe("Task priority: high, medium, or low")
1264
+ },
1265
+ async execute(args, ctx) {
1266
+ await taskManagerReady;
1267
+ return JSON.stringify(taskManager.create({ ...args, createdBy: ctx.agent }));
1268
+ }
1269
+ }),
1270
+ task_dispatch: pluginTool({
1271
+ description: "Record a dispatch intent linking a task board entry to the next child session. " + "Call this immediately before dispatching work via the task tool. " + "Sets the task status to in_progress and records the correlation intent.",
1272
+ args: {
1273
+ taskID: pluginTool.schema.string().describe("ID of the task being dispatched"),
1274
+ notes: pluginTool.schema.string().optional().describe("Optional dispatch notes or context")
1275
+ },
1276
+ async execute(args, ctx) {
1277
+ await taskManagerReady;
1278
+ const task = taskManager.get(args.taskID);
1279
+ if ("error" in task)
1280
+ return JSON.stringify(task);
1281
+ const updated = taskManager.update(args.taskID, { status: "in_progress" });
1282
+ if ("error" in updated)
1283
+ return JSON.stringify(updated);
1284
+ if (correlation) {
1285
+ const parentSessionID = ctx.sessionID ?? "";
1286
+ const targetAgent = task.assignedAgent ?? "unknown";
1287
+ correlation.recordIntent(parentSessionID, args.taskID, targetAgent);
1288
+ }
1289
+ await note("task_dispatch_intent", {
1290
+ taskID: args.taskID,
1291
+ agent: ctx.agent,
1292
+ notes: args.notes
1293
+ });
1294
+ return JSON.stringify({
1295
+ ...updated,
1296
+ dispatch: "intent_recorded",
1297
+ notes: args.notes
1298
+ });
1299
+ }
1300
+ }),
1301
+ task_update: pluginTool({
1302
+ description: "Update a task's status or result on the shared task board. " + "Dependency enforcement: transitions to in_progress or claimed are blocked if dependencies are not completed.",
1303
+ args: {
1304
+ taskID: pluginTool.schema.string().describe("ID of the task to update"),
1305
+ status: pluginTool.schema.enum(["pending", "claimed", "in_progress", "completed", "failed", "blocked"]).describe("New status for the task"),
1306
+ result: pluginTool.schema.string().optional().describe("Result summary or notes"),
1307
+ force: pluginTool.schema.boolean().optional().describe("Force completion even without quality evidence (bypass quality gate)")
1308
+ },
1309
+ async execute(args, ctx) {
1310
+ await taskManagerReady;
1311
+ if (flags?.quality_gate_enforcement && args.status === "completed" && !args.force && quality) {
1312
+ const link = correlation?.getLinkByTask(args.taskID);
1313
+ if (link) {
1314
+ const evidence = quality.hasQualityEvidence(link.childSessionID);
1315
+ if (!evidence.passed) {
1316
+ await note("quality_gate_blocked", {
1317
+ taskID: args.taskID,
1318
+ sessionID: link.childSessionID,
1319
+ reason: evidence.reason
1320
+ });
1321
+ return JSON.stringify({
1322
+ error: `Quality gate: ${evidence.reason}. Run verification commands (test/lint/build) before completing, or pass force: true to bypass.`,
1323
+ taskID: args.taskID,
1324
+ quality_gate: "blocked"
1325
+ });
1326
+ }
1327
+ }
1328
+ }
1329
+ if (args.force && args.status === "completed") {
1330
+ await note("quality_gate_bypassed", {
1331
+ taskID: args.taskID,
1332
+ agent: ctx.agent
1333
+ });
1334
+ }
1335
+ return JSON.stringify(taskManager.update(args.taskID, { status: args.status, result: args.result }));
1336
+ }
1337
+ }),
1338
+ task_get: pluginTool({
1339
+ description: "Get a single task by ID from the shared task board",
1340
+ args: {
1341
+ taskID: pluginTool.schema.string().describe("ID of the task to retrieve")
1342
+ },
1343
+ async execute(args) {
1344
+ await taskManagerReady;
1345
+ return JSON.stringify(taskManager.get(args.taskID));
1346
+ }
1347
+ }),
1348
+ task_list: pluginTool({
1349
+ description: "List tasks on the shared task board with optional filters",
1350
+ args: {
1351
+ status: pluginTool.schema.string().optional().describe("Filter by task status"),
1352
+ assignedAgent: pluginTool.schema.string().optional().describe("Filter by assigned agent name")
1353
+ },
1354
+ async execute(args) {
1355
+ await taskManagerReady;
1356
+ return JSON.stringify(taskManager.list(args));
1357
+ }
1358
+ })
1359
+ });
1360
+
1361
+ // src/opencode-multiagent/hooks.ts
1362
+ var isMcpTool = (tool) => typeof tool === "string" && mcpToolPrefixes.some((prefix) => tool.startsWith(prefix));
1363
+ var qaWarningThreshold = 3;
1364
+ var qaWarningCooldownMs = 5 * 60 * 1000;
1365
+ var getTaskTarget = (args = {}) => {
1366
+ for (const key of ["subagent_type", "agent", "subagent", "type", "name"]) {
1367
+ if (typeof args?.[key] === "string")
1368
+ return args[key];
1369
+ }
1370
+ return;
1371
+ };
1372
+ var createPluginHooks = ({
1373
+ client,
1374
+ flags,
1375
+ agentSettings,
1376
+ projectRoot
1377
+ }) => {
1378
+ const projectAgentsDir = projectRoot ? join4(projectRoot, ".opencode", "agents") : undefined;
1379
+ const projectCommandsDir = projectRoot ? join4(projectRoot, ".opencode", "commands") : undefined;
1380
+ const supervision = createSupervisionController({ flags, client });
1381
+ const { handleSupervision, cleanup, getChildInfo } = supervision;
1382
+ const quality = createQualityController({ flags, client });
1383
+ const telemetry = createTelemetryController({ flags });
1384
+ const correlation = createCorrelationController();
1385
+ const sessionAgentMap = new Map;
1386
+ const qaDispatchState = new Map;
1387
+ let mcpDefaults = null;
1388
+ let mcpRegistry = null;
1389
+ let taskRoutingRegistry = null;
1390
+ const taskManager = createTaskManager(projectRoot);
1391
+ const taskManagerReady = taskManager.load();
1392
+ const rememberSession = (sessionID, extra = {}) => {
1393
+ if (!sessionID)
1394
+ return;
1395
+ const current = sessionAgentMap.get(sessionID) ?? {};
1396
+ const next = { ...current, ...extra };
1397
+ sessionAgentMap.set(sessionID, next);
1398
+ telemetry.rememberSession(sessionID, next);
1399
+ };
1400
+ const trackQaDispatch = async (sessionID, agentInfo = {}) => {
1401
+ if (!sessionID)
1402
+ return;
1403
+ const state = qaDispatchState.get(sessionID) ?? { count: 0, remindedAt: 0 };
1404
+ state.count += 1;
1405
+ qaDispatchState.set(sessionID, state);
1406
+ if (state.count < qaWarningThreshold || !client?.session?.prompt)
1407
+ return;
1408
+ const now = Date.now();
1409
+ if (now - state.remindedAt < qaWarningCooldownMs)
1410
+ return;
1411
+ state.remindedAt = now;
1412
+ try {
1413
+ await client.session.prompt({
1414
+ path: { id: sessionID },
1415
+ body: {
1416
+ noReply: true,
1417
+ parts: [
1418
+ {
1419
+ type: "text",
1420
+ text: `[opencode-multiagent review] This session has dispatched reviewer ${state.count} times. ` + "Reassess the brief, consider planner repair, or narrow the defect before sending reviewer again."
1421
+ }
1422
+ ]
1423
+ }
1424
+ });
1425
+ } catch {
1426
+ return;
1427
+ }
1428
+ await note("qa_counter_warning", {
1429
+ observation: true,
1430
+ sessionID,
1431
+ agent: agentInfo.agent,
1432
+ model: agentInfo.model,
1433
+ qa_dispatch_count: state.count
1434
+ });
1435
+ };
1436
+ const notifyParentOnChildCompletion = async (childSessionID, parentSessionID, agentName, taskLabel) => {
1437
+ const agentLabel = agentName ? ` (agent: ${agentName})` : "";
1438
+ const taskInfo = taskLabel ? `, task ${taskLabel}` : "";
1439
+ const text2 = `Child session ${childSessionID}${agentLabel}${taskInfo} has completed. ` + "Review its output and proceed with the next task.";
1440
+ try {
1441
+ if (client.session?.prompt) {
1442
+ await client.session.prompt({
1443
+ path: { id: parentSessionID },
1444
+ body: { parts: [{ type: "text", text: text2 }], noReply: false }
1445
+ });
1446
+ } else if (client.session?.promptAsync) {
1447
+ await client.session.promptAsync({
1448
+ sessionID: parentSessionID,
1449
+ noReply: false,
1450
+ parts: [{ type: "text", text: text2 }]
1451
+ });
1452
+ } else {
1453
+ return;
1454
+ }
1455
+ await note("child_completion_notify", {
1456
+ observation: true,
1457
+ childSessionID,
1458
+ parentSessionID,
1459
+ agentName
1460
+ });
1461
+ } catch {
1462
+ return;
1463
+ }
1464
+ };
1465
+ const loadActivePlanSummary = async () => {
1466
+ if (!projectRoot)
1467
+ return "";
1468
+ const plansDir = join4(projectRoot, ".magent", "plans");
1469
+ try {
1470
+ const entries = await readdir2(plansDir);
1471
+ const mdFiles = entries.filter((f) => f.endsWith(".md")).sort();
1472
+ if (mdFiles.length === 0)
1473
+ return "";
1474
+ const lastPlan = mdFiles[mdFiles.length - 1];
1475
+ const content = await readFile5(join4(plansDir, lastPlan), "utf8");
1476
+ const objectiveMatch = content.match(/##\s+Objective\s*\n([\s\S]*?)(?=\n##|\s*$)/);
1477
+ const objective = objectiveMatch ? objectiveMatch[1].trim().split(`
1478
+ `)[0].slice(0, 200) : "";
1479
+ return objective ? `Active plan: ${lastPlan} \u2014 ${objective}` : `Active plan: ${lastPlan}`;
1480
+ } catch {
1481
+ return "";
1482
+ }
1483
+ };
1484
+ return {
1485
+ async cleanup() {
1486
+ await telemetry.flushAll();
1487
+ cleanup?.();
1488
+ quality.cleanup?.();
1489
+ telemetry.cleanup?.();
1490
+ },
1491
+ async event(input) {
1492
+ const type = input.event?.type;
1493
+ const props = input.event?.properties ?? {};
1494
+ if (flags.observation && trackedEventTypes.has(type)) {
1495
+ await note("event", compact({
1496
+ event: type,
1497
+ data: compact({
1498
+ sessionID: typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.sessionID === "string" ? props.info.sessionID : undefined,
1499
+ messageID: typeof props.messageID === "string" ? props.messageID : undefined,
1500
+ partID: typeof props.partID === "string" ? props.partID : undefined,
1501
+ permissionID: typeof props.permissionID === "string" ? props.permissionID : undefined,
1502
+ response: typeof props.response === "string" ? props.response : undefined,
1503
+ file: typeof props.file === "string" ? clip(props.file) : undefined,
1504
+ error: typeof props.error?.message === "string" ? clip(props.error.message) : undefined
1505
+ })
1506
+ }));
1507
+ }
1508
+ if (type === "session.created") {
1509
+ const parentID = typeof props.info?.parentID === "string" ? props.info.parentID : undefined;
1510
+ const childID = typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : undefined;
1511
+ if (parentID && childID) {
1512
+ const parent = sessionAgentMap.get(parentID) ?? {};
1513
+ telemetry.trackTaskDispatch(parentID, { agent: parent.agent, model: parent.model });
1514
+ const agentHint = typeof props.info?.agent === "string" ? props.info.agent : undefined;
1515
+ const link = correlation.tryCorrelate(parentID, childID, agentHint);
1516
+ if (link) {
1517
+ taskManager.linkSession(link.taskID, childID);
1518
+ await note("task_correlation", {
1519
+ observation: true,
1520
+ taskID: link.taskID,
1521
+ parentSessionID: parentID,
1522
+ childSessionID: childID,
1523
+ agentHint
1524
+ });
1525
+ }
1526
+ await note("task_dispatch", {
1527
+ observation: true,
1528
+ parent_sessionID: parentID,
1529
+ parent_agent: parent.agent,
1530
+ child_sessionID: childID
1531
+ });
1532
+ }
1533
+ } else if (type === "message.updated") {
1534
+ const sessionID = typeof props.info?.sessionID === "string" ? props.info.sessionID : undefined;
1535
+ const agent = typeof props.info?.agent === "string" ? props.info.agent : undefined;
1536
+ if (sessionID && agent) {
1537
+ rememberSession(sessionID, { agent });
1538
+ }
1539
+ } else if (type === "session.deleted") {
1540
+ const sessionID = typeof props.info?.id === "string" ? props.info.id : typeof props.sessionID === "string" ? props.sessionID : undefined;
1541
+ if (sessionID) {
1542
+ const childInfo = getChildInfo(sessionID);
1543
+ sessionAgentMap.delete(sessionID);
1544
+ qaDispatchState.delete(sessionID);
1545
+ await telemetry.flushSession(sessionID);
1546
+ let taskLabel;
1547
+ const link = correlation.getLink(sessionID);
1548
+ if (link && flags.task_lifecycle) {
1549
+ const recentErrorMs = 30000;
1550
+ const hasRecentError = childInfo?.lastToolErrorAt != null && childInfo.lastToolErrorAt > 0 && Date.now() - childInfo.lastToolErrorAt < recentErrorMs;
1551
+ const autoStatus = hasRecentError ? "failed" : "completed";
1552
+ const autoResult = hasRecentError ? "Auto-failed: tool error detected near session end" : "Auto-completed: session ended without recent errors";
1553
+ await taskManagerReady;
1554
+ taskManager.update(link.taskID, { status: autoStatus, result: autoResult });
1555
+ taskLabel = link.taskID;
1556
+ await note("task_lifecycle_auto", {
1557
+ observation: true,
1558
+ taskID: link.taskID,
1559
+ childSessionID: sessionID,
1560
+ autoStatus
1561
+ });
1562
+ }
1563
+ if (link)
1564
+ correlation.removeLink(sessionID);
1565
+ if (childInfo?.parentID) {
1566
+ await notifyParentOnChildCompletion(sessionID, childInfo.parentID, childInfo.agentName, taskLabel);
1567
+ }
1568
+ }
1569
+ } else if (type === "session.idle") {
1570
+ const sessionID = typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : undefined;
1571
+ if (sessionID) {
1572
+ await telemetry.flushSession(sessionID, "session_idle");
1573
+ }
1574
+ }
1575
+ await handleSupervision(input.event);
1576
+ await quality.handleQualityEvent(input.event);
1577
+ },
1578
+ async "permission.ask"(input, output) {
1579
+ if (!flags.enforcement)
1580
+ return;
1581
+ const patternValue = Array.isArray(input.pattern) ? input.pattern.join(" ") : String(input.pattern ?? "");
1582
+ if (["read", "edit", "external_directory"].includes(input.type) && blocked(patternValue)) {
1583
+ output.status = "deny";
1584
+ return;
1585
+ }
1586
+ if (input.type === "bash" && tokenizedBashBlocked(patternValue)) {
1587
+ output.status = "deny";
1588
+ }
1589
+ },
1590
+ async "chat.params"(input, output) {
1591
+ rememberSession(input.sessionID, {
1592
+ agent: label(input.agent),
1593
+ model: label(input.model, ["id", "name", "modelID"])
1594
+ });
1595
+ if (!flags.observation)
1596
+ return;
1597
+ const marks = [];
1598
+ if (typeof output.temperature === "number" && output.temperature > 1)
1599
+ marks.push("high_temperature");
1600
+ if (typeof output.topK === "number" && output.topK > 0)
1601
+ marks.push("nonzero_top_k");
1602
+ if (Object.keys(output.options ?? {}).length > 0)
1603
+ marks.push("non_empty_options");
1604
+ await note("chat_params", compact({
1605
+ observation: true,
1606
+ sessionID: input.sessionID,
1607
+ agent: label(input.agent),
1608
+ provider: compact({
1609
+ source: input.provider?.source,
1610
+ id: label(input.provider?.info, ["id", "name", "providerID"])
1611
+ }),
1612
+ model: compact({
1613
+ id: label(input.model, ["id", "name", "modelID"]),
1614
+ providerID: label(input.model, ["providerID"])
1615
+ }),
1616
+ params: compact({
1617
+ temperature: output.temperature,
1618
+ topP: output.topP,
1619
+ topK: output.topK,
1620
+ optionKeys: Object.keys(output.options ?? {})
1621
+ }),
1622
+ heuristic: marks.length > 0 ? marks : undefined
1623
+ }));
1624
+ },
1625
+ async "chat.headers"(input, output) {
1626
+ if (!flags.prompt_controls)
1627
+ return;
1628
+ Object.assign(output.headers, {
1629
+ "x-opencode-multiagent": "1",
1630
+ "x-opencode-multiagent-profile": flags.profile ?? "standard",
1631
+ "x-opencode-agent": label(input.agent) ?? "unknown"
1632
+ });
1633
+ },
1634
+ async "chat.message"(input, output) {
1635
+ const userParts = Array.isArray(output.parts) ? output.parts.filter((p) => p?.type === "text" && p?.role !== "tool") : output.parts;
1636
+ if (!flags.prompt_controls || !risky(text(userParts)))
1637
+ return;
1638
+ await note("chat_risk", compact({
1639
+ observation: true,
1640
+ sessionID: input.sessionID,
1641
+ agent: label(input.agent),
1642
+ text: clip(text(output.parts))
1643
+ }));
1644
+ },
1645
+ async "command.execute.before"(input) {
1646
+ if (!flags.prompt_controls || !risky(input.arguments))
1647
+ return;
1648
+ await note("command_risk", compact({
1649
+ observation: true,
1650
+ sessionID: input.sessionID,
1651
+ command: input.command,
1652
+ arguments: clip(input.arguments)
1653
+ }));
1654
+ },
1655
+ async "experimental.chat.system.transform"(_input, output) {
1656
+ if (flags.experimental?.chat_system_transform !== true)
1657
+ return;
1658
+ if (output.system.includes(experimentalText))
1659
+ return;
1660
+ output.system.push(experimentalText);
1661
+ },
1662
+ async "experimental.chat.messages.transform"(_input, output) {
1663
+ if (flags.experimental?.chat_messages_transform !== true)
1664
+ return;
1665
+ const last = [...output.messages].reverse().find((message) => message.info?.role === "user");
1666
+ if (!last)
1667
+ return;
1668
+ if (text(last.parts).includes(experimentalText))
1669
+ return;
1670
+ last.parts.push({ type: "text", text: experimentalText });
1671
+ },
1672
+ async "experimental.session.compacting"(_input, output) {
1673
+ if (flags.experimental?.session_compacting !== true)
1674
+ return;
1675
+ const parts = [];
1676
+ const planSummary = await loadActivePlanSummary();
1677
+ if (planSummary)
1678
+ parts.push(planSummary);
1679
+ await taskManagerReady;
1680
+ const taskSummary = taskManager.getActiveSummary();
1681
+ if (taskSummary)
1682
+ parts.push(taskSummary);
1683
+ if (parts.length > 0) {
1684
+ output.context.push(`[opencode-multiagent workflow state]
1685
+ ${parts.join(`
1686
+
1687
+ `)}`);
1688
+ } else if (!output.context.includes(experimentalText)) {
1689
+ output.context.push(experimentalText);
1690
+ }
1691
+ },
1692
+ async "experimental.text.complete"(_input, output) {
1693
+ if (flags.experimental?.text_complete !== true)
1694
+ return;
1695
+ if (output.text.includes(experimentalText))
1696
+ return;
1697
+ output.text += `
1698
+ ${experimentalText}`;
1699
+ },
1700
+ async config(sdkCfg) {
1701
+ const cfg = sdkCfg;
1702
+ if (flags.agent_compilation) {
1703
+ const result = await compileAgents(cfg, [bundledAgentsDir, globalAgentsDir, projectAgentsDir], agentSettings);
1704
+ mcpRegistry = result.mcpRegistry;
1705
+ taskRoutingRegistry = result.taskRouting;
1706
+ }
1707
+ if (flags.command_compilation) {
1708
+ await compileCommands(cfg, [bundledCommandsDir, globalCommandsDir, projectCommandsDir]);
1709
+ }
1710
+ if (flags.mcp_compilation !== false) {
1711
+ mcpDefaults ??= await loadMcpDefaults();
1712
+ applyMcpDefaults(cfg, mcpDefaults);
1713
+ }
1714
+ await applyBuiltInAgentPolicy(cfg);
1715
+ if (flags.compiler?.permission_compilation === true) {
1716
+ applyPermissionDefaults(cfg);
1717
+ }
1718
+ },
1719
+ async "tool.execute.before"(input, output) {
1720
+ const agentInfo = sessionAgentMap.get(input.sessionID) ?? {};
1721
+ const agentName = agentInfo.agent;
1722
+ if (input.tool === "task") {
1723
+ const limit = flags.concurrency_limit;
1724
+ if (typeof limit === "number" && limit > 0 && input.sessionID) {
1725
+ const activeCount = supervision.getActiveChildCount(input.sessionID);
1726
+ if (activeCount >= limit) {
1727
+ await note("concurrency_blocked", {
1728
+ observation: true,
1729
+ sessionID: input.sessionID,
1730
+ activeCount,
1731
+ limit
1732
+ });
1733
+ throw new Error(`[opencode-multiagent] Concurrency limit reached: ${activeCount}/${limit} active child sessions. ` + "Wait for existing sessions to complete before dispatching new work.");
1734
+ }
1735
+ }
1736
+ const targetAgent = getTaskTarget(output.args ?? input.args ?? {});
1737
+ if (targetAgent === "reviewer") {
1738
+ await trackQaDispatch(input.sessionID, agentInfo);
1739
+ }
1740
+ if (flags.enforcement && agentName && targetAgent && taskRoutingRegistry && !isTaskRoutingAllowed(agentName, targetAgent, taskRoutingRegistry)) {
1741
+ telemetry.trackPermissionDenied(input.sessionID, {
1742
+ agent: agentName,
1743
+ model: agentInfo.model
1744
+ });
1745
+ await note("permission_denied", {
1746
+ observation: true,
1747
+ sessionID: input.sessionID,
1748
+ agent: agentName,
1749
+ model: agentInfo.model,
1750
+ tool: "task",
1751
+ target: targetAgent,
1752
+ reason: "task_routing_not_allowed",
1753
+ enforcement_layer: "plugin_hook"
1754
+ });
1755
+ throw new Error(`[opencode-multiagent] Agent ${agentName} is not allowed to delegate to ${targetAgent}`);
1756
+ }
1757
+ }
1758
+ if (input.tool === "webfetch" && typeof output.args?.url === "string" && output.args.url.startsWith("http://")) {
1759
+ output.args.url = output.args.url.replace("http://", "https://");
1760
+ }
1761
+ if (flags.enforcement && ["read", "edit"].includes(input.tool) && typeof output.args?.filePath === "string" && blocked(output.args.filePath)) {
1762
+ throw new Error("[opencode-multiagent] blocked sensitive path access");
1763
+ }
1764
+ if (flags.enforcement && input.tool === "bash" && typeof output.args?.command === "string" && tokenizedBashBlocked(output.args.command)) {
1765
+ throw new Error("[opencode-multiagent] blocked destructive bash command");
1766
+ }
1767
+ if (!flags.enforcement)
1768
+ return;
1769
+ if (isMcpTool(input.tool) && agentName && mcpRegistry?.has(agentName)) {
1770
+ const allowed = matchesMcpPermission(input.tool, mcpRegistry.get(agentName));
1771
+ if (!allowed) {
1772
+ telemetry.trackPermissionDenied(input.sessionID, {
1773
+ agent: agentName,
1774
+ model: agentInfo.model
1775
+ });
1776
+ await note("permission_denied", {
1777
+ observation: true,
1778
+ sessionID: input.sessionID,
1779
+ agent: agentName,
1780
+ model: agentInfo.model,
1781
+ tool: input.tool,
1782
+ reason: "mcp_tool_not_allowed",
1783
+ enforcement_layer: "plugin_hook"
1784
+ });
1785
+ throw new Error(`[opencode-multiagent] MCP tool ${input.tool} not allowed for agent ${agentName}`);
1786
+ }
1787
+ }
1788
+ },
1789
+ async "tool.definition"(input, output) {
1790
+ if (!flags.enforcement)
1791
+ return;
1792
+ if (input.toolID === "bash") {
1793
+ output.description += `
1794
+
1795
+ [Policy] Commands matching destructive patterns (rm -rf /, sudo, shutdown, reboot, git reset --hard, etc.) are blocked by opencode-multiagent.`;
1796
+ }
1797
+ if (input.toolID === "read") {
1798
+ output.description += `
1799
+
1800
+ [Policy] Reads targeting sensitive system paths (/etc, /root, /proc, /sys, /dev, /boot, .ssh) are blocked.`;
1801
+ }
1802
+ if (input.toolID === "edit") {
1803
+ output.description += `
1804
+
1805
+ [Policy] Edits targeting sensitive system paths (/etc, /root, /proc, /sys, /dev, /boot, .ssh) are blocked.`;
1806
+ }
1807
+ if (input.toolID === "webfetch") {
1808
+ output.description += `
1809
+
1810
+ [Policy] http:// URLs are automatically upgraded to https://.`;
1811
+ }
1812
+ },
1813
+ async "shell.env"(_input, output) {
1814
+ if (!flags.enforcement)
1815
+ return;
1816
+ Object.assign(output.env, {
1817
+ OPENCODE_MULTIAGENT: "1",
1818
+ OPENCODE_MULTIAGENT_MODE: pluginMode,
1819
+ OPENCODE_MULTIAGENT_PROFILE: flags.profile ?? "standard",
1820
+ OPENCODE_CONTROL_PLANE: "1"
1821
+ });
1822
+ },
1823
+ async "tool.execute.after"(input, output) {
1824
+ if (input.tool === "edit" && typeof input.args?.filePath === "string") {
1825
+ quality.trackEdit(input.sessionID, input.args.filePath);
1826
+ telemetry.trackEdit(input.sessionID);
1827
+ }
1828
+ if (input.tool === "bash" && typeof input.args?.command === "string") {
1829
+ quality.recordQualityEvidence(input.sessionID, input.args.command);
1830
+ }
1831
+ const agentInfo = sessionAgentMap.get(input.sessionID) ?? {};
1832
+ telemetry.trackToolCall(input.sessionID, { agent: agentInfo.agent, model: agentInfo.model });
1833
+ const isNonzeroExit = typeof output.metadata?.exit === "number" && output.metadata.exit !== 0;
1834
+ if (isNonzeroExit) {
1835
+ telemetry.trackToolError(input.sessionID, {
1836
+ agent: agentInfo.agent,
1837
+ model: agentInfo.model
1838
+ });
1839
+ supervision.recordToolError(input.sessionID);
1840
+ }
1841
+ if (!flags.observation)
1842
+ return;
1843
+ const lower = `${String(output.title ?? "")}
1844
+ ${String(output.output ?? "")}`.toLowerCase();
1845
+ const marks = flagged(lower);
1846
+ if (isNonzeroExit)
1847
+ marks.push("nonzero_exit");
1848
+ await note("tool_after", compact({
1849
+ observation: true,
1850
+ tool: input.tool,
1851
+ sessionID: input.sessionID,
1852
+ callID: input.callID,
1853
+ title: clip(output.title),
1854
+ args: input.args,
1855
+ suspicious: marks.length > 0,
1856
+ flags: marks,
1857
+ output: clip(output.output),
1858
+ ...isNonzeroExit ? { exit_code: output.metadata.exit, error_type: "nonzero_exit" } : {}
1859
+ }));
1860
+ },
1861
+ tool: createTaskTools(taskManager, taskManagerReady, correlation, quality, flags)
1862
+ };
1863
+ };
1864
+
1865
+ // src/opencode-multiagent/runtime.ts
1866
+ var readSettingsSection = (value) => {
1867
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1868
+ return {};
1869
+ }
1870
+ return value;
1871
+ };
1872
+ async function loadRuntimeSettings() {
1873
+ const [bundledDefaults, unifiedUserSettings] = await Promise.all([
1874
+ loadBundledDefaults().catch(() => ({})),
1875
+ readJSON(settingsPath, {})
1876
+ ]);
1877
+ const defaultAgentSettings = readBundledDefaultsSection(bundledDefaults, "agentSettings", {});
1878
+ const userFlags = readSettingsSection(unifiedUserSettings.flags);
1879
+ const userAgentSettings = readSettingsSection(unifiedUserSettings.agentSettings);
1880
+ const userProfiles = readSettingsSection(unifiedUserSettings.profiles);
1881
+ const profiles = merge(defaultProfiles, userProfiles);
1882
+ const profileName = typeof userFlags.profile === "string" ? userFlags.profile : defaultFlags.profile;
1883
+ const profile = profiles[profileName] ?? {};
1884
+ if (!(profileName in profiles)) {
1885
+ await note("config_warning", {
1886
+ observation: true,
1887
+ warning: "unknown_profile",
1888
+ profile: profileName
1889
+ });
1890
+ }
1891
+ const flags = merge(merge(defaultFlags, profile), userFlags);
1892
+ const agentSettings = merge(defaultAgentSettings, userAgentSettings);
1893
+ return { flags, agentSettings };
1894
+ }
1895
+
1896
+ // src/control-plane.ts
1897
+ var OpenCodeMultiAgentPlugin = async (context) => {
1898
+ const client = context.client;
1899
+ const projectRoot = context.worktree || context.directory;
1900
+ const { flags, agentSettings } = await loadRuntimeSettings();
1901
+ return createPluginHooks({
1902
+ client,
1903
+ flags,
1904
+ agentSettings,
1905
+ projectRoot
1906
+ });
1907
+ };
1908
+ var control_plane_default = OpenCodeMultiAgentPlugin;
1909
+
1910
+ // src/index.ts
1911
+ var plugin = control_plane_default;
1912
+ var src_default = plugin;
1913
+ export {
1914
+ src_default as default,
1915
+ plugin as OpenCodeMultiAgentPlugin
1916
+ };