@prismer/runtime 2.0.3 → 2.0.5

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/dist/index.d.cts CHANGED
@@ -1,9 +1,404 @@
1
1
  import { z } from 'zod';
2
- import { EventEmitter } from 'node:events';
3
2
  import Database from 'better-sqlite3';
3
+ import { EventEmitter } from 'node:events';
4
4
  import { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import { Command } from 'commander';
6
6
 
7
+ type IMAgentStatus = 'online' | 'busy' | 'idle' | 'offline';
8
+ interface HostedAgentDeclaration {
9
+ imUserId: string;
10
+ name: string;
11
+ adapterName: string;
12
+ capabilities: string[];
13
+ profiles: Array<{
14
+ id: string;
15
+ version: number;
16
+ }>;
17
+ }
18
+ interface AgentHostDeclarePayload {
19
+ daemonId: string;
20
+ daemonVersion: string;
21
+ platform: 'darwin' | 'linux' | 'win32';
22
+ agents: HostedAgentDeclaration[];
23
+ }
24
+ interface HostAckedPayload {
25
+ workspaceId: string;
26
+ syncCursor: {
27
+ workspaces: number;
28
+ agent_profiles: number;
29
+ [key: string]: number;
30
+ };
31
+ profilesToSync: string[];
32
+ /** Profile IDs the daemon declared but that no longer exist on the cloud
33
+ * (soft-deleted). The daemon should remove these from its local store. */
34
+ profilesToDelete: string[];
35
+ }
36
+ interface AgentStatusChangedPayload {
37
+ agentImUserId: string;
38
+ status: IMAgentStatus;
39
+ activeProfileId?: string;
40
+ runningTaskIds?: string[];
41
+ }
42
+ interface TaskDispatchContextEntry {
43
+ sender: string;
44
+ senderRole: 'human' | 'agent' | 'admin' | 'system';
45
+ content: string;
46
+ createdAt: string;
47
+ /** Wave-8 W1: assets the human attached to THIS chat message. */
48
+ attachedAssetIds?: string[];
49
+ }
50
+ /** Wave-8 W1: hydrated asset reference attached to a dispatch. */
51
+ interface AssetRef {
52
+ assetId: string;
53
+ contentHash: string;
54
+ mime: string | null;
55
+ sizeBytes: number | null;
56
+ kind: string;
57
+ workspaceId: string;
58
+ role: 'attachment' | 'context';
59
+ /**
60
+ * 14b rev.3 §3.0.4 — cloud-hosted URL for this asset, when present.
61
+ * Adapters prefer this for image/file blocks (cheaper than base64) but
62
+ * fall back to `base64` when the URL is not reachable by the upstream
63
+ * LLM provider (D35 smart probing).
64
+ */
65
+ cdnUrl?: string;
66
+ /**
67
+ * Optional human-readable filename. Surfaced to adapters that want to
68
+ * label `input_file` blocks (OpenClaw `/v1/responses`).
69
+ */
70
+ filename?: string;
71
+ }
72
+ /**
73
+ * 14b rev.3 §3.0.4 / §9 P3 — what `resolveAssetRefs` hands to an adapter for
74
+ * multimodal-aware dispatch. Extends AssetRef with:
75
+ * - `localPath` — daemon's local cache copy (still surfaced so file-based
76
+ * tools in legacy adapters keep working);
77
+ * - `base64` — populated when daemon decided cdnUrl was unreachable and
78
+ * inlined the bytes (D35 fallback);
79
+ * - `reachable` — cdnUrl reachability probe result for adapter introspection.
80
+ */
81
+ interface ResolvedAssetRef extends AssetRef {
82
+ localPath?: string;
83
+ base64?: string;
84
+ reachable?: 'cdn' | 'base64' | 'unknown';
85
+ }
86
+ interface TaskDispatchRequestPayload {
87
+ taskId: string;
88
+ /** Agent target for runtimeRoute='agent'. Shell dispatches do not use this. */
89
+ agentImUserId?: string;
90
+ /** Runtime/device target for runtimeRoute='shell'. */
91
+ targetDaemonId?: string;
92
+ profileId: string;
93
+ capability: string;
94
+ prompt: string;
95
+ /** Execution surface. `shell` is daemon-local command execution. */
96
+ runtimeRoute?: 'agent' | 'sandbox' | 'shell';
97
+ metadata?: Record<string, unknown>;
98
+ timeoutMs?: number;
99
+ context?: TaskDispatchContextEntry[];
100
+ conversationId?: string;
101
+ /**
102
+ * Channel mode for the originating conversation. Optional and
103
+ * forward-compatible: when missing, daemon renders 'unknown' in the
104
+ * [Channel context] prompt block. See appendChannelContext in
105
+ * daemon/dispatch.ts.
106
+ * - `direct`: 1:1 DM (no @-mention needed in reply).
107
+ * - `group`: multi-party room (end reply with `@<recipient>` to
108
+ * continue the chain).
109
+ */
110
+ conversationType?: 'direct' | 'group';
111
+ /**
112
+ * Active participants of the dispatch's conversation. Daemon injects this
113
+ * into [Channel context] so the agent knows the authoritative recipient list
114
+ * without hallucinating or having to call `prismer.conversation.listAgents`. Capped at 50
115
+ * entries server-side.
116
+ */
117
+ participants?: Array<{
118
+ imUserId: string;
119
+ username: string;
120
+ displayName: string;
121
+ role: string;
122
+ agentType?: string | null;
123
+ }>;
124
+ /** Wave-8 W1: assets cloud wants daemon to fold into agent context. */
125
+ assetRefs?: AssetRef[];
126
+ }
127
+ interface TaskDispatchProgressPayload {
128
+ taskId: string;
129
+ progress: number;
130
+ message?: string;
131
+ detail?: Record<string, unknown>;
132
+ }
133
+ /** Wave-8 W1: how the daemon handled a single AssetRef. */
134
+ type AssetDispatchStrategy = 'inline-text' | 'inline-text-truncated' | 'uri-only' | 'fetched-https' | 'error';
135
+ interface AssetDispatchObservation {
136
+ /** AssetRef.assetId for cloud-attached refs; undefined for L5 https fetches. */
137
+ assetId?: string;
138
+ contentHash: string;
139
+ mime: string | null;
140
+ sizeBytes: number | null;
141
+ strategy: AssetDispatchStrategy;
142
+ inlinedBytes?: number;
143
+ error?: string;
144
+ /** L5: original URL the user wrote in the prompt. */
145
+ originalUrl?: string;
146
+ /** L5: post-redirect URL the body was actually downloaded from. */
147
+ finalUrl?: string;
148
+ /** L5: end-to-end fetch duration in ms (DNS + connect + body read). */
149
+ durationMs?: number;
150
+ }
151
+ interface TaskDispatchReplyPayload {
152
+ taskId: string;
153
+ ok: boolean;
154
+ output?: string;
155
+ error?: {
156
+ code: string;
157
+ message: string;
158
+ };
159
+ assetIds?: string[];
160
+ metrics?: {
161
+ tokensUsed?: number;
162
+ durationMs?: number;
163
+ };
164
+ /** Wave-8 W1: per-asset handling report. */
165
+ assetObservability?: AssetDispatchObservation[];
166
+ }
167
+ interface TaskCancelPayload {
168
+ taskId: string;
169
+ reason?: string;
170
+ }
171
+ interface IMWSMessage<T = unknown> {
172
+ type: string;
173
+ payload: T;
174
+ requestId?: string;
175
+ timestamp: number;
176
+ }
177
+ interface WorkspaceChangedPayload {
178
+ workspaceId: string;
179
+ /** ISO-8601 timestamp from im_workspaces.updatedAt. */
180
+ updatedAt: string;
181
+ }
182
+ interface AgentProfileChangedPayload {
183
+ profileId: string;
184
+ version: number;
185
+ }
186
+ interface AgentChangedPayload {
187
+ agentImUserId: string;
188
+ fields: {
189
+ displayName?: string;
190
+ capabilities?: string[];
191
+ };
192
+ }
193
+ interface WorkspaceFileChangedPayload {
194
+ workspaceId: string;
195
+ path: string;
196
+ operation: 'create' | 'update' | 'delete';
197
+ assetId?: string;
198
+ contentHash?: string;
199
+ version: number;
200
+ }
201
+ interface AssetChangedPayload {
202
+ workspaceId: string;
203
+ assetId: string;
204
+ operation: 'create' | 'update' | 'delete';
205
+ contentHash?: string;
206
+ assetIndexSeq?: number;
207
+ revision?: number;
208
+ }
209
+
210
+ type MemoryPageType = 'hub' | 'leaf' | 'decision' | 'glossary' | 'archive';
211
+ type MemoryVisibility = {
212
+ kind: 'workspace';
213
+ } | {
214
+ kind: 'agent';
215
+ imUserId: string;
216
+ } | {
217
+ kind: 'private';
218
+ imUserId: string;
219
+ };
220
+ type ActorKind = 'human' | 'agent';
221
+ type MemorySyncStatus = 'local-only' | 'pending' | 'acked' | 'remote-conflict';
222
+ interface MemoryPage {
223
+ id: string;
224
+ workspaceId: string;
225
+ path: string;
226
+ title: string | null;
227
+ description: string | null;
228
+ contentHash: string;
229
+ version: number;
230
+ pageType: MemoryPageType;
231
+ visibility: MemoryVisibility;
232
+ encrypted: boolean;
233
+ stale: boolean;
234
+ archivedAt: number | null;
235
+ sourceAssetId: string | null;
236
+ sourceRefs: string[];
237
+ syncStatus: MemorySyncStatus;
238
+ createdAt: number;
239
+ updatedAt: number;
240
+ }
241
+ interface MemoryPageContent {
242
+ pageId: string;
243
+ version: number;
244
+ content: string;
245
+ }
246
+ interface MemoryLink {
247
+ sourceUri: string;
248
+ targetUri: string;
249
+ relation: string;
250
+ weight: number;
251
+ extractedFromPageId: string | null;
252
+ }
253
+ interface MemorySearchResult {
254
+ pageId: string;
255
+ path: string;
256
+ title: string | null;
257
+ snippet: string;
258
+ score: number;
259
+ tokenCount: number;
260
+ }
261
+ interface MemorySearchOptions {
262
+ topK?: number;
263
+ relevanceThreshold?: number;
264
+ maxBytes?: number;
265
+ pageType?: MemoryPageType[];
266
+ }
267
+ interface MemoryWriteInput {
268
+ workspaceId: string;
269
+ path: string;
270
+ content: string;
271
+ pageType?: MemoryPageType;
272
+ title?: string;
273
+ description?: string;
274
+ visibility?: MemoryVisibility;
275
+ sourceAssetId?: string;
276
+ sourceRefs?: string[];
277
+ stale?: boolean;
278
+ actorImUserId: string;
279
+ actorKind: ActorKind;
280
+ }
281
+ interface MemoryStats {
282
+ workspaceId: string | null;
283
+ pageCount: number;
284
+ pendingOutbox: number;
285
+ deadLetterCount: number;
286
+ lastSyncAt: number | null;
287
+ dbPath: string;
288
+ }
289
+
290
+ interface MemoryStoreOptions {
291
+ /** Absolute path to the SQLite database file. Parent dir created with 0o700 if absent. */
292
+ dbPath: string;
293
+ /** Workspace this store belongs to. Reject ops referencing other workspaces. */
294
+ workspaceId: string;
295
+ /** Device identifier. Stamped onto version + outbox rows at write time. */
296
+ deviceId: string;
297
+ }
298
+ declare class MemoryStore {
299
+ private readonly opts;
300
+ private db;
301
+ constructor(opts: MemoryStoreOptions);
302
+ open(): void;
303
+ close(): void;
304
+ loadByPath(pagePath: string): MemoryPage | null;
305
+ loadById(pageId: string): MemoryPage | null;
306
+ loadContent(pageId: string, version?: number): MemoryPageContent | null;
307
+ list(options?: {
308
+ pageType?: MemoryPageType;
309
+ limit?: number;
310
+ }): MemoryPage[];
311
+ write(input: MemoryWriteInput): MemoryPage;
312
+ invalidate(pageIds: string[], _reason: string): void;
313
+ upsertLink(link: MemoryLink): void;
314
+ stats(): MemoryStats;
315
+ /**
316
+ * Record sync cursor for incremental sync. Used by cloud-sync.ts to
317
+ * persist the high-water mark for future cursor-based catch-up.
318
+ */
319
+ recordCursor(workspaceId: string, cursor: string): void;
320
+ /**
321
+ * Internal accessor for outbox.ts — outbox writes its own table within the
322
+ * same DB. Returning the live Database handle keeps outbox transactions
323
+ * shareable with store transactions if ever needed.
324
+ */
325
+ rawDb(): Database.Database;
326
+ /** Workspace this store is bound to (read-only). */
327
+ workspaceId(): string;
328
+ /** Device id stamped onto version + outbox rows. */
329
+ deviceId(): string;
330
+ private requireDb;
331
+ private rowToPage;
332
+ }
333
+
334
+ type MemoryScope = 'workspace-shared' | 'agent-private';
335
+ interface ScopedMemoryStoreOptions {
336
+ /** Filesystem root, e.g. `~/.prismer/memory` (will create per-workspace dir under). */
337
+ rootDir: string;
338
+ /** Workspace identifier — used both as the cloud workspaceId and the dir slug. */
339
+ workspaceId: string;
340
+ /** Optional human-friendly slug for the dir; defaults to workspaceId. */
341
+ workspaceSlug?: string;
342
+ /** Device identifier, stamped onto outbox + version rows. */
343
+ deviceId: string;
344
+ }
345
+ interface ScopedWriteInput extends Omit<MemoryWriteInput, 'workspaceId'> {
346
+ scope: MemoryScope;
347
+ /** Required when scope='agent-private'; rejected when scope='workspace-shared'. */
348
+ agentImUserId?: string;
349
+ }
350
+ interface ScopedSearchInput {
351
+ query: string;
352
+ scope: MemoryScope;
353
+ /** Required when scope='agent-private' — search only this agent's bucket. */
354
+ agentImUserId?: string;
355
+ options?: MemorySearchOptions;
356
+ }
357
+ /**
358
+ * Per-workspace, per-bucket SQLite store with the same lifecycle semantics
359
+ * as the existing single-store MemoryStore class.
360
+ */
361
+ declare class ScopedMemoryStore {
362
+ private readonly opts;
363
+ private sharedStore;
364
+ private agentStores;
365
+ private readonly workspaceDir;
366
+ constructor(opts: ScopedMemoryStoreOptions);
367
+ /** Resolve the shared store (lazy open). */
368
+ shared(): MemoryStore;
369
+ /** Resolve the per-agent store (lazy open). */
370
+ forAgent(agentImUserId: string): MemoryStore;
371
+ /** Resolve the right store for (scope, agentImUserId). */
372
+ resolve(scope: MemoryScope, agentImUserId?: string): MemoryStore;
373
+ /** Write a memory page into the correct bucket. */
374
+ write(input: ScopedWriteInput): {
375
+ pageId: string;
376
+ scope: MemoryScope;
377
+ agentImUserId?: string;
378
+ };
379
+ /**
380
+ * Search only the requested bucket. This is the daemon-first FTS5 path
381
+ * referenced by FF_MEMORY_SEARCH_DAEMON_FIRST.
382
+ */
383
+ search(input: ScopedSearchInput): MemorySearchResult[];
384
+ /**
385
+ * Search across BOTH the shared bucket and the requesting agent's private
386
+ * bucket, merging results by descending score. Used by the recall-injector
387
+ * so the agent's prompt sees both shared knowledge and its own private notes.
388
+ */
389
+ searchForAgent(query: string, agentImUserId: string, options?: MemorySearchOptions): MemorySearchResult[];
390
+ /** List which agent ids currently have a private DB on disk (for diagnostics). */
391
+ listAgentBuckets(): string[];
392
+ /** Total disk footprint (best-effort; sums each bucket's main DB file). */
393
+ diskBytes(): {
394
+ shared: number;
395
+ perAgent: Record<string, number>;
396
+ total: number;
397
+ };
398
+ /** Close all underlying stores. */
399
+ close(): void;
400
+ }
401
+
7
402
  type AdapterKind = 'long-running' | 'interactive';
8
403
  /**
9
404
  * Adapter definition: how the daemon hosts one class of agent
@@ -70,6 +465,17 @@ interface AgentProfile {
70
465
  */
71
466
  agentUsername?: string;
72
467
  }
468
+ interface TaskHeartbeatHandle {
469
+ setPhase(phase: string): void;
470
+ touchStep(): void;
471
+ }
472
+ interface StepRecorderHandle {
473
+ recordPhaseChange(phase: string): void;
474
+ recordToolCall(toolName: string, input: unknown, toolCallId?: string): void;
475
+ recordToolResult(toolCallId: string, output: unknown): void;
476
+ recordReasoningChunk(text: string): void;
477
+ recordError(message: string, payload?: Record<string, unknown>): void;
478
+ }
73
479
  interface TaskInput {
74
480
  taskId: string;
75
481
  prompt: string;
@@ -81,6 +487,59 @@ interface TaskInput {
81
487
  detail?: Record<string, unknown>;
82
488
  }) => void;
83
489
  signal?: AbortSignal;
490
+ /**
491
+ * Wave-3 D2 observability handles. Optional — pre-Wave-3 adapters that
492
+ * don't read these still function (heartbeat keeps ticking with the
493
+ * adapter's last-set phase; no step timeline is emitted).
494
+ *
495
+ * `heartbeat`:
496
+ * - Adapter calls `heartbeat.setPhase('thinking' | 'tool_use' | ...)`
497
+ * when the lifecycle advances. The 15s timer is owned by dispatch.ts;
498
+ * adapters MUST NOT start/stop it themselves.
499
+ * - Adapter may call `heartbeat.touchStep()` around observable boundaries
500
+ * (tool call, token emission) so cloud's 45s "stuck" reaper can
501
+ * distinguish slow-but-alive from truly silent.
502
+ *
503
+ * `recorder`:
504
+ * - Per-task run step uploader. Adapter calls `recordToolCall`,
505
+ * `recordToolResult`, `recordReasoningChunk`, `recordPhaseChange`,
506
+ * `recordError` at the relevant moments. Implementation is throttled
507
+ * for reasoning_chunk (500ms batched).
508
+ * - Per §3.0.2 Gap C-⑤, short skill calls (<10s) may rely solely on
509
+ * the default 'tool_use' phase; long-running ones SHOULD self-report.
510
+ *
511
+ * See sdk/prismer-cloud/runtime/src/daemon/task-heartbeat.ts and
512
+ * sdk/prismer-cloud/runtime/src/daemon/step-recorder.ts.
513
+ */
514
+ heartbeat?: TaskHeartbeatHandle;
515
+ recorder?: StepRecorderHandle;
516
+ /**
517
+ * 14b rev.3 §3.0.4 / §9 P3 — assets the daemon has resolved (cdnUrl
518
+ * reachability probed, base64 prefetched if necessary). Multimodal-aware
519
+ * adapters (Hermes `/v1/chat/completions`, OpenClaw `/v1/responses`) lift
520
+ * image/file blocks into their wire format; text-like inlining is still
521
+ * handled by `dispatch.ts:composePrompt` (it stays in the `prompt` string).
522
+ * Empty/undefined preserves the legacy text-only contract.
523
+ */
524
+ assetRefs?: ResolvedAssetRef[];
525
+ /**
526
+ * v2.0 Wave 4-E6 (doc 14 §4.7 Track-F) — scoped daemon memory store.
527
+ *
528
+ * Optional handle the adapter may use to:
529
+ * - Search this agent's memory buckets (workspace-shared + agent-private)
530
+ * for daemon-first FTS5 lookup (0 round-trips to cloud)
531
+ * - Build a `[Relevant memory]` system block via the recall-injector for
532
+ * prompt enrichment before the LLM call
533
+ *
534
+ * When undefined, the adapter MUST NOT attempt local memory recall (legacy
535
+ * behaviour pre-Wave-4 — cloud `/memory/search` is the only path). When
536
+ * defined, FF_MEMORY_SEARCH_DAEMON_FIRST and operatingPrinciples drive
537
+ * whether the adapter actually injects.
538
+ *
539
+ * Wiring is owned by dispatch.ts (resolves per-agent slot → ScopedMemoryStore
540
+ * → passes here). Adapters never instantiate the store themselves.
541
+ */
542
+ memoryStore?: ScopedMemoryStore;
84
543
  }
85
544
  interface TaskResult {
86
545
  ok: boolean;
@@ -528,7 +987,7 @@ type LocalDb = Database.Database;
528
987
  declare function openLocalDb(path: string): LocalDb;
529
988
  declare function runMigrations(db: LocalDb): void;
530
989
  declare function currentSchemaVersion(db: LocalDb): number;
531
- declare const TARGET_SCHEMA_VERSION = 3;
990
+ declare const TARGET_SCHEMA_VERSION = 4;
532
991
 
533
992
  type SyncResourceType = 'workspace' | 'agent' | 'agent_profile';
534
993
  type SyncOperation = 'create' | 'update' | 'delete';
@@ -1062,183 +1521,6 @@ declare class ParseClaimController {
1062
1521
  }): HeartbeatHandle;
1063
1522
  }
1064
1523
 
1065
- type IMAgentStatus = 'online' | 'busy' | 'idle' | 'offline';
1066
- interface HostedAgentDeclaration {
1067
- imUserId: string;
1068
- name: string;
1069
- adapterName: string;
1070
- capabilities: string[];
1071
- profiles: Array<{
1072
- id: string;
1073
- version: number;
1074
- }>;
1075
- }
1076
- interface AgentHostDeclarePayload {
1077
- daemonId: string;
1078
- daemonVersion: string;
1079
- platform: 'darwin' | 'linux' | 'win32';
1080
- agents: HostedAgentDeclaration[];
1081
- }
1082
- interface HostAckedPayload {
1083
- workspaceId: string;
1084
- syncCursor: {
1085
- workspaces: number;
1086
- agent_profiles: number;
1087
- [key: string]: number;
1088
- };
1089
- profilesToSync: string[];
1090
- /** Profile IDs the daemon declared but that no longer exist on the cloud
1091
- * (soft-deleted). The daemon should remove these from its local store. */
1092
- profilesToDelete: string[];
1093
- }
1094
- interface AgentStatusChangedPayload {
1095
- agentImUserId: string;
1096
- status: IMAgentStatus;
1097
- activeProfileId?: string;
1098
- runningTaskIds?: string[];
1099
- }
1100
- interface TaskDispatchContextEntry {
1101
- sender: string;
1102
- senderRole: 'human' | 'agent' | 'admin' | 'system';
1103
- content: string;
1104
- createdAt: string;
1105
- /** Wave-8 W1: assets the human attached to THIS chat message. */
1106
- attachedAssetIds?: string[];
1107
- }
1108
- /** Wave-8 W1: hydrated asset reference attached to a dispatch. */
1109
- interface AssetRef {
1110
- assetId: string;
1111
- contentHash: string;
1112
- mime: string | null;
1113
- sizeBytes: number | null;
1114
- kind: string;
1115
- workspaceId: string;
1116
- role: 'attachment' | 'context';
1117
- }
1118
- interface TaskDispatchRequestPayload {
1119
- taskId: string;
1120
- /** Agent target for runtimeRoute='agent'. Shell dispatches do not use this. */
1121
- agentImUserId?: string;
1122
- /** Runtime/device target for runtimeRoute='shell'. */
1123
- targetDaemonId?: string;
1124
- profileId: string;
1125
- capability: string;
1126
- prompt: string;
1127
- /** Execution surface. `shell` is daemon-local command execution. */
1128
- runtimeRoute?: 'agent' | 'sandbox' | 'shell';
1129
- metadata?: Record<string, unknown>;
1130
- timeoutMs?: number;
1131
- context?: TaskDispatchContextEntry[];
1132
- conversationId?: string;
1133
- /**
1134
- * Channel mode for the originating conversation. Optional and
1135
- * forward-compatible: when missing, daemon renders 'unknown' in the
1136
- * [Channel context] prompt block. See appendChannelContext in
1137
- * daemon/dispatch.ts.
1138
- * - `direct`: 1:1 DM (no @-mention needed in reply).
1139
- * - `group`: multi-party room (end reply with `@<recipient>` to
1140
- * continue the chain).
1141
- */
1142
- conversationType?: 'direct' | 'group';
1143
- /**
1144
- * Active participants of the dispatch's conversation. Daemon injects this
1145
- * into [Channel context] so the agent knows the authoritative recipient list
1146
- * without hallucinating or having to call `prismer.conversation.listAgents`. Capped at 50
1147
- * entries server-side.
1148
- */
1149
- participants?: Array<{
1150
- imUserId: string;
1151
- username: string;
1152
- displayName: string;
1153
- role: string;
1154
- agentType?: string | null;
1155
- }>;
1156
- /** Wave-8 W1: assets cloud wants daemon to fold into agent context. */
1157
- assetRefs?: AssetRef[];
1158
- }
1159
- interface TaskDispatchProgressPayload {
1160
- taskId: string;
1161
- progress: number;
1162
- message?: string;
1163
- detail?: Record<string, unknown>;
1164
- }
1165
- /** Wave-8 W1: how the daemon handled a single AssetRef. */
1166
- type AssetDispatchStrategy = 'inline-text' | 'inline-text-truncated' | 'uri-only' | 'fetched-https' | 'error';
1167
- interface AssetDispatchObservation {
1168
- /** AssetRef.assetId for cloud-attached refs; undefined for L5 https fetches. */
1169
- assetId?: string;
1170
- contentHash: string;
1171
- mime: string | null;
1172
- sizeBytes: number | null;
1173
- strategy: AssetDispatchStrategy;
1174
- inlinedBytes?: number;
1175
- error?: string;
1176
- /** L5: original URL the user wrote in the prompt. */
1177
- originalUrl?: string;
1178
- /** L5: post-redirect URL the body was actually downloaded from. */
1179
- finalUrl?: string;
1180
- /** L5: end-to-end fetch duration in ms (DNS + connect + body read). */
1181
- durationMs?: number;
1182
- }
1183
- interface TaskDispatchReplyPayload {
1184
- taskId: string;
1185
- ok: boolean;
1186
- output?: string;
1187
- error?: {
1188
- code: string;
1189
- message: string;
1190
- };
1191
- assetIds?: string[];
1192
- metrics?: {
1193
- tokensUsed?: number;
1194
- durationMs?: number;
1195
- };
1196
- /** Wave-8 W1: per-asset handling report. */
1197
- assetObservability?: AssetDispatchObservation[];
1198
- }
1199
- interface TaskCancelPayload {
1200
- taskId: string;
1201
- reason?: string;
1202
- }
1203
- interface IMWSMessage<T = unknown> {
1204
- type: string;
1205
- payload: T;
1206
- requestId?: string;
1207
- timestamp: number;
1208
- }
1209
- interface WorkspaceChangedPayload {
1210
- workspaceId: string;
1211
- /** ISO-8601 timestamp from im_workspaces.updatedAt. */
1212
- updatedAt: string;
1213
- }
1214
- interface AgentProfileChangedPayload {
1215
- profileId: string;
1216
- version: number;
1217
- }
1218
- interface AgentChangedPayload {
1219
- agentImUserId: string;
1220
- fields: {
1221
- displayName?: string;
1222
- capabilities?: string[];
1223
- };
1224
- }
1225
- interface WorkspaceFileChangedPayload {
1226
- workspaceId: string;
1227
- path: string;
1228
- operation: 'create' | 'update' | 'delete';
1229
- assetId?: string;
1230
- contentHash?: string;
1231
- version: number;
1232
- }
1233
- interface AssetChangedPayload {
1234
- workspaceId: string;
1235
- assetId: string;
1236
- operation: 'create' | 'update' | 'delete';
1237
- contentHash?: string;
1238
- assetIndexSeq?: number;
1239
- revision?: number;
1240
- }
1241
-
1242
1524
  type PrismerUriType = 'asset' | 'file';
1243
1525
  interface ParsedPrismerUri {
1244
1526
  /** Original full URI string. */
@@ -1479,6 +1761,29 @@ declare class OutboxWatcher {
1479
1761
  private currentScanDirs;
1480
1762
  private tick;
1481
1763
  private scanDir;
1764
+ /**
1765
+ * F2B — report an OUTBOX_MIME_MISMATCH task event to cloud so the task log
1766
+ * gains an auditable record of the rejection. Without this, the failure is
1767
+ * silent (file quarantined locally, agent unaware) and the next dispatch
1768
+ * loops on the same bad strategy.
1769
+ *
1770
+ * Best-effort: every catch site swallows errors. We never want this
1771
+ * upstream call to block local quarantine or trigger a retry loop, because
1772
+ * the reject decision is already final by the time we get here.
1773
+ *
1774
+ * Endpoint contract: POST /api/im/tasks/:id/event
1775
+ * { code: 'OUTBOX_MIME_MISMATCH', payload: { file, daemonError }, message }
1776
+ * Only callable when `task?.taskId` is known — pre-dispatch outbox files
1777
+ * (no active task) are quarantined silently as before.
1778
+ */
1779
+ private reportMimeMismatchToCloud;
1780
+ /**
1781
+ * Move a file under `<dir>/_rejected/` so it stops being a scan candidate
1782
+ * (the loop skips the reserved subdir) and operators can find the
1783
+ * offending artifact. Filename collision is rare per task; on collision
1784
+ * we append a millisecond suffix to keep the original observable.
1785
+ */
1786
+ private quarantine;
1482
1787
  private upload;
1483
1788
  }
1484
1789
 
@@ -1576,6 +1881,15 @@ interface DispatchDeps {
1576
1881
  * Keyed by workspaceId. If absent, #ref resolution is skipped entirely.
1577
1882
  */
1578
1883
  assetMetadataIndexes?: Map<string, AssetMetadataIndex>;
1884
+ /**
1885
+ * 14b D34=C — optional override for image vision-fallback. Enabled by
1886
+ * default when image assetRefs are present.
1887
+ */
1888
+ visionAux?: {
1889
+ enabled?: boolean;
1890
+ cacheDir?: string;
1891
+ timeoutMs?: number;
1892
+ };
1579
1893
  }
1580
1894
  declare function handleDispatch(payload: TaskDispatchRequestPayload, requestId: string | undefined, deps: DispatchDeps): Promise<TaskDispatchReplyPayload>;
1581
1895
  /**
@@ -1907,6 +2221,14 @@ declare class LocalServer {
1907
2221
  * to `/api/sandboxes/:id/snapshot/manifest`.
1908
2222
  */
1909
2223
  private handleSnapshot;
2224
+ /**
2225
+ * POST /v1/agents/:agentId/dump-state — agent-scoped daemon manifest.
2226
+ *
2227
+ * This is narrower than `/v1/snapshot`: it walks only the current agent's
2228
+ * profile/work dirs so cloud can attach the result to IMAgentSnapshot without
2229
+ * capturing unrelated agents hosted in the same daemon/container.
2230
+ */
2231
+ private handleAgentDumpState;
1910
2232
  /**
1911
2233
  * POST /local/asset/write — agent-gen adapter RPC.
1912
2234
  *
@@ -1991,6 +2313,8 @@ declare class Runner extends EventEmitter {
1991
2313
  private taskReaperTimer?;
1992
2314
  private skillResyncTimer?;
1993
2315
  private skillSyncInFlight;
2316
+ private pendingReplyCache;
2317
+ private pendingReplyRecoveryDone;
1994
2318
  constructor(opts?: RunnerOptions);
1995
2319
  start(): Promise<void>;
1996
2320
  /**
@@ -2076,11 +2400,39 @@ declare class Runner extends EventEmitter {
2076
2400
  private wireWsHandlers;
2077
2401
  private sendDeclare;
2078
2402
  private handleIncoming;
2403
+ /**
2404
+ * v2.0 §4.8.1 — webhook dispatch over WS reverse channel.
2405
+ *
2406
+ * Acks via `webhook.dispatch.reply { _rpcId, ok, acceptedAt }` immediately
2407
+ * after `handleAgentMessageDispatch()` returns its synchronous response
2408
+ * — the actual reply (the agent's reply text/attachments) still flows
2409
+ * through `postMessageDispatchReply()` which posts to
2410
+ * `/api/im/dispatch/reply` after the adapter finishes. That mirrors the
2411
+ * HTTP /dispatch contract: ack first, asynchronous final reply via the
2412
+ * dispatch-reply REST endpoint.
2413
+ *
2414
+ * The `_rpcId` field is the only addition versus the HTTP path; we strip
2415
+ * it before passing the payload to the dispatch handler so existing
2416
+ * validation logic doesn't trip on an unknown field.
2417
+ */
2418
+ private onWebhookDispatch;
2079
2419
  private onHostAcked;
2080
2420
  private onTaskDispatch;
2081
2421
  private onTaskCancel;
2082
2422
  private findMessageDispatchAgent;
2083
2423
  private postMessageDispatchReply;
2424
+ /**
2425
+ * v2.0 §4.8.1 (Wave 4-E4) — submit the transport-probe report. Called
2426
+ * from `onHostAcked` so the daemon's container row exists in cloud
2427
+ * before we POST to /runtime/transport-report.
2428
+ *
2429
+ * The gatewayUrl reported here is `http://<local-ipv4>:<localPort>`
2430
+ * (typically `http://192.168.x.x:3210`). For a daemon without a local
2431
+ * HTTP server (startLocalServer=false in tests) we still post the
2432
+ * probe with `gatewayUrl=null` so cloud knows transport='ws' is the
2433
+ * only path.
2434
+ */
2435
+ private reportTransport;
2084
2436
  private onAgentChanged;
2085
2437
  private onAgentProfileChanged;
2086
2438
  private syncProfileFromCloud;
@@ -2268,17 +2620,17 @@ declare const CodexConfigSchema: z.ZodObject<{
2268
2620
  */
2269
2621
  apiKeyEnv: z.ZodDefault<z.ZodString>;
2270
2622
  }, "strip", z.ZodTypeAny, {
2623
+ sandbox: "read-only" | "workspace-write" | "danger-full-access";
2271
2624
  model: string;
2272
2625
  cwd: string;
2273
- sandbox: "read-only" | "workspace-write" | "danger-full-access";
2274
2626
  apiKeyEnv: string;
2275
2627
  systemPrompt?: string | undefined;
2276
2628
  envVars?: Record<string, string> | undefined;
2277
2629
  }, {
2278
2630
  cwd: string;
2631
+ sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
2279
2632
  model?: string | undefined;
2280
2633
  systemPrompt?: string | undefined;
2281
- sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
2282
2634
  envVars?: Record<string, string> | undefined;
2283
2635
  apiKeyEnv?: string | undefined;
2284
2636
  }>;