opencode-multiagent 0.2.0 → 0.3.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +18 -0
- package/CONTRIBUTING.md +36 -0
- package/README.md +41 -165
- package/README.tr.md +84 -0
- package/RELEASE.md +68 -0
- package/agents/advisor.md +9 -6
- package/agents/auditor.md +8 -6
- package/agents/critic.md +19 -10
- package/agents/deep-worker.md +11 -7
- package/agents/devil.md +3 -1
- package/agents/executor.md +20 -19
- package/agents/heavy-worker.md +11 -7
- package/agents/lead.md +22 -30
- package/agents/librarian.md +6 -2
- package/agents/planner.md +18 -10
- package/agents/qa.md +9 -6
- package/agents/quick.md +12 -7
- package/agents/reviewer.md +9 -6
- package/agents/scout.md +9 -5
- package/agents/scribe.md +33 -28
- package/agents/strategist.md +10 -7
- package/agents/ui-heavy-worker.md +11 -7
- package/agents/ui-worker.md +12 -7
- package/agents/validator.md +8 -5
- package/agents/worker.md +12 -7
- package/commands/execute.md +1 -0
- package/commands/init-deep.md +1 -0
- package/commands/init.md +1 -0
- package/commands/inspect.md +1 -0
- package/commands/plan.md +1 -0
- package/commands/quality.md +1 -0
- package/commands/review.md +1 -0
- package/commands/status.md +1 -0
- package/defaults/opencode-multiagent.json +223 -0
- package/defaults/opencode-multiagent.schema.json +249 -0
- package/dist/control-plane.d.ts +4 -0
- package/dist/control-plane.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1583 -0
- package/dist/opencode-multiagent/compiler.d.ts +19 -0
- package/dist/opencode-multiagent/compiler.d.ts.map +1 -0
- package/dist/opencode-multiagent/constants.d.ts +116 -0
- package/dist/opencode-multiagent/constants.d.ts.map +1 -0
- package/dist/opencode-multiagent/defaults.d.ts +10 -0
- package/dist/opencode-multiagent/defaults.d.ts.map +1 -0
- package/dist/opencode-multiagent/file-lock.d.ts +15 -0
- package/dist/opencode-multiagent/file-lock.d.ts.map +1 -0
- package/dist/opencode-multiagent/hooks.d.ts +62 -0
- package/dist/opencode-multiagent/hooks.d.ts.map +1 -0
- package/dist/opencode-multiagent/log.d.ts +2 -0
- package/dist/opencode-multiagent/log.d.ts.map +1 -0
- package/dist/opencode-multiagent/markdown.d.ts +8 -0
- package/dist/opencode-multiagent/markdown.d.ts.map +1 -0
- package/dist/opencode-multiagent/mcp.d.ts +3 -0
- package/dist/opencode-multiagent/mcp.d.ts.map +1 -0
- package/dist/opencode-multiagent/policy.d.ts +5 -0
- package/dist/opencode-multiagent/policy.d.ts.map +1 -0
- package/dist/opencode-multiagent/quality.d.ts +14 -0
- package/dist/opencode-multiagent/quality.d.ts.map +1 -0
- package/dist/opencode-multiagent/runtime.d.ts +7 -0
- package/dist/opencode-multiagent/runtime.d.ts.map +1 -0
- package/dist/opencode-multiagent/session-tracker.d.ts +32 -0
- package/dist/opencode-multiagent/session-tracker.d.ts.map +1 -0
- package/dist/opencode-multiagent/skills.d.ts +17 -0
- package/dist/opencode-multiagent/skills.d.ts.map +1 -0
- package/dist/opencode-multiagent/supervision.d.ts +12 -0
- package/dist/opencode-multiagent/supervision.d.ts.map +1 -0
- package/dist/opencode-multiagent/task-manager.d.ts +48 -0
- package/dist/opencode-multiagent/task-manager.d.ts.map +1 -0
- package/dist/opencode-multiagent/telemetry.d.ts +26 -0
- package/dist/opencode-multiagent/telemetry.d.ts.map +1 -0
- package/dist/opencode-multiagent/tools.d.ts +56 -0
- package/dist/opencode-multiagent/tools.d.ts.map +1 -0
- package/dist/opencode-multiagent/types.d.ts +36 -0
- package/dist/opencode-multiagent/types.d.ts.map +1 -0
- package/dist/opencode-multiagent/utils.d.ts +9 -0
- package/dist/opencode-multiagent/utils.d.ts.map +1 -0
- package/docs/agents.md +260 -0
- package/docs/agents.tr.md +260 -0
- package/docs/configuration.md +255 -0
- package/docs/configuration.tr.md +255 -0
- package/docs/usage-guide.md +226 -0
- package/docs/usage-guide.tr.md +227 -0
- package/examples/opencode.with-overrides.json +1 -5
- package/package.json +23 -13
- package/skills/advanced-evaluation/SKILL.md +37 -21
- package/skills/advanced-evaluation/manifest.json +2 -13
- package/skills/cek-context-engineering/SKILL.md +159 -87
- package/skills/cek-context-engineering/manifest.json +1 -3
- package/skills/cek-prompt-engineering/SKILL.md +13 -10
- package/skills/cek-prompt-engineering/manifest.json +1 -3
- package/skills/cek-test-prompt/SKILL.md +38 -28
- package/skills/cek-test-prompt/manifest.json +1 -3
- package/skills/cek-thought-based-reasoning/SKILL.md +75 -21
- package/skills/cek-thought-based-reasoning/manifest.json +1 -3
- package/skills/context-degradation/SKILL.md +14 -13
- package/skills/context-degradation/manifest.json +1 -3
- package/skills/debate/SKILL.md +23 -78
- package/skills/debate/manifest.json +2 -12
- package/skills/design-first/manifest.json +2 -13
- package/skills/dispatching-parallel-agents/SKILL.md +14 -3
- package/skills/dispatching-parallel-agents/manifest.json +1 -4
- package/skills/drift-analysis/SKILL.md +50 -29
- package/skills/drift-analysis/manifest.json +2 -12
- package/skills/evaluation/manifest.json +2 -12
- package/skills/executing-plans/SKILL.md +15 -8
- package/skills/executing-plans/manifest.json +1 -3
- package/skills/handoff-protocols/manifest.json +2 -12
- package/skills/parallel-investigation/SKILL.md +25 -12
- package/skills/parallel-investigation/manifest.json +1 -4
- package/skills/reflexion-critique/SKILL.md +21 -10
- package/skills/reflexion-critique/manifest.json +1 -3
- package/skills/reflexion-reflect/SKILL.md +36 -34
- package/skills/reflexion-reflect/manifest.json +2 -10
- package/skills/root-cause-analysis/manifest.json +2 -13
- package/skills/sadd-judge-with-debate/SKILL.md +50 -26
- package/skills/sadd-judge-with-debate/manifest.json +1 -3
- package/skills/structured-code-review/manifest.json +2 -11
- package/skills/task-decomposition/manifest.json +2 -13
- package/skills/verification-before-completion/manifest.json +2 -15
- package/skills/verification-gates/SKILL.md +27 -19
- package/skills/verification-gates/manifest.json +2 -12
- package/defaults/agent-settings.json +0 -102
- package/defaults/agent-settings.schema.json +0 -25
- package/defaults/flags.json +0 -35
- package/defaults/flags.schema.json +0 -119
- package/defaults/mcp-defaults.json +0 -47
- package/defaults/mcp-defaults.schema.json +0 -38
- package/defaults/profiles.json +0 -53
- package/defaults/profiles.schema.json +0 -60
- package/defaults/team-profiles.json +0 -83
- package/src/control-plane.ts +0 -21
- package/src/index.ts +0 -8
- package/src/opencode-multiagent/compiler.ts +0 -168
- package/src/opencode-multiagent/constants.ts +0 -178
- package/src/opencode-multiagent/file-lock.ts +0 -90
- package/src/opencode-multiagent/hooks.ts +0 -599
- package/src/opencode-multiagent/log.ts +0 -12
- package/src/opencode-multiagent/mailbox.ts +0 -287
- package/src/opencode-multiagent/markdown.ts +0 -99
- package/src/opencode-multiagent/mcp.ts +0 -35
- package/src/opencode-multiagent/policy.ts +0 -67
- package/src/opencode-multiagent/quality.ts +0 -140
- package/src/opencode-multiagent/runtime.ts +0 -55
- package/src/opencode-multiagent/skills.ts +0 -144
- package/src/opencode-multiagent/supervision.ts +0 -156
- package/src/opencode-multiagent/task-manager.ts +0 -148
- package/src/opencode-multiagent/team-manager.ts +0 -219
- package/src/opencode-multiagent/team-tools.ts +0 -359
- package/src/opencode-multiagent/telemetry.ts +0 -124
- package/src/opencode-multiagent/utils.ts +0 -54
package/dist/index.js
ADDED
|
@@ -0,0 +1,1583 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/opencode-multiagent/hooks.ts
|
|
3
|
+
import { join as join4 } from "path";
|
|
4
|
+
|
|
5
|
+
// src/opencode-multiagent/constants.ts
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { dirname, join, resolve } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
var hasBundledAssets = (directory) => {
|
|
11
|
+
return ["package.json", "agents", "commands", "defaults", "skills"].every((entry) => {
|
|
12
|
+
return existsSync(join(directory, entry));
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
var detectPackageRoot = () => {
|
|
16
|
+
let current = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
while (true) {
|
|
18
|
+
if (hasBundledAssets(current)) {
|
|
19
|
+
return current;
|
|
20
|
+
}
|
|
21
|
+
const parent = resolve(current, "..");
|
|
22
|
+
if (parent === current) {
|
|
23
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
24
|
+
}
|
|
25
|
+
current = parent;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var pluginName = "opencode-multiagent";
|
|
29
|
+
var pluginMode = "stable";
|
|
30
|
+
var opencodeDir = join(homedir(), ".config", "opencode");
|
|
31
|
+
var pluginDir = join(opencodeDir, "plugins");
|
|
32
|
+
var globalAgentsDir = join(opencodeDir, "agents");
|
|
33
|
+
var globalCommandsDir = join(opencodeDir, "commands");
|
|
34
|
+
var logDirPath = join(opencodeDir, "logs");
|
|
35
|
+
var logFilePath = join(logDirPath, `${pluginName}.jsonl`);
|
|
36
|
+
var settingsPath = join(pluginDir, `${pluginName}.json`);
|
|
37
|
+
var packageRoot = detectPackageRoot();
|
|
38
|
+
var bundledDefaultsPath = join(packageRoot, "defaults", "opencode-multiagent.json");
|
|
39
|
+
var bundledAgentsDir = join(packageRoot, "agents");
|
|
40
|
+
var bundledCommandsDir = join(packageRoot, "commands");
|
|
41
|
+
var bundledSkillsDir = join(packageRoot, "skills");
|
|
42
|
+
var trackedEventTypes = new Set([
|
|
43
|
+
"permission.updated",
|
|
44
|
+
"permission.replied",
|
|
45
|
+
"session.created",
|
|
46
|
+
"session.status",
|
|
47
|
+
"session.diff",
|
|
48
|
+
"file.edited",
|
|
49
|
+
"session.deleted",
|
|
50
|
+
"session.idle",
|
|
51
|
+
"message.updated",
|
|
52
|
+
"message.part.updated",
|
|
53
|
+
"message.part.delta",
|
|
54
|
+
"command.executed"
|
|
55
|
+
]);
|
|
56
|
+
var supervisionEventTypes = new Set([
|
|
57
|
+
"session.created",
|
|
58
|
+
"message.updated",
|
|
59
|
+
"message.part.updated",
|
|
60
|
+
"message.part.delta",
|
|
61
|
+
"session.idle",
|
|
62
|
+
"session.deleted"
|
|
63
|
+
]);
|
|
64
|
+
var qualityEventTypes = new Set(["file.edited", "session.idle", "session.deleted"]);
|
|
65
|
+
var suspiciousTerms = ["blocked", "failed", "error", "permission", "refused"];
|
|
66
|
+
var qualitySignalRegex = /\b(test|tests|lint|typecheck|tsc|build|pytest|vitest|jest|cargo test|go test|ruff|mypy|eslint|biome|prettier)\b/i;
|
|
67
|
+
var mcpToolPrefixes = [
|
|
68
|
+
"exa_",
|
|
69
|
+
"context7_",
|
|
70
|
+
"gh_grep_",
|
|
71
|
+
"github_",
|
|
72
|
+
"code_index_",
|
|
73
|
+
"repo_git_"
|
|
74
|
+
];
|
|
75
|
+
var defaultProfiles = {
|
|
76
|
+
minimal: {
|
|
77
|
+
enforcement: true,
|
|
78
|
+
observation: false,
|
|
79
|
+
prompt_controls: false,
|
|
80
|
+
agent_compilation: true,
|
|
81
|
+
command_compilation: true,
|
|
82
|
+
mcp_compilation: true,
|
|
83
|
+
telemetry: false,
|
|
84
|
+
supervision: false,
|
|
85
|
+
quality_gate: false,
|
|
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
|
+
skill_sources: [
|
|
104
|
+
bundledSkillsDir,
|
|
105
|
+
join(homedir(), ".agents", "skills"),
|
|
106
|
+
join(homedir(), "skills")
|
|
107
|
+
],
|
|
108
|
+
skill_injection: false,
|
|
109
|
+
experimental: {
|
|
110
|
+
chat_system_transform: false,
|
|
111
|
+
chat_messages_transform: false,
|
|
112
|
+
session_compacting: false,
|
|
113
|
+
text_complete: false
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
strict: {
|
|
117
|
+
enforcement: true,
|
|
118
|
+
observation: true,
|
|
119
|
+
prompt_controls: true,
|
|
120
|
+
agent_compilation: true,
|
|
121
|
+
command_compilation: true,
|
|
122
|
+
mcp_compilation: true,
|
|
123
|
+
telemetry: true,
|
|
124
|
+
supervision: true,
|
|
125
|
+
quality_gate: true,
|
|
126
|
+
experimental: {
|
|
127
|
+
chat_system_transform: true,
|
|
128
|
+
chat_messages_transform: true,
|
|
129
|
+
session_compacting: true,
|
|
130
|
+
text_complete: true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var defaultFlags = {
|
|
135
|
+
profile: "standard",
|
|
136
|
+
enforcement: true,
|
|
137
|
+
observation: true,
|
|
138
|
+
prompt_controls: true,
|
|
139
|
+
agent_compilation: true,
|
|
140
|
+
command_compilation: true,
|
|
141
|
+
mcp_compilation: true,
|
|
142
|
+
telemetry: true,
|
|
143
|
+
supervision: true,
|
|
144
|
+
quality_gate: true,
|
|
145
|
+
skill_sources: [
|
|
146
|
+
bundledSkillsDir,
|
|
147
|
+
join(homedir(), ".agents", "skills"),
|
|
148
|
+
join(homedir(), "skills")
|
|
149
|
+
],
|
|
150
|
+
skill_injection: false,
|
|
151
|
+
compiler: {
|
|
152
|
+
permission_compilation: true
|
|
153
|
+
},
|
|
154
|
+
experimental: {
|
|
155
|
+
chat_system_transform: false,
|
|
156
|
+
chat_messages_transform: false,
|
|
157
|
+
session_compacting: false,
|
|
158
|
+
text_complete: false
|
|
159
|
+
},
|
|
160
|
+
supervision_config: {
|
|
161
|
+
idle_timeout_ms: 180000,
|
|
162
|
+
cooldown_ms: 300000
|
|
163
|
+
},
|
|
164
|
+
quality_config: {
|
|
165
|
+
reminder_idle_ms: 120000,
|
|
166
|
+
reminder_cooldown_ms: 300000
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var disabledNativeAgents = ["build", "plan", "general", "explore"];
|
|
170
|
+
var blockedPathPrefixRegex = /^(\/etc|\/root|\/boot|\/proc|\/sys|\/dev)(?:\/|$)/;
|
|
171
|
+
var blockedPathFragments = [
|
|
172
|
+
"/.ssh/",
|
|
173
|
+
"/credentials",
|
|
174
|
+
"/secrets",
|
|
175
|
+
"/.aws/",
|
|
176
|
+
"/.gcloud/",
|
|
177
|
+
"/.kube/"
|
|
178
|
+
];
|
|
179
|
+
var blockedPathSuffixes = ["/.ssh", "/credentials"];
|
|
180
|
+
var destructiveBashFragments = [
|
|
181
|
+
"git reset --" + "hard",
|
|
182
|
+
"git che" + "ckout --",
|
|
183
|
+
"r" + "m -rf /",
|
|
184
|
+
"r" + "m -rf ~",
|
|
185
|
+
"su" + "do ",
|
|
186
|
+
"shut" + "down",
|
|
187
|
+
"re" + "boot",
|
|
188
|
+
"mk" + "fs",
|
|
189
|
+
"dd i" + "f=",
|
|
190
|
+
"cu" + "rl | ba" + "sh",
|
|
191
|
+
"cu" + "rl | sh",
|
|
192
|
+
"wg" + "et | ba" + "sh",
|
|
193
|
+
"wg" + "et | sh",
|
|
194
|
+
"ev" + "al $(",
|
|
195
|
+
"> /de" + "v/",
|
|
196
|
+
":" + "(){",
|
|
197
|
+
"ch" + "mod 777",
|
|
198
|
+
"ki" + "ll -9 -1",
|
|
199
|
+
"no" + "hup "
|
|
200
|
+
];
|
|
201
|
+
var sensitiveMentions = [
|
|
202
|
+
"/etc",
|
|
203
|
+
"/root",
|
|
204
|
+
"/boot",
|
|
205
|
+
"/proc",
|
|
206
|
+
"/sys",
|
|
207
|
+
"/dev",
|
|
208
|
+
"/.ssh",
|
|
209
|
+
"~/.ssh"
|
|
210
|
+
];
|
|
211
|
+
var experimentalText = "[opencode-multiagent experimental] Experimental control-plane transforms are active for this session.";
|
|
212
|
+
|
|
213
|
+
// src/opencode-multiagent/log.ts
|
|
214
|
+
import { appendFile, mkdir } from "fs/promises";
|
|
215
|
+
var note = async (kind, payload) => {
|
|
216
|
+
try {
|
|
217
|
+
await mkdir(logDirPath, { recursive: true });
|
|
218
|
+
await appendFile(logFilePath, `${JSON.stringify({ ts: Date.now(), kind, ...payload })}
|
|
219
|
+
`);
|
|
220
|
+
} catch {}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// src/opencode-multiagent/markdown.ts
|
|
224
|
+
import { readFile, readdir } from "fs/promises";
|
|
225
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
226
|
+
var unquote = (value) => {
|
|
227
|
+
if (typeof value !== "string" || value.length < 2)
|
|
228
|
+
return value;
|
|
229
|
+
const first = value[0];
|
|
230
|
+
const last = value[value.length - 1];
|
|
231
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
232
|
+
return value.slice(1, -1);
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
};
|
|
236
|
+
var parseScalar = (value) => {
|
|
237
|
+
const trimmed = value.trim();
|
|
238
|
+
if (trimmed === "")
|
|
239
|
+
return "";
|
|
240
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
241
|
+
return unquote(trimmed);
|
|
242
|
+
}
|
|
243
|
+
if (trimmed === "true")
|
|
244
|
+
return true;
|
|
245
|
+
if (trimmed === "false")
|
|
246
|
+
return false;
|
|
247
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed))
|
|
248
|
+
return Number(trimmed);
|
|
249
|
+
return trimmed;
|
|
250
|
+
};
|
|
251
|
+
function parseFrontmatter(value) {
|
|
252
|
+
if (typeof value !== "string" || !value.startsWith("---")) {
|
|
253
|
+
return { data: {}, body: value };
|
|
254
|
+
}
|
|
255
|
+
const openingMatch = value.match(/^---\r?\n/);
|
|
256
|
+
if (!openingMatch)
|
|
257
|
+
return { data: {}, body: value };
|
|
258
|
+
const rest = value.slice(openingMatch[0].length);
|
|
259
|
+
const closingIndex = rest.search(/\r?\n---(?:\r?\n|$)/);
|
|
260
|
+
if (closingIndex < 0)
|
|
261
|
+
return { data: {}, body: value };
|
|
262
|
+
const rawFrontmatter = rest.slice(0, closingIndex);
|
|
263
|
+
const closingSlice = rest.slice(closingIndex);
|
|
264
|
+
const closingMatch = closingSlice.match(/^\r?\n---(?:\r?\n|$)/);
|
|
265
|
+
if (!closingMatch)
|
|
266
|
+
return { data: {}, body: value };
|
|
267
|
+
const body = closingSlice.slice(closingMatch[0].length);
|
|
268
|
+
const data = {};
|
|
269
|
+
const stack = [{ indent: -1, obj: data }];
|
|
270
|
+
for (const line of rawFrontmatter.split(/\r?\n/)) {
|
|
271
|
+
if (!line.trim())
|
|
272
|
+
continue;
|
|
273
|
+
const indent = line.match(/^ */)?.[0].length ?? 0;
|
|
274
|
+
const trimmed = line.trim();
|
|
275
|
+
const colonIndex = trimmed.indexOf(":");
|
|
276
|
+
if (colonIndex < 0)
|
|
277
|
+
continue;
|
|
278
|
+
const key = String(unquote(trimmed.slice(0, colonIndex).trim()));
|
|
279
|
+
const rawValue = trimmed.slice(colonIndex + 1).trim();
|
|
280
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
281
|
+
stack.pop();
|
|
282
|
+
}
|
|
283
|
+
const parent = stack[stack.length - 1].obj;
|
|
284
|
+
if (rawValue === "") {
|
|
285
|
+
parent[key] = {};
|
|
286
|
+
stack.push({ indent, obj: parent[key] });
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
parent[key] = parseScalar(rawValue);
|
|
290
|
+
}
|
|
291
|
+
return { data, body };
|
|
292
|
+
}
|
|
293
|
+
var dedupe = (values) => [
|
|
294
|
+
...new Set(values.filter(Boolean).map((value) => resolve2(value)))
|
|
295
|
+
];
|
|
296
|
+
async function loadMarkdownDefs(dirs) {
|
|
297
|
+
const defs = new Map;
|
|
298
|
+
for (const dir of dedupe(dirs)) {
|
|
299
|
+
const files = await readdir(dir).catch(() => []);
|
|
300
|
+
for (const file of files.filter((name) => name.endsWith(".md")).sort()) {
|
|
301
|
+
try {
|
|
302
|
+
const value = await readFile(join2(dir, file), "utf8");
|
|
303
|
+
const { data, body } = parseFrontmatter(value);
|
|
304
|
+
defs.set(file.slice(0, -3), { data, body, source: join2(dir, file) });
|
|
305
|
+
} catch {}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return defs;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/opencode-multiagent/utils.ts
|
|
312
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
313
|
+
var compact = (value) => Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
314
|
+
var clip = (value, size = 240) => {
|
|
315
|
+
if (typeof value !== "string")
|
|
316
|
+
return;
|
|
317
|
+
if (value.length <= size)
|
|
318
|
+
return value;
|
|
319
|
+
return `${value.slice(0, size)}...`;
|
|
320
|
+
};
|
|
321
|
+
var text = (parts) => Array.isArray(parts) ? parts.filter((part) => Boolean(part) && part?.type === "text" && typeof part?.text === "string").map((part) => part.text).join(`
|
|
322
|
+
`) : "";
|
|
323
|
+
var label = (value, keys = ["name", "id", "modelID", "providerID"]) => {
|
|
324
|
+
if (typeof value === "string")
|
|
325
|
+
return value;
|
|
326
|
+
if (!value || typeof value !== "object")
|
|
327
|
+
return;
|
|
328
|
+
const objectValue = value;
|
|
329
|
+
for (const key of keys) {
|
|
330
|
+
if (typeof objectValue[key] === "string")
|
|
331
|
+
return objectValue[key];
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
};
|
|
335
|
+
var own = (value, key) => Boolean(value) && Object.prototype.hasOwnProperty.call(value, key);
|
|
336
|
+
var clone = (value) => {
|
|
337
|
+
if (value === undefined)
|
|
338
|
+
return value;
|
|
339
|
+
return JSON.parse(JSON.stringify(value));
|
|
340
|
+
};
|
|
341
|
+
var readJSON = async (path, fallback) => {
|
|
342
|
+
try {
|
|
343
|
+
return JSON.parse(await readFile2(path, "utf8"));
|
|
344
|
+
} catch {
|
|
345
|
+
return clone(fallback);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var merge = (base, extra) => {
|
|
349
|
+
if (!extra || typeof extra !== "object" || Array.isArray(extra)) {
|
|
350
|
+
return clone(extra !== undefined ? extra : base);
|
|
351
|
+
}
|
|
352
|
+
const result = {
|
|
353
|
+
...base && typeof base === "object" && !Array.isArray(base) ? base : {}
|
|
354
|
+
};
|
|
355
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
356
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
357
|
+
result[key] = value;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
result[key] = merge(result[key], value);
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/opencode-multiagent/compiler.ts
|
|
366
|
+
var normalizeDefinitionData = (data) => {
|
|
367
|
+
const result = clone(data) ?? {};
|
|
368
|
+
delete result.variant;
|
|
369
|
+
return result;
|
|
370
|
+
};
|
|
371
|
+
var isMcpPermissionKey = (key) => mcpToolPrefixes.some((prefix) => key === prefix || key.startsWith(prefix));
|
|
372
|
+
var buildMcpPermissionRegistry = (permission = {}) => {
|
|
373
|
+
const allowed = [];
|
|
374
|
+
const denied = [];
|
|
375
|
+
for (const [key, value] of Object.entries(permission)) {
|
|
376
|
+
if (!isMcpPermissionKey(key))
|
|
377
|
+
continue;
|
|
378
|
+
if (value === "allow")
|
|
379
|
+
allowed.push(key);
|
|
380
|
+
if (value === "deny")
|
|
381
|
+
denied.push(key);
|
|
382
|
+
}
|
|
383
|
+
return { allowed, denied, fallback: permission["*"] === "allow" ? "allow" : "deny" };
|
|
384
|
+
};
|
|
385
|
+
async function compileAgents(cfg, dirs, agentSettings = {}) {
|
|
386
|
+
const defs = await loadMarkdownDefs(dirs);
|
|
387
|
+
if (!cfg.agent || typeof cfg.agent !== "object")
|
|
388
|
+
cfg.agent = {};
|
|
389
|
+
const mcpRegistry = new Map;
|
|
390
|
+
for (const [name, raw] of defs.entries()) {
|
|
391
|
+
const data = normalizeDefinitionData(raw.data);
|
|
392
|
+
if (!own(cfg.agent, name)) {
|
|
393
|
+
cfg.agent[name] = {};
|
|
394
|
+
} else if (!cfg.agent[name] || typeof cfg.agent[name] !== "object") {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const target = cfg.agent[name];
|
|
398
|
+
const explicitFields = new Set(Object.keys(target));
|
|
399
|
+
for (const [field, value] of Object.entries(data)) {
|
|
400
|
+
if (own(target, field))
|
|
401
|
+
continue;
|
|
402
|
+
target[field] = clone(value);
|
|
403
|
+
}
|
|
404
|
+
const overrides = agentSettings?.[name];
|
|
405
|
+
if (overrides && typeof overrides === "object") {
|
|
406
|
+
for (const [field, value] of Object.entries(overrides)) {
|
|
407
|
+
if (["model", "temperature", "steps"].includes(field) && !explicitFields.has(field)) {
|
|
408
|
+
target[field] = clone(value);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (!own(target, "prompt") && typeof raw.body === "string") {
|
|
413
|
+
target.prompt = raw.body.trim();
|
|
414
|
+
}
|
|
415
|
+
mcpRegistry.set(name, buildMcpPermissionRegistry(target.permission ?? {}));
|
|
416
|
+
}
|
|
417
|
+
return mcpRegistry;
|
|
418
|
+
}
|
|
419
|
+
async function compileCommands(cfg, dirs) {
|
|
420
|
+
const defs = await loadMarkdownDefs(dirs);
|
|
421
|
+
if (!cfg.command || typeof cfg.command !== "object")
|
|
422
|
+
cfg.command = {};
|
|
423
|
+
for (const [name, raw] of defs.entries()) {
|
|
424
|
+
const data = normalizeDefinitionData(raw.data);
|
|
425
|
+
if (!own(cfg.command, name)) {
|
|
426
|
+
cfg.command[name] = {};
|
|
427
|
+
} else if (!cfg.command[name] || typeof cfg.command[name] !== "object") {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const target = cfg.command[name];
|
|
431
|
+
for (const [field, value] of Object.entries(data)) {
|
|
432
|
+
if (own(target, field))
|
|
433
|
+
continue;
|
|
434
|
+
target[field] = clone(value);
|
|
435
|
+
}
|
|
436
|
+
if (!own(target, "template") && typeof raw.body === "string") {
|
|
437
|
+
target.template = raw.body.trim();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
var applyBuiltInAgentPolicy = async (cfg) => {
|
|
442
|
+
if (!cfg.agent || typeof cfg.agent !== "object")
|
|
443
|
+
cfg.agent = {};
|
|
444
|
+
for (const name of disabledNativeAgents) {
|
|
445
|
+
cfg.agent[name] = merge(cfg.agent[name], { disable: true });
|
|
446
|
+
}
|
|
447
|
+
const current = typeof cfg.default_agent === "string" ? cfg.default_agent : undefined;
|
|
448
|
+
const currentAgent = current ? cfg.agent?.[current] : undefined;
|
|
449
|
+
const preferred = cfg.agent.lead && cfg.agent.lead.disable !== true ? "lead" : cfg.agent.critic && cfg.agent.critic.disable !== true ? "critic" : undefined;
|
|
450
|
+
const invalidCurrent = !current || disabledNativeAgents.includes(current) || !currentAgent || currentAgent.disable === true || currentAgent.hidden === true || currentAgent.mode === "subagent" || currentAgent.mode === "sub" || preferred === "lead" && current !== "lead";
|
|
451
|
+
if (invalidCurrent && preferred) {
|
|
452
|
+
const previous = current;
|
|
453
|
+
cfg.default_agent = preferred;
|
|
454
|
+
await note("config_warning", compact({
|
|
455
|
+
observation: true,
|
|
456
|
+
warning: "default_agent_reset",
|
|
457
|
+
previous,
|
|
458
|
+
next: preferred
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var applyPermissionDefaults = (cfg) => {
|
|
463
|
+
if (!cfg.permission || typeof cfg.permission !== "object")
|
|
464
|
+
cfg.permission = {};
|
|
465
|
+
for (const [key, value] of Object.entries({
|
|
466
|
+
bash: "allow",
|
|
467
|
+
read: "allow",
|
|
468
|
+
edit: "allow",
|
|
469
|
+
glob: "allow",
|
|
470
|
+
grep: "allow",
|
|
471
|
+
list: "allow"
|
|
472
|
+
})) {
|
|
473
|
+
if (!own(cfg.permission, key)) {
|
|
474
|
+
cfg.permission[key] = value;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
var matchesMcpPermission = (tool, registry) => {
|
|
479
|
+
if (!registry)
|
|
480
|
+
return true;
|
|
481
|
+
for (const pattern of registry.denied ?? []) {
|
|
482
|
+
if (pattern.endsWith("*") && tool.startsWith(pattern.slice(0, -1)))
|
|
483
|
+
return false;
|
|
484
|
+
if (pattern === tool)
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
for (const pattern of registry.allowed ?? []) {
|
|
488
|
+
if (pattern.endsWith("*") && tool.startsWith(pattern.slice(0, -1)))
|
|
489
|
+
return true;
|
|
490
|
+
if (pattern === tool)
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
return registry.fallback === "allow";
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// src/opencode-multiagent/file-lock.ts
|
|
497
|
+
var cleanupIntervalMs = 5 * 60 * 1000;
|
|
498
|
+
var staleLockTtlMs = 30 * 60 * 1000;
|
|
499
|
+
var normalizeFilePath = (filePath) => typeof filePath === "string" ? filePath.replace(/\\/g, "/") : undefined;
|
|
500
|
+
var createFileLockController = () => {
|
|
501
|
+
const locks = new Map;
|
|
502
|
+
const sessionFiles = new Map;
|
|
503
|
+
const trackSessionFile = (sessionID, filePath) => {
|
|
504
|
+
if (!sessionFiles.has(sessionID))
|
|
505
|
+
sessionFiles.set(sessionID, new Set);
|
|
506
|
+
sessionFiles.get(sessionID)?.add(filePath);
|
|
507
|
+
};
|
|
508
|
+
const releaseAll = (sessionID) => {
|
|
509
|
+
const files = sessionFiles.get(sessionID);
|
|
510
|
+
if (!files)
|
|
511
|
+
return 0;
|
|
512
|
+
let count = 0;
|
|
513
|
+
for (const filePath of files) {
|
|
514
|
+
const existing = locks.get(filePath);
|
|
515
|
+
if (existing?.sessionID === sessionID) {
|
|
516
|
+
locks.delete(filePath);
|
|
517
|
+
count += 1;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
sessionFiles.delete(sessionID);
|
|
521
|
+
return count;
|
|
522
|
+
};
|
|
523
|
+
const cleanupStaleLocks = (now = Date.now()) => {
|
|
524
|
+
for (const [filePath, info] of locks.entries()) {
|
|
525
|
+
if (now - info.lastTouchedAt <= staleLockTtlMs)
|
|
526
|
+
continue;
|
|
527
|
+
locks.delete(filePath);
|
|
528
|
+
const files = sessionFiles.get(info.sessionID);
|
|
529
|
+
if (files) {
|
|
530
|
+
files.delete(filePath);
|
|
531
|
+
if (files.size === 0)
|
|
532
|
+
sessionFiles.delete(info.sessionID);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
const interval = setInterval(() => cleanupStaleLocks(), cleanupIntervalMs);
|
|
537
|
+
interval.unref?.();
|
|
538
|
+
return {
|
|
539
|
+
acquire(sessionID, filePath) {
|
|
540
|
+
const normalized = normalizeFilePath(filePath);
|
|
541
|
+
if (!sessionID || !normalized)
|
|
542
|
+
return { ok: true, filePath: normalized };
|
|
543
|
+
const existing = locks.get(normalized);
|
|
544
|
+
if (existing && existing.sessionID !== sessionID) {
|
|
545
|
+
return { ok: false, filePath: normalized, ownerSessionID: existing.sessionID };
|
|
546
|
+
}
|
|
547
|
+
locks.set(normalized, { sessionID, lastTouchedAt: Date.now() });
|
|
548
|
+
trackSessionFile(sessionID, normalized);
|
|
549
|
+
return { ok: true, filePath: normalized };
|
|
550
|
+
},
|
|
551
|
+
releaseAll,
|
|
552
|
+
cleanup() {
|
|
553
|
+
cleanupStaleLocks();
|
|
554
|
+
clearInterval(interval);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/opencode-multiagent/defaults.ts
|
|
560
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
561
|
+
var asRecord = (value) => {
|
|
562
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
563
|
+
return {};
|
|
564
|
+
}
|
|
565
|
+
return value;
|
|
566
|
+
};
|
|
567
|
+
var cachedDefaults;
|
|
568
|
+
var loadBundledDefaults = async () => {
|
|
569
|
+
if (cachedDefaults) {
|
|
570
|
+
return clone(cachedDefaults);
|
|
571
|
+
}
|
|
572
|
+
const parsed = JSON.parse(await readFile3(bundledDefaultsPath, "utf8"));
|
|
573
|
+
cachedDefaults = asRecord(parsed);
|
|
574
|
+
return clone(cachedDefaults);
|
|
575
|
+
};
|
|
576
|
+
var readBundledDefaultsSection = (defaults, section, fallback) => {
|
|
577
|
+
const value = defaults[section];
|
|
578
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
579
|
+
return clone(fallback);
|
|
580
|
+
}
|
|
581
|
+
return clone(value);
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// src/opencode-multiagent/mcp.ts
|
|
585
|
+
var cachedDefaults2;
|
|
586
|
+
async function loadMcpDefaults() {
|
|
587
|
+
if (cachedDefaults2)
|
|
588
|
+
return clone(cachedDefaults2);
|
|
589
|
+
try {
|
|
590
|
+
const defaults = await loadBundledDefaults();
|
|
591
|
+
cachedDefaults2 = readBundledDefaultsSection(defaults, "mcpDefaults", {});
|
|
592
|
+
} catch {
|
|
593
|
+
cachedDefaults2 = {};
|
|
594
|
+
await note("config_warning", {
|
|
595
|
+
observation: true,
|
|
596
|
+
warning: "mcp_defaults_unreadable",
|
|
597
|
+
path: bundledDefaultsPath
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
return clone(cachedDefaults2);
|
|
601
|
+
}
|
|
602
|
+
function applyMcpDefaults(cfg, defaults) {
|
|
603
|
+
if (!cfg.mcp || typeof cfg.mcp !== "object")
|
|
604
|
+
cfg.mcp = {};
|
|
605
|
+
const mcp = cfg.mcp;
|
|
606
|
+
for (const [name, definition] of Object.entries(defaults ?? {})) {
|
|
607
|
+
if (own(mcp, name))
|
|
608
|
+
continue;
|
|
609
|
+
mcp[name] = clone(definition);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/opencode-multiagent/policy.ts
|
|
614
|
+
var normalize = (value) => String(value ?? "").toLowerCase();
|
|
615
|
+
var hasBlockedEnvName = (value) => {
|
|
616
|
+
const name = value.split("/").pop() ?? "";
|
|
617
|
+
if (name === ".env.example")
|
|
618
|
+
return false;
|
|
619
|
+
return name.endsWith(".env") || name.includes(".env.");
|
|
620
|
+
};
|
|
621
|
+
var blocked = (value) => {
|
|
622
|
+
const normalized = normalize(value);
|
|
623
|
+
return blockedPathPrefixRegex.test(normalized) || blockedPathFragments.some((fragment) => normalized.includes(fragment)) || blockedPathSuffixes.some((suffix) => normalized.endsWith(suffix)) || hasBlockedEnvName(normalized);
|
|
624
|
+
};
|
|
625
|
+
var tokenizedBashBlocked = (command) => {
|
|
626
|
+
const normalized = normalize(command).replace(/\s+/g, " ").trim();
|
|
627
|
+
if (normalized.length === 0)
|
|
628
|
+
return false;
|
|
629
|
+
const forkBombPattern = /(?:^|[;&|])\s*(?::|[a-z_][a-z0-9_]*)\s*\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*(?::|[a-z_][a-z0-9_]*)\s*(?:$|[;&|])/;
|
|
630
|
+
if (forkBombPattern.test(normalized))
|
|
631
|
+
return true;
|
|
632
|
+
const segments = normalized.split(/&&|\||;/).map((segment) => segment.trim()).filter(Boolean);
|
|
633
|
+
if (segments.some((segment) => destructiveBashFragments.some((fragment) => segment.includes(fragment)))) {
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
for (let index = 0;index < segments.length - 1; index += 1) {
|
|
637
|
+
if (!/^(curl|wget)(?:\s|$)/.test(segments[index]))
|
|
638
|
+
continue;
|
|
639
|
+
if (/\b(?:bash|sh)\b/.test(segments[index + 1]))
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
return false;
|
|
643
|
+
};
|
|
644
|
+
var mentions = (value) => sensitiveMentions.some((fragment) => value.includes(fragment));
|
|
645
|
+
var flagged = (value) => suspiciousTerms.filter((term) => value.includes(term));
|
|
646
|
+
var risky = (value) => {
|
|
647
|
+
const lower = normalize(value);
|
|
648
|
+
return mentions(lower) || blocked(lower) || tokenizedBashBlocked(lower);
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// src/opencode-multiagent/session-tracker.ts
|
|
652
|
+
function createSessionTracker(config) {
|
|
653
|
+
const {
|
|
654
|
+
getActivityTime,
|
|
655
|
+
onRemove,
|
|
656
|
+
cleanupIntervalMs: cleanupIntervalMs2 = 5 * 60 * 1000,
|
|
657
|
+
staleTtlMs = 30 * 60 * 1000,
|
|
658
|
+
maxTracked = 200,
|
|
659
|
+
evictionFraction = 0.2,
|
|
660
|
+
enabled
|
|
661
|
+
} = config;
|
|
662
|
+
const entries = new Map;
|
|
663
|
+
const remove = (key) => {
|
|
664
|
+
entries.delete(key);
|
|
665
|
+
onRemove?.(key);
|
|
666
|
+
};
|
|
667
|
+
const cleanupStale = (now = Date.now()) => {
|
|
668
|
+
for (const [key, entry] of entries.entries()) {
|
|
669
|
+
if (now - getActivityTime(entry) > staleTtlMs)
|
|
670
|
+
remove(key);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
const enforceLimit = () => {
|
|
674
|
+
if (entries.size <= maxTracked)
|
|
675
|
+
return;
|
|
676
|
+
const toEvict = [...entries.entries()].sort((a, b) => getActivityTime(a[1]) - getActivityTime(b[1])).slice(0, Math.max(1, Math.ceil(entries.size * evictionFraction)));
|
|
677
|
+
for (const [key] of toEvict)
|
|
678
|
+
remove(key);
|
|
679
|
+
};
|
|
680
|
+
let interval = null;
|
|
681
|
+
if (enabled) {
|
|
682
|
+
interval = setInterval(() => cleanupStale(), cleanupIntervalMs2);
|
|
683
|
+
interval.unref?.();
|
|
684
|
+
}
|
|
685
|
+
const cleanup = () => {
|
|
686
|
+
if (!interval)
|
|
687
|
+
return;
|
|
688
|
+
clearInterval(interval);
|
|
689
|
+
interval = null;
|
|
690
|
+
};
|
|
691
|
+
return { entries, cleanupStale, enforceLimit, cleanup };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/opencode-multiagent/quality.ts
|
|
695
|
+
var createQualityController = ({
|
|
696
|
+
flags,
|
|
697
|
+
client
|
|
698
|
+
}) => {
|
|
699
|
+
const tracker = createSessionTracker({
|
|
700
|
+
getActivityTime: (entry) => entry.lastActivityAt,
|
|
701
|
+
enabled: Boolean(flags.quality_gate)
|
|
702
|
+
});
|
|
703
|
+
const getSessionState = (sessionID) => {
|
|
704
|
+
if (!tracker.entries.has(sessionID)) {
|
|
705
|
+
tracker.entries.set(sessionID, {
|
|
706
|
+
editedFiles: new Set,
|
|
707
|
+
lastEditAt: 0,
|
|
708
|
+
qualityRemindedAt: 0,
|
|
709
|
+
lastQualityEvidenceAt: 0,
|
|
710
|
+
lastActivityAt: Date.now()
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
return tracker.entries.get(sessionID);
|
|
714
|
+
};
|
|
715
|
+
const trackEdit = (sessionID, filePath) => {
|
|
716
|
+
if (!sessionID || !filePath)
|
|
717
|
+
return;
|
|
718
|
+
const state = getSessionState(sessionID);
|
|
719
|
+
state.editedFiles.add(filePath);
|
|
720
|
+
state.lastEditAt = Date.now();
|
|
721
|
+
state.lastActivityAt = Date.now();
|
|
722
|
+
tracker.enforceLimit();
|
|
723
|
+
};
|
|
724
|
+
const recordQualityEvidence = (sessionID, command) => {
|
|
725
|
+
if (!sessionID || typeof command !== "string")
|
|
726
|
+
return;
|
|
727
|
+
if (!qualitySignalRegex.test(command))
|
|
728
|
+
return;
|
|
729
|
+
const state = getSessionState(sessionID);
|
|
730
|
+
state.lastQualityEvidenceAt = Date.now();
|
|
731
|
+
state.lastActivityAt = Date.now();
|
|
732
|
+
};
|
|
733
|
+
const handleQualityEvent = async (event) => {
|
|
734
|
+
if (!flags.quality_gate || !qualityEventTypes.has(event?.type ?? ""))
|
|
735
|
+
return;
|
|
736
|
+
const props = event.properties ?? {};
|
|
737
|
+
if (event.type === "file.edited") {
|
|
738
|
+
const sessionID = typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.sessionID === "string" ? props.info.sessionID : undefined;
|
|
739
|
+
const file = typeof props.file === "string" ? props.file : typeof props.info?.file === "string" ? props.info.file : undefined;
|
|
740
|
+
trackEdit(sessionID, file);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (event.type === "session.idle") {
|
|
744
|
+
const sessionID = typeof props.sessionID === "string" ? props.sessionID : undefined;
|
|
745
|
+
if (!sessionID || !client?.session?.prompt)
|
|
746
|
+
return;
|
|
747
|
+
const state = tracker.entries.get(sessionID);
|
|
748
|
+
if (!state || state.editedFiles.size === 0)
|
|
749
|
+
return;
|
|
750
|
+
state.lastActivityAt = Date.now();
|
|
751
|
+
if (state.lastQualityEvidenceAt >= state.lastEditAt)
|
|
752
|
+
return;
|
|
753
|
+
const now = Date.now();
|
|
754
|
+
const idleMs = flags.quality_config?.reminder_idle_ms ?? 120000;
|
|
755
|
+
const cooldownMs = flags.quality_config?.reminder_cooldown_ms ?? 300000;
|
|
756
|
+
if (now - state.lastEditAt < idleMs || now - state.qualityRemindedAt < cooldownMs)
|
|
757
|
+
return;
|
|
758
|
+
state.qualityRemindedAt = now;
|
|
759
|
+
const files = [...state.editedFiles].slice(0, 6).join(", ");
|
|
760
|
+
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}` : "");
|
|
761
|
+
try {
|
|
762
|
+
await client.session.prompt({
|
|
763
|
+
path: { id: sessionID },
|
|
764
|
+
body: { parts: [{ type: "text", text: reminder }], noReply: true }
|
|
765
|
+
});
|
|
766
|
+
} catch {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
await note("quality_gate", {
|
|
770
|
+
event: "idle_reminder",
|
|
771
|
+
sessionID,
|
|
772
|
+
files: [...state.editedFiles].slice(0, 12)
|
|
773
|
+
});
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (event.type === "session.deleted") {
|
|
777
|
+
const sessionID = typeof props.info?.id === "string" ? props.info.id : typeof props.sessionID === "string" ? props.sessionID : undefined;
|
|
778
|
+
if (sessionID)
|
|
779
|
+
tracker.entries.delete(sessionID);
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
return {
|
|
783
|
+
handleQualityEvent,
|
|
784
|
+
recordQualityEvidence,
|
|
785
|
+
trackEdit,
|
|
786
|
+
cleanup: tracker.cleanup
|
|
787
|
+
};
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
// src/opencode-multiagent/supervision.ts
|
|
791
|
+
var createSupervisionController = ({
|
|
792
|
+
flags,
|
|
793
|
+
client
|
|
794
|
+
}) => {
|
|
795
|
+
const childMap = new Map;
|
|
796
|
+
const cleanupChildMap = (childID) => {
|
|
797
|
+
const info = tracker.entries.get(childID);
|
|
798
|
+
if (!info)
|
|
799
|
+
return;
|
|
800
|
+
const children = childMap.get(info.parentID);
|
|
801
|
+
if (!children)
|
|
802
|
+
return;
|
|
803
|
+
children.delete(childID);
|
|
804
|
+
if (children.size === 0)
|
|
805
|
+
childMap.delete(info.parentID);
|
|
806
|
+
};
|
|
807
|
+
const tracker = createSessionTracker({
|
|
808
|
+
getActivityTime: (entry) => entry.lastActivity,
|
|
809
|
+
maxTracked: 100,
|
|
810
|
+
onRemove: cleanupChildMap,
|
|
811
|
+
enabled: Boolean(flags.supervision)
|
|
812
|
+
});
|
|
813
|
+
const removeChild = (childID) => {
|
|
814
|
+
const info = tracker.entries.get(childID);
|
|
815
|
+
if (!info)
|
|
816
|
+
return false;
|
|
817
|
+
const children = childMap.get(info.parentID);
|
|
818
|
+
if (children) {
|
|
819
|
+
children.delete(childID);
|
|
820
|
+
if (children.size === 0)
|
|
821
|
+
childMap.delete(info.parentID);
|
|
822
|
+
}
|
|
823
|
+
tracker.entries.delete(childID);
|
|
824
|
+
return true;
|
|
825
|
+
};
|
|
826
|
+
const handleSupervision = async (event) => {
|
|
827
|
+
if (!flags.supervision || !supervisionEventTypes.has(event?.type ?? ""))
|
|
828
|
+
return;
|
|
829
|
+
const props = event.properties ?? {};
|
|
830
|
+
if (event.type === "session.created") {
|
|
831
|
+
const parentID = typeof props.info?.parentID === "string" ? props.info.parentID : undefined;
|
|
832
|
+
const childID = typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : undefined;
|
|
833
|
+
if (!parentID || !childID)
|
|
834
|
+
return;
|
|
835
|
+
removeChild(childID);
|
|
836
|
+
if (!childMap.has(parentID))
|
|
837
|
+
childMap.set(parentID, new Set);
|
|
838
|
+
childMap.get(parentID)?.add(childID);
|
|
839
|
+
tracker.entries.set(childID, {
|
|
840
|
+
parentID,
|
|
841
|
+
agentName: null,
|
|
842
|
+
lastActivity: Date.now(),
|
|
843
|
+
remindedAt: 0
|
|
844
|
+
});
|
|
845
|
+
tracker.enforceLimit();
|
|
846
|
+
await note("supervision", { event: "child_tracked", parentID, childID });
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (["message.updated", "message.part.updated", "message.part.delta"].includes(event.type ?? "")) {
|
|
850
|
+
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;
|
|
851
|
+
if (!sessionID || !tracker.entries.has(sessionID))
|
|
852
|
+
return;
|
|
853
|
+
const info = tracker.entries.get(sessionID);
|
|
854
|
+
info.lastActivity = Date.now();
|
|
855
|
+
if (event.type === "message.updated" && props.info?.role === "assistant" && typeof props.info?.agent === "string") {
|
|
856
|
+
info.agentName = props.info.agent;
|
|
857
|
+
}
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (event.type === "session.idle") {
|
|
861
|
+
const sessionID = typeof props.sessionID === "string" ? props.sessionID : undefined;
|
|
862
|
+
if (!sessionID || !tracker.entries.has(sessionID) || !client?.session?.prompt)
|
|
863
|
+
return;
|
|
864
|
+
const info = tracker.entries.get(sessionID);
|
|
865
|
+
const now = Date.now();
|
|
866
|
+
const idleTimeout = flags.supervision_config?.idle_timeout_ms ?? 180000;
|
|
867
|
+
const cooldown = flags.supervision_config?.cooldown_ms ?? 300000;
|
|
868
|
+
if (now - info.lastActivity < idleTimeout || now - info.remindedAt < cooldown)
|
|
869
|
+
return;
|
|
870
|
+
info.remindedAt = now;
|
|
871
|
+
const reminderText = `[opencode-multiagent supervision] Child session ${sessionID} ` + `(agent: ${info.agentName ?? "unknown"}) has been idle. Check status or resume if needed.`;
|
|
872
|
+
try {
|
|
873
|
+
await client.session.prompt({
|
|
874
|
+
path: { id: info.parentID },
|
|
875
|
+
body: { parts: [{ type: "text", text: reminderText }], noReply: true }
|
|
876
|
+
});
|
|
877
|
+
} catch {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
await note("supervision", {
|
|
881
|
+
event: "idle_reminder",
|
|
882
|
+
parentID: info.parentID,
|
|
883
|
+
childID: sessionID,
|
|
884
|
+
agentName: info.agentName
|
|
885
|
+
});
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (event.type === "session.deleted") {
|
|
889
|
+
const sessionID = typeof props.info?.id === "string" ? props.info.id : undefined;
|
|
890
|
+
if (!sessionID || !removeChild(sessionID))
|
|
891
|
+
return;
|
|
892
|
+
await note("supervision", { event: "child_removed", sessionID });
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
return { handleSupervision, cleanup: tracker.cleanup };
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
// src/opencode-multiagent/task-manager.ts
|
|
899
|
+
import { mkdir as mkdir2, readFile as readFile4, writeFile } from "fs/promises";
|
|
900
|
+
import { join as join3 } from "path";
|
|
901
|
+
var createTaskManager = (projectRoot) => {
|
|
902
|
+
let taskCounter = 0;
|
|
903
|
+
const generateTaskID = () => {
|
|
904
|
+
taskCounter += 1;
|
|
905
|
+
return `T-${Date.now()}-${taskCounter.toString().padStart(4, "0")}`;
|
|
906
|
+
};
|
|
907
|
+
const tasks = new Map;
|
|
908
|
+
const boardPath = projectRoot ? join3(projectRoot, ".opencode", "tasks", "taskboard.json") : undefined;
|
|
909
|
+
const persist = async () => {
|
|
910
|
+
if (!boardPath)
|
|
911
|
+
return;
|
|
912
|
+
try {
|
|
913
|
+
await mkdir2(join3(boardPath, ".."), { recursive: true });
|
|
914
|
+
await writeFile(boardPath, JSON.stringify([...tasks.values()], null, 2), "utf8");
|
|
915
|
+
} catch {}
|
|
916
|
+
};
|
|
917
|
+
const load = async () => {
|
|
918
|
+
if (!boardPath)
|
|
919
|
+
return;
|
|
920
|
+
try {
|
|
921
|
+
const raw = await readFile4(boardPath, "utf8");
|
|
922
|
+
const items = JSON.parse(raw);
|
|
923
|
+
for (const item of items)
|
|
924
|
+
tasks.set(item.id, item);
|
|
925
|
+
} catch {}
|
|
926
|
+
};
|
|
927
|
+
const create = (input) => {
|
|
928
|
+
const task = {
|
|
929
|
+
id: generateTaskID(),
|
|
930
|
+
title: input.title,
|
|
931
|
+
description: input.description,
|
|
932
|
+
status: "pending",
|
|
933
|
+
priority: input.priority ?? "medium",
|
|
934
|
+
assignedAgent: input.assignedAgent,
|
|
935
|
+
createdBy: input.createdBy,
|
|
936
|
+
dependencies: input.dependencies ?? [],
|
|
937
|
+
createdAt: Date.now(),
|
|
938
|
+
updatedAt: Date.now()
|
|
939
|
+
};
|
|
940
|
+
tasks.set(task.id, task);
|
|
941
|
+
persist();
|
|
942
|
+
return task;
|
|
943
|
+
};
|
|
944
|
+
const update = (taskID, input) => {
|
|
945
|
+
const task = tasks.get(taskID);
|
|
946
|
+
if (!task)
|
|
947
|
+
return { error: `Task ${taskID} not found` };
|
|
948
|
+
if (input.status !== undefined)
|
|
949
|
+
task.status = input.status;
|
|
950
|
+
if (input.result !== undefined)
|
|
951
|
+
task.result = input.result;
|
|
952
|
+
if (input.assignedAgent !== undefined)
|
|
953
|
+
task.assignedAgent = input.assignedAgent;
|
|
954
|
+
task.updatedAt = Date.now();
|
|
955
|
+
persist();
|
|
956
|
+
return task;
|
|
957
|
+
};
|
|
958
|
+
const list = (filter) => {
|
|
959
|
+
const all = [...tasks.values()];
|
|
960
|
+
if (!filter)
|
|
961
|
+
return all;
|
|
962
|
+
return all.filter((task) => {
|
|
963
|
+
if (filter.status && task.status !== filter.status)
|
|
964
|
+
return false;
|
|
965
|
+
if (filter.assignedAgent && task.assignedAgent !== filter.assignedAgent)
|
|
966
|
+
return false;
|
|
967
|
+
return true;
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
return { create, update, list, load };
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
// src/opencode-multiagent/telemetry.ts
|
|
974
|
+
var createTelemetryController = ({ flags }) => {
|
|
975
|
+
const tracker = createSessionTracker({
|
|
976
|
+
getActivityTime: (entry) => entry.lastActivityAt,
|
|
977
|
+
enabled: Boolean(flags.telemetry)
|
|
978
|
+
});
|
|
979
|
+
const ensureSession = (sessionID, extra = {}) => {
|
|
980
|
+
if (!sessionID)
|
|
981
|
+
return null;
|
|
982
|
+
const now = Date.now();
|
|
983
|
+
if (!tracker.entries.has(sessionID)) {
|
|
984
|
+
tracker.entries.set(sessionID, {
|
|
985
|
+
sessionID,
|
|
986
|
+
agent: undefined,
|
|
987
|
+
model: undefined,
|
|
988
|
+
startedAt: now,
|
|
989
|
+
lastActivityAt: now,
|
|
990
|
+
toolCalls: 0,
|
|
991
|
+
toolErrors: 0,
|
|
992
|
+
filesEdited: 0,
|
|
993
|
+
tasksDispatched: 0,
|
|
994
|
+
permissionDenied: 0,
|
|
995
|
+
...extra
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
const state = tracker.entries.get(sessionID);
|
|
999
|
+
Object.assign(state, extra);
|
|
1000
|
+
state.lastActivityAt = now;
|
|
1001
|
+
return state;
|
|
1002
|
+
};
|
|
1003
|
+
return {
|
|
1004
|
+
cleanup: tracker.cleanup,
|
|
1005
|
+
rememberSession(sessionID, extra = {}) {
|
|
1006
|
+
ensureSession(sessionID, extra);
|
|
1007
|
+
tracker.enforceLimit();
|
|
1008
|
+
},
|
|
1009
|
+
trackEdit(sessionID) {
|
|
1010
|
+
const state = ensureSession(sessionID);
|
|
1011
|
+
if (!state)
|
|
1012
|
+
return;
|
|
1013
|
+
state.filesEdited += 1;
|
|
1014
|
+
},
|
|
1015
|
+
trackToolCall(sessionID, extra = {}) {
|
|
1016
|
+
const state = ensureSession(sessionID, extra);
|
|
1017
|
+
if (!state)
|
|
1018
|
+
return;
|
|
1019
|
+
state.toolCalls += 1;
|
|
1020
|
+
},
|
|
1021
|
+
trackToolError(sessionID, extra = {}) {
|
|
1022
|
+
const state = ensureSession(sessionID, extra);
|
|
1023
|
+
if (!state)
|
|
1024
|
+
return;
|
|
1025
|
+
state.toolErrors += 1;
|
|
1026
|
+
},
|
|
1027
|
+
trackTaskDispatch(sessionID, extra = {}) {
|
|
1028
|
+
const state = ensureSession(sessionID, extra);
|
|
1029
|
+
if (!state)
|
|
1030
|
+
return;
|
|
1031
|
+
state.tasksDispatched += 1;
|
|
1032
|
+
},
|
|
1033
|
+
trackPermissionDenied(sessionID, extra = {}) {
|
|
1034
|
+
const state = ensureSession(sessionID, extra);
|
|
1035
|
+
if (!state)
|
|
1036
|
+
return;
|
|
1037
|
+
state.permissionDenied += 1;
|
|
1038
|
+
},
|
|
1039
|
+
async flushSession(sessionID, reason = "session_deleted") {
|
|
1040
|
+
const state = tracker.entries.get(sessionID);
|
|
1041
|
+
if (!state)
|
|
1042
|
+
return;
|
|
1043
|
+
tracker.entries.delete(sessionID);
|
|
1044
|
+
await note("session_metrics", {
|
|
1045
|
+
observation: true,
|
|
1046
|
+
sessionID,
|
|
1047
|
+
agent: state.agent,
|
|
1048
|
+
model: state.model,
|
|
1049
|
+
duration_ms: Math.max(0, Date.now() - state.startedAt),
|
|
1050
|
+
tool_calls: state.toolCalls,
|
|
1051
|
+
tool_errors: state.toolErrors,
|
|
1052
|
+
files_edited: state.filesEdited,
|
|
1053
|
+
tasks_dispatched: state.tasksDispatched,
|
|
1054
|
+
permission_denied: state.permissionDenied,
|
|
1055
|
+
reason
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// src/opencode-multiagent/tools.ts
|
|
1062
|
+
import { tool as pluginTool } from "@opencode-ai/plugin";
|
|
1063
|
+
var createTaskTools = (taskManager, taskManagerReady) => ({
|
|
1064
|
+
task_create: pluginTool({
|
|
1065
|
+
description: "Create a new task on the shared task board",
|
|
1066
|
+
args: {
|
|
1067
|
+
title: pluginTool.schema.string().describe("Short title for the task"),
|
|
1068
|
+
description: pluginTool.schema.string().describe("Detailed description of what needs to be done"),
|
|
1069
|
+
assignedAgent: pluginTool.schema.string().optional().describe("Name of the agent to assign this task to"),
|
|
1070
|
+
dependencies: pluginTool.schema.array(pluginTool.schema.string()).optional().describe("IDs of tasks this task depends on"),
|
|
1071
|
+
priority: pluginTool.schema.enum(["high", "medium", "low"]).optional().describe("Task priority: high, medium, or low")
|
|
1072
|
+
},
|
|
1073
|
+
async execute(args, ctx) {
|
|
1074
|
+
await taskManagerReady;
|
|
1075
|
+
return JSON.stringify(taskManager.create({ ...args, createdBy: ctx.agent }));
|
|
1076
|
+
}
|
|
1077
|
+
}),
|
|
1078
|
+
task_update: pluginTool({
|
|
1079
|
+
description: "Update a task's status or result on the shared task board",
|
|
1080
|
+
args: {
|
|
1081
|
+
taskID: pluginTool.schema.string().describe("ID of the task to update"),
|
|
1082
|
+
status: pluginTool.schema.enum(["pending", "claimed", "in_progress", "completed", "failed", "blocked"]).describe("New status for the task"),
|
|
1083
|
+
result: pluginTool.schema.string().optional().describe("Result summary or notes")
|
|
1084
|
+
},
|
|
1085
|
+
async execute(args) {
|
|
1086
|
+
await taskManagerReady;
|
|
1087
|
+
return JSON.stringify(taskManager.update(args.taskID, { status: args.status, result: args.result }));
|
|
1088
|
+
}
|
|
1089
|
+
}),
|
|
1090
|
+
task_list: pluginTool({
|
|
1091
|
+
description: "List tasks on the shared task board with optional filters",
|
|
1092
|
+
args: {
|
|
1093
|
+
status: pluginTool.schema.string().optional().describe("Filter by task status"),
|
|
1094
|
+
assignedAgent: pluginTool.schema.string().optional().describe("Filter by assigned agent name")
|
|
1095
|
+
},
|
|
1096
|
+
async execute(args) {
|
|
1097
|
+
await taskManagerReady;
|
|
1098
|
+
return JSON.stringify(taskManager.list(args));
|
|
1099
|
+
}
|
|
1100
|
+
})
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
// src/opencode-multiagent/hooks.ts
|
|
1104
|
+
var isMcpTool = (tool) => typeof tool === "string" && mcpToolPrefixes.some((prefix) => tool.startsWith(prefix));
|
|
1105
|
+
var qaWarningThreshold = 3;
|
|
1106
|
+
var qaWarningCooldownMs = 5 * 60 * 1000;
|
|
1107
|
+
var getTaskTarget = (args = {}) => {
|
|
1108
|
+
for (const key of ["subagent_type", "agent", "subagent", "type", "name"]) {
|
|
1109
|
+
if (typeof args?.[key] === "string")
|
|
1110
|
+
return args[key];
|
|
1111
|
+
}
|
|
1112
|
+
return;
|
|
1113
|
+
};
|
|
1114
|
+
var createPluginHooks = ({
|
|
1115
|
+
client,
|
|
1116
|
+
flags,
|
|
1117
|
+
agentSettings,
|
|
1118
|
+
projectRoot
|
|
1119
|
+
}) => {
|
|
1120
|
+
const projectAgentsDir = projectRoot ? join4(projectRoot, ".opencode", "agents") : undefined;
|
|
1121
|
+
const projectCommandsDir = projectRoot ? join4(projectRoot, ".opencode", "commands") : undefined;
|
|
1122
|
+
const { handleSupervision, cleanup } = createSupervisionController({ flags, client });
|
|
1123
|
+
const quality = createQualityController({ flags, client });
|
|
1124
|
+
const telemetry = createTelemetryController({ flags });
|
|
1125
|
+
const fileLocks = createFileLockController();
|
|
1126
|
+
const sessionAgentMap = new Map;
|
|
1127
|
+
const qaDispatchState = new Map;
|
|
1128
|
+
let mcpDefaults = null;
|
|
1129
|
+
let mcpRegistry = null;
|
|
1130
|
+
const taskManager = createTaskManager(projectRoot);
|
|
1131
|
+
const taskManagerReady = taskManager.load();
|
|
1132
|
+
const childSessionInfo = new Map;
|
|
1133
|
+
const rememberSession = (sessionID, extra = {}) => {
|
|
1134
|
+
if (!sessionID)
|
|
1135
|
+
return;
|
|
1136
|
+
const current = sessionAgentMap.get(sessionID) ?? {};
|
|
1137
|
+
const next = { ...current, ...extra };
|
|
1138
|
+
sessionAgentMap.set(sessionID, next);
|
|
1139
|
+
telemetry.rememberSession(sessionID, next);
|
|
1140
|
+
};
|
|
1141
|
+
const trackQaDispatch = async (sessionID, agentInfo = {}) => {
|
|
1142
|
+
if (!sessionID)
|
|
1143
|
+
return;
|
|
1144
|
+
const state = qaDispatchState.get(sessionID) ?? { count: 0, remindedAt: 0 };
|
|
1145
|
+
state.count += 1;
|
|
1146
|
+
qaDispatchState.set(sessionID, state);
|
|
1147
|
+
if (state.count < qaWarningThreshold || !client?.session?.prompt)
|
|
1148
|
+
return;
|
|
1149
|
+
const now = Date.now();
|
|
1150
|
+
if (now - state.remindedAt < qaWarningCooldownMs)
|
|
1151
|
+
return;
|
|
1152
|
+
state.remindedAt = now;
|
|
1153
|
+
try {
|
|
1154
|
+
await client.session.prompt({
|
|
1155
|
+
path: { id: sessionID },
|
|
1156
|
+
body: {
|
|
1157
|
+
noReply: true,
|
|
1158
|
+
parts: [
|
|
1159
|
+
{
|
|
1160
|
+
type: "text",
|
|
1161
|
+
text: `[opencode-multiagent qa] This session has dispatched QA ${state.count} times. ` + "Reassess the brief, consider planner repair, or narrow the defect before sending QA again."
|
|
1162
|
+
}
|
|
1163
|
+
]
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
} catch {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
await note("qa_counter_warning", {
|
|
1170
|
+
observation: true,
|
|
1171
|
+
sessionID,
|
|
1172
|
+
agent: agentInfo.agent,
|
|
1173
|
+
model: agentInfo.model,
|
|
1174
|
+
qa_dispatch_count: state.count
|
|
1175
|
+
});
|
|
1176
|
+
};
|
|
1177
|
+
const notifyParentOnChildCompletion = async (childSessionID, parentSessionID, agentName) => {
|
|
1178
|
+
const agentLabel = agentName ? ` (agent: ${agentName})` : "";
|
|
1179
|
+
const text2 = `Child session ${childSessionID}${agentLabel} has completed. ` + "Review its output and proceed with the next task.";
|
|
1180
|
+
try {
|
|
1181
|
+
if (client.session?.prompt) {
|
|
1182
|
+
await client.session.prompt({
|
|
1183
|
+
path: { id: parentSessionID },
|
|
1184
|
+
body: { parts: [{ type: "text", text: text2 }], noReply: false }
|
|
1185
|
+
});
|
|
1186
|
+
} else if (client.session?.promptAsync) {
|
|
1187
|
+
await client.session.promptAsync({
|
|
1188
|
+
sessionID: parentSessionID,
|
|
1189
|
+
noReply: false,
|
|
1190
|
+
parts: [{ type: "text", text: text2 }]
|
|
1191
|
+
});
|
|
1192
|
+
} else {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
await note("child_completion_notify", {
|
|
1196
|
+
observation: true,
|
|
1197
|
+
childSessionID,
|
|
1198
|
+
parentSessionID,
|
|
1199
|
+
agentName
|
|
1200
|
+
});
|
|
1201
|
+
} catch {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
return {
|
|
1206
|
+
cleanup() {
|
|
1207
|
+
cleanup?.();
|
|
1208
|
+
quality.cleanup?.();
|
|
1209
|
+
telemetry.cleanup?.();
|
|
1210
|
+
fileLocks.cleanup?.();
|
|
1211
|
+
childSessionInfo.clear();
|
|
1212
|
+
},
|
|
1213
|
+
async event(input) {
|
|
1214
|
+
const type = input.event?.type;
|
|
1215
|
+
const props = input.event?.properties ?? {};
|
|
1216
|
+
if (flags.observation && trackedEventTypes.has(type)) {
|
|
1217
|
+
await note("event", compact({
|
|
1218
|
+
event: type,
|
|
1219
|
+
data: compact({
|
|
1220
|
+
sessionID: typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.sessionID === "string" ? props.info.sessionID : undefined,
|
|
1221
|
+
messageID: typeof props.messageID === "string" ? props.messageID : undefined,
|
|
1222
|
+
partID: typeof props.partID === "string" ? props.partID : undefined,
|
|
1223
|
+
permissionID: typeof props.permissionID === "string" ? props.permissionID : undefined,
|
|
1224
|
+
response: typeof props.response === "string" ? props.response : undefined,
|
|
1225
|
+
file: typeof props.file === "string" ? clip(props.file) : undefined,
|
|
1226
|
+
error: typeof props.error?.message === "string" ? clip(props.error.message) : undefined
|
|
1227
|
+
})
|
|
1228
|
+
}));
|
|
1229
|
+
}
|
|
1230
|
+
if (type === "session.created") {
|
|
1231
|
+
const parentID = typeof props.info?.parentID === "string" ? props.info.parentID : undefined;
|
|
1232
|
+
const childID = typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : undefined;
|
|
1233
|
+
if (parentID && childID) {
|
|
1234
|
+
const parent = sessionAgentMap.get(parentID) ?? {};
|
|
1235
|
+
telemetry.trackTaskDispatch(parentID, { agent: parent.agent, model: parent.model });
|
|
1236
|
+
await note("task_dispatch", {
|
|
1237
|
+
observation: true,
|
|
1238
|
+
parent_sessionID: parentID,
|
|
1239
|
+
parent_agent: parent.agent,
|
|
1240
|
+
child_sessionID: childID
|
|
1241
|
+
});
|
|
1242
|
+
childSessionInfo.set(childID, { parentID, agentName: null });
|
|
1243
|
+
}
|
|
1244
|
+
} else if (type === "message.updated") {
|
|
1245
|
+
const sessionID = typeof props.info?.sessionID === "string" ? props.info.sessionID : undefined;
|
|
1246
|
+
const agent = typeof props.info?.agent === "string" ? props.info.agent : undefined;
|
|
1247
|
+
if (sessionID && agent) {
|
|
1248
|
+
rememberSession(sessionID, { agent });
|
|
1249
|
+
const childInfo = childSessionInfo.get(sessionID);
|
|
1250
|
+
if (childInfo)
|
|
1251
|
+
childInfo.agentName = agent;
|
|
1252
|
+
}
|
|
1253
|
+
} else if (type === "session.deleted") {
|
|
1254
|
+
const sessionID = typeof props.info?.id === "string" ? props.info.id : typeof props.sessionID === "string" ? props.sessionID : undefined;
|
|
1255
|
+
if (sessionID) {
|
|
1256
|
+
const childInfo = childSessionInfo.get(sessionID);
|
|
1257
|
+
sessionAgentMap.delete(sessionID);
|
|
1258
|
+
qaDispatchState.delete(sessionID);
|
|
1259
|
+
fileLocks.releaseAll(sessionID);
|
|
1260
|
+
await telemetry.flushSession(sessionID);
|
|
1261
|
+
if (childInfo?.parentID) {
|
|
1262
|
+
await notifyParentOnChildCompletion(sessionID, childInfo.parentID, childInfo.agentName);
|
|
1263
|
+
}
|
|
1264
|
+
childSessionInfo.delete(sessionID);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
await handleSupervision(input.event);
|
|
1268
|
+
await quality.handleQualityEvent(input.event);
|
|
1269
|
+
},
|
|
1270
|
+
async "permission.ask"(input, output) {
|
|
1271
|
+
if (!flags.enforcement)
|
|
1272
|
+
return;
|
|
1273
|
+
const patternValue = Array.isArray(input.pattern) ? input.pattern.join(" ") : String(input.pattern ?? "");
|
|
1274
|
+
if (["read", "edit", "external_directory"].includes(input.type) && blocked(patternValue)) {
|
|
1275
|
+
output.status = "deny";
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (input.type === "bash" && tokenizedBashBlocked(patternValue)) {
|
|
1279
|
+
output.status = "deny";
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
async "chat.params"(input, output) {
|
|
1283
|
+
rememberSession(input.sessionID, {
|
|
1284
|
+
agent: label(input.agent),
|
|
1285
|
+
model: label(input.model, ["id", "name", "modelID"])
|
|
1286
|
+
});
|
|
1287
|
+
if (!flags.observation)
|
|
1288
|
+
return;
|
|
1289
|
+
const marks = [];
|
|
1290
|
+
if (typeof output.temperature === "number" && output.temperature > 1)
|
|
1291
|
+
marks.push("high_temperature");
|
|
1292
|
+
if (typeof output.topK === "number" && output.topK > 0)
|
|
1293
|
+
marks.push("nonzero_top_k");
|
|
1294
|
+
if (Object.keys(output.options ?? {}).length > 0)
|
|
1295
|
+
marks.push("non_empty_options");
|
|
1296
|
+
await note("chat_params", compact({
|
|
1297
|
+
observation: true,
|
|
1298
|
+
sessionID: input.sessionID,
|
|
1299
|
+
agent: label(input.agent),
|
|
1300
|
+
provider: compact({
|
|
1301
|
+
source: input.provider?.source,
|
|
1302
|
+
id: label(input.provider?.info, ["id", "name", "providerID"])
|
|
1303
|
+
}),
|
|
1304
|
+
model: compact({
|
|
1305
|
+
id: label(input.model, ["id", "name", "modelID"]),
|
|
1306
|
+
providerID: label(input.model, ["providerID"])
|
|
1307
|
+
}),
|
|
1308
|
+
params: compact({
|
|
1309
|
+
temperature: output.temperature,
|
|
1310
|
+
topP: output.topP,
|
|
1311
|
+
topK: output.topK,
|
|
1312
|
+
optionKeys: Object.keys(output.options ?? {})
|
|
1313
|
+
}),
|
|
1314
|
+
heuristic: marks.length > 0 ? marks : undefined
|
|
1315
|
+
}));
|
|
1316
|
+
},
|
|
1317
|
+
async "chat.headers"(input, output) {
|
|
1318
|
+
if (!flags.prompt_controls)
|
|
1319
|
+
return;
|
|
1320
|
+
Object.assign(output.headers, {
|
|
1321
|
+
"x-opencode-multiagent": "1",
|
|
1322
|
+
"x-opencode-multiagent-profile": flags.profile ?? "standard",
|
|
1323
|
+
"x-opencode-agent": label(input.agent) ?? "unknown"
|
|
1324
|
+
});
|
|
1325
|
+
},
|
|
1326
|
+
async "chat.message"(input, output) {
|
|
1327
|
+
if (!flags.prompt_controls || !risky(text(output.parts)))
|
|
1328
|
+
return;
|
|
1329
|
+
await note("chat_risk", compact({
|
|
1330
|
+
observation: true,
|
|
1331
|
+
sessionID: input.sessionID,
|
|
1332
|
+
agent: label(input.agent),
|
|
1333
|
+
text: clip(text(output.parts))
|
|
1334
|
+
}));
|
|
1335
|
+
},
|
|
1336
|
+
async "command.execute.before"(input) {
|
|
1337
|
+
if (!flags.prompt_controls || !risky(input.arguments))
|
|
1338
|
+
return;
|
|
1339
|
+
await note("command_risk", compact({
|
|
1340
|
+
observation: true,
|
|
1341
|
+
sessionID: input.sessionID,
|
|
1342
|
+
command: input.command,
|
|
1343
|
+
arguments: clip(input.arguments)
|
|
1344
|
+
}));
|
|
1345
|
+
},
|
|
1346
|
+
async "experimental.chat.system.transform"(_input, output) {
|
|
1347
|
+
if (flags.experimental?.chat_system_transform !== true)
|
|
1348
|
+
return;
|
|
1349
|
+
if (output.system.includes(experimentalText))
|
|
1350
|
+
return;
|
|
1351
|
+
output.system.push(experimentalText);
|
|
1352
|
+
},
|
|
1353
|
+
async "experimental.chat.messages.transform"(_input, output) {
|
|
1354
|
+
if (flags.experimental?.chat_messages_transform !== true)
|
|
1355
|
+
return;
|
|
1356
|
+
const last = [...output.messages].reverse().find((message) => message.info?.role === "user");
|
|
1357
|
+
if (!last)
|
|
1358
|
+
return;
|
|
1359
|
+
if (text(last.parts).includes(experimentalText))
|
|
1360
|
+
return;
|
|
1361
|
+
last.parts.push({ type: "text", text: experimentalText });
|
|
1362
|
+
},
|
|
1363
|
+
async "experimental.session.compacting"(_input, output) {
|
|
1364
|
+
if (flags.experimental?.session_compacting !== true)
|
|
1365
|
+
return;
|
|
1366
|
+
if (output.context.includes(experimentalText))
|
|
1367
|
+
return;
|
|
1368
|
+
output.context.push(experimentalText);
|
|
1369
|
+
},
|
|
1370
|
+
async "experimental.text.complete"(_input, output) {
|
|
1371
|
+
if (flags.experimental?.text_complete !== true)
|
|
1372
|
+
return;
|
|
1373
|
+
if (output.text.includes(experimentalText))
|
|
1374
|
+
return;
|
|
1375
|
+
output.text += `
|
|
1376
|
+
${experimentalText}`;
|
|
1377
|
+
},
|
|
1378
|
+
async config(sdkCfg) {
|
|
1379
|
+
const cfg = sdkCfg;
|
|
1380
|
+
if (flags.agent_compilation) {
|
|
1381
|
+
mcpRegistry = await compileAgents(cfg, [bundledAgentsDir, globalAgentsDir, projectAgentsDir], agentSettings);
|
|
1382
|
+
}
|
|
1383
|
+
if (flags.command_compilation) {
|
|
1384
|
+
await compileCommands(cfg, [bundledCommandsDir, globalCommandsDir, projectCommandsDir]);
|
|
1385
|
+
}
|
|
1386
|
+
if (flags.mcp_compilation !== false) {
|
|
1387
|
+
mcpDefaults ??= await loadMcpDefaults();
|
|
1388
|
+
applyMcpDefaults(cfg, mcpDefaults);
|
|
1389
|
+
}
|
|
1390
|
+
await applyBuiltInAgentPolicy(cfg);
|
|
1391
|
+
if (flags.compiler?.permission_compilation === true) {
|
|
1392
|
+
applyPermissionDefaults(cfg);
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
async "tool.execute.before"(input, output) {
|
|
1396
|
+
const agentInfo = sessionAgentMap.get(input.sessionID) ?? {};
|
|
1397
|
+
const agentName = agentInfo.agent;
|
|
1398
|
+
if (input.tool === "task") {
|
|
1399
|
+
const targetAgent = getTaskTarget(output.args ?? input.args ?? {});
|
|
1400
|
+
if (targetAgent === "qa") {
|
|
1401
|
+
await trackQaDispatch(input.sessionID, agentInfo);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (input.tool === "webfetch" && typeof output.args?.url === "string" && output.args.url.startsWith("http://")) {
|
|
1405
|
+
output.args.url = output.args.url.replace("http://", "https://");
|
|
1406
|
+
}
|
|
1407
|
+
if (flags.enforcement && ["read", "edit"].includes(input.tool) && typeof output.args?.filePath === "string" && blocked(output.args.filePath)) {
|
|
1408
|
+
throw new Error("[opencode-multiagent] blocked sensitive path access");
|
|
1409
|
+
}
|
|
1410
|
+
if (flags.enforcement && input.tool === "bash" && typeof output.args?.command === "string" && tokenizedBashBlocked(output.args.command)) {
|
|
1411
|
+
throw new Error("[opencode-multiagent] blocked destructive bash command");
|
|
1412
|
+
}
|
|
1413
|
+
if (input.tool === "edit" && typeof output.args?.filePath === "string") {
|
|
1414
|
+
const lock = fileLocks.acquire(input.sessionID, output.args.filePath);
|
|
1415
|
+
if (!lock.ok) {
|
|
1416
|
+
telemetry.trackPermissionDenied(input.sessionID, {
|
|
1417
|
+
agent: agentName,
|
|
1418
|
+
model: agentInfo.model
|
|
1419
|
+
});
|
|
1420
|
+
await note("permission_denied", {
|
|
1421
|
+
observation: true,
|
|
1422
|
+
sessionID: input.sessionID,
|
|
1423
|
+
agent: agentName,
|
|
1424
|
+
model: agentInfo.model,
|
|
1425
|
+
tool: input.tool,
|
|
1426
|
+
file: lock.filePath,
|
|
1427
|
+
reason: "file_locked_by_other_session",
|
|
1428
|
+
owner_sessionID: lock.ownerSessionID,
|
|
1429
|
+
enforcement_layer: "plugin_hook"
|
|
1430
|
+
});
|
|
1431
|
+
throw new Error(`[opencode-multiagent] File lock conflict for ${lock.filePath}; active session ${lock.ownerSessionID}`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (!flags.enforcement)
|
|
1435
|
+
return;
|
|
1436
|
+
if (isMcpTool(input.tool) && agentName && mcpRegistry?.has(agentName)) {
|
|
1437
|
+
const allowed = matchesMcpPermission(input.tool, mcpRegistry.get(agentName));
|
|
1438
|
+
if (!allowed) {
|
|
1439
|
+
telemetry.trackPermissionDenied(input.sessionID, {
|
|
1440
|
+
agent: agentName,
|
|
1441
|
+
model: agentInfo.model
|
|
1442
|
+
});
|
|
1443
|
+
await note("permission_denied", {
|
|
1444
|
+
observation: true,
|
|
1445
|
+
sessionID: input.sessionID,
|
|
1446
|
+
agent: agentName,
|
|
1447
|
+
model: agentInfo.model,
|
|
1448
|
+
tool: input.tool,
|
|
1449
|
+
reason: "mcp_tool_not_allowed",
|
|
1450
|
+
enforcement_layer: "plugin_hook"
|
|
1451
|
+
});
|
|
1452
|
+
throw new Error(`[opencode-multiagent] MCP tool ${input.tool} not allowed for agent ${agentName}`);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
},
|
|
1456
|
+
async "tool.definition"(input, output) {
|
|
1457
|
+
if (!flags.enforcement)
|
|
1458
|
+
return;
|
|
1459
|
+
if (input.toolID === "bash") {
|
|
1460
|
+
output.description += `
|
|
1461
|
+
|
|
1462
|
+
[Policy] Commands matching destructive patterns (rm -rf /, sudo, shutdown, reboot, git reset --hard, etc.) are blocked by opencode-multiagent.`;
|
|
1463
|
+
}
|
|
1464
|
+
if (input.toolID === "read") {
|
|
1465
|
+
output.description += `
|
|
1466
|
+
|
|
1467
|
+
[Policy] Reads targeting sensitive system paths (/etc, /root, /proc, /sys, /dev, /boot, .ssh) are blocked.`;
|
|
1468
|
+
}
|
|
1469
|
+
if (input.toolID === "edit") {
|
|
1470
|
+
output.description += `
|
|
1471
|
+
|
|
1472
|
+
[Policy] Edits targeting sensitive system paths (/etc, /root, /proc, /sys, /dev, /boot, .ssh) are blocked.`;
|
|
1473
|
+
}
|
|
1474
|
+
if (input.toolID === "webfetch") {
|
|
1475
|
+
output.description += `
|
|
1476
|
+
|
|
1477
|
+
[Policy] http:// URLs are automatically upgraded to https://.`;
|
|
1478
|
+
}
|
|
1479
|
+
},
|
|
1480
|
+
async "shell.env"(_input, output) {
|
|
1481
|
+
if (!flags.enforcement)
|
|
1482
|
+
return;
|
|
1483
|
+
Object.assign(output.env, {
|
|
1484
|
+
OPENCODE_MULTIAGENT: "1",
|
|
1485
|
+
OPENCODE_MULTIAGENT_MODE: pluginMode,
|
|
1486
|
+
OPENCODE_MULTIAGENT_PROFILE: flags.profile ?? "standard",
|
|
1487
|
+
OPENCODE_CONTROL_PLANE: "1"
|
|
1488
|
+
});
|
|
1489
|
+
},
|
|
1490
|
+
async "tool.execute.after"(input, output) {
|
|
1491
|
+
if (input.tool === "edit" && typeof input.args?.filePath === "string") {
|
|
1492
|
+
quality.trackEdit(input.sessionID, input.args.filePath);
|
|
1493
|
+
telemetry.trackEdit(input.sessionID);
|
|
1494
|
+
fileLocks.acquire(input.sessionID, input.args.filePath);
|
|
1495
|
+
}
|
|
1496
|
+
if (input.tool === "bash" && typeof input.args?.command === "string") {
|
|
1497
|
+
quality.recordQualityEvidence(input.sessionID, input.args.command);
|
|
1498
|
+
}
|
|
1499
|
+
const agentInfo = sessionAgentMap.get(input.sessionID) ?? {};
|
|
1500
|
+
telemetry.trackToolCall(input.sessionID, { agent: agentInfo.agent, model: agentInfo.model });
|
|
1501
|
+
const isNonzeroExit = typeof output.metadata?.exit === "number" && output.metadata.exit !== 0;
|
|
1502
|
+
if (isNonzeroExit) {
|
|
1503
|
+
telemetry.trackToolError(input.sessionID, {
|
|
1504
|
+
agent: agentInfo.agent,
|
|
1505
|
+
model: agentInfo.model
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
if (!flags.observation)
|
|
1509
|
+
return;
|
|
1510
|
+
const lower = `${String(output.title ?? "")}
|
|
1511
|
+
${String(output.output ?? "")}`.toLowerCase();
|
|
1512
|
+
const marks = flagged(lower);
|
|
1513
|
+
if (isNonzeroExit)
|
|
1514
|
+
marks.push("nonzero_exit");
|
|
1515
|
+
await note("tool_after", compact({
|
|
1516
|
+
observation: true,
|
|
1517
|
+
tool: input.tool,
|
|
1518
|
+
sessionID: input.sessionID,
|
|
1519
|
+
callID: input.callID,
|
|
1520
|
+
title: clip(output.title),
|
|
1521
|
+
args: input.args,
|
|
1522
|
+
suspicious: marks.length > 0,
|
|
1523
|
+
flags: marks,
|
|
1524
|
+
output: clip(output.output),
|
|
1525
|
+
...isNonzeroExit ? { exit_code: output.metadata.exit, error_type: "nonzero_exit" } : {}
|
|
1526
|
+
}));
|
|
1527
|
+
},
|
|
1528
|
+
tool: createTaskTools(taskManager, taskManagerReady)
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1532
|
+
// src/opencode-multiagent/runtime.ts
|
|
1533
|
+
var readSettingsSection = (value) => {
|
|
1534
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1535
|
+
return {};
|
|
1536
|
+
}
|
|
1537
|
+
return value;
|
|
1538
|
+
};
|
|
1539
|
+
async function loadRuntimeSettings() {
|
|
1540
|
+
const [bundledDefaults, unifiedUserSettings] = await Promise.all([
|
|
1541
|
+
loadBundledDefaults().catch(() => ({})),
|
|
1542
|
+
readJSON(settingsPath, {})
|
|
1543
|
+
]);
|
|
1544
|
+
const defaultAgentSettings = readBundledDefaultsSection(bundledDefaults, "agentSettings", {});
|
|
1545
|
+
const userFlags = readSettingsSection(unifiedUserSettings.flags);
|
|
1546
|
+
const userAgentSettings = readSettingsSection(unifiedUserSettings.agentSettings);
|
|
1547
|
+
const userProfiles = readSettingsSection(unifiedUserSettings.profiles);
|
|
1548
|
+
const profiles = merge(defaultProfiles, userProfiles);
|
|
1549
|
+
const profileName = typeof userFlags.profile === "string" ? userFlags.profile : defaultFlags.profile;
|
|
1550
|
+
const profile = profiles[profileName] ?? {};
|
|
1551
|
+
if (!(profileName in profiles)) {
|
|
1552
|
+
await note("config_warning", {
|
|
1553
|
+
observation: true,
|
|
1554
|
+
warning: "unknown_profile",
|
|
1555
|
+
profile: profileName
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
const flags = merge(merge(defaultFlags, profile), userFlags);
|
|
1559
|
+
const agentSettings = merge(defaultAgentSettings, userAgentSettings);
|
|
1560
|
+
return { flags, agentSettings };
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// src/control-plane.ts
|
|
1564
|
+
var OpenCodeMultiAgentPlugin = async (context) => {
|
|
1565
|
+
const client = context.client;
|
|
1566
|
+
const projectRoot = context.worktree || context.directory;
|
|
1567
|
+
const { flags, agentSettings } = await loadRuntimeSettings();
|
|
1568
|
+
return createPluginHooks({
|
|
1569
|
+
client,
|
|
1570
|
+
flags,
|
|
1571
|
+
agentSettings,
|
|
1572
|
+
projectRoot
|
|
1573
|
+
});
|
|
1574
|
+
};
|
|
1575
|
+
var control_plane_default = OpenCodeMultiAgentPlugin;
|
|
1576
|
+
|
|
1577
|
+
// src/index.ts
|
|
1578
|
+
var plugin = control_plane_default;
|
|
1579
|
+
var src_default = plugin;
|
|
1580
|
+
export {
|
|
1581
|
+
src_default as default,
|
|
1582
|
+
plugin as OpenCodeMultiAgentPlugin
|
|
1583
|
+
};
|