@kisev/agentomatic 11.0.2 → 12.0.0-dev.113.g1bd7385234c6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +32 -16
  2. package/README.ru.md +33 -16
  3. package/dist/agent-profiles.d.ts +8 -2
  4. package/dist/agent-profiles.js +131 -82
  5. package/dist/assets/agents/architect.md +4 -4
  6. package/dist/assets/agents/critic.md +10 -11
  7. package/dist/assets/agents/manager.md +11 -12
  8. package/dist/assets/agents/mapper.md +4 -4
  9. package/dist/assets/agents/review.md +11 -12
  10. package/dist/assets/agents/worker.md +29 -32
  11. package/dist/assets/commands/asd-ste100.md +10 -0
  12. package/dist/assets/commands/code-simplify.md +12 -0
  13. package/dist/assets/commands/eli5.md +10 -0
  14. package/dist/assets/commands/handoff.md +10 -0
  15. package/dist/assets/commands/rtk-stats.md +2 -2
  16. package/dist/assets/commands/skill-doctor.md +10 -0
  17. package/dist/assets/migration-inventory.json +17 -6
  18. package/dist/assets/plugins/code-simplify.js +3 -0
  19. package/dist/assets/plugins/rtk.js +1 -1
  20. package/dist/assets/plugins/rules-injector.js +1 -1
  21. package/dist/assets/plugins/zed-bell.js +1 -1
  22. package/dist/catalog.d.ts +2 -2
  23. package/dist/catalog.js +9 -3
  24. package/dist/cli-output.js +6 -6
  25. package/dist/cli.d.ts +1 -1
  26. package/dist/cli.js +1 -1257
  27. package/dist/command-cli.d.ts +1 -0
  28. package/dist/command-cli.js +1004 -0
  29. package/dist/config-setup.d.ts +14 -8
  30. package/dist/config-setup.js +197 -64
  31. package/dist/contracts.js +26 -0
  32. package/dist/doctor.d.ts +0 -1
  33. package/dist/doctor.js +42 -55
  34. package/dist/index.d.ts +13 -118
  35. package/dist/index.js +118 -125
  36. package/dist/installer.d.ts +25 -2
  37. package/dist/installer.js +68 -23
  38. package/dist/jsonc.d.ts +18 -2
  39. package/dist/jsonc.js +109 -5
  40. package/dist/opencode-config.d.ts +11 -0
  41. package/dist/opencode-config.js +237 -0
  42. package/dist/plugins/code-simplify.d.ts +25 -0
  43. package/dist/plugins/code-simplify.js +49 -0
  44. package/dist/plugins/events.d.ts +20 -0
  45. package/dist/plugins/events.js +48 -0
  46. package/dist/plugins/rtk.d.ts +9 -12
  47. package/dist/plugins/rtk.js +20 -5
  48. package/dist/plugins/rules-injector.d.ts +21 -11
  49. package/dist/plugins/rules-injector.js +43 -31
  50. package/dist/plugins/zed-bell.d.ts +8 -5
  51. package/dist/plugins/zed-bell.js +10 -2
  52. package/dist/reconcile.d.ts +4 -1
  53. package/dist/reconcile.js +12 -16
  54. package/dist/registry.d.ts +1 -0
  55. package/dist/registry.js +21 -5
  56. package/dist/routing.d.ts +5 -3
  57. package/dist/routing.js +57 -22
  58. package/dist/self-install.d.ts +7 -0
  59. package/dist/self-install.js +52 -1
  60. package/dist/terminal-wizard.d.ts +1 -1
  61. package/dist/terminal-wizard.js +2 -2
  62. package/package.json +12 -7
  63. package/dist/assets/commands/skill-improve.md +0 -10
  64. package/dist/assets/commands/stopit.md +0 -10
package/dist/doctor.js CHANGED
@@ -12,6 +12,7 @@ import { archiveRoot, assertSafePath, deploymentRoot, lifecycleRoot, } from "./l
12
12
  import { inspectReconcile } from "./reconcile.js";
13
13
  import { readPackageVersion } from "./package-metadata.js";
14
14
  import { planDependency } from "./self-install.js";
15
+ import { parseJsonc } from "./jsonc.js";
15
16
  import { readRtkStats, rtkCharsSaved, rtkStatsPath, rtkTokensSavedEstimate, } from "./plugins/rtk.js";
16
17
  import { stateRoot } from "./runtime/state.js";
17
18
  const run = promisify(execFile);
@@ -114,17 +115,19 @@ function agentManifestValid(raw) {
114
115
  }
115
116
  function redactedConfig(value) {
116
117
  const object = value && typeof value === "object" && !Array.isArray(value) ? value : {};
117
- const rawPlugins = Array.isArray(object.plugin)
118
- ? object.plugin.flatMap((item) => {
118
+ const configuredPlugins = object.plugins;
119
+ const rawPlugins = Array.isArray(configuredPlugins)
120
+ ? configuredPlugins.flatMap((item) => {
119
121
  if (typeof item === "string")
120
122
  return [item];
121
- if (Array.isArray(item) && typeof item[0] === "string")
122
- return [item[0]];
123
+ if (item && typeof item === "object" && typeof item.package === "string")
124
+ return [item.package];
123
125
  return [];
124
126
  })
125
127
  : [];
126
128
  const knownPlugins = new Set([packageName, ...CATALOG.plugins]);
127
- const plugins = rawPlugins.filter((plugin) => knownPlugins.has(plugin));
129
+ const normalizePlugin = (plugin) => plugin === packageName || plugin.startsWith(`${packageName}@`) ? packageName : plugin;
130
+ const plugins = rawPlugins.map(normalizePlugin).filter((plugin) => knownPlugins.has(plugin));
128
131
  const enabled = plugins.filter((plugin) => plugin === packageName ||
129
132
  CATALOG.plugins.includes(plugin));
130
133
  return {
@@ -135,17 +138,14 @@ function redactedConfig(value) {
135
138
  keys: Object.keys(object)
136
139
  .filter((key) => [
137
140
  "$schema",
138
- "theme",
139
- "keybinds",
140
- "logLevel",
141
- "command",
141
+ "commands",
142
142
  "watcher",
143
- "plugin",
143
+ "plugins",
144
144
  "lsp",
145
145
  "formatter",
146
- "provider",
147
- "agent",
148
- "permission",
146
+ "providers",
147
+ "agents",
148
+ "permissions",
149
149
  "compaction",
150
150
  "model",
151
151
  ].includes(key))
@@ -155,12 +155,26 @@ function redactedConfig(value) {
155
155
  }
156
156
  async function localConfig(scope, cwd, home) {
157
157
  const paths = scope === "project"
158
- ? [join(cwd, "opencode.json"), join(cwd, ".opencode", "opencode.json")]
159
- : [join(home, ".config", "opencode", "opencode.json")];
158
+ ? [
159
+ join(cwd, "opencode.json"),
160
+ join(cwd, "opencode.jsonc"),
161
+ join(cwd, ".opencode", "opencode.json"),
162
+ join(cwd, ".opencode", "opencode.jsonc"),
163
+ ]
164
+ : [
165
+ join(home, ".config", "opencode", "opencode.json"),
166
+ join(home, ".config", "opencode", "opencode.jsonc"),
167
+ ];
160
168
  for (const path of paths) {
161
169
  const value = await regular(path);
162
170
  if (value.status === "present") {
163
- const parsed = json(value.raw);
171
+ let parsed;
172
+ try {
173
+ parsed = parseJsonc(value.raw.toString("utf8"));
174
+ }
175
+ catch {
176
+ parsed = undefined;
177
+ }
164
178
  return parsed
165
179
  ? { projection: redactedConfig(parsed), source: path }
166
180
  : { incomplete: true, source: path };
@@ -250,7 +264,7 @@ async function executableAvailable(name) {
250
264
  }
251
265
  return false;
252
266
  }
253
- async function lspFacts(project, host, partial) {
267
+ async function lspFacts(project, partial) {
254
268
  const suffixes = new Set();
255
269
  let scanIncomplete = false;
256
270
  const visit = async (root) => {
@@ -277,7 +291,6 @@ async function lspFacts(project, host, partial) {
277
291
  await visit(project);
278
292
  if (scanIncomplete)
279
293
  partial.push("lsp.project-files");
280
- const disabled = process.env.OPENCODE_DISABLE_LSP_DOWNLOAD === "true";
281
294
  const servers = await Promise.all(lspCatalog.servers.map(async (server) => {
282
295
  let available = false;
283
296
  available = await executableAvailable(server.executable);
@@ -285,48 +298,20 @@ async function lspFacts(project, host, partial) {
285
298
  return {
286
299
  name: server.name,
287
300
  applicable,
288
- configured: applicable,
301
+ configured: false,
289
302
  binary_available: available,
290
- active: applicable && available && !disabled,
291
- runtime_status: "unavailable",
303
+ active: false,
304
+ runtime_status: "unsupported",
292
305
  requirement_class: server.requirement_class,
293
- reason: !applicable
294
- ? "not-selected"
295
- : disabled
296
- ? "download-disabled"
297
- : !available
298
- ? "missing-dependency"
299
- : "available",
306
+ reason: "unsupported-in-opencode-v2",
300
307
  missing: available ? [] : [server.executable],
301
308
  install: `Install ${server.executable} with your project toolchain.`,
302
309
  };
303
310
  }));
304
- let runtimeStatus = "unavailable";
305
- if (host?.lsp) {
306
- try {
307
- const value = await host.lsp();
308
- runtimeStatus = Array.isArray(value)
309
- ? value.map((item) => {
310
- const entry = item && typeof item === "object" ? item : {};
311
- return {
312
- id: typeof entry.id === "string" ? entry.id : "unknown",
313
- name: typeof entry.name === "string" ? entry.name : "unknown",
314
- status: entry.status === "connected" || entry.status === "error" ? entry.status : "unknown",
315
- };
316
- })
317
- : { status: "unavailable" };
318
- }
319
- catch {
320
- partial.push("lsp.runtime-status");
321
- }
322
- }
323
- else
324
- partial.push("lsp.runtime-status");
325
311
  return {
326
312
  schema_version: 1,
327
313
  catalog_version: lspCatalog.catalog_version,
328
- download_disabled: disabled,
329
- runtime_status: runtimeStatus,
314
+ runtime_status: "unsupported",
330
315
  servers,
331
316
  };
332
317
  }
@@ -462,7 +447,9 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
462
447
  collisions: inventory?.collisions.length ?? 0,
463
448
  drift: inventory?.drift.length ?? 0,
464
449
  user_owned: inventory?.user_owned.length ?? 0,
465
- }, ["Review collisions and run agent reconcile only after explicit confirmation."]));
450
+ }, [
451
+ "Resolve ownership conflicts and restore modified bytes; preview maintenance repair for missing selected files.",
452
+ ]));
466
453
  const config = await localConfig(scope, project, homeRoot);
467
454
  if (config.incomplete)
468
455
  partial.push("config.local");
@@ -491,7 +478,7 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
491
478
  source: config.source ?? "host-or-file-unavailable",
492
479
  }, [
493
480
  "Inspect OpenCode configuration without exposing secrets.",
494
- "Local plugin wrappers in the deployment plugins directory load automatically without a plugin array entry.",
481
+ "Local plugin wrappers in the deployment plugins directory load automatically without a plugins array entry.",
495
482
  ]));
496
483
  const wired = (config.projection?.enabled_plugins ?? []).includes(packageName) ||
497
484
  (hostConfig?.enabled_plugins ?? []).includes(packageName);
@@ -623,7 +610,7 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
623
610
  }, lifecycleFiles.incomplete
624
611
  ? ["Resolve inaccessible lifecycle state before applying a mutation."]
625
612
  : undefined));
626
- const tools = await Promise.all(["opencode", ...lspCatalog.servers.map((server) => server.executable)].map(async (name) => {
613
+ const tools = await Promise.all(["opencode"].map(async (name) => {
627
614
  try {
628
615
  const result = await run(name, ["--version"], { timeout: 1000 });
629
616
  return {
@@ -640,7 +627,7 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
640
627
  }
641
628
  }));
642
629
  checks.push(check("host.tools", tools.some((item) => item.name === "opencode" && item.available) ? "pass" : "incomplete", "External tool availability is reported", { tools: tools.map((item) => `${item.name}:${item.available ? "available" : "missing"}`) }, ["Install OpenCode or missing external tools when needed."]));
643
- const lsp = await lspFacts(project, host, partial);
630
+ const lsp = await lspFacts(project, partial);
644
631
  const counts = { pass: 0, warn: 0, fail: 0, incomplete: 0 };
645
632
  for (const item of checks)
646
633
  counts[item.status] += 1;
package/dist/index.d.ts CHANGED
@@ -1,123 +1,18 @@
1
- import { type PluginInput } from "@opencode-ai/plugin";
2
- import rulesInjector, { type RulesInjectorOptions } from "./plugins/rules-injector.js";
3
- import rtk, { type RtkOptions } from "./plugins/rtk.js";
4
- import zedBell, { type ZedBellOptions } from "./plugins/zed-bell.js";
5
- export { rulesInjector, rtk, zedBell };
1
+ import type { Plugin } from "@opencode/plugin";
2
+ import { rulesInjector, type RulesInjectorOptions } from "./plugins/rules-injector.js";
3
+ import { rtk, type RtkOptions } from "./plugins/rtk.js";
4
+ import { zedBell, type ZedBellOptions } from "./plugins/zed-bell.js";
5
+ import { codeSimplify, CODE_SIMPLIFY_ROLES, type CodeSimplifyOptions, type CodeSimplifyRole } from "./plugins/code-simplify.js";
6
+ export { rulesInjector, rtk, zedBell, codeSimplify, CODE_SIMPLIFY_ROLES };
7
+ export type { CodeSimplifyOptions, CodeSimplifyRole };
6
8
  export type OpenCodeOptions = {
7
9
  rulesInjector?: RulesInjectorOptions;
8
10
  rtk?: RtkOptions;
9
11
  zedBell?: ZedBellOptions;
12
+ codeSimplify?: CodeSimplifyOptions;
10
13
  };
11
- declare const plugin: (input: PluginInput) => Promise<{
12
- tool: {
13
- route: {
14
- description: string;
15
- args: {
16
- action: import("zod").ZodEnum<{
17
- dispatch: "dispatch";
18
- preview: "preview";
19
- }>;
20
- category: import("zod").ZodEnum<{
21
- architecture: "architecture";
22
- exploration: "exploration";
23
- implementation: "implementation";
24
- review: "review";
25
- }>;
26
- task: import("zod").ZodString;
27
- requirements: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodString>>;
28
- execution_card: import("zod").ZodOptional<import("zod").ZodAny>;
29
- override: import("zod").ZodOptional<import("zod").ZodString>;
30
- trusted_override: import("zod").ZodOptional<import("zod").ZodBoolean>;
31
- budget: import("zod").ZodOptional<import("zod").ZodObject<{
32
- cost_class: import("zod").ZodOptional<import("zod").ZodString>;
33
- latency_class: import("zod").ZodOptional<import("zod").ZodString>;
34
- }, import("zod/v4/core").$strip>>;
35
- decision: import("zod").ZodOptional<import("zod").ZodAny>;
36
- };
37
- execute(args: {
38
- action: "dispatch" | "preview";
39
- category: "architecture" | "exploration" | "implementation" | "review";
40
- task: string;
41
- requirements: string[];
42
- execution_card?: any;
43
- override?: string | undefined;
44
- trusted_override?: boolean | undefined;
45
- budget?: {
46
- cost_class?: string | undefined;
47
- latency_class?: string | undefined;
48
- } | undefined;
49
- decision?: any;
50
- }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
51
- };
52
- };
53
- "tool.execute.before": (input: {
54
- tool: string;
55
- sessionID: string;
56
- }, output: {
57
- args: unknown;
58
- }) => Promise<void>;
59
- "tool.execute.after": (input: {
60
- tool: string;
61
- sessionID: string;
62
- args: unknown;
63
- }, output: {
64
- output: string;
65
- }) => Promise<void>;
66
- }>;
67
- export declare const server: (input: PluginInput) => Promise<{
68
- tool: {
69
- route: {
70
- description: string;
71
- args: {
72
- action: import("zod").ZodEnum<{
73
- dispatch: "dispatch";
74
- preview: "preview";
75
- }>;
76
- category: import("zod").ZodEnum<{
77
- architecture: "architecture";
78
- exploration: "exploration";
79
- implementation: "implementation";
80
- review: "review";
81
- }>;
82
- task: import("zod").ZodString;
83
- requirements: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodString>>;
84
- execution_card: import("zod").ZodOptional<import("zod").ZodAny>;
85
- override: import("zod").ZodOptional<import("zod").ZodString>;
86
- trusted_override: import("zod").ZodOptional<import("zod").ZodBoolean>;
87
- budget: import("zod").ZodOptional<import("zod").ZodObject<{
88
- cost_class: import("zod").ZodOptional<import("zod").ZodString>;
89
- latency_class: import("zod").ZodOptional<import("zod").ZodString>;
90
- }, import("zod/v4/core").$strip>>;
91
- decision: import("zod").ZodOptional<import("zod").ZodAny>;
92
- };
93
- execute(args: {
94
- action: "dispatch" | "preview";
95
- category: "architecture" | "exploration" | "implementation" | "review";
96
- task: string;
97
- requirements: string[];
98
- execution_card?: any;
99
- override?: string | undefined;
100
- trusted_override?: boolean | undefined;
101
- budget?: {
102
- cost_class?: string | undefined;
103
- latency_class?: string | undefined;
104
- } | undefined;
105
- decision?: any;
106
- }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
107
- };
108
- };
109
- "tool.execute.before": (input: {
110
- tool: string;
111
- sessionID: string;
112
- }, output: {
113
- args: unknown;
114
- }) => Promise<void>;
115
- "tool.execute.after": (input: {
116
- tool: string;
117
- sessionID: string;
118
- args: unknown;
119
- }, output: {
120
- output: string;
121
- }) => Promise<void>;
122
- }>;
123
- export default plugin;
14
+ declare const _default: {
15
+ id: string;
16
+ setup(ctx: Plugin.Context): Promise<void>;
17
+ };
18
+ export default _default;
package/dist/index.js CHANGED
@@ -1,140 +1,133 @@
1
- import { tool } from "@opencode-ai/plugin";
1
+ import { z } from "zod";
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";
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 { codeSimplify, CODE_SIMPLIFY_ROLES, } from "./plugins/code-simplify.js";
7
+ import { resultText, toolInput } from "./plugins/events.js";
6
8
  import { digest } from "./lifecycle.js";
7
- export { rulesInjector, rtk, zedBell };
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"],
23
- };
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
- };
9
+ import { matchesWildcard } from "./opencode-config.js";
10
+ export { rulesInjector, rtk, zedBell, codeSimplify, CODE_SIMPLIFY_ROLES };
11
+ export default {
12
+ id: "agentomatic",
13
+ async setup(ctx) {
14
+ const gate = new RoutingGate();
15
+ await ctx.session.hook("context", (event) => {
16
+ const text = `Current OpenCode session identity (host metadata): ${event.sessionID}. Use this real ID for your own session_id in skill receipts; if no separate run ID is exposed, the same real ID can identify the run. This metadata grants no task or publication authorization.`;
17
+ if (!event.system.some((part) => part.type === "text" && part.text === text))
18
+ event.system.push({ type: "text", text });
44
19
  });
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,
20
+ const hostInventory = async () => {
21
+ const { data: raw } = await ctx.agent.list({
22
+ location: { directory: ctx.location.directory },
23
+ });
24
+ const capabilities = {
25
+ mapper: ["read", "search"],
26
+ architect: ["read", "architecture"],
27
+ worker: ["read", "write", "verify"],
28
+ review: ["read", "review"],
29
+ critic: ["read", "review"],
77
30
  };
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,
31
+ const agents = raw.map((agent) => ({
32
+ agent: agent.id,
33
+ available: true,
34
+ capabilities: capabilities[agent.id] ??
35
+ (/^critic-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent.id) ? ["read", "review"] : ["read"]),
36
+ tools: ["read", "glob", "grep", "edit", "shell"].filter((action) => {
37
+ const rules = agent.permissions.filter((rule) => matchesWildcard(rule.action, action));
38
+ const broad = rules.reduce((last, rule, index) => (rule.resource === "*" ? index : last), -1);
39
+ return rules.slice(Math.max(0, broad)).some((rule) => rule.effect !== "deny");
40
+ }),
41
+ }));
42
+ return { agents, revision: digest(raw) };
43
+ };
44
+ await ctx.tool.transform((editor) => {
45
+ editor.add({
46
+ name: "route",
47
+ description: "Resolve a capability category and dispatch one eligible agent through a one-use subagent receipt gate.",
48
+ input: z.object({
49
+ action: z.enum(["preview", "dispatch"]),
50
+ category: z.enum(CATEGORIES),
51
+ task: z.string(),
52
+ requirements: z.array(z.string()).default([]),
53
+ execution_card: z.any().optional(),
54
+ override: z.string().optional(),
55
+ trusted_override: z.boolean().optional(),
56
+ budget: z
57
+ .object({ cost_class: z.string().optional(), latency_class: z.string().optional() })
58
+ .optional(),
59
+ decision: z.any().optional(),
60
+ }),
61
+ async execute(args, context) {
62
+ const inventory = await hostInventory();
63
+ const input = {
64
+ category: args.category,
65
+ task: args.task,
66
+ requirements: args.requirements,
67
+ agents: inventory.agents,
68
+ inventory_revision: inventory.revision,
69
+ execution_card: args.execution_card,
70
+ override: args.override,
71
+ trusted_override: args.trusted_override,
72
+ budget: args.budget,
73
+ };
74
+ if (args.action === "preview")
75
+ return { content: JSON.stringify(gate.preview(input)) };
76
+ const decision = gate.dispatch(input, args.decision);
77
+ if (decision.agent === "worker" && args.execution_card === undefined)
78
+ throw new Error("implementation dispatch requires a confirmed execution card");
79
+ const receipt = gate.grant(context.sessionID, decision, {
80
+ task: args.task,
81
+ requirements: args.requirements,
82
+ card: args.execution_card,
83
+ });
84
+ return { content: JSON.stringify({ decision, receipt, status: "routed" }) };
85
+ },
87
86
  });
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")
87
+ });
88
+ await ctx.tool.hook("execute.before", async (event) => {
89
+ if (event.tool !== "subagent")
90
+ return;
91
+ const args = toolInput(event.input);
92
+ const agent = typeof args.agent === "string" ? args.agent : undefined;
93
+ if (!gate.requiresReceipt(event.sessionID, agent ?? event.agent))
95
94
  return;
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;
104
95
  if (!agent)
105
- throw new Error("Native Task requires an explicit agent and an active routing receipt");
96
+ throw new Error("Native subagent requires an explicit agent and an active routing receipt");
106
97
  const hasBinding = "task" in args || "requirements" in args || "execution_card" in args;
107
98
  if (!hasBinding)
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")
99
+ gate.consume(event.sessionID, agent, undefined, event.id);
100
+ else
101
+ gate.consume(event.sessionID, agent, {
102
+ task: typeof args.task === "string" ? args.task : "",
103
+ requirements: Array.isArray(args.requirements) ? args.requirements : [],
104
+ card: args.execution_card,
105
+ }, event.id);
106
+ });
107
+ await ctx.tool.hook("execute.after", async (event) => {
108
+ if (event.tool !== "subagent")
119
109
  return;
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");
110
+ const args = toolInput(event.input);
111
+ if (typeof args.agent !== "string" || !gate.hasActive(event.sessionID, args.agent, event.id))
112
+ return;
113
+ if (event.status !== "completed") {
114
+ gate.cancel(event.sessionID, event.id);
115
+ return;
116
+ }
128
117
  let result;
129
118
  try {
130
- result = JSON.parse(output.output);
119
+ result = JSON.parse(resultText(event.result) ?? "");
131
120
  }
132
121
  catch {
133
- throw new Error("Task result must be JSON structured report");
122
+ gate.cancel(event.sessionID, event.id);
123
+ throw new Error("Subagent result must be JSON structured report");
134
124
  }
135
- gate.complete(input.sessionID, agent, result);
136
- },
137
- };
138
- });
139
- export const server = plugin;
140
- export default plugin;
125
+ try {
126
+ gate.complete(event.sessionID, args.agent, result, event.id);
127
+ }
128
+ finally {
129
+ gate.cancel(event.sessionID, event.id);
130
+ }
131
+ });
132
+ },
133
+ };
@@ -1,3 +1,4 @@
1
+ import { type AgentProfileRequest } from "./agent-profiles.js";
1
2
  import { LifecycleError, type FileMutation, type Scope, type TransactionOptions } from "./lifecycle.js";
2
3
  import { CATALOG } from "./catalog.js";
3
4
  import { type FixedAgentRole } from "./agent-profiles.js";
@@ -30,6 +31,24 @@ export type Plan = {
30
31
  applied?: "changed" | "unchanged" | "skipped";
31
32
  };
32
33
  requires_restart: boolean;
34
+ digest: string;
35
+ };
36
+ type ManifestFile = {
37
+ sha256: string;
38
+ mode: number;
39
+ kind: "command" | "agent" | "plugin" | "state";
40
+ };
41
+ type Manifest = {
42
+ schema_version: 2;
43
+ package: string;
44
+ package_version: string;
45
+ version: string;
46
+ scope: Scope;
47
+ commands: string[];
48
+ agents: FixedAgentRole[];
49
+ plugins: SelectablePlugin[];
50
+ core_activation: boolean;
51
+ files: Record<string, ManifestFile>;
33
52
  };
34
53
  export type ArchiveCandidate = {
35
54
  path: string;
@@ -44,12 +63,16 @@ export type ArchiveCandidate = {
44
63
  export declare const SELECTABLE_PLUGINS: SelectablePlugin[];
45
64
  export declare const SKILL_COMMANDS: string[];
46
65
  export declare function defaultSelection(): InstallerSelection;
66
+ export declare function savedSelection(manifest: Pick<Manifest, "commands" | "agents" | "plugins">): Partial<InstallerSelection>;
67
+ export declare function installedSelection(scope: Scope, cwd?: string, home?: string, overrides?: Partial<InstallerSelection>): Promise<InstallerSelection | null>;
68
+ export declare function coreSelectionMutation(root: string, enabled: boolean): Promise<FileMutation | undefined>;
47
69
  export declare function normalizeSelection(value?: Partial<InstallerSelection>): InstallerSelection;
48
70
  export declare class InstallerError extends LifecycleError {
49
71
  }
50
72
  export declare function archiveMutations(candidates: readonly ArchiveCandidate[], scope: Scope, cwd: string, home: string, sourceVersion: string, timestamp?: string): Promise<FileMutation[]>;
51
- export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string, selection?: Partial<InstallerSelection>, provisionDependency?: boolean): Promise<Plan>;
73
+ export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string, selection?: Partial<InstallerSelection>, provisionDependency?: boolean, profileChanges?: AgentProfileRequest[]): Promise<Plan>;
52
74
  export declare function apply(action: Action, scope: Scope, cwd?: string, home?: string, options?: TransactionOptions & {
53
75
  dependencyRunner?: DependencyRunner;
54
- }, selection?: Partial<InstallerSelection>, provisionDependency?: boolean): Promise<Plan>;
76
+ expectedDigest?: string;
77
+ }, selection?: Partial<InstallerSelection>, provisionDependency?: boolean, profileChanges?: AgentProfileRequest[]): Promise<Plan>;
55
78
  export declare function result(plan: Plan, applied: boolean): string;