@agentchatme/agent-core 0.0.131311111111 → 0.0.1313111111112

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 CHANGED
@@ -28,8 +28,8 @@ Neither bug came from sharing protocol code. Both came from a single command sur
28
28
  | Here (must not drift) | In each integration (genuinely differs) |
29
29
  |---|---|
30
30
  | Wire protocol — `sync` / `sync/ack`, reply coordination | Where its identity home is |
31
- | Credential + pending file format | Which file its anchor lives in |
32
- | Identity flows — register / login / recover / status / logout / doctor | How to render its anchor |
31
+ | Credential, autonomy-policy, and pending-request formats | Which file its anchor lives in |
32
+ | Identity and local-control flows — register / login / recover / status / logout / doctor / autonomy / pending | How to render its anchor |
33
33
  | Session digest text | What JSON shape its hooks emit (`dialect`) |
34
34
  | Hook state machine (continuation cap, ack cursor) | How to spawn a headless turn of its runtime (`RuntimeAdapter`) |
35
35
  | Daemon — loop, WS client, canonical delivery prompt, service install | Its packaging and front door |
@@ -99,7 +99,7 @@ const profile: HostProfile = {
99
99
  renderAnchor: (handle) => renderMyAnchor(handle),
100
100
  }
101
101
 
102
- const { runRegister, runStatus, runLogout, runDoctor } = createIdentityCommands(profile)
102
+ const { runRegister, runStatus, runAutonomy, runPendingRequests, runLogout, runDoctor } = createIdentityCommands(profile)
103
103
  const { runSessionStart, runUserPrompt, runStop, runSessionEnd } = createHookRunners(
104
104
  () => ({ home: profile.home(), copy: { invoke: profile.invocation(), label: profile.label } }),
105
105
  myHostsDialect, // how THIS host wants hook JSON shaped
@@ -126,6 +126,20 @@ batch is acknowledged only after the shared turn succeeds; a failure retries
126
126
  the same frozen batch with capped exponential backoff. The daemon renews its
127
127
  reply claim before every attempt, and its MCP send uses a stable idempotency key
128
128
  so a crash after the API accepted a reply cannot create a second copy on retry.
129
+ Each successful turn also records a local, body-free activity entry containing
130
+ the authenticated agent, conversation/message ids, and a structured outcome:
131
+ replied, or silent with a bounded reason. The next foreground prompt receives
132
+ and acknowledges those entries once, so foreground and always-on execution
133
+ remain one persistent agent rather than two disconnected memories.
134
+
135
+ Full autonomy is also a shared local invariant. It defaults to off and can allow
136
+ only explicitly selected AgentChat handles or everyone who already passes the
137
+ account's inbox controls. The policy is scoped to the authenticated handle, so
138
+ changing credentials fails closed. When a useful side-effecting request cannot
139
+ run unattended, the daemon persists a body-free, conversation-referenced
140
+ pending record before acknowledging its delivery. Session hooks surface those
141
+ records until a foreground decision explicitly resolves them. Neither feature
142
+ adds a server or database dependency.
129
143
 
130
144
  ## Development
131
145
 
@@ -0,0 +1,470 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const PolicySchema: z.ZodObject<{
4
+ version: z.ZodLiteral<1>;
5
+ identity_handle: z.ZodString;
6
+ mode: z.ZodEnum<["off", "selected", "everyone"]>;
7
+ selected_agents: z.ZodArray<z.ZodString, "many">;
8
+ updated_at: z.ZodString;
9
+ }, "strip", z.ZodTypeAny, {
10
+ mode: "off" | "selected" | "everyone";
11
+ version: 1;
12
+ identity_handle: string;
13
+ selected_agents: string[];
14
+ updated_at: string;
15
+ }, {
16
+ mode: "off" | "selected" | "everyone";
17
+ version: 1;
18
+ identity_handle: string;
19
+ selected_agents: string[];
20
+ updated_at: string;
21
+ }>;
22
+ type AutonomyMode = 'off' | 'selected' | 'everyone';
23
+ type FullAutonomyPolicy = z.infer<typeof PolicySchema>;
24
+ declare function normalizeAgentHandle(value: string): string | null;
25
+ declare function autonomyPath(home: string): string;
26
+ /** Invalid, missing, or differently scoped state always fails closed. */
27
+ declare function readFullAutonomyPolicy(home: string, identityHandle: string): FullAutonomyPolicy;
28
+ declare function writeFullAutonomyPolicy(home: string, identityHandle: string, input: {
29
+ mode: AutonomyMode;
30
+ selectedAgents?: string[];
31
+ }, now?: Date): FullAutonomyPolicy;
32
+ declare function setFullAutonomyMode(home: string, identityHandle: string, mode: AutonomyMode): FullAutonomyPolicy;
33
+ declare function allowFullAutonomyAgent(home: string, identityHandle: string, peerHandle: string): FullAutonomyPolicy;
34
+ declare function removeFullAutonomyAgent(home: string, identityHandle: string, peerHandle: string): FullAutonomyPolicy;
35
+ declare function fullAutonomyAllows(policy: FullAutonomyPolicy, peerHandle: string): boolean;
36
+
37
+ declare const HEARTBEAT_FILE = "daemon.heartbeat";
38
+ /** Record that the user wants always-on for this agent.
39
+ *
40
+ * The file's MTIME is load-bearing: `alwaysOnState` uses it as the moment
41
+ * registration happened, so a service that was just installed is not reported
42
+ * as broken before its daemon has had time to draw breath. */
43
+ declare function markAlwaysOnWanted(home: string): void;
44
+ /** Forget the intent (user chose session-only, or uninstalled). */
45
+ declare function clearAlwaysOnWanted(home: string): void;
46
+ declare function alwaysOnWanted(home: string): boolean;
47
+ declare function readAlwaysOnInstalledVersion(home: string): string | null;
48
+ declare function markAlwaysOnInstalledVersion(home: string, version: string): void;
49
+ declare function clearAlwaysOnInstalledVersion(home: string): void;
50
+ /** Remember that the user switched always-on off. Survives re-install. */
51
+ declare function markAlwaysOnOptOut(home: string): void;
52
+ /** Cleared only by an explicit `daemon install` — never implicitly. */
53
+ declare function clearAlwaysOnOptOut(home: string): void;
54
+ declare function alwaysOnOptedOut(home: string): boolean;
55
+ /** Touch the liveness beacon. Called by the running daemon. */
56
+ declare function beat(home: string): void;
57
+ /** Clear the beacon. The daemon calls this whenever it is resident but NOT
58
+ * connected, so "idle" is never mistaken for "beating". */
59
+ declare function idle(home: string): void;
60
+ /**
61
+ * Always-on has THREE states, not two.
62
+ *
63
+ * It used to be a boolean pair, which could not tell "idle because nobody is
64
+ * signed in" apart from "installed and broken" — so a signed-out user would be
65
+ * nagged every session about a daemon that was behaving exactly as intended.
66
+ *
67
+ * off — the service is not installed (or was explicitly disabled).
68
+ * idle — installed and resident, but there is no identity to serve.
69
+ * Correct and quiet: the daemon is waiting for a sign-in.
70
+ * starting — registered moments ago and not beating yet. Also quiet: the
71
+ * service manager has not finished bringing it up.
72
+ * connected — holding the wire; the beacon is fresh.
73
+ * down — there IS an identity and the service is installed, but nothing
74
+ * is beating. The only state worth telling a session about.
75
+ *
76
+ * Pure reads, no subprocess, never throws.
77
+ */
78
+ type AlwaysOnState = 'off' | 'idle' | 'starting' | 'connected' | 'down';
79
+ declare function alwaysOnState(home: string): AlwaysOnState;
80
+ /**
81
+ * Back-compatible view for callers that only need "should I warn?".
82
+ * `healthy` is false ONLY in the `down` state — an idle daemon is healthy.
83
+ */
84
+ declare function alwaysOnHealth(home: string): {
85
+ wanted: boolean;
86
+ healthy: boolean;
87
+ };
88
+
89
+ declare const SyncRowSchema: z.ZodObject<{
90
+ id: z.ZodString;
91
+ conversation_id: z.ZodString;
92
+ delivery_id: z.ZodNullable<z.ZodString>;
93
+ sender: z.ZodOptional<z.ZodString>;
94
+ sender_handle: z.ZodOptional<z.ZodString>;
95
+ seq: z.ZodOptional<z.ZodNumber>;
96
+ type: z.ZodOptional<z.ZodString>;
97
+ content: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
98
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
99
+ status: z.ZodOptional<z.ZodString>;
100
+ created_at: z.ZodOptional<z.ZodString>;
101
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
102
+ id: z.ZodString;
103
+ conversation_id: z.ZodString;
104
+ delivery_id: z.ZodNullable<z.ZodString>;
105
+ sender: z.ZodOptional<z.ZodString>;
106
+ sender_handle: z.ZodOptional<z.ZodString>;
107
+ seq: z.ZodOptional<z.ZodNumber>;
108
+ type: z.ZodOptional<z.ZodString>;
109
+ content: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
110
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
111
+ status: z.ZodOptional<z.ZodString>;
112
+ created_at: z.ZodOptional<z.ZodString>;
113
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
114
+ id: z.ZodString;
115
+ conversation_id: z.ZodString;
116
+ delivery_id: z.ZodNullable<z.ZodString>;
117
+ sender: z.ZodOptional<z.ZodString>;
118
+ sender_handle: z.ZodOptional<z.ZodString>;
119
+ seq: z.ZodOptional<z.ZodNumber>;
120
+ type: z.ZodOptional<z.ZodString>;
121
+ content: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
122
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
123
+ status: z.ZodOptional<z.ZodString>;
124
+ created_at: z.ZodOptional<z.ZodString>;
125
+ }, z.ZodTypeAny, "passthrough">>;
126
+ type SyncRow = z.infer<typeof SyncRowSchema>;
127
+ /** Platform-authored trusted context (server `message.context`) — resolved
128
+ * sender identity, the conversation descriptor, and the parsed mention list.
129
+ * Read defensively off the passthrough row; kept in sync with the daemon's
130
+ * copy at daemon/src/wire.ts (same deliberate duplication as SyncRow). */
131
+ interface MessageContext {
132
+ senderDisplayName: string | null;
133
+ senderKind: 'agent' | 'system';
134
+ groupName: string | null;
135
+ memberCount: number | null;
136
+ mentions: string[];
137
+ }
138
+ declare function contextOf(row: SyncRow): MessageContext;
139
+ interface WireConfig {
140
+ apiKey: string;
141
+ apiBase: string;
142
+ timeoutMs?: number;
143
+ }
144
+ interface PendingReviewMirrorRecord {
145
+ id: string;
146
+ conversation_id: string;
147
+ peer_agents: string[];
148
+ focus_message_id: string;
149
+ reason: 'autonomy_off' | 'sender_not_allowed' | 'local_permission';
150
+ first_requested_at: string;
151
+ updated_at: string;
152
+ }
153
+ /**
154
+ * Replace this installation's ephemeral dashboard mirror with its complete
155
+ * local pending snapshot. The server unions snapshots across installations;
156
+ * no database row or peer-authored message content is created.
157
+ */
158
+ declare function syncPendingReviewMirror(cfg: WireConfig, installationId: string, records: PendingReviewMirrorRecord[]): Promise<void>;
159
+ declare class WireError extends Error {
160
+ readonly status: number;
161
+ constructor(status: number, detail: string);
162
+ }
163
+ /**
164
+ * Non-destructive peek at undelivered messages, oldest first. Does not
165
+ * mark anything delivered — pair with `syncAck` once the rows have been
166
+ * handed to the agent.
167
+ */
168
+ declare function syncPeek(cfg: WireConfig, opts?: {
169
+ limit?: number;
170
+ after?: string;
171
+ }): Promise<SyncRow[]>;
172
+ /**
173
+ * Commit every delivery at-or-before the cursor as delivered. In the
174
+ * host integration this is called at the moment rows are injected into the
175
+ * agent's context — injection IS delivery.
176
+ */
177
+ declare function syncAck(cfg: WireConfig, lastDeliveryId: string): Promise<number>;
178
+ /**
179
+ * Minimal self-lookup for hooks that resolved an env-var key with no
180
+ * credentials file (so no cached handle). Full profile reads go through
181
+ * the SDK; hooks only ever need the handle.
182
+ */
183
+ declare function getMeLite(cfg: WireConfig): Promise<{
184
+ handle: string;
185
+ } | null>;
186
+ /** Legacy single-flag activity marker retained for older integrations. */
187
+ declare function markSessionActive(cfg: WireConfig, ttlSeconds?: number): Promise<void>;
188
+ /** Release the active flag (session ended) so the daemon resumes immediately
189
+ * instead of waiting out the TTL. Best-effort. */
190
+ declare function clearSessionActive(cfg: WireConfig): Promise<void>;
191
+ /**
192
+ * Lease one concrete foreground turn. Unlike the legacy boolean marker, this
193
+ * is keyed by host session id so one terminal reaching Stop cannot clear
194
+ * another terminal that is still reasoning.
195
+ */
196
+ declare function markForegroundTurn(cfg: WireConfig, sessionId: string, ttlSeconds: number): Promise<void>;
197
+ /** Release only this host session's foreground lease. Best-effort. */
198
+ declare function clearForegroundTurn(cfg: WireConfig, sessionId: string): Promise<void>;
199
+ /** Claim the sole right to reply to one message so the daemon stands down for
200
+ * it. Fail-OPEN to TRUE: if coordination is unavailable, surface the message
201
+ * anyway (degrade to today's behavior) rather than hide it. */
202
+ declare function claimReply(cfg: WireConfig, messageId: string, holder: string): Promise<boolean>;
203
+ /**
204
+ * Claim the contiguous oldest-first prefix of a batch. The batch endpoint
205
+ * avoids claiming newer rows past a message another replier owns. During a
206
+ * rolling server upgrade, fall back to ordered single-message claims only
207
+ * when the endpoint itself is absent. Other coordination failures stay
208
+ * fail-open and surface the whole batch.
209
+ */
210
+ declare function claimReplyBatch(cfg: WireConfig, messageIds: string[], holder: string): Promise<number>;
211
+ /** Latest ackable cursor from a batch of rows (rows arrive oldest-first). */
212
+ declare function lastDeliveryId(rows: SyncRow[]): string | null;
213
+
214
+ declare function formatSessionStart(handle: string | null, rows: SyncRow[]): string;
215
+ declare function formatStopPickup(handle: string | null, rows: SyncRow[]): string;
216
+ /**
217
+ * Injected at session start when always-on was set up but the daemon isn't
218
+ * beating (its heartbeat is stale — see alwaysOnHealth). Written in the FIRST
219
+ * person because the agent relays it to its user, and deliberately careful not
220
+ * to imply that stored messages disappear: they remain in conversation
221
+ * history and their delivery envelopes queue within the normal retention
222
+ * window. The one-line fix is inline so the agent can act on it.
223
+ */
224
+ declare function formatAlwaysOnDown(copy: HostCopy): string;
225
+ /**
226
+ * How ONE integration names itself in user-facing copy.
227
+ *
228
+ * There is deliberately no `--platform` anywhere in this module. An
229
+ * integration's CLI acts on exactly one agent — its own — so a flag naming
230
+ * which agent to act on has nothing to select between. Removing the flag is
231
+ * what makes the wrong-agent mistake unrepresentable rather than merely
232
+ * guarded against.
233
+ */
234
+ interface HostCopy {
235
+ /** Exactly what the user types, e.g. `npx -y @agentchatme/codex`, or
236
+ * `node "/abs/path/to/bin/agentchat"` for a plugin-shipped bundle. */
237
+ invoke: string;
238
+ /** Human label for the host, e.g. `Codex` or `Claude Code`. */
239
+ label: string;
240
+ }
241
+ /**
242
+ * What a session is told when the integration is installed but has no identity.
243
+ *
244
+ * Two things this must NOT do, both learned from the first real install:
245
+ *
246
+ * • It must not read like a runbook. The earlier version was a numbered list
247
+ * of CLI invocations, and agents did the natural thing with a numbered list
248
+ * of CLI invocations: they pasted it at the user. Someone who just installed
249
+ * a plugin got a wall of `--email`/`--code` syntax instead of "want a handle
250
+ * other agents can message you at?". The commands are the AGENT'S to run;
251
+ * that has to be said outright, because the format alone implies otherwise.
252
+ *
253
+ * • It must not assert always-on is running. That line used to be
254
+ * unconditional, so a session whose registration had just FAILED was told
255
+ * always-on was already up — the one moment the user needed to know it was
256
+ * not.
257
+ */
258
+ declare function formatRegistrationOffer(copy: HostCopy, alwaysOn?: AlwaysOnState): string;
259
+ /**
260
+ * "You have AgentChat but no handle — offer to set one up."
261
+ *
262
+ * Deliberately bounded: static text is re-read every session, so without an
263
+ * explicit stop condition an agent would raise it forever. `--not-now` records
264
+ * the decline and rewrites this block to the silent variant below.
265
+ */
266
+ declare function renderUnregisteredBlock(copy: HostCopy): string;
267
+ /**
268
+ * The silent variant, written after `--not-now`.
269
+ *
270
+ * Still states the fact — an agent asked "am I on AgentChat?" should be able to
271
+ * answer, and a user who changes their mind should find the command — but it
272
+ * gives no instruction to act on, so there is nothing to nag with.
273
+ */
274
+ declare function renderDeclinedBlock(copy: HostCopy): string;
275
+
276
+ declare const PendingReasonSchema: z.ZodEnum<["autonomy_off", "sender_not_allowed", "local_permission"]>;
277
+ type PendingReason = z.infer<typeof PendingReasonSchema>;
278
+ declare const PendingRequestSchema: z.ZodObject<{
279
+ version: z.ZodLiteral<1>;
280
+ id: z.ZodString;
281
+ status: z.ZodLiteral<"pending">;
282
+ identity_handle: z.ZodString;
283
+ source: z.ZodLiteral<"always_on">;
284
+ conversation_id: z.ZodString;
285
+ peer_agents: z.ZodArray<z.ZodString, "many">;
286
+ inbound_message_ids: z.ZodArray<z.ZodString, "many">;
287
+ focus_message_id: z.ZodString;
288
+ reason: z.ZodEnum<["autonomy_off", "sender_not_allowed", "local_permission"]>;
289
+ summary: z.ZodString;
290
+ first_requested_at: z.ZodString;
291
+ updated_at: z.ZodString;
292
+ }, "strip", z.ZodTypeAny, {
293
+ id: string;
294
+ conversation_id: string;
295
+ status: "pending";
296
+ version: 1;
297
+ identity_handle: string;
298
+ updated_at: string;
299
+ source: "always_on";
300
+ peer_agents: string[];
301
+ inbound_message_ids: string[];
302
+ focus_message_id: string;
303
+ reason: "autonomy_off" | "sender_not_allowed" | "local_permission";
304
+ summary: string;
305
+ first_requested_at: string;
306
+ }, {
307
+ id: string;
308
+ conversation_id: string;
309
+ status: "pending";
310
+ version: 1;
311
+ identity_handle: string;
312
+ updated_at: string;
313
+ source: "always_on";
314
+ peer_agents: string[];
315
+ inbound_message_ids: string[];
316
+ focus_message_id: string;
317
+ reason: "autonomy_off" | "sender_not_allowed" | "local_permission";
318
+ summary: string;
319
+ first_requested_at: string;
320
+ }>;
321
+ type PendingRequest = z.infer<typeof PendingRequestSchema>;
322
+ interface RecordPendingRequestInput {
323
+ selfHandle: string;
324
+ conversationId: string;
325
+ peerAgents: string[];
326
+ inboundMessageIds: string[];
327
+ focusMessageId: string;
328
+ reason: PendingReason;
329
+ summary: string;
330
+ }
331
+ interface RecordedPendingRequest {
332
+ record: PendingRequest;
333
+ /** True only when this write represents review-worthy information not
334
+ * already present in the local record. Used to avoid duplicate OS alerts. */
335
+ changed: boolean;
336
+ }
337
+ declare function pendingRequestId(identityHandle: string, conversationId: string): string;
338
+ /**
339
+ * Persist-before-ack storage. Unlike background activity, this deliberately
340
+ * throws on write failure: the daemon must retry rather than acknowledge a
341
+ * request that the foreground agent would then never learn about.
342
+ */
343
+ declare function recordPendingRequestWithStatus(home: string, input: RecordPendingRequestInput, now?: Date): RecordedPendingRequest;
344
+ declare function recordPendingRequest(home: string, input: RecordPendingRequestInput, now?: Date): PendingRequest;
345
+ declare function getPendingRequest(home: string, identityHandle: string, id: string): PendingRequest | null;
346
+ declare function listPendingRequests(home: string, identityHandle: string): PendingRequest[];
347
+ declare function resolvePendingRequest(home: string, identityHandle: string, id: string): boolean;
348
+ declare function pendingRequestsFingerprint(records: PendingRequest[]): string;
349
+ declare function formatPendingRequestsNotice(records: PendingRequest[], copy: HostCopy): string | null;
350
+ /** Short, deterministic copy for a host's user-visible hook surface. */
351
+ declare function formatPendingRequestsSystemMessage(records: PendingRequest[]): string | null;
352
+
353
+ interface TurnMentionContext {
354
+ messageId: string;
355
+ messageSeq?: number | undefined;
356
+ sender: string;
357
+ senderDisplayName?: string | null | undefined;
358
+ senderKind?: 'agent' | 'system' | undefined;
359
+ createdAt?: string | undefined;
360
+ replyToMessageId?: string | null | undefined;
361
+ /** Bounded notification preview. Full content comes from the anchored
362
+ * conversation read requested by the turn prompt. */
363
+ textPreview: string;
364
+ }
365
+ interface TurnBatchContext {
366
+ /** Number of durable deliveries represented by this one runtime turn. */
367
+ count: number;
368
+ /** Exact oldest-first delivery ids in the frozen batch. */
369
+ messageIds: string[];
370
+ oldestMessageId: string;
371
+ oldestMessageSeq?: number | undefined;
372
+ newestMessageId: string;
373
+ newestMessageSeq?: number | undefined;
374
+ /** Group messages in this batch that explicitly @mentioned this agent. */
375
+ mentionedMessages: TurnMentionContext[];
376
+ }
377
+ interface TurnContext {
378
+ /** Authenticated AgentChat identity handling this delivery. */
379
+ selfHandle?: string | undefined;
380
+ /** Trusted server message id that caused this autonomous turn. */
381
+ messageId?: string | undefined;
382
+ /** Monotonic sequence number inside the AgentChat conversation. */
383
+ messageSeq?: number | undefined;
384
+ /** The AgentChat conversation the message belongs to. */
385
+ conversationId: string;
386
+ /** @handle of the sender. */
387
+ sender: string;
388
+ /** The message text (snippet — the agent re-reads full context via MCP). */
389
+ text: string;
390
+ /** The message's `created_at` (ISO-8601 UTC). A headless turn has no clock;
391
+ * surfacing this lets it judge staleness/urgency before deciding to reply.
392
+ * Explicit `| undefined` so daemon.ts can pass through an absent stamp under
393
+ * exactOptionalPropertyTypes. */
394
+ createdAt?: string | undefined;
395
+ /** Message type ('text' | 'structured' | 'file' | 'system'). Lets a non-text
396
+ * message render a clear placeholder instead of an empty body. */
397
+ type?: string | undefined;
398
+ /** Sender's resolved display name, or null when unset / no context block. */
399
+ senderDisplayName?: string | null | undefined;
400
+ /** 'system' = platform agent (authoritative); 'agent' = peer. */
401
+ senderKind?: 'agent' | 'system' | undefined;
402
+ /** Group's human-readable name (null for DMs / when the server omitted it). */
403
+ groupName?: string | null | undefined;
404
+ /** Current group size when the delivery carried it. */
405
+ memberCount?: number | null | undefined;
406
+ /** Sender-authored reply-parent id, when this message is a threaded reply. */
407
+ replyToMessageId?: string | null | undefined;
408
+ /** Recipient-scoped delivery/read state from the server envelope. */
409
+ deliveryStatus?: string | undefined;
410
+ /** True when THIS agent's handle is in the server-parsed mention list. The
411
+ * daemon computes membership (it knows its own handle) so the adapter just
412
+ * renders the positive fact. */
413
+ mentioned?: boolean | undefined;
414
+ /** Frozen same-conversation backlog represented by this turn. The ordinary
415
+ * top-level message fields always describe its newest/focus message. */
416
+ pendingBatch?: TurnBatchContext | undefined;
417
+ /** Trusted, identity-scoped local policy calculated by the daemon. Peer text
418
+ * cannot populate or alter this field. Handles are the requesters present in
419
+ * this frozen batch, split by whether they may authorize unattended work. */
420
+ fullAutonomy?: FullAutonomyTurnContext | undefined;
421
+ }
422
+ interface FullAutonomyTurnContext {
423
+ mode: AutonomyMode;
424
+ authorizedSenders: string[];
425
+ unauthorizedSenders: string[];
426
+ }
427
+ interface TurnResult {
428
+ ok: boolean;
429
+ /** true if the runtime reported an unrecoverable error (bad setup/auth). */
430
+ fatal?: boolean;
431
+ detail?: string;
432
+ /** Structured local result of a successful autonomous turn. A successful
433
+ * send observed by the adapter is authoritative over model-authored text. */
434
+ disposition?: TurnDisposition;
435
+ }
436
+ type SilentReason = 'informational' | 'closed_thread' | 'not_actionable' | 'not_authorized' | 'other';
437
+ interface PendingTurnRequest {
438
+ reason: PendingReason;
439
+ /** Bounded, model-authored description for local triage. The full request is
440
+ * always re-read from the server conversation before anyone acts. */
441
+ summary: string;
442
+ }
443
+ type TurnDisposition = {
444
+ action: 'replied';
445
+ pending?: PendingTurnRequest;
446
+ } | {
447
+ action: 'silent';
448
+ reason: SilentReason;
449
+ pending?: PendingTurnRequest;
450
+ };
451
+ interface RuntimeAdapter {
452
+ readonly name: string;
453
+ /**
454
+ * Reset conversation continuity when the authenticated AgentChat identity
455
+ * changes. `identityNamespace` contains no credential material; callers use
456
+ * the authenticated API base + handle. Adapters that persist host sessions
457
+ * must include it in their session key, not merely clear an in-memory map.
458
+ */
459
+ reset?(identityNamespace: string): void;
460
+ /** Verify the runtime is usable (binary present, logged in). */
461
+ preflight(): Promise<{
462
+ ok: boolean;
463
+ detail?: string;
464
+ }>;
465
+ /** Run one turn to handle `ctx`. Continuity per conversation is the
466
+ * adapter's concern (session resume). Never throws — returns TurnResult. */
467
+ runTurn(ctx: TurnContext): Promise<TurnResult>;
468
+ }
469
+
470
+ export { markForegroundTurn as $, type AutonomyMode as A, clearForegroundTurn as B, clearSessionActive as C, contextOf as D, formatAlwaysOnDown as E, type FullAutonomyTurnContext as F, formatPendingRequestsNotice as G, type HostCopy as H, formatPendingRequestsSystemMessage as I, formatRegistrationOffer as J, formatSessionStart as K, formatStopPickup as L, type MessageContext as M, fullAutonomyAllows as N, getMeLite as O, type PendingReason as P, getPendingRequest as Q, type RuntimeAdapter as R, type SilentReason as S, type TurnContext as T, idle as U, lastDeliveryId as V, type WireConfig as W, listPendingRequests as X, markAlwaysOnInstalledVersion as Y, markAlwaysOnOptOut as Z, markAlwaysOnWanted as _, type TurnDisposition as a, markSessionActive as a0, normalizeAgentHandle as a1, pendingRequestId as a2, pendingRequestsFingerprint as a3, readAlwaysOnInstalledVersion as a4, readFullAutonomyPolicy as a5, recordPendingRequest as a6, recordPendingRequestWithStatus as a7, removeFullAutonomyAgent as a8, renderDeclinedBlock as a9, renderUnregisteredBlock as aa, resolvePendingRequest as ab, setFullAutonomyMode as ac, syncAck as ad, syncPeek as ae, syncPendingReviewMirror as af, writeFullAutonomyPolicy as ag, type PendingTurnRequest as b, type TurnBatchContext as c, type TurnMentionContext as d, type TurnResult as e, type PendingRequest as f, type AlwaysOnState as g, type FullAutonomyPolicy as h, HEARTBEAT_FILE as i, type PendingReviewMirrorRecord as j, type RecordPendingRequestInput as k, type RecordedPendingRequest as l, type SyncRow as m, WireError as n, allowFullAutonomyAgent as o, alwaysOnHealth as p, alwaysOnOptedOut as q, alwaysOnState as r, alwaysOnWanted as s, autonomyPath as t, beat as u, claimReply as v, claimReplyBatch as w, clearAlwaysOnInstalledVersion as x, clearAlwaysOnOptOut as y, clearAlwaysOnWanted as z };