@prismer/runtime 1.9.1 → 1.9.7

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +210 -0
  2. package/README.md +363 -229
  3. package/dist/cli.cjs +10274 -0
  4. package/dist/cli.cjs.map +1 -0
  5. package/dist/cli.js +10262 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/index.cjs +10608 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.cts +1643 -0
  10. package/dist/index.d.ts +1496 -1916
  11. package/dist/index.js +9707 -9177
  12. package/dist/index.js.map +1 -1
  13. package/package.json +31 -33
  14. package/dist/adapter-registry-EMZFKFYK.mjs +0 -8
  15. package/dist/adapter-registry-EMZFKFYK.mjs.map +0 -1
  16. package/dist/artifacts-uploader-N2HNBSTW.mjs +0 -159
  17. package/dist/artifacts-uploader-N2HNBSTW.mjs.map +0 -1
  18. package/dist/auto-register-M4YSPJS6.mjs +0 -204
  19. package/dist/auto-register-M4YSPJS6.mjs.map +0 -1
  20. package/dist/bin/prismer.js +0 -14988
  21. package/dist/chunk-BJTO5JO5.mjs +0 -11
  22. package/dist/chunk-BJTO5JO5.mjs.map +0 -1
  23. package/dist/chunk-JIEDFDVI.mjs +0 -148
  24. package/dist/chunk-JIEDFDVI.mjs.map +0 -1
  25. package/dist/chunk-NDNX2G6O.mjs +0 -68
  26. package/dist/chunk-NDNX2G6O.mjs.map +0 -1
  27. package/dist/chunk-POWV475F.mjs +0 -45
  28. package/dist/chunk-POWV475F.mjs.map +0 -1
  29. package/dist/chunk-VTFKZAUY.mjs +0 -68
  30. package/dist/chunk-VTFKZAUY.mjs.map +0 -1
  31. package/dist/dispatch-mux-MW4HHICS.mjs +0 -8
  32. package/dist/dispatch-mux-MW4HHICS.mjs.map +0 -1
  33. package/dist/dispatch-rpc-J7H6KC2V.mjs +0 -78
  34. package/dist/dispatch-rpc-J7H6KC2V.mjs.map +0 -1
  35. package/dist/fs-rpc-OSHJZYK6.mjs +0 -58
  36. package/dist/fs-rpc-OSHJZYK6.mjs.map +0 -1
  37. package/dist/heartbeat-loop-H2LAI3V5.mjs +0 -95
  38. package/dist/heartbeat-loop-H2LAI3V5.mjs.map +0 -1
  39. package/dist/index.d.mts +0 -2063
  40. package/dist/index.mjs +0 -8807
  41. package/dist/index.mjs.map +0 -1
  42. package/dist/mode-b-DXJ7FJAL.mjs +0 -152
  43. package/dist/mode-b-DXJ7FJAL.mjs.map +0 -1
  44. package/dist/registry-ZYU2HDFL.mjs +0 -12
  45. package/dist/registry-ZYU2HDFL.mjs.map +0 -1
  46. /package/{dist → assets}/icon +0 -0
  47. /package/{dist → assets}/smallicon +0 -0
@@ -0,0 +1,1643 @@
1
+ import { z } from 'zod';
2
+ import { EventEmitter } from 'node:events';
3
+ import Database from 'better-sqlite3';
4
+ import { IncomingMessage, ServerResponse } from 'node:http';
5
+ import { Command } from 'commander';
6
+
7
+ type AdapterKind = 'long-running' | 'interactive';
8
+ /**
9
+ * Adapter definition: how the daemon hosts one class of agent
10
+ * (Hermes, Claude Code, OpenClaw, ...).
11
+ *
12
+ * One Adapter ←(1:N)─ Agent ←(1:N)─ AgentProfile
13
+ *
14
+ * Long-running adapters implement {@link AdapterDef.ensureService} and
15
+ * return a reusable {@link AdapterService} (e.g. HTTP client to
16
+ * `hermes gateway`). Interactive adapters implement
17
+ * {@link AdapterDef.dispatch} and spawn a fresh subprocess per task
18
+ * (e.g. `claude --headless`).
19
+ */
20
+ interface AdapterDef {
21
+ /** Unique name, e.g. 'hermes', 'claude-code'. Matches `IMAgentCard.adapterName`. */
22
+ name: string;
23
+ /** Long-running services expose ensureService; interactive adapters expose dispatch. */
24
+ kind: AdapterKind;
25
+ /** Capability tags surfaced to mobile (e.g. ['shell', 'code', 'mcp']). */
26
+ capabilities: string[];
27
+ /** Zod schema validating `AgentProfile.config` for this adapter. */
28
+ workspaceSchema: z.ZodSchema;
29
+ /** Long-running: ensure the underlying service is reachable. May spawn or just connect. */
30
+ ensureService?(profile: AgentProfile): Promise<AdapterService>;
31
+ /** Interactive: one-shot dispatch (spawn-per-task). */
32
+ dispatch?(profile: AgentProfile, task: TaskInput): Promise<TaskResult>;
33
+ /** Validate config before persisting `AgentProfile.config`. */
34
+ validate(config: unknown): ValidationResult;
35
+ /** Health probe — binary in PATH, HTTP endpoint reachable, etc. */
36
+ health(): Promise<HealthStatus>;
37
+ }
38
+ /**
39
+ * AgentProfile mirrors the cloud `im_agent_profiles` row.
40
+ *
41
+ * Field names match Track A's `IMAgentProfile` Prisma model + `AgentProfileDTO`
42
+ * in `src/im/api/agent-profiles.ts` (m1, merged in d5186ca / a4b3d89).
43
+ *
44
+ * Daemon-local SQLite mirror columns use snake_case
45
+ * (workspace_id / agent_im_user_id) per docs/refactor/04-daemon-runtime.md
46
+ * §SQLite schema; the TS types stay camelCase across the boundary.
47
+ */
48
+ interface AgentProfile {
49
+ id: string;
50
+ workspaceId: string;
51
+ agentImUserId: string;
52
+ adapterName: string;
53
+ name: string;
54
+ config: Record<string, unknown>;
55
+ version: number;
56
+ createdAt: Date;
57
+ updatedAt: Date;
58
+ /**
59
+ * IMUser.username of the agent bound to this profile. Used by long-running
60
+ * adapters (e.g. Hermes) to derive a stable, human-readable per-agent
61
+ * profile directory and to scope MCP env so per-call agent identity is not
62
+ * lost when multiple agents share a daemon. Parallel task F1 populates this
63
+ * on the cloud DTO; runtime treats it as optional to stay backwards-compat
64
+ * with snapshots produced before F1 lands.
65
+ */
66
+ agentUsername?: string;
67
+ }
68
+ interface TaskInput {
69
+ taskId: string;
70
+ prompt: string;
71
+ metadata?: Record<string, unknown>;
72
+ timeoutMs?: number;
73
+ onProgress?: (p: {
74
+ progress: number;
75
+ message?: string;
76
+ detail?: Record<string, unknown>;
77
+ }) => void;
78
+ signal?: AbortSignal;
79
+ }
80
+ interface TaskResult {
81
+ ok: boolean;
82
+ output?: string;
83
+ error?: {
84
+ code: string;
85
+ message: string;
86
+ };
87
+ artifacts?: Array<{
88
+ kind: string;
89
+ storageUri: string;
90
+ mime?: string;
91
+ size?: number;
92
+ }>;
93
+ metrics?: {
94
+ tokensUsed?: number;
95
+ durationMs?: number;
96
+ };
97
+ /** Adapter-private metadata used by daemon-side bridges/observability. */
98
+ metadata?: Record<string, unknown>;
99
+ }
100
+ /**
101
+ * Reusable handle to a long-running adapter's underlying service.
102
+ * For Hermes this wraps an HTTP client to `hermes gateway`; for OpenClaw
103
+ * (1.9.5+) it wraps an in-process TS worker.
104
+ */
105
+ interface AdapterService {
106
+ /** Service-specific identifier (e.g. 'http://127.0.0.1:8642' for hermes). */
107
+ id: string;
108
+ dispatch(task: TaskInput): Promise<TaskResult>;
109
+ healthy(): Promise<boolean>;
110
+ /** Optional: stop the service. May be no-op if user owns lifecycle. */
111
+ shutdown?(): Promise<void>;
112
+ /** Optional: subscribe to crash events. */
113
+ on?(event: 'crash', cb: (err: Error) => void): void;
114
+ }
115
+ interface ValidationResult {
116
+ ok: boolean;
117
+ errors?: string[];
118
+ }
119
+ interface HealthStatus {
120
+ available: boolean;
121
+ reason?: string;
122
+ hint?: string;
123
+ }
124
+
125
+ /**
126
+ * Process-wide adapter registry.
127
+ *
128
+ * Maps `adapter.name` → {@link AdapterDef}. Selection policy lives in
129
+ * `daemon/dispatch.ts`; the registry is just a typed Map.
130
+ *
131
+ * Shell cherry-picked (conceptually, not via cp) from
132
+ * `feat/release190-milestone:sdk/prismer-cloud/runtime/src/adapter-registry.ts`.
133
+ * The registered type was swapped `AdapterImpl` → `AdapterDef` per the
134
+ * 1.9.x adapter contract; `tiersSupported` (PARA tier) is dropped — 1.9.x
135
+ * uses capability tags only.
136
+ */
137
+ declare class AdapterRegistry {
138
+ private readonly adapters;
139
+ register(adapter: AdapterDef): void;
140
+ unregister(name: string): boolean;
141
+ has(name: string): boolean;
142
+ get(name: string): AdapterDef | undefined;
143
+ list(): AdapterDef[];
144
+ size(): number;
145
+ /**
146
+ * Find adapters that satisfy a capability tag.
147
+ *
148
+ * Match rules (deterministic, sorted by name):
149
+ * - exact: adapter declared the tag verbatim
150
+ * - wildcard: adapter declared `code.*` → matches `code.write`
151
+ */
152
+ findByCapability(capability: string): AdapterDef[];
153
+ }
154
+
155
+ declare const HermesProfileConfigSchema: z.ZodObject<{
156
+ /** Hermes profile name (default: AgentProfile.id slice). */
157
+ hermesProfileName: z.ZodOptional<z.ZodString>;
158
+ /** Hermes API server port. Distinct profiles use distinct ports to avoid clashes. */
159
+ port: z.ZodDefault<z.ZodNumber>;
160
+ /** Bearer token from `~/.hermes/.env API_SERVER_KEY`. */
161
+ apiKey: z.ZodString;
162
+ /** Auto-spawn `hermes -p <name> gateway run` if not reachable. Default false. */
163
+ autoStart: z.ZodDefault<z.ZodBoolean>;
164
+ /** Wait timeout when autoStart=true. */
165
+ startupTimeoutMs: z.ZodDefault<z.ZodNumber>;
166
+ /**
167
+ * Configure Hermes' inference provider to use Prismer Cloud's existing
168
+ * OpenAI-compatible provider endpoint before starting the gateway.
169
+ */
170
+ configurePrismerProvider: z.ZodDefault<z.ZodBoolean>;
171
+ /**
172
+ * Auto-install the @prismer/mcp-server stdio MCP server into the Hermes
173
+ * profile so the long-running role agent can drive Workspace tasks
174
+ * (create / update / approve / move) through tool calls. Default true.
175
+ */
176
+ installPrismerMcpServer: z.ZodDefault<z.ZodBoolean>;
177
+ /**
178
+ * Override the absolute path to @prismer/mcp-server's `dist/index.js`.
179
+ * If unset the adapter resolves it via Node's module resolver from the
180
+ * runtime install location, then falls back to PRISMER_MCP_SERVER env.
181
+ */
182
+ prismerMcpServerPath: z.ZodOptional<z.ZodString>;
183
+ /** Model sent to Prismer's /api/v1/chat/completions endpoint. */
184
+ model: z.ZodDefault<z.ZodString>;
185
+ /** Named custom provider written into Hermes config.yaml. */
186
+ prismerProviderName: z.ZodDefault<z.ZodString>;
187
+ /** Override cloud provider base. Defaults to PRISMER_BASE_URL + /api/v1. */
188
+ prismerProviderBaseUrl: z.ZodOptional<z.ZodString>;
189
+ /** Env key Hermes reads from profile .env for the Prismer API key. */
190
+ prismerApiKeyEnv: z.ZodDefault<z.ZodString>;
191
+ /**
192
+ * Mirror Prismer work_item projections into Hermes native Kanban as triage
193
+ * cards. Triage avoids duplicate execution; Prismer's agent_run remains the
194
+ * executable source of truth.
195
+ */
196
+ mirrorNativeKanban: z.ZodDefault<z.ZodBoolean>;
197
+ /**
198
+ * Mirror Prismer standing-objective IMTask projections into Hermes' native
199
+ * per-session GoalManager state (`state.db.state_meta["goal:<session_id>"]`).
200
+ * Hermes has no public goals REST/CLI surface; this writes the documented
201
+ * native state key directly and records the exact bridge result on the task.
202
+ */
203
+ mirrorNativeGoals: z.ZodDefault<z.ZodBoolean>;
204
+ /**
205
+ * Optional checkout path for Hermes Agent source. Used only as a fallback
206
+ * when the installed `hermes` binary is older than the native Kanban CLI
207
+ * surface but the local source tree contains hermes_cli/kanban_db.py.
208
+ */
209
+ hermesSourceDir: z.ZodOptional<z.ZodString>;
210
+ nativeMirrorTimeoutMs: z.ZodDefault<z.ZodNumber>;
211
+ }, "strip", z.ZodTypeAny, {
212
+ port: number;
213
+ apiKey: string;
214
+ autoStart: boolean;
215
+ startupTimeoutMs: number;
216
+ configurePrismerProvider: boolean;
217
+ installPrismerMcpServer: boolean;
218
+ model: string;
219
+ prismerProviderName: string;
220
+ prismerApiKeyEnv: string;
221
+ mirrorNativeKanban: boolean;
222
+ mirrorNativeGoals: boolean;
223
+ nativeMirrorTimeoutMs: number;
224
+ hermesProfileName?: string | undefined;
225
+ prismerMcpServerPath?: string | undefined;
226
+ prismerProviderBaseUrl?: string | undefined;
227
+ hermesSourceDir?: string | undefined;
228
+ }, {
229
+ apiKey: string;
230
+ hermesProfileName?: string | undefined;
231
+ port?: number | undefined;
232
+ autoStart?: boolean | undefined;
233
+ startupTimeoutMs?: number | undefined;
234
+ configurePrismerProvider?: boolean | undefined;
235
+ installPrismerMcpServer?: boolean | undefined;
236
+ prismerMcpServerPath?: string | undefined;
237
+ model?: string | undefined;
238
+ prismerProviderName?: string | undefined;
239
+ prismerProviderBaseUrl?: string | undefined;
240
+ prismerApiKeyEnv?: string | undefined;
241
+ mirrorNativeKanban?: boolean | undefined;
242
+ mirrorNativeGoals?: boolean | undefined;
243
+ hermesSourceDir?: string | undefined;
244
+ nativeMirrorTimeoutMs?: number | undefined;
245
+ }>;
246
+ type HermesProfileConfig = z.infer<typeof HermesProfileConfigSchema>;
247
+ declare const hermesAdapter: AdapterDef;
248
+
249
+ interface WsClientOptions {
250
+ /** Cloud WS URL, e.g. `wss://cloud.prismer.dev/ws` or `ws://127.0.0.1:3000/ws`. */
251
+ url: string;
252
+ /** API key passed as `?token=<apiKey>` (matches 1.7.x token query auth). */
253
+ apiKey: string;
254
+ /** Initial reconnect delay (ms). Defaults to 1000. */
255
+ reconnectInitialMs?: number;
256
+ /** Reconnect cap (ms). Defaults to 60_000. */
257
+ reconnectMaxMs?: number;
258
+ /** Max consecutive reconnect attempts before entering degraded mode. Defaults to 30. */
259
+ maxReconnectAttempts?: number;
260
+ /** How long to stay in degraded mode before retrying (ms). Defaults to 5 minutes. */
261
+ degradedCooldownMs?: number;
262
+ }
263
+ /** Subset of close codes we special-case. */
264
+ declare const WS_CLOSE: {
265
+ /** RFC 6455 normal closure — no reconnect. */
266
+ readonly NORMAL: 1000;
267
+ /** Custom auth failure — no auto-reconnect; surface to user. */
268
+ readonly AUTH: 4001;
269
+ };
270
+ /**
271
+ * Events emitted:
272
+ * - `open` ws successfully opened
273
+ * - `message` (msg) incoming JSON-parsed message
274
+ * - `close` (code, reason) connection closed (auto-reconnect happens after this)
275
+ * - `error` (err) transport error or invalid JSON received
276
+ * - `drop` (msg) send() called when not OPEN; caller may enqueue
277
+ * - `auth-failed` close code 4001 — user must rerun setup; no auto-reconnect
278
+ * - `degraded` max reconnect attempts hit; pausing for cooldown
279
+ * - `reconnect-scheduled` (ms) reconnect timer set
280
+ */
281
+ declare class WsClient extends EventEmitter {
282
+ private opts;
283
+ private ws?;
284
+ private reconnectMs;
285
+ private reconnectAttempts;
286
+ private closed;
287
+ private reconnectTimer?;
288
+ constructor(opts: WsClientOptions);
289
+ start(): void;
290
+ /** Send a JSON-serializable message. Caller is responsible for envelope construction. */
291
+ send(msg: unknown): void;
292
+ /** Close intentionally — no reconnect. */
293
+ close(code?: 1000): void;
294
+ isOpen(): boolean;
295
+ private connect;
296
+ private scheduleReconnect;
297
+ }
298
+
299
+ type LocalDb = Database.Database;
300
+ /**
301
+ * Open `~/.prismer/local.db` (or any path), enable WAL, run migrations.
302
+ * Idempotent — safe to call multiple times.
303
+ *
304
+ * Pass `:memory:` for in-test usage.
305
+ */
306
+ declare function openLocalDb(path: string): LocalDb;
307
+ declare function runMigrations(db: LocalDb): void;
308
+ declare function currentSchemaVersion(db: LocalDb): number;
309
+ declare const TARGET_SCHEMA_VERSION = 2;
310
+
311
+ type SyncResourceType = 'workspace' | 'agent' | 'agent_profile';
312
+ type SyncOperation = 'create' | 'update' | 'delete';
313
+ type SyncStatus = 'pending' | 'failed_conflict' | 'failed_other';
314
+ interface SyncQueueRow {
315
+ id: number;
316
+ resource_type: SyncResourceType;
317
+ resource_id: string;
318
+ operation: SyncOperation;
319
+ payload: string;
320
+ attempt_count: number;
321
+ last_attempt_at: number | null;
322
+ next_attempt_at: number;
323
+ status: SyncStatus;
324
+ }
325
+ declare function nextBackoffMs(attemptCount: number): number;
326
+ declare class SyncQueue {
327
+ private db;
328
+ constructor(db: LocalDb);
329
+ enqueue(input: {
330
+ resourceType: SyncResourceType;
331
+ resourceId: string;
332
+ operation: SyncOperation;
333
+ payload: unknown;
334
+ /** Defaults to now (ready immediately). */
335
+ runAt?: number;
336
+ }): number;
337
+ /** Take up to `limit` rows whose next_attempt_at <= now and status='pending'. Oldest first. */
338
+ dequeueBatch(limit: number, now?: number): SyncQueueRow[];
339
+ markCompleted(id: number): void;
340
+ markConflict(id: number): void;
341
+ /** Increment attempt + reschedule via backoff. Caller passes the current attempt_count. */
342
+ markBackoff(id: number, currentAttemptCount: number): void;
343
+ /** Permanent failure (4xx other than 409). */
344
+ markFailedOther(id: number): void;
345
+ pendingCount(): number;
346
+ /** All rows for a given resource (debugging / CLI status). */
347
+ listForResource(type: SyncResourceType, id: string): SyncQueueRow[];
348
+ }
349
+
350
+ interface FlushResult {
351
+ ok: boolean;
352
+ /** HTTP status when applicable. 409 → markConflict; 400-499 (other) → markFailedOther; else markBackoff. */
353
+ status?: number;
354
+ message?: string;
355
+ }
356
+ /** Caller injects the actual cloud HTTP push (m3). Scaffold uses a noop that returns ok:true. */
357
+ type FlushFn = (row: SyncQueueRow) => Promise<FlushResult>;
358
+ interface SyncWorkerOptions {
359
+ queue: SyncQueue;
360
+ flush: FlushFn;
361
+ /** Tick interval in ms. Defaults to 5000. */
362
+ tickMs?: number;
363
+ /** Max rows per tick. Defaults to 10. */
364
+ batchSize?: number;
365
+ }
366
+ /**
367
+ * Events:
368
+ * - `tick` ({ pending, processed }) each completed tick
369
+ * - `flushed` (row, result) one row push outcome
370
+ * - `error` (err, row?) unexpected error in flush()
371
+ */
372
+ declare class SyncWorker extends EventEmitter {
373
+ private opts;
374
+ private timer?;
375
+ private running;
376
+ constructor(opts: SyncWorkerOptions);
377
+ start(): void;
378
+ stop(): void;
379
+ /**
380
+ * Drain up to `batchSize` ready rows. Public for test injection.
381
+ * Re-entrant guarded — concurrent ticks return immediately.
382
+ */
383
+ tick(): Promise<{
384
+ processed: number;
385
+ }>;
386
+ private handleResult;
387
+ }
388
+
389
+ interface RoleTemplate {
390
+ templateName: string;
391
+ displayName: string;
392
+ description: string;
393
+ applicableAdapters: string[];
394
+ configSchema: Record<string, unknown>;
395
+ }
396
+ declare const BUILTIN_ROLE_TEMPLATES: ReadonlyArray<RoleTemplate>;
397
+ declare function getRoleTemplate(name: string): RoleTemplate | undefined;
398
+ declare function listRoleTemplates(): ReadonlyArray<{
399
+ name: string;
400
+ displayName: string;
401
+ description: string;
402
+ }>;
403
+
404
+ declare const ConfigSchema: z.ZodObject<{
405
+ /** API key from `prismer setup`, or env override. */
406
+ api_key: z.ZodString;
407
+ /** Cloud REST + WS base; `ws://` is derived by stripping `http`. */
408
+ cloud_api_base: z.ZodString;
409
+ /** Stable per-machine daemon identifier. Generated once on first setup. */
410
+ daemon_id: z.ZodString;
411
+ /** Optional adapter-specific overrides keyed by adapter name. */
412
+ adapters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
413
+ /** Local daemon shell execution. Default disabled. */
414
+ shell: z.ZodOptional<z.ZodObject<{
415
+ enabled: z.ZodDefault<z.ZodBoolean>;
416
+ default_cwd: z.ZodOptional<z.ZodString>;
417
+ defaultCwd: z.ZodOptional<z.ZodString>;
418
+ shell: z.ZodOptional<z.ZodEnum<["bash", "zsh", "sh"]>>;
419
+ max_timeout_ms: z.ZodOptional<z.ZodNumber>;
420
+ maxTimeoutMs: z.ZodOptional<z.ZodNumber>;
421
+ max_output_bytes: z.ZodOptional<z.ZodNumber>;
422
+ maxOutputBytes: z.ZodOptional<z.ZodNumber>;
423
+ allowed_workspaces: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
424
+ allowedWorkspaces: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
425
+ }, "strip", z.ZodTypeAny, {
426
+ enabled: boolean;
427
+ shell?: "bash" | "zsh" | "sh" | undefined;
428
+ default_cwd?: string | undefined;
429
+ defaultCwd?: string | undefined;
430
+ max_timeout_ms?: number | undefined;
431
+ maxTimeoutMs?: number | undefined;
432
+ max_output_bytes?: number | undefined;
433
+ maxOutputBytes?: number | undefined;
434
+ allowed_workspaces?: string[] | undefined;
435
+ allowedWorkspaces?: string[] | undefined;
436
+ }, {
437
+ shell?: "bash" | "zsh" | "sh" | undefined;
438
+ enabled?: boolean | undefined;
439
+ default_cwd?: string | undefined;
440
+ defaultCwd?: string | undefined;
441
+ max_timeout_ms?: number | undefined;
442
+ maxTimeoutMs?: number | undefined;
443
+ max_output_bytes?: number | undefined;
444
+ maxOutputBytes?: number | undefined;
445
+ allowed_workspaces?: string[] | undefined;
446
+ allowedWorkspaces?: string[] | undefined;
447
+ }>>;
448
+ /** Local cache settings. */
449
+ cache: z.ZodOptional<z.ZodObject<{
450
+ max_bytes: z.ZodDefault<z.ZodNumber>;
451
+ }, "strip", z.ZodTypeAny, {
452
+ max_bytes: number;
453
+ }, {
454
+ max_bytes?: number | undefined;
455
+ }>>;
456
+ }, "strip", z.ZodTypeAny, {
457
+ api_key: string;
458
+ cloud_api_base: string;
459
+ daemon_id: string;
460
+ shell?: {
461
+ enabled: boolean;
462
+ shell?: "bash" | "zsh" | "sh" | undefined;
463
+ default_cwd?: string | undefined;
464
+ defaultCwd?: string | undefined;
465
+ max_timeout_ms?: number | undefined;
466
+ maxTimeoutMs?: number | undefined;
467
+ max_output_bytes?: number | undefined;
468
+ maxOutputBytes?: number | undefined;
469
+ allowed_workspaces?: string[] | undefined;
470
+ allowedWorkspaces?: string[] | undefined;
471
+ } | undefined;
472
+ adapters?: Record<string, Record<string, unknown>> | undefined;
473
+ cache?: {
474
+ max_bytes: number;
475
+ } | undefined;
476
+ }, {
477
+ api_key: string;
478
+ cloud_api_base: string;
479
+ daemon_id: string;
480
+ shell?: {
481
+ shell?: "bash" | "zsh" | "sh" | undefined;
482
+ enabled?: boolean | undefined;
483
+ default_cwd?: string | undefined;
484
+ defaultCwd?: string | undefined;
485
+ max_timeout_ms?: number | undefined;
486
+ maxTimeoutMs?: number | undefined;
487
+ max_output_bytes?: number | undefined;
488
+ maxOutputBytes?: number | undefined;
489
+ allowed_workspaces?: string[] | undefined;
490
+ allowedWorkspaces?: string[] | undefined;
491
+ } | undefined;
492
+ adapters?: Record<string, Record<string, unknown>> | undefined;
493
+ cache?: {
494
+ max_bytes?: number | undefined;
495
+ } | undefined;
496
+ }>;
497
+ type Config = z.infer<typeof ConfigSchema>;
498
+ interface ConfigPaths {
499
+ /** Default `~/.prismer`. */
500
+ root: string;
501
+ configFile: string;
502
+ localDb: string;
503
+ cacheDir: string;
504
+ logsDir: string;
505
+ /**
506
+ * Per-task run scratch dirs. Each chat-mention dispatch creates
507
+ * `${runsDir}/${taskId}/_outbox/` for adapter-produced files; the
508
+ * OutboxWatcher uploads from there into IMAssets.
509
+ */
510
+ runsDir: string;
511
+ }
512
+ /**
513
+ * Resolve paths for the prismer home directory. Honors `PRISMER_HOME` env var;
514
+ * defaults to `~/.prismer`.
515
+ */
516
+ declare function resolvePaths(home?: string): ConfigPaths;
517
+ declare function configExists(paths?: ConfigPaths): boolean;
518
+ /**
519
+ * Read and validate config. Env vars override file values for `api_key` and
520
+ * `cloud_api_base` (handy in CI / dev without rewriting config.toml).
521
+ */
522
+ declare function loadConfig(paths?: ConfigPaths): Config;
523
+ declare function saveConfig(config: Config, paths?: ConfigPaths): void;
524
+ /** Derive a `wss?://` URL from a `https?://` base. Trailing `/ws` appended. */
525
+ declare function deriveWsUrl(httpBase: string): string;
526
+
527
+ declare function newDaemonId(): string;
528
+ declare function isDaemonId(s: string): boolean;
529
+
530
+ interface CloudClientOptions {
531
+ baseUrl: string;
532
+ apiKey: string;
533
+ /** Per-request timeout (ms). Defaults to 30s. */
534
+ defaultTimeoutMs?: number;
535
+ /** Pluggable fetch (tests inject a stub). */
536
+ fetchImpl?: typeof fetch;
537
+ }
538
+ interface CloudResponse<T> {
539
+ ok: boolean;
540
+ status: number;
541
+ data?: T;
542
+ error?: {
543
+ code: string;
544
+ message: string;
545
+ };
546
+ }
547
+ declare class CloudClient {
548
+ private readonly opts;
549
+ private readonly fetchImpl;
550
+ constructor(opts: CloudClientOptions);
551
+ /** Low-level request. Caller decides on retry; sync-worker handles backoff. */
552
+ request<T = unknown>(method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE', path: string, init?: {
553
+ body?: unknown;
554
+ headers?: Record<string, string>;
555
+ signal?: AbortSignal;
556
+ timeoutMs?: number;
557
+ auth?: boolean;
558
+ }): Promise<CloudResponse<T>>;
559
+ /** Convenience: GET expecting `{ ok, data }` envelope. Returns `data` or throws. */
560
+ get<T>(path: string, opts?: {
561
+ signal?: AbortSignal;
562
+ }): Promise<T>;
563
+ urlFor(path: string): string;
564
+ /** Raw byte download (for asset cache). Returns response so caller can stream body. */
565
+ fetchRaw(path: string, init?: {
566
+ headers?: Record<string, string>;
567
+ signal?: AbortSignal;
568
+ timeoutMs?: number;
569
+ }): Promise<Response>;
570
+ }
571
+ declare class CloudError extends Error {
572
+ readonly status: number;
573
+ readonly code: string;
574
+ constructor(status: number, code: string, message: string);
575
+ }
576
+
577
+ interface Envelope<T = unknown> {
578
+ type: string;
579
+ payload: T;
580
+ requestId?: string;
581
+ timestamp: number;
582
+ }
583
+ declare function envelope<T>(type: string, payload: T, requestId?: string): Envelope<T>;
584
+
585
+ interface CachedAsset {
586
+ contentHash: string;
587
+ sizeBytes: number;
588
+ mime: string | null;
589
+ localPath: string;
590
+ fetchedAt: number;
591
+ lastUsedAt: number;
592
+ pin: boolean;
593
+ }
594
+ interface AssetCacheOptions {
595
+ db: LocalDb;
596
+ cloud: CloudClient;
597
+ /** ~/.prismer/cache. */
598
+ cacheDir: string;
599
+ /** LRU cap in bytes. Default 5 GiB. */
600
+ maxBytes?: number;
601
+ }
602
+ /**
603
+ * Local asset cache.
604
+ *
605
+ * `getOrFetch(hash, opts)` returns the local path; downloads from
606
+ * `GET /api/im/assets/by-hash/:hash?wsId=…` on miss. Pinned rows survive LRU.
607
+ */
608
+ declare class AssetCache {
609
+ private readonly db;
610
+ private readonly cloud;
611
+ private readonly cacheDir;
612
+ private readonly maxBytes;
613
+ constructor(opts: AssetCacheOptions);
614
+ /** Local file path for a content hash. Files split by first 2 chars for fs scaling. */
615
+ pathFor(hash: string): string;
616
+ get(hash: string): CachedAsset | undefined;
617
+ /** Bump `last_used_at` (call when an asset is consulted). */
618
+ touch(hash: string): void;
619
+ pin(hash: string): void;
620
+ unpin(hash: string): void;
621
+ /**
622
+ * Get or fetch by hash. On miss, downloads the raw bytes and caches them.
623
+ *
624
+ * Why two endpoints: `GET /api/im/assets/by-hash/:hash` returns the JSON
625
+ * `AssetDTO` (id, contentHash, storageUri, …) — *metadata only*. The raw
626
+ * bytes live at `GET /api/im/assets/:id`. If `opts.assetId` is supplied
627
+ * the caller already has the id from an `AssetRef`, so we go straight to
628
+ * the byte endpoint; otherwise we resolve `hash → id` first (one extra
629
+ * round-trip on the metadata route) before downloading the body.
630
+ *
631
+ * Earlier this method called `by-hash` and treated the JSON body as the
632
+ * asset content — the daemon stored the 500-byte metadata blob on disk
633
+ * and `resolveAssetRefs` inlined that JSON into the prompt. The agent
634
+ * saw `{"id":"…","storageUri":"…"}` instead of the user's file and
635
+ * concluded the upload had failed.
636
+ */
637
+ getOrFetch(hash: string, opts?: {
638
+ workspaceIdHint?: string;
639
+ signal?: AbortSignal;
640
+ assetId?: string;
641
+ }): Promise<CachedAsset>;
642
+ /** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
643
+ registerLocal(hash: string, localPath: string, mime?: string): CachedAsset;
644
+ /** Total bytes across all rows (incl. pinned). */
645
+ totalBytes(): number;
646
+ /** Run LRU eviction until under cap. Pinned rows are never evicted. */
647
+ evictIfOver(): {
648
+ removed: number;
649
+ freed: number;
650
+ };
651
+ }
652
+
653
+ interface WorkspaceFileBinding {
654
+ workspaceId: string;
655
+ path: string;
656
+ assetId: string;
657
+ contentHash: string;
658
+ version: number;
659
+ syncedAt: number | null;
660
+ dirty: boolean;
661
+ }
662
+ interface WorkspaceMirrorOptions {
663
+ db: LocalDb;
664
+ cloud: CloudClient;
665
+ workspaceId: string;
666
+ /**
667
+ * Per-workspace state dir. Caller should pass ~/.prismer/<wid>/ so different
668
+ * paired workspaces don't race on the cursor file.
669
+ */
670
+ workspaceStateDir: string;
671
+ }
672
+ /**
673
+ * Mirror reads cloud-side path→asset bindings into SQLite for fast local
674
+ * lookup. The cursor file is the only piece of state that must survive
675
+ * daemon restart; the SQLite mirror is rebuilt from scratch by the very
676
+ * first pullDelta() with no cursor.
677
+ */
678
+ declare class WorkspaceMirror {
679
+ private readonly db;
680
+ private readonly cloud;
681
+ private readonly workspaceId;
682
+ private readonly cursorPath;
683
+ constructor(opts: WorkspaceMirrorOptions);
684
+ /** Persisted cursor for this workspace, or null on first run / corrupted file. */
685
+ readCursor(): string | null;
686
+ private writeCursor;
687
+ /**
688
+ * Pull workspace_file changes since the persisted cursor and upsert into
689
+ * the local mirror. Soft-deleted rows are removed from the mirror; active
690
+ * rows are upserted with synced_at = now and dirty = 0.
691
+ *
692
+ * Returns the count of items applied + the new cursor (may be unchanged
693
+ * when the cloud has nothing new). Idempotent — safe to retry.
694
+ */
695
+ pullDelta(opts?: {
696
+ signal?: AbortSignal;
697
+ }): Promise<{
698
+ applied: number;
699
+ cursor: string | null;
700
+ }>;
701
+ /** Lookup the active binding for a workspace path. Returns undefined if not mirrored. */
702
+ lookupByPath(path: string): WorkspaceFileBinding | undefined;
703
+ /** Find an asset's contentHash via any path binding (asset may map to many paths). */
704
+ hashForAssetId(assetId: string): string | undefined;
705
+ /** All active bindings for this workspace — used by status / debug surfaces. */
706
+ list(): WorkspaceFileBinding[];
707
+ /** Wipe the mirror for this workspace (e.g., re-pair). Cursor file is left intact. */
708
+ clear(): void;
709
+ }
710
+
711
+ interface ParseClaim {
712
+ id: string;
713
+ workspaceId: string;
714
+ assetId: string;
715
+ ingestVersion: number;
716
+ claimantImUserId: string;
717
+ claimantDeviceId: string;
718
+ status: 'active' | 'completed' | 'abandoned';
719
+ claimedAt: string;
720
+ heartbeatAt: string;
721
+ completedAt: string | null;
722
+ }
723
+ type AcquireResult = {
724
+ kind: 'acquired';
725
+ claim: ParseClaim;
726
+ } | {
727
+ kind: 'refreshed';
728
+ claim: ParseClaim;
729
+ } | {
730
+ kind: 'claimed_active';
731
+ current: ParseClaim;
732
+ } | {
733
+ kind: 'already_complete';
734
+ current: ParseClaim;
735
+ };
736
+ type ClaimLostReason = 'claim_taken_over' | 'claim_inactive';
737
+ interface ParseClaimControllerOptions {
738
+ cloud: CloudClient;
739
+ /** Daemon's persisted device id. Same value across heartbeats / completions. */
740
+ deviceId: string;
741
+ /** Heartbeat interval (ms). Default 10s — well below the 30s server TTL. */
742
+ heartbeatIntervalMs?: number;
743
+ }
744
+ interface HeartbeatHandle {
745
+ stop: () => void;
746
+ /**
747
+ * Symbol.dispose so consumers can write `using hb = ctrl.startHeartbeat(...)`
748
+ * once they're on TypeScript 5.2 + Node 22 explicit-resource-management.
749
+ * Without that runtime support the field is harmless dead weight; the
750
+ * explicit `stop()` is the universal contract.
751
+ */
752
+ [Symbol.dispose]: () => void;
753
+ }
754
+ /**
755
+ * Coordinates a single daemon's asset-parse claims. Owns no global state —
756
+ * callers may run multiple controllers if they share the same device id.
757
+ */
758
+ declare class ParseClaimController {
759
+ private readonly cloud;
760
+ private readonly deviceId;
761
+ private readonly heartbeatIntervalMs;
762
+ constructor(opts: ParseClaimControllerOptions);
763
+ /**
764
+ * Acquire (or refresh) a claim. The return value is a 4-variant tagged
765
+ * union — callers must branch on `kind`. Throws on transport / 5xx / 4xx
766
+ * codes that aren't part of the documented contract (e.g. `claim_race`
767
+ * is mapped to a thrown Error so the worker can retry).
768
+ */
769
+ acquire(input: {
770
+ assetId: string;
771
+ ingestVersion: number;
772
+ }): Promise<AcquireResult>;
773
+ /**
774
+ * Refresh the heartbeat for an active claim. Throws `ClaimLostError` on
775
+ * HTTP 410 with code `claim_taken_over` or `claim_inactive` — caller
776
+ * should abort the parse work. Throws plain `Error` on any other failure.
777
+ */
778
+ sendHeartbeat(claimId: string): Promise<ParseClaim>;
779
+ /**
780
+ * Mark a claim complete. Throws `ClaimLostError` on 410 (claim was
781
+ * stolen before the worker finished — output should be discarded).
782
+ */
783
+ complete(claimId: string): Promise<ParseClaim>;
784
+ /**
785
+ * Start a heartbeat loop in the background. Returns a handle with `stop()`
786
+ * and a Symbol.dispose alias for the `using` pattern.
787
+ *
788
+ * On 410 (claim taken over or marked inactive) the loop stops itself and
789
+ * invokes `opts.onLost` so the parse worker can abort. Other transient
790
+ * errors (network blip, 5xx) are logged to stderr and the loop retries on
791
+ * the next tick — the server-side TTL (default 30s) will only drop us if
792
+ * the partition outlasts the TTL.
793
+ */
794
+ startHeartbeat(claimId: string, opts?: {
795
+ onLost?: (reason: ClaimLostReason) => void;
796
+ }): HeartbeatHandle;
797
+ }
798
+
799
+ type PrismerUriType = 'asset' | 'file';
800
+ interface ParsedPrismerUri {
801
+ /** Original full URI string. */
802
+ raw: string;
803
+ /**
804
+ * Owner segment for legacy `prismer://<owner>/...` form (informational,
805
+ * not ACL-checked). For `prismer://workspace/<wid>/...` this is the
806
+ * literal string 'workspace' — prefer `workspaceId` instead.
807
+ */
808
+ owner: string;
809
+ type: PrismerUriType;
810
+ /** For type='asset': the sha256 hash. For legacy file URIs: '<wsId>/<path>'. */
811
+ rest: string;
812
+ /** Convenience parses for type='file' and for workspace-form 'asset'. */
813
+ workspaceId?: string;
814
+ filePath?: string;
815
+ }
816
+ /**
817
+ * Find every recognized `prismer://...` reference in a string.
818
+ * Only `asset` and `file` types are recognized — other types (memory,
819
+ * context, conversation, evolution, task, pair) pass through untouched.
820
+ */
821
+ declare function parseUris(text: string): ParsedPrismerUri[];
822
+ interface UriResolverOptions {
823
+ db: LocalDb;
824
+ cloud: CloudClient;
825
+ assetCache: AssetCache;
826
+ }
827
+ declare class UriResolver {
828
+ private readonly db;
829
+ private readonly cloud;
830
+ private readonly assetCache;
831
+ constructor(opts: UriResolverOptions);
832
+ /**
833
+ * Resolve a single URI to a local file path. Throws on auth/4xx;
834
+ * caller decides whether to leave the URI in place (best-effort) or fail.
835
+ */
836
+ resolveOne(uri: ParsedPrismerUri, opts?: {
837
+ signal?: AbortSignal;
838
+ }): Promise<string>;
839
+ /**
840
+ * Walk a string, replace every `prismer://(asset|file)/...` with `file://<localPath>`.
841
+ * Unrecognized URIs pass through. Returns rewritten text + the list of pinned hashes.
842
+ */
843
+ rewrite(text: string, opts?: {
844
+ pin?: boolean;
845
+ signal?: AbortSignal;
846
+ }): Promise<{
847
+ text: string;
848
+ resolvedHashes: string[];
849
+ }>;
850
+ /** Bulk-rewrite an array of strings (e.g. context entries' content). */
851
+ rewriteAll(texts: string[], opts?: {
852
+ pin?: boolean;
853
+ signal?: AbortSignal;
854
+ }): Promise<{
855
+ texts: string[];
856
+ resolvedHashes: string[];
857
+ }>;
858
+ private lookupFile;
859
+ }
860
+
861
+ type IMAgentStatus = 'online' | 'busy' | 'idle' | 'offline';
862
+ interface HostedAgentDeclaration {
863
+ imUserId: string;
864
+ name: string;
865
+ adapterName: string;
866
+ capabilities: string[];
867
+ profiles: Array<{
868
+ id: string;
869
+ version: number;
870
+ }>;
871
+ }
872
+ interface AgentHostDeclarePayload {
873
+ daemonId: string;
874
+ daemonVersion: string;
875
+ platform: 'darwin' | 'linux' | 'win32';
876
+ agents: HostedAgentDeclaration[];
877
+ }
878
+ interface HostAckedPayload {
879
+ workspaceId: string;
880
+ syncCursor: {
881
+ workspaces: number;
882
+ agent_profiles: number;
883
+ [key: string]: number;
884
+ };
885
+ profilesToSync: string[];
886
+ }
887
+ interface AgentStatusChangedPayload {
888
+ agentImUserId: string;
889
+ status: IMAgentStatus;
890
+ activeProfileId?: string;
891
+ runningTaskIds?: string[];
892
+ }
893
+ interface TaskDispatchContextEntry {
894
+ sender: string;
895
+ senderRole: 'human' | 'agent' | 'admin' | 'system';
896
+ content: string;
897
+ createdAt: string;
898
+ /** Wave-8 W1: assets the human attached to THIS chat message. */
899
+ attachedAssetIds?: string[];
900
+ }
901
+ /** Wave-8 W1: hydrated asset reference attached to a dispatch. */
902
+ interface AssetRef {
903
+ assetId: string;
904
+ contentHash: string;
905
+ mime: string | null;
906
+ sizeBytes: number | null;
907
+ kind: string;
908
+ workspaceId: string;
909
+ role: 'attachment' | 'context';
910
+ }
911
+ interface TaskDispatchRequestPayload {
912
+ taskId: string;
913
+ /** Agent target for runtimeRoute='agent'. Shell dispatches do not use this. */
914
+ agentImUserId?: string;
915
+ /** Runtime/device target for runtimeRoute='shell'. */
916
+ targetDaemonId?: string;
917
+ profileId: string;
918
+ capability: string;
919
+ prompt: string;
920
+ /** Execution surface. `shell` is daemon-local command execution. */
921
+ runtimeRoute?: 'agent' | 'sandbox' | 'shell';
922
+ metadata?: Record<string, unknown>;
923
+ timeoutMs?: number;
924
+ context?: TaskDispatchContextEntry[];
925
+ conversationId?: string;
926
+ /**
927
+ * Channel mode for the originating conversation. Optional and
928
+ * forward-compatible: when missing, daemon renders 'unknown' in the
929
+ * [Channel context] prompt block. See appendChannelContext in
930
+ * daemon/dispatch.ts.
931
+ * - `direct`: 1:1 DM (no @-mention needed in reply).
932
+ * - `group`: multi-party room (end reply with `@<recipient>` to
933
+ * continue the chain).
934
+ */
935
+ conversationType?: 'direct' | 'group';
936
+ /**
937
+ * Active participants of the dispatch's conversation. Daemon injects this
938
+ * into [Channel context] so the agent knows the authoritative recipient list
939
+ * without hallucinating or having to call `prismer.conversation.listAgents`. Capped at 50
940
+ * entries server-side.
941
+ */
942
+ participants?: Array<{
943
+ imUserId: string;
944
+ username: string;
945
+ displayName: string;
946
+ role: string;
947
+ agentType?: string | null;
948
+ }>;
949
+ /** Wave-8 W1: assets cloud wants daemon to fold into agent context. */
950
+ assetRefs?: AssetRef[];
951
+ }
952
+ interface TaskDispatchProgressPayload {
953
+ taskId: string;
954
+ progress: number;
955
+ message?: string;
956
+ detail?: Record<string, unknown>;
957
+ }
958
+ /** Wave-8 W1: how the daemon handled a single AssetRef. */
959
+ type AssetDispatchStrategy = 'inline-text' | 'inline-text-truncated' | 'uri-only' | 'error';
960
+ interface AssetDispatchObservation {
961
+ assetId: string;
962
+ contentHash: string;
963
+ mime: string | null;
964
+ sizeBytes: number | null;
965
+ strategy: AssetDispatchStrategy;
966
+ inlinedBytes?: number;
967
+ error?: string;
968
+ }
969
+ interface TaskDispatchReplyPayload {
970
+ taskId: string;
971
+ ok: boolean;
972
+ output?: string;
973
+ error?: {
974
+ code: string;
975
+ message: string;
976
+ };
977
+ assetIds?: string[];
978
+ metrics?: {
979
+ tokensUsed?: number;
980
+ durationMs?: number;
981
+ };
982
+ /** Wave-8 W1: per-asset handling report. */
983
+ assetObservability?: AssetDispatchObservation[];
984
+ }
985
+ interface TaskCancelPayload {
986
+ taskId: string;
987
+ reason?: string;
988
+ }
989
+ interface IMWSMessage<T = unknown> {
990
+ type: string;
991
+ payload: T;
992
+ requestId?: string;
993
+ timestamp: number;
994
+ }
995
+ interface WorkspaceChangedPayload {
996
+ workspaceId: string;
997
+ /** ISO-8601 timestamp from im_workspaces.updatedAt. */
998
+ updatedAt: string;
999
+ }
1000
+ interface AgentProfileChangedPayload {
1001
+ profileId: string;
1002
+ version: number;
1003
+ }
1004
+ interface AgentChangedPayload {
1005
+ agentImUserId: string;
1006
+ fields: {
1007
+ displayName?: string;
1008
+ capabilities?: string[];
1009
+ };
1010
+ }
1011
+ interface WorkspaceFileChangedPayload {
1012
+ workspaceId: string;
1013
+ path: string;
1014
+ operation: 'create' | 'update' | 'delete';
1015
+ assetId?: string;
1016
+ contentHash?: string;
1017
+ version: number;
1018
+ }
1019
+
1020
+ interface OutboxWatcherOptions {
1021
+ /**
1022
+ * Default directory to watch when no setActiveTask has narrowed it.
1023
+ * Container mode: `/workspace/_outbox`. Host mode: omit (no default
1024
+ * scan — only watch when an active task points at a specific dir).
1025
+ */
1026
+ outboxDir?: string;
1027
+ /** Cloud client (already configured with apiKey + baseUrl). */
1028
+ cloud: CloudClient;
1029
+ /**
1030
+ * Container identifier — required by the cloud's kind=sandbox-output
1031
+ * contract as metadata.containerId. Read from env PRISMER_CONTAINER_ID
1032
+ * when controller injected one; otherwise the daemon_id as fallback so
1033
+ * the request still passes validation. Host-mode (kind=agent-output)
1034
+ * uploads bypass containerId validation.
1035
+ */
1036
+ containerId: string;
1037
+ /**
1038
+ * Resolver for the owning workspace at upload time. Returns null when no
1039
+ * workspace context is yet known — the watcher will skip the file and
1040
+ * retry on the next scan tick.
1041
+ */
1042
+ workspaceId: () => string | null;
1043
+ /** Poll interval in ms. Default 2000. */
1044
+ pollIntervalMs?: number;
1045
+ /** Optional log sink (default writes to stdout/stderr). */
1046
+ log?: {
1047
+ info: (msg: string) => void;
1048
+ warn: (msg: string) => void;
1049
+ };
1050
+ }
1051
+ interface ActiveTask {
1052
+ taskId: string;
1053
+ /** Adapter name; surfaces in upload metadata for observability. */
1054
+ adapter?: string;
1055
+ /**
1056
+ * Per-task outbox directory. When set, scans this directory in
1057
+ * addition to `opts.outboxDir`. Files uploaded from here are tagged
1058
+ * with `kind=agent-output` (host-mode contract).
1059
+ */
1060
+ outboxDir?: string;
1061
+ }
1062
+ declare class OutboxWatcher {
1063
+ private opts;
1064
+ private timer?;
1065
+ private uploaded;
1066
+ /**
1067
+ * Host-mode tasks keyed by taskId. Each entry has its own `outboxDir`
1068
+ * and the watcher scans every entry's directory each tick. Concurrent
1069
+ * dispatches (Wave-9: orchestrator + spawned worker on the same daemon)
1070
+ * each occupy their own slot — finishing one dispatch only removes its
1071
+ * slot, leaving every other concurrent dispatch's tracking intact.
1072
+ *
1073
+ * Replaces the v1 single `activeTask` slot which was last-wins and
1074
+ * caused cross-dispatch clobbering: an orchestrator finishing first
1075
+ * would `setActiveTask(null)` and silently discard the concurrent
1076
+ * worker's outbox tracking.
1077
+ */
1078
+ private activeTasks;
1079
+ /**
1080
+ * Legacy container/sandbox task. Used only when an `ActiveTask` is set
1081
+ * **without** an `outboxDir` (i.e. the legacy `setActiveTask` shape
1082
+ * where the watcher's default `opts.outboxDir` is the scan target).
1083
+ * Single slot because the legacy container deployment only ever runs
1084
+ * one task at a time per container.
1085
+ */
1086
+ private legacyContainerTask;
1087
+ private busy;
1088
+ /**
1089
+ * Per-task accumulated assetIds. Populated on each successful upload
1090
+ * keyed by the task whose outbox dir the file came from.
1091
+ * `flushPending(taskId)` drains and returns this list so dispatch.ts
1092
+ * can stamp it onto task.dispatch.reply.assetIds.
1093
+ */
1094
+ private pendingByTask;
1095
+ constructor(opts: OutboxWatcherOptions);
1096
+ /**
1097
+ * Register a host-mode task envelope. Each call adds (or replaces) an
1098
+ * entry keyed by `task.taskId` so concurrent dispatches do not clobber
1099
+ * each other.
1100
+ *
1101
+ * Requires `task.outboxDir` to be set — the per-task workdir is the
1102
+ * isolation boundary. For the legacy container/sandbox single-slot
1103
+ * shape (no per-task dir), use `setActiveTask` instead.
1104
+ */
1105
+ addActiveTask(task: ActiveTask): void;
1106
+ /**
1107
+ * Remove a host-mode task entry by id. No-op if the id was not
1108
+ * registered. Idempotent — dispatch.ts calls this in `finally{}` after
1109
+ * `flushPending` so a failed dispatch still clears its slot.
1110
+ */
1111
+ removeActiveTask(taskId: string): void;
1112
+ /**
1113
+ * Legacy single-slot API.
1114
+ *
1115
+ * - Non-null with `outboxDir` → forwards to `addActiveTask` (host
1116
+ * mode). The caller is still responsible for `removeActiveTask`
1117
+ * (or another `setActiveTask(null)`) at teardown.
1118
+ * - Non-null **without** `outboxDir` → store in the legacy container
1119
+ * slot. Uploads from `opts.outboxDir` are tagged with this task.
1120
+ * - `null` → clear the legacy container slot only. Does NOT touch
1121
+ * the host-mode `activeTasks` map; concurrent host-mode dispatches
1122
+ * keep their own slots until they call `removeActiveTask`
1123
+ * themselves.
1124
+ *
1125
+ * The null-clears-only-legacy semantic is the core fix for the
1126
+ * Wave-9 concurrency race: previously a finishing orchestrator's
1127
+ * `setActiveTask(null)` would clobber a still-running worker's
1128
+ * tracking, dropping its uploads on the floor.
1129
+ */
1130
+ setActiveTask(task: ActiveTask | null): void;
1131
+ /**
1132
+ * Drain and return the assetIds collected for `taskId` since the last
1133
+ * flush. Idempotent — calling twice with the same id returns `[]` the
1134
+ * second time. dispatch.ts calls this once at reply-build time.
1135
+ */
1136
+ flushPending(taskId: string): string[];
1137
+ start(): void;
1138
+ stop(): void;
1139
+ /**
1140
+ * Force a synchronous scan — useful for tests + e2e harnesses that want to
1141
+ * trigger upload after writing a file rather than wait for the next tick.
1142
+ * Also used by dispatch.ts at reply-build time to drain any pending files
1143
+ * before flushPending() is called.
1144
+ */
1145
+ scanNow(): Promise<void>;
1146
+ private log;
1147
+ /**
1148
+ * Build the list of directories to scan this tick — one entry per
1149
+ * concurrently-active task plus the legacy container slot (if any).
1150
+ * Each entry carries its own `task` envelope so uploads can be tagged
1151
+ * and accumulated per-task without sharing global mutable state.
1152
+ *
1153
+ * Dedup so a directory configured in both spots only scans once
1154
+ * (legacy single-task case where opts.outboxDir == legacyTask.outboxDir
1155
+ * is unlikely but cheap to handle).
1156
+ */
1157
+ private currentScanDirs;
1158
+ private tick;
1159
+ private scanDir;
1160
+ private upload;
1161
+ }
1162
+
1163
+ interface DispatchDeps {
1164
+ registry: AdapterRegistry;
1165
+ cloud: CloudClient;
1166
+ uriResolver: UriResolver;
1167
+ assetCache: AssetCache;
1168
+ ws: WsClient;
1169
+ /** Cap on total chars of joined context history. Default 8000. */
1170
+ contextMaxChars?: number;
1171
+ /** Resolves a long-running adapter's service handle (cached across calls). */
1172
+ ensureService: (profile: AgentProfile, adapter: AdapterDef) => Promise<AdapterService>;
1173
+ /** Aborts the in-flight adapter dispatch when cloud sends task.cancel. */
1174
+ signal?: AbortSignal;
1175
+ /** Hook for tests / observability. */
1176
+ onProgress?: (taskId: string, p: TaskDispatchProgressPayload) => void;
1177
+ /**
1178
+ * Outbox uploader. When provided, dispatch creates a per-task outbox
1179
+ * directory under `paths.runsDir`, points the watcher at it via
1180
+ * setActiveTask, and drains its assetIds into reply.assetIds at flush
1181
+ * time. Optional so tests can omit it.
1182
+ */
1183
+ outboxWatcher?: OutboxWatcher;
1184
+ /** Daemon paths — used to derive the per-task outbox dir. */
1185
+ paths?: ConfigPaths;
1186
+ }
1187
+ declare function handleDispatch(payload: TaskDispatchRequestPayload, requestId: string | undefined, deps: DispatchDeps): Promise<TaskDispatchReplyPayload>;
1188
+ /**
1189
+ * Concatenate context entries + current prompt. Trims oldest entries first when
1190
+ * total chars exceed `maxChars` (matches Track C's `trimContextWindow` behavior).
1191
+ *
1192
+ * `assetBlocks` (Wave-8 W1) is prepended above the conversation history. They
1193
+ * are *not* counted against `maxChars` — assets are content the user
1194
+ * explicitly attached, so dropping them silently when the chat history
1195
+ * happens to be long would defeat the whole point of the attachment.
1196
+ */
1197
+ declare function composePrompt(currentPrompt: string, context: TaskDispatchContextEntry[], maxChars: number, assetBlocks?: string[]): string;
1198
+
1199
+ declare class ServicePool {
1200
+ private readonly services;
1201
+ /**
1202
+ * Returns a service handle for the profile, lazily calling `adapter.ensureService`
1203
+ * on first use or after a crash. Re-checks `healthy()` before returning a cached entry.
1204
+ */
1205
+ ensureService(profile: AgentProfile, adapter: AdapterDef): Promise<AdapterService>;
1206
+ /** Drop a service handle (e.g. on profile delete or daemon shutdown). */
1207
+ drop(profileId: string): Promise<void>;
1208
+ /** Drop all handles, e.g. on graceful shutdown. */
1209
+ shutdown(): Promise<void>;
1210
+ size(): number;
1211
+ }
1212
+
1213
+ interface LocalServerOptions {
1214
+ port: number;
1215
+ /** Snapshot getter — runner provides current state on demand. */
1216
+ getState: () => LocalServerState;
1217
+ /**
1218
+ * Optional dispatch sink called when POST /v1/runs receives a task envelope
1219
+ * from the sandbox controller. Phase 1 contract: the sink is fire-and-forget;
1220
+ * daemon acks immediately with `{ runId, status: 'accepted' }`. Cloud-side
1221
+ * task lifecycle (status → done/failed) is closed asynchronously by the
1222
+ * daemon's WS upstream channel in S4 follow-up. The runner provides this
1223
+ * sink; tests may pass a stub.
1224
+ */
1225
+ onDispatch?: (payload: DispatchPayload, runId: string) => void;
1226
+ /**
1227
+ * Install or update one hosted agent/profile in the daemon's local mirror.
1228
+ * The runner persists it, reloads hostedAgents, and redeclares to cloud.
1229
+ */
1230
+ onInstallAgent?: (payload: InstallAgentPayload) => Promise<InstallAgentResult>;
1231
+ /**
1232
+ * Filesystem root for snapshot manifests. Default `/workspace`. POST
1233
+ * /v1/snapshot walks this tree, computes per-file sha256 + size + mtime,
1234
+ * and returns the manifest. Cloud-side persistence (POST
1235
+ * /api/sandboxes/:id/snapshot/manifest) is a separate step in Phase 1.
1236
+ */
1237
+ snapshotRoot?: string;
1238
+ /**
1239
+ * Optional first-pass handler for `/local/memory/*` routes. Returns true
1240
+ * if the request was handled (response written); false to let the normal
1241
+ * route table fall through. Wired by the daemon runner from
1242
+ * `attachMemoryRpc()` in `daemon/memory/rpc.ts` when memory is enabled.
1243
+ * When set, /healthz reports `memoryReady: true`.
1244
+ */
1245
+ attachMemory?: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
1246
+ /**
1247
+ * Optional handler for `POST /local/asset/write` — agent-gen adapter
1248
+ * (daemon/asset/origin/agent-gen.ts). Synchronous: agent posts bytes,
1249
+ * daemon uploads to cloud, returns prismer:// URI in one round-trip.
1250
+ * Sink receives the parsed JSON body and returns a structured result.
1251
+ */
1252
+ onAssetWrite?: (body: unknown) => Promise<AssetWriteHandlerResult>;
1253
+ }
1254
+ interface AssetWriteHandlerResult {
1255
+ status: 200 | 400 | 502;
1256
+ body: unknown;
1257
+ }
1258
+ /**
1259
+ * Dispatch envelope received from the sandbox controller proxy. The schema
1260
+ * mirrors `DaemonDispatchSchema` in `infra/sandbox-controller/src/api/v1/
1261
+ * containers.ts` — kept permissive so the controller can pass through any
1262
+ * future fields without a daemon-side bump.
1263
+ *
1264
+ * Phase 1 escape hatch: when `shellCommand` is present, the daemon spawns
1265
+ * it via `bash -c` with cwd=/workspace as a fire-and-forget subprocess.
1266
+ * This bridges the gap until S4 wires the WS-mediated adapter dispatch +
1267
+ * completion path. Production traffic does NOT set shellCommand — task
1268
+ * service forwards it only when present in IMTask.metadata.shellCommand
1269
+ * (a test escape hatch).
1270
+ */
1271
+ interface DispatchPayload {
1272
+ taskId: string;
1273
+ adapter?: string;
1274
+ prompt?: string;
1275
+ env?: Record<string, string>;
1276
+ shellCommand?: string;
1277
+ [k: string]: unknown;
1278
+ }
1279
+ interface InstallAgentPayload {
1280
+ workspaceId: string;
1281
+ imUserId: string;
1282
+ name: string;
1283
+ adapterName: string;
1284
+ capabilities: string[];
1285
+ profile: {
1286
+ id: string;
1287
+ name: string;
1288
+ adapterName: string;
1289
+ config: Record<string, unknown>;
1290
+ version: number;
1291
+ };
1292
+ }
1293
+ interface InstallAgentResult {
1294
+ ok: true;
1295
+ daemonId: string;
1296
+ installedAgent: {
1297
+ imUserId: string;
1298
+ name: string;
1299
+ adapterName: string;
1300
+ profileId: string;
1301
+ };
1302
+ hostedAgents: Array<{
1303
+ imUserId: string;
1304
+ name: string;
1305
+ adapterName: string;
1306
+ }>;
1307
+ }
1308
+ interface LocalServerState {
1309
+ daemonId: string;
1310
+ cloudBaseUrl?: string;
1311
+ workspaceId?: string | null;
1312
+ pid: number;
1313
+ startedAt: number;
1314
+ wsConnected: boolean;
1315
+ hostedAgents: Array<{
1316
+ imUserId: string;
1317
+ name: string;
1318
+ adapterName: string;
1319
+ }>;
1320
+ runningTaskIds: string[];
1321
+ observability?: {
1322
+ adapters?: Record<string, unknown>;
1323
+ lastTaskError?: {
1324
+ taskId: string;
1325
+ message: string;
1326
+ at: string;
1327
+ };
1328
+ };
1329
+ }
1330
+ declare class LocalServer {
1331
+ private opts;
1332
+ private server?;
1333
+ constructor(opts: LocalServerOptions);
1334
+ start(): Promise<void>;
1335
+ stop(): Promise<void>;
1336
+ private route;
1337
+ private routeStandard;
1338
+ /**
1339
+ * POST /v1/agents/install — cloud/controller installs one hosted agent on
1340
+ * this daemon. The payload is intentionally explicit and idempotent: the
1341
+ * runner upserts local `agents` and `agent_profiles` rows, reloads the
1342
+ * in-memory declaration set, then sends `agent.host.declare` immediately.
1343
+ */
1344
+ private handleInstallAgent;
1345
+ /**
1346
+ * POST /v1/snapshot — daemon-first FS manifest snapshot.
1347
+ *
1348
+ * Walks `snapshotRoot` (default `/workspace`), computes per-file
1349
+ * sha256 + sizeBytes + mtime, returns `{ files: [...], rootPath }`.
1350
+ * Skips dotfiles + the `_outbox/_uploaded` reserved subdir. Phase 1
1351
+ * caller (cloud or controller) is responsible for POSTing the result
1352
+ * to `/api/sandboxes/:id/snapshot/manifest`.
1353
+ */
1354
+ private handleSnapshot;
1355
+ /**
1356
+ * POST /local/asset/write — agent-gen adapter RPC.
1357
+ *
1358
+ * Body: { workspaceId, bytes (base64), filename, mime?, path?, description?, sourceAgentImUserId? }
1359
+ * Response 200: { assetId, contentHash, prismerUri }
1360
+ * 400 on validation failure; 502 on cloud upstream failure; 501 if no sink wired.
1361
+ */
1362
+ private handleAssetWrite;
1363
+ /**
1364
+ * POST /v1/runs — dispatch ack-only (Cloud 3 S3 Phase 1).
1365
+ *
1366
+ * The sandbox controller resolves the pod IP and POSTs a task envelope here.
1367
+ * Phase 1 just acknowledges receipt and forwards the payload to onDispatch
1368
+ * (the runner). Actual task execution + cloud-side completion are wired in
1369
+ * S4 (daemon WS upstream emits task.dispatch.reply once the adapter finishes).
1370
+ *
1371
+ * Response on success: 202 + `{ runId, status: 'accepted' }`.
1372
+ * Bad JSON / missing taskId: 400. Internal error: 500.
1373
+ */
1374
+ private handleDispatch;
1375
+ }
1376
+
1377
+ interface RunnerOptions {
1378
+ /** Override config path; defaults to ~/.prismer. */
1379
+ paths?: ConfigPaths;
1380
+ /** Skip starting the local 127.0.0.1 server (useful in tests). */
1381
+ startLocalServer?: boolean;
1382
+ /** Local server port; defaults to 3210. */
1383
+ localPort?: number;
1384
+ /** Override daemonVersion reported in agent.host.declare. */
1385
+ daemonVersion?: string;
1386
+ /** Pre-loaded config (skip filesystem read). */
1387
+ configOverride?: Config;
1388
+ /** Override built-in adapter list (tests). */
1389
+ adaptersOverride?: AdapterDef[];
1390
+ }
1391
+ declare class Runner extends EventEmitter {
1392
+ private opts;
1393
+ private config;
1394
+ private paths;
1395
+ private db;
1396
+ private cloud;
1397
+ private ws;
1398
+ private syncWorker;
1399
+ private syncQueue;
1400
+ private assetCache;
1401
+ private uriResolver;
1402
+ private registry;
1403
+ private servicePool;
1404
+ private shellConfig;
1405
+ private localServer?;
1406
+ private outboxWatcher?;
1407
+ private memoryWiring?;
1408
+ private state;
1409
+ private startedAt;
1410
+ private workspaceId;
1411
+ private wsConnected;
1412
+ private readonly hostedAgents;
1413
+ private readonly runningTasks;
1414
+ private lastTaskError?;
1415
+ private heartbeatTimer?;
1416
+ private taskReaperTimer?;
1417
+ constructor(opts?: RunnerOptions);
1418
+ start(): Promise<void>;
1419
+ stop(): Promise<void>;
1420
+ isRunning(): boolean;
1421
+ /**
1422
+ * Phase 1 escape hatch — see `DispatchPayload.shellCommand`. Spawns
1423
+ * `bash -c <cmd>` with cwd=/workspace, mirrors output to pod logs.
1424
+ * Fire-and-forget; nothing here writes back to cloud.
1425
+ */
1426
+ private runShellCommand;
1427
+ snapshotState(): LocalServerState;
1428
+ /**
1429
+ * Re-read the local `agents` table (populated by `prismer agent register`)
1430
+ * and re-populate `hostedAgents` map. Profiles per agent come from local
1431
+ * `agent_profiles` table (filled by host.acked sync).
1432
+ */
1433
+ loadAgentsFromDb(): void;
1434
+ /**
1435
+ * Register an in-process AgentProfile snapshot (called by agent CLI / sync layer).
1436
+ * Used to populate the `agents` list in agent.host.declare.
1437
+ */
1438
+ setHostedAgent(agent: {
1439
+ imUserId: string;
1440
+ name: string;
1441
+ adapterName: string;
1442
+ capabilities: string[];
1443
+ profiles: Array<{
1444
+ id: string;
1445
+ version: number;
1446
+ }>;
1447
+ }): void;
1448
+ private installHostedAgent;
1449
+ private installStaticHostedAgentFromEnv;
1450
+ private wireWsHandlers;
1451
+ private sendDeclare;
1452
+ private handleIncoming;
1453
+ private onHostAcked;
1454
+ private onTaskDispatch;
1455
+ private onTaskCancel;
1456
+ private onAgentChanged;
1457
+ private onAgentProfileChanged;
1458
+ private syncProfileFromCloud;
1459
+ private resolveOwnedAgent;
1460
+ private onWorkspaceChanged;
1461
+ private onWorkspaceFileChanged;
1462
+ private snapshotAdapterObservability;
1463
+ /**
1464
+ * SyncWorker FlushFn — pushes local writes to cloud.
1465
+ *
1466
+ * Maps:
1467
+ * workspace → PATCH /api/im/workspaces/:id
1468
+ * agent_profile → PATCH /api/im/agent_profiles/:id
1469
+ * agent → PATCH /api/im/agents/:imUserId
1470
+ * On 'create' we POST instead. On 'delete' we DELETE.
1471
+ */
1472
+ /**
1473
+ * SyncWorker FlushFn — pushes one local sync row to cloud via CloudClient.
1474
+ *
1475
+ * resource_type × operation → endpoint:
1476
+ * workspace.create → POST /api/im/workspaces
1477
+ * workspace.update → PATCH /api/im/workspaces/:id
1478
+ * workspace.delete → DELETE /api/im/workspaces/:id
1479
+ * agent.create → POST /api/im/register (the only public path
1480
+ * that creates an
1481
+ * IMUser of role='agent')
1482
+ * agent.update → PATCH /api/im/agents/:id
1483
+ * agent.delete → DELETE /api/im/agents/:id
1484
+ * agent_profile.create → POST /api/im/agent_profiles
1485
+ * agent_profile.update → PATCH /api/im/agent_profiles/:id
1486
+ * agent_profile.delete → DELETE /api/im/agent_profiles/:id
1487
+ *
1488
+ * Error classification per docs/refactor/13-error-handling.md §2.1 / §2.7:
1489
+ * 2xx → ok:true (SyncWorker drops the row)
1490
+ * 408 / 429 → retryable (SyncWorker re-queues with exponential backoff)
1491
+ * 5xx / net err → retryable
1492
+ * 4xx (other) → permanent (SyncWorker marks failed; 409 is conflict)
1493
+ */
1494
+ private flushSyncRow;
1495
+ }
1496
+
1497
+ interface PairOptions {
1498
+ cloudBaseUrl: string;
1499
+ /** Human-readable label visible to the approver in mobile UI. */
1500
+ deviceName?: string;
1501
+ /** Override config save path (tests). */
1502
+ paths?: ConfigPaths;
1503
+ /** Number of poll attempts (5 s each). Default 60 → 5 min. */
1504
+ maxPollAttempts?: number;
1505
+ /** Pluggable fetch (tests). */
1506
+ fetchImpl?: typeof fetch;
1507
+ /** Allow overwriting existing config. Default false. */
1508
+ force?: boolean;
1509
+ /** Hook for test capture of the QR URL. */
1510
+ onQrReady?: (qrUrl: string) => void;
1511
+ /**
1512
+ * LAN-dev bypass: skip QR + mobile approval and auto-approve the offer
1513
+ * as the named human IMUser. Requires `LOCAL_ONLY=1` in this process AND
1514
+ * cloud-side `LOCAL_ONLY=1 && NODE_ENV !== 'production'`. Without those
1515
+ * env conditions the cloud returns 403 and we surface the error.
1516
+ */
1517
+ asUserEmail?: string;
1518
+ /** Override LOCAL_ONLY env read (tests). */
1519
+ isLocalOnly?: () => boolean;
1520
+ /** Override poll wait (tests). Default 5_000ms. */
1521
+ pollIntervalMs?: number;
1522
+ }
1523
+ interface PairResult {
1524
+ config: Config;
1525
+ paths: ConfigPaths;
1526
+ }
1527
+ declare function pair(opts: PairOptions): Promise<PairResult>;
1528
+
1529
+ declare const CCConfigSchema: z.ZodObject<{
1530
+ cwd: z.ZodString;
1531
+ model: z.ZodDefault<z.ZodString>;
1532
+ systemPrompt: z.ZodOptional<z.ZodString>;
1533
+ envVars: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1534
+ mcpServers: z.ZodOptional<z.ZodArray<z.ZodObject<{
1535
+ name: z.ZodString;
1536
+ command: z.ZodString;
1537
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1538
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1539
+ }, "strip", z.ZodTypeAny, {
1540
+ name: string;
1541
+ command: string;
1542
+ env?: Record<string, string> | undefined;
1543
+ args?: string[] | undefined;
1544
+ }, {
1545
+ name: string;
1546
+ command: string;
1547
+ env?: Record<string, string> | undefined;
1548
+ args?: string[] | undefined;
1549
+ }>, "many">>;
1550
+ allowedTools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1551
+ maxTurns: z.ZodDefault<z.ZodNumber>;
1552
+ baseURL: z.ZodOptional<z.ZodString>;
1553
+ apiKeyRef: z.ZodOptional<z.ZodString>;
1554
+ route: z.ZodDefault<z.ZodEnum<["default", "prismer", "omniroute"]>>;
1555
+ }, "strip", z.ZodTypeAny, {
1556
+ model: string;
1557
+ cwd: string;
1558
+ maxTurns: number;
1559
+ route: "default" | "prismer" | "omniroute";
1560
+ systemPrompt?: string | undefined;
1561
+ allowedTools?: string[] | undefined;
1562
+ envVars?: Record<string, string> | undefined;
1563
+ mcpServers?: {
1564
+ name: string;
1565
+ command: string;
1566
+ env?: Record<string, string> | undefined;
1567
+ args?: string[] | undefined;
1568
+ }[] | undefined;
1569
+ baseURL?: string | undefined;
1570
+ apiKeyRef?: string | undefined;
1571
+ }, {
1572
+ cwd: string;
1573
+ model?: string | undefined;
1574
+ systemPrompt?: string | undefined;
1575
+ allowedTools?: string[] | undefined;
1576
+ envVars?: Record<string, string> | undefined;
1577
+ mcpServers?: {
1578
+ name: string;
1579
+ command: string;
1580
+ env?: Record<string, string> | undefined;
1581
+ args?: string[] | undefined;
1582
+ }[] | undefined;
1583
+ maxTurns?: number | undefined;
1584
+ baseURL?: string | undefined;
1585
+ apiKeyRef?: string | undefined;
1586
+ route?: "default" | "prismer" | "omniroute" | undefined;
1587
+ }>;
1588
+ type ClaudeCodeConfig = z.infer<typeof CCConfigSchema>;
1589
+ declare const claudeCodeAdapter: AdapterDef;
1590
+
1591
+ declare const CodexConfigSchema: z.ZodObject<{
1592
+ /** Working directory for the codex subprocess. */
1593
+ cwd: z.ZodString;
1594
+ /**
1595
+ * Model identifier passed to --model (e.g. 'codex-mini-latest', 'o4-mini').
1596
+ * Default matches the Codex CLI built-in default as of 2026-05.
1597
+ */
1598
+ model: z.ZodDefault<z.ZodString>;
1599
+ /**
1600
+ * Sandbox level for the codex subprocess.
1601
+ * read-only — analysis only; code-gen tasks will stall
1602
+ * workspace-write — write inside cwd, read-only outside (recommended)
1603
+ * danger-full-access — no restrictions; safe only inside isolated containers
1604
+ */
1605
+ sandbox: z.ZodDefault<z.ZodEnum<["read-only", "workspace-write", "danger-full-access"]>>;
1606
+ /** Optional system prompt prepended to the user prompt as a preamble (see U4). */
1607
+ systemPrompt: z.ZodOptional<z.ZodString>;
1608
+ /** Extra env vars merged into the codex subprocess environment. */
1609
+ envVars: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1610
+ /**
1611
+ * Name of the env var holding the OpenAI API key. Defaults to
1612
+ * 'OPENAI_API_KEY' (Codex CLI default). Override if using a workspace-
1613
+ * scoped key under a different name.
1614
+ */
1615
+ apiKeyEnv: z.ZodDefault<z.ZodString>;
1616
+ }, "strip", z.ZodTypeAny, {
1617
+ model: string;
1618
+ sandbox: "read-only" | "workspace-write" | "danger-full-access";
1619
+ cwd: string;
1620
+ apiKeyEnv: string;
1621
+ systemPrompt?: string | undefined;
1622
+ envVars?: Record<string, string> | undefined;
1623
+ }, {
1624
+ cwd: string;
1625
+ model?: string | undefined;
1626
+ systemPrompt?: string | undefined;
1627
+ sandbox?: "read-only" | "workspace-write" | "danger-full-access" | undefined;
1628
+ envVars?: Record<string, string> | undefined;
1629
+ apiKeyEnv?: string | undefined;
1630
+ }>;
1631
+ type CodexConfig = z.infer<typeof CodexConfigSchema>;
1632
+ /**
1633
+ * Parse `codex exec --json` stdout. The CLI emits newline-delimited JSON
1634
+ * lines on stdout; we look for the final-message line and extract its
1635
+ * content. Tolerant: any parse failure falls back to raw stdout.
1636
+ */
1637
+ declare function parseCodexOutput(stdout: string): string;
1638
+ declare const codexAdapter: AdapterDef;
1639
+
1640
+ declare function buildProgram(): Command;
1641
+ declare function runCli(argv?: string[]): Promise<void>;
1642
+
1643
+ export { type AcquireResult, type AdapterDef, type AdapterKind, AdapterRegistry, type AdapterService, type AgentChangedPayload, type AgentHostDeclarePayload, type AgentProfile, type AgentProfileChangedPayload, type AgentStatusChangedPayload, AssetCache, type AssetCacheOptions, type AssetDispatchObservation, type AssetDispatchStrategy, type AssetRef, BUILTIN_ROLE_TEMPLATES, type CachedAsset, type ClaudeCodeConfig, CloudClient, type CloudClientOptions, CloudError, type CloudResponse, type CodexConfig, type Config, type ConfigPaths, ConfigSchema, type DispatchDeps, type Envelope, type FlushFn, type FlushResult, type HealthStatus, type HermesProfileConfig, type HostAckedPayload, type HostedAgentDeclaration, type IMAgentStatus, type IMWSMessage, type LocalDb, LocalServer, type LocalServerOptions, type LocalServerState, type PairOptions, type PairResult, type ParseClaim, ParseClaimController, type ParseClaimControllerOptions, type ParsedPrismerUri, type PrismerUriType, type RoleTemplate, Runner, type RunnerOptions, ServicePool, type SyncOperation, SyncQueue, type SyncQueueRow, type SyncResourceType, type SyncStatus, SyncWorker, type SyncWorkerOptions, TARGET_SCHEMA_VERSION, type TaskCancelPayload, type TaskDispatchContextEntry, type TaskDispatchProgressPayload, type TaskDispatchReplyPayload, type TaskDispatchRequestPayload, type TaskInput, type TaskResult, UriResolver, type UriResolverOptions, type ValidationResult, WS_CLOSE, type WorkspaceChangedPayload, type WorkspaceFileBinding, type WorkspaceFileChangedPayload, WorkspaceMirror, type WorkspaceMirrorOptions, WsClient, type WsClientOptions, buildProgram, claudeCodeAdapter, codexAdapter, composePrompt, configExists, currentSchemaVersion, deriveWsUrl, envelope, getRoleTemplate, handleDispatch, hermesAdapter, isDaemonId, listRoleTemplates, loadConfig, newDaemonId, nextBackoffMs, openLocalDb, pair, parseCodexOutput, parseUris, resolvePaths, runCli, runMigrations, saveConfig };