agents-can-communicate 0.5.9 → 0.6.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/README.md +2 -1
- package/bin/acc-antigravity-relay.mjs +7 -0
- package/bin/entrypoints/acc-antigravity-relay.mjs +116 -0
- package/bin/entrypoints/acc-bootstrap.mjs +4 -0
- package/bin/entrypoints/acc-hook.mjs +11 -7
- package/bin/entrypoints/antigravity-relay-binding.mjs +43 -0
- package/docs/ADAPTER_AUTHORING.md +33 -0
- package/docs/ARCHITECTURE.md +27 -4
- package/docs/CAPABILITIES.md +22 -16
- package/docs/CLI.md +21 -3
- package/docs/CONCEPTS.md +7 -0
- package/docs/CONFIGURATION.md +1 -0
- package/docs/GETTING_STARTED.md +10 -4
- package/docs/HOW_IT_WORKS.md +7 -1
- package/docs/TROUBLESHOOTING.md +99 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/certification.json +91 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/PreInvocation-1.2.7.json +11 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/SessionStart-1.2.7.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/agentapi-error-answers-1.2.7.json +19 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/agentapi-live-push-1.2.7.json +31 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/certification-provenance.json +217 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-cli-1.2.7-relay-product-evidence.json +132 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-cli-1.2.7-relay-product.json +29 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/hooks-first-trust-no-workspace-1.2.7.json +37 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/live-push-surfaces-1.2.7.json +38 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +28 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +5 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +329 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +119 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/agentapi.mjs +98 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/hooks.mjs +201 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +708 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +170 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-endpoint.mjs +116 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-start.mjs +123 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay.mjs +195 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relays.mjs +64 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +14 -2
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/hooks.mjs +5 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +14 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +14 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +14 -2
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +14 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +37 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +25 -5
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +6 -2
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/hook-workspace.mjs +118 -0
- package/node_modules/@agents-can-communicate/cli/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +17 -3
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +8 -4
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/command-prefix.mjs +26 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +6 -4
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +3 -1
- package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +27 -6
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +31 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/owner-context.mjs +16 -3
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +70 -17
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +4 -0
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +10 -0
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The one way ACC talks to a running Antigravity session: `agy agentapi`, run as
|
|
7
|
+
* a child whose environment - and only whose environment - carries the session
|
|
8
|
+
* endpoint. argv is readable by every process on the machine, so the token is
|
|
9
|
+
* never an argument; the environment of a short-lived child is the exposure
|
|
10
|
+
* every command the agent itself runs already has.
|
|
11
|
+
*
|
|
12
|
+
* Answer shapes are the ones captured on 1.2.7: a delivered message comes back
|
|
13
|
+
* as `response.sendMessage.recipientId`; a gone session as
|
|
14
|
+
* `code = Unavailable`; a refused credential as `code = Unauthenticated`.
|
|
15
|
+
*/
|
|
16
|
+
export const ENDPOINT_VARIABLES = Object.freeze(["ANTIGRAVITY_LS_ADDRESS",
|
|
17
|
+
"ANTIGRAVITY_CSRF_TOKEN", "ANTIGRAVITY_CONVERSATION_ID"]);
|
|
18
|
+
const TITLE = "ACC peer message";
|
|
19
|
+
|
|
20
|
+
export function endpointFrom(env) {
|
|
21
|
+
const [lsAddress, csrfToken, conversationId] = ENDPOINT_VARIABLES.map(name => env?.[name]);
|
|
22
|
+
return [lsAddress, csrfToken, conversationId].every(value => typeof value === "string"
|
|
23
|
+
&& value !== "") ? { lsAddress, csrfToken, conversationId } : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function childEnv(baseEnv, endpoint) {
|
|
27
|
+
const env = { ...baseEnv };
|
|
28
|
+
for (const name of ENDPOINT_VARIABLES) delete env[name];
|
|
29
|
+
return { ...env, ANTIGRAVITY_LS_ADDRESS: endpoint.lsAddress,
|
|
30
|
+
ANTIGRAVITY_CSRF_TOKEN: endpoint.csrfToken,
|
|
31
|
+
ANTIGRAVITY_CONVERSATION_ID: endpoint.conversationId };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function classifyAnswer(stdout, conversationId) {
|
|
35
|
+
let answer;
|
|
36
|
+
try {
|
|
37
|
+
answer = JSON.parse(stdout);
|
|
38
|
+
} catch {
|
|
39
|
+
return { ok: false, reasonCode: "transport_error" };
|
|
40
|
+
}
|
|
41
|
+
const sent = answer?.response?.sendMessage;
|
|
42
|
+
if (sent !== undefined) {
|
|
43
|
+
return sent.recipientId === conversationId ? { ok: true, reasonCode: null }
|
|
44
|
+
: { ok: false, reasonCode: "transport_rejected" };
|
|
45
|
+
}
|
|
46
|
+
if (answer?.response?.conversationMetadata !== undefined) return { ok: true, reasonCode: null };
|
|
47
|
+
const error = typeof answer?.error === "string" ? answer.error : "";
|
|
48
|
+
if (/code = Unavailable/.test(error)) return { ok: false, reasonCode: "recipient_unavailable" };
|
|
49
|
+
if (/code = (Unauthenticated|PermissionDenied)/.test(error)) {
|
|
50
|
+
return { ok: false, reasonCode: "transport_rejected" };
|
|
51
|
+
}
|
|
52
|
+
return { ok: false, reasonCode: "transport_error" };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function run(command, args, { env, timeout }) {
|
|
56
|
+
return new Promise(resolve => {
|
|
57
|
+
execFile(command, args, { env, timeout, windowsHide: true, maxBuffer: 1024 * 1024 },
|
|
58
|
+
(_error, stdout) => resolve({ stdout: String(stdout ?? "") }));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createAgentApi({ endpoint, baseEnv = process.env, run: runner = run,
|
|
63
|
+
command = "agy", timeoutMs = 4_000 }) {
|
|
64
|
+
const call = async args => {
|
|
65
|
+
try {
|
|
66
|
+
const { stdout } = await runner(command, ["agentapi", ...args],
|
|
67
|
+
{ env: childEnv(baseEnv, endpoint), timeout: timeoutMs });
|
|
68
|
+
return classifyAnswer(stdout, endpoint.conversationId);
|
|
69
|
+
} catch {
|
|
70
|
+
return { ok: false, reasonCode: "transport_error" };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
sendMessage: text => call(["send-message", "--title", TITLE, endpoint.conversationId, text]),
|
|
75
|
+
conversationMetadata: () => call(["get-conversation-metadata", endpoint.conversationId]),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The agy the relay runs. The agent's shell names the client's own binary in
|
|
81
|
+
* ANTIGRAVITY_AGENTAPI_EXE - captured as the same file as the agy on its PATH -
|
|
82
|
+
* and preferring it keeps a push working for a client started by a full path
|
|
83
|
+
* that is not on the PATH. Only an absolute, executable file named agy is
|
|
84
|
+
* taken, because the relay calls it as `agy agentapi ...`; anything else falls
|
|
85
|
+
* back to the PATH.
|
|
86
|
+
*/
|
|
87
|
+
export function agentApiCommand(env) {
|
|
88
|
+
const named = env?.ANTIGRAVITY_AGENTAPI_EXE;
|
|
89
|
+
if (typeof named !== "string" || !path.isAbsolute(named) || path.basename(named) !== "agy") {
|
|
90
|
+
return "agy";
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
accessSync(named, constants.X_OK);
|
|
94
|
+
return statSync(named).isFile() ? named : "agy";
|
|
95
|
+
} catch {
|
|
96
|
+
return "agy";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { normalizedEvent } from "@agents-can-communicate/adapter-sdk";
|
|
2
|
+
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The events that load on this client. Captured on 1.2.7 by registering each
|
|
6
|
+
* one alone and then together, and reading the effective list back from
|
|
7
|
+
* `agy -p "/hooks" --output-format json`.
|
|
8
|
+
*
|
|
9
|
+
* The list is short because most of what an adapter would want is not here.
|
|
10
|
+
* There is no tool event, so no guard; and no session end, so no lifecycle
|
|
11
|
+
* deregistration. See COMPATIBILITY.md.
|
|
12
|
+
*/
|
|
13
|
+
export const ANTIGRAVITY_HOOK_EVENTS = Object.freeze(["SessionStart", "PreInvocation",
|
|
14
|
+
"PostInvocation", "Stop"]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Names this client accepts into a config file and then silently drops.
|
|
18
|
+
*
|
|
19
|
+
* Nothing is logged, no error is produced, and `/hooks` reports an empty list.
|
|
20
|
+
* That is the single most important failure mode here, so the names are kept
|
|
21
|
+
* rather than merely absent: an install that writes one of them succeeds on
|
|
22
|
+
* disk and registers nothing, and the only defence is to refuse the name in
|
|
23
|
+
* both directions - never write it, and never normalise it if it somehow
|
|
24
|
+
* arrives.
|
|
25
|
+
*
|
|
26
|
+
* `SessionEnd`, `PreToolUse`, `PostToolUse`, `Notification` and
|
|
27
|
+
* `turn-completion` were each registered and never fired; `turn-completion`
|
|
28
|
+
* does not appear in the 1.2.7 binary at all. The last four are the Gemini CLI
|
|
29
|
+
* names, which matter because both clients share `~/.gemini`.
|
|
30
|
+
*/
|
|
31
|
+
export const ANTIGRAVITY_INERT_EVENTS = Object.freeze(["SessionEnd", "PreToolUse",
|
|
32
|
+
"PostToolUse", "Notification", "turn-completion",
|
|
33
|
+
"BeforeAgent", "AfterAgent", "BeforeTool", "AfterTool"]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* How many times this adapter will continue one turn.
|
|
37
|
+
*
|
|
38
|
+
* One. The vendor caps consecutive continuations itself - changelog 1.1.9 fixed
|
|
39
|
+
* "stop hooks that always block hanging the agent forever" - but the cap is
|
|
40
|
+
* configurable and its value was not captured, so relying on it would be
|
|
41
|
+
* relying on a number nobody here has read. One is also the whole of what was
|
|
42
|
+
* observed: `executionNum` 0 continued into `executionNum` 1, and that pair is
|
|
43
|
+
* the evidence this ceiling rests on.
|
|
44
|
+
*
|
|
45
|
+
* A continuation is a nudge, not a gate. Nothing about it guarantees a peer is
|
|
46
|
+
* answered, and the sender-facing story must not say otherwise.
|
|
47
|
+
*/
|
|
48
|
+
export const STOP_CONTINUATION_CEILING = 1;
|
|
49
|
+
|
|
50
|
+
const KIND_BY_EVENT = Object.freeze({
|
|
51
|
+
SessionStart: "sessionStart",
|
|
52
|
+
PreInvocation: "beforeTurn",
|
|
53
|
+
// Recorded rather than used. It fires after each invocation with an envelope
|
|
54
|
+
// byte-identical to PreInvocation's, and there is nothing ACC does at that
|
|
55
|
+
// point that PreInvocation has not already done.
|
|
56
|
+
PostInvocation: "other",
|
|
57
|
+
Stop: "turnEnd",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const data = (message, details) => {
|
|
61
|
+
throw new AccError(EXIT.DATA, message, details);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Normalise an Antigravity CLI hook payload.
|
|
66
|
+
*
|
|
67
|
+
* The event name is an argument, never a payload field. This client sends no
|
|
68
|
+
* `hook_event_name`, and `PreInvocation` and `PostInvocation` hand over
|
|
69
|
+
* byte-identical envelopes for the same invocation - so a payload alone cannot
|
|
70
|
+
* say which of the two ran. The command registered for each event carries its
|
|
71
|
+
* own name, and that is the only thing that knows.
|
|
72
|
+
*
|
|
73
|
+
* A whitelist, for the reason every adapter uses one: the payload carries
|
|
74
|
+
* `transcriptPath` and `artifactDirectoryPath`, both of which lead to the
|
|
75
|
+
* conversation. Neither survives this function.
|
|
76
|
+
*/
|
|
77
|
+
export function normalizeAntigravityHook(payload, { args = [] } = {}) {
|
|
78
|
+
const event = args[0];
|
|
79
|
+
if (typeof event !== "string" || !ANTIGRAVITY_HOOK_EVENTS.includes(event)) {
|
|
80
|
+
data("unrecognised Antigravity hook event", { event: event ?? null });
|
|
81
|
+
}
|
|
82
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
83
|
+
data("Antigravity hook payload is not an object", { event });
|
|
84
|
+
}
|
|
85
|
+
if (typeof payload.conversationId !== "string" || payload.conversationId === "") {
|
|
86
|
+
data("hook payload has no conversation id",
|
|
87
|
+
{ event, received: Object.keys(payload) });
|
|
88
|
+
}
|
|
89
|
+
// The only working directory this client offers. `workspacePaths` is an array
|
|
90
|
+
// because a session can hold several; the first is the one the conversation
|
|
91
|
+
// was opened against, and ACC's workspace identity needs exactly one.
|
|
92
|
+
//
|
|
93
|
+
// It is empty whenever the session has no open workspace, which an ordinary
|
|
94
|
+
// `agy -p` in a project directory is: captured on 1.2.7 as
|
|
95
|
+
// `fixtures/SessionStart-no-workspace-1.2.7.json`. Nothing else in the
|
|
96
|
+
// payload can stand in for it. `transcriptPath` and `artifactDirectoryPath`
|
|
97
|
+
// both point inside `~/.gemini/antigravity-cli/brain/<conversationId>/`,
|
|
98
|
+
// which is the client's own state and is per conversation - adopting it would
|
|
99
|
+
// give every conversation a private ACC workspace and two agents in one
|
|
100
|
+
// project would never see each other. Nor does the hook process inherit the
|
|
101
|
+
// client's directory: its working directory is the directory of the
|
|
102
|
+
// hooks.json it was registered from, `~/.gemini/config` for a global
|
|
103
|
+
// registration (`fixtures/hook-process-environment-1.2.7.json`).
|
|
104
|
+
//
|
|
105
|
+
// So this refuses, and it refuses *by name*. A hook that fails open is
|
|
106
|
+
// invisible by design, and an anonymous refusal here is indistinguishable
|
|
107
|
+
// from ACC not being installed - which is the state issue #176 describes.
|
|
108
|
+
const paths = payload.workspacePaths;
|
|
109
|
+
if (Array.isArray(paths) && paths.length === 0) {
|
|
110
|
+
data("this Antigravity session has no open workspace, so the hook was given no "
|
|
111
|
+
+ "project directory and ACC cannot tell which workspace to join; open the project "
|
|
112
|
+
+ "as an Antigravity workspace, or pass it with --add-dir",
|
|
113
|
+
{ event, reasonCode: "antigravity_no_open_workspace" });
|
|
114
|
+
}
|
|
115
|
+
const cwd = Array.isArray(paths) ? paths[0] : undefined;
|
|
116
|
+
if (typeof cwd !== "string" || cwd === "") {
|
|
117
|
+
data("hook payload has no workspace path", { event, received: Object.keys(payload) });
|
|
118
|
+
}
|
|
119
|
+
return normalizedEvent({
|
|
120
|
+
kind: KIND_BY_EVENT[event],
|
|
121
|
+
sessionId: payload.conversationId,
|
|
122
|
+
cwd,
|
|
123
|
+
model: typeof payload.modelName === "string" ? payload.modelName : null,
|
|
124
|
+
parentSessionId: null,
|
|
125
|
+
// This client runs no tool hook, so there is never a tool to name and never
|
|
126
|
+
// a target to guard. Declared empty rather than left to a default, because
|
|
127
|
+
// a future reader will want to know it was measured.
|
|
128
|
+
tool: null,
|
|
129
|
+
targets: [],
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Context for the next invocation.
|
|
135
|
+
*
|
|
136
|
+
* Measured, not copied: returning this from `PreInvocation` put the text where
|
|
137
|
+
* the model read it, and the model reproduced the probe token in its reply. The
|
|
138
|
+
* client's `userMessage` and `toolCall` injection types exist in the binary and
|
|
139
|
+
* were not exercised, so neither is used here.
|
|
140
|
+
*/
|
|
141
|
+
export function injectResponse(context) {
|
|
142
|
+
return context === "" || context === undefined || context === null
|
|
143
|
+
? {} : { injectSteps: [{ ephemeralMessage: String(context) }] };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Whether to hold this turn open, and why.
|
|
148
|
+
*
|
|
149
|
+
* `continue` is the exact string; any other value, including `block` and
|
|
150
|
+
* `stop`, permits shutdown. The `reason` reaches the model - the model
|
|
151
|
+
* reproduced a token carried in it - so this is a delivery path as well as a
|
|
152
|
+
* decision. It arrives inside a `<SYSTEM_MESSAGE>` prefixed
|
|
153
|
+
* `Stop hook blocked termination:`, so a peer body carried here sits in a
|
|
154
|
+
* system-framed block, and the untrusted-peer fence the projector draws around
|
|
155
|
+
* it is what tells the model it is data
|
|
156
|
+
* (`fixtures/stop-continuation-live-1.2.7.json`).
|
|
157
|
+
*
|
|
158
|
+
* Everything that is not a good reason below the ceiling releases the turn.
|
|
159
|
+
* That is deliberate and it is the whole fail-open rule in one expression: a
|
|
160
|
+
* hook error, an expired budget and an unreachable store all arrive here as an
|
|
161
|
+
* absent reason, and none of them may be the cause of a session that will not
|
|
162
|
+
* finish.
|
|
163
|
+
*/
|
|
164
|
+
export function stopResponse(input) {
|
|
165
|
+
const reason = input?.reason;
|
|
166
|
+
const executionNum = input?.executionNum;
|
|
167
|
+
if (typeof reason !== "string" || reason.trim() === "") return {};
|
|
168
|
+
if (!Number.isInteger(executionNum) || executionNum < 0) return {};
|
|
169
|
+
if (executionNum >= STOP_CONTINUATION_CEILING) return {};
|
|
170
|
+
return { decision: "continue", reason };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function injectOutcome(context) {
|
|
174
|
+
const response = injectResponse(context);
|
|
175
|
+
return { stdout: Object.keys(response).length === 0 ? "" : `${JSON.stringify(response)}\n`,
|
|
176
|
+
stderr: "", exitCode: 0 };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function stopOutcome(input) {
|
|
180
|
+
const response = stopResponse(input);
|
|
181
|
+
// Printing nothing is how a turn is allowed to end. An empty object would do
|
|
182
|
+
// the same thing, but silence cannot be mistaken later for a decision.
|
|
183
|
+
return { stdout: Object.keys(response).length === 0 ? "" : `${JSON.stringify(response)}\n`,
|
|
184
|
+
stderr: "", exitCode: 0 };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* There is no deny on this client.
|
|
189
|
+
*
|
|
190
|
+
* No tool event loads, so nothing ever reaches a guard, and no shape was ever
|
|
191
|
+
* found that stops a tool call. This exists because the hook runtime asks every
|
|
192
|
+
* adapter how it denies, and the honest answer here is that it cannot: the call
|
|
193
|
+
* is allowed and the reason goes to stderr where an operator can read it.
|
|
194
|
+
*
|
|
195
|
+
* It must never return a deny envelope copied from another client. A shape that
|
|
196
|
+
* does nothing, printed as though it did something, is exactly the silent
|
|
197
|
+
* failure this package documents.
|
|
198
|
+
*/
|
|
199
|
+
export function denyOutcome(reason) {
|
|
200
|
+
return { stdout: "", stderr: `acc: ${reason} (this client has no tool guard)`, exitCode: 0 };
|
|
201
|
+
}
|