agents-can-communicate 0.6.3 → 0.7.1
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 +3 -1
- package/bin/entrypoints/hook-output.mjs +12 -0
- package/docs/ARCHITECTURE.md +5 -0
- package/docs/CAPABILITIES.md +1 -1
- package/docs/CLI.md +50 -2
- package/docs/CONCEPTS.md +7 -0
- package/docs/GETTING_STARTED.md +2 -2
- package/docs/PROTOCOL.md +31 -0
- package/docs/TROUBLESHOOTING.md +4 -3
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +57 -11
- 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 +8 -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 +8 -0
- 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 +8 -0
- 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 +8 -0
- 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 +8 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +15 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt-writer.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +3 -1
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +6 -0
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +13 -1
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +33 -8
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/attention.mjs +7 -2
- package/node_modules/@agents-can-communicate/core/src/conversations.mjs +2 -2
- package/node_modules/@agents-can-communicate/core/src/intents.mjs +16 -0
- package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +21 -4
- package/node_modules/@agents-can-communicate/core/src/prune.mjs +241 -0
- package/node_modules/@agents-can-communicate/core/src/receipts.mjs +66 -10
- package/node_modules/@agents-can-communicate/core/src/service.mjs +3 -0
- package/node_modules/@agents-can-communicate/core/src/status.mjs +25 -1
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +15 -2
- 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 +60 -7
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +62 -27
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +5 -3
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +26 -7
- package/node_modules/@agents-can-communicate/storage-filesystem/src/doomed-directory.mjs +98 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/event-floor.mjs +52 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/reclaim.mjs +272 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/recovery.mjs +54 -7
- package/node_modules/@agents-can-communicate/storage-filesystem/src/stage-sweep.mjs +174 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +97 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,7 +119,9 @@ Automatic updates are enabled on first install. Use `acc update --auto off` to d
|
|
|
119
119
|
[update controls](docs/UPGRADING.md) for details.
|
|
120
120
|
|
|
121
121
|
MIT-licensed and permanently noncommercial. Try it on one real task and
|
|
122
|
-
[tell us
|
|
122
|
+
[tell us how it went](https://github.com/automatis-tools/agents-can-communicate/discussions/categories/show-and-tell),
|
|
123
|
+
including where you still had to carry messages yourself. Bugs belong in
|
|
124
|
+
[issues](https://github.com/automatis-tools/agents-can-communicate/issues).
|
|
123
125
|
|
|
124
126
|
[Documentation](docs/index.md) ·
|
|
125
127
|
[Contributing](https://github.com/automatis-tools/agents-can-communicate/blob/main/AGENTS.md)
|
|
@@ -38,6 +38,15 @@ function boundedDiagnostic(label, error) {
|
|
|
38
38
|
return `${line}\n`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
async function releaseActivation(release) {
|
|
42
|
+
if (typeof release !== "function") return;
|
|
43
|
+
try {
|
|
44
|
+
await release();
|
|
45
|
+
} catch {
|
|
46
|
+
// The line was not delivered. Releasing its reservation is not the hook.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
function tryWrite(stream, output) {
|
|
42
51
|
if (output === "") return;
|
|
43
52
|
try {
|
|
@@ -62,6 +71,9 @@ export async function completeHookOutput(result,
|
|
|
62
71
|
await writeOutput(stdout, result.stdout ?? "", { deadlineAt: result.deadlineAt });
|
|
63
72
|
} catch (error) {
|
|
64
73
|
tryWrite(stderr, boundedDiagnostic("stdout write failed", error));
|
|
74
|
+
// The bytes did not cross, so a reserved activation line is not an ask.
|
|
75
|
+
// release may return nothing or throw; neither may fail this hook.
|
|
76
|
+
await releaseActivation(result.releaseActivationAsk);
|
|
65
77
|
return { exitCode: 0, wroteStdout: false, committedOffers: false };
|
|
66
78
|
}
|
|
67
79
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -181,6 +181,11 @@ Adapters project peer bodies in an attributed untrusted frame. If a complete bod
|
|
|
181
181
|
fit, the projection keeps the message id and points to `acc inbox --message <id>` instead
|
|
182
182
|
of silently truncating it.
|
|
183
183
|
|
|
184
|
+
On a client that takes next-turn bodies, a live offer that is still `offered` 15 minutes later
|
|
185
|
+
is projected once more before a turn,
|
|
186
|
+
after the new bodies and marked as a repeat, and never by an end-of-turn continuation; see
|
|
187
|
+
[the protocol contract](PROTOCOL.md#one-repeat-of-a-live-offer).
|
|
188
|
+
|
|
184
189
|
For this participant's unresolved messages already `offered` or `retrieved`, the standard
|
|
185
190
|
projector combines reply and acknowledgement reminders into a compact count after new
|
|
186
191
|
message bodies. Queued obligations and claim conflicts remain individual attention items.
|
package/docs/CAPABILITIES.md
CHANGED
|
@@ -60,7 +60,7 @@ The limitations belong next to the adapters they affect:
|
|
|
60
60
|
|
|
61
61
|
| Adapter | Exact limitation and evidence |
|
|
62
62
|
|---|---|
|
|
63
|
-
| Antigravity CLI | 1.2.7 on darwin-arm64, captured in print mode, and every later release by the forward rule. Only `SessionStart`, `PreInvocation`, `PostInvocation` and `Stop` load; `SessionEnd`, `PreToolUse` and `PostToolUse` are accepted into the config file and silently dropped, so there is no tool guard and no session-end deregistration - a session goes offline by presence age or an explicit `acc finish`. Payloads carry no `hook_event_name`, so each registered command passes its own event name. The end-of-turn `Stop` continuation reaches the model and is a bounded nudge, not a gate: ACC continues a turn at most once and fails open, and the client caps consecutive continuations itself (vendor 1.1.9). `agy agentapi send-message` can wake an idle session - captured - but only with that session's language-server address and CSRF token, which exist in the agent's own shell and in no hook. ACC does not take that token, so live push and reply routing are false. A peer message that arrives while the model writes its last answer is carried by the `Stop` continuation instead. A write that parses can register nothing, so install and doctor read `agy -p "/hooks"` back instead of trusting the file. Live push (1.2.7, darwin-arm64, TUI only, experimental, recorded opt-in) runs through a relay the agent starts once per conversation from its own shell - the only process holding the session endpoint - after ACC's context asks it to; the operator approves that command at the client's permission prompt. An idle session wakes; a busy one sees the message after its running answer, or at the next model invocation when the turn waits on a tool. Print mode and the first session in a folder trusted at that launch get no relay. |
|
|
63
|
+
| Antigravity CLI | 1.2.7 on darwin-arm64, captured in print mode, and every later release by the forward rule. Only `SessionStart`, `PreInvocation`, `PostInvocation` and `Stop` load; `SessionEnd`, `PreToolUse` and `PostToolUse` are accepted into the config file and silently dropped, so there is no tool guard and no session-end deregistration - a session goes offline by presence age or an explicit `acc finish`. Payloads carry no `hook_event_name`, so each registered command passes its own event name. The end-of-turn `Stop` continuation reaches the model and is a bounded nudge, not a gate: ACC continues a turn at most once and fails open, and the client caps consecutive continuations itself (vendor 1.1.9). `agy agentapi send-message` can wake an idle session - captured - but only with that session's language-server address and CSRF token, which exist in the agent's own shell and in no hook. ACC does not take that token, so live push and reply routing are false. A peer message that arrives while the model writes its last answer is carried by the `Stop` continuation instead. A write that parses can register nothing, so install and doctor read `agy -p "/hooks"` back instead of trusting the file. Live push (1.2.7, darwin-arm64, TUI only, experimental, recorded opt-in) runs through a relay the agent starts once per conversation from its own shell - the only process holding the session endpoint - after ACC's context asks it to, up to three times while none is serving; the operator approves that command at the client's permission prompt. An idle session wakes; a busy one sees the message after its running answer, or at the next model invocation when the turn waits on a tool. Print mode and the first session in a folder trusted at that launch get no relay. |
|
|
64
64
|
| Codex | Next-turn context, captured on 0.147.0, requires plugin trust. The observed stock 0.153.4 upgrade from ACC 0.3.1 to 0.4 required fresh review of five modified hook definitions; a subsequent restart retained all five active (activation evidence, not new event certification). LocalDaemon native delivery was captured through the installed package on 0.152.1 and 0.153.4, darwin-arm64; minimum 0.152.1, recorded opt-in, current feature probe and exact thread/cwd/process/version/protocol checks are required. Ordinary launch preserves the receiver workspace without ACC arguments or daemon ownership. Embedded or unreachable sessions keep their inbox. Native `replyRoute` remains false. |
|
|
65
65
|
| Claude Code | 2.1.233 next-turn delivery waits for the next user prompt. A 2.1.258 Channel capture proved idle offer, busy queue-after-turn, explicit reply, duplicate suppression, and durable fallback, so `delivery.livePush` and `delivery.replyRoute` are live capabilities behind the native contract (experimental, off until opted in; Claude's development-channel warning is vendor-owned and visible). |
|
|
66
66
|
| Gemini CLI | Package-shipped next-turn certification starts at 0.57.0. Its TUI has no captured external wake or queue interface and `--acp` changes launch ownership, so native delivery is fallback-only; live push and reply routing remain false. |
|
package/docs/CLI.md
CHANGED
|
@@ -97,10 +97,53 @@ acc work --clear
|
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
`status` returns participants, current intent, claims, protection, attention, and current
|
|
100
|
-
delivery bindings.
|
|
100
|
+
delivery bindings. Each session row carries its participant's `unretrieved: {queued, offered}`,
|
|
101
|
+
the messages addressed to that participant that it has not fetched, split by how far delivery
|
|
102
|
+
got. Two sessions of one participant show the same numbers, so read the workspace total from
|
|
103
|
+
`counts.unretrieved` rather than adding rows, and the text line ends with
|
|
104
|
+
`<n> offered, not retrieved` when that number is above zero. Offered is not read: a
|
|
105
|
+
transport took the bytes, and the model may or may not have looked. Default `sync` reads
|
|
106
|
+
events after a 16-digit event cursor (100 by
|
|
101
107
|
default, up to 500). `--scope history` discovers historical message headers and reads
|
|
102
108
|
selected records; `--scope full` adds an unbounded snapshot for explicit workspace forensics.
|
|
103
109
|
|
|
110
|
+
### Retention
|
|
111
|
+
|
|
112
|
+
A workspace keeps what it is told to keep. `acc prune` reports what it no longer needs and
|
|
113
|
+
reclaims it only when asked:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
acc prune
|
|
117
|
+
acc prune --apply
|
|
118
|
+
acc prune --class claims --apply
|
|
119
|
+
acc prune --before event_cursor
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**`prune` changes nothing without `--apply`.** The reporting run is the default because this
|
|
123
|
+
is the one command that takes records out of the store.
|
|
124
|
+
|
|
125
|
+
| Class | Reclaimed when | Threshold |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `sessions` | presence is `offline` | a confirmed dead pid, or 24 hours without a heartbeat |
|
|
128
|
+
| `intents` | the session they belong to is reclaimed | - |
|
|
129
|
+
| `claims` | the lease has expired | leases default to 30 minutes (`--lease-seconds`) |
|
|
130
|
+
| `participants` | no session of theirs survives and no surviving message names them | - |
|
|
131
|
+
|
|
132
|
+
`--before` takes a 16-digit cursor, the same one `sync` returns, and trims the event log to
|
|
133
|
+
it. Messages recorded at or below that point go too, but only once nobody is owed them: a
|
|
134
|
+
receipt that is still `queued`, `offered` or `retrieved` keeps its message, because offered is
|
|
135
|
+
not read and retrieved is not model attention. A recipient that no longer exists cannot
|
|
136
|
+
acknowledge anything, and does not hold a message forever.
|
|
137
|
+
|
|
138
|
+
After a trim, `sync` reports `trimmedThrough`. A caller whose cursor precedes it was served a
|
|
139
|
+
short page and can tell. **An ACC older than this feature cannot**: it will serve the same
|
|
140
|
+
cursor without reporting the boundary, which is why trimming history is never automatic.
|
|
141
|
+
|
|
142
|
+
Two classes need no operator at all. A retired transaction journal and a superseded retention
|
|
143
|
+
marker have no reader, so a bounded pass reclaims them once a day when a store is opened, and
|
|
144
|
+
`acc doctor --repair` reclaims them without that bound. `acc doctor` reports what is still
|
|
145
|
+
held.
|
|
146
|
+
|
|
104
147
|
### Claims
|
|
105
148
|
|
|
106
149
|
```bash
|
|
@@ -221,7 +264,12 @@ summary fields, ordering, page limits, and message-id cursors as inbox. `--type`
|
|
|
221
264
|
any message kind and filters before pagination; keep the same filter between pages.
|
|
222
265
|
This public workspace observation includes records from before your session joined.
|
|
223
266
|
Exact history reads return `view: "message"` and one complete message in `items`,
|
|
224
|
-
without changing any receipt
|
|
267
|
+
without changing any receipt, plus `receipts`: one entry per recipient with `state`,
|
|
268
|
+
`updatedAt` (the last state change) and `offer` - the transport that accepted it, when, and
|
|
269
|
+
`repeatedAt` if the next turn showed it again - or `offer: null` when no offer was recorded
|
|
270
|
+
or an older ACC recorded it. This is how a sender tells a message a peer's client accepted
|
|
271
|
+
(`offered`) from one its participant fetched (`retrieved`) or answered (`acknowledged`).
|
|
272
|
+
Retrieved is not model attention either. Do not combine `--message` with type, cursor, or limit;
|
|
225
273
|
message and type are valid only in history scope. Decision reads also include `decisionStatus`. Use
|
|
226
274
|
`acc sync --scope history --type decision --current --json` for terminal decisions
|
|
227
275
|
and withdrawals; keep these filters when paging. Exact reads cannot use `--current`.
|
package/docs/CONCEPTS.md
CHANGED
|
@@ -121,6 +121,13 @@ Listing inbox headers and observing workspace history do not change receipts.
|
|
|
121
121
|
There is no `seen` receipt. A failed delivery attempt leaves the message queued and
|
|
122
122
|
recoverable rather than creating a terminal failure state.
|
|
123
123
|
|
|
124
|
+
The sender can see where each copy got to. An exact history read of a message lists every
|
|
125
|
+
recipient's receipt with the time of its last change and the transport that offered it, and
|
|
126
|
+
`acc status` counts, per participant, the messages offered and not yet retrieved. A live
|
|
127
|
+
transport accepting a message is the weakest of these facts, so a live offer that nothing
|
|
128
|
+
followed for 15 minutes is shown once more at the recipient's next turn, marked as a repeat,
|
|
129
|
+
on a client that takes next-turn bodies.
|
|
130
|
+
|
|
124
131
|
## Durable first, faster delivery second
|
|
125
132
|
|
|
126
133
|
The durable inbox is universal. An adapter whose captures reach the installed client may
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -146,8 +146,8 @@ until a running turn finishes. Codex requires 0.152.1 or newer, LocalDaemon infr
|
|
|
146
146
|
recorded opt-in, and a verified session. Supported explicit setup can prepare a missing
|
|
147
147
|
service. Start the client with your normal command. A loaded daemon thread can receive
|
|
148
148
|
messages after its terminal exits. Antigravity CLI 1.2.7 and later has one too: with delivery enabled,
|
|
149
|
-
ACC's context asks the agent
|
|
150
|
-
you approve that command at the client's prompt.
|
|
149
|
+
ACC's context asks the agent to start a relay from its own shell, up to three times while
|
|
150
|
+
none is running, and you approve that command at the client's prompt.
|
|
151
151
|
[Capabilities](CAPABILITIES.md) explains policy, versions and fallback.
|
|
152
152
|
|
|
153
153
|
Delivery evidence is deliberately narrow: `queued -> offered -> retrieved -> acknowledged`.
|
package/docs/PROTOCOL.md
CHANGED
|
@@ -199,6 +199,37 @@ peer body into diagnostics.
|
|
|
199
199
|
Receipt `offered` is committed only after the transport accepts bytes. A failed attempt
|
|
200
200
|
leaves the receipt queued.
|
|
201
201
|
|
|
202
|
+
The same transaction records how the receipt was offered, in the receipt's `extensions`:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{ "offer": { "transport": "codex-app-server", "at": "<timestamp>", "repeatedAt": null } }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
It lives in `extensions` because a new receipt field would fail validation in an older ACC
|
|
209
|
+
that reads the same store. A receipt offered by an older ACC has no `offer` entry; readers
|
|
210
|
+
report `offer: null` and never guess a transport. An exact history read returns every
|
|
211
|
+
recipient's `state`, `updatedAt` and `offer` beside the message.
|
|
212
|
+
|
|
213
|
+
### One repeat of a live offer
|
|
214
|
+
|
|
215
|
+
A `next-turn` offer is recorded only after the hook's output carried the body, which is the
|
|
216
|
+
closest ACC comes to the model. A live offer proves that a transport accepted the bytes. A
|
|
217
|
+
direct message whose receipt is still `offered`, whose offer transport is anything other than
|
|
218
|
+
`next-turn`, and whose offer is at least 15 minutes old, is shown once more by the
|
|
219
|
+
recipient's next turn on a client that takes next-turn bodies (a certified `nextTurn` and a
|
|
220
|
+
structured renderer): the body in the usual untrusted block, after new messages, with the
|
|
221
|
+
line `repeat: offered via <transport> at <timestamp>; no retrieval recorded`. An end-of-turn
|
|
222
|
+
continuation never carries a repeat, because continuing a turn costs a model call.
|
|
223
|
+
|
|
224
|
+
When the hook's output carried it, the repeat is recorded as another
|
|
225
|
+
`message.offer_succeeded` with `transport: "next-turn"` and `repeat: true`, and
|
|
226
|
+
`extensions.offer.repeatedAt` is set. The receipt keeps its state and `updatedAt`: a repeat
|
|
227
|
+
is an attempt, never a transition and never a second message. A repeat is recorded at most
|
|
228
|
+
once per receipt, and a receipt that was retrieved, acknowledged or repeated in the meantime is
|
|
229
|
+
left unchanged with nothing appended. Like a first offer, the display itself can happen twice:
|
|
230
|
+
two sessions of one participant can both show it before either commits, and a hook that runs
|
|
231
|
+
out of time after writing does not commit at all.
|
|
232
|
+
|
|
202
233
|
`transport_permission_denied` means the sender's OS or sandbox refused local transport
|
|
203
234
|
access (`EPERM` or `EACCES`). It is distinct from `recipient_unavailable` and remains a
|
|
204
235
|
failed offer with a queued receipt; raw OS error strings are not persisted.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -278,9 +278,10 @@ Live delivery needs four things, and `acc doctor` shows each:
|
|
|
278
278
|
- the live policy is on: `acc install --adapter antigravity --delivery actionable` (or `all`);
|
|
279
279
|
- the session is interactive - print mode ends with its turn and never runs a relay;
|
|
280
280
|
- the session attached to ACC at all (see the section above);
|
|
281
|
-
- the agent started the relay. ACC's context asks
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
- the agent started the relay. While none is running, ACC's context asks up to three times
|
|
282
|
+
in that conversation. A declined prompt and an ignored ask look the same, so the line can
|
|
283
|
+
return after a decline; after the third ask it stays quiet. Run
|
|
284
|
+
`sh "~/.gemini/config/acc/acc-relay.sh" start` to start it directly.
|
|
284
285
|
|
|
285
286
|
`acc doctor` reports how many relays are running and, per session, whether a live transport is
|
|
286
287
|
active. A relay ends with its client; nothing is left running after the TUI exits or after
|
|
@@ -111,6 +111,10 @@ the communication obligation rather than proving the requested action is complet
|
|
|
111
111
|
An acknowledgement confirms receipt, not acceptance of work. Read the peer's
|
|
112
112
|
answer for the scope it accepted or the result it actually checked.
|
|
113
113
|
|
|
114
|
+
To see where a message you sent got to, read it exactly:
|
|
115
|
+
`{{ACC}} sync --scope history --message <id> --json` lists each recipient's
|
|
116
|
+
receipt state, when it last changed, and the transport that offered it.
|
|
117
|
+
|
|
114
118
|
### Stay reachable while idle
|
|
115
119
|
|
|
116
120
|
ACC can wake this conversation when a peer writes while you are idle, once its
|
|
@@ -137,6 +141,10 @@ Pages default to 20 items and stay within 12,000 bytes of formatted page JSON.
|
|
|
137
141
|
Use `inbox --cursor <nextCursor>` for older headers when needed. Omit the cursor
|
|
138
142
|
on a new poll to see arrivals; a cursor is the complete last message id, not an offset.
|
|
139
143
|
|
|
144
|
+
A peer block with a `repeat:` line was pushed live earlier and nothing has
|
|
145
|
+
retrieved it since. A note you already acted on needs nothing more; for a
|
|
146
|
+
message that asks for one, a reply or acknowledgement is still owed.
|
|
147
|
+
|
|
140
148
|
An injected peer block is already the message body. If context was compacted,
|
|
141
149
|
or a body did not fit, retrieve exactly the named message:
|
|
142
150
|
|
|
@@ -654,7 +654,8 @@ export async function doctorAntigravity(context) {
|
|
|
654
654
|
diagnostics.push("registered per workspace; a session opened anywhere else loads nothing");
|
|
655
655
|
}
|
|
656
656
|
diagnostics.push(`live delivery starts when the agent runs sh "${relayShimPath(context.home)}" `
|
|
657
|
-
+ "start once in a conversation; ACC asks
|
|
657
|
+
+ "start once in a conversation; while none is serving, ACC asks for that in its context "
|
|
658
|
+
+ "line, up to three times, when the policy is on");
|
|
658
659
|
diagnostics.push(`antigravity relays running on this machine: ${await runningRelays({
|
|
659
660
|
dataHome: context.dataHome })}`);
|
|
660
661
|
return { ok: true, changes: [], diagnostics };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { lstat, mkdir, open, rm } from "node:fs/promises";
|
|
4
5
|
import net from "node:net";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
|
|
@@ -144,27 +145,72 @@ export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 8
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
const ASK_LIMIT = 3;
|
|
147
149
|
const HINT = home => "ACC: live delivery is on but not running in this conversation. To let "
|
|
148
150
|
+ `peers reach you while idle, run once: sh "${path.join(home, ".gemini", "config", "acc",
|
|
149
151
|
"acc-relay.sh")}" start`;
|
|
150
152
|
|
|
153
|
+
// Each ask is its own file, created exclusively. A counter read and then
|
|
154
|
+
// rewritten lets overlapping calls all observe the same count and each return
|
|
155
|
+
// a hint. An empty file at the unsuffixed hash is the old one-ask marker: it
|
|
156
|
+
// is not one of these three, so it does not spend the budget. The path is a
|
|
157
|
+
// reservation: the caller deletes it when the line is not delivered.
|
|
158
|
+
async function claimAsk(directory, marker) {
|
|
159
|
+
try {
|
|
160
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
for (let n = 1; n <= ASK_LIMIT; n += 1) {
|
|
165
|
+
const slot = `${marker}.${n}`;
|
|
166
|
+
let handle;
|
|
167
|
+
try {
|
|
168
|
+
handle = await open(slot, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL
|
|
169
|
+
| constants.O_NOFOLLOW, 0o600);
|
|
170
|
+
return slot;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error?.code === "EEXIST") continue;
|
|
173
|
+
return null;
|
|
174
|
+
} finally {
|
|
175
|
+
await handle?.close().catch(() => {});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function releaseAsk(slot) {
|
|
182
|
+
return rm(slot, { force: true }).catch(() => {});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function relayAlreadyServing({ runtimeDir, conversationId, clientPid, isAlive }) {
|
|
186
|
+
for (const record of await listRegistrations({ runtimeDir })) {
|
|
187
|
+
if (record.conversationId !== conversationId) continue;
|
|
188
|
+
if (await serving(record, { timeoutMs: 100, isAlive, clientPid }) === null) return true;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
export async function nativeActivationHint({ event, nativeBinding, runtimeDir, clientPid, env,
|
|
152
|
-
argvOf = argvDefault }) {
|
|
194
|
+
argvOf = argvDefault, isAlive = defaultAlive }) {
|
|
153
195
|
// Only a degraded binding can be helped by starting a relay: "off" means the
|
|
154
196
|
// live policy is off, "unsupported" that the contract does not admit this
|
|
155
|
-
// client, and "active" that a relay already serves it.
|
|
197
|
+
// client, and "active" that a relay already serves it. Those calls spend
|
|
198
|
+
// none of the asks recorded below.
|
|
156
199
|
if (nativeBinding?.state !== "degraded") return null;
|
|
157
200
|
const home = env?.HOME;
|
|
158
201
|
if (typeof event?.sessionId !== "string" || typeof home !== "string" || home === "") return null;
|
|
159
202
|
if (!Number.isInteger(clientPid) || isPrintMode(await argvOf(clientPid).catch(() => []))) return null;
|
|
203
|
+
// The runner asks only when its own handshake failed. A relay can still be
|
|
204
|
+
// serving under a stale degraded binding; telling the agent to start it again
|
|
205
|
+
// would be wrong, and the check does not spend an ask.
|
|
206
|
+
if (await relayAlreadyServing({ runtimeDir, conversationId: event.sessionId, clientPid,
|
|
207
|
+
isAlive })) return null;
|
|
160
208
|
const asked = path.join(path.dirname(relayDir(runtimeDir)), "antigravity-asked");
|
|
161
209
|
const marker = path.join(asked, createHash("sha256").update(event.sessionId).digest("hex"));
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
return HINT(home);
|
|
210
|
+
// A decline and an ignored ask leave the same trace, so neither can stop the
|
|
211
|
+
// line by itself. Three exclusive creates are the bound. The file reserves
|
|
212
|
+
// the number now; release gives it back when the runner does not deliver.
|
|
213
|
+
const slot = await claimAsk(asked, marker);
|
|
214
|
+
if (slot === null) return null;
|
|
215
|
+
return { line: HINT(home), release: () => releaseAsk(slot) };
|
|
170
216
|
}
|
|
@@ -111,6 +111,10 @@ the communication obligation rather than proving the requested action is complet
|
|
|
111
111
|
An acknowledgement confirms receipt, not acceptance of work. Read the peer's
|
|
112
112
|
answer for the scope it accepted or the result it actually checked.
|
|
113
113
|
|
|
114
|
+
To see where a message you sent got to, read it exactly:
|
|
115
|
+
`{{ACC}} sync --scope history --message <id> --json` lists each recipient's
|
|
116
|
+
receipt state, when it last changed, and the transport that offered it.
|
|
117
|
+
|
|
114
118
|
## Read and answer only your inbox
|
|
115
119
|
|
|
116
120
|
Plain `{{ACC}} inbox` returns `{items, nextCursor}`: pending message headers,
|
|
@@ -125,6 +129,10 @@ Pages default to 20 items and stay within 12,000 bytes of formatted page JSON.
|
|
|
125
129
|
Use `inbox --cursor <nextCursor>` for older headers when needed. Omit the cursor
|
|
126
130
|
on a new poll to see arrivals; a cursor is the complete last message id, not an offset.
|
|
127
131
|
|
|
132
|
+
A peer block with a `repeat:` line was pushed live earlier and nothing has
|
|
133
|
+
retrieved it since. A note you already acted on needs nothing more; for a
|
|
134
|
+
message that asks for one, a reply or acknowledgement is still owed.
|
|
135
|
+
|
|
128
136
|
An injected peer block is already the message body. If context was compacted,
|
|
129
137
|
or a body did not fit, retrieve exactly the named message:
|
|
130
138
|
|
|
@@ -111,6 +111,10 @@ the communication obligation rather than proving the requested action is complet
|
|
|
111
111
|
An acknowledgement confirms receipt, not acceptance of work. Read the peer's
|
|
112
112
|
answer for the scope it accepted or the result it actually checked.
|
|
113
113
|
|
|
114
|
+
To see where a message you sent got to, read it exactly:
|
|
115
|
+
`{{ACC}} sync --scope history --message <id> --json` lists each recipient's
|
|
116
|
+
receipt state, when it last changed, and the transport that offered it.
|
|
117
|
+
|
|
114
118
|
## Read and answer only your inbox
|
|
115
119
|
|
|
116
120
|
Plain `{{ACC}} inbox` returns `{items, nextCursor}`: pending message headers,
|
|
@@ -125,6 +129,10 @@ Pages default to 20 items and stay within 12,000 bytes of formatted page JSON.
|
|
|
125
129
|
Use `inbox --cursor <nextCursor>` for older headers when needed. Omit the cursor
|
|
126
130
|
on a new poll to see arrivals; a cursor is the complete last message id, not an offset.
|
|
127
131
|
|
|
132
|
+
A peer block with a `repeat:` line was pushed live earlier and nothing has
|
|
133
|
+
retrieved it since. A note you already acted on needs nothing more; for a
|
|
134
|
+
message that asks for one, a reply or acknowledgement is still owed.
|
|
135
|
+
|
|
128
136
|
An injected peer block is already the message body. If context was compacted,
|
|
129
137
|
or a body did not fit, retrieve exactly the named message:
|
|
130
138
|
|
package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md
CHANGED
|
@@ -111,6 +111,10 @@ the communication obligation rather than proving the requested action is complet
|
|
|
111
111
|
An acknowledgement confirms receipt, not acceptance of work. Read the peer's
|
|
112
112
|
answer for the scope it accepted or the result it actually checked.
|
|
113
113
|
|
|
114
|
+
To see where a message you sent got to, read it exactly:
|
|
115
|
+
`{{ACC}} sync --scope history --message <id> --json` lists each recipient's
|
|
116
|
+
receipt state, when it last changed, and the transport that offered it.
|
|
117
|
+
|
|
114
118
|
## Read and answer only your inbox
|
|
115
119
|
|
|
116
120
|
Plain `{{ACC}} inbox` returns `{items, nextCursor}`: pending message headers,
|
|
@@ -125,6 +129,10 @@ Pages default to 20 items and stay within 12,000 bytes of formatted page JSON.
|
|
|
125
129
|
Use `inbox --cursor <nextCursor>` for older headers when needed. Omit the cursor
|
|
126
130
|
on a new poll to see arrivals; a cursor is the complete last message id, not an offset.
|
|
127
131
|
|
|
132
|
+
A peer block with a `repeat:` line was pushed live earlier and nothing has
|
|
133
|
+
retrieved it since. A note you already acted on needs nothing more; for a
|
|
134
|
+
message that asks for one, a reply or acknowledgement is still owed.
|
|
135
|
+
|
|
128
136
|
An injected peer block is already the message body. If context was compacted,
|
|
129
137
|
or a body did not fit, retrieve exactly the named message:
|
|
130
138
|
|
|
@@ -127,6 +127,10 @@ the communication obligation rather than proving the requested action is complet
|
|
|
127
127
|
An acknowledgement confirms receipt, not acceptance of work. Read the peer's
|
|
128
128
|
answer for the scope it accepted or the result it actually checked.
|
|
129
129
|
|
|
130
|
+
To see where a message you sent got to, read it exactly:
|
|
131
|
+
`{{ACC}} sync --scope history --message <id> --json` lists each recipient's
|
|
132
|
+
receipt state, when it last changed, and the transport that offered it.
|
|
133
|
+
|
|
130
134
|
## Read and answer only your inbox
|
|
131
135
|
|
|
132
136
|
Plain `{{ACC}} inbox` returns `{items, nextCursor}`: pending message headers,
|
|
@@ -141,6 +145,10 @@ Pages default to 20 items and stay within 12,000 bytes of formatted page JSON.
|
|
|
141
145
|
Use `inbox --cursor <nextCursor>` for older headers when needed. Omit the cursor
|
|
142
146
|
on a new poll to see arrivals; a cursor is the complete last message id, not an offset.
|
|
143
147
|
|
|
148
|
+
A peer block with a `repeat:` line was pushed live earlier and nothing has
|
|
149
|
+
retrieved it since. A note you already acted on needs nothing more; for a
|
|
150
|
+
message that asks for one, a reply or acknowledgement is still owed.
|
|
151
|
+
|
|
144
152
|
An injected peer block is already the message body. If context was compacted,
|
|
145
153
|
or a body did not fit, retrieve exactly the named message:
|
|
146
154
|
|
|
@@ -111,6 +111,10 @@ the communication obligation rather than proving the requested action is complet
|
|
|
111
111
|
An acknowledgement confirms receipt, not acceptance of work. Read the peer's
|
|
112
112
|
answer for the scope it accepted or the result it actually checked.
|
|
113
113
|
|
|
114
|
+
To see where a message you sent got to, read it exactly:
|
|
115
|
+
`{{ACC}} sync --scope history --message <id> --json` lists each recipient's
|
|
116
|
+
receipt state, when it last changed, and the transport that offered it.
|
|
117
|
+
|
|
114
118
|
## Read and answer only your inbox
|
|
115
119
|
|
|
116
120
|
Plain `{{ACC}} inbox` returns `{items, nextCursor}`: pending message headers,
|
|
@@ -125,6 +129,10 @@ Pages default to 20 items and stay within 12,000 bytes of formatted page JSON.
|
|
|
125
129
|
Use `inbox --cursor <nextCursor>` for older headers when needed. Omit the cursor
|
|
126
130
|
on a new poll to see arrivals; a cursor is the complete last message id, not an offset.
|
|
127
131
|
|
|
132
|
+
A peer block with a `repeat:` line was pushed live earlier and nothing has
|
|
133
|
+
retrieved it since. A note you already acted on needs nothing more; for a
|
|
134
|
+
message that asks for one, a reply or acknowledgement is still owed.
|
|
135
|
+
|
|
128
136
|
An injected peer block is already the message body. If context was compacted,
|
|
129
137
|
or a body did not fit, retrieve exactly the named message:
|
|
130
138
|
|
|
@@ -51,7 +51,18 @@ function offeredBreadcrumb(item, liveOfferedMessageIds) {
|
|
|
51
51
|
+ `\`acc inbox --message ${item.sourceId}\``;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
// A body shown again because a live transport accepted it and nothing followed.
|
|
55
|
+
// Said inside the block so the model can match it to what it may already have.
|
|
56
|
+
function repeatLines(message, repeats) {
|
|
57
|
+
const repeat = repeats.get(message.messageId);
|
|
58
|
+
// Both values come from the store, and the store is shared with every peer's
|
|
59
|
+
// process, so they are escaped like any other line inside the block.
|
|
60
|
+
return repeat === undefined ? []
|
|
61
|
+
: [oneLine(escapePeerText(`repeat: offered via ${repeat.transport} at ${repeat.at}; `
|
|
62
|
+
+ "no retrieval recorded"))];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function messageGroups(messages, repeats = new Map()) {
|
|
55
66
|
return messages.map(message => ({
|
|
56
67
|
messageId: message.messageId,
|
|
57
68
|
kind: "message",
|
|
@@ -63,6 +74,7 @@ function messageGroups(messages) {
|
|
|
63
74
|
`messageId: ${message.messageId}`,
|
|
64
75
|
`sender: ${message.fromParticipantId} (session ${message.fromSessionId})`,
|
|
65
76
|
`obligation: ${message.obligation}`,
|
|
77
|
+
...repeatLines(message, repeats),
|
|
66
78
|
...decisionLines(message).map(escapePeerText),
|
|
67
79
|
`subject: ${oneLine(escapePeerText(message.subject))}`,
|
|
68
80
|
"body:",
|
|
@@ -129,7 +141,8 @@ export function projectContextResult(sync, { budgetBytes = DEFAULT_BUDGET_BYTES
|
|
|
129
141
|
const liveOfferedMessageIds = new Set(sync.liveOfferedMessageIds ?? []);
|
|
130
142
|
const groups = [
|
|
131
143
|
...attentionGroups(urgent, { truncatable: true, liveOfferedMessageIds }),
|
|
132
|
-
...messageGroups(messages)
|
|
144
|
+
...messageGroups(messages, new Map((sync.repeatOffers ?? [])
|
|
145
|
+
.map(repeat => [repeat.messageId, repeat]))),
|
|
133
146
|
...reminderGroups(attention.filter(isReminder)),
|
|
134
147
|
...attentionGroups(informational, { truncatable: false }),
|
|
135
148
|
];
|
|
@@ -15,7 +15,7 @@ const checkDeadline = deadline => {
|
|
|
15
15
|
if (Date.now() >= deadline) throw new Error("native diagnostic deadline expired");
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
// The caller stops waiting after
|
|
18
|
+
// The caller stops waiting after 1.5 s at most. An in-flight filesystem call
|
|
19
19
|
// may complete later, so retain this SEPARATE mutex until its continuation is
|
|
20
20
|
// finished. Unique staging names also isolate cleanup after stale-lock recovery.
|
|
21
21
|
// No diagnostic operation can touch the authoritative hook-owner file.
|
|
@@ -39,7 +39,9 @@ export async function loadNativeAttempt({ runtimeDir, harnessSessionId, accSessi
|
|
|
39
39
|
// must not keep the vendor's short-lived hook process alive after its output.
|
|
40
40
|
// The unreferenced worker owns its mutex through any late I/O continuation.
|
|
41
41
|
async function writeDiagnostic(operation, input) {
|
|
42
|
-
|
|
42
|
+
// Same ceiling as the hook runner's diagnostic budget. A caller cannot wait
|
|
43
|
+
// longer than this, and a shorter deadlineAt is still honored.
|
|
44
|
+
const deadlineAt = Math.min(input.deadlineAt ?? Infinity, Date.now() + 1_500);
|
|
43
45
|
if (Date.now() >= deadlineAt) return false;
|
|
44
46
|
let worker;
|
|
45
47
|
let timer;
|
|
@@ -40,6 +40,12 @@ export const COMMANDS = Object.freeze({
|
|
|
40
40
|
repeated: ["completed", "remaining", "blocker"] },
|
|
41
41
|
status: { required: [], optional: ["session", "generation", "participant"], flags: ["all"] },
|
|
42
42
|
doctor: { required: [], optional: ["home"], flags: ["repair"] },
|
|
43
|
+
// `--apply` rather than `--dry-run`: this is the one command that takes
|
|
44
|
+
// records out of the store, so the reporting run is what happens by
|
|
45
|
+
// default and meaning it is what has to be said out loud.
|
|
46
|
+
// No `--session`: this acts on the workspace, not as a participant in it, so
|
|
47
|
+
// a session id would be a credential nothing reads.
|
|
48
|
+
prune: { required: [], optional: ["before"], repeated: ["class"], flags: ["apply"] },
|
|
43
49
|
// The one command with a subcommand. Kept as an explicit list rather than a
|
|
44
50
|
// free positional: `acc config delete` should fail at the parser, not deep
|
|
45
51
|
// inside a handler that has already decided what to do.
|