@kisev/agentomatic 11.0.2-dev.93.g810c3f0460fa → 11.0.2
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/README.md +8 -10
- package/README.ru.md +8 -11
- package/dist/agent-profiles.js +48 -33
- package/dist/assets/agents/architect.md +4 -4
- package/dist/assets/agents/critic.md +11 -10
- package/dist/assets/agents/manager.md +12 -11
- package/dist/assets/agents/mapper.md +4 -4
- package/dist/assets/agents/review.md +12 -11
- package/dist/assets/agents/worker.md +32 -29
- package/dist/assets/commands/rtk-stats.md +1 -1
- package/dist/assets/plugins/rtk.js +1 -1
- package/dist/assets/plugins/rules-injector.js +1 -1
- package/dist/assets/plugins/zed-bell.js +1 -1
- package/dist/cli.js +5 -8
- package/dist/config-setup.d.ts +7 -2
- package/dist/config-setup.js +52 -74
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +54 -39
- package/dist/index.d.ts +117 -9
- package/dist/index.js +125 -100
- package/dist/jsonc.d.ts +1 -12
- package/dist/jsonc.js +5 -82
- package/dist/plugins/rtk.d.ts +12 -9
- package/dist/plugins/rtk.js +5 -20
- package/dist/plugins/rules-injector.d.ts +11 -21
- package/dist/plugins/rules-injector.js +31 -43
- package/dist/plugins/zed-bell.d.ts +5 -8
- package/dist/plugins/zed-bell.js +2 -10
- package/dist/registry.js +1 -1
- package/dist/routing.js +1 -1
- package/package.json +7 -8
- package/dist/opencode-config.d.ts +0 -9
- package/dist/opencode-config.js +0 -158
- package/dist/plugins/events.d.ts +0 -20
- package/dist/plugins/events.js +0 -48
package/dist/index.js
CHANGED
|
@@ -1,115 +1,140 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
2
|
import { CATEGORIES, RoutingGate, } from "./routing.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { resultText, toolInput } from "./plugins/events.js";
|
|
3
|
+
import rulesInjector from "./plugins/rules-injector.js";
|
|
4
|
+
import rtk from "./plugins/rtk.js";
|
|
5
|
+
import zedBell from "./plugins/zed-bell.js";
|
|
7
6
|
import { digest } from "./lifecycle.js";
|
|
8
|
-
import { matchesWildcard } from "./opencode-config.js";
|
|
9
7
|
export { rulesInjector, rtk, zedBell };
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
async
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const agents = raw.map((agent) => ({
|
|
26
|
-
agent: agent.id,
|
|
27
|
-
available: true,
|
|
28
|
-
capabilities: capabilities[agent.id] ??
|
|
29
|
-
(/^critic-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent.id) ? ["read", "review"] : ["read"]),
|
|
30
|
-
tools: ["read", "glob", "grep", "edit", "shell"].filter((action) => {
|
|
31
|
-
const rules = agent.permissions.filter((rule) => matchesWildcard(rule.action, action));
|
|
32
|
-
const broad = rules.reduce((last, rule, index) => (rule.resource === "*" ? index : last), -1);
|
|
33
|
-
return rules.slice(Math.max(0, broad)).some((rule) => rule.effect !== "deny");
|
|
34
|
-
}),
|
|
35
|
-
}));
|
|
36
|
-
return { agents, revision: digest(raw) };
|
|
8
|
+
const plugin = (async (input) => {
|
|
9
|
+
const gate = new RoutingGate();
|
|
10
|
+
const hostInventory = async () => {
|
|
11
|
+
if (!input.client?.app?.agents)
|
|
12
|
+
throw new Error("Resolved OpenCode agent inventory is unavailable");
|
|
13
|
+
const response = await input.client.app.agents({ query: { directory: input.directory } });
|
|
14
|
+
const raw = response.data;
|
|
15
|
+
if (!Array.isArray(raw))
|
|
16
|
+
throw new Error("Resolved OpenCode agent inventory is malformed");
|
|
17
|
+
const capabilities = {
|
|
18
|
+
mapper: ["read", "search"],
|
|
19
|
+
architect: ["read", "architecture"],
|
|
20
|
+
worker: ["read", "write", "verify"],
|
|
21
|
+
review: ["read", "review"],
|
|
22
|
+
critic: ["read", "review"],
|
|
37
23
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.
|
|
52
|
-
.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
category: args.category,
|
|
59
|
-
task: args.task,
|
|
60
|
-
requirements: args.requirements,
|
|
61
|
-
agents: inventory.agents,
|
|
62
|
-
inventory_revision: inventory.revision,
|
|
63
|
-
execution_card: args.execution_card,
|
|
64
|
-
override: args.override,
|
|
65
|
-
trusted_override: args.trusted_override,
|
|
66
|
-
budget: args.budget,
|
|
67
|
-
};
|
|
68
|
-
if (args.action === "preview")
|
|
69
|
-
return { content: JSON.stringify(gate.preview(input)) };
|
|
70
|
-
const decision = gate.dispatch(input, args.decision);
|
|
71
|
-
if (decision.agent === "worker" && args.execution_card === undefined)
|
|
72
|
-
throw new Error("implementation dispatch requires a confirmed execution card");
|
|
73
|
-
const receipt = gate.grant(context.sessionID, decision, {
|
|
74
|
-
task: args.task,
|
|
75
|
-
requirements: args.requirements,
|
|
76
|
-
card: args.execution_card,
|
|
77
|
-
});
|
|
78
|
-
return { content: JSON.stringify({ decision, receipt, status: "routed" }) };
|
|
79
|
-
},
|
|
80
|
-
});
|
|
24
|
+
const agents = raw.map((value) => {
|
|
25
|
+
const agent = value;
|
|
26
|
+
if (typeof agent.name !== "string" || !agent.name)
|
|
27
|
+
throw new Error("Resolved OpenCode agent has no name");
|
|
28
|
+
return {
|
|
29
|
+
agent: agent.name,
|
|
30
|
+
available: true,
|
|
31
|
+
capabilities: capabilities[agent.name] ??
|
|
32
|
+
(/^critic-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent.name) ? ["read", "review"] : ["read"]),
|
|
33
|
+
tools: [
|
|
34
|
+
"read",
|
|
35
|
+
...(agent.permission?.edit !== "deny" ? ["edit"] : []),
|
|
36
|
+
...Object.entries(agent.tools ?? {})
|
|
37
|
+
.filter(([, enabled]) => enabled)
|
|
38
|
+
.map(([name]) => name),
|
|
39
|
+
...Object.entries(agent.permission?.bash ?? {})
|
|
40
|
+
.filter(([, mode]) => mode !== "deny")
|
|
41
|
+
.map(() => "bash"),
|
|
42
|
+
].filter((name, index, list) => list.indexOf(name) === index),
|
|
43
|
+
};
|
|
81
44
|
});
|
|
82
|
-
|
|
83
|
-
|
|
45
|
+
return { agents, revision: digest(raw) };
|
|
46
|
+
};
|
|
47
|
+
const route = tool({
|
|
48
|
+
description: "Resolve a capability category and dispatch one eligible agent through a one-use Task receipt gate.",
|
|
49
|
+
args: {
|
|
50
|
+
action: tool.schema.enum(["preview", "dispatch"]),
|
|
51
|
+
category: tool.schema.enum(CATEGORIES),
|
|
52
|
+
task: tool.schema.string(),
|
|
53
|
+
requirements: tool.schema.array(tool.schema.string()).default([]),
|
|
54
|
+
execution_card: tool.schema.any().optional(),
|
|
55
|
+
override: tool.schema.string().optional(),
|
|
56
|
+
trusted_override: tool.schema.boolean().optional(),
|
|
57
|
+
budget: tool.schema
|
|
58
|
+
.object({
|
|
59
|
+
cost_class: tool.schema.string().optional(),
|
|
60
|
+
latency_class: tool.schema.string().optional(),
|
|
61
|
+
})
|
|
62
|
+
.optional(),
|
|
63
|
+
decision: tool.schema.any().optional(),
|
|
64
|
+
},
|
|
65
|
+
async execute(args, context) {
|
|
66
|
+
const inventory = await hostInventory();
|
|
67
|
+
const input = {
|
|
68
|
+
category: args.category,
|
|
69
|
+
task: args.task,
|
|
70
|
+
requirements: args.requirements,
|
|
71
|
+
agents: inventory.agents,
|
|
72
|
+
inventory_revision: inventory.revision,
|
|
73
|
+
execution_card: args.execution_card,
|
|
74
|
+
override: args.override,
|
|
75
|
+
trusted_override: args.trusted_override,
|
|
76
|
+
budget: args.budget,
|
|
77
|
+
};
|
|
78
|
+
if (args.action === "preview")
|
|
79
|
+
return JSON.stringify(gate.preview(input));
|
|
80
|
+
const decision = gate.dispatch(input, args.decision);
|
|
81
|
+
if (decision.agent === "worker" && args.execution_card === undefined)
|
|
82
|
+
throw new Error("implementation dispatch requires a confirmed execution card");
|
|
83
|
+
const receipt = gate.grant(context.sessionID, decision, {
|
|
84
|
+
task: args.task,
|
|
85
|
+
requirements: args.requirements,
|
|
86
|
+
card: args.execution_card,
|
|
87
|
+
});
|
|
88
|
+
return JSON.stringify({ decision, receipt, status: "routed" });
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
tool: { route },
|
|
93
|
+
"tool.execute.before": async (input, output) => {
|
|
94
|
+
if (input.tool !== "task")
|
|
84
95
|
return;
|
|
85
|
-
const args =
|
|
86
|
-
|
|
96
|
+
const args = output.args && typeof output.args === "object"
|
|
97
|
+
? output.args
|
|
98
|
+
: {};
|
|
99
|
+
const agent = typeof args.agent === "string"
|
|
100
|
+
? args.agent
|
|
101
|
+
: typeof args.subagent_type === "string"
|
|
102
|
+
? args.subagent_type
|
|
103
|
+
: undefined;
|
|
87
104
|
if (!agent)
|
|
88
|
-
throw new Error("Native
|
|
105
|
+
throw new Error("Native Task requires an explicit agent and an active routing receipt");
|
|
89
106
|
const hasBinding = "task" in args || "requirements" in args || "execution_card" in args;
|
|
90
107
|
if (!hasBinding)
|
|
91
|
-
gate.consume(
|
|
92
|
-
else
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
gate.consume(input.sessionID, agent);
|
|
109
|
+
else {
|
|
110
|
+
const task = typeof args.task === "string" ? args.task : "";
|
|
111
|
+
const requirements = Array.isArray(args.requirements)
|
|
112
|
+
? args.requirements
|
|
113
|
+
: [];
|
|
114
|
+
gate.consume(input.sessionID, agent, { task, requirements, card: args.execution_card });
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"tool.execute.after": async (input, output) => {
|
|
118
|
+
if (input.tool !== "task")
|
|
101
119
|
return;
|
|
102
|
-
const args =
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
const args = input.args && typeof input.args === "object" ? input.args : {};
|
|
121
|
+
const agent = typeof args.agent === "string"
|
|
122
|
+
? args.agent
|
|
123
|
+
: typeof args.subagent_type === "string"
|
|
124
|
+
? args.subagent_type
|
|
125
|
+
: undefined;
|
|
126
|
+
if (!agent)
|
|
127
|
+
throw new Error("Task result requires an explicit agent");
|
|
105
128
|
let result;
|
|
106
129
|
try {
|
|
107
|
-
result = JSON.parse(
|
|
130
|
+
result = JSON.parse(output.output);
|
|
108
131
|
}
|
|
109
132
|
catch {
|
|
110
|
-
throw new Error("
|
|
133
|
+
throw new Error("Task result must be JSON structured report");
|
|
111
134
|
}
|
|
112
|
-
gate.complete(
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
};
|
|
135
|
+
gate.complete(input.sessionID, agent, result);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
export const server = plugin;
|
|
140
|
+
export default plugin;
|
package/dist/jsonc.d.ts
CHANGED
|
@@ -30,18 +30,7 @@ export type JsoncPath = Array<string | number>;
|
|
|
30
30
|
export type JsoncEdit = {
|
|
31
31
|
kind: "append-unique";
|
|
32
32
|
path: JsoncPath;
|
|
33
|
-
value:
|
|
34
|
-
} | {
|
|
35
|
-
kind: "set-value";
|
|
36
|
-
path: JsoncPath;
|
|
37
|
-
value: unknown;
|
|
38
|
-
} | {
|
|
39
|
-
kind: "rename-key";
|
|
40
|
-
path: JsoncPath;
|
|
41
|
-
to: string;
|
|
42
|
-
} | {
|
|
43
|
-
kind: "remove-key";
|
|
44
|
-
path: JsoncPath;
|
|
33
|
+
value: string;
|
|
45
34
|
} | {
|
|
46
35
|
kind: "replace-array-value";
|
|
47
36
|
path: JsoncPath;
|
package/dist/jsonc.js
CHANGED
|
@@ -143,9 +143,6 @@ function parseObject(parser) {
|
|
|
143
143
|
parser.cursor += 1;
|
|
144
144
|
trailingComma = true;
|
|
145
145
|
}
|
|
146
|
-
else if (next && !(next.type === "punct" && next.raw === "}")) {
|
|
147
|
-
throw new JsoncError("invalid_jsonc", `Expected comma at offset ${next.start}`);
|
|
148
|
-
}
|
|
149
146
|
}
|
|
150
147
|
if (!closed)
|
|
151
148
|
throw new JsoncError("invalid_jsonc", `Unterminated object at offset ${open.start}`);
|
|
@@ -183,9 +180,6 @@ function parseArray(parser) {
|
|
|
183
180
|
parser.cursor += 1;
|
|
184
181
|
trailingComma = true;
|
|
185
182
|
}
|
|
186
|
-
else if (next && !(next.type === "punct" && next.raw === "]")) {
|
|
187
|
-
throw new JsoncError("invalid_jsonc", `Expected comma at offset ${next.start}`);
|
|
188
|
-
}
|
|
189
183
|
}
|
|
190
184
|
if (!closed)
|
|
191
185
|
throw new JsoncError("invalid_jsonc", `Unterminated array at offset ${open.start}`);
|
|
@@ -206,12 +200,7 @@ export function nodeToValue(node) {
|
|
|
206
200
|
return node.items.map(nodeToValue);
|
|
207
201
|
const value = {};
|
|
208
202
|
for (const key of node.propOrder)
|
|
209
|
-
|
|
210
|
-
value: nodeToValue(node.props.get(key)),
|
|
211
|
-
writable: true,
|
|
212
|
-
configurable: true,
|
|
213
|
-
enumerable: true,
|
|
214
|
-
});
|
|
203
|
+
value[key] = nodeToValue(node.props.get(key));
|
|
215
204
|
return value;
|
|
216
205
|
}
|
|
217
206
|
function parseDocument(text) {
|
|
@@ -289,62 +278,11 @@ function requireObjectPath(text, root, path) {
|
|
|
289
278
|
return { text: currentText, node };
|
|
290
279
|
}
|
|
291
280
|
function applyEdit(text, root, edit) {
|
|
292
|
-
if (edit.kind === "rename-key" || edit.kind === "remove-key") {
|
|
293
|
-
const key = edit.path.at(-1);
|
|
294
|
-
const parent = resolve(root, edit.path.slice(0, -1));
|
|
295
|
-
if (typeof key !== "string" || !parent || parent.kind !== "object")
|
|
296
|
-
throw new JsoncError("conflict", "Key edit requires an object parent");
|
|
297
|
-
const target = parent.props.get(key);
|
|
298
|
-
if (!target)
|
|
299
|
-
return { text, result: "present" };
|
|
300
|
-
const index = parent.propOrder.indexOf(key);
|
|
301
|
-
const previous = index ? parent.props.get(parent.propOrder[index - 1]) : undefined;
|
|
302
|
-
const tokens = tokenize(text).filter((token) => token.start >= (previous?.end ?? parent.start + 1) && token.end <= target.start);
|
|
303
|
-
const keyToken = tokens.find((token) => token.type === "string");
|
|
304
|
-
if (!keyToken)
|
|
305
|
-
throw new JsoncError("conflict", "Object key token is unavailable");
|
|
306
|
-
if (edit.kind === "rename-key") {
|
|
307
|
-
if (parent.props.has(edit.to))
|
|
308
|
-
throw new JsoncError("conflict", `Destination key already exists: ${edit.to}`);
|
|
309
|
-
return {
|
|
310
|
-
text: text.slice(0, keyToken.start) + JSON.stringify(edit.to) + text.slice(keyToken.end),
|
|
311
|
-
result: "replaced",
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
const following = tokenize(text).find((token) => token.start >= target.end && token.type !== "ws" && token.type !== "comment");
|
|
315
|
-
const start = following?.raw === "," || !previous ? keyToken.start : previous.end;
|
|
316
|
-
const end = following?.raw === "," ? following.end : target.end;
|
|
317
|
-
const comments = tokenize(text.slice(start, end))
|
|
318
|
-
.filter((token) => token.type === "comment")
|
|
319
|
-
.map((token) => `${token.raw}\n`)
|
|
320
|
-
.join("");
|
|
321
|
-
return { text: text.slice(0, start) + comments + text.slice(end), result: "replaced" };
|
|
322
|
-
}
|
|
323
|
-
if (edit.kind === "set-value") {
|
|
324
|
-
const target = resolve(root, edit.path);
|
|
325
|
-
if (!target)
|
|
326
|
-
return applyEdit(text, root, { ...edit, kind: "set-if-absent" });
|
|
327
|
-
if (JSON.stringify(nodeToValue(target)) === JSON.stringify(edit.value))
|
|
328
|
-
return { text, result: "present" };
|
|
329
|
-
const comments = tokenize(text.slice(target.start, target.end))
|
|
330
|
-
.filter((token) => token.type === "comment")
|
|
331
|
-
.map((token) => `${token.raw}\n${lineIndent(text, target.start)}`)
|
|
332
|
-
.join("");
|
|
333
|
-
return {
|
|
334
|
-
text: text.slice(0, target.start) +
|
|
335
|
-
comments +
|
|
336
|
-
serialize(edit.value, lineIndent(text, target.start)) +
|
|
337
|
-
text.slice(target.end),
|
|
338
|
-
result: "replaced",
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
281
|
if (edit.kind === "append-unique") {
|
|
342
282
|
const target = resolve(root, edit.path);
|
|
343
283
|
if (!target || target.kind !== "array")
|
|
344
284
|
throw new JsoncError("conflict", "append-unique target is missing or not an array");
|
|
345
|
-
if (target.items
|
|
346
|
-
.map(nodeToValue)
|
|
347
|
-
.some((value) => JSON.stringify(value) === JSON.stringify(edit.value)))
|
|
285
|
+
if (target.items.map(nodeToValue).includes(edit.value))
|
|
348
286
|
return { text, result: "present" };
|
|
349
287
|
const insertion = target.empty ? JSON.stringify(edit.value) : `, ${JSON.stringify(edit.value)}`;
|
|
350
288
|
const at = target.empty ? target.start + 1 : target.items.at(-1).end;
|
|
@@ -431,24 +369,9 @@ function at(value, path) {
|
|
|
431
369
|
: undefined, value);
|
|
432
370
|
}
|
|
433
371
|
function verifyEdit(before, after, edit) {
|
|
434
|
-
if (edit.kind === "set-value") {
|
|
435
|
-
if (JSON.stringify(at(after, edit.path)) !== JSON.stringify(edit.value))
|
|
436
|
-
throw new JsoncError("merge_validation_failed", "set-value postcondition failed");
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
if (edit.kind === "rename-key" || edit.kind === "remove-key") {
|
|
440
|
-
if (at(after, edit.path) !== undefined)
|
|
441
|
-
throw new JsoncError("merge_validation_failed", "Old key remains after key edit");
|
|
442
|
-
if (edit.kind === "rename-key" &&
|
|
443
|
-
JSON.stringify(at(before, edit.path)) !==
|
|
444
|
-
JSON.stringify(at(after, [...edit.path.slice(0, -1), edit.to])))
|
|
445
|
-
throw new JsoncError("merge_validation_failed", "Renamed key lost its value");
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
372
|
if (edit.kind === "append-unique") {
|
|
449
373
|
const target = at(after, edit.path);
|
|
450
|
-
if (!Array.isArray(target) ||
|
|
451
|
-
!target.some((value) => JSON.stringify(value) === JSON.stringify(edit.value)))
|
|
374
|
+
if (!Array.isArray(target) || !target.includes(edit.value))
|
|
452
375
|
throw new JsoncError("merge_validation_failed", "append-unique postcondition failed");
|
|
453
376
|
return;
|
|
454
377
|
}
|
|
@@ -496,12 +419,12 @@ function verifyEdit(before, after, edit) {
|
|
|
496
419
|
throw new JsoncError("merge_validation_failed", "widened map lost the original scalar value");
|
|
497
420
|
}
|
|
498
421
|
export function applyJsoncEdits(text, edits) {
|
|
499
|
-
parseJsonc(text);
|
|
422
|
+
const before = parseJsonc(text);
|
|
500
423
|
let current = text;
|
|
501
424
|
const results = [];
|
|
502
425
|
for (const edit of edits) {
|
|
503
426
|
const applied = applyEdit(current, parseDocument(current), edit);
|
|
504
|
-
verifyEdit(
|
|
427
|
+
verifyEdit(before, parseJsonc(applied.text), edit);
|
|
505
428
|
results.push(applied.result);
|
|
506
429
|
current = applied.text;
|
|
507
430
|
}
|
package/dist/plugins/rtk.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type HookInput = {
|
|
2
|
+
tool: string;
|
|
3
|
+
args?: {
|
|
4
|
+
command?: unknown;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
type HookOutput = {
|
|
8
|
+
output?: unknown;
|
|
9
|
+
};
|
|
3
10
|
export type RtkMethod = "compressed-rtk" | "truncated-head-tail" | "rtk-unavailable" | "ineligible" | "below-threshold";
|
|
4
11
|
export type RtkStats = {
|
|
5
12
|
schema_version: 1;
|
|
@@ -27,12 +34,8 @@ export declare function rtkCharsSaved(stats: RtkStats): number;
|
|
|
27
34
|
export declare function rtkTokensSavedEstimate(stats: RtkStats): number;
|
|
28
35
|
export declare function readRtkStats(path?: string): Promise<RtkStats | undefined>;
|
|
29
36
|
export declare function rtk(options?: RtkOptions): Promise<{
|
|
30
|
-
"execute.after"?: undefined;
|
|
37
|
+
"tool.execute.after"?: undefined;
|
|
31
38
|
} | {
|
|
32
|
-
"execute.after": (input:
|
|
39
|
+
"tool.execute.after": (input: HookInput, output: HookOutput) => Promise<void>;
|
|
33
40
|
}>;
|
|
34
|
-
|
|
35
|
-
id: string;
|
|
36
|
-
setup(ctx: Plugin.Context): Promise<void>;
|
|
37
|
-
};
|
|
38
|
-
export default _default;
|
|
41
|
+
export default rtk;
|
package/dist/plugins/rtk.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resultText, replaceResultText, toolInput } from "./events.js";
|
|
2
1
|
import { spawn } from "node:child_process";
|
|
3
2
|
import { readFile } from "node:fs/promises";
|
|
4
3
|
import { homedir } from "node:os";
|
|
@@ -138,33 +137,26 @@ export async function rtk(options = {}) {
|
|
|
138
137
|
((selected, input) => external(options.bin ?? "rtk", selected, input));
|
|
139
138
|
const statsPath = options.statsPath === null ? undefined : (options.statsPath ?? rtkStatsPath());
|
|
140
139
|
return {
|
|
141
|
-
"execute.after": async (input) => {
|
|
142
|
-
if (input.status !== "completed")
|
|
143
|
-
return;
|
|
144
|
-
const output = { output: resultText(input.result) };
|
|
140
|
+
"tool.execute.after": async (input, output) => {
|
|
145
141
|
try {
|
|
146
142
|
if (input.tool === "edit" &&
|
|
147
143
|
typeof output.output === "string" &&
|
|
148
144
|
/oldString (not found|found multiple times|and newString must be different)/i.test(output.output))
|
|
149
145
|
output.output = `${output.output}\nSTOP. Read the file before retrying Edit.`;
|
|
150
|
-
if (input.tool !== "
|
|
146
|
+
if (input.tool !== "bash" ||
|
|
151
147
|
typeof output.output !== "string" ||
|
|
152
148
|
output.output.length < (options.threshold ?? THRESHOLD)) {
|
|
153
|
-
if (input.tool === "
|
|
149
|
+
if (input.tool === "bash" && typeof output.output === "string" && statsPath)
|
|
154
150
|
await recordStats(statsPath, "below-threshold", output.output.length, output.output.length);
|
|
155
|
-
if (output.output !== undefined)
|
|
156
|
-
input.result = replaceResultText(input.result, output.output);
|
|
157
151
|
return;
|
|
158
152
|
}
|
|
159
|
-
const
|
|
160
|
-
const selected = typeof command === "string" ? filter(command) : undefined;
|
|
153
|
+
const selected = typeof input.args?.command === "string" ? filter(input.args.command) : undefined;
|
|
161
154
|
const compressed = selected ? await run(selected, output.output) : undefined;
|
|
162
155
|
const result = compressed && compressed.length < output.output.length
|
|
163
156
|
? compressed
|
|
164
157
|
: truncate(output.output);
|
|
165
158
|
const originalSize = output.output.length;
|
|
166
159
|
output.output = `${result}\n[rtk: compressed method=${result === compressed ? `rtk/${selected}` : "head+tail"}; sizes=${originalSize}->${result.length}; evidence_complete=false; loss=possible]`;
|
|
167
|
-
input.result = replaceResultText(input.result, output.output);
|
|
168
160
|
if (statsPath) {
|
|
169
161
|
const method = result === compressed
|
|
170
162
|
? "compressed-rtk"
|
|
@@ -182,11 +174,4 @@ export async function rtk(options = {}) {
|
|
|
182
174
|
},
|
|
183
175
|
};
|
|
184
176
|
}
|
|
185
|
-
export default
|
|
186
|
-
id: "agentomatic.rtk",
|
|
187
|
-
async setup(ctx) {
|
|
188
|
-
const hooks = await rtk(ctx.options);
|
|
189
|
-
if (hooks["execute.after"])
|
|
190
|
-
await ctx.tool.hook("execute.after", hooks["execute.after"]);
|
|
191
|
-
},
|
|
192
|
-
};
|
|
177
|
+
export default rtk;
|
|
@@ -1,32 +1,22 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode/plugin";
|
|
2
|
-
import { type ToolAfter } from "./events.js";
|
|
3
1
|
export type RulesInjectorOptions = {
|
|
4
2
|
budget?: number;
|
|
5
3
|
cwd?: string;
|
|
6
4
|
enabled?: boolean;
|
|
7
5
|
};
|
|
8
6
|
export declare function rulesInjector(options?: RulesInjectorOptions): Promise<{
|
|
9
|
-
"execute.after"?: undefined;
|
|
7
|
+
"tool.execute.after"?: undefined;
|
|
10
8
|
event?: undefined;
|
|
11
|
-
|
|
9
|
+
"experimental.chat.system.transform"?: undefined;
|
|
12
10
|
} | {
|
|
13
|
-
"execute.after": (input:
|
|
14
|
-
|
|
11
|
+
"tool.execute.after": (input: unknown, result: unknown) => Promise<void>;
|
|
12
|
+
event: ({ event, }: {
|
|
13
|
+
event?: {
|
|
14
|
+
type?: string;
|
|
15
|
+
properties?: Record<string, unknown>;
|
|
16
|
+
};
|
|
15
17
|
}) => Promise<void>;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
data?: unknown;
|
|
19
|
-
}) => Promise<void>;
|
|
20
|
-
context: (input: {
|
|
21
|
-
sessionID: string;
|
|
22
|
-
system: Array<{
|
|
23
|
-
type: "text";
|
|
24
|
-
text: string;
|
|
25
|
-
}>;
|
|
18
|
+
"experimental.chat.system.transform": (input: unknown, result: {
|
|
19
|
+
system?: unknown[];
|
|
26
20
|
}) => Promise<void>;
|
|
27
21
|
}>;
|
|
28
|
-
|
|
29
|
-
id: string;
|
|
30
|
-
setup(ctx: Plugin.Context): Promise<(() => Promise<void>) | undefined>;
|
|
31
|
-
};
|
|
32
|
-
export default _default;
|
|
22
|
+
export default rulesInjector;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resultText, replaceResultText, subscribeEvents, toolInput, } from "./events.js";
|
|
2
1
|
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { dirname, resolve } from "node:path";
|
|
@@ -10,6 +9,20 @@ function sessionID(value) {
|
|
|
10
9
|
return item[key];
|
|
11
10
|
return undefined;
|
|
12
11
|
}
|
|
12
|
+
function pathOf(input, output) {
|
|
13
|
+
for (const value of [input, output]) {
|
|
14
|
+
const args = value?.args;
|
|
15
|
+
for (const key of ["filePath", "file_path", "path"])
|
|
16
|
+
if (typeof args?.[key] === "string")
|
|
17
|
+
return args[key];
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
function output(value, text) {
|
|
22
|
+
const item = value;
|
|
23
|
+
if (typeof item?.output === "string")
|
|
24
|
+
item.output = `${item.output}\n${text}`;
|
|
25
|
+
}
|
|
13
26
|
function marker(rule) {
|
|
14
27
|
return `[rules-injector source=${rule.path} revision=${rule.revision}]`;
|
|
15
28
|
}
|
|
@@ -31,19 +44,16 @@ export async function rulesInjector(options = {}) {
|
|
|
31
44
|
}
|
|
32
45
|
return item;
|
|
33
46
|
};
|
|
34
|
-
const inject = async (input) => {
|
|
35
|
-
|
|
47
|
+
const inject = async (input, result) => {
|
|
48
|
+
const call = input;
|
|
49
|
+
if (call?.tool !== "read" && call?.tool !== "edit")
|
|
36
50
|
return;
|
|
37
|
-
const identifier = input
|
|
38
|
-
const target =
|
|
39
|
-
|
|
40
|
-
if (!identifier || typeof target !== "string" || original === undefined)
|
|
51
|
+
const identifier = sessionID(input);
|
|
52
|
+
const target = pathOf(input, result);
|
|
53
|
+
if (!identifier || !target)
|
|
41
54
|
return;
|
|
42
|
-
const output = (text) => {
|
|
43
|
-
input.result = replaceResultText(input.result, `${original}\n${text}`);
|
|
44
|
-
};
|
|
45
55
|
try {
|
|
46
|
-
const native =
|
|
56
|
+
const native = typeof call?.directory === "string" ? call.directory : cwd;
|
|
47
57
|
const paths = [];
|
|
48
58
|
let current = dirname(resolve(native, target));
|
|
49
59
|
while (true) {
|
|
@@ -93,51 +103,29 @@ export async function rulesInjector(options = {}) {
|
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
105
|
if (additions.length)
|
|
96
|
-
output(additions.join("\n\n"));
|
|
106
|
+
output(result, additions.join("\n\n"));
|
|
97
107
|
}
|
|
98
108
|
catch (error) {
|
|
99
|
-
output(`[rules-injector warning] cannot read AGENTS.md: ${String(error)}`);
|
|
109
|
+
output(result, `[rules-injector warning] cannot read AGENTS.md: ${String(error)}`);
|
|
100
110
|
}
|
|
101
111
|
};
|
|
102
112
|
return {
|
|
103
|
-
"execute.after": inject,
|
|
104
|
-
event: async (event) => {
|
|
105
|
-
if (event
|
|
106
|
-
const identifier = sessionID(event.
|
|
113
|
+
"tool.execute.after": inject,
|
|
114
|
+
event: async ({ event, }) => {
|
|
115
|
+
if (event?.type?.includes("compaction")) {
|
|
116
|
+
const identifier = sessionID(event.properties);
|
|
107
117
|
if (identifier)
|
|
108
118
|
state(identifier).replay = true;
|
|
109
119
|
}
|
|
110
120
|
},
|
|
111
|
-
|
|
121
|
+
"experimental.chat.system.transform": async (input, result) => {
|
|
112
122
|
const identifier = sessionID(input);
|
|
113
|
-
if (!identifier || !state(identifier).replay)
|
|
123
|
+
if (!identifier || !state(identifier).replay || !Array.isArray(result.system))
|
|
114
124
|
return;
|
|
115
125
|
for (const rule of state(identifier).loaded.values())
|
|
116
|
-
|
|
126
|
+
result.system.push(block(rule));
|
|
117
127
|
state(identifier).replay = false;
|
|
118
128
|
},
|
|
119
129
|
};
|
|
120
130
|
}
|
|
121
|
-
export default
|
|
122
|
-
id: "agentomatic.rules-injector",
|
|
123
|
-
async setup(ctx) {
|
|
124
|
-
const hooks = await rulesInjector({ ...ctx.options, cwd: ctx.location.directory });
|
|
125
|
-
if (hooks["execute.after"])
|
|
126
|
-
await ctx.tool.hook("execute.after", async (event) => {
|
|
127
|
-
if (event.status !== "completed" || (event.tool !== "read" && event.tool !== "edit"))
|
|
128
|
-
return;
|
|
129
|
-
const session = await ctx.session
|
|
130
|
-
.get({ sessionID: event.sessionID })
|
|
131
|
-
.catch(() => undefined);
|
|
132
|
-
if (session) {
|
|
133
|
-
const scoped = { ...event, directory: session.location.directory };
|
|
134
|
-
await hooks["execute.after"](scoped);
|
|
135
|
-
event.result = scoped.result;
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
if (hooks.context)
|
|
139
|
-
await ctx.session.hook("context", hooks.context);
|
|
140
|
-
if (hooks.event)
|
|
141
|
-
return subscribeEvents(ctx, hooks.event);
|
|
142
|
-
},
|
|
143
|
-
};
|
|
131
|
+
export default rulesInjector;
|