agents-can-communicate 0.5.10 → 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 +5 -3
- package/docs/CAPABILITIES.md +22 -16
- 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 +72 -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-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- 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-kimi/package.json +1 -1
- 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/install-command.mjs +17 -3
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +2 -2
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +1 -0
- 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/runner.mjs +59 -9
- 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
|
@@ -3,6 +3,7 @@ import { AccError, EXIT, assertPortableId } from "@agents-can-communicate/protoc
|
|
|
3
3
|
import { CAPABILITY_SHAPE, freezeCapabilities, validateCertification }
|
|
4
4
|
from "./certification.mjs";
|
|
5
5
|
import { validateNativeDeliveryContract } from "./native-delivery.mjs";
|
|
6
|
+
import { parseStableVersion } from "./native-vocabulary.mjs";
|
|
6
7
|
|
|
7
8
|
// The capability surface, documented in docs/ADAPTER_AUTHORING.md and measured
|
|
8
9
|
// per client in docs/CAPABILITIES.md. False is the default for every
|
|
@@ -115,8 +116,13 @@ export function defineAdapter(manifest) {
|
|
|
115
116
|
{ evidenceClient: item.client, client });
|
|
116
117
|
}
|
|
117
118
|
}
|
|
119
|
+
const floor = {};
|
|
120
|
+
if (manifest.certificationFloor !== undefined) {
|
|
121
|
+
floor.certificationFloor = validateCertificationFloor(manifest.certificationFloor,
|
|
122
|
+
{ certification, client: manifest.client.certificationName ?? manifest.client.command });
|
|
123
|
+
}
|
|
118
124
|
const native = {};
|
|
119
|
-
for (const method of ["refreshNativeSession", "retireNativeSession"]) {
|
|
125
|
+
for (const method of ["refreshNativeSession", "retireNativeSession", "nativeActivationHint"]) {
|
|
120
126
|
if (manifest[method] !== undefined && typeof manifest[method] !== "function") {
|
|
121
127
|
usage(`optional native method ${method} must be a function`, { id: manifest.id, method });
|
|
122
128
|
}
|
|
@@ -134,7 +140,37 @@ export function defineAdapter(manifest) {
|
|
|
134
140
|
return Object.freeze({
|
|
135
141
|
...manifest,
|
|
136
142
|
certification,
|
|
143
|
+
...floor,
|
|
137
144
|
...native,
|
|
138
145
|
capabilities: assertCapabilities(manifest.capabilities, manifest, certification),
|
|
139
146
|
});
|
|
140
147
|
}
|
|
148
|
+
|
|
149
|
+
const FLOOR_PLATFORM = /^(?:darwin|linux|win32)-(?:arm64|x64)$/;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* An optional per-platform floor: later stable versions are judged by this
|
|
153
|
+
* version's evidence until a capture of their own says otherwise. It can only
|
|
154
|
+
* name a version that has passing evidence on that platform, so a floor never
|
|
155
|
+
* certifies anything nobody captured.
|
|
156
|
+
*/
|
|
157
|
+
function validateCertificationFloor(value, { certification, client }) {
|
|
158
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
159
|
+
usage("certificationFloor must map a platform to a stable version");
|
|
160
|
+
}
|
|
161
|
+
for (const [platform, version] of Object.entries(value)) {
|
|
162
|
+
if (!FLOOR_PLATFORM.test(platform)) {
|
|
163
|
+
usage(`certificationFloor names an unknown platform ${platform}`, { platform });
|
|
164
|
+
}
|
|
165
|
+
if (parseStableVersion(version) === null) {
|
|
166
|
+
usage(`certificationFloor ${platform} must be a stable version`, { platform, version });
|
|
167
|
+
}
|
|
168
|
+
const proven = certification.evidence.some(item => item.result === "pass"
|
|
169
|
+
&& item.client === client && item.version === version && item.platform === platform);
|
|
170
|
+
if (!proven) {
|
|
171
|
+
usage(`certificationFloor ${version} on ${platform} has no passing evidence`,
|
|
172
|
+
{ platform, version });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return Object.freeze({ ...value });
|
|
176
|
+
}
|
|
@@ -2,6 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
4
4
|
|
|
5
|
+
import { compareStableVersions, parseStableVersion } from "./native-vocabulary.mjs";
|
|
6
|
+
|
|
5
7
|
export const CAPABILITY_SHAPE = Object.freeze({
|
|
6
8
|
lifecycle: Object.freeze(["sessionStart", "sessionResume", "sessionEnd", "heartbeat",
|
|
7
9
|
"childSessions"]),
|
|
@@ -132,6 +134,16 @@ function falseCapabilities() {
|
|
|
132
134
|
Object.fromEntries(names.map(name => [name, false]))]));
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
/**
|
|
138
|
+
* What a client is certified for, judged against the exact version and
|
|
139
|
+
* platform it reports.
|
|
140
|
+
*
|
|
141
|
+
* A version's own captures decide, capability by capability, including a
|
|
142
|
+
* capture that failed. An adapter may also declare a certification floor per
|
|
143
|
+
* platform: a stable version at or above it, with no capture of its own for a
|
|
144
|
+
* capability, is judged by the floor version's evidence for that capability.
|
|
145
|
+
* Versions below the floor, prereleases and other platforms stay uncertified.
|
|
146
|
+
*/
|
|
135
147
|
export function effectiveCapabilities(adapter, { clientVersion, platform } = {}) {
|
|
136
148
|
const resolved = falseCapabilities();
|
|
137
149
|
if (typeof clientVersion !== "string" || !VERSION.test(clientVersion)
|
|
@@ -139,14 +151,22 @@ export function effectiveCapabilities(adapter, { clientVersion, platform } = {})
|
|
|
139
151
|
|| typeof platform !== "string" || !PLATFORM.test(platform)
|
|
140
152
|
|| platform.toLowerCase() === "unknown") return freezeCapabilities(resolved);
|
|
141
153
|
const client = adapter.client?.certificationName ?? adapter.client?.command;
|
|
142
|
-
const
|
|
143
|
-
.filter(item => item.
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
const rows = (adapter.certification?.evidence ?? [])
|
|
155
|
+
.filter(item => item.client === client && item.platform === platform);
|
|
156
|
+
const floor = adapter.certificationFloor?.[platform];
|
|
157
|
+
const floored = typeof floor === "string" && parseStableVersion(floor) !== null
|
|
158
|
+
&& parseStableVersion(clientVersion) !== null
|
|
159
|
+
&& compareStableVersions(clientVersion, floor) >= 0;
|
|
160
|
+
const certified = capability => {
|
|
161
|
+
const own = rows.filter(item => item.version === clientVersion && item.capability === capability);
|
|
162
|
+
const judged = own.length > 0 || !floored ? own
|
|
163
|
+
: rows.filter(item => item.version === floor && item.capability === capability);
|
|
164
|
+
return judged.some(item => item.result === "pass");
|
|
165
|
+
};
|
|
146
166
|
for (const [group, names] of Object.entries(CAPABILITY_SHAPE)) {
|
|
147
167
|
for (const name of names) {
|
|
148
168
|
resolved[group][name] = adapter.capabilities?.[group]?.[name] === true
|
|
149
|
-
&&
|
|
169
|
+
&& certified(`${group}.${name}`);
|
|
150
170
|
}
|
|
151
171
|
}
|
|
152
172
|
return freezeCapabilities(resolved);
|
|
@@ -60,6 +60,7 @@ const ownBinary = name => {
|
|
|
60
60
|
export const defaultRunner = () => ownBinary("acc-hook.mjs");
|
|
61
61
|
export const defaultBootstrap = () => ownBinary("acc-bootstrap.mjs");
|
|
62
62
|
export const defaultChannel = () => ownBinary("acc-claude-channel.mjs");
|
|
63
|
+
export const defaultAntigravityRelay = () => ownBinary("acc-antigravity-relay.mjs");
|
|
63
64
|
|
|
64
65
|
export const runnerExists = async runner => {
|
|
65
66
|
try {
|
|
@@ -7,7 +7,7 @@ export { NATIVE_ACTIVATION_KINDS, NATIVE_BINDING_MODES, NATIVE_PLATFORMS, NATIVE
|
|
|
7
7
|
validateNativeActivationPlan, validateNativeDeliveryContract, validateNativeHandshake }
|
|
8
8
|
from "./native-delivery.mjs";
|
|
9
9
|
export { EVENT_KINDS, NORMALIZED_EVENT_KEYS, normalizedEvent } from "./events.mjs";
|
|
10
|
-
export { assertRunner, bakeSkillCommand, defaultBootstrap, defaultChannel, defaultCli,
|
|
10
|
+
export { assertRunner, bakeSkillCommand, defaultAntigravityRelay, defaultBootstrap, defaultChannel, defaultCli,
|
|
11
11
|
defaultRunner, removeInstalledTree, runnerExists, writeCliShim, writeHookShim }
|
|
12
12
|
from "./hook-shim.mjs";
|
|
13
13
|
export { BEGIN, END, removeTomlBlock, renderBlock, stripBlock, tomlString, writeTomlBlock }
|
|
@@ -11,7 +11,7 @@ import { NATIVE_REASON_CODES, TIMESTAMP } from "./native-vocabulary.mjs";
|
|
|
11
11
|
export function nativeAttemptFrom(value) {
|
|
12
12
|
if (!value || typeof value.at !== "string" || !TIMESTAMP.test(value.at)
|
|
13
13
|
|| !Number.isFinite(Date.parse(value.at))
|
|
14
|
-
|| !["sessionStart", "beforeTurn", "channelReady"].includes(value.event)
|
|
14
|
+
|| !["sessionStart", "beforeTurn", "channelReady", "relayReady"].includes(value.event)
|
|
15
15
|
|| !["active", "off", "degraded", "unsupported"].includes(value.state)
|
|
16
16
|
|| !(value.reasonCode === null || NATIVE_REASON_CODES.includes(value.reasonCode))
|
|
17
17
|
|| !["off", "actionable", "all"].includes(value.policy)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
import { createAntigravityAdapter } from "@agents-can-communicate/adapter-antigravity";
|
|
4
5
|
import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
|
|
5
6
|
import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
|
|
6
7
|
import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
|
|
@@ -27,7 +28,8 @@ export { decideDelivery } from "./install-delivery-consent.mjs";
|
|
|
27
28
|
// Each client keeps its own directory under the user's home, and an adapter
|
|
28
29
|
// pointed at the home itself writes beside them rather than inside them. That
|
|
29
30
|
// install reports success and the client never reads a byte of it.
|
|
30
|
-
export const clientContext = (home, stateRoot,
|
|
31
|
+
export const clientContext = (home, stateRoot,
|
|
32
|
+
{ shell = null, env = {}, dataHome, cwd = process.cwd() } = {}) => ({
|
|
31
33
|
home,
|
|
32
34
|
...(dataHome === undefined ? {} : { dataHome }),
|
|
33
35
|
configDir: path.join(home, ".claude"),
|
|
@@ -42,6 +44,18 @@ export const clientContext = (home, stateRoot, { shell = null, env = {}, dataHom
|
|
|
42
44
|
// shim would exec. Detection reads them; nothing here writes.
|
|
43
45
|
shell,
|
|
44
46
|
env,
|
|
47
|
+
// Antigravity CLI keeps its hooks in the `~/.gemini` tree Gemini CLI also
|
|
48
|
+
// uses, and reads none of the same files. Two locations load - the
|
|
49
|
+
// machine-wide `~/.gemini/config/hooks.json` and a project's own
|
|
50
|
+
// `.agents/hooks.json`, the second only while that project is an open
|
|
51
|
+
// workspace. Global is the default (issue #178), because a workspace
|
|
52
|
+
// registration in a folder nobody opened as one registers nothing and says
|
|
53
|
+
// nothing. ACC_ANTIGRAVITY_HOOKS is carried through exactly as it was set,
|
|
54
|
+
// wrong values included, so the adapter can refuse one by name rather than
|
|
55
|
+
// quietly installing somewhere else.
|
|
56
|
+
antigravityWorkspace: cwd,
|
|
57
|
+
antigravityHookLocation: typeof env.ACC_ANTIGRAVITY_HOOKS === "string"
|
|
58
|
+
&& env.ACC_ANTIGRAVITY_HOOKS !== "" ? env.ACC_ANTIGRAVITY_HOOKS : "global",
|
|
45
59
|
// Where ACC keeps its own state, for the client that has to be told. Codex
|
|
46
60
|
// sandboxes the commands a model runs to the workspace, and ACC's state is
|
|
47
61
|
// outside every workspace on purpose - so an agent there could read the
|
|
@@ -50,8 +64,8 @@ export const clientContext = (home, stateRoot, { shell = null, env = {}, dataHom
|
|
|
50
64
|
stateRoot,
|
|
51
65
|
});
|
|
52
66
|
|
|
53
|
-
export const ALL_ADAPTERS = () => [
|
|
54
|
-
createGeminiCliAdapter(), createGrokAdapter(), createKimiAdapter()];
|
|
67
|
+
export const ALL_ADAPTERS = () => [createAntigravityAdapter(), createClaudeCodeAdapter(),
|
|
68
|
+
createCodexAdapter(), createGeminiCliAdapter(), createGrokAdapter(), createKimiAdapter()];
|
|
55
69
|
|
|
56
70
|
/**
|
|
57
71
|
* How long to wait for a client to say its version.
|
|
@@ -7,7 +7,7 @@ import { canonicalManagerRoot, readControl, readManagedJson } from "./state.mjs"
|
|
|
7
7
|
import { commandPrefix } from "./command-prefix.mjs";
|
|
8
8
|
|
|
9
9
|
export const ENTRY_KINDS = Object.freeze([
|
|
10
|
-
"acc", "acc-hook", "acc-mcp", "acc-bootstrap", "acc-claude-channel",
|
|
10
|
+
"acc", "acc-hook", "acc-mcp", "acc-bootstrap", "acc-claude-channel", "acc-antigravity-relay",
|
|
11
11
|
]);
|
|
12
12
|
|
|
13
13
|
const BOOTSTRAP_FLAGS = new Map([["--adapter", "adapter"], ["--real-executable", "realExecutable"],
|
|
@@ -43,7 +43,7 @@ export function invokedDirectly(url) {
|
|
|
43
43
|
|
|
44
44
|
function unavailable(kind) {
|
|
45
45
|
// Never reflect control file content or payloads into a client diagnostic.
|
|
46
|
-
if (kind === "acc-hook" || kind === "acc-claude-channel") {
|
|
46
|
+
if (kind === "acc-hook" || kind === "acc-claude-channel" || kind === "acc-antigravity-relay") {
|
|
47
47
|
process.stderr.write("acc: coordination unavailable during runtime update; session may continue\n");
|
|
48
48
|
process.exitCode = 0;
|
|
49
49
|
} else if (kind === "acc-bootstrap") {
|
|
@@ -16,6 +16,7 @@ export const stablePaths = root => ({
|
|
|
16
16
|
cli: path.join(root, "bin", "acc.mjs"), runner: path.join(root, "bin", "acc-hook.mjs"),
|
|
17
17
|
bootstrap: path.join(root, "bin", "acc-bootstrap.mjs"),
|
|
18
18
|
channel: path.join(root, "bin", "acc-claude-channel.mjs"),
|
|
19
|
+
antigravityRelay: path.join(root, "bin", "acc-antigravity-relay.mjs"),
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
/** Publish immutable launcher modules, then atomically replace each tiny entry.
|
|
@@ -41,3 +41,34 @@ export async function bindNative({ adapter, event, hookBinding, clientVersion, p
|
|
|
41
41
|
}
|
|
42
42
|
return result;
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
const HINT_MAX_BYTES = 512;
|
|
46
|
+
const HINT_MAX_MS = 250;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One line an adapter asks the agent to act on, when the agent itself can fix
|
|
50
|
+
* a degraded native binding - Antigravity's relay is started from the agent's
|
|
51
|
+
* own shell, and nothing else can start it. Bounded and fail-open: a slow or
|
|
52
|
+
* failing adapter costs the turn nothing, and anything but one short line is
|
|
53
|
+
* dropped rather than trusted into model context.
|
|
54
|
+
*/
|
|
55
|
+
export async function nativeActivationHintFor({ adapter, event, nativeBinding, binding, context,
|
|
56
|
+
paths, deadline }) {
|
|
57
|
+
if (typeof adapter?.nativeActivationHint !== "function" || nativeBinding?.state !== "degraded") return null;
|
|
58
|
+
const budget = Math.min(HINT_MAX_MS, deadline - Date.now() - HINT_MAX_MS);
|
|
59
|
+
if (budget <= 0) return null;
|
|
60
|
+
let timer = null;
|
|
61
|
+
try {
|
|
62
|
+
const line = await Promise.race([
|
|
63
|
+
Promise.resolve(adapter.nativeActivationHint({ event, nativeBinding, runtimeDir: paths.root,
|
|
64
|
+
clientPid: binding?.clientPid, env: context.env })),
|
|
65
|
+
new Promise(resolve => { timer = setTimeout(resolve, budget, null); }),
|
|
66
|
+
]);
|
|
67
|
+
return typeof line === "string" && line !== "" && !/[\r\n]/.test(line)
|
|
68
|
+
&& Buffer.byteLength(line, "utf8") <= HINT_MAX_BYTES ? line : null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
} finally {
|
|
72
|
+
if (timer !== null) clearTimeout(timer);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -14,7 +14,7 @@ import { clearPin, createGitProbe, resolveHookWorkspace, platformDataHome, runti
|
|
|
14
14
|
|
|
15
15
|
import { resolveClientPid } from "./client-pid.mjs";
|
|
16
16
|
import { probeClientVersion as defaultProbeClientVersion } from "./client-version.mjs";
|
|
17
|
-
import { bindNative, nativeDiagnosticDeadline } from "./native-attempt.mjs";
|
|
17
|
+
import { bindNative, nativeActivationHintFor, nativeDiagnosticDeadline } from "./native-attempt.mjs";
|
|
18
18
|
import { readProcessTable as defaultReadProcessTable } from "./process-table.mjs";
|
|
19
19
|
import { withSessionLifecycle } from "./session-lifecycle.mjs";
|
|
20
20
|
import { appendStartOwner, appendToolOwner, ownerHeader, ownerOnlyOutcome } from "./owner-context.mjs";
|
|
@@ -232,7 +232,10 @@ async function openContext({ event, adapterId, dataHome, runtime, env, deadline
|
|
|
232
232
|
|
|
233
233
|
// What this turn is shown and which complete groups it may commit as
|
|
234
234
|
// offered. Runs after the heartbeat and the bounded native retry.
|
|
235
|
-
|
|
235
|
+
// `render` is how the projected text reaches this client: before a turn it is
|
|
236
|
+
// the adapter's context envelope, at the end of one it is a continuation.
|
|
237
|
+
async function projectTurn({ binding, context, adapter, adapterId,
|
|
238
|
+
render = text => adapter.injectOutcome?.(text), activationHint = null }) {
|
|
236
239
|
const sync = await context.service.sync({ sessionId: binding.accSessionId,
|
|
237
240
|
cursor: null, scope: "delta" });
|
|
238
241
|
|
|
@@ -255,8 +258,14 @@ async function projectTurn({ binding, context, adapter, adapterId }) {
|
|
|
255
258
|
// Only this hook's payload selected the binding. Supply its own pair as
|
|
256
259
|
// trusted context, outside peer bodies, rather than exporting inheritable
|
|
257
260
|
// credentials or teaching the CLI to guess from a public roster.
|
|
258
|
-
const owner = ownerHeader(binding, context.workspaceCwd, context.workspaceRef);
|
|
259
261
|
const totalBudget = context.descriptor.policy?.contextBudgetBytes ?? 6_000;
|
|
262
|
+
// An adapter's one-line ask rides with the owner line, which every projected
|
|
263
|
+
// turn carries - and only when both fit in half the budget, so peer bodies
|
|
264
|
+
// always keep the rest.
|
|
265
|
+
const header = ownerHeader(binding, context.workspaceCwd, context.workspaceRef);
|
|
266
|
+
const owner = activationHint === null
|
|
267
|
+
|| byteLength(header) + 1 + byteLength(activationHint) > totalBudget / 2
|
|
268
|
+
? header : `${header}\n${activationHint}`;
|
|
260
269
|
// A peer can join after this prompt has begun. The current turn must already
|
|
261
270
|
// have its own arguments when it needs inbox/reply, without reattaching or
|
|
262
271
|
// waiting for another user prompt. Solo emits identity, not a peer notice.
|
|
@@ -332,7 +341,7 @@ async function projectTurn({ binding, context, adapter, adapterId }) {
|
|
|
332
341
|
// The entry point owns the transport boundary. This handler only prepares
|
|
333
342
|
// offer inputs; recording them here would claim delivery before stdout's
|
|
334
343
|
// callback proves that the bytes crossed.
|
|
335
|
-
const outcome = { stdout: "", ...
|
|
344
|
+
const outcome = { stdout: "", ...render(projected) };
|
|
336
345
|
const writableOffers = outcome.stdout === "" ? [] : offerInputs;
|
|
337
346
|
return { ...outcome,
|
|
338
347
|
stderr: [outcome.stderr, ownerWarning, degradation].filter(Boolean).join("\n"),
|
|
@@ -485,7 +494,9 @@ const HANDLERS = {
|
|
|
485
494
|
const started = await HANDLERS.sessionStart(input);
|
|
486
495
|
const fresh = await loadSessionBinding({ runtimeDir: paths.root,
|
|
487
496
|
harnessSessionId: event.sessionId });
|
|
488
|
-
const
|
|
497
|
+
const activationHint = await nativeActivationHintFor({ adapter, event,
|
|
498
|
+
nativeBinding: started.nativeBinding, binding: fresh, context, paths, deadline });
|
|
499
|
+
const turn = await projectTurn({ ...input, binding: fresh, activationHint });
|
|
489
500
|
return { ...turn, nativeBinding: started.nativeBinding };
|
|
490
501
|
}
|
|
491
502
|
const current = await context.service.locateSession(binding.accSessionId);
|
|
@@ -499,7 +510,9 @@ const HANDLERS = {
|
|
|
499
510
|
const started = await HANDLERS.sessionStart(input);
|
|
500
511
|
const fresh = await loadSessionBinding({ runtimeDir: paths.root,
|
|
501
512
|
harnessSessionId: event.sessionId });
|
|
502
|
-
const
|
|
513
|
+
const activationHint = await nativeActivationHintFor({ adapter, event,
|
|
514
|
+
nativeBinding: started.nativeBinding, binding: fresh, context, paths, deadline });
|
|
515
|
+
const turn = await projectTurn({ ...input, binding: fresh, activationHint });
|
|
503
516
|
return { ...turn, nativeBinding: started.nativeBinding };
|
|
504
517
|
}
|
|
505
518
|
// A turn is the clearest sign a session is alive. Never a reason to fail:
|
|
@@ -511,10 +524,38 @@ const HANDLERS = {
|
|
|
511
524
|
const nativeBinding = await bindNative({ adapter, event, hookBinding: binding,
|
|
512
525
|
clientVersion: binding.clientVersion, platform: binding.platform, context, paths,
|
|
513
526
|
deadline });
|
|
514
|
-
const
|
|
527
|
+
const activationHint = await nativeActivationHintFor({ adapter, event, nativeBinding,
|
|
528
|
+
binding, context, paths, deadline });
|
|
529
|
+
const turn = await projectTurn({ ...input, activationHint });
|
|
515
530
|
return nativeBinding === undefined ? turn : { ...turn, nativeBinding };
|
|
516
531
|
},
|
|
517
532
|
|
|
533
|
+
/**
|
|
534
|
+
* The end of a turn, for a client that can hold one open.
|
|
535
|
+
*
|
|
536
|
+
* A peer message that arrives while the model is producing its last answer
|
|
537
|
+
* has no next invocation to ride on, and would otherwise wait for the next
|
|
538
|
+
* user prompt - which can be hours. A client whose end-of-turn hook can
|
|
539
|
+
* continue the turn (`continueTurnOutcome`) gets that message now instead.
|
|
540
|
+
*
|
|
541
|
+
* Only for peer bodies the projector actually offers. Continuing a turn costs
|
|
542
|
+
* the operator a model invocation, so an owner header, an attention count or
|
|
543
|
+
* a degradation notice is never a reason to. The adapter owns the shape and
|
|
544
|
+
* the ceiling, and reads its own counter from the payload handed back to it;
|
|
545
|
+
* when it declines, it prints nothing, no offer is recorded, and the body
|
|
546
|
+
* stays queued for the next invocation. Every failure ends the turn normally.
|
|
547
|
+
*/
|
|
548
|
+
async turnEnd(input) {
|
|
549
|
+
const { binding, context, adapter, payload } = input;
|
|
550
|
+
if (typeof adapter.continueTurnOutcome !== "function" || binding === null) return {};
|
|
551
|
+
const current = await context.service.locateSession(binding.accSessionId);
|
|
552
|
+
if (current === null || current.record.state !== "open"
|
|
553
|
+
|| current.record.generation !== binding.generation) return {};
|
|
554
|
+
const turn = await projectTurn({ ...input,
|
|
555
|
+
render: text => adapter.continueTurnOutcome({ reason: text, payload }) });
|
|
556
|
+
return (turn.offerInputs ?? []).length === 0 ? {} : turn;
|
|
557
|
+
},
|
|
558
|
+
|
|
518
559
|
async beforeTool({ binding, context, event, adapter }) {
|
|
519
560
|
if (binding === null) return { decision: "allow" };
|
|
520
561
|
if (event.targets.length === 0) {
|
|
@@ -582,6 +623,13 @@ const HANDLERS = {
|
|
|
582
623
|
* only thing this function refuses to do is fail closed.
|
|
583
624
|
*/
|
|
584
625
|
export async function runHook({ adapterId, payload, adapters, dataHome, env,
|
|
626
|
+
// The arguments the client's hook command carried, after the adapter id.
|
|
627
|
+
// Most clients name the event inside the payload and their adapters ignore
|
|
628
|
+
// this; Antigravity CLI sends no `hook_event_name` at all, and two of its
|
|
629
|
+
// four events hand over byte-identical envelopes - so for that client the
|
|
630
|
+
// registered command's own argument is the only thing that knows which hook
|
|
631
|
+
// ran. Passed to every adapter, read by the ones that need it.
|
|
632
|
+
args = [],
|
|
585
633
|
runtime = defaultRuntime(), budgetMs = DEFAULT_BUDGET_MS,
|
|
586
634
|
readProcessTable = defaultReadProcessTable,
|
|
587
635
|
probeClientVersion = defaultProbeClientVersion,
|
|
@@ -594,7 +642,7 @@ export async function runHook({ adapterId, payload, adapters, dataHome, env,
|
|
|
594
642
|
const adapter = adapters?.[adapterId];
|
|
595
643
|
if (adapter === undefined) throw new Error(`no adapter named ${adapterId}`);
|
|
596
644
|
|
|
597
|
-
const event = await adapter.normalizeHook(payload);
|
|
645
|
+
const event = await adapter.normalizeHook(payload, { args });
|
|
598
646
|
const context = await openContext({ event, adapterId, dataHome, runtime, env, deadline });
|
|
599
647
|
const handler = HANDLERS[event.kind];
|
|
600
648
|
const lifecycle = ["sessionStart", "sessionEnd", "beforeTurn"].includes(event.kind);
|
|
@@ -610,8 +658,10 @@ export async function runHook({ adapterId, payload, adapters, dataHome, env,
|
|
|
610
658
|
const binding = await loadSessionBinding({ runtimeDir: context.paths.root,
|
|
611
659
|
harnessSessionId: event.sessionId });
|
|
612
660
|
assertHookBudget(deadline);
|
|
661
|
+
// The raw payload goes to a handler only to be handed back to the adapter
|
|
662
|
+
// that produced it; nothing in core reads it.
|
|
613
663
|
const result = handler === undefined ? {} : await handler({ event, context, adapter, adapterId,
|
|
614
|
-
binding, paths: context.paths,
|
|
664
|
+
binding, paths: context.paths, payload,
|
|
615
665
|
readProcessTable, probeClientVersion, platform, deadline });
|
|
616
666
|
return appendToolOwner(appendStartOwner(result, { event, context, adapter }),
|
|
617
667
|
{ event, binding, context, adapter });
|
|
@@ -159,6 +159,10 @@ export async function detectInstallation({ adapters, context, probe = spawnProbe
|
|
|
159
159
|
try {
|
|
160
160
|
const detected = await adapter.detect({ ...context, clientVersion: entry.version,
|
|
161
161
|
platform, nativeDelivery: entry.nativeDelivery });
|
|
162
|
+
// An adapter that knows it cannot be installed right now says so here,
|
|
163
|
+
// rather than by throwing out of install and taking every other client
|
|
164
|
+
// on the machine down with it.
|
|
165
|
+
if (detected.blocked) entry.blocked = detected.blocked;
|
|
162
166
|
if (detected.outgoingDelivery) entry.outgoingDelivery = detected.outgoingDelivery;
|
|
163
167
|
if (detected.nativeSetup) entry.nativeSetup = detected.nativeSetup;
|
|
164
168
|
entry.diagnostics = [...(detected.diagnostics ?? [])];
|
|
@@ -78,6 +78,16 @@ export function planInstallation({ adapters, detected, context, action = "instal
|
|
|
78
78
|
+ `if it is, wire it with --adapter ${entry.adapterId}` });
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
|
+
// An adapter can refuse itself for a reason that is nobody's fault and that
|
|
82
|
+
// no file on disk shows: Antigravity CLI cannot be wired until somebody
|
|
83
|
+
// picks between a machine-wide and a per-workspace hook registration. It is
|
|
84
|
+
// skipped by name so the rest of the run proceeds - but never when ACC has
|
|
85
|
+
// a record for it, because that record is the only account of what was
|
|
86
|
+
// written and an unremovable install is the worse failure.
|
|
87
|
+
if (entry.blocked !== undefined && recordedById.get(entry.adapterId) === undefined) {
|
|
88
|
+
skipped.push({ adapterId: entry.adapterId, reason: entry.blocked.reason });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
81
91
|
// The version doing the installing is whichever `acc` came first on PATH,
|
|
82
92
|
// and the shim it writes pins that copy's node and runner. So a second ACC
|
|
83
93
|
// on the machine quietly replaces every client's wiring with its own, older
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-can-communicate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"accManagedUpdateProtocol": 2,
|
|
5
5
|
"accStoreVersion": 6,
|
|
6
6
|
"type": "module",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"LICENSE"
|
|
71
71
|
],
|
|
72
72
|
"dependencies": {
|
|
73
|
+
"@agents-can-communicate/adapter-antigravity": "*",
|
|
73
74
|
"@agents-can-communicate/adapter-claude-code": "*",
|
|
74
75
|
"@agents-can-communicate/adapter-codex": "*",
|
|
75
76
|
"@agents-can-communicate/adapter-gemini-cli": "*",
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
"@agents-can-communicate/storage-filesystem": "*"
|
|
87
88
|
},
|
|
88
89
|
"bundleDependencies": [
|
|
90
|
+
"@agents-can-communicate/adapter-antigravity",
|
|
89
91
|
"@agents-can-communicate/adapter-claude-code",
|
|
90
92
|
"@agents-can-communicate/adapter-codex",
|
|
91
93
|
"@agents-can-communicate/adapter-gemini-cli",
|