@zeroroot-ai/zerocool 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZeroRoot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
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;
@@ -0,0 +1,149 @@
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
+ }
@@ -0,0 +1,11 @@
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>;
@@ -0,0 +1,216 @@
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
+ }
@@ -0,0 +1,27 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin";
2
+ import { type Finding, type GibsonSession } from "@zeroroot-ai/sdk";
3
+ export interface FindingsBackend {
4
+ /** Submit a finding; returns the identifier it was recorded under. */
5
+ submit(f: Finding): Promise<string>;
6
+ /** Where findings go, for the tool's result message. */
7
+ describe(): string;
8
+ }
9
+ /** Platform backend — findings land in the tenant knowledge graph. */
10
+ export declare function gibsonFindingsBackend(session: GibsonSession): FindingsBackend;
11
+ /**
12
+ * Standalone backend — findings are appended as JSONL. The same Finding shape is
13
+ * written, so a local log can be replayed into Gibson later.
14
+ */
15
+ export declare function localFindingsBackend(path: string): FindingsBackend;
16
+ /** Provenance stamped onto every finding this session emits. */
17
+ export interface SessionContext {
18
+ /** opencode session ID, recorded as the finding's mission correlation. */
19
+ sessionID?: string;
20
+ }
21
+ /**
22
+ * Build the `submit_finding` tool.
23
+ *
24
+ * `agent_name` is fixed to "zerocool" rather than exposed as an argument — a
25
+ * model must not be able to attribute its findings to another agent.
26
+ */
27
+ export declare function submitFindingTool(backend: FindingsBackend, context: SessionContext): ToolDefinition;
@@ -0,0 +1,109 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { newFinding, submitFinding, } from "@zeroroot-ai/sdk";
3
+ import { appendFile, mkdir } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ /**
6
+ * Findings — emit what the agent discovers into the tenant World
7
+ * (zerocool-plugins#7).
8
+ *
9
+ * One code path, two backends. With a Gibson session a finding goes to the
10
+ * tenant graph through `ComponentService.SubmitFinding` under the agent's
11
+ * COMPONENT identity; standalone it is appended to a local JSONL log. The tool
12
+ * the model sees is identical either way, so a prompt that works standalone
13
+ * works on the platform.
14
+ *
15
+ * The agent decides what is a finding. Nothing here invents findings from file
16
+ * edits or message traffic: a `file.edited` event is not a security finding, and
17
+ * auto-emitting one would fill the tenant graph with noise that a human then has
18
+ * to triage. The `event` hook only tracks session context used to stamp findings
19
+ * with provenance.
20
+ */
21
+ const SEVERITIES = ["critical", "high", "medium", "low", "info"];
22
+ /** Platform backend — findings land in the tenant knowledge graph. */
23
+ export function gibsonFindingsBackend(session) {
24
+ return {
25
+ submit: (f) => submitFinding(session.clients.component, f),
26
+ describe: () => "the tenant Gibson graph",
27
+ };
28
+ }
29
+ /**
30
+ * Standalone backend — findings are appended as JSONL. The same Finding shape is
31
+ * written, so a local log can be replayed into Gibson later.
32
+ */
33
+ export function localFindingsBackend(path) {
34
+ return {
35
+ submit: async (f) => {
36
+ await mkdir(dirname(path), { recursive: true });
37
+ await appendFile(path, `${JSON.stringify(f)}\n`, "utf8");
38
+ return f.id;
39
+ },
40
+ describe: () => path,
41
+ };
42
+ }
43
+ /**
44
+ * Build the `submit_finding` tool.
45
+ *
46
+ * `agent_name` is fixed to "zerocool" rather than exposed as an argument — a
47
+ * model must not be able to attribute its findings to another agent.
48
+ */
49
+ export function submitFindingTool(backend, context) {
50
+ const z = tool.schema;
51
+ return tool({
52
+ description: "Record a security finding in the Gibson knowledge graph. Use this when you " +
53
+ "discover a real vulnerability, misconfiguration or notable security fact about " +
54
+ "the target or codebase — not for progress updates or general observations.",
55
+ args: {
56
+ title: z.string().describe("One-line summary of the finding."),
57
+ description: z.string().describe("What the issue is, where it is, and why it matters."),
58
+ category: z
59
+ .string()
60
+ .describe('Type of issue, e.g. "injection", "auth", "secrets-exposure", "misconfiguration".'),
61
+ severity: z.enum(SEVERITIES).describe("Impact level of the finding."),
62
+ confidence: z
63
+ .number()
64
+ .min(0)
65
+ .max(1)
66
+ .optional()
67
+ .describe("How certain you are, from 0 to 1. Defaults to 1."),
68
+ evidence: z
69
+ .string()
70
+ .optional()
71
+ .describe("Supporting evidence — a code excerpt, request/response, or log line."),
72
+ remediation: z.string().optional().describe("How to fix or mitigate the issue."),
73
+ target_id: z.string().optional().describe("Identifier of the affected target or component."),
74
+ tags: z.array(z.string()).optional().describe("Labels for filtering."),
75
+ },
76
+ async execute(args, ctx) {
77
+ const finding = newFinding({
78
+ title: args.title,
79
+ description: args.description,
80
+ category: args.category,
81
+ severity: args.severity,
82
+ confidence: args.confidence,
83
+ missionID: context.sessionID ?? ctx.sessionID,
84
+ agentName: "zerocool",
85
+ ...(args.remediation ? { remediation: args.remediation } : {}),
86
+ ...(args.target_id ? { targetID: args.target_id } : {}),
87
+ ...(args.tags ? { tags: args.tags } : {}),
88
+ ...(args.evidence
89
+ ? {
90
+ evidence: [
91
+ {
92
+ type: "text",
93
+ title: "evidence",
94
+ content: args.evidence,
95
+ timestamp: new Date().toISOString(),
96
+ },
97
+ ],
98
+ }
99
+ : {}),
100
+ });
101
+ const id = await backend.submit(finding);
102
+ return {
103
+ title: `${args.severity}: ${args.title}`,
104
+ output: `Recorded finding ${id} in ${backend.describe()}.`,
105
+ metadata: { finding_id: id, severity: args.severity, category: args.category },
106
+ };
107
+ },
108
+ });
109
+ }
@@ -0,0 +1,45 @@
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
+ }>;