@kisev/agentomatic 11.0.2-dev.85.gb47f85ffc092 → 11.0.2-dev.87.g5a92e74c6754

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.
@@ -1,13 +1,5 @@
1
1
  import type { Plugin } from "@opencode/plugin";
2
- type HookInput = {
3
- tool: string;
4
- args?: {
5
- command?: unknown;
6
- };
7
- };
8
- type HookOutput = {
9
- output?: unknown;
10
- };
2
+ import { type ToolAfter } from "./events.js";
11
3
  export type RtkMethod = "compressed-rtk" | "truncated-head-tail" | "rtk-unavailable" | "ineligible" | "below-threshold";
12
4
  export type RtkStats = {
13
5
  schema_version: 1;
@@ -35,17 +27,12 @@ export declare function rtkCharsSaved(stats: RtkStats): number;
35
27
  export declare function rtkTokensSavedEstimate(stats: RtkStats): number;
36
28
  export declare function readRtkStats(path?: string): Promise<RtkStats | undefined>;
37
29
  export declare function rtk(options?: RtkOptions): Promise<{
38
- "tool.execute.after"?: undefined;
30
+ "execute.after"?: undefined;
39
31
  } | {
40
- "tool.execute.after": (input: HookInput, output: HookOutput) => Promise<void>;
32
+ "execute.after": (input: ToolAfter) => Promise<void>;
41
33
  }>;
42
34
  declare const _default: {
43
35
  id: string;
44
- server: (_input: unknown, options?: RtkOptions) => Promise<{
45
- "tool.execute.after"?: undefined;
46
- } | {
47
- "tool.execute.after": (input: HookInput, output: HookOutput) => Promise<void>;
48
- }>;
49
- setup(ctx: Plugin.Context): Promise<(() => Promise<void>) | undefined>;
36
+ setup(ctx: Plugin.Context): Promise<void>;
50
37
  };
51
38
  export default _default;
@@ -1,4 +1,4 @@
1
- import { registerV2Hooks } from "./compatibility.js";
1
+ import { resultText, replaceResultText, toolInput } from "./events.js";
2
2
  import { spawn } from "node:child_process";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
@@ -138,26 +138,33 @@ export async function rtk(options = {}) {
138
138
  ((selected, input) => external(options.bin ?? "rtk", selected, input));
139
139
  const statsPath = options.statsPath === null ? undefined : (options.statsPath ?? rtkStatsPath());
140
140
  return {
141
- "tool.execute.after": async (input, output) => {
141
+ "execute.after": async (input) => {
142
+ if (input.status !== "completed")
143
+ return;
144
+ const output = { output: resultText(input.result) };
142
145
  try {
143
146
  if (input.tool === "edit" &&
144
147
  typeof output.output === "string" &&
145
148
  /oldString (not found|found multiple times|and newString must be different)/i.test(output.output))
146
149
  output.output = `${output.output}\nSTOP. Read the file before retrying Edit.`;
147
- if (input.tool !== "bash" ||
150
+ if (input.tool !== "shell" ||
148
151
  typeof output.output !== "string" ||
149
152
  output.output.length < (options.threshold ?? THRESHOLD)) {
150
- if (input.tool === "bash" && typeof output.output === "string" && statsPath)
153
+ if (input.tool === "shell" && typeof output.output === "string" && statsPath)
151
154
  await recordStats(statsPath, "below-threshold", output.output.length, output.output.length);
155
+ if (output.output !== undefined)
156
+ input.result = replaceResultText(input.result, output.output);
152
157
  return;
153
158
  }
154
- const selected = typeof input.args?.command === "string" ? filter(input.args.command) : undefined;
159
+ const command = toolInput(input.input).command;
160
+ const selected = typeof command === "string" ? filter(command) : undefined;
155
161
  const compressed = selected ? await run(selected, output.output) : undefined;
156
162
  const result = compressed && compressed.length < output.output.length
157
163
  ? compressed
158
164
  : truncate(output.output);
159
165
  const originalSize = output.output.length;
160
166
  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);
161
168
  if (statsPath) {
162
169
  const method = result === compressed
163
170
  ? "compressed-rtk"
@@ -177,8 +184,9 @@ export async function rtk(options = {}) {
177
184
  }
178
185
  export default {
179
186
  id: "agentomatic.rtk",
180
- server: async (_input, options = {}) => rtk(options),
181
187
  async setup(ctx) {
182
- return registerV2Hooks(ctx, await rtk(ctx.options));
188
+ const hooks = await rtk(ctx.options);
189
+ if (hooks["execute.after"])
190
+ await ctx.tool.hook("execute.after", hooks["execute.after"]);
183
191
  },
184
192
  };
@@ -1,45 +1,32 @@
1
1
  import type { Plugin } from "@opencode/plugin";
2
+ import { type ToolAfter } from "./events.js";
2
3
  export type RulesInjectorOptions = {
3
4
  budget?: number;
4
5
  cwd?: string;
5
6
  enabled?: boolean;
6
7
  };
7
8
  export declare function rulesInjector(options?: RulesInjectorOptions): Promise<{
8
- "tool.execute.after"?: undefined;
9
+ "execute.after"?: undefined;
9
10
  event?: undefined;
10
- "experimental.chat.system.transform"?: undefined;
11
+ context?: undefined;
11
12
  } | {
12
- "tool.execute.after": (input: unknown, result: unknown) => Promise<void>;
13
- event: ({ event, }: {
14
- event?: {
15
- type?: string;
16
- properties?: Record<string, unknown>;
17
- };
13
+ "execute.after": (input: ToolAfter & {
14
+ directory?: string;
15
+ }) => Promise<void>;
16
+ event: (event: {
17
+ type: string;
18
+ data?: unknown;
18
19
  }) => Promise<void>;
19
- "experimental.chat.system.transform": (input: unknown, result: {
20
- system?: unknown[];
20
+ context: (input: {
21
+ sessionID: string;
22
+ system: Array<{
23
+ type: "text";
24
+ text: string;
25
+ }>;
21
26
  }) => Promise<void>;
22
27
  }>;
23
28
  declare const _default: {
24
29
  id: string;
25
- server: (input: {
26
- directory?: string;
27
- }, options?: RulesInjectorOptions) => Promise<{
28
- "tool.execute.after"?: undefined;
29
- event?: undefined;
30
- "experimental.chat.system.transform"?: undefined;
31
- } | {
32
- "tool.execute.after": (input: unknown, result: unknown) => Promise<void>;
33
- event: ({ event, }: {
34
- event?: {
35
- type?: string;
36
- properties?: Record<string, unknown>;
37
- };
38
- }) => Promise<void>;
39
- "experimental.chat.system.transform": (input: unknown, result: {
40
- system?: unknown[];
41
- }) => Promise<void>;
42
- }>;
43
30
  setup(ctx: Plugin.Context): Promise<(() => Promise<void>) | undefined>;
44
31
  };
45
32
  export default _default;
@@ -1,4 +1,4 @@
1
- import { registerV2Hooks } from "./compatibility.js";
1
+ import { resultText, replaceResultText, subscribeEvents, toolInput, } from "./events.js";
2
2
  import { lstat, readFile, realpath } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, resolve } from "node:path";
@@ -10,20 +10,6 @@ function sessionID(value) {
10
10
  return item[key];
11
11
  return undefined;
12
12
  }
13
- function pathOf(input, output) {
14
- for (const value of [input, output]) {
15
- const args = value?.args;
16
- for (const key of ["filePath", "file_path", "path"])
17
- if (typeof args?.[key] === "string")
18
- return args[key];
19
- }
20
- return undefined;
21
- }
22
- function output(value, text) {
23
- const item = value;
24
- if (typeof item?.output === "string")
25
- item.output = `${item.output}\n${text}`;
26
- }
27
13
  function marker(rule) {
28
14
  return `[rules-injector source=${rule.path} revision=${rule.revision}]`;
29
15
  }
@@ -45,16 +31,19 @@ export async function rulesInjector(options = {}) {
45
31
  }
46
32
  return item;
47
33
  };
48
- const inject = async (input, result) => {
49
- const call = input;
50
- if (call?.tool !== "read" && call?.tool !== "edit")
34
+ const inject = async (input) => {
35
+ if (input.status !== "completed" || (input.tool !== "read" && input.tool !== "edit"))
51
36
  return;
52
- const identifier = sessionID(input);
53
- const target = pathOf(input, result);
54
- if (!identifier || !target)
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)
55
41
  return;
42
+ const output = (text) => {
43
+ input.result = replaceResultText(input.result, `${original}\n${text}`);
44
+ };
56
45
  try {
57
- const native = typeof call?.directory === "string" ? call.directory : cwd;
46
+ const native = input.directory ?? cwd;
58
47
  const paths = [];
59
48
  let current = dirname(resolve(native, target));
60
49
  while (true) {
@@ -104,35 +93,51 @@ export async function rulesInjector(options = {}) {
104
93
  }
105
94
  }
106
95
  if (additions.length)
107
- output(result, additions.join("\n\n"));
96
+ output(additions.join("\n\n"));
108
97
  }
109
98
  catch (error) {
110
- output(result, `[rules-injector warning] cannot read AGENTS.md: ${String(error)}`);
99
+ output(`[rules-injector warning] cannot read AGENTS.md: ${String(error)}`);
111
100
  }
112
101
  };
113
102
  return {
114
- "tool.execute.after": inject,
115
- event: async ({ event, }) => {
116
- if (event?.type === "session.compacted" || event?.type?.includes("compaction")) {
117
- const identifier = sessionID(event.properties);
103
+ "execute.after": inject,
104
+ event: async (event) => {
105
+ if (event.type === "session.compacted" || event.type.includes("compaction")) {
106
+ const identifier = sessionID(event.data);
118
107
  if (identifier)
119
108
  state(identifier).replay = true;
120
109
  }
121
110
  },
122
- "experimental.chat.system.transform": async (input, result) => {
111
+ context: async (input) => {
123
112
  const identifier = sessionID(input);
124
- if (!identifier || !state(identifier).replay || !Array.isArray(result.system))
113
+ if (!identifier || !state(identifier).replay)
125
114
  return;
126
115
  for (const rule of state(identifier).loaded.values())
127
- result.system.push(block(rule));
116
+ input.system.push({ type: "text", text: block(rule) });
128
117
  state(identifier).replay = false;
129
118
  },
130
119
  };
131
120
  }
132
121
  export default {
133
122
  id: "agentomatic.rules-injector",
134
- server: async (input, options = {}) => rulesInjector({ ...options, cwd: options.cwd ?? input.directory }),
135
123
  async setup(ctx) {
136
- return registerV2Hooks(ctx, await rulesInjector({ ...ctx.options, cwd: ctx.location.directory }), { sessionDirectory: true });
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);
137
142
  },
138
143
  };
@@ -5,23 +5,12 @@ export type ZedBellOptions = {
5
5
  export declare function zedBell(options?: ZedBellOptions): Promise<{
6
6
  event?: undefined;
7
7
  } | {
8
- event: ({ event }: {
9
- event: {
10
- type?: string;
11
- };
8
+ event: (event: {
9
+ type: string;
12
10
  }) => Promise<void>;
13
11
  }>;
14
12
  declare const _default: {
15
13
  id: string;
16
- server: (_input: unknown, options?: ZedBellOptions) => Promise<{
17
- event?: undefined;
18
- } | {
19
- event: ({ event }: {
20
- event: {
21
- type?: string;
22
- };
23
- }) => Promise<void>;
24
- }>;
25
14
  setup(ctx: Plugin.Context): Promise<(() => Promise<void>) | undefined>;
26
15
  };
27
16
  export default _default;
@@ -1,9 +1,9 @@
1
- import { registerV2Hooks } from "./compatibility.js";
1
+ import { subscribeEvents } from "./events.js";
2
2
  export async function zedBell(options = {}) {
3
3
  if (!options.enabled)
4
4
  return {};
5
5
  return {
6
- event: async ({ event }) => {
6
+ event: async (event) => {
7
7
  if (event.type !== "session.idle" && event.type !== "permission.asked")
8
8
  return;
9
9
  const acp = process.env.OPENCODE_CLIENT === "acp" || process.argv.includes("acp");
@@ -14,8 +14,9 @@ export async function zedBell(options = {}) {
14
14
  }
15
15
  export default {
16
16
  id: "agentomatic.zed-bell",
17
- server: async (_input, options = {}) => zedBell(options),
18
17
  async setup(ctx) {
19
- return registerV2Hooks(ctx, await zedBell(ctx.options));
18
+ const hooks = await zedBell(ctx.options);
19
+ if (hooks.event)
20
+ return subscribeEvents(ctx, hooks.event);
20
21
  },
21
22
  };
package/dist/registry.js CHANGED
@@ -57,7 +57,7 @@ const COMMANDS = [
57
57
  "Show the RTK output-compression observability summary for this host.",
58
58
  `Run \`npx --yes @kisev/agentomatic@${requirePackageVersion()} doctor --json\` and render the \`rtk.observability\` check as a short human summary: wrapper status, rtk binary availability, event counters, characters saved, and the token estimate.`,
59
59
  "When the doctor command is unavailable, read the stats file directly: `$XDG_STATE_HOME/opencode/skills/rtk/stats.json`, or `~/.local/state/opencode/skills/rtk/stats.json` when that variable is unset.",
60
- "Zero counters with an active wrapper mean no verbose bash output has been compressed yet.",
60
+ "Zero counters with an active wrapper mean no verbose shell output has been compressed yet.",
61
61
  "The `/rtk` command still loads the portable rtk skill and is unaffected by this summary.",
62
62
  ],
63
63
  },
package/dist/routing.js CHANGED
@@ -21,7 +21,7 @@ const MATRIX = {
21
21
  implementation: {
22
22
  profiles: ["worker"],
23
23
  capabilities: ["read", "write", "verify"],
24
- tools: ["read", "edit", "bash"],
24
+ tools: ["read", "edit", "shell"],
25
25
  cost: "medium",
26
26
  latency: "standard",
27
27
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kisev/agentomatic",
3
- "version": "11.0.2-dev.85.gb47f85ffc092",
3
+ "version": "11.0.2-dev.87.g5a92e74c6754",
4
4
  "description": "OpenCode integration and opt-in installer for portable Agent Skills.",
5
5
  "homepage": "https://github.com/kisev/skills#readme",
6
6
  "bugs": {
@@ -46,24 +46,19 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@clack/prompts": "^1.8.1",
49
- "@kisev/safe-fs": "1.0.0-dev.85.gb47f85ffc092",
49
+ "@kisev/safe-fs": "1.0.0-dev.87.g5a92e74c6754",
50
50
  "commander": "15.0.0",
51
51
  "zod": "4.1.8"
52
52
  },
53
53
  "devDependencies": {
54
- "@opencode-ai/plugin": "1.18.32",
55
54
  "@opencode/plugin": "2.0.19",
56
55
  "@types/node": "22.15.30",
57
56
  "typescript": "7.0.2"
58
57
  },
59
58
  "peerDependencies": {
60
- "@opencode-ai/plugin": ">=1.18.0 <1.19.0",
61
59
  "@opencode/plugin": ">=2.0.0 <2.1.0"
62
60
  },
63
61
  "peerDependenciesMeta": {
64
- "@opencode-ai/plugin": {
65
- "optional": true
66
- },
67
62
  "@opencode/plugin": {
68
63
  "optional": true
69
64
  }
@@ -1,30 +0,0 @@
1
- import type { Plugin } from "@opencode/plugin";
2
- type Input = {
3
- tool: string;
4
- sessionID: string;
5
- args: Record<string, unknown>;
6
- directory?: string;
7
- };
8
- type Hooks = {
9
- "tool.execute.before"?: (input: Input, output: {
10
- args: unknown;
11
- }) => Promise<void>;
12
- "tool.execute.after"?: (input: Input, output: {
13
- output: string;
14
- }) => Promise<void>;
15
- event?: (input: {
16
- event: {
17
- type: string;
18
- properties?: Record<string, unknown>;
19
- };
20
- }) => Promise<void>;
21
- "experimental.chat.system.transform"?: (input: {
22
- sessionID: string;
23
- }, output: {
24
- system: string[];
25
- }) => Promise<void>;
26
- };
27
- export declare function registerV2Hooks(ctx: Plugin.Context, hooks: Hooks, options?: {
28
- sessionDirectory?: boolean;
29
- }): Promise<(() => Promise<void>) | undefined>;
30
- export {};
@@ -1,87 +0,0 @@
1
- function toolName(name) {
2
- return name === "shell" ? "bash" : name === "subagent" ? "task" : name;
3
- }
4
- function args(value) {
5
- return value && typeof value === "object" && !Array.isArray(value)
6
- ? value
7
- : {};
8
- }
9
- function text(result) {
10
- if (typeof result.content === "string")
11
- return result.content;
12
- if (Array.isArray(result.content))
13
- return result.content
14
- .filter((part) => part.type === "text")
15
- .map((part) => part.text)
16
- .join("\n");
17
- return undefined;
18
- }
19
- function replaceText(result, output) {
20
- if (!Array.isArray(result.content))
21
- return { ...result, content: output };
22
- let replaced = false;
23
- return {
24
- ...result,
25
- content: result.content.flatMap((part) => {
26
- if (part.type !== "text")
27
- return [part];
28
- if (replaced)
29
- return [];
30
- replaced = true;
31
- return [{ type: "text", text: output }];
32
- }),
33
- };
34
- }
35
- export async function registerV2Hooks(ctx, hooks, options = {}) {
36
- if (hooks["tool.execute.before"])
37
- await ctx.tool.hook("execute.before", async (event) => {
38
- const output = { args: event.input };
39
- await hooks["tool.execute.before"]({ ...event, tool: toolName(event.tool), args: args(event.input) }, output);
40
- event.input = output.args;
41
- });
42
- if (hooks["tool.execute.after"])
43
- await ctx.tool.hook("execute.after", async (event) => {
44
- if (event.status !== "completed")
45
- return;
46
- const original = text(event.result);
47
- if (original === undefined)
48
- return;
49
- const output = { output: original };
50
- let directory = ctx.location.directory;
51
- if (options.sessionDirectory && (event.tool === "read" || event.tool === "edit")) {
52
- const session = await ctx.session
53
- .get({ sessionID: event.sessionID })
54
- .catch(() => undefined);
55
- if (!session)
56
- return;
57
- directory = session.location.directory;
58
- }
59
- await hooks["tool.execute.after"]({ ...event, tool: toolName(event.tool), args: args(event.input), directory }, output);
60
- if (output.output !== original)
61
- event.result = replaceText(event.result, output.output);
62
- });
63
- if (hooks["experimental.chat.system.transform"])
64
- await ctx.session.hook("context", async (event) => {
65
- const output = { system: [] };
66
- await hooks["experimental.chat.system.transform"]({ sessionID: event.sessionID }, output);
67
- event.system.push(...output.system.map((text) => ({ type: "text", text })));
68
- });
69
- if (!hooks.event)
70
- return;
71
- const controller = new AbortController();
72
- const subscription = (async () => {
73
- try {
74
- for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
75
- await hooks.event({ event: { type: event.type, properties: args(event.data) } });
76
- }
77
- }
78
- catch (error) {
79
- if (!controller.signal.aborted)
80
- console.error("agentomatic event subscription failed", error);
81
- }
82
- })();
83
- return async () => {
84
- controller.abort();
85
- await subscription;
86
- };
87
- }