@jini-ai/daemon 0.5.2 → 0.5.4

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 (78) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/agent-executor/contracts.d.ts +26 -20
  3. package/dist/agent-executor/contracts.js +0 -9
  4. package/dist/agent-executor/event-translation.d.ts +2 -2
  5. package/dist/agent-executor/event-translation.js +1 -2
  6. package/dist/agent-executor/events.d.ts +9 -4
  7. package/dist/agent-executor/events.js +29 -7
  8. package/dist/agent-executor/handoff.d.ts +1 -1
  9. package/dist/agent-executor/handoff.js +0 -1
  10. package/dist/agent-executor/launch.d.ts +1 -1
  11. package/dist/agent-executor/launch.js +24 -1
  12. package/dist/agent-executor/mcp-bridge.d.ts +2 -2
  13. package/dist/agent-executor/mcp-bridge.js +0 -2
  14. package/dist/agent-executor/model-receipts.d.ts +6 -0
  15. package/dist/agent-executor/model-receipts.js +26 -0
  16. package/dist/agent-executor.d.ts +8 -1663
  17. package/dist/agent-executor.js +8 -3005
  18. package/dist/attachment-content.d.ts +45 -0
  19. package/dist/attachment-content.js +40 -0
  20. package/dist/core/read-only-tools.d.ts +130 -0
  21. package/dist/core/read-only-tools.js +115 -0
  22. package/dist/core/tool-audit.d.ts +131 -0
  23. package/dist/core/tool-audit.js +113 -0
  24. package/dist/http/active-context.d.ts +2 -2
  25. package/dist/http/agents.d.ts +8 -2
  26. package/dist/http/agents.js +6 -2
  27. package/dist/http/attachments.d.ts +16 -7
  28. package/dist/http/attachments.js +3 -2
  29. package/dist/http/component-catalog.d.ts +2 -2
  30. package/dist/http/connectors.d.ts +17 -17
  31. package/dist/http/daemon-status.d.ts +2 -2
  32. package/dist/http/db-ops.d.ts +3 -3
  33. package/dist/http/delegated-tools.d.ts +1 -1
  34. package/dist/http/delegated-tools.js +1 -1
  35. package/dist/http/frontend-sessions.d.ts +1 -1
  36. package/dist/http/health.d.ts +6 -6
  37. package/dist/http/host-tools.d.ts +2 -2
  38. package/dist/http/media.d.ts +4 -4
  39. package/dist/http/memory.d.ts +15 -15
  40. package/dist/http/remote-run-events.d.ts +2 -2
  41. package/dist/http/research.d.ts +1 -1
  42. package/dist/http/routines.d.ts +7 -7
  43. package/dist/http/run-credentials.d.ts +2 -2
  44. package/dist/http/run-credentials.js +2 -2
  45. package/dist/http/runs.d.ts +4 -4
  46. package/dist/http/terminals.d.ts +6 -6
  47. package/dist/http/tool-catalog.d.ts +2 -2
  48. package/dist/http/xai.d.ts +6 -6
  49. package/dist/index.d.ts +76 -571
  50. package/dist/index.js +76 -571
  51. package/dist/read-only-tools.d.ts +1 -129
  52. package/dist/read-only-tools.js +2 -114
  53. package/dist/run-credentials.d.ts +47 -23
  54. package/dist/run-credentials.js +24 -8
  55. package/dist/run-lifecycle.d.ts +1 -1
  56. package/dist/run-lifecycle.js +6 -5
  57. package/dist/scheduler.d.ts +5 -0
  58. package/dist/scheduler.js +13 -1
  59. package/dist/session-coordination.d.ts +58 -24
  60. package/dist/session-coordination.js +40 -12
  61. package/dist/store/event-log/sqlite.js +3 -3
  62. package/dist/surface-exchanges.d.ts +207 -61
  63. package/dist/surface-exchanges.js +167 -20
  64. package/dist/tool-audit.d.ts +1 -113
  65. package/dist/tool-audit.js +2 -94
  66. package/dist/tool-recovery/diagnostics.d.ts +132 -0
  67. package/dist/tool-recovery/diagnostics.js +110 -0
  68. package/dist/tool-recovery/federated-refusal.d.ts +92 -0
  69. package/dist/tool-recovery/federated-refusal.js +152 -0
  70. package/dist/tool-recovery/index.d.ts +6 -0
  71. package/dist/tool-recovery/index.js +6 -0
  72. package/dist/tool-recovery/recovery.d.ts +49 -0
  73. package/dist/tool-recovery/recovery.js +419 -0
  74. package/dist/tool-recovery/redaction.d.ts +166 -0
  75. package/dist/tool-recovery/redaction.js +192 -0
  76. package/dist/tool-recovery/stack.d.ts +69 -0
  77. package/dist/tool-recovery/stack.js +62 -0
  78. package/package.json +35 -12
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.5.4 — pending release
2
+
3
+ - Re-export the canonical core surface-exchange contracts and parameter constants. Store and ask/report behavior are unchanged; requires core 0.4.2.
4
+
5
+ ## 0.5.3 — 2026-10-06
6
+
7
+ - Message attachments reach every runtime: new `prepareMessageAttachments` / `messageContentWithImages` (+ `MessageAttachmentImage`, `MessageAttachmentSource`, `MessageAttachmentReader`) turn claimed refs into image pixels for providers and a file notice for non-images.
8
+ - `AttachmentStore.listPendingForOwner({ ownerId }, { runId })`: with the claiming `runId`, that run's own claimed attachments stay discoverable (owner match still required); without it, behavior is unchanged.
9
+ - The agent executor reports the CLI session id on an early `status` event so hosts can checkpoint a run before it ends; run lifecycle carries it through.
10
+ - Requires `@jini-ai/protocol` ^0.4.1 and `@jini-ai/agent-runtime` ^0.4.1.
11
+
1
12
  ## 0.5.2 — 2026-10-06
2
13
 
3
14
  - No slow-run notice while a delegated tool waits on a surface: the first `emitSurface` of a call suspends the notice and the call's end resumes it. Suspensions are counted, so with two calls in flight the first to finish does not re-arm the notice over the other's open form; `resume()` drops suspensions left by an ended run.
@@ -1,12 +1,13 @@
1
- import { type spawn as nodeSpawn, type ChildProcess } from 'node:child_process';
2
- import { type Principal } from '@jini-ai/core';
3
- import { type RunAgentPayload, type RunErrorPayload } from '@jini-ai/protocol';
4
- import { type applyAgentLaunchEnv, type ensureAgentCapabilities, type getAgentDef, type resolveAgentLaunch, type attachAcpSession, type attachPiRpcSession, type prepareAgentLogFile, type preparePromptFileForAgent, type AcpMcpServerInput, type AcpPermissionHandler, type PromptAugmenter } from '@jini-ai/agent-runtime';
5
- import { type createCommandInvocation, type listProcessSnapshots, type ProcessSnapshot, type StopProcessesResult } from '@jini-ai/platform';
6
- import { type RunByteJournal } from '../continuation/journal.js';
7
- import { type RunRetrySideEffectState } from '../run/core/index.js';
8
- import { type ToolExecutor } from '../tool-executor.js';
9
- import { type RunLifecycle } from '../run-lifecycle.js';
1
+ import type { MessageAttachmentImage } from '../attachment-content.js';
2
+ import type { spawn as nodeSpawn, ChildProcess } from 'node:child_process';
3
+ import type { Principal } from '@jini-ai/core';
4
+ import type { RunAgentPayload, RunErrorPayload } from '@jini-ai/protocol';
5
+ import type { applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, prepareAgentLogFile, preparePromptFileForAgent, AcpMcpServerInput, AcpPermissionHandler, PromptAugmenter } from '@jini-ai/agent-runtime';
6
+ import type { createCommandInvocation, listProcessSnapshots, ProcessSnapshot, StopProcessesResult } from '@jini-ai/platform';
7
+ import type { RunByteJournal } from '../continuation/journal.js';
8
+ import type { RunRetrySideEffectState } from '../run/core/index.js';
9
+ import type { ToolExecutor } from '../tool-executor.js';
10
+ import type { RunLifecycle } from '../run-lifecycle.js';
10
11
  /**
11
12
  * A parsed, loosely-typed event as produced by one of `@jini-ai/agent-runtime`'s
12
13
  * four stream-parser factories — each parser's `onEvent` callback receives
@@ -55,15 +56,13 @@ export type AgentExecutorCompatibility = {
55
56
  * produce plus defensively malformed/non-record input.
56
57
  *
57
58
  * `'agent'`'s optional `sessionId` (gap 5, session resume — see
58
- * `RunEndPayload.sessionRef`'s doc in `@jini-ai/protocol`) is a daemon-internal
59
- * side channel, not part of the `RunAgentPayload` wire payload itself:
60
- * OpenCode's `sessionID`/Codex's `thread_id`/Qoder's and Claude's
61
- * `session_id` all arrive on a `'status'` event alongside fields
62
- * `RunAgentPayload`'s `'status'` variant already models (`label`/`model`/
63
- * `ttftMs`/`detail`) but has no room for a session id itself — surfacing it
64
- * here lets a lifecycle-wiring function capture it into a local variable and
65
- * thread it into its own terminal `finish()` call, without widening the
66
- * public wire protocol just to carry a value that only this module reads.
59
+ * `RunEndPayload.sessionRef`'s doc in `@jini-ai/protocol`) remains a daemon-internal
60
+ * side channel for lifecycle wiring to capture the locator and thread it into
61
+ * its terminal `finish()` call. The `'status'` wire payload also carries
62
+ * `sessionId` so durable hosts can persist it immediately, before a process
63
+ * crash can prevent the terminal `end` event from arriving. OpenCode's
64
+ * `sessionID`/Codex's `thread_id`/Qoder's and Claude's `session_id` are normalized
65
+ * by their stream parsers into this shared locator.
67
66
  *
68
67
  * `'turn-end'`'s optional `stopReason` (gap 3, capability-routed
69
68
  * continuation transport) is the same kind of internal side channel: the
@@ -91,7 +90,7 @@ export type AgentRuntimeEventTranslation = {
91
90
  readonly kind: 'ignored';
92
91
  };
93
92
  /** Machine-readable failure reasons `run()` can reject with — every one is preceded by a `lifecycle.finish({status:'failed'})` call (see index.ts module doc's Invariant section). */
94
- export type AgentExecutorErrorCode = 'AGENT_PERMISSION_MODE_INVALID' | 'AGENT_NOT_FOUND' | 'AGENT_RUNTIME_UNSUPPORTED' | 'AGENT_BINARY_NOT_RESOLVED' | 'AGENT_SPAWN_FAILED' | 'AGENT_PROMPT_TOO_LARGE';
93
+ export type AgentExecutorErrorCode = 'AGENT_MODEL_UNRESOLVED' | 'AGENT_PERMISSION_MODE_INVALID' | 'AGENT_NOT_FOUND' | 'AGENT_RUNTIME_UNSUPPORTED' | 'AGENT_BINARY_NOT_RESOLVED' | 'AGENT_SPAWN_FAILED' | 'AGENT_PROMPT_TOO_LARGE';
95
94
  /** Thrown by `AgentExecutor.run()` on every failure path — never a bare `Error`, so callers can branch on `.code` instead of parsing `.message`. */
96
95
  export declare class AgentExecutorError extends Error {
97
96
  readonly code: AgentExecutorErrorCode;
@@ -129,6 +128,8 @@ export interface AgentExecutorRunInput {
129
128
  * the top of `run()` in `launch.ts`.
130
129
  */
131
130
  readonly imagePaths?: readonly string[];
131
+ /** Host-prepared pixels from the shared attachment reader, for structured message delivery. */
132
+ readonly imageContents?: readonly MessageAttachmentImage[];
132
133
  /** Additional host-validated directories the runtime may read. */
133
134
  readonly extraAllowedDirs?: readonly string[];
134
135
  /** Trusted root that must contain pi-rpc image paths after realpath resolution. */
@@ -192,7 +193,7 @@ export interface AgentExecutor {
192
193
  * underlying run is always already transitioned to `'failed'` via
193
194
  * `lifecycle.finish()` before this rejects (see index.ts module doc's Invariant).
194
195
  */
195
- run(requiredArgs: Pick<AgentExecutorRunInput, "runId" | "agentId" | "prompt" | "cwd">, optionalArgs?: Pick<AgentExecutorRunInput, "model" | "reasoning" | "permissionMode" | "imagePaths" | "extraAllowedDirs" | "uploadRoot" | "credentialEnv" | "env" | "resumeSessionId" | "newSessionId" | "disallowedTools" | "allowedTools" | "settingSources" | "settings">): Promise<void>;
196
+ run(requiredArgs: Pick<AgentExecutorRunInput, "runId" | "agentId" | "prompt" | "cwd">, optionalArgs?: Pick<AgentExecutorRunInput, "model" | "reasoning" | "permissionMode" | "imagePaths" | "imageContents" | "extraAllowedDirs" | "uploadRoot" | "credentialEnv" | "env" | "resumeSessionId" | "newSessionId" | "disallowedTools" | "allowedTools" | "settingSources" | "settings">): Promise<void>;
196
197
  }
197
198
  /** Process discovery is a getter; process collection and stopping accept explicit host inputs. */
198
199
  export type CollectProcessTreePidsPort = (args: {
@@ -230,6 +231,9 @@ export interface AgentCleanupFailureContext {
230
231
  }
231
232
  /** A small handle `writePromptToStdin` uses to close stdin exactly once, shared with the `turn_end`-triggered close inside {@link wireChildLifecycle}. */
232
233
  export interface StdinCloseHandle {
234
+ /** Prepared pixels ride on the initial stdin message without reopening filesystem tools or
235
+ * serializing base64 into text. Native argv/ACP/pi transports keep their existing image path. */
236
+ imageContents?: readonly MessageAttachmentImage[];
233
237
  closeStdinOnce(): void;
234
238
  /** Journals a sent-to-stdin byte chunk, queued through the same FIFO {@link wireChildLifecycle} already uses for emitted events. No-op when no journal was configured (see `CreateAgentExecutorOptions.journal`). */
235
239
  recordSentBytes(content: string): void;
@@ -539,6 +543,8 @@ export interface FailureClassificationContext {
539
543
  export type ClassifyFailure = (context: FailureClassificationContext) => boolean | Promise<boolean>;
540
544
  export interface CreateAgentExecutorOptions {
541
545
  readonly lifecycle: RunLifecycle;
546
+ /** Resolves/pins the concrete starting model against the actual launch scope. */
547
+ readonly resolveModelForLaunch?: typeof resolveModelForLaunch;
542
548
  /** @default the real `@jini-ai/agent-runtime` registry lookup */
543
549
  readonly getAgentDef?: typeof getAgentDef;
544
550
  /** @default the real `@jini-ai/agent-runtime` launch resolver */
@@ -1,12 +1,3 @@
1
- import {} from 'node:child_process';
2
- import {} from '@jini-ai/core';
3
- import {} from '@jini-ai/protocol';
4
- import {} from '@jini-ai/agent-runtime';
5
- import {} from '@jini-ai/platform';
6
- import {} from '../continuation/journal.js';
7
- import {} from '../run/core/index.js';
8
- import {} from '../tool-executor.js';
9
- import {} from '../run-lifecycle.js';
10
1
  export const SUPPORTED_STREAM_FORMATS = [
11
2
  'claude-stream-json',
12
3
  'json-event-stream',
@@ -1,5 +1,5 @@
1
- import { type RunAgentPayload, type RunErrorPayload } from '@jini-ai/protocol';
2
- import { type AgentRuntimeEventTranslation } from './contracts.js';
1
+ import type { RunAgentPayload, RunErrorPayload } from '@jini-ai/protocol';
2
+ import type { AgentRuntimeEventTranslation } from './contracts.js';
3
3
  /**
4
4
  * Narrows a parsed `usage` event's `usage` sub-object (`{input_tokens?, output_tokens?}`) — the one
5
5
  * piece of {@link translateUsagePayload} with real nested branching (an optional container holding
@@ -1,5 +1,3 @@
1
- import {} from '@jini-ai/protocol';
2
- import {} from './contracts.js';
3
1
  import { isRecord, asString, asOptionalString, } from './values.js';
4
2
  function asOptionalNumber(value) {
5
3
  return typeof value === 'number' ? value : undefined;
@@ -62,6 +60,7 @@ export function translateStatusEvent({ rawEvent }) {
62
60
  ...(model !== undefined ? { model } : {}),
63
61
  ...(ttftMs !== undefined ? { ttftMs } : {}),
64
62
  ...(detail !== undefined ? { detail } : {}),
63
+ ...(sessionId !== undefined ? { sessionId } : {}),
65
64
  },
66
65
  ...(sessionId !== undefined ? { sessionId } : {}),
67
66
  };
@@ -1,8 +1,9 @@
1
- import { type ChildProcess } from 'node:child_process';
1
+ import type { ModelCatalogSnapshot } from '@jini-ai/agent-runtime';
2
+ import type { ChildProcess } from 'node:child_process';
2
3
  import { attachAcpSession, attachPiRpcSession, type AcpPermissionHandler, type RuntimeAgentDef } from '@jini-ai/agent-runtime';
3
- import { type RunByteJournal } from '../continuation/journal.js';
4
- import { type RunLifecycle } from '../run-lifecycle.js';
5
- import { type ChildDrivenStreamFormat, type TerminateChildTreeDeps, type AgentCleanupFailureContext, type StdinCloseHandle, type ContinuationOptions, type McpBridgeDelivery, type ClassifyFailure, type FailBeforeSpawn } from './contracts.js';
4
+ import type { RunByteJournal } from '../continuation/journal.js';
5
+ import type { RunLifecycle } from '../run-lifecycle.js';
6
+ import type { ChildDrivenStreamFormat, TerminateChildTreeDeps, AgentCleanupFailureContext, StdinCloseHandle, ContinuationOptions, McpBridgeDelivery, ClassifyFailure, FailBeforeSpawn } from './contracts.js';
6
7
  /** Default sink when a host does not supply `onCleanupFailure`: still observable, never silent. Redacted per SEC-007 (a spawn/permission error can embed paths/host detail). */
7
8
  export declare function defaultCleanupFailureSink(context: AgentCleanupFailureContext): void;
8
9
  /**
@@ -22,6 +23,8 @@ export declare function defaultCleanupFailureSink(context: AgentCleanupFailureCo
22
23
  */
23
24
  export declare const DEFAULT_BUFFERED_STDOUT_MAX_BYTES: number;
24
25
  interface WireChildLifecycleContext extends TerminateChildTreeDeps {
26
+ readonly startingModel?: string;
27
+ readonly modelCatalog?: ModelCatalogSnapshot;
25
28
  readonly runId: string;
26
29
  readonly def: RuntimeAgentDef;
27
30
  readonly streamFormat: ChildDrivenStreamFormat;
@@ -141,6 +144,7 @@ interface RunAcpDispatchInput {
141
144
  readonly prompt: string;
142
145
  readonly cwd: string;
143
146
  readonly model: string | undefined;
147
+ readonly modelCatalog?: ModelCatalogSnapshot;
144
148
  readonly imagePaths: readonly string[];
145
149
  readonly envFormat: 'array' | 'map' | undefined;
146
150
  readonly mcpBridge: McpBridgeDelivery | null;
@@ -168,6 +172,7 @@ interface RunPiRpcDispatchInput {
168
172
  readonly prompt: string;
169
173
  readonly cwd: string;
170
174
  readonly model: string | undefined;
175
+ readonly modelCatalog?: ModelCatalogSnapshot;
171
176
  readonly imagePaths: readonly string[];
172
177
  readonly uploadRoot: string | undefined;
173
178
  }
@@ -1,18 +1,20 @@
1
- import {} from 'node:child_process';
1
+ import { createModelReceiptTracker } from './model-receipts.js';
2
+ import { messageContentWithImages } from '../attachment-content.js';
2
3
  import { redactSecrets, } from '@jini-ai/core';
3
- import {} from '@jini-ai/protocol';
4
4
  import { createClaudeStreamHandler, createCopilotStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, attachAcpSession, attachPiRpcSession, } from '@jini-ai/agent-runtime';
5
5
  import { collectProcessTreePids, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform';
6
6
  import { classifyRunCloseStatus, } from '../close-status.js';
7
7
  import { resolveContinuationTransport, } from '../continuation/continuation-transport.js';
8
- import {} from '../continuation/journal.js';
9
8
  import { resultContent, } from '../delegated-tool-bridge.js';
10
9
  import { extractResultMedia, } from '../tool-result-media.js';
11
- import {} from '../run-lifecycle.js';
12
- import {} from './contracts.js';
13
10
  import { translateAgentRuntimeEvent, translateAcpError, applyAgentTranslationSideEffects, } from './event-translation.js';
14
11
  import { errorMessage, } from './values.js';
15
12
  import { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, bridgeUnavailableMessage, } from './mcp-bridge.js';
13
+ /** Carry PID only on structured status so the host can record its OS start identity early.
14
+ * A PID alone is never proof of death; hosts verify PID reuse before native continuation. */
15
+ function statusWithChildIdentity({ payload, childPid }, _optional = {}) {
16
+ return { ...payload, ...(payload.type === 'status' && childPid !== undefined ? { childPid } : {}) };
17
+ }
16
18
  /**
17
19
  * Selects and constructs the real stream-parser handler for a supported
18
20
  * `streamFormat`. `json-event-stream` additionally dispatches on
@@ -254,6 +256,7 @@ function bufferedStdoutTruncationNotice(droppedBytes, maxBytes) {
254
256
  */
255
257
  export function wireChildLifecycle(ctx) {
256
258
  const { runId, def, streamFormat, child, lifecycle, journal, continuation, classifyFailure } = ctx;
259
+ const modelReceipt = createModelReceiptTracker(ctx.startingModel, ctx.modelCatalog?.models);
257
260
  let stdinClosed = false;
258
261
  let cancelRequested = false;
259
262
  let emitQueue = Promise.resolve();
@@ -300,6 +303,8 @@ export function wireChildLifecycle(ctx) {
300
303
  }
301
304
  });
302
305
  }
306
+ if (ctx.startingModel)
307
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: { type: 'status', label: 'starting_model', model: ctx.startingModel } } }));
303
308
  function closeStdinOnce() {
304
309
  if (stdinClosed)
305
310
  return;
@@ -393,6 +398,9 @@ export function wireChildLifecycle(ctx) {
393
398
  return;
394
399
  const translation = translateAgentRuntimeEvent({ rawEvent: rawEvent });
395
400
  if (translation.kind === 'agent') {
401
+ const receipt = modelReceipt(translation.payload);
402
+ if (receipt)
403
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: receipt } }));
396
404
  if (translation.sessionId !== undefined)
397
405
  capturedSessionId = translation.sessionId;
398
406
  if (!bridgeChecked && translation.payload.type === 'status' && translation.payload.label === 'initializing') {
@@ -411,7 +419,7 @@ export function wireChildLifecycle(ctx) {
411
419
  translation.payload.delta.length > 0) {
412
420
  userVisibleOutputSeen = true;
413
421
  }
414
- enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
422
+ enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: statusWithChildIdentity({ payload: translation.payload, childPid: ctx.child.pid }, {}) } }));
415
423
  }
416
424
  else if (translation.kind === 'error') {
417
425
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
@@ -564,6 +572,7 @@ export function wireChildLifecycle(ctx) {
564
572
  * success.
565
573
  */
566
574
  function wireAcpLifecycle(ctx) {
575
+ const modelReceipt = createModelReceiptTracker(ctx.model, ctx.modelCatalog?.models);
567
576
  const { runId, agentId, child, lifecycle, journal, classifyFailure } = ctx;
568
577
  let cancelRequested = false;
569
578
  let emitQueue = Promise.resolve();
@@ -583,6 +592,8 @@ function wireAcpLifecycle(ctx) {
583
592
  }
584
593
  });
585
594
  }
595
+ if (ctx.model)
596
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: { type: 'status', label: 'starting_model', model: ctx.model } } }));
586
597
  child.stdout?.on('data', (chunk) => {
587
598
  const text = chunk.toString('utf8');
588
599
  if (journal)
@@ -626,6 +637,9 @@ function wireAcpLifecycle(ctx) {
626
637
  if (event === 'agent') {
627
638
  const translation = translateAgentRuntimeEvent({ rawEvent: payload });
628
639
  if (translation.kind === 'agent') {
640
+ const receipt = modelReceipt(translation.payload);
641
+ if (receipt)
642
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: receipt } }));
629
643
  applyAgentTranslationSideEffects({ payload: translation.payload, sessionId: translation.sessionId, sink: {
630
644
  onSessionId: ({ sessionId }) => { capturedSessionId = sessionId; },
631
645
  onToolCall: () => { toolCallSeen = true; },
@@ -668,6 +682,7 @@ function wireAcpLifecycle(ctx) {
668
682
  * multi-turn tool continuation, resumable session ids, etc.).
669
683
  */
670
684
  function wirePiRpcLifecycle(ctx) {
685
+ const modelReceipt = createModelReceiptTracker(ctx.model, ctx.modelCatalog?.models);
671
686
  const { runId, agentId, child, lifecycle, journal, classifyFailure } = ctx;
672
687
  let cancelRequested = false;
673
688
  let emitQueue = Promise.resolve();
@@ -687,6 +702,8 @@ function wirePiRpcLifecycle(ctx) {
687
702
  }
688
703
  });
689
704
  }
705
+ if (ctx.model)
706
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: { type: 'status', label: 'starting_model', model: ctx.model } } }));
690
707
  child.stdout?.on('data', (chunk) => {
691
708
  const text = chunk.toString('utf8');
692
709
  if (journal)
@@ -729,6 +746,9 @@ function wirePiRpcLifecycle(ctx) {
729
746
  session = ctx.attachPiRpcSession({ child: ctx.child, prompt: ctx.prompt, send({ payload }) {
730
747
  const translation = translateAgentRuntimeEvent({ rawEvent: payload });
731
748
  if (translation.kind === 'agent') {
749
+ const receipt = modelReceipt(translation.payload);
750
+ if (receipt)
751
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: receipt } }));
732
752
  if (translation.sessionId !== undefined)
733
753
  capturedSessionId = translation.sessionId;
734
754
  if (translation.payload.type === 'tool_use') {
@@ -769,7 +789,7 @@ export function writePromptToStdin(def, child, prompt, handle) {
769
789
  if (!stdin)
770
790
  return;
771
791
  if (def.promptInputFormat === 'stream-json') {
772
- const line = JSON.stringify({ type: 'user', message: { role: 'user', content: [{ type: 'text', text: prompt }] } });
792
+ const line = JSON.stringify({ type: 'user', message: { role: 'user', content: messageContentWithImages({ prompt, images: handle.imageContents ?? [] }, {}) } });
773
793
  stdin.write(`${line}\n`, 'utf8');
774
794
  handle.recordSentBytes(prompt);
775
795
  return;
@@ -789,6 +809,7 @@ export async function runAcpDispatch({ input, deps }) {
789
809
  prompt: input.prompt,
790
810
  cwd: input.cwd,
791
811
  model: input.model,
812
+ ...(input.modelCatalog ? { modelCatalog: input.modelCatalog } : {}),
792
813
  imagePaths: input.imagePaths,
793
814
  envFormat: input.envFormat,
794
815
  // Mechanism 2 of 5 — see `WireAcpLifecycleContext.mcpServers`. `undefined` for any def that
@@ -825,6 +846,7 @@ export async function runPiRpcDispatch({ input, deps }) {
825
846
  prompt: input.prompt,
826
847
  cwd: input.cwd,
827
848
  model: input.model,
849
+ ...(input.modelCatalog ? { modelCatalog: input.modelCatalog } : {}),
828
850
  imagePaths: input.imagePaths,
829
851
  uploadRoot: input.uploadRoot,
830
852
  attachPiRpcSession: deps.attachPiRpcSession,
@@ -1,4 +1,4 @@
1
- import { type RuntimeAgentDef, type RuntimeLockHold } from '@jini-ai/agent-runtime';
1
+ import type { RuntimeAgentDef, RuntimeLockHold } from '@jini-ai/agent-runtime';
2
2
  /**
3
3
  * Phase 7: acquires a `runtimeLock` def's process-global mutex before `buildArgs` runs — see
4
4
  * `RuntimeLock`'s own doc for the concrete race. A no-op (`undefined`) for the 23 of 24 defs with no
@@ -1,4 +1,3 @@
1
- import {} from '@jini-ai/agent-runtime';
2
1
  /**
3
2
  * Phase 7: acquires a `runtimeLock` def's process-global mutex before `buildArgs` runs — see
4
3
  * `RuntimeLock`'s own doc for the concrete race. A no-op (`undefined`) for the 23 of 24 defs with no
@@ -370,6 +370,6 @@ export declare function confirmChildSpawned({ input, deps }: {
370
370
  * @complexity `run()`'s own setup is O(1); steady-state cost is the chosen stream parser's.
371
371
  * @overallScore 100/100
372
372
  */
373
- export declare function createAgentExecutor(requiredArgs: Pick<CreateAgentExecutorOptions, "lifecycle">, optionalArgs?: Pick<CreateAgentExecutorOptions, "getAgentDef" | "resolveAgentLaunch" | "ensureAgentCapabilities" | "applyAgentLaunchEnv" | "createCommandInvocation" | "spawn" | "attachAcpSession" | "acpPermissionHandler" | "attachPiRpcSession" | "preparePromptFileForAgent" | "prepareAgentLogFile" | "listProcessSnapshots" | "collectProcessTreePids" | "stopProcesses" | "onCleanupFailure" | "journal" | "continuation" | "classifyFailure" | "mcpJsonInjection" | "claudeConfigDirIsolation" | "claudeConfigDirIsolationEnabled" | "bufferedStdoutMaxBytes" | "promptAugmenter">): AgentExecutor;
373
+ export declare function createAgentExecutor(requiredArgs: Pick<CreateAgentExecutorOptions, "lifecycle">, optionalArgs?: Pick<CreateAgentExecutorOptions, "getAgentDef" | "resolveAgentLaunch" | "resolveModelForLaunch" | "ensureAgentCapabilities" | "applyAgentLaunchEnv" | "createCommandInvocation" | "spawn" | "attachAcpSession" | "acpPermissionHandler" | "attachPiRpcSession" | "preparePromptFileForAgent" | "prepareAgentLogFile" | "listProcessSnapshots" | "collectProcessTreePids" | "stopProcesses" | "onCleanupFailure" | "journal" | "continuation" | "classifyFailure" | "mcpJsonInjection" | "claudeConfigDirIsolation" | "claudeConfigDirIsolationEnabled" | "bufferedStdoutMaxBytes" | "promptAugmenter">): AgentExecutor;
374
374
  export {};
375
375
  //# sourceMappingURL=launch.d.ts.map
@@ -2,7 +2,7 @@ import { spawn as nodeSpawn, } from 'node:child_process';
2
2
  import { promises as fsPromises, } from 'node:fs';
3
3
  import { tmpdir, } from 'node:os';
4
4
  import { join, } from 'node:path';
5
- import { agentCapabilities, applyAgentLaunchEnv, ensureAgentCapabilities, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
5
+ import { agentCapabilities, applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
6
6
  import { collectProcessTreePids, createCommandInvocation, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform';
7
7
  import { applyImagePromptDelivery, } from '../image-prompt-delivery.js';
8
8
  import { SUPPORTED_STREAM_FORMATS, AgentExecutorError, } from './contracts.js';
@@ -706,6 +706,23 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
706
706
  const resolvedEnv = { ...(def.env ?? {}), ...resolveRunEnv({ input: input, hostEnv: process.env }) };
707
707
  const launch = await resolveLaunch({ input: { runId: input.runId, def, resolvedEnv }, deps: { resolveAgentLaunch: resolveAgentLaunchFn, failBeforeSpawn } });
708
708
  const spawnEnv = applyAgentLaunchEnvFn({ env: { ...resolvedEnv }, launch });
709
+ // Resolve from the launch environment before staging or sending any prompt. Pin the same
710
+ // concrete ID that the picker displays; an opaque native route cannot silently start a run.
711
+ let modelCatalog;
712
+ try {
713
+ const selection = await (options.resolveModelForLaunch ?? resolveModelForLaunch)({ def, context: {
714
+ executable: launch.launchPath, cwd: input.cwd, env: spawnEnv,
715
+ ...(input.model && input.model !== 'default' ? { model: input.model } : {}),
716
+ ...(input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {}),
717
+ ...(input.settingSources ? { settingSources: input.settingSources } : {}),
718
+ ...(input.settings ? { settings: input.settings } : {}),
719
+ } });
720
+ input.model = selection.model;
721
+ modelCatalog = selection.catalog;
722
+ }
723
+ catch {
724
+ return failBeforeSpawn({ runId: input.runId, code: 'AGENT_MODEL_UNRESOLVED', message: 'AgentExecutor: pick a concrete model; the starting model could not be resolved for this launch.' });
725
+ }
709
726
  // Fill the def's `--help` capability gate before `buildArgs` reads it. Without this, only a
710
727
  // host that happened to call `detectAgents` in THIS process ever had the gate filled, so e.g.
711
728
  // `claude` never got `--include-partial-messages` (no streamed text). Probes once per binary;
@@ -898,6 +915,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
898
915
  const formatForStdin = { streamFormat };
899
916
  const stdinHandle = isStdinDrivenFormat(formatForStdin)
900
917
  ? wireChildLifecycle({
918
+ startingModel: input.model,
919
+ modelCatalog,
901
920
  runId: input.runId,
902
921
  def,
903
922
  streamFormat: formatForStdin.streamFormat,
@@ -937,6 +956,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
937
956
  prompt: `${overlayDelivery.promptPrefix}${input.prompt}`,
938
957
  cwd: input.cwd,
939
958
  model: input.model,
959
+ modelCatalog,
940
960
  imagePaths: input.imagePaths ?? [],
941
961
  envFormat: def.acpMcpEnvFormat,
942
962
  mcpBridge,
@@ -967,6 +987,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
967
987
  prompt: `${overlayDelivery.promptPrefix}${input.prompt}`,
968
988
  cwd: input.cwd,
969
989
  model: input.model,
990
+ modelCatalog,
970
991
  imagePaths: input.imagePaths ?? [],
971
992
  uploadRoot: input.uploadRoot,
972
993
  }, deps: {
@@ -994,6 +1015,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
994
1015
  // fallback strategy, so their prefix is genuinely non-empty; what makes stdin the wrong
995
1016
  // channel for them is the def's declared transport, not the strategy.
996
1017
  const stdinPrompt = def.promptViaStdin === true ? overlayDelivery.prompt : imageDelivery.prompt;
1018
+ if (input.imageContents !== undefined)
1019
+ stdinHandle.imageContents = input.imageContents;
997
1020
  writePromptToStdin(def, child, stdinPrompt, stdinHandle);
998
1021
  }
999
1022
  return { run };
@@ -1,5 +1,5 @@
1
- import { type AcpMcpServerInput, type RuntimeAgentDef } from '@jini-ai/agent-runtime';
2
- import { type McpJsonInjectionOptions, type McpJsonServerEntry, type McpBridgeDelivery, type PreparedCodexHome, type PreparedClaudeConfigDir, type ClaudeConfigDirIsolationOptions, type FailBeforeSpawn } from './contracts.js';
1
+ import type { AcpMcpServerInput, RuntimeAgentDef } from '@jini-ai/agent-runtime';
2
+ import type { McpJsonInjectionOptions, McpJsonServerEntry, McpBridgeDelivery, PreparedCodexHome, PreparedClaudeConfigDir, ClaudeConfigDirIsolationOptions, FailBeforeSpawn } from './contracts.js';
3
3
  /** The run-error code a run gets when its CLI reports the injected `jini` bridge as anything but connected. */
4
4
  export declare const MCP_BRIDGE_UNAVAILABLE = "MCP_BRIDGE_UNAVAILABLE";
5
5
  /**
@@ -1,9 +1,7 @@
1
1
  import { promises as fsPromises, } from 'node:fs';
2
2
  import { homedir, tmpdir, } from 'node:os';
3
3
  import { join, } from 'node:path';
4
- import {} from '@jini-ai/agent-runtime';
5
4
  import { isRecord, errorMessage, } from './values.js';
6
- import {} from './contracts.js';
7
5
  const JINI_MCP_SERVER_KEY = 'jini';
8
6
  /** The run-error code a run gets when its CLI reports the injected `jini` bridge as anything but connected. */
9
7
  export const MCP_BRIDGE_UNAVAILABLE = 'MCP_BRIDGE_UNAVAILABLE';
@@ -0,0 +1,6 @@
1
+ import type { RunAgentPayload } from '@jini-ai/protocol';
2
+ import { type DiscoveredModel } from '@jini-ai/agent-runtime';
3
+ /** Status metadata comes from CLI/RPC frames, never generated text. Dedupe repeats and record
4
+ * each observed transition separately so durable run events can describe model switches. */
5
+ export declare function createModelReceiptTracker(startingModel?: string, models?: readonly DiscoveredModel[]): (payload: RunAgentPayload) => RunAgentPayload | null;
6
+ //# sourceMappingURL=model-receipts.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { modelIdentityKind } from '@jini-ai/agent-runtime';
2
+ /** Status metadata comes from CLI/RPC frames, never generated text. Dedupe repeats and record
3
+ * each observed transition separately so durable run events can describe model switches. */
4
+ export function createModelReceiptTracker(startingModel, models = []) {
5
+ // The catalog is launch-scoped evidence. Normalize both the pinned and reported names
6
+ // so a CLI's alias/display spelling does not fabricate a durable model transition.
7
+ const normalize = (id) => {
8
+ const row = models.find(model => model.id === id) || models.find(model => model.label === id);
9
+ return row?.resolvedId || row?.id || id;
10
+ };
11
+ let current = startingModel && normalize(startingModel);
12
+ return (payload) => {
13
+ if (payload.type !== 'status' || !payload.model || typeof payload.model !== 'string')
14
+ return null;
15
+ const observed = normalize(payload.model);
16
+ if (modelIdentityKind(observed) !== 'concrete')
17
+ return null;
18
+ if (observed === current)
19
+ return null;
20
+ const prior = current;
21
+ current = observed;
22
+ return { type: 'status', label: prior ? 'model_switch' : 'observed_model', model: current,
23
+ ...(prior ? { previousModel: prior, detail: `Previous model: ${prior}` } : {}) };
24
+ };
25
+ }
26
+ //# sourceMappingURL=model-receipts.js.map