agents-can-communicate 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/bin/entrypoints/acc-antigravity-relay.mjs +2 -4
- package/bin/entrypoints/acc-hook.mjs +33 -19
- package/bin/entrypoints/hook-output.mjs +14 -5
- package/docs/ADAPTER_AUTHORING.md +32 -0
- package/docs/ARCHITECTURE.md +7 -4
- package/docs/CAPABILITIES.md +7 -6
- package/docs/CLI.md +5 -0
- package/docs/CONFIGURATION.md +15 -10
- package/docs/DESIGN_DECISIONS.md +0 -2
- package/docs/GETTING_STARTED.md +39 -6
- package/docs/HOW_IT_WORKS.md +13 -8
- package/docs/SECURITY_MODEL.md +26 -5
- package/docs/TROUBLESHOOTING.md +74 -12
- package/docs/UPGRADING.md +50 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/certification.json +78 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/certification-provenance.json +111 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-desktop-2.19.1-product-evidence.json +156 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-desktop-2.19.1-product.json +25 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/desktop-2.19.1.json +141 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +4 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +13 -2
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/agentapi.mjs +7 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/allow-rule.mjs +98 -17
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/client-process.mjs +64 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-allow-rule.mjs +140 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-delivery.mjs +110 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-endpoint.mjs +203 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +61 -30
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +61 -24
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-endpoint.mjs +30 -20
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-start.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay.mjs +20 -12
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relays.mjs +18 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +6 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +50 -37
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +44 -31
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-registry.mjs +99 -14
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-settings.mjs +3 -7
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-socket-directories.mjs +6 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +32 -7
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +68 -3
- package/node_modules/@agents-can-communicate/adapter-codex/src/daemon-layouts.mjs +13 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/embedded-host.mjs +87 -13
- package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +16 -6
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +18 -9
- package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +68 -27
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +66 -26
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +39 -16
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +4 -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/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +15 -9
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +25 -14
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +15 -10
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +50 -5
- package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +11 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/executables.mjs +149 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +79 -12
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +11 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +6 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +82 -17
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +9 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/private-files.mjs +35 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/process-table.mjs +232 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +8 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/windows-command.mjs +69 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/windows-process-chain.wsf +45 -0
- package/node_modules/@agents-can-communicate/cli/package.json +2 -1
- package/node_modules/@agents-can-communicate/cli/src/config-command.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +60 -4
- package/node_modules/@agents-can-communicate/cli/src/hook-support.mjs +9 -0
- package/node_modules/@agents-can-communicate/cli/src/hook-workspace.mjs +12 -3
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +3 -2
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +12 -3
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +7 -5
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +7 -7
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +8 -5
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/maintenance.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/mutex.mjs +7 -6
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/portable-fs.mjs +165 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +18 -8
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +18 -3
- package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +15 -4
- package/node_modules/@agents-can-communicate/cli/src/platform-paths.mjs +15 -10
- package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +3 -2
- package/node_modules/@agents-can-communicate/cli/src/update-command.mjs +11 -1
- package/node_modules/@agents-can-communicate/cli/src/workspace-discovery.mjs +4 -2
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +7 -0
- package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +39 -1
- package/node_modules/@agents-can-communicate/core/src/session-opening.mjs +11 -0
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +8 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/native-reason.mjs +6 -2
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +9 -5
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +65 -6
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +80 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +7 -2
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +6 -3
- package/node_modules/@agents-can-communicate/hook-runner/src/owner-context.mjs +34 -6
- package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +3 -50
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +167 -34
- package/node_modules/@agents-can-communicate/installer/package.json +3 -2
- package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +11 -2
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +13 -3
- package/node_modules/@agents-can-communicate/installer/src/hook-support.mjs +5 -0
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +6 -14
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +14 -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/protocol/src/config.mjs +6 -2
- package/node_modules/@agents-can-communicate/protocol/src/resources.mjs +6 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/active-journal.mjs +5 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +41 -23
- package/node_modules/@agents-can-communicate/storage-filesystem/src/doomed-directory.mjs +4 -12
- package/node_modules/@agents-can-communicate/storage-filesystem/src/identity.mjs +7 -3
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/journal.mjs +24 -7
- package/node_modules/@agents-can-communicate/storage-filesystem/src/portable-fs.mjs +165 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/publication-path.mjs +8 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/reclaim.mjs +26 -17
- package/node_modules/@agents-can-communicate/storage-filesystem/src/recovery.mjs +7 -5
- package/node_modules/@agents-can-communicate/storage-filesystem/src/retention.mjs +7 -16
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-directory.mjs +138 -24
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-file.mjs +3 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/stage-sweep.mjs +9 -6
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +30 -8
- package/node_modules/@agents-can-communicate/storage-filesystem/src/writer-mutex.mjs +102 -32
- package/package.json +1 -5
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"note": "The desktop-app capture, run on Antigravity 2.19.1 (the desktop app), macOS 27.0 arm64, 2026-10-04, with ACC 0.8.5 installed. The operator opened a project in the app and sent one prompt; ACC's installed hooks attached the conversation. A separate process read the language server's command line, took the CSRF token only into one child's environment, found the HTTP port in the server's log, and ran agy agentapi send-message once into the idle conversation, rendered as ACC's relay renders a peer message. Nothing printed or recorded holds the token. The conversation id is real; the probe project path is redacted.",
|
|
3
|
+
"client": "antigravity-desktop",
|
|
4
|
+
"version": "2.19.1",
|
|
5
|
+
"platform": "darwin-arm64",
|
|
6
|
+
"languageServer": {
|
|
7
|
+
"parent": "/Applications/Antigravity.app/Contents/MacOS/Antigravity",
|
|
8
|
+
"command": "/Applications/Antigravity.app/Contents/Resources/bin/language_server --standalone --override_ide_name antigravity --subclient_type hub --override_ide_version 2.19.1 --override_user_agent_name antigravity --https_server_port 0 --csrf_token <token> --app_data_dir antigravity --api_server_url https://generativelanguage.googleapis.com --cloud_code_endpoint https://daily-cloudcode-pa.googleapis.com --enable_sidecars --host_bridge_url=http://127.0.0.1:<port> --host_bridge_token=<token>",
|
|
9
|
+
"log": [
|
|
10
|
+
"Language server listening on random port at 55329 for HTTPS (gRPC)",
|
|
11
|
+
"Language server listening on random port at 55330 for HTTP"
|
|
12
|
+
],
|
|
13
|
+
"listening": [
|
|
14
|
+
"127.0.0.1:55329",
|
|
15
|
+
"127.0.0.1:55330"
|
|
16
|
+
],
|
|
17
|
+
"healthz": {
|
|
18
|
+
"55329": 400,
|
|
19
|
+
"55330": 200
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"hookProcesses": [
|
|
23
|
+
{
|
|
24
|
+
"at": "03:06:21Z",
|
|
25
|
+
"parent": "language_server",
|
|
26
|
+
"command": "node <data home>/acc/runtime/bin/acc-hook.mjs antigravity SessionStart"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"at": "03:06:22Z",
|
|
30
|
+
"parent": "language_server",
|
|
31
|
+
"command": "node <data home>/acc/runtime/bin/acc-hook.mjs antigravity PreInvocation"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"agentShellVariables": [
|
|
35
|
+
"ANTIGRAVITY_AGENT",
|
|
36
|
+
"ANTIGRAVITY_AGENTAPI_EXE",
|
|
37
|
+
"ANTIGRAVITY_APP_DATA_DIR",
|
|
38
|
+
"ANTIGRAVITY_CONVERSATION_ID",
|
|
39
|
+
"ANTIGRAVITY_CSRF_TOKEN",
|
|
40
|
+
"ANTIGRAVITY_LS_ADDRESS",
|
|
41
|
+
"ANTIGRAVITY_LS_VERSION",
|
|
42
|
+
"ANTIGRAVITY_PROJECT_ID",
|
|
43
|
+
"ANTIGRAVITY_SOURCE_METADATA",
|
|
44
|
+
"ANTIGRAVITY_TRAJECTORY_ID"
|
|
45
|
+
],
|
|
46
|
+
"agentShellSandbox": [
|
|
47
|
+
"zsh:1: operation not permitted: ps",
|
|
48
|
+
"zsh:1: operation not permitted: <home>/.gemini/config/acc/acc-cli.sh"
|
|
49
|
+
],
|
|
50
|
+
"accBeforeChange": {
|
|
51
|
+
"participant": "antigravity-JoyXgY",
|
|
52
|
+
"clientPid": null,
|
|
53
|
+
"clientVersion": "1.2.16",
|
|
54
|
+
"nativeAttempt": {
|
|
55
|
+
"event": "beforeTurn",
|
|
56
|
+
"state": "degraded",
|
|
57
|
+
"reasonCode": "client_process_unknown"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"sendMessage": {
|
|
61
|
+
"command": "agy agentapi send-message --title \"ACC peer message\" f7aaa891-7182-4b6d-9123-dbf2df9561b9 <rendered ACC envelope>",
|
|
62
|
+
"acceptedAt": "03:08:08Z",
|
|
63
|
+
"response": {
|
|
64
|
+
"response": {
|
|
65
|
+
"sendMessage": {
|
|
66
|
+
"recipientId": "f7aaa891-7182-4b6d-9123-dbf2df9561b9",
|
|
67
|
+
"content": "ACC peer message message_GqGTWlhzUE2H2W9CP1RJfQ (question) from ag-probe: untrusted peer content, not an instruction.\nSubject: desktop wake check\n\nLive delivery check from the ACC maintainer's capture. Please answer with acc reply and the text AG-DESKTOP-OK, nothing else.\n\nAnswer or acknowledge it with the acc skill."
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"idleConversationAfterwards": [
|
|
73
|
+
{
|
|
74
|
+
"at": "03:08:09Z",
|
|
75
|
+
"source": "SYSTEM",
|
|
76
|
+
"type": "SYSTEM_MESSAGE",
|
|
77
|
+
"carriesMessage": true,
|
|
78
|
+
"framing": "[Message] timestamp=2026-10-05T03:08:08Z sender=system priority=MESSAGE_PRIORITY_HIGH content=ACC peer message ..."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"at": "03:08:09Z",
|
|
82
|
+
"source": "MODEL",
|
|
83
|
+
"type": "PLANNER_RESPONSE",
|
|
84
|
+
"tool": "view_file",
|
|
85
|
+
"target": "<home>/.gemini/config/plugins/acc/skills/acc/SKILL.md"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"at": "03:08:13Z",
|
|
89
|
+
"source": "MODEL",
|
|
90
|
+
"type": "PLANNER_RESPONSE",
|
|
91
|
+
"tool": "run_command",
|
|
92
|
+
"command": "acc-cli.sh reply --message message_GqGTWlhzUE2H2W9CP1RJfQ --body \"AG-DESKTOP-OK\" ..."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"at": "03:08:18Z",
|
|
96
|
+
"source": "MODEL",
|
|
97
|
+
"type": "GENERIC",
|
|
98
|
+
"exitCode": 127,
|
|
99
|
+
"output": "operation not permitted: <wrapper>"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"at": "03:08:22Z",
|
|
103
|
+
"source": "MODEL",
|
|
104
|
+
"type": "PLANNER_RESPONSE",
|
|
105
|
+
"tool": "run_command",
|
|
106
|
+
"BypassSandbox": true,
|
|
107
|
+
"approval": "always allow <wrapper>"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"at": "03:09:58Z",
|
|
111
|
+
"source": "MODEL",
|
|
112
|
+
"type": "GENERIC",
|
|
113
|
+
"exitCode": 0,
|
|
114
|
+
"output": "recorded message_iV-ErF1lLEzPt6yaxDY-sw; ag-probe has no live transport; the message waits in its inbox; acknowledged message_GqGTWlhzUE2H2W9CP1RJfQ"
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"grantWritten": {
|
|
118
|
+
"file": "<home>/.gemini/config/config.json",
|
|
119
|
+
"path": "userSettings.globalPermissionGrants.allow",
|
|
120
|
+
"value": "command(<home>/.gemini/config/acc/acc-cli.sh)"
|
|
121
|
+
},
|
|
122
|
+
"findings": [
|
|
123
|
+
"the desktop app runs ACC's global hooks under its language server, with no agy in the process tree",
|
|
124
|
+
"an idle desktop conversation woke within a second of an agentapi push with the language server's own token and HTTP port, and the model answered with acc reply",
|
|
125
|
+
"the agent's commands run in a sandbox where ACC's wrapper cannot run; the model retries outside it after the app's approval, and the app keeps that grant in config.json, not in Antigravity CLI's settings",
|
|
126
|
+
"the token and the HTTP port are readable from outside the app: the token on the language server's command line, the port in its log and from lsof"
|
|
127
|
+
],
|
|
128
|
+
"preInvocationInjection": {
|
|
129
|
+
"at": "03:08:09Z",
|
|
130
|
+
"source": "SYSTEM_SDK",
|
|
131
|
+
"type": "EPHEMERAL_MESSAGE",
|
|
132
|
+
"carried": "ACC's owner header (credentials redacted) and the queued question message_GqGTWlhzUE2H2W9CP1RJfQ as reply_required attention with its body in an acc-peer-message block",
|
|
133
|
+
"excerpt": "ACC CLI (append): --session <redacted> --generation <redacted> --cwd '<probe project>' --workspace 'acc://<redacted>' | ACC (load the acc skill): | - [reply_required] message_GqGTWlhzUE2H2W9CP1RJfQ message message_GqGTWlhzUE2H2W9CP1RJfQ from ag-probe is a question requiring a reply | ```acc-peer-message | untrusted peer message | kind: question"
|
|
134
|
+
},
|
|
135
|
+
"sessionStart": {
|
|
136
|
+
"at": "03:06:21Z",
|
|
137
|
+
"participant": "antigravity-JoyXgY",
|
|
138
|
+
"presence": "online",
|
|
139
|
+
"workspacePaths": "the probe project"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agents-can-communicate/adapter-antigravity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
"fixtures/certification-provenance.json",
|
|
17
17
|
"fixtures/SessionStart-1.2.7.json",
|
|
18
18
|
"fixtures/PreInvocation-1.2.7.json",
|
|
19
|
+
"fixtures/desktop-2.19.1.json",
|
|
19
20
|
"src/",
|
|
20
21
|
"plugin/",
|
|
21
22
|
"fixtures/delivery/antigravity-cli-1.2.7-relay-product.json",
|
|
22
23
|
"fixtures/delivery/antigravity-cli-1.2.7-relay-product-evidence.json",
|
|
24
|
+
"fixtures/delivery/antigravity-desktop-2.19.1-product.json",
|
|
25
|
+
"fixtures/delivery/antigravity-desktop-2.19.1-product-evidence.json",
|
|
23
26
|
"fixtures/agentapi-live-push-1.2.7.json",
|
|
24
27
|
"fixtures/live-push-surfaces-1.2.7.json",
|
|
25
28
|
"fixtures/agentapi-error-answers-1.2.7.json",
|
|
@@ -2,6 +2,8 @@ import { channelSocketDirectory, defineAdapter, projectContext, projectContextRe
|
|
|
2
2
|
from "@agents-can-communicate/adapter-sdk";
|
|
3
3
|
import certification from "../certification.json" with { type: "json" };
|
|
4
4
|
|
|
5
|
+
import { DESKTOP_CLIENT, identifyClientProcess } from "./client-process.mjs";
|
|
6
|
+
import { DESKTOP_PROTOCOL } from "./desktop-endpoint.mjs";
|
|
5
7
|
import { denyOutcome, injectOutcome, normalizeAntigravityHook, stopOutcome } from "./hooks.mjs";
|
|
6
8
|
import { detectAntigravity, doctorAntigravity, installAntigravity, planAntigravityInstall,
|
|
7
9
|
preflightAntigravityUninstall, uninstallAntigravity } from "./install.mjs";
|
|
@@ -14,6 +16,8 @@ import { PROTOCOL_CONTRACT } from "./relay-endpoint.mjs";
|
|
|
14
16
|
// capture until one of their own says otherwise, which is how certification
|
|
15
17
|
// evidence reads everywhere since 0.6.2.
|
|
16
18
|
export const ANTIGRAVITY_CLI_VERSION = "1.2.7";
|
|
19
|
+
// The first passing product capture of live delivery into Antigravity 2.0.
|
|
20
|
+
export const DESKTOP_FIRST_CAPTURE = "2.19.1";
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Antigravity CLI.
|
|
@@ -63,7 +67,10 @@ export function createAntigravityAdapter() {
|
|
|
63
67
|
// spawn: `agy --version` answers `1.2.7`. Presence liveness also walks the
|
|
64
68
|
// hook's process ancestry for this basename to learn the client's own pid.
|
|
65
69
|
client: { command: "agy", certificationName: "antigravity-cli",
|
|
66
|
-
versionArgs: ["--version"] },
|
|
70
|
+
versionArgs: ["--version"], variants: [DESKTOP_CLIENT] },
|
|
71
|
+
// Antigravity 2.0, the desktop app, runs these same hooks under its
|
|
72
|
+
// language server, which names its own version; see client-process.mjs.
|
|
73
|
+
identifyClientProcess,
|
|
67
74
|
certification,
|
|
68
75
|
capabilities: {
|
|
69
76
|
lifecycle: { sessionStart: true },
|
|
@@ -77,7 +84,11 @@ export function createAntigravityAdapter() {
|
|
|
77
84
|
// binding on every turn, and must not take the relay with it.
|
|
78
85
|
nativeDelivery: {
|
|
79
86
|
minimum: ANTIGRAVITY_CLI_VERSION,
|
|
80
|
-
anchors: [{ version: ANTIGRAVITY_CLI_VERSION, protocolContract: PROTOCOL_CONTRACT }
|
|
87
|
+
anchors: [{ version: ANTIGRAVITY_CLI_VERSION, protocolContract: PROTOCOL_CONTRACT },
|
|
88
|
+
// Antigravity 2.0 pushes through its own language server, captured on
|
|
89
|
+
// its own version line; see desktop-delivery.mjs.
|
|
90
|
+
{ version: DESKTOP_FIRST_CAPTURE, protocolContract: DESKTOP_PROTOCOL,
|
|
91
|
+
client: DESKTOP_CLIENT.certificationName }],
|
|
81
92
|
knownBad: [], activationKinds: ["native-config"], policySource: "installation-record",
|
|
82
93
|
},
|
|
83
94
|
probeNativeDelivery, planNativeActivation, bindNativeSession, refreshNativeSession,
|
|
@@ -82,13 +82,15 @@ export function createAgentApi({ endpoint, baseEnv = process.env, run: runner =
|
|
|
82
82
|
* and preferring it keeps a push working for a client started by a full path
|
|
83
83
|
* that is not on the PATH. Only an absolute, executable file named agy is
|
|
84
84
|
* taken, because the relay calls it as `agy agentapi ...`; anything else falls
|
|
85
|
-
* back to the PATH.
|
|
85
|
+
* back to the PATH. On Windows that file is agy.exe: the message goes to agy as
|
|
86
|
+
* an argument, and a .cmd would hand it to cmd.exe to read; a bare `agy` there
|
|
87
|
+
* starts only an .exe or .com, for the same reason.
|
|
86
88
|
*/
|
|
87
|
-
export function agentApiCommand(env) {
|
|
89
|
+
export function agentApiCommand(env, { platform = process.platform } = {}) {
|
|
88
90
|
const named = env?.ANTIGRAVITY_AGENTAPI_EXE;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const base = typeof named === "string" ? path.basename(named) : "";
|
|
92
|
+
const isAgy = platform === "win32" ? base.toLowerCase() === "agy.exe" : base === "agy";
|
|
93
|
+
if (typeof named !== "string" || !path.isAbsolute(named) || !isAgy) return "agy";
|
|
92
94
|
try {
|
|
93
95
|
accessSync(named, constants.X_OK);
|
|
94
96
|
return statSync(named).isFile() ? named : "agy";
|
|
@@ -4,8 +4,12 @@ import path from "node:path";
|
|
|
4
4
|
import { blankJson, isShellWord, removeIfEmpty, writeForeignJson }
|
|
5
5
|
from "@agents-can-communicate/adapter-sdk";
|
|
6
6
|
|
|
7
|
+
import { relayStartCommand } from "./relays.mjs";
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
|
-
* The
|
|
10
|
+
* The allow rules ACC adds to Antigravity CLI's settings: this one for its
|
|
11
|
+
* wrapper, and the one for the command that starts live delivery
|
|
12
|
+
* (`inspectRelayRule`).
|
|
9
13
|
*
|
|
10
14
|
* Antigravity CLI asks before every shell command. On 1.2.12 a live push woke an
|
|
11
15
|
* idle session and its first ACC command waited at an approval prompt with
|
|
@@ -31,12 +35,15 @@ import { blankJson, isShellWord, removeIfEmpty, writeForeignJson }
|
|
|
31
35
|
*/
|
|
32
36
|
export const agySettingsPath = home => path.join(home, ".gemini", "antigravity-cli",
|
|
33
37
|
"settings.json");
|
|
34
|
-
|
|
38
|
+
// Windows runs a Node shim where POSIX runs `acc-cli.sh`.
|
|
39
|
+
export const cliWrapperPath = (home, platform = process.platform) => (platform === "win32"
|
|
40
|
+
? path.win32.join(home, ".gemini", "config", "acc", "acc-cli.mjs")
|
|
41
|
+
: path.join(home, ".gemini", "config", "acc", "acc-cli.sh"));
|
|
35
42
|
export const accAllowRule = wrapper => `command(${wrapper})`;
|
|
36
43
|
|
|
37
44
|
// Forms that already allow every command starting with the bare wrapper: the
|
|
38
45
|
// one ACC writes, and the quoted one-word form 1.2.12 was captured matching.
|
|
39
|
-
const allowingForms = wrapper => [accAllowRule(wrapper), `command("${wrapper}")`];
|
|
46
|
+
export const allowingForms = wrapper => [accAllowRule(wrapper), `command("${wrapper}")`];
|
|
40
47
|
|
|
41
48
|
// Beside the `created-*` markers, for the same reason: the installer removes the
|
|
42
49
|
// shim directory before uninstall runs. `acc doctor` passes the state root.
|
|
@@ -45,12 +52,13 @@ const claimDir = ({ dataHome, stateRoot, home }) => typeof dataHome === "string"
|
|
|
45
52
|
: typeof stateRoot === "string" && stateRoot !== ""
|
|
46
53
|
? path.join(stateRoot, "adapter-antigravity")
|
|
47
54
|
: path.join(home, ".gemini", "config", "acc");
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
// One claim per rule, so an older ACC keeps reading the one it wrote.
|
|
56
|
+
export const claimPath = (context, file, kind = "cli") => path.join(claimDir(context),
|
|
57
|
+
`allow-rule-${kind === "cli" ? "" : `${kind}-`}${Buffer.from(file).toString("base64url")}`);
|
|
50
58
|
|
|
51
59
|
const isObject = value => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
52
60
|
|
|
53
|
-
async function readClaim(file) {
|
|
61
|
+
export async function readClaim(file) {
|
|
54
62
|
try {
|
|
55
63
|
const claim = JSON.parse(await readFile(file, "utf8"));
|
|
56
64
|
return typeof claim?.rule === "string" ? claim : null;
|
|
@@ -91,9 +99,16 @@ const RESTART = "Antigravity CLI reads it at startup, so a session started befor
|
|
|
91
99
|
/** What the rule's state is on this machine, in words doctor can print. */
|
|
92
100
|
export async function inspectAllowRule(context) {
|
|
93
101
|
const file = agySettingsPath(context.home);
|
|
94
|
-
const
|
|
102
|
+
const hostPlatform = context.hostPlatform ?? process.platform;
|
|
103
|
+
const wrapper = cliWrapperPath(context.home, hostPlatform);
|
|
95
104
|
const rule = accAllowRule(wrapper);
|
|
96
105
|
const base = { file, wrapper, rule, owned: false };
|
|
106
|
+
if (hostPlatform === "win32") {
|
|
107
|
+
return { ...base, state: "unmatchable", diagnostic: "On Windows ACC adds no allow rule, so "
|
|
108
|
+
+ "Antigravity CLI asks before each ACC command. The skill runs `node` on the CLI shim, and "
|
|
109
|
+
+ "this client matches a rule against a command's first word: a rule on `node` would let "
|
|
110
|
+
+ "every node command through without asking" };
|
|
111
|
+
}
|
|
97
112
|
if (!isShellWord(wrapper)) {
|
|
98
113
|
return { ...base, state: "unmatchable", diagnostic: "each ACC command waits for approval, "
|
|
99
114
|
+ "so a live wake stops at the agent's first ACC command until someone answers the "
|
|
@@ -126,9 +141,9 @@ export async function inspectAllowRule(context) {
|
|
|
126
141
|
+ "--adapter antigravity, then restart agy" };
|
|
127
142
|
}
|
|
128
143
|
|
|
129
|
-
async function addRule(context, found) {
|
|
144
|
+
async function addRule(context, found, kind = "cli") {
|
|
130
145
|
const settings = await readSettings(found.file);
|
|
131
|
-
const claimFile = claimPath(context, found.file);
|
|
146
|
+
const claimFile = claimPath(context, found.file, kind);
|
|
132
147
|
// Only what this write creates. A file or container that is already there is
|
|
133
148
|
// the operator's as far as ACC can tell: one ACC made earlier cannot be told
|
|
134
149
|
// from one they put in its place, and carrying the old flags over deleted
|
|
@@ -157,9 +172,9 @@ async function addRule(context, found) {
|
|
|
157
172
|
* Anything else in the file stays, and a rule the operator had before ACC came
|
|
158
173
|
* was never claimed.
|
|
159
174
|
*/
|
|
160
|
-
|
|
175
|
+
async function withdrawRule(context, kind) {
|
|
161
176
|
const file = agySettingsPath(context.home);
|
|
162
|
-
const claimFile = claimPath(context, file);
|
|
177
|
+
const claimFile = claimPath(context, file, kind);
|
|
163
178
|
const claim = await readClaim(claimFile);
|
|
164
179
|
if (claim === null) return { changes: [], diagnostics: [], needsAction: [] };
|
|
165
180
|
const settings = await readSettings(file);
|
|
@@ -188,17 +203,83 @@ export async function withdrawAllowRule(context) {
|
|
|
188
203
|
needsAction: [] };
|
|
189
204
|
}
|
|
190
205
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
206
|
+
// In the reverse of the order install adds them: the wrapper's rule is the one
|
|
207
|
+
// that may have created the file, so it is the last to leave.
|
|
208
|
+
export async function withdrawAllowRule(context) {
|
|
209
|
+
const results = [await withdrawRule(context, "relay"), await withdrawRule(context, "cli")];
|
|
210
|
+
return { changes: [...new Set(results.flatMap(result => result.changes))],
|
|
211
|
+
diagnostics: results.flatMap(result => result.diagnostics),
|
|
212
|
+
needsAction: results.flatMap(result => result.needsAction) };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The rule for the command that starts live delivery.
|
|
217
|
+
*
|
|
218
|
+
* The hook tells the agent to start its relay once per conversation with
|
|
219
|
+
* `relayStartCommand`, and Antigravity CLI asks before running it: in a session
|
|
220
|
+
* nobody watches, the relay never starts and the session is never woken (e2e
|
|
221
|
+
* on Antigravity CLI 1.2.16, 2026-10-04). The rule is that command in the exact
|
|
222
|
+
* words the client persists itself when the operator picks "always allow" -
|
|
223
|
+
* its first word, `sh`, is bare, and the quoted path is an argument. It is
|
|
224
|
+
* written and taken back beside the wrapper's, under a claim of its own.
|
|
225
|
+
*/
|
|
226
|
+
export async function inspectRelayRule(context) {
|
|
227
|
+
const file = agySettingsPath(context.home);
|
|
228
|
+
const hostPlatform = context.hostPlatform ?? process.platform;
|
|
229
|
+
const command = relayStartCommand(context.home, hostPlatform);
|
|
230
|
+
const rule = accAllowRule(command);
|
|
231
|
+
const base = { file, rule, owned: false };
|
|
232
|
+
if (hostPlatform === "win32") {
|
|
233
|
+
return { ...base, state: "unmatchable", diagnostic: "On Windows ACC adds no allow rule "
|
|
234
|
+
+ "for the command that starts live delivery either: it runs `node`, and a rule on "
|
|
235
|
+
+ "`node` would let every node command through without asking" };
|
|
236
|
+
}
|
|
237
|
+
// Without the wrapper's rule the agent still asks before each ACC command, so
|
|
238
|
+
// this one would only move the prompt one command later.
|
|
239
|
+
if (!isShellWord(cliWrapperPath(context.home, hostPlatform))) {
|
|
240
|
+
return { ...base, state: "unmatchable", diagnostic: "ACC adds no allow rule for the command "
|
|
241
|
+
+ "that starts live delivery where it can add none for its wrapper: the agent would "
|
|
242
|
+
+ "still ask before each ACC command" };
|
|
243
|
+
}
|
|
244
|
+
const settings = await readSettings(file);
|
|
245
|
+
if (settings.state === "unreadable") {
|
|
246
|
+
return { ...base, state: "unreadable", diagnostic: `${file} ${settings.reason}, so ACC `
|
|
247
|
+
+ `cannot add ${rule}; the agent then asks before it starts live delivery` };
|
|
248
|
+
}
|
|
249
|
+
if (settings.allow.includes(rule)) {
|
|
250
|
+
const owned = (await readClaim(claimPath(context, file, "relay")))?.rule === rule;
|
|
251
|
+
return { ...base, state: "allowed", owned, diagnostic: "live delivery starts without an "
|
|
252
|
+
+ `approval prompt: permissions.allow in ${file} holds ${rule}${owned ? ", added by ACC" : ""}`
|
|
253
|
+
+ `. ${RESTART}` };
|
|
254
|
+
}
|
|
255
|
+
return { ...base, state: "prompts", diagnostic: "the agent asks before it starts live "
|
|
256
|
+
+ "delivery, so a session nobody watches is never woken. ACC's install adds "
|
|
257
|
+
+ `${rule} to permissions.allow in ${file}, and it is not there: run acc install `
|
|
258
|
+
+ "--adapter antigravity, then restart agy" };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function ensureRule(context, found, kind, added) {
|
|
194
262
|
if (found.state === "unmatchable" || found.state === "unreadable") {
|
|
195
263
|
return { changes: [], diagnostics: [found.diagnostic], needsAction: [found.diagnostic] };
|
|
196
264
|
}
|
|
197
265
|
if (found.state === "allowed") {
|
|
198
266
|
return { changes: [], diagnostics: [found.diagnostic], needsAction: [] };
|
|
199
267
|
}
|
|
200
|
-
await addRule(context, found);
|
|
268
|
+
await addRule(context, found, kind);
|
|
201
269
|
return { changes: [found.file], needsAction: [], diagnostics: [`added ${found.rule} to `
|
|
202
|
-
+ `permissions.allow in ${found.file}:
|
|
203
|
-
+ "
|
|
270
|
+
+ `permissions.allow in ${found.file}: ${added} without an approval prompt in Antigravity `
|
|
271
|
+
+ "sessions started from now on; restart agy for a session already open"] };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Put ACC's rules in place, at every install. */
|
|
275
|
+
export async function ensureAllowRule(context) {
|
|
276
|
+
const cli = await ensureRule(context, await inspectAllowRule(context), "cli",
|
|
277
|
+
"ACC commands run");
|
|
278
|
+
// A settings file that does not read is reported once, by the wrapper's rule.
|
|
279
|
+
const relayFound = await inspectRelayRule(context);
|
|
280
|
+
const relay = relayFound.state === "unreadable" ? { changes: [], diagnostics: [], needsAction: [] }
|
|
281
|
+
: await ensureRule(context, relayFound, "relay", "live delivery starts");
|
|
282
|
+
return { changes: [...new Set([...cli.changes, ...relay.changes])],
|
|
283
|
+
diagnostics: [...cli.diagnostics, ...relay.diagnostics],
|
|
284
|
+
needsAction: [...cli.needsAction, ...relay.needsAction] };
|
|
204
285
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which Antigravity product a hook runs under.
|
|
5
|
+
*
|
|
6
|
+
* Antigravity CLI runs hooks under `agy`, which the adapter's client command
|
|
7
|
+
* already names. Antigravity 2.0 - the desktop app - runs the same
|
|
8
|
+
* `~/.gemini/config/hooks.json` under its language server, with no `agy` at
|
|
9
|
+
* all, on a version line of its own. Captured on 2.19.1, 2026-10-04:
|
|
10
|
+
*
|
|
11
|
+
* /Applications/Antigravity.app/Contents/Resources/bin/language_server --standalone
|
|
12
|
+
* --override_ide_name antigravity --subclient_type hub --override_ide_version 2.19.1 ...
|
|
13
|
+
* --https_server_port 0 --csrf_token <uuid> --app_data_dir antigravity ...
|
|
14
|
+
*
|
|
15
|
+
* One language server serves every window and conversation of the app. The
|
|
16
|
+
* IDE's language server has another name, and the CLI keeps its own data in
|
|
17
|
+
* `antigravity-cli`, so `--standalone` with `--app_data_dir antigravity` names
|
|
18
|
+
* the desktop app alone.
|
|
19
|
+
*/
|
|
20
|
+
export const DESKTOP_CLIENT = Object.freeze({ certificationName: "antigravity-desktop",
|
|
21
|
+
displayName: "Antigravity" });
|
|
22
|
+
|
|
23
|
+
const STABLE = /^\d+\.\d+\.\d+$/;
|
|
24
|
+
|
|
25
|
+
/** The value of `--name value` or `--name=value` among the words, or null. */
|
|
26
|
+
export function flagValue(words, name) {
|
|
27
|
+
for (let index = 0; index < words.length; index += 1) {
|
|
28
|
+
if (words[index] === name) return words[index + 1] ?? null;
|
|
29
|
+
if (words[index].startsWith(`${name}=`)) return words[index].slice(name.length + 1);
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The desktop language server's command line, split into its executable and
|
|
36
|
+
* the words after it, or null for any other process. `ps` drops quoting, so the
|
|
37
|
+
* executable is everything before the first option: the bundle path may hold
|
|
38
|
+
* spaces, the options never do.
|
|
39
|
+
*/
|
|
40
|
+
export function desktopServer(args) {
|
|
41
|
+
if (typeof args !== "string") return null;
|
|
42
|
+
const cut = args.search(/\s--/);
|
|
43
|
+
if (cut === -1) return null;
|
|
44
|
+
const executable = args.slice(0, cut);
|
|
45
|
+
if (path.basename(executable) !== "language_server" || !path.isAbsolute(executable)) return null;
|
|
46
|
+
const words = args.slice(cut).trim().split(/\s+/);
|
|
47
|
+
if (!words.includes("--standalone") || flagValue(words, "--app_data_dir") !== "antigravity") return null;
|
|
48
|
+
const version = flagValue(words, "--override_ide_version");
|
|
49
|
+
return { executable, words, version: STABLE.test(version ?? "") ? version : null };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The adapter's identifyClientProcess: the desktop app, or null for the runner's own match. */
|
|
53
|
+
export function identifyClientProcess(entry) {
|
|
54
|
+
const server = desktopServer(entry?.args);
|
|
55
|
+
if (server === null) return null;
|
|
56
|
+
return { certificationName: DESKTOP_CLIENT.certificationName,
|
|
57
|
+
...(server.version === null ? {} : { version: server.version }) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Whether a process's argv is Antigravity CLI itself: `agy`, by path or bare, `.exe` on Windows. */
|
|
61
|
+
export function isAgy(argv) {
|
|
62
|
+
const program = (argv?.[0] ?? "").split(/[\\/]/).at(-1).replace(/\.exe$/i, "").toLowerCase();
|
|
63
|
+
return program === "agy";
|
|
64
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { isShellWord, writeForeignJson } from "@agents-can-communicate/adapter-sdk";
|
|
5
|
+
|
|
6
|
+
import { accAllowRule, allowingForms, claimPath, cliWrapperPath, readClaim } from "./allow-rule.mjs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The allow rule for ACC commands in Antigravity 2.0, the desktop app.
|
|
10
|
+
*
|
|
11
|
+
* The desktop keeps its own grants: picking "always allow" for ACC's wrapper on
|
|
12
|
+
* 2.19.1 wrote `command(<wrapper>)` to `~/.gemini/config/config.json` under
|
|
13
|
+
* `userSettings.globalPermissionGrants.allow`, and the rule ACC writes for
|
|
14
|
+
* Antigravity CLI in `~/.gemini/antigravity-cli/settings.json` never stopped
|
|
15
|
+
* the desktop from asking. The desktop creates config.json at its first
|
|
16
|
+
* launch, so a machine without the file has no desktop to allow anything for,
|
|
17
|
+
* and ACC does not create it.
|
|
18
|
+
*/
|
|
19
|
+
export const desktopConfigPath = home => path.join(home, ".gemini", "config", "config.json");
|
|
20
|
+
|
|
21
|
+
const isObject = value => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
22
|
+
const none = { changes: [], diagnostics: [], needsAction: [] };
|
|
23
|
+
|
|
24
|
+
async function readConfig(file) {
|
|
25
|
+
let text;
|
|
26
|
+
try {
|
|
27
|
+
text = await readFile(file, "utf8");
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === "ENOENT") return { state: "missing" };
|
|
30
|
+
return { state: "unreadable", reason: `could not be read (${error.code ?? error.message})` };
|
|
31
|
+
}
|
|
32
|
+
let value;
|
|
33
|
+
try {
|
|
34
|
+
value = JSON.parse(text.replace(/^/, ""));
|
|
35
|
+
} catch {
|
|
36
|
+
return { state: "unreadable", reason: "is not valid JSON" };
|
|
37
|
+
}
|
|
38
|
+
if (!isObject(value)) return { state: "unreadable", reason: "does not hold a JSON object" };
|
|
39
|
+
const settings = value.userSettings;
|
|
40
|
+
if (settings !== undefined && !isObject(settings)) {
|
|
41
|
+
return { state: "unreadable", reason: "has a userSettings value that is not an object" };
|
|
42
|
+
}
|
|
43
|
+
const grants = settings?.globalPermissionGrants;
|
|
44
|
+
if (grants !== undefined && !isObject(grants)) {
|
|
45
|
+
return { state: "unreadable", reason: "has a globalPermissionGrants value that is not an object" };
|
|
46
|
+
}
|
|
47
|
+
const allow = grants?.allow;
|
|
48
|
+
if (allow !== undefined && !Array.isArray(allow)) {
|
|
49
|
+
return { state: "unreadable", reason: "has a globalPermissionGrants.allow value that is not a list" };
|
|
50
|
+
}
|
|
51
|
+
return { state: "read", value, allow: allow ?? [] };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** What the desktop rule's state is on this machine, in words doctor can print. */
|
|
55
|
+
export async function inspectDesktopRule(context) {
|
|
56
|
+
const file = desktopConfigPath(context.home);
|
|
57
|
+
const hostPlatform = context.hostPlatform ?? process.platform;
|
|
58
|
+
const wrapper = cliWrapperPath(context.home, hostPlatform);
|
|
59
|
+
const rule = accAllowRule(wrapper);
|
|
60
|
+
const base = { file, wrapper, rule, owned: false };
|
|
61
|
+
if (hostPlatform === "win32" || !isShellWord(wrapper)) {
|
|
62
|
+
return { ...base, state: "unmatchable", diagnostic: "Antigravity (the desktop app) asks before "
|
|
63
|
+
+ "each ACC command: no allow rule can name ACC's wrapper here" };
|
|
64
|
+
}
|
|
65
|
+
const config = await readConfig(file);
|
|
66
|
+
if (config.state === "missing") return { ...base, state: "absent", diagnostic: null };
|
|
67
|
+
if (config.state === "unreadable") {
|
|
68
|
+
return { ...base, state: "unreadable", diagnostic: `${file} ${config.reason}, so ACC leaves it `
|
|
69
|
+
+ "as it is, and Antigravity (the desktop app) asks before each ACC command. Repair the "
|
|
70
|
+
+ `file, then run acc install --adapter antigravity to add ${rule}` };
|
|
71
|
+
}
|
|
72
|
+
const matched = config.allow.find(entry => allowingForms(wrapper).includes(entry));
|
|
73
|
+
if (matched !== undefined) {
|
|
74
|
+
const owned = (await readClaim(claimPath(context, file, "desktop")))?.rule === matched;
|
|
75
|
+
return { ...base, state: "allowed", owned, diagnostic: "ACC commands run without an approval "
|
|
76
|
+
+ `prompt in Antigravity (the desktop app): ${file} allows ${matched}`
|
|
77
|
+
+ `${owned ? ", added by ACC" : ""}` };
|
|
78
|
+
}
|
|
79
|
+
return { ...base, state: "prompts", diagnostic: "Antigravity (the desktop app) asks before each "
|
|
80
|
+
+ `ACC command: ${file} does not allow ${rule}. Run acc install --adapter antigravity` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Add the rule where the desktop keeps its grants, when the desktop is there. */
|
|
84
|
+
export async function ensureDesktopRule(context) {
|
|
85
|
+
const found = await inspectDesktopRule(context);
|
|
86
|
+
if (found.state === "absent") return none;
|
|
87
|
+
if (found.state === "unmatchable" || found.state === "unreadable") {
|
|
88
|
+
return { changes: [], diagnostics: [found.diagnostic], needsAction: [found.diagnostic] };
|
|
89
|
+
}
|
|
90
|
+
if (found.state === "allowed") return { ...none, diagnostics: [found.diagnostic] };
|
|
91
|
+
const config = await readConfig(found.file);
|
|
92
|
+
const settings = config.value.userSettings;
|
|
93
|
+
const grants = settings?.globalPermissionGrants;
|
|
94
|
+
const created = { userSettings: settings === undefined, grants: grants === undefined,
|
|
95
|
+
allow: grants?.allow === undefined };
|
|
96
|
+
const claimFile = claimPath(context, found.file, "desktop");
|
|
97
|
+
// The claim first, as for the CLI rule: a crash between the two leaves a claim
|
|
98
|
+
// with no rule, which uninstall drops, never a rule nobody can tell is ACC's.
|
|
99
|
+
await mkdir(path.dirname(claimFile), { recursive: true });
|
|
100
|
+
await writeFile(claimFile, `${JSON.stringify({ file: found.file, rule: found.rule, created })}\n`);
|
|
101
|
+
await writeForeignJson(found.file, { ...config.value, userSettings: { ...settings,
|
|
102
|
+
globalPermissionGrants: { ...grants, allow: [...config.allow, found.rule] } } },
|
|
103
|
+
{ readFile, writeFile, mkdir });
|
|
104
|
+
return { changes: [found.file], needsAction: [], diagnostics: [`added ${found.rule} to `
|
|
105
|
+
+ `${found.file}: ACC commands run without an approval prompt in Antigravity (the desktop `
|
|
106
|
+
+ "app); restart the app if it is open"] };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Take back exactly what ACC recorded adding, and containers it created if empty. */
|
|
110
|
+
export async function withdrawDesktopRule(context) {
|
|
111
|
+
const file = desktopConfigPath(context.home);
|
|
112
|
+
const claimFile = claimPath(context, file, "desktop");
|
|
113
|
+
const claim = await readClaim(claimFile);
|
|
114
|
+
if (claim === null) return none;
|
|
115
|
+
const config = await readConfig(file);
|
|
116
|
+
if (config.state === "missing") {
|
|
117
|
+
await rm(claimFile, { force: true });
|
|
118
|
+
return none;
|
|
119
|
+
}
|
|
120
|
+
if (config.state === "unreadable") {
|
|
121
|
+
return { changes: [], diagnostics: [], needsAction: [`${file} ${config.reason}; ACC left it as `
|
|
122
|
+
+ `it is and still has to remove its ${claim.rule}`] };
|
|
123
|
+
}
|
|
124
|
+
if (!config.allow.includes(claim.rule)) {
|
|
125
|
+
await rm(claimFile, { force: true });
|
|
126
|
+
return none;
|
|
127
|
+
}
|
|
128
|
+
const grants = { ...config.value.userSettings.globalPermissionGrants,
|
|
129
|
+
allow: config.allow.filter(rule => rule !== claim.rule) };
|
|
130
|
+
if (claim.created?.allow === true && grants.allow.length === 0) delete grants.allow;
|
|
131
|
+
const settings = { ...config.value.userSettings, globalPermissionGrants: grants };
|
|
132
|
+
if (claim.created?.grants === true && Object.keys(grants).length === 0) {
|
|
133
|
+
delete settings.globalPermissionGrants;
|
|
134
|
+
}
|
|
135
|
+
const next = { ...config.value, userSettings: settings };
|
|
136
|
+
if (claim.created?.userSettings === true && Object.keys(settings).length === 0) delete next.userSettings;
|
|
137
|
+
await writeForeignJson(file, next, { readFile, writeFile, mkdir });
|
|
138
|
+
await rm(claimFile, { force: true });
|
|
139
|
+
return { changes: [file], diagnostics: [`removed ACC's ${claim.rule} from ${file}`], needsAction: [] };
|
|
140
|
+
}
|