@zeroroot-ai/gibson-mcp 0.1.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 +21 -0
- package/README.md +160 -0
- package/dist/ambient.d.ts +7 -0
- package/dist/ambient.js +18 -0
- package/dist/ask.d.ts +70 -0
- package/dist/ask.js +111 -0
- package/dist/build.d.ts +83 -0
- package/dist/build.js +209 -0
- package/dist/cli.d.ts +88 -0
- package/dist/cli.js +186 -0
- package/dist/config.d.ts +45 -0
- package/dist/config.js +54 -0
- package/dist/discovery.d.ts +57 -0
- package/dist/discovery.js +132 -0
- package/dist/flags.d.ts +32 -0
- package/dist/flags.js +85 -0
- package/dist/generated/tools.d.ts +15 -0
- package/dist/generated/tools.js +276 -0
- package/dist/helpers/componentize.d.ts +19 -0
- package/dist/helpers/componentize.js +106 -0
- package/dist/helpers/context.d.ts +19 -0
- package/dist/helpers/context.js +19 -0
- package/dist/helpers/coverage.d.ts +23 -0
- package/dist/helpers/coverage.js +119 -0
- package/dist/helpers/delegate.d.ts +4 -0
- package/dist/helpers/delegate.js +182 -0
- package/dist/helpers/findings.d.ts +24 -0
- package/dist/helpers/findings.js +118 -0
- package/dist/helpers/index.d.ts +17 -0
- package/dist/helpers/index.js +24 -0
- package/dist/helpers/knowledge.d.ts +16 -0
- package/dist/helpers/knowledge.js +161 -0
- package/dist/helpers/tools.d.ts +113 -0
- package/dist/helpers/tools.js +80 -0
- package/dist/http.d.ts +57 -0
- package/dist/http.js +137 -0
- package/dist/inbox.d.ts +88 -0
- package/dist/inbox.js +176 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +25 -0
- package/dist/log.d.ts +4 -0
- package/dist/log.js +5 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +61 -0
- package/dist/mode.d.ts +32 -0
- package/dist/mode.js +21 -0
- package/dist/registry.d.ts +83 -0
- package/dist/registry.js +133 -0
- package/dist/resources.d.ts +63 -0
- package/dist/resources.js +98 -0
- package/dist/rpc.d.ts +80 -0
- package/dist/rpc.js +184 -0
- package/dist/schema.d.ts +31 -0
- package/dist/schema.js +143 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.js +70 -0
- package/dist/session.d.ts +41 -0
- package/dist/session.js +170 -0
- package/dist/source.d.ts +30 -0
- package/dist/source.js +23 -0
- package/dist/state.d.ts +29 -0
- package/dist/state.js +37 -0
- package/dist/tls.d.ts +1 -0
- package/dist/tls.js +19 -0
- package/dist/tool.d.ts +17 -0
- package/dist/tool.js +22 -0
- package/dist/tools/connect.d.ts +24 -0
- package/dist/tools/connect.js +115 -0
- package/dist/tools/result.d.ts +7 -0
- package/dist/tools/result.js +16 -0
- package/dist/tools/status.d.ts +5 -0
- package/dist/tools/status.js +36 -0
- package/dist/turn.d.ts +75 -0
- package/dist/turn.js +95 -0
- package/package.json +58 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { buildComponentManifest, enrollComponent, enrollmentSupported, validateComponentSpec } from "@zeroroot-ai/sdk";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { defineTool } from "../tool.js";
|
|
6
|
+
import { failure, json, text } from "../tools/result.js";
|
|
7
|
+
/** Componentize and enroll: produced artifacts join the fleet. */
|
|
8
|
+
const KINDS = ["agent", "tool", "plugin"];
|
|
9
|
+
const specSchema = {
|
|
10
|
+
kind: z.enum(KINDS).describe("Component kind."),
|
|
11
|
+
name: z.string().describe("Component name, as it will appear in the tenant registry."),
|
|
12
|
+
version: z.string().describe('Semantic version of the artifact, e.g. "0.1.0".'),
|
|
13
|
+
image: z.string().optional().describe("OCI image reference of the built artifact. Required before it can be dispatched."),
|
|
14
|
+
language: z.string().optional().describe("Language the artifact is written in, for the catalog."),
|
|
15
|
+
capabilities: z.array(z.string()).optional().describe('Agent capabilities. Required when kind is "agent".'),
|
|
16
|
+
methods: z.array(z.string()).optional().describe('Plugin method names. Required when kind is "plugin".'),
|
|
17
|
+
input_message_type: z.string().optional().describe('Fully-qualified proto input type. Required when kind is "tool".'),
|
|
18
|
+
output_message_type: z.string().optional().describe('Fully-qualified proto output type. Required when kind is "tool".'),
|
|
19
|
+
config_schema_json: z.string().optional().describe("JSON Schema for a plugin's configuration."),
|
|
20
|
+
};
|
|
21
|
+
export function toSpec(args) {
|
|
22
|
+
const metadata = {};
|
|
23
|
+
if (args.image)
|
|
24
|
+
metadata.image = args.image;
|
|
25
|
+
if (args.language)
|
|
26
|
+
metadata.language = args.language;
|
|
27
|
+
return {
|
|
28
|
+
kind: args.kind,
|
|
29
|
+
name: args.name,
|
|
30
|
+
version: args.version,
|
|
31
|
+
metadata,
|
|
32
|
+
...(args.capabilities ? { capabilities: args.capabilities } : {}),
|
|
33
|
+
...(args.methods ? { methods: args.methods } : {}),
|
|
34
|
+
...(args.input_message_type ? { inputMessageType: args.input_message_type } : {}),
|
|
35
|
+
...(args.output_message_type ? { outputMessageType: args.output_message_type } : {}),
|
|
36
|
+
...(args.config_schema_json ? { configSchemaJson: args.config_schema_json } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function invalid(spec, problems) {
|
|
40
|
+
return failure("invalid component spec", `This artifact does not satisfy Gibson's ${spec.kind} contract:\n${problems.map((p) => `- ${p}`).join("\n")}`);
|
|
41
|
+
}
|
|
42
|
+
export function componentizeTools(ctx) {
|
|
43
|
+
const out = [
|
|
44
|
+
defineTool({
|
|
45
|
+
name: "validate_component",
|
|
46
|
+
description: "Check an artifact against Gibson's component contract without writing anything. Names every problem it finds.",
|
|
47
|
+
input: specSchema,
|
|
48
|
+
annotations: { readOnlyHint: true },
|
|
49
|
+
handler: async (args) => {
|
|
50
|
+
const spec = toSpec(args);
|
|
51
|
+
const problems = validateComponentSpec(spec);
|
|
52
|
+
if (problems.length > 0)
|
|
53
|
+
return invalid(spec, problems);
|
|
54
|
+
return text("valid", `${spec.kind} ${spec.name}@${spec.version} satisfies the component contract.`);
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
defineTool({
|
|
58
|
+
name: "componentize",
|
|
59
|
+
description: "Turn a built artifact into a Gibson component manifest and write it to disk. Use this after " +
|
|
60
|
+
"building a tool or agent you want to add to the fleet. Validates the artifact against " +
|
|
61
|
+
"Gibson's component contract. Does not build or push an image.",
|
|
62
|
+
input: { ...specSchema, path: z.string().optional().describe('Where to write the manifest. Defaults to "gibson-component.json".') },
|
|
63
|
+
handler: async (args) => {
|
|
64
|
+
const spec = toSpec(args);
|
|
65
|
+
const problems = validateComponentSpec(spec);
|
|
66
|
+
if (problems.length > 0)
|
|
67
|
+
return invalid(spec, problems);
|
|
68
|
+
const manifest = buildComponentManifest(spec);
|
|
69
|
+
const target = resolve(ctx.cwd, args.path ?? "gibson-component.json");
|
|
70
|
+
await mkdir(dirname(target), { recursive: true });
|
|
71
|
+
await writeFile(target, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
72
|
+
const missingImage = spec.metadata?.image
|
|
73
|
+
? ""
|
|
74
|
+
: "\n\nNo image reference was given. Build and push an image, then re-run with image=<oci-ref> before you enroll. A component without an image cannot be dispatched.";
|
|
75
|
+
return text(`${spec.kind} manifest: ${spec.name}`, `Wrote a valid ${spec.kind} manifest for ${spec.name}@${spec.version} to ${target}.${missingImage}`);
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
];
|
|
79
|
+
if (ctx.gibson.session) {
|
|
80
|
+
const component = ctx.gibson.session.clients.component;
|
|
81
|
+
out.push(defineTool({
|
|
82
|
+
name: "enroll_component",
|
|
83
|
+
description: "Register a produced artifact with Gibson so it joins the tenant fleet. Run componentize first to check the artifact against the component contract.",
|
|
84
|
+
input: specSchema,
|
|
85
|
+
handler: async (args) => {
|
|
86
|
+
const spec = toSpec(args);
|
|
87
|
+
const problems = validateComponentSpec(spec);
|
|
88
|
+
if (problems.length > 0)
|
|
89
|
+
return invalid(spec, problems);
|
|
90
|
+
try {
|
|
91
|
+
const result = await enrollComponent(component, spec);
|
|
92
|
+
const { reason } = enrollmentSupported();
|
|
93
|
+
return text(`enrolled ${spec.name}`, `Registered ${spec.kind} ${spec.name}@${spec.version} as instance ${result.instanceId}.\n\nNote: ${reason}. It is visible in the tenant registry, but it drops out when heartbeats stop (every ${Math.round(result.heartbeatIntervalMs / 1000)}s) unless the artifact itself runs and checks in.`);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
return failure("enroll failed", e.message);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/** Kept exported so a caller can inspect the manifest a spec would produce. */
|
|
104
|
+
export function manifestOf(args) {
|
|
105
|
+
return json(buildComponentManifest(toSpec(args)));
|
|
106
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Gibson } from "../session.js";
|
|
2
|
+
import type { FindingsBackend } from "./findings.js";
|
|
3
|
+
/** What every helper tool is built from. Decided once per posture. */
|
|
4
|
+
export interface HelperContext {
|
|
5
|
+
gibson: Gibson;
|
|
6
|
+
/** The working directory a componentize writes into. */
|
|
7
|
+
cwd: string;
|
|
8
|
+
env: NodeJS.ProcessEnv;
|
|
9
|
+
/** Where a finding lands in this posture. */
|
|
10
|
+
findings: FindingsBackend;
|
|
11
|
+
}
|
|
12
|
+
export declare function helperContext(gibson: Gibson, cwd: string, env: NodeJS.ProcessEnv): HelperContext;
|
|
13
|
+
/**
|
|
14
|
+
* A finding always has somewhere to go. Under a task grant it is the typed
|
|
15
|
+
* callback RPC; as a checked-in component it is ComponentService; with no
|
|
16
|
+
* platform it is a local JSONL log, so a standalone session still records
|
|
17
|
+
* what it found instead of dropping it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findingsBackendFor(gibson: Gibson, env: NodeJS.ProcessEnv): FindingsBackend;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { gibsonFindingsBackend, localFindingsBackend, taskFindingsBackend } from "./findings.js";
|
|
4
|
+
export function helperContext(gibson, cwd, env) {
|
|
5
|
+
return { gibson, cwd, env, findings: findingsBackendFor(gibson, env) };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A finding always has somewhere to go. Under a task grant it is the typed
|
|
9
|
+
* callback RPC; as a checked-in component it is ComponentService; with no
|
|
10
|
+
* platform it is a local JSONL log, so a standalone session still records
|
|
11
|
+
* what it found instead of dropping it.
|
|
12
|
+
*/
|
|
13
|
+
export function findingsBackendFor(gibson, env) {
|
|
14
|
+
if (gibson.live)
|
|
15
|
+
return taskFindingsBackend(gibson.live.harness);
|
|
16
|
+
if (gibson.session)
|
|
17
|
+
return gibsonFindingsBackend(gibson.session);
|
|
18
|
+
return localFindingsBackend(env.ZEROCOOL_FINDINGS_LOG ?? join(homedir(), ".zerocool", "findings.jsonl"));
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which SDK export each helper tool comes from, and why the rest are not
|
|
3
|
+
* tools.
|
|
4
|
+
*
|
|
5
|
+
* The MCP surface is 1:1 with everything the SDK produces (gibson#1706,
|
|
6
|
+
* decision 2). The RPC half is generated, so it cannot fall behind. The
|
|
7
|
+
* helper half is hand-signed, so this table is what keeps it honest: a guard
|
|
8
|
+
* test lists the SDK's exports and fails when one is in neither map, which
|
|
9
|
+
* means a new helper cannot land in the SDK without a decision here.
|
|
10
|
+
*
|
|
11
|
+
* A reason is required, not optional. "Not a tool" with no reason is how a
|
|
12
|
+
* surface quietly loses half of itself.
|
|
13
|
+
*/
|
|
14
|
+
/** SDK export -> the tool built on it. Several exports may back one tool. */
|
|
15
|
+
export declare const HELPER_TOOL_FOR_EXPORT: Record<string, string>;
|
|
16
|
+
/** SDK export -> why it is not a tool. */
|
|
17
|
+
export declare const NOT_A_TOOL: Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* Tools with no single SDK export behind them, and where they come from.
|
|
20
|
+
* The guard walks this direction too, so a tool cannot appear with no
|
|
21
|
+
* account of itself.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TOOL_WITHOUT_EXPORT: Record<string, string>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which SDK export each helper tool comes from, and why the rest are not
|
|
3
|
+
* tools.
|
|
4
|
+
*
|
|
5
|
+
* The MCP surface is 1:1 with everything the SDK produces (gibson#1706,
|
|
6
|
+
* decision 2). The RPC half is generated, so it cannot fall behind. The
|
|
7
|
+
* helper half is hand-signed, so this table is what keeps it honest: a guard
|
|
8
|
+
* test lists the SDK's exports and fails when one is in neither map, which
|
|
9
|
+
* means a new helper cannot land in the SDK without a decision here.
|
|
10
|
+
*
|
|
11
|
+
* A reason is required, not optional. "Not a tool" with no reason is how a
|
|
12
|
+
* surface quietly loses half of itself.
|
|
13
|
+
*/
|
|
14
|
+
/** SDK export -> the tool built on it. Several exports may back one tool. */
|
|
15
|
+
export const HELPER_TOOL_FOR_EXPORT = {
|
|
16
|
+
buildComponentManifest: "componentize",
|
|
17
|
+
callGibsonTool: "gibson_call_tool",
|
|
18
|
+
cancelMission: "cancel_mission",
|
|
19
|
+
createMission: "create_mission",
|
|
20
|
+
createTaskMission: "create_task_mission",
|
|
21
|
+
delegateToAgent: "delegate",
|
|
22
|
+
enrollComponent: "enroll_component",
|
|
23
|
+
findSimilarAttacks: "similar_attacks",
|
|
24
|
+
findSimilarFindings: "similar_findings",
|
|
25
|
+
getAttackChains: "attack_chains",
|
|
26
|
+
getFindings: "get_findings",
|
|
27
|
+
getMissionResults: "mission_results",
|
|
28
|
+
getMissionStatus: "mission_status",
|
|
29
|
+
getRelatedFindings: "related_findings",
|
|
30
|
+
listAgents: "list_agents",
|
|
31
|
+
listGibsonPlugins: "list_gibson_plugins",
|
|
32
|
+
listGibsonTools: "list_gibson_tools",
|
|
33
|
+
listMissions: "list_missions",
|
|
34
|
+
newFinding: "submit_finding",
|
|
35
|
+
observe: "observe",
|
|
36
|
+
parseMaybeJSON: "parse_gibson_output",
|
|
37
|
+
queryGibsonPlugin: "query_plugin",
|
|
38
|
+
queryKnowledge: "recall",
|
|
39
|
+
remember: "remember",
|
|
40
|
+
runMission: "run_mission",
|
|
41
|
+
submitFinding: "submit_finding",
|
|
42
|
+
validateComponentSpec: "validate_component",
|
|
43
|
+
waitMission: "wait_mission",
|
|
44
|
+
};
|
|
45
|
+
/** SDK export -> why it is not a tool. */
|
|
46
|
+
export const NOT_A_TOOL = {
|
|
47
|
+
// Check-in and identity. The source is decided at start and the server
|
|
48
|
+
// never mints identity (ADR-0045), so no tool may reach these.
|
|
49
|
+
CapabilityGrantClient: "the check-in client; the credential source is decided at start, never by a tool",
|
|
50
|
+
connectGibson: "the check-in itself, done once at start",
|
|
51
|
+
createGibsonClients: "builds the service clients at start",
|
|
52
|
+
discover: "reads the platform discovery document at check-in",
|
|
53
|
+
generateAgentKey: "key material; the server never mints identity (ADR-0045)",
|
|
54
|
+
jwkThumbprint: "key material",
|
|
55
|
+
loadOrGenerateHostKey: "key material",
|
|
56
|
+
publicKeyJWK: "key material",
|
|
57
|
+
signAgentJWT: "key material",
|
|
58
|
+
signHostJWT: "key material",
|
|
59
|
+
registerAgent: "registers the component at check-in, before any tool exists",
|
|
60
|
+
registerComponentAs: "registers the component at check-in, before any tool exists",
|
|
61
|
+
registerInstance: "registers this process as one instance at check-in",
|
|
62
|
+
startHeartbeat: "the check-in keeps its own registration alive",
|
|
63
|
+
normalizePlatformURL: "canonicalizes the platform URL at check-in",
|
|
64
|
+
// Transport and grant plumbing. Every tool call already rides these.
|
|
65
|
+
callbackBaseUrl: "turns a dial target into a base URL at start",
|
|
66
|
+
contextFromGrant: "derives ContextInfo from the grant; every tool call already carries it",
|
|
67
|
+
decodeGrantClaims: "reads the addressing claims off the grant at start",
|
|
68
|
+
grantInterceptor: "puts the current grant on every request",
|
|
69
|
+
openTaskHarness: "opens the callback transport at start",
|
|
70
|
+
sandboxHarness: "opens a dispatched run's harness at start",
|
|
71
|
+
sessionHarness: "the component-grant harness, chosen at start",
|
|
72
|
+
readSandboxDispatch: "reads the dispatch contract off the environment at start",
|
|
73
|
+
taskFromB64: "decodes the dispatched task at start",
|
|
74
|
+
taskKnowledge: "chooses which grant recall reads over",
|
|
75
|
+
componentKnowledge: "chooses which grant recall reads over",
|
|
76
|
+
// The session mission is started by the check-in, not by a tool
|
|
77
|
+
// (ADR-0007 decision 3: one live mission per session).
|
|
78
|
+
componentOriginator: "who creates the session mission; decided at check-in",
|
|
79
|
+
liveMissionDefinition: "the definition the session mission is created from at start",
|
|
80
|
+
startLiveMission: "starts the session mission at check-in",
|
|
81
|
+
// Serving work. This server is a client of the platform, not a worker.
|
|
82
|
+
startWorker: "a served tool's own poll loop; this server serves no work",
|
|
83
|
+
startAgentWorker: "a served agent's own poll loop; this server serves no work",
|
|
84
|
+
decodeAgentExecute: "decodes a work payload inside a worker loop",
|
|
85
|
+
decodeToolInput: "decodes a work payload inside a worker loop",
|
|
86
|
+
encodeAgentError: "encodes a worker's result",
|
|
87
|
+
encodeAgentResult: "encodes a worker's result",
|
|
88
|
+
encodeToolError: "encodes a worker's result",
|
|
89
|
+
encodeToolOutput: "encodes a worker's result",
|
|
90
|
+
// The model stays on the host's own provider (ADR-0007). The LLM shim is
|
|
91
|
+
// the opencode adapter's business and never a tool.
|
|
92
|
+
startCompletionsShim: "the local OpenAI-compatible shim; a host adapter starts it, and the model never routes through this server",
|
|
93
|
+
// Encoding and formatting the tools already do for the caller.
|
|
94
|
+
calculateRiskScore: "arithmetic on a severity and a confidence; no round trip is worth it",
|
|
95
|
+
decodeJSONBytes: "byte decoding inside other helpers",
|
|
96
|
+
decodeProperties: "decodes a graph property bag inside recall",
|
|
97
|
+
decodeValue: "decodes one graph value inside recall",
|
|
98
|
+
encodeFinding: "submit_finding encodes the finding for you",
|
|
99
|
+
formatKnowledgeForPrompt: "recall already formats its own hits",
|
|
100
|
+
validateFinding: "submit_finding validates before it submits",
|
|
101
|
+
newTask: "builds delegate's argument",
|
|
102
|
+
buildCreateMissionRequest: "shapes the request create_task_mission sends",
|
|
103
|
+
enrollmentSupported: "a fixed explanation, printed in enroll_component's answer",
|
|
104
|
+
isSeamUnavailable: "classifies a daemon error inside other helpers",
|
|
105
|
+
seamReason: "reads the daemon's own explanation inside other helpers",
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Tools with no single SDK export behind them, and where they come from.
|
|
109
|
+
* The guard walks this direction too, so a tool cannot appear with no
|
|
110
|
+
* account of itself.
|
|
111
|
+
*/
|
|
112
|
+
export const TOOL_WITHOUT_EXPORT = {
|
|
113
|
+
world_view: "HarnessCallbackService.WorldView through the task harness, formatted for reading",
|
|
114
|
+
run_history: "KnowledgeSource.runHistory, which is an interface method rather than a free function",
|
|
115
|
+
application_findings: "KnowledgeSource.applicationFindings, which is an interface method rather than a free function",
|
|
116
|
+
gibson_status: "the server's own posture, which is not an SDK concept",
|
|
117
|
+
gibson_login: "the gibson CLI device flow",
|
|
118
|
+
gibson_connect: "the gibson CLI enrollment and the check-in, driven in the session",
|
|
119
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { cancelMission, createMission, createTaskMission, delegateToAgent, getMissionResults, getMissionStatus, isSeamUnavailable, listAgents, listMissions, newTask, runMission, waitMission, } from "@zeroroot-ai/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { defineTool } from "../tool.js";
|
|
4
|
+
import { failure, json, text } from "../tools/result.js";
|
|
5
|
+
const UNWIRED = "This daemon has no agent delegation or mission management wired (gibson#1186). Complete the work directly instead.";
|
|
6
|
+
/** Delegation and missions: the network of agents in the tenant. */
|
|
7
|
+
export function delegationTools(ctx) {
|
|
8
|
+
const out = [];
|
|
9
|
+
const live = ctx.gibson.live;
|
|
10
|
+
if (live) {
|
|
11
|
+
out.push(defineTool({
|
|
12
|
+
name: "create_task_mission",
|
|
13
|
+
description: "Originate a mission from the tenant's checked-in catalog, by name, from inside this run. " +
|
|
14
|
+
"Use it to start a named piece of platform work; use delegate for a one-off task.",
|
|
15
|
+
input: {
|
|
16
|
+
name: z.string().describe("Catalog mission name."),
|
|
17
|
+
target_id: z.string().optional().describe("Target the mission binds to. Defaults to this run's target."),
|
|
18
|
+
params: z.record(z.string(), z.string()).optional().describe("Catalog parameters, by name."),
|
|
19
|
+
metadata: z.record(z.string(), z.unknown()).optional().describe("Free-form metadata recorded on the mission."),
|
|
20
|
+
},
|
|
21
|
+
handler: async (args) => {
|
|
22
|
+
try {
|
|
23
|
+
return json(await createTaskMission(live.harness, {
|
|
24
|
+
name: args.name,
|
|
25
|
+
...(args.target_id ? { targetId: args.target_id } : {}),
|
|
26
|
+
...(args.params ? { catalogParams: args.params } : {}),
|
|
27
|
+
...(args.metadata ? { metadata: args.metadata } : {}),
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return failure("create_task_mission failed", e.message);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
if (!ctx.gibson.session)
|
|
37
|
+
return out;
|
|
38
|
+
const component = ctx.gibson.session.clients.component;
|
|
39
|
+
const seam = (e, what) => (isSeamUnavailable(e) ? text(`${what} unavailable`, UNWIRED) : failure(`${what} failed`, e.message));
|
|
40
|
+
out.push(defineTool({
|
|
41
|
+
name: "list_agents",
|
|
42
|
+
description: "List the Gibson agents in this tenant that work can be delegated to, with their capabilities and the target types they handle.",
|
|
43
|
+
input: {},
|
|
44
|
+
annotations: { readOnlyHint: true },
|
|
45
|
+
handler: async () => {
|
|
46
|
+
const { agents, unavailable } = await listAgents(component);
|
|
47
|
+
if (unavailable)
|
|
48
|
+
return text("delegation unavailable", UNWIRED);
|
|
49
|
+
if (agents.length === 0)
|
|
50
|
+
return text("no agents", "No agents are registered for this tenant.");
|
|
51
|
+
const lines = agents.map((a) => {
|
|
52
|
+
const caps = a.capabilities.length > 0 ? `, capabilities: ${a.capabilities.join(", ")}` : "";
|
|
53
|
+
const targets = a.targetTypes.length > 0 ? `, targets: ${a.targetTypes.join(", ")}` : "";
|
|
54
|
+
return `- ${a.name} (${a.version}) ${a.description}${caps}${targets}`;
|
|
55
|
+
});
|
|
56
|
+
return text(`${agents.length} agent${agents.length === 1 ? "" : "s"}`, lines.join("\n"));
|
|
57
|
+
},
|
|
58
|
+
}), defineTool({
|
|
59
|
+
name: "delegate",
|
|
60
|
+
description: "Delegate a sub-task to another Gibson agent and wait for its result. Use list_agents " +
|
|
61
|
+
"first to pick an agent whose capabilities match the task.",
|
|
62
|
+
input: {
|
|
63
|
+
agent: z.string().describe("Name of the agent to delegate to."),
|
|
64
|
+
goal: z.string().describe("What the delegate should accomplish."),
|
|
65
|
+
context: z.record(z.string(), z.unknown()).optional().describe("Target details, prior findings, or other context the delegate needs."),
|
|
66
|
+
max_turns: z.number().int().min(1).optional().describe("Cap on the delegate's LLM turns."),
|
|
67
|
+
allowed_tools: z.array(z.string()).optional().describe("Restrict the delegate to these tools."),
|
|
68
|
+
},
|
|
69
|
+
handler: async (args) => {
|
|
70
|
+
try {
|
|
71
|
+
const task = newTask(args.goal, {
|
|
72
|
+
...(args.context ? { Context: args.context } : {}),
|
|
73
|
+
...(args.max_turns || args.allowed_tools
|
|
74
|
+
? { Constraints: { ...(args.max_turns ? { MaxTurns: args.max_turns } : {}), ...(args.allowed_tools ? { AllowedTools: args.allowed_tools } : {}) } }
|
|
75
|
+
: {}),
|
|
76
|
+
});
|
|
77
|
+
const result = await delegateToAgent(component, args.agent, task);
|
|
78
|
+
return text(`${args.agent}: ${result.Status}`, JSON.stringify(result, null, 2));
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return seam(e, "delegation");
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
}), defineTool({
|
|
85
|
+
name: "create_mission",
|
|
86
|
+
description: "Create a Gibson mission from a mission definition, bound to a target. The mission is not started; call run_mission to queue it.",
|
|
87
|
+
input: {
|
|
88
|
+
definition: z.record(z.string(), z.unknown()).describe("Mission definition object (gibson.mission.v1.MissionDefinition as JSON)."),
|
|
89
|
+
target_id: z.string().describe("Identifier of the target the mission runs against."),
|
|
90
|
+
opts: z.record(z.string(), z.unknown()).optional().describe("Optional mission creation options."),
|
|
91
|
+
},
|
|
92
|
+
handler: async (args) => {
|
|
93
|
+
try {
|
|
94
|
+
return json(await createMission(component, args.definition, args.target_id, args.opts));
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return seam(e, "create mission");
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}), defineTool({
|
|
101
|
+
name: "run_mission",
|
|
102
|
+
description: "Queue a created Gibson mission for execution. Returns as soon as it is queued.",
|
|
103
|
+
input: { mission_id: z.string().describe("Mission to run.") },
|
|
104
|
+
handler: async (args) => {
|
|
105
|
+
try {
|
|
106
|
+
await runMission(component, args.mission_id);
|
|
107
|
+
return text("queued", `Mission ${args.mission_id} is queued.`);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return seam(e, "run mission");
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
}), defineTool({
|
|
114
|
+
name: "mission_status",
|
|
115
|
+
description: "Check a Gibson mission's status.",
|
|
116
|
+
input: { mission_id: z.string().describe("Mission to inspect.") },
|
|
117
|
+
annotations: { readOnlyHint: true },
|
|
118
|
+
handler: async (args) => {
|
|
119
|
+
try {
|
|
120
|
+
return json(await getMissionStatus(component, args.mission_id));
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
return seam(e, "mission status");
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
}), defineTool({
|
|
127
|
+
name: "wait_mission",
|
|
128
|
+
description: "Block until a Gibson mission reaches a terminal state. Use it only when you intend to stop and wait for the result.",
|
|
129
|
+
input: {
|
|
130
|
+
mission_id: z.string().describe("Mission to wait for."),
|
|
131
|
+
timeout_ms: z.number().int().min(1).optional().describe("How long to block. Defaults to 5 minutes."),
|
|
132
|
+
},
|
|
133
|
+
handler: async (args) => {
|
|
134
|
+
try {
|
|
135
|
+
return json(await waitMission(component, args.mission_id, args.timeout_ms));
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
return seam(e, "wait mission");
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
}), defineTool({
|
|
142
|
+
name: "mission_results",
|
|
143
|
+
description: "Read the final results of a completed Gibson mission.",
|
|
144
|
+
input: { mission_id: z.string().describe("Mission whose results to read.") },
|
|
145
|
+
annotations: { readOnlyHint: true },
|
|
146
|
+
handler: async (args) => {
|
|
147
|
+
try {
|
|
148
|
+
return json(await getMissionResults(component, args.mission_id));
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
return seam(e, "mission results");
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
}), defineTool({
|
|
155
|
+
name: "cancel_mission",
|
|
156
|
+
description: "Request cancellation of a running Gibson mission.",
|
|
157
|
+
input: { mission_id: z.string().describe("Mission to cancel.") },
|
|
158
|
+
handler: async (args) => {
|
|
159
|
+
try {
|
|
160
|
+
await cancelMission(component, args.mission_id);
|
|
161
|
+
return text("cancel requested", `Cancellation of ${args.mission_id} is requested.`);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
return seam(e, "cancel mission");
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
}), defineTool({
|
|
168
|
+
name: "list_missions",
|
|
169
|
+
description: "List this tenant's missions, optionally filtered.",
|
|
170
|
+
input: { filter: z.record(z.string(), z.unknown()).optional().describe("Equality filters on mission fields.") },
|
|
171
|
+
annotations: { readOnlyHint: true },
|
|
172
|
+
handler: async (args) => {
|
|
173
|
+
try {
|
|
174
|
+
return json(await listMissions(component, args.filter ?? {}));
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
return seam(e, "list missions");
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
}));
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Finding, type GibsonSession, type TaskHarness } from "@zeroroot-ai/sdk";
|
|
2
|
+
import type { ToolDefinition } from "../registry.js";
|
|
3
|
+
import type { HelperContext } from "./context.js";
|
|
4
|
+
export interface FindingsBackend {
|
|
5
|
+
submit(f: Finding): Promise<string>;
|
|
6
|
+
describe(): string;
|
|
7
|
+
}
|
|
8
|
+
/** Platform backend: the finding lands in the tenant knowledge graph. */
|
|
9
|
+
export declare function gibsonFindingsBackend(session: GibsonSession): FindingsBackend;
|
|
10
|
+
/**
|
|
11
|
+
* Task backend: the callback service's typed SubmitFinding, under the
|
|
12
|
+
* dispatch grant. Fields map from the SDK's JSON finding onto
|
|
13
|
+
* gibson.types.v1.Finding; the daemon assigns the id and the mission.
|
|
14
|
+
*/
|
|
15
|
+
export declare function taskFindingsBackend(harness: TaskHarness): FindingsBackend;
|
|
16
|
+
/** Standalone backend: an append-only JSONL log. */
|
|
17
|
+
export declare function localFindingsBackend(path: string): FindingsBackend;
|
|
18
|
+
/**
|
|
19
|
+
* Findings: emit what the agent discovers. The model calls this when it
|
|
20
|
+
* finds something. Nothing here emits on its own: a file edit is not a
|
|
21
|
+
* security finding, and inventing one fills the tenant graph with noise a
|
|
22
|
+
* person then has to triage.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findingTools(ctx: HelperContext): ToolDefinition[];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { getFindings, newFinding, submitFinding } from "@zeroroot-ai/sdk";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { defineTool } from "../tool.js";
|
|
6
|
+
import { failure, json, text } from "../tools/result.js";
|
|
7
|
+
const SEVERITIES = ["critical", "high", "medium", "low", "info"];
|
|
8
|
+
/** Platform backend: the finding lands in the tenant knowledge graph. */
|
|
9
|
+
export function gibsonFindingsBackend(session) {
|
|
10
|
+
return { submit: (f) => submitFinding(session.clients.component, f), describe: () => "the tenant Gibson graph" };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Task backend: the callback service's typed SubmitFinding, under the
|
|
14
|
+
* dispatch grant. Fields map from the SDK's JSON finding onto
|
|
15
|
+
* gibson.types.v1.Finding; the daemon assigns the id and the mission.
|
|
16
|
+
*/
|
|
17
|
+
export function taskFindingsBackend(harness) {
|
|
18
|
+
const severity = (s) => ({ critical: 1, high: 2, medium: 3, low: 4, info: 5 })[s] ?? 0;
|
|
19
|
+
return {
|
|
20
|
+
submit: async (f) => {
|
|
21
|
+
const res = await harness.client.submitFinding({
|
|
22
|
+
context: harness.context,
|
|
23
|
+
finding: {
|
|
24
|
+
id: f.id,
|
|
25
|
+
missionId: f.mission_id,
|
|
26
|
+
agentName: f.agent_name,
|
|
27
|
+
title: f.title,
|
|
28
|
+
description: f.description,
|
|
29
|
+
category: f.category,
|
|
30
|
+
severity: severity(f.severity),
|
|
31
|
+
confidence: f.confidence,
|
|
32
|
+
remediation: f.remediation ?? "",
|
|
33
|
+
targetId: f.target_id ?? "",
|
|
34
|
+
tags: f.tags ?? [],
|
|
35
|
+
evidence: (f.evidence ?? []).map((e) => ({ type: e.type, title: e.title, content: e.content })),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
if (res.error)
|
|
39
|
+
throw new Error(`SubmitFinding refused: ${res.error.message}`);
|
|
40
|
+
// The callback acknowledges without an id; the finding keeps the one
|
|
41
|
+
// newFinding minted, which is what the daemon stored.
|
|
42
|
+
return f.id;
|
|
43
|
+
},
|
|
44
|
+
describe: () => "the tenant Gibson graph (dispatch grant)",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Standalone backend: an append-only JSONL log. */
|
|
48
|
+
export function localFindingsBackend(path) {
|
|
49
|
+
return {
|
|
50
|
+
submit: async (f) => {
|
|
51
|
+
await mkdir(dirname(path), { recursive: true });
|
|
52
|
+
await appendFile(path, `${JSON.stringify(f)}\n`, "utf8");
|
|
53
|
+
return f.id;
|
|
54
|
+
},
|
|
55
|
+
describe: () => path,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Findings: emit what the agent discovers. The model calls this when it
|
|
60
|
+
* finds something. Nothing here emits on its own: a file edit is not a
|
|
61
|
+
* security finding, and inventing one fills the tenant graph with noise a
|
|
62
|
+
* person then has to triage.
|
|
63
|
+
*/
|
|
64
|
+
export function findingTools(ctx) {
|
|
65
|
+
const out = [
|
|
66
|
+
defineTool({
|
|
67
|
+
name: "submit_finding",
|
|
68
|
+
description: "Record a security finding in the Gibson knowledge graph. Use this when you discover a " +
|
|
69
|
+
"real vulnerability, misconfiguration or notable security fact about the target or " +
|
|
70
|
+
"codebase. Not for progress updates or general observations.",
|
|
71
|
+
input: {
|
|
72
|
+
title: z.string().describe("One-line summary of the finding."),
|
|
73
|
+
description: z.string().describe("What the issue is, where it is, and why it matters."),
|
|
74
|
+
category: z.string().describe('Type of issue, e.g. "injection", "auth", "secrets-exposure", "misconfiguration".'),
|
|
75
|
+
severity: z.enum(SEVERITIES).describe("Impact level of the finding."),
|
|
76
|
+
confidence: z.number().min(0).max(1).optional().describe("How certain you are, from 0 to 1. Defaults to 1."),
|
|
77
|
+
evidence: z.string().optional().describe("A code excerpt, request/response, or log line."),
|
|
78
|
+
remediation: z.string().optional().describe("How to fix or mitigate the issue."),
|
|
79
|
+
target_id: z.string().optional().describe("Identifier of the affected target or component."),
|
|
80
|
+
tags: z.array(z.string()).optional().describe("Labels for filtering."),
|
|
81
|
+
},
|
|
82
|
+
handler: async (args) => {
|
|
83
|
+
const finding = newFinding({
|
|
84
|
+
title: args.title,
|
|
85
|
+
description: args.description,
|
|
86
|
+
category: args.category,
|
|
87
|
+
severity: args.severity,
|
|
88
|
+
confidence: args.confidence,
|
|
89
|
+
missionID: ctx.gibson.live?.missionId ?? "",
|
|
90
|
+
agentName: ctx.gibson.agentName,
|
|
91
|
+
...(args.remediation ? { remediation: args.remediation } : {}),
|
|
92
|
+
...(args.target_id ? { targetID: args.target_id } : {}),
|
|
93
|
+
...(args.tags ? { tags: args.tags } : {}),
|
|
94
|
+
...(args.evidence ? { evidence: [{ type: "text", title: "evidence", content: args.evidence, timestamp: new Date().toISOString() }] } : {}),
|
|
95
|
+
});
|
|
96
|
+
try {
|
|
97
|
+
const id = await ctx.findings.submit(finding);
|
|
98
|
+
return text(`${args.severity}: ${args.title}`, `Recorded finding ${id} in ${ctx.findings.describe()}.`);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return failure("submit_finding failed", e.message);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
];
|
|
106
|
+
if (ctx.gibson.session) {
|
|
107
|
+
const component = ctx.gibson.session.clients.component;
|
|
108
|
+
out.push(defineTool({
|
|
109
|
+
name: "get_findings",
|
|
110
|
+
description: "Read findings already recorded in this tenant's Gibson graph. Filter by any field the " +
|
|
111
|
+
"finding carries, for example mission_id, severity, category or target_id.",
|
|
112
|
+
input: { filter: z.record(z.string(), z.unknown()).optional().describe("Equality filters on finding fields. Omit for every finding the caller may read.") },
|
|
113
|
+
annotations: { readOnlyHint: true },
|
|
114
|
+
handler: async (args) => json(await getFindings(component, args.filter ?? {})),
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ToolDefinition } from "../registry.js";
|
|
2
|
+
import type { Gibson } from "../session.js";
|
|
3
|
+
import { type HelperContext } from "./context.js";
|
|
4
|
+
export * from "./context.js";
|
|
5
|
+
export * from "./coverage.js";
|
|
6
|
+
export { componentizeTools } from "./componentize.js";
|
|
7
|
+
export { delegationTools } from "./delegate.js";
|
|
8
|
+
export { findingTools, gibsonFindingsBackend, localFindingsBackend, taskFindingsBackend, type FindingsBackend } from "./findings.js";
|
|
9
|
+
export { knowledgeTools } from "./knowledge.js";
|
|
10
|
+
export { platformToolHelpers } from "./tools.js";
|
|
11
|
+
/**
|
|
12
|
+
* One tool per SDK helper (gibson#1706, decision 2), for whatever this
|
|
13
|
+
* posture can reach. A helper whose grant is absent registers no tool
|
|
14
|
+
* rather than a tool that always fails.
|
|
15
|
+
*/
|
|
16
|
+
export declare function helperTools(ctx: HelperContext): ToolDefinition[];
|
|
17
|
+
export declare function helperToolsFor(gibson: Gibson, cwd: string, env: NodeJS.ProcessEnv): ToolDefinition[];
|