@kisev/agentomatic 11.0.2-dev.92.gc67bdc97fcd1 → 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/dist/index.js CHANGED
@@ -1,115 +1,140 @@
1
- import { z } from "zod";
1
+ import { tool } from "@opencode-ai/plugin";
2
2
  import { CATEGORIES, RoutingGate, } from "./routing.js";
3
- import { rulesInjector } from "./plugins/rules-injector.js";
4
- import { rtk } from "./plugins/rtk.js";
5
- import { zedBell } from "./plugins/zed-bell.js";
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
- export default {
11
- id: "agentomatic",
12
- async setup(ctx) {
13
- const gate = new RoutingGate();
14
- const hostInventory = async () => {
15
- const { data: raw } = await ctx.agent.list({
16
- location: { directory: ctx.location.directory },
17
- });
18
- const capabilities = {
19
- mapper: ["read", "search"],
20
- architect: ["read", "architecture"],
21
- worker: ["read", "write", "verify"],
22
- review: ["read", "review"],
23
- critic: ["read", "review"],
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
- await ctx.tool.transform((editor) => {
39
- editor.add({
40
- name: "route",
41
- description: "Resolve a capability category and dispatch one eligible agent through a one-use subagent receipt gate.",
42
- input: z.object({
43
- action: z.enum(["preview", "dispatch"]),
44
- category: z.enum(CATEGORIES),
45
- task: z.string(),
46
- requirements: z.array(z.string()).default([]),
47
- execution_card: z.any().optional(),
48
- override: z.string().optional(),
49
- trusted_override: z.boolean().optional(),
50
- budget: z
51
- .object({ cost_class: z.string().optional(), latency_class: z.string().optional() })
52
- .optional(),
53
- decision: z.any().optional(),
54
- }),
55
- async execute(args, context) {
56
- const inventory = await hostInventory();
57
- const input = {
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
- await ctx.tool.hook("execute.before", async (event) => {
83
- if (event.tool !== "subagent")
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 = toolInput(event.input);
86
- const agent = typeof args.agent === "string" ? args.agent : undefined;
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 subagent requires an explicit agent and an active routing receipt");
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(event.sessionID, agent);
92
- else
93
- gate.consume(event.sessionID, agent, {
94
- task: typeof args.task === "string" ? args.task : "",
95
- requirements: Array.isArray(args.requirements) ? args.requirements : [],
96
- card: args.execution_card,
97
- });
98
- });
99
- await ctx.tool.hook("execute.after", async (event) => {
100
- if (event.tool !== "subagent" || event.status !== "completed")
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 = toolInput(event.input);
103
- if (typeof args.agent !== "string")
104
- throw new Error("Subagent result requires an explicit agent");
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(resultText(event.result) ?? "");
130
+ result = JSON.parse(output.output);
108
131
  }
109
132
  catch {
110
- throw new Error("Subagent result must be JSON structured report");
133
+ throw new Error("Task result must be JSON structured report");
111
134
  }
112
- gate.complete(event.sessionID, args.agent, result);
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: unknown;
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
- Object.defineProperty(value, key, {
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(parseJsonc(current), parseJsonc(applied.text), edit);
427
+ verifyEdit(before, parseJsonc(applied.text), edit);
505
428
  results.push(applied.result);
506
429
  current = applied.text;
507
430
  }
@@ -1,5 +1,12 @@
1
- import type { Plugin } from "@opencode/plugin";
2
- import { type ToolAfter } from "./events.js";
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: ToolAfter) => Promise<void>;
39
+ "tool.execute.after": (input: HookInput, output: HookOutput) => Promise<void>;
33
40
  }>;
34
- declare const _default: {
35
- id: string;
36
- setup(ctx: Plugin.Context): Promise<void>;
37
- };
38
- export default _default;
41
+ export default rtk;
@@ -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 !== "shell" ||
146
+ if (input.tool !== "bash" ||
151
147
  typeof output.output !== "string" ||
152
148
  output.output.length < (options.threshold ?? THRESHOLD)) {
153
- if (input.tool === "shell" && typeof output.output === "string" && statsPath)
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 command = toolInput(input.input).command;
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
- context?: undefined;
9
+ "experimental.chat.system.transform"?: undefined;
12
10
  } | {
13
- "execute.after": (input: ToolAfter & {
14
- directory?: string;
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
- event: (event: {
17
- type: string;
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
- declare const _default: {
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
- if (input.status !== "completed" || (input.tool !== "read" && input.tool !== "edit"))
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.sessionID;
38
- const target = toolInput(input.input).path;
39
- const original = resultText(input.result);
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 = input.directory ?? cwd;
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.type === "session.compacted" || event.type.includes("compaction")) {
106
- const identifier = sessionID(event.data);
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
- context: async (input) => {
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
- input.system.push({ type: "text", text: block(rule) });
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;