@zeroroot-ai/zerocool 0.7.0 → 0.9.0

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/watch.js CHANGED
@@ -1,4 +1,4 @@
1
- import { remember, taskKnowledge } from "@zeroroot-ai/sdk";
1
+ import { buildCreateMissionRequest, remember, taskKnowledge } from "@zeroroot-ai/sdk";
2
2
  import { TRIGGER_STATUS } from "./gitlab.js";
3
3
  /**
4
4
  * The always-on watch loop (zerocool-plugins#88).
@@ -222,81 +222,47 @@ export function worldCheckpoints(harness, application) {
222
222
  export function checkpointQueryText(application) {
223
223
  return `${CHECKPOINT_KIND} application=${application}`;
224
224
  }
225
+ // ---------------------------------------------------------------------------
226
+ // The Scan mission
227
+ // ---------------------------------------------------------------------------
225
228
  /**
226
- * The Scan mission definition, as canonical proto JSON.
229
+ * The name of the checked-in Scan mission in gibson's mission catalog.
227
230
  *
228
- * STILL BUILT HERE, AND IT SHOULD NOT BE. gibson#1685 landed the Scan mission
229
- * as a checked-in definition in gibson's `missioncatalog`, and ADR-0018 makes
230
- * that copy authoritative: this function is meant to become a mission name plus
231
- * seven parameters, not a second description of the same graph.
231
+ * One file in `internal/platform/missioncatalog/missions/` is one mission,
232
+ * named for its file: `scan.cue` is `"scan"`.
233
+ */
234
+ export const SCAN_CATALOG_MISSION = "scan";
235
+ /**
236
+ * The seven parameters the checked-in Scan mission is rendered with.
232
237
  *
233
- * It cannot yet. `missioncatalog.Render` has no production caller, and
234
- * `CreateMission` carries only `mission_definition_json` — there is no field
235
- * naming a checked-in mission and none carrying its parameters, so an agent has
236
- * no way to ask for the catalog's copy. That is gibson#1688. Until it lands the
237
- * only way to originate a Scan is to send a graph, so this one stays, with the
238
- * shape the catalog definition expects: everything the fan-out needs is carried
239
- * in the node's task context, and the swap is this function's body alone.
238
+ * These names are the catalog's, not ours: `missioncatalog.Params.fields()` is
239
+ * the one declaration of them, and the daemon REFUSES an unrecognised key
240
+ * rather than ignoring it. That refusal is the smuggling defence — `Params` has
241
+ * no target or host field, so the runtime target binds from the mission's
242
+ * `target_id` alone and a caller cannot point a scan at a host the tenant never
243
+ * registered. A key renamed here without renaming it there fails loudly at the
244
+ * next launch, which is the intended outcome.
240
245
  *
241
- * The definition is built as protojson by hand rather than through the
242
- * generated schema because the SDK's package exports only its index, and the
243
- * mission schema is not re-exported from it.
246
+ * Every one is required. CUE refuses an incomplete render rather than
247
+ * substituting an empty string, so a missing commit fails instead of scanning
248
+ * HEAD and a missing image fails instead of scanning nothing.
244
249
  */
245
- export function scanMissionDefinition(inputs) {
246
- const { application, pipeline } = inputs;
247
- const context = {
248
- "zerocool.task": "source-analysis",
249
- application,
250
- "pipeline.id": String(pipeline.id),
251
- "pipeline.url": pipeline.webUrl,
252
- "repository.url": inputs.repositoryUrl,
253
- "repository.ref": pipeline.ref,
254
- "repository.commit": pipeline.sha,
255
- ...(inputs.imageRef ? { "image.ref": inputs.imageRef } : {}),
256
- ...(inputs.targetId ? { "target.id": inputs.targetId } : {}),
257
- };
250
+ export function scanCatalogParams(inputs) {
251
+ const { pipeline } = inputs;
258
252
  return {
259
- name: scanMissionName(inputs),
260
- description: `Scan ${application} at ${pipeline.sha.slice(0, 12)}, originated by the always-on agent ` +
261
- `for pipeline ${pipeline.id}.`,
262
- version: "1.0.0",
263
- nodes: {
264
- scan: {
265
- id: "scan",
266
- type: "NODE_TYPE_AGENT",
267
- name: "scan",
268
- description: "Source analysis of the commit the pipeline built.",
269
- agentConfig: {
270
- agentName: "zerocool",
271
- task: {
272
- id: "scan",
273
- goal: `Scan the ${application} repository at commit ${pipeline.sha} and submit a Finding for ` +
274
- "every real weakness you can support with evidence.",
275
- context: typedContext(context),
276
- },
277
- },
278
- },
279
- },
280
- entryPoints: ["scan"],
253
+ application: inputs.application,
254
+ repositoryUrl: inputs.repositoryUrl,
255
+ ref: pipeline.ref,
256
+ commit: pipeline.sha,
257
+ pipelineId: String(pipeline.id),
258
+ pipelineUrl: pipeline.webUrl,
259
+ imageRef: inputs.imageRef,
281
260
  };
282
261
  }
283
262
  /** The child mission's name, which is also how a person finds it in the dashboard. */
284
263
  export function scanMissionName(inputs) {
285
264
  return `scan ${inputs.application} pipeline ${inputs.pipeline.id}`;
286
265
  }
287
- /**
288
- * Render a string map as `map<string, gibson.common.v1.TypedValue>` in
289
- * protojson. Every value the loop passes is a string, so every entry is a
290
- * `stringValue`.
291
- */
292
- export function typedContext(ctx) {
293
- const out = {};
294
- for (const [k, v] of Object.entries(ctx)) {
295
- if (v !== "")
296
- out[k] = { stringValue: v };
297
- }
298
- return out;
299
- }
300
266
  /**
301
267
  * A {@link ScanLauncher} over the harness callbacks, on the dispatch grant.
302
268
  *
@@ -304,19 +270,43 @@ export function typedContext(ctx) {
304
270
  * daemon resolves the parent from this harness, so the Scan mission is a child
305
271
  * of the watch mission and inherits its tenant. Nothing in the request names a
306
272
  * parent, a tenant or an originator, so nothing here can widen them.
273
+ *
274
+ * THE MISSION IS NAMED, NOT BUILT (ADR-0018, gibson#1688). The Scan mission is
275
+ * checked into gibson's `missioncatalog` and that copy is authoritative; this
276
+ * launcher sends its name and seven parameters. It used to build its own graph,
277
+ * because `CreateMission` had no way to ask for the catalog's copy — two
278
+ * descriptions of one mission, which is the parallel definition ADR-0027
279
+ * forbids. There is no fallback to the old path: a definition that only one of
280
+ * the two knew about is the drift the single copy exists to prevent.
281
+ *
282
+ * The request goes through the SDK's `buildCreateMissionRequest` rather than
283
+ * being assembled here, because that builder normalises the one trap in this
284
+ * call. `JSON.stringify(undefined)` encodes to zero bytes but
285
+ * `JSON.stringify(null)` encodes to the four-byte string `"null"`, so a graph
286
+ * field filled in unconditionally puts a non-empty body on the wire beside the
287
+ * catalog name; the daemon refuses the pair, and the failure reads as "the
288
+ * catalog path does not work" rather than "you passed a null definition".
289
+ *
290
+ * The response is then sent by hand rather than through `createTaskMission`,
291
+ * for one reason: `CreateMissionResponse` carries an in-band `error`, and the
292
+ * daemon puts its most useful refusals there — an unknown parameter key naming
293
+ * the key and listing the known set, or every missing parameter reported at
294
+ * once. `createTaskMission` reads only `mission`, so those would surface as
295
+ * "returned no mission" and the operator would lose the sentence that says what
296
+ * to fix.
307
297
  */
308
298
  export function harnessScanLauncher(harness, opts) {
309
- const build = opts.definition ?? scanMissionDefinition;
310
299
  const timeoutMs = BigInt(opts.scanTimeoutMs ?? DEFAULT_SCAN_TIMEOUT_MS);
311
300
  return {
312
301
  async launch(pipeline) {
313
- const inputs = { ...opts.inputs, pipeline, targetId: opts.targetId };
314
- const created = await harness.client.createMission({
315
- context: harness.context,
316
- missionDefinitionJson: new TextEncoder().encode(JSON.stringify(build(inputs))),
302
+ const inputs = { ...opts.inputs, pipeline };
303
+ const wire = buildCreateMissionRequest({
304
+ catalogMission: SCAN_CATALOG_MISSION,
305
+ catalogParams: scanCatalogParams(inputs),
317
306
  targetId: opts.targetId,
318
307
  name: scanMissionName(inputs),
319
308
  });
309
+ const created = await harness.client.createMission({ context: harness.context, ...wire });
320
310
  if (created.error) {
321
311
  throw new Error(`CreateMission refused: ${created.error.message}`);
322
312
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zeroroot-ai/zerocool",
3
- "version": "0.7.0",
4
- "description": "opencode plugin: Gibson platform integration (zerocool). Zero-config LLM, tools, findings, knowledge, delegate/missions.",
3
+ "version": "0.9.0",
4
+ "description": "opencode host adapter for Gibson (zerocool): zero-config LLM through the harness, ambient knowledge, and the Gibson MCP server registered for you. The tools come from @zeroroot-ai/gibson-mcp.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -23,7 +23,7 @@
23
23
  "test": "tsc -p tsconfig.test.json && node --test \"dist-test/**/*.test.js\""
24
24
  },
25
25
  "dependencies": {
26
- "@zeroroot-ai/sdk": "^0.10.0"
26
+ "@zeroroot-ai/sdk": "^0.11.0"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@opencode-ai/plugin": "*"
@@ -1,10 +0,0 @@
1
- import type { ToolDefinition } from "@opencode-ai/plugin";
2
- import { type GibsonSession } from "@zeroroot-ai/sdk";
3
- /**
4
- * Write a validated component manifest to disk. Available with or without a
5
- * Gibson session — the manifest is the artifact a build pipeline needs, and
6
- * producing it must not require a platform connection.
7
- */
8
- export declare function componentizeTool(): ToolDefinition;
9
- /** Register a produced artifact with the tenant registry. */
10
- export declare function enrollComponentTool(session: GibsonSession): ToolDefinition;
@@ -1,149 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin";
2
- import { buildComponentManifest, enrollComponent, enrollmentSupported, validateComponentSpec, } from "@zeroroot-ai/sdk";
3
- import { mkdir, writeFile } from "node:fs/promises";
4
- import { dirname, resolve } from "node:path";
5
- /**
6
- * Componentize produced artifacts into the tenant fleet (zerocool-plugins#11).
7
- *
8
- * The factory payoff: an artifact the agent built becomes a registered Gibson
9
- * component. The contract is language-neutral — `RegisterComponent` describes a
10
- * component by kind, name, version and contract fields, never by the language
11
- * it was written in.
12
- *
13
- * Two tools, because the work splits at a real boundary:
14
- *
15
- * - `gibson_componentize` writes the manifest to disk without touching the
16
- * platform. It is the step that can always run, and the artefact it produces
17
- * is what a build pipeline consumes.
18
- * - `gibson_enroll_component` registers the manifest with the daemon.
19
- *
20
- * WHAT THIS DOES NOT DO. Image build and publish is a build-system concern, not
21
- * an RPC; the OCI reference is an input here, carried in `metadata.image`. And
22
- * enrollment currently registers under the CALLING agent's identity — the
23
- * policy-bounded RPC that would give a produced component its own short-TTL
24
- * credential is gibson#1185 and does not exist yet. The tool says so in its
25
- * output rather than implying the artifact is independently dispatchable.
26
- */
27
- const KINDS = ["agent", "tool", "plugin"];
28
- /** Shared argument shape for both tools. */
29
- function specArgs() {
30
- const z = tool.schema;
31
- return {
32
- kind: z.enum(KINDS).describe("Component kind."),
33
- name: z.string().describe("Component name, as it will appear in the tenant registry."),
34
- version: z.string().describe('Semantic version of the artifact, e.g. "0.1.0".'),
35
- image: z
36
- .string()
37
- .optional()
38
- .describe("OCI image reference of the built artifact. Required before it can be dispatched."),
39
- language: z.string().optional().describe("Language the artifact is written in, for the catalog."),
40
- capabilities: z
41
- .array(z.string())
42
- .optional()
43
- .describe('Agent capabilities. Required when kind is "agent".'),
44
- methods: z.array(z.string()).optional().describe('Plugin method names. Required when kind is "plugin".'),
45
- input_message_type: z
46
- .string()
47
- .optional()
48
- .describe('Fully-qualified proto input type. Required when kind is "tool".'),
49
- output_message_type: z
50
- .string()
51
- .optional()
52
- .describe('Fully-qualified proto output type. Required when kind is "tool".'),
53
- config_schema_json: z.string().optional().describe("JSON Schema for a plugin's configuration."),
54
- };
55
- }
56
- /** Build a ComponentSpec from tool arguments. */
57
- function toSpec(args) {
58
- const metadata = {};
59
- if (args.image)
60
- metadata.image = args.image;
61
- if (args.language)
62
- metadata.language = args.language;
63
- return {
64
- kind: args.kind,
65
- name: args.name,
66
- version: args.version,
67
- metadata,
68
- ...(args.capabilities ? { capabilities: args.capabilities } : {}),
69
- ...(args.methods ? { methods: args.methods } : {}),
70
- ...(args.input_message_type ? { inputMessageType: args.input_message_type } : {}),
71
- ...(args.output_message_type ? { outputMessageType: args.output_message_type } : {}),
72
- ...(args.config_schema_json ? { configSchemaJson: args.config_schema_json } : {}),
73
- };
74
- }
75
- /**
76
- * Write a validated component manifest to disk. Available with or without a
77
- * Gibson session — the manifest is the artifact a build pipeline needs, and
78
- * producing it must not require a platform connection.
79
- */
80
- export function componentizeTool() {
81
- const z = tool.schema;
82
- return tool({
83
- description: "Turn a built artifact into a Gibson component manifest and write it to disk. " +
84
- "Use this after building a tool or agent you want to add to the fleet. Validates " +
85
- "the artifact against Gibson's component contract; does not build or push an image.",
86
- args: {
87
- ...specArgs(),
88
- path: z
89
- .string()
90
- .optional()
91
- .describe('Where to write the manifest. Defaults to "gibson-component.json".'),
92
- },
93
- async execute(args, ctx) {
94
- const spec = toSpec(args);
95
- const problems = validateComponentSpec(spec);
96
- if (problems.length > 0) {
97
- return {
98
- title: "invalid component spec",
99
- output: `This artifact does not satisfy Gibson's ${spec.kind} contract:\n${problems.map((p) => `- ${p}`).join("\n")}`,
100
- };
101
- }
102
- const manifest = buildComponentManifest(spec);
103
- const target = resolve(ctx.directory, args.path ?? "gibson-component.json");
104
- await mkdir(dirname(target), { recursive: true });
105
- await writeFile(target, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
106
- const missingImage = spec.metadata?.image
107
- ? ""
108
- : "\n\nNo image reference was given. Build and push an image, then re-run with " +
109
- "image=<oci-ref> before enrolling — a component without an image cannot be dispatched.";
110
- return {
111
- title: `${spec.kind} manifest: ${spec.name}`,
112
- output: `Wrote a valid ${spec.kind} manifest for ${spec.name}@${spec.version} to ${target}.${missingImage}`,
113
- metadata: { path: target, kind: spec.kind, name: spec.name },
114
- };
115
- },
116
- });
117
- }
118
- /** Register a produced artifact with the tenant registry. */
119
- export function enrollComponentTool(session) {
120
- return tool({
121
- description: "Register a produced artifact with Gibson so it joins the tenant fleet. Run " +
122
- "gibson_componentize first to check the artifact against the component contract.",
123
- args: specArgs(),
124
- async execute(args) {
125
- const spec = toSpec(args);
126
- const problems = validateComponentSpec(spec);
127
- if (problems.length > 0) {
128
- return {
129
- title: "invalid component spec",
130
- output: `This artifact does not satisfy Gibson's ${spec.kind} contract:\n${problems.map((p) => `- ${p}`).join("\n")}`,
131
- };
132
- }
133
- const result = await enrollComponent(session.clients.component, spec);
134
- const { reason } = enrollmentSupported();
135
- return {
136
- title: `enrolled ${spec.name}`,
137
- output: `Registered ${spec.kind} ${spec.name}@${spec.version} as instance ${result.instanceId}.\n\n` +
138
- `Note: ${reason}. It is visible in the tenant registry, but it will drop out ` +
139
- `when heartbeats stop (every ${Math.round(result.heartbeatIntervalMs / 1000)}s) ` +
140
- `unless the artifact itself runs and checks in.`,
141
- metadata: {
142
- instance_id: result.instanceId,
143
- has_own_identity: result.hasOwnIdentity,
144
- kind: spec.kind,
145
- },
146
- };
147
- },
148
- });
149
- }
@@ -1,11 +0,0 @@
1
- import type { ToolDefinition } from "@opencode-ai/plugin";
2
- import { type GibsonSession } from "@zeroroot-ai/sdk";
3
- export declare function listAgentsTool(session: GibsonSession): ToolDefinition;
4
- export declare function delegateTool(session: GibsonSession): ToolDefinition;
5
- export declare function createMissionTool(session: GibsonSession): ToolDefinition;
6
- export declare function runMissionTool(session: GibsonSession): ToolDefinition;
7
- export declare function missionStatusTool(session: GibsonSession): ToolDefinition;
8
- export declare function missionResultsTool(session: GibsonSession): ToolDefinition;
9
- export declare function cancelMissionTool(session: GibsonSession): ToolDefinition;
10
- /** Every delegation and mission tool, keyed for the opencode `tool` hook. */
11
- export declare function delegationTools(session: GibsonSession): Record<string, ToolDefinition>;
package/dist/delegate.js DELETED
@@ -1,216 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin";
2
- import { cancelMission, createMission, delegateToAgent, getMissionResults, getMissionStatus, isSeamUnavailable, listAgents, newTask, runMission, waitMission, } from "@zeroroot-ai/sdk";
3
- /**
4
- * Delegation and missions — the factory's dispatch and recursion
5
- * (zerocool-plugins#10).
6
- *
7
- * Rules-of-engagement, authz and budget denials come back as daemon errors.
8
- * They are surfaced verbatim to the model rather than retried: a denial is a
9
- * policy decision, and an agent that retries a denied delegation is just
10
- * burning budget against a wall.
11
- *
12
- * Every RPC here needs the daemon's `agentDelegator` / `missionMgr` seams,
13
- * which are not wired (gibson#1186). Each tool reports that state plainly
14
- * instead of surfacing a bare "unimplemented" to the model.
15
- */
16
- const UNWIRED = "This daemon has no agent delegation or mission management wired (gibson#1186). " +
17
- "Complete the work directly instead.";
18
- export function listAgentsTool(session) {
19
- return tool({
20
- description: "List the Gibson agents in this tenant that work can be delegated to, with " +
21
- "their capabilities and the target types they handle.",
22
- args: {},
23
- async execute() {
24
- const { agents, unavailable } = await listAgents(session.clients.component);
25
- if (unavailable)
26
- return { title: "delegation unavailable", output: UNWIRED };
27
- if (agents.length === 0) {
28
- return { title: "no agents", output: "No agents are registered for this tenant." };
29
- }
30
- const lines = agents.map((a) => {
31
- const caps = a.capabilities.length > 0 ? ` — capabilities: ${a.capabilities.join(", ")}` : "";
32
- const targets = a.targetTypes.length > 0 ? ` — targets: ${a.targetTypes.join(", ")}` : "";
33
- return `- ${a.name} (${a.version}) ${a.description}${caps}${targets}`;
34
- });
35
- return {
36
- title: `${agents.length} agent${agents.length === 1 ? "" : "s"}`,
37
- output: lines.join("\n"),
38
- metadata: { agent_count: agents.length },
39
- };
40
- },
41
- });
42
- }
43
- export function delegateTool(session) {
44
- const z = tool.schema;
45
- return tool({
46
- description: "Delegate a sub-task to another Gibson agent and wait for its result. Use " +
47
- "gibson_list_agents first to pick an agent whose capabilities match the task.",
48
- args: {
49
- agent: z.string().describe("Name of the agent to delegate to."),
50
- goal: z.string().describe("What the delegate should accomplish."),
51
- context: z
52
- .record(z.string(), z.any())
53
- .optional()
54
- .describe("Target details, prior findings, or other context the delegate needs."),
55
- max_turns: z.number().optional().describe("Cap on the delegate's LLM turns."),
56
- allowed_tools: z.array(z.string()).optional().describe("Restrict the delegate to these tools."),
57
- },
58
- async execute(args) {
59
- const constraints = {
60
- ...(args.max_turns ? { MaxTurns: args.max_turns } : {}),
61
- ...(args.allowed_tools ? { AllowedTools: args.allowed_tools } : {}),
62
- };
63
- const task = newTask(args.goal, {
64
- ...(args.context ? { Context: args.context } : {}),
65
- ...(Object.keys(constraints).length > 0 ? { Constraints: constraints } : {}),
66
- });
67
- try {
68
- const result = await delegateToAgent(session.clients.component, args.agent, task);
69
- const output = typeof result.Output === "string" ? result.Output : JSON.stringify(result.Output, null, 2);
70
- const findings = result.Findings && result.Findings.length > 0
71
- ? `\n\nFindings recorded: ${result.Findings.join(", ")}`
72
- : "";
73
- const failure = result.error?.message ? `\n\nError: ${result.error.message}` : "";
74
- return {
75
- title: `${args.agent}: ${result.Status}`,
76
- output: `${output ?? "(no output)"}${findings}${failure}`,
77
- metadata: { status: result.Status, finding_ids: result.Findings ?? [] },
78
- };
79
- }
80
- catch (e) {
81
- if (isSeamUnavailable(e))
82
- return { title: "delegation unavailable", output: UNWIRED };
83
- // Authz / RoE / budget denials land here. Show the daemon's reason.
84
- return {
85
- title: `${args.agent}: denied`,
86
- output: `Delegation to ${args.agent} was refused: ${e.message}`,
87
- };
88
- }
89
- },
90
- });
91
- }
92
- export function createMissionTool(session) {
93
- const z = tool.schema;
94
- return tool({
95
- description: "Create a Gibson mission from a mission definition, bound to a target. The " +
96
- "mission is not started — call gibson_run_mission to queue it.",
97
- args: {
98
- definition: z.record(z.string(), z.any()).describe("Mission definition object."),
99
- target_id: z.string().describe("Identifier of the target the mission runs against."),
100
- opts: z.record(z.string(), z.any()).optional().describe("Optional mission creation options."),
101
- },
102
- async execute(args) {
103
- try {
104
- const info = await createMission(session.clients.component, args.definition, args.target_id, args.opts);
105
- const id = (info.id ?? info.mission_id ?? info.missionId);
106
- return {
107
- title: id ? `mission ${id}` : "mission created",
108
- output: JSON.stringify(info, null, 2),
109
- ...(id ? { metadata: { mission_id: id } } : {}),
110
- };
111
- }
112
- catch (e) {
113
- if (isSeamUnavailable(e))
114
- return { title: "missions unavailable", output: UNWIRED };
115
- throw e;
116
- }
117
- },
118
- });
119
- }
120
- export function runMissionTool(session) {
121
- const z = tool.schema;
122
- return tool({
123
- description: "Queue a created Gibson mission for execution. Returns as soon as it is queued.",
124
- args: { mission_id: z.string().describe("Mission to run.") },
125
- async execute(args) {
126
- try {
127
- await runMission(session.clients.component, args.mission_id);
128
- return { title: "queued", output: `Mission ${args.mission_id} is queued for execution.` };
129
- }
130
- catch (e) {
131
- if (isSeamUnavailable(e))
132
- return { title: "missions unavailable", output: UNWIRED };
133
- throw e;
134
- }
135
- },
136
- });
137
- }
138
- export function missionStatusTool(session) {
139
- const z = tool.schema;
140
- return tool({
141
- description: "Check a Gibson mission's status, or block until it finishes. Use wait=true " +
142
- "only when you intend to stop and wait for the result.",
143
- args: {
144
- mission_id: z.string().describe("Mission to inspect."),
145
- wait: z.boolean().optional().describe("Block until the mission reaches a terminal state."),
146
- timeout_ms: z
147
- .number()
148
- .optional()
149
- .describe("How long to block when wait is true. Defaults to 5 minutes."),
150
- },
151
- async execute(args) {
152
- try {
153
- const state = args.wait
154
- ? await waitMission(session.clients.component, args.mission_id, args.timeout_ms ?? 300_000)
155
- : await getMissionStatus(session.clients.component, args.mission_id);
156
- return {
157
- title: `mission ${args.mission_id}`,
158
- output: JSON.stringify(state, null, 2),
159
- };
160
- }
161
- catch (e) {
162
- if (isSeamUnavailable(e))
163
- return { title: "missions unavailable", output: UNWIRED };
164
- throw e;
165
- }
166
- },
167
- });
168
- }
169
- export function missionResultsTool(session) {
170
- const z = tool.schema;
171
- return tool({
172
- description: "Read the final results of a completed Gibson mission.",
173
- args: { mission_id: z.string().describe("Mission whose results to read.") },
174
- async execute(args) {
175
- try {
176
- const results = await getMissionResults(session.clients.component, args.mission_id);
177
- return { title: `results ${args.mission_id}`, output: JSON.stringify(results, null, 2) };
178
- }
179
- catch (e) {
180
- if (isSeamUnavailable(e))
181
- return { title: "missions unavailable", output: UNWIRED };
182
- throw e;
183
- }
184
- },
185
- });
186
- }
187
- export function cancelMissionTool(session) {
188
- const z = tool.schema;
189
- return tool({
190
- description: "Request cancellation of a running Gibson mission.",
191
- args: { mission_id: z.string().describe("Mission to cancel.") },
192
- async execute(args) {
193
- try {
194
- await cancelMission(session.clients.component, args.mission_id);
195
- return { title: "cancelled", output: `Cancellation requested for mission ${args.mission_id}.` };
196
- }
197
- catch (e) {
198
- if (isSeamUnavailable(e))
199
- return { title: "missions unavailable", output: UNWIRED };
200
- throw e;
201
- }
202
- },
203
- });
204
- }
205
- /** Every delegation and mission tool, keyed for the opencode `tool` hook. */
206
- export function delegationTools(session) {
207
- return {
208
- gibson_list_agents: listAgentsTool(session),
209
- gibson_delegate: delegateTool(session),
210
- gibson_create_mission: createMissionTool(session),
211
- gibson_run_mission: runMissionTool(session),
212
- gibson_mission_status: missionStatusTool(session),
213
- gibson_mission_results: missionResultsTool(session),
214
- gibson_cancel_mission: cancelMissionTool(session),
215
- };
216
- }
@@ -1,45 +0,0 @@
1
- import type { ToolDefinition } from "@opencode-ai/plugin";
2
- import { type GibsonSession, type GibsonTool } from "@zeroroot-ai/sdk";
3
- /**
4
- * Gibson tools and plugins as opencode tools (zerocool-plugins#9).
5
- *
6
- * Discovered tools are registered **individually** — one opencode tool per
7
- * Gibson tool, named `gibson_<tool>` — so the model sees them in its tool list
8
- * with their own descriptions, rather than having to know that a generic
9
- * dispatcher exists and what to pass it.
10
- *
11
- * ON SCHEMAS — the catalog carries no JSON Schema. `ToolDescriptorProto` only
12
- * names the proto message types of a tool's input and output
13
- * (`gibson.tool.v1.ScanRequest`), and there is no RPC that resolves those to a
14
- * schema. So a per-tool wrapper takes a single free-form `input` object and
15
- * names the expected message type in its description. Inventing a Zod schema
16
- * from a message type name would be a guess, and a wrong guess would reject
17
- * valid input before it ever reached the tool.
18
- *
19
- * A generic `gibson_call_tool` is always registered as well: discovery is
20
- * `Unimplemented` on the current daemon (gibson#1186), and without it there
21
- * would be no way to reach a tool whose name the operator already knows.
22
- */
23
- /** Sanitise a Gibson tool name into an opencode tool key. */
24
- export declare function toolKey(name: string): string;
25
- /** Wrap one discovered Gibson tool as an opencode tool. */
26
- export declare function wrapGibsonTool(session: GibsonSession, t: GibsonTool): ToolDefinition;
27
- /** The always-present dispatcher for tools the catalog did not surface. */
28
- export declare function callToolTool(session: GibsonSession): ToolDefinition;
29
- /** List what Gibson exposes to this tenant — tools and enabled plugins. */
30
- export declare function listToolsTool(session: GibsonSession): ToolDefinition;
31
- /** Invoke a method on an enabled Gibson plugin. */
32
- export declare function queryPluginTool(session: GibsonSession): ToolDefinition;
33
- /**
34
- * Discover the tenant's tools and build one opencode tool for each, plus the
35
- * always-present dispatcher, catalog and plugin tools.
36
- *
37
- * Discovery runs once at plugin load. opencode reads the `tool` hook as a
38
- * static object, so a tool registered later in the session would not be visible
39
- * to the model anyway.
40
- */
41
- export declare function buildGibsonTools(session: GibsonSession): Promise<{
42
- tools: Record<string, ToolDefinition>;
43
- discovered: number;
44
- note?: string;
45
- }>;