@jini-ai/daemon 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.5.3 — 2026-10-06
2
+
3
+ - 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.
4
+ - `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.
5
+ - 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.
6
+ - Requires `@jini-ai/protocol` ^0.4.1 and `@jini-ai/agent-runtime` ^0.4.1.
7
+
1
8
  ## 0.5.2 — 2026-10-06
2
9
 
3
10
  - 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,3 +1,4 @@
1
+ import type { MessageAttachmentImage } from '../attachment-content.js';
1
2
  import { type spawn as nodeSpawn, type ChildProcess } from 'node:child_process';
2
3
  import { type Principal } from '@jini-ai/core';
3
4
  import { type RunAgentPayload, type RunErrorPayload } from '@jini-ai/protocol';
@@ -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
@@ -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;
@@ -62,6 +62,7 @@ export function translateStatusEvent({ rawEvent }) {
62
62
  ...(model !== undefined ? { model } : {}),
63
63
  ...(ttftMs !== undefined ? { ttftMs } : {}),
64
64
  ...(detail !== undefined ? { detail } : {}),
65
+ ...(sessionId !== undefined ? { sessionId } : {}),
65
66
  },
66
67
  ...(sessionId !== undefined ? { sessionId } : {}),
67
68
  };
@@ -1,3 +1,4 @@
1
+ import { messageContentWithImages } from '../attachment-content.js';
1
2
  import {} from 'node:child_process';
2
3
  import { redactSecrets, } from '@jini-ai/core';
3
4
  import {} from '@jini-ai/protocol';
@@ -13,6 +14,11 @@ import {} from './contracts.js';
13
14
  import { translateAgentRuntimeEvent, translateAcpError, applyAgentTranslationSideEffects, } from './event-translation.js';
14
15
  import { errorMessage, } from './values.js';
15
16
  import { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, bridgeUnavailableMessage, } from './mcp-bridge.js';
17
+ /** Carry PID only on structured status so the host can record its OS start identity early.
18
+ * A PID alone is never proof of death; hosts verify PID reuse before native continuation. */
19
+ function statusWithChildIdentity({ payload, childPid }, _optional = {}) {
20
+ return { ...payload, ...(payload.type === 'status' && childPid !== undefined ? { childPid } : {}) };
21
+ }
16
22
  /**
17
23
  * Selects and constructs the real stream-parser handler for a supported
18
24
  * `streamFormat`. `json-event-stream` additionally dispatches on
@@ -411,7 +417,7 @@ export function wireChildLifecycle(ctx) {
411
417
  translation.payload.delta.length > 0) {
412
418
  userVisibleOutputSeen = true;
413
419
  }
414
- enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
420
+ enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: statusWithChildIdentity({ payload: translation.payload, childPid: ctx.child.pid }, {}) } }));
415
421
  }
416
422
  else if (translation.kind === 'error') {
417
423
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
@@ -769,7 +775,7 @@ export function writePromptToStdin(def, child, prompt, handle) {
769
775
  if (!stdin)
770
776
  return;
771
777
  if (def.promptInputFormat === 'stream-json') {
772
- const line = JSON.stringify({ type: 'user', message: { role: 'user', content: [{ type: 'text', text: prompt }] } });
778
+ const line = JSON.stringify({ type: 'user', message: { role: 'user', content: messageContentWithImages({ prompt, images: handle.imageContents ?? [] }, {}) } });
773
779
  stdin.write(`${line}\n`, 'utf8');
774
780
  handle.recordSentBytes(prompt);
775
781
  return;
@@ -994,6 +994,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
994
994
  // fallback strategy, so their prefix is genuinely non-empty; what makes stdin the wrong
995
995
  // channel for them is the def's declared transport, not the strategy.
996
996
  const stdinPrompt = def.promptViaStdin === true ? overlayDelivery.prompt : imageDelivery.prompt;
997
+ if (input.imageContents !== undefined)
998
+ stdinHandle.imageContents = input.imageContents;
997
999
  writePromptToStdin(def, child, stdinPrompt, stdinHandle);
998
1000
  }
999
1001
  return { run };
@@ -1,3 +1,4 @@
1
+ import type { MessageAttachmentImage } from './attachment-content.js';
1
2
  /**
2
3
  * `AgentExecutor` — the driver `RunLifecycle`'s own module doc names as the
3
4
  * missing piece: *"It does not spawn or signal a subprocess... A driver...
@@ -312,6 +313,8 @@ export interface AgentExecutorRunInput {
312
313
  * the top of `run()` below.
313
314
  */
314
315
  readonly imagePaths?: readonly string[];
316
+ /** Host-prepared pixels from the shared attachment reader, for structured message delivery. */
317
+ readonly imageContents?: readonly MessageAttachmentImage[];
315
318
  /** Additional host-validated directories the runtime may read. */
316
319
  readonly extraAllowedDirs?: readonly string[];
317
320
  /** Trusted root that must contain pi-rpc image paths after realpath resolution. */
@@ -375,7 +378,7 @@ export interface AgentExecutor {
375
378
  * underlying run is always already transitioned to `'failed'` via
376
379
  * `lifecycle.finish()` before this rejects (see module doc's Invariant).
377
380
  */
378
- 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>;
381
+ 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>;
379
382
  }
380
383
  /** Process discovery is a getter; process collection and stopping accept explicit host inputs. */
381
384
  export type CollectProcessTreePidsPort = (args: {
@@ -1,3 +1,4 @@
1
+ import { messageContentWithImages } from './attachment-content.js';
1
2
  /**
2
3
  * `AgentExecutor` — the driver `RunLifecycle`'s own module doc names as the
3
4
  * missing piece: *"It does not spawn or signal a subprocess... A driver...
@@ -1985,7 +1986,7 @@ function writePromptToStdin(def, child, prompt, handle) {
1985
1986
  if (!stdin)
1986
1987
  return;
1987
1988
  if (def.promptInputFormat === 'stream-json') {
1988
- const line = JSON.stringify({ type: 'user', message: { role: 'user', content: [{ type: 'text', text: prompt }] } });
1989
+ const line = JSON.stringify({ type: 'user', message: { role: 'user', content: messageContentWithImages({ prompt, images: handle.imageContents ?? [] }, {}) } });
1989
1990
  stdin.write(`${line}\n`, 'utf8');
1990
1991
  handle.recordSentBytes(prompt);
1991
1992
  return;
@@ -3000,6 +3001,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
3000
3001
  // fallback strategy, so their prefix is genuinely non-empty; what makes stdin the wrong
3001
3002
  // channel for them is the def's declared transport, not the strategy.
3002
3003
  const stdinPrompt = def.promptViaStdin === true ? overlayDelivery.prompt : imageDelivery.prompt;
3004
+ if (input.imageContents !== undefined)
3005
+ stdinHandle.imageContents = input.imageContents;
3003
3006
  writePromptToStdin(def, child, stdinPrompt, stdinHandle);
3004
3007
  }
3005
3008
  return { run };
@@ -0,0 +1,45 @@
1
+ export interface MessageAttachmentImage {
2
+ readonly mimeType: string;
3
+ readonly data: string;
4
+ }
5
+ export interface MessageAttachmentSource {
6
+ readonly name: string;
7
+ readonly mimeType: string;
8
+ readonly bytes: Uint8Array;
9
+ readonly path?: string;
10
+ }
11
+ export interface MessageAttachmentReader {
12
+ (required: {
13
+ ref: string;
14
+ }, optional: Record<string, never>): Promise<MessageAttachmentSource>;
15
+ }
16
+ /** One byte-bearing message path for CLI and API consumers. The host reader owns authorization,
17
+ * integrity and MIME sniffing; this module never opens a caller-selected path itself. Unsupported
18
+ * files are explicit so the model cannot mistake a filename for content it has received. */
19
+ export declare function prepareMessageAttachments({ refs, read }: {
20
+ refs: readonly string[];
21
+ read: MessageAttachmentReader;
22
+ }, { fileAccess, imageAccess }?: {
23
+ fileAccess?: boolean;
24
+ imageAccess?: boolean;
25
+ }): Promise<{
26
+ images: MessageAttachmentImage[];
27
+ imagePaths: string[];
28
+ notice: string;
29
+ }>;
30
+ /** Stream-JSON runtimes use the same normalized pixels as provider APIs. */
31
+ export declare function messageContentWithImages({ prompt, images }: {
32
+ prompt: string;
33
+ images: readonly MessageAttachmentImage[];
34
+ }, _optional?: {}): ({
35
+ type: "image";
36
+ source: {
37
+ type: "base64";
38
+ media_type: string;
39
+ data: string;
40
+ };
41
+ } | {
42
+ type: "text";
43
+ text: string;
44
+ })[];
45
+ //# sourceMappingURL=attachment-content.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { Buffer } from 'node:buffer';
2
+ /** One byte-bearing message path for CLI and API consumers. The host reader owns authorization,
3
+ * integrity and MIME sniffing; this module never opens a caller-selected path itself. Unsupported
4
+ * files are explicit so the model cannot mistake a filename for content it has received. */
5
+ export async function prepareMessageAttachments({ refs, read }, { fileAccess = false, imageAccess = true } = {}) {
6
+ const images = [];
7
+ const notices = [];
8
+ const imagePaths = [];
9
+ for (const ref of refs) {
10
+ const source = await read({ ref }, {});
11
+ if (['image/png', 'image/jpeg', 'image/gif', 'image/webp'].includes(source.mimeType)) {
12
+ if (!imageAccess) {
13
+ notices.push(`${source.name}: image attachments are unsupported by this runtime.`);
14
+ continue;
15
+ }
16
+ if (source.path)
17
+ imagePaths.push(source.path);
18
+ images.push({ mimeType: source.mimeType, data: Buffer.from(source.bytes).toString('base64') });
19
+ continue;
20
+ }
21
+ const video = source.mimeType.startsWith('video/');
22
+ if (fileAccess && source.path) {
23
+ notices.push(video
24
+ ? `${source.name}: video attachments are unsupported by this runtime; the file is available at ${source.path}.`
25
+ : `${source.name}: read the attached file at ${source.path}.`);
26
+ }
27
+ else {
28
+ notices.push(`${source.name}: ${video ? 'video' : 'file'} attachments are unsupported by this runtime.`);
29
+ }
30
+ }
31
+ return { images, imagePaths, notice: notices.length ? `\n\nAttachments to this message:\n${notices.join('\n')}` : '' };
32
+ }
33
+ /** Stream-JSON runtimes use the same normalized pixels as provider APIs. */
34
+ export function messageContentWithImages({ prompt, images }, _optional = {}) {
35
+ return [
36
+ { type: 'text', text: prompt },
37
+ ...images.map(image => ({ type: 'image', source: { type: 'base64', media_type: image.mimeType, data: image.data } })),
38
+ ];
39
+ }
40
+ //# sourceMappingURL=attachment-content.js.map
@@ -147,7 +147,8 @@ export interface AttachmentStore {
147
147
  readonly runId: string;
148
148
  }, _optional?: Record<string, never>) => Promise<StoredAttachment | undefined>;
149
149
  /**
150
- * Lists every still-unclaimed attachment registered with `ownerId` — the discovery counterpart to
150
+ * Lists unclaimed attachments registered with `ownerId`, plus attachments claimed by optional
151
+ * `runId` when supplied — the discovery counterpart to
151
152
  * `resolveForRun`'s single-lookup: a caller that does not yet hold a specific ref at all (an
152
153
  * attachment uploaded in an earlier turn, never named in this run's prompt) has no id to look up
153
154
  * with `resolveForRun` in the first place. This is what makes that attachment findable.
@@ -164,12 +165,18 @@ export interface AttachmentStore {
164
165
  * attachment from before this method existed) is excluded from every caller's results, never
165
166
  * just "unscoped" — this method is not callable with `ownerId: undefined`, so there is no input
166
167
  * that could accidentally match an ownerless record;
167
- * - a claimed attachment is excluded outright, whether or not this caller's own `runId` claimed
168
- * it, so this method can never be used to re-discover something already handed to a run — the
169
- * same "runs, not listings, are the reach here" boundary `resolveForRun` draws for a single ref.
168
+ * - a claimed attachment is excluded unless the host supplies that exact claiming `runId` in
169
+ * the optional argument. Hosts must derive ownerId/runId from authenticated execution context,
170
+ * never tool input. Run startup claims ALL message attachments before tools execute; including
171
+ * that run's claims keeps image, video and generic file discovery available after delivery.
172
+ * Owner matching remains mandatory even for the matching run. Without runId, the original
173
+ * unclaimed-only behavior is unchanged. Listing never claims, releases, or reads a file; callers
174
+ * still use `resolveForRun` for integrity-checked bytes/promotion.
170
175
  *
171
176
  * What this does NOT scope by: batch, conversation, or run — none of those are recorded on an
172
- * `AttachmentRecord` today. Two different conversations run by the SAME `ownerId` will each see
177
+ * `AttachmentRecord` today (except run ownership once claimed). Hosts restricting discovery to
178
+ * an accepted message must also intersect these summaries with that message's refs. Without that
179
+ * intersection, two different conversations run by the SAME `ownerId` will each see
173
180
  * the other's pending attachments through this method. A host for whom that is too wide needs a
174
181
  * finer-grained id than `ownerId` to pass into `register()` — this method does not itself assume
175
182
  * `ownerId` means "one admin account" rather than "one conversation, one composer, one browser
@@ -184,7 +191,9 @@ export interface AttachmentStore {
184
191
  */
185
192
  listPendingForOwner: ({ ownerId }: {
186
193
  readonly ownerId: string;
187
- }, _optional?: Record<string, never>) => Promise<PendingAttachmentSummary[]>;
194
+ }, optional?: {
195
+ readonly runId?: string;
196
+ }) => Promise<PendingAttachmentSummary[]>;
188
197
  /** Deletes the named still-unclaimed uploads, then the batch directory if it is now empty. */
189
198
  deleteUnclaimed: ({ batchId, paths }: {
190
199
  readonly batchId: string;
@@ -265,7 +274,7 @@ export interface AttachmentRecord {
265
274
  ownerId?: string;
266
275
  }
267
276
  /**
268
- * What `listPendingForOwner` hands back for one still-unclaimed attachment: enough for a caller to
277
+ * What `listPendingForOwner` hands back for an unclaimed or same-run claimed attachment: enough for a caller to
269
278
  * show a person what is waiting, and to name it again (`ref`) to `claim()`/`resolveForRun`.
270
279
  */
271
280
  export interface PendingAttachmentSummary {
@@ -835,12 +835,13 @@ export async function createDiskAttachmentStore(requiredArgs, optionalArgs = {})
835
835
  }
836
836
  return { path: record.filePath, name: record.name, kind: record.kind, size: record.size };
837
837
  },
838
- async listPendingForOwner({ ownerId }, _optional = {}) {
838
+ async listPendingForOwner({ ownerId }, { runId } = {}) {
839
839
  return [...records.values()]
840
840
  // `record.ownerId !== undefined` first, short-circuiting before the comparison: this is
841
841
  // what makes an ownerless record excluded rather than accidentally matched by a falsy-ish
842
842
  // `ownerId` argument — see this method's own doc on why that must never be a wildcard.
843
- .filter((record) => record.claimedRunId === undefined && record.ownerId !== undefined && record.ownerId === ownerId)
843
+ .filter((record) => record.ownerId !== undefined && record.ownerId === ownerId
844
+ && (record.claimedRunId === undefined || (runId !== undefined && record.claimedRunId === runId)))
844
845
  .sort((a, b) => a.createdAt - b.createdAt)
845
846
  .map((record) => ({ ref: record.id, name: record.name, kind: record.kind, size: record.size, createdAt: record.createdAt }));
846
847
  },
package/dist/index.d.ts CHANGED
@@ -588,4 +588,5 @@ export * from './legacy-data-migration.js';
588
588
  export * from './run/index.js';
589
589
  export * from './continuation/index.js';
590
590
  export * from './routines/index.js';
591
+ export * from './attachment-content.js';
591
592
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -588,4 +588,5 @@ export * from './legacy-data-migration.js';
588
588
  export * from './run/index.js';
589
589
  export * from './continuation/index.js';
590
590
  export * from './routines/index.js';
591
+ export * from './attachment-content.js';
591
592
  //# sourceMappingURL=index.js.map
@@ -15,7 +15,7 @@ export interface StartRunInput {
15
15
  readonly idempotencyKey?: string;
16
16
  /** Override the generated run id — test/fixture hook only. */
17
17
  readonly runId?: string;
18
- /** When set, arms an inactivity watchdog that fails the run as a resumable failure if no `emit()` occurs within this window. */
18
+ /** When set, arms an inactivity watchdog that fails the run permanently if no `emit()` occurs within this window. */
19
19
  readonly inactivityTimeoutMs?: number;
20
20
  }
21
21
  export interface StartRunResult {
@@ -450,10 +450,9 @@ export function createRunLifecycle(requiredArgs, optionalArgs = {}) {
450
450
  }
451
451
  /**
452
452
  * Fires when a run's inactivity watchdog times out with no intervening
453
- * `emit()`/`finish()`. Classified as a resumable failure (`code: null,
454
- * signal: null`), mirroring OD's own timeout/inactivity classification
455
- * (`isResumableFailure` in the researched `run-failure-classification.ts`
456
- * treats timeout/inactivity as one of only two resumable categories).
453
+ * `emit()`/`finish()`. A deliberate watchdog kill is permanent: automatically restarting
454
+ * it would undo the operator's inactivity policy. Other unexpected process failures still
455
+ * retain their original runtime classification.
457
456
  *
458
457
  * The `!record || isTerminalRunState(...)` guard below is defensive only and is currently
459
458
  * unreachable through the public API — deliberately kept, not fake-tested, and the reason is
@@ -479,7 +478,9 @@ export function createRunLifecycle(requiredArgs, optionalArgs = {}) {
479
478
  return;
480
479
  }
481
480
  try {
482
- await lifecycle.finish({ runId, status: 'failed', code: null, signal: null, resumable: true });
481
+ // A deliberate inactivity kill is permanent for this attempt, not an auto-resume signal.
482
+ await lifecycle.emit({ runId, input: { event: 'agent', data: { type: 'status', label: 'Stopped by inactivity watchdog', detail: 'inactivity-watchdog' } } });
483
+ await lifecycle.finish({ runId, status: 'failed', code: null, signal: null, resumable: false });
483
484
  }
484
485
  catch (error) {
485
486
  const context = { source: 'inactivity-timeout', runId, error };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/daemon",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "The stateful agent daemon runtime: run lifecycle, durable event-log port, the tool-execution boundary, agent executor, terminal sessions, and routines.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -116,10 +116,10 @@
116
116
  "registry": "https://registry.npmjs.org"
117
117
  },
118
118
  "dependencies": {
119
- "@jini-ai/agent-runtime": "^0.4.0",
120
- "@jini-ai/core": "^0.4.1",
119
+ "@jini-ai/protocol": "^0.4.1",
120
+ "@jini-ai/agent-runtime": "^0.4.1",
121
121
  "@jini-ai/platform": "^0.5.1",
122
- "@jini-ai/protocol": "^0.4.0"
122
+ "@jini-ai/core": "^0.4.1"
123
123
  },
124
124
  "devDependencies": {
125
125
  "@vitest/coverage-v8": "^2.1.9",
@@ -132,9 +132,9 @@
132
132
  "@types/pg": "8.23.1",
133
133
  "express": "^4.21.0",
134
134
  "@types/express": "^4.17.21",
135
- "@jini-ai/db": "0.3.0",
136
135
  "@jini-ai/http-kit": "0.4.0",
137
- "@jini-ai/chat": "0.5.3"
136
+ "@jini-ai/db": "0.3.0",
137
+ "@jini-ai/chat": "0.5.4"
138
138
  },
139
139
  "peerDependencies": {
140
140
  "node-pty": "^1.1.0",