@jini-ai/daemon 0.2.1 → 0.3.0

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 (77) hide show
  1. package/README.md +92 -0
  2. package/dist/agent-executor.d.ts +714 -42
  3. package/dist/agent-executor.d.ts.map +1 -1
  4. package/dist/agent-executor.js +1416 -351
  5. package/dist/agent-executor.js.map +1 -1
  6. package/dist/continuation/index.d.ts +1 -0
  7. package/dist/continuation/index.d.ts.map +1 -1
  8. package/dist/continuation/index.js +1 -0
  9. package/dist/continuation/index.js.map +1 -1
  10. package/dist/continuation/run-scoped-context-store.d.ts +79 -0
  11. package/dist/continuation/run-scoped-context-store.d.ts.map +1 -0
  12. package/dist/continuation/run-scoped-context-store.js +56 -0
  13. package/dist/continuation/run-scoped-context-store.js.map +1 -0
  14. package/dist/continuation/run-start-handler.d.ts +39 -9
  15. package/dist/continuation/run-start-handler.d.ts.map +1 -1
  16. package/dist/continuation/run-start-handler.js +12 -2
  17. package/dist/continuation/run-start-handler.js.map +1 -1
  18. package/dist/delegated-tool-bridge.d.ts +8 -0
  19. package/dist/delegated-tool-bridge.d.ts.map +1 -1
  20. package/dist/delegated-tool-bridge.js +117 -1
  21. package/dist/delegated-tool-bridge.js.map +1 -1
  22. package/dist/event-log.d.ts +18 -105
  23. package/dist/event-log.d.ts.map +1 -1
  24. package/dist/event-log.js +0 -17
  25. package/dist/event-log.js.map +1 -1
  26. package/dist/frontend-capability-tools.d.ts +1 -1
  27. package/dist/frontend-capability-tools.js +1 -1
  28. package/dist/frontend-session-registry.d.ts.map +1 -1
  29. package/dist/frontend-session-registry.js +26 -10
  30. package/dist/frontend-session-registry.js.map +1 -1
  31. package/dist/image-prompt-delivery.d.ts +56 -0
  32. package/dist/image-prompt-delivery.d.ts.map +1 -0
  33. package/dist/image-prompt-delivery.js +104 -0
  34. package/dist/image-prompt-delivery.js.map +1 -0
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/remote-tool-bridge.d.ts +45 -0
  40. package/dist/remote-tool-bridge.d.ts.map +1 -0
  41. package/dist/remote-tool-bridge.js +23 -0
  42. package/dist/remote-tool-bridge.js.map +1 -0
  43. package/dist/routines/routine-store.js +1 -1
  44. package/dist/routines/schedule.d.ts +0 -8
  45. package/dist/routines/schedule.d.ts.map +1 -1
  46. package/dist/routines/schedule.js +71 -44
  47. package/dist/routines/schedule.js.map +1 -1
  48. package/dist/routines/scheduler.d.ts +43 -0
  49. package/dist/routines/scheduler.d.ts.map +1 -1
  50. package/dist/routines/scheduler.js +160 -120
  51. package/dist/routines/scheduler.js.map +1 -1
  52. package/dist/run/core/retry.d.ts.map +1 -1
  53. package/dist/run/core/retry.js +47 -35
  54. package/dist/run/core/retry.js.map +1 -1
  55. package/dist/run/diagnostics/diagnostics.d.ts.map +1 -1
  56. package/dist/run/diagnostics/diagnostics.js +149 -91
  57. package/dist/run/diagnostics/diagnostics.js.map +1 -1
  58. package/dist/run-lifecycle.d.ts +84 -4
  59. package/dist/run-lifecycle.d.ts.map +1 -1
  60. package/dist/run-lifecycle.js +378 -116
  61. package/dist/run-lifecycle.js.map +1 -1
  62. package/dist/terminal-session.d.ts +1 -1
  63. package/dist/terminal-session.d.ts.map +1 -1
  64. package/dist/terminal-session.js +1 -1
  65. package/dist/tool-executor.d.ts +26 -6
  66. package/dist/tool-executor.d.ts.map +1 -1
  67. package/dist/tool-executor.js +220 -55
  68. package/dist/tool-executor.js.map +1 -1
  69. package/dist/tool-result-media.d.ts +79 -0
  70. package/dist/tool-result-media.d.ts.map +1 -0
  71. package/dist/tool-result-media.js +80 -0
  72. package/dist/tool-result-media.js.map +1 -0
  73. package/dist/tool-result-surfaces.d.ts +78 -0
  74. package/dist/tool-result-surfaces.d.ts.map +1 -0
  75. package/dist/tool-result-surfaces.js +92 -0
  76. package/dist/tool-result-surfaces.js.map +1 -0
  77. package/package.json +18 -9
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Extracts UI-renderable media blocks from a tool handler's raw MCP-style content envelope
3
+ * (`{ content: [...] }`), for `delegated-tool-bridge.ts` and `agent-executor.ts` to attach to the
4
+ * `tool_result` wire event's new `media` field (`@jini-ai/protocol`'s `events.ts`).
5
+ *
6
+ * ## Why this is not `tool-result-surfaces.ts`
7
+ *
8
+ * That module's whitelist governs a SECURITY property: which blocks may reach the model versus
9
+ * which are withheld for a human-only UI resource (MCP-UI, still deferred). This module answers a
10
+ * different question — "does this block carry displayable media a chat pane can render inline" —
11
+ * and is deliberately narrower: it recognizes exactly one block type today (`image`), and has no
12
+ * withhold-from-model concept of its own.
13
+ *
14
+ * The two DO interact, though not by pipelining: `delegated-tool-bridge.ts`'s `execute()` calls both
15
+ * of them against the SAME raw `executed.output`, not this module's `remainder` into
16
+ * `splitToolResultSurfaces`. That used to be pipelined — this module removed recognized image
17
+ * blocks from `content` before the surfaces pass ran, because `splitToolResultSurfaces`'s whitelist
18
+ * used to recognize only `text`, and an image block left in place would have been swept into
19
+ * `surfaces` and withheld from the model. Now that `image` is itself one of
20
+ * `tool-result-surfaces.ts`'s `MODEL_VISIBLE_BLOCK_TYPES` (a deliberate addition — see that
21
+ * constant's own doc), the surfaces pass classifies an image block correctly on its own, so there is
22
+ * no misclassification left to prevent by removing it first. This module's `remainder` field is
23
+ * therefore no longer read by that caller at all (kept for this module's own tests and any future
24
+ * caller that wants a de-imaged copy); only `media` is used, to populate the `tool_result` event's
25
+ * sibling `media` field for a UI that wants to render inline without re-parsing `content`.
26
+ *
27
+ * ## Why only `image`, and why blocks are silently skipped rather than reported
28
+ *
29
+ * `text` blocks already reach the model (and the UI, via `content`) through the existing flattened
30
+ * string — duplicating them into `media` would be redundant, not additive, so this module leaves
31
+ * them in `remainder` and never collects them. A block claiming `type: 'image'` without a string
32
+ * `mimeType`/`data` is dropped rather than surfaced as a malformed-block error: a handler's return
33
+ * value is untrusted input to this module (it may come from an external MCP server), and the
34
+ * contract is "render what is well-formed, ignore what isn't" — the same fail-quiet posture
35
+ * `serializeDelegatedToolOutput` already takes on a non-serializable output.
36
+ */
37
+ /** One MCP-style text content block. Part of the recognized envelope shape but never collected —
38
+ * see this module's doc for why `text` blocks stay out of `media`. Exported so a caller narrowing
39
+ * `ToolResultMediaBlock.type` has both arms of the union in scope. */
40
+ export interface ToolResultTextBlock {
41
+ readonly type: 'text';
42
+ readonly text: string;
43
+ }
44
+ /** One MCP-style image content block — the one block type this slice proves end to end.
45
+ * `mimeType`/`data` mirror MCP's own `ImageContent` field names deliberately, so a block copied
46
+ * verbatim out of an MCP tool result already matches this shape with no field renaming. */
47
+ export interface ToolResultImageBlock {
48
+ readonly type: 'image';
49
+ /** IANA media type, e.g. `"image/png"`. */
50
+ readonly mimeType: string;
51
+ /** Base64-encoded image bytes. */
52
+ readonly data: string;
53
+ }
54
+ /** A tool result content block `media` may carry. Modeled on MCP's own content-block vocabulary —
55
+ * see this module's doc for why that vocabulary is reused rather than a fourth one invented. */
56
+ export type ToolResultMediaBlock = ToolResultTextBlock | ToolResultImageBlock;
57
+ export interface ExtractResultMedia {
58
+ /**
59
+ * `output` with every recognized image block removed from its `content` array. Identical
60
+ * reference to the input when nothing was extracted (not an envelope, no `content` array, or no
61
+ * well-formed `image` blocks) — mirrors `tool-result-surfaces.ts`'s `SplitToolResultSurfaces`
62
+ * same-reference contract for the common no-op case.
63
+ */
64
+ readonly remainder: unknown;
65
+ /** Recognized image blocks, in arrival order. Empty when nothing was extracted. */
66
+ readonly media: readonly ToolResultMediaBlock[];
67
+ }
68
+ /**
69
+ * Splits a tool handler's raw return value into its recognized media blocks and the remainder,
70
+ * ready to feed onward into `splitToolResultSurfaces` (see this module's doc for why that order
71
+ * matters).
72
+ *
73
+ * @param output - A tool handler's raw return value (`ToolExecutionResult.output` when
74
+ * `status === 'completed'`). Non-envelope values (the overwhelming majority of tools, which return
75
+ * plain JSON) pass through untouched with no media.
76
+ * @complexity O(n) in the number of content blocks.
77
+ */
78
+ export declare function extractResultMedia(output: unknown): ExtractResultMedia;
79
+ //# sourceMappingURL=tool-result-media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result-media.d.ts","sourceRoot":"","sources":["../src/tool-result-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;uEAEuE;AACvE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;4FAE4F;AAC5F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;iGACiG;AACjG,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG,oBAAoB,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACjD;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,kBAAkB,CAetE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Extracts UI-renderable media blocks from a tool handler's raw MCP-style content envelope
3
+ * (`{ content: [...] }`), for `delegated-tool-bridge.ts` and `agent-executor.ts` to attach to the
4
+ * `tool_result` wire event's new `media` field (`@jini-ai/protocol`'s `events.ts`).
5
+ *
6
+ * ## Why this is not `tool-result-surfaces.ts`
7
+ *
8
+ * That module's whitelist governs a SECURITY property: which blocks may reach the model versus
9
+ * which are withheld for a human-only UI resource (MCP-UI, still deferred). This module answers a
10
+ * different question — "does this block carry displayable media a chat pane can render inline" —
11
+ * and is deliberately narrower: it recognizes exactly one block type today (`image`), and has no
12
+ * withhold-from-model concept of its own.
13
+ *
14
+ * The two DO interact, though not by pipelining: `delegated-tool-bridge.ts`'s `execute()` calls both
15
+ * of them against the SAME raw `executed.output`, not this module's `remainder` into
16
+ * `splitToolResultSurfaces`. That used to be pipelined — this module removed recognized image
17
+ * blocks from `content` before the surfaces pass ran, because `splitToolResultSurfaces`'s whitelist
18
+ * used to recognize only `text`, and an image block left in place would have been swept into
19
+ * `surfaces` and withheld from the model. Now that `image` is itself one of
20
+ * `tool-result-surfaces.ts`'s `MODEL_VISIBLE_BLOCK_TYPES` (a deliberate addition — see that
21
+ * constant's own doc), the surfaces pass classifies an image block correctly on its own, so there is
22
+ * no misclassification left to prevent by removing it first. This module's `remainder` field is
23
+ * therefore no longer read by that caller at all (kept for this module's own tests and any future
24
+ * caller that wants a de-imaged copy); only `media` is used, to populate the `tool_result` event's
25
+ * sibling `media` field for a UI that wants to render inline without re-parsing `content`.
26
+ *
27
+ * ## Why only `image`, and why blocks are silently skipped rather than reported
28
+ *
29
+ * `text` blocks already reach the model (and the UI, via `content`) through the existing flattened
30
+ * string — duplicating them into `media` would be redundant, not additive, so this module leaves
31
+ * them in `remainder` and never collects them. A block claiming `type: 'image'` without a string
32
+ * `mimeType`/`data` is dropped rather than surfaced as a malformed-block error: a handler's return
33
+ * value is untrusted input to this module (it may come from an external MCP server), and the
34
+ * contract is "render what is well-formed, ignore what isn't" — the same fail-quiet posture
35
+ * `serializeDelegatedToolOutput` already takes on a non-serializable output.
36
+ */
37
+ function isRecord(value) {
38
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
39
+ }
40
+ /** Narrows one loosely-typed content-array entry to a well-formed {@link ToolResultImageBlock}, or
41
+ * `undefined` for anything else (wrong `type`, or a `type: 'image'` block missing/mistyping
42
+ * `mimeType`/`data`). */
43
+ function asImageBlock(block) {
44
+ if (!isRecord(block) || block['type'] !== 'image')
45
+ return undefined;
46
+ const { mimeType, data } = block;
47
+ if (typeof mimeType !== 'string' || typeof data !== 'string')
48
+ return undefined;
49
+ return { type: 'image', mimeType, data };
50
+ }
51
+ /**
52
+ * Splits a tool handler's raw return value into its recognized media blocks and the remainder,
53
+ * ready to feed onward into `splitToolResultSurfaces` (see this module's doc for why that order
54
+ * matters).
55
+ *
56
+ * @param output - A tool handler's raw return value (`ToolExecutionResult.output` when
57
+ * `status === 'completed'`). Non-envelope values (the overwhelming majority of tools, which return
58
+ * plain JSON) pass through untouched with no media.
59
+ * @complexity O(n) in the number of content blocks.
60
+ */
61
+ export function extractResultMedia(output) {
62
+ if (!isRecord(output))
63
+ return { remainder: output, media: [] };
64
+ const content = output['content'];
65
+ if (!Array.isArray(content))
66
+ return { remainder: output, media: [] };
67
+ const media = [];
68
+ const kept = [];
69
+ for (const entry of content) {
70
+ const image = asImageBlock(entry);
71
+ if (image)
72
+ media.push(image);
73
+ else
74
+ kept.push(entry);
75
+ }
76
+ if (media.length === 0)
77
+ return { remainder: output, media: [] };
78
+ return { remainder: { ...output, content: kept }, media };
79
+ }
80
+ //# sourceMappingURL=tool-result-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result-media.js","sourceRoot":"","sources":["../src/tool-result-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAqCH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;0BAE0B;AAC1B,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IACpE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACjC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC/E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAe;IAChD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC/D,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAErE,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YACxB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAChE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Splits a tool handler's return value into the part the MODEL may see and the parts only a HUMAN
3
+ * may see.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * MCP's own model is that a tool result carries content blocks and the *host* decides which blocks
8
+ * reach the model and which are rendered for the person. Keeping them in one payload is the
9
+ * protocol; separating them is the host's job. Jini's daemon is the only actor standing in that
10
+ * position — the spawned coding-agent CLI is not an MCP-UI host, and `okResult()`
11
+ * (`@jini-ai/mcp`'s `tool-protocol.ts`) `JSON.stringify`s whatever it is handed into a single text
12
+ * block. So a UI resource left in a handler's return value becomes ordinary model-visible context.
13
+ *
14
+ * That is not hypothetical. A host's `content_post_delete` tool returned
15
+ * `{ content: [textBlock, uiResource] }` where the resource's inline `<script>` held a single-use
16
+ * confirmation token, and the tool's own description told the model the token "is never shown to
17
+ * you". The model could read it out of its own tool result and approve its own deletion. The split
18
+ * this module performs is what makes that structurally impossible.
19
+ *
20
+ * ## Whitelist, not blacklist — the security boundary
21
+ *
22
+ * {@link splitToolResultSurfaces} does NOT look for known UI shapes and strip them. It keeps only
23
+ * block types explicitly known to be model-safe (`text`) and routes **everything else** to the
24
+ * human channel, including block types that did not exist when this was written.
25
+ *
26
+ * The direction of that default is the whole point. A blacklist fails OPEN: the first time the
27
+ * MCP-UI spec adds a block type this file has never heard of, an unrecognised block sails through
28
+ * into model context — exactly the leak being fixed, reintroduced silently. A whitelist fails
29
+ * CLOSED: an unrecognised block is withheld from the model and rendered for the human, so the worst
30
+ * case is a surface that renders oddly rather than a secret that escapes.
31
+ *
32
+ * **Do not "improve" this by enumerating UI types to remove.** If a new block type is genuinely
33
+ * model-safe, add it to {@link MODEL_VISIBLE_BLOCK_TYPES} deliberately, with a reason.
34
+ *
35
+ * ## What is deliberately left alone
36
+ *
37
+ * A return value that is not an MCP content envelope (`{ content: [...] }`) passes through
38
+ * untouched. The overwhelming majority of registered tools return plain JSON — a list of posts, an
39
+ * id, a count — and are not making a model-vs-human distinction at all. Rewriting those would be a
40
+ * behavioural change to every tool in the registry in service of a problem they do not have.
41
+ */
42
+ /**
43
+ * Block `type` values a tool result may show the model.
44
+ *
45
+ * `text` and `image`, deliberately so — see this module's header before adding to it. `image` was
46
+ * added rather than left to the fail-closed default because it is genuinely model-safe in a way an
47
+ * MCP-UI resource is not: `ImageContent` is MCP's own vision-input mechanism, defined exactly so a
48
+ * tool result can put picture bytes in front of the model (`assistant_demo_image`'s whole reason to
49
+ * exist, in Tovu, is proving that round trip). That is a different property from "renders a UI a
50
+ * human clicks" — an image block carries no interactive surface and no host-side secret the way an
51
+ * embedded confirmation dialog can, so withholding it here would not close a leak, it would just
52
+ * break the feature. Anything else absent from this list is still treated as human-only.
53
+ */
54
+ export declare const MODEL_VISIBLE_BLOCK_TYPES: readonly string[];
55
+ /** One entry of an MCP tool result's `content` array. Structure beyond `type` is not this module's business. */
56
+ export type ToolResultBlock = Record<string, unknown>;
57
+ export interface SplitToolResultSurfaces {
58
+ /**
59
+ * What may be returned to the caller and therefore reach the model. Identical to the input
60
+ * (same reference) when the value is not an MCP content envelope.
61
+ */
62
+ readonly modelOutput: unknown;
63
+ /**
64
+ * Blocks withheld from the model, in arrival order, for the host to render to the human. Empty
65
+ * when nothing was withheld — the common case.
66
+ */
67
+ readonly surfaces: readonly ToolResultBlock[];
68
+ }
69
+ /**
70
+ * Partitions an MCP content envelope into model-visible and human-only halves.
71
+ *
72
+ * @param output - A tool handler's raw return value.
73
+ * @returns `modelOutput` (safe to return to the agent) and `surfaces` (human-only blocks). A
74
+ * non-envelope value is passed through by reference with no surfaces.
75
+ * @complexity O(n) in the number of content blocks.
76
+ */
77
+ export declare function splitToolResultSurfaces(output: unknown): SplitToolResultSurfaces;
78
+ //# sourceMappingURL=tool-result-surfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result-surfaces.d.ts","sourceRoot":"","sources":["../src/tool-result-surfaces.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAAsB,CAAC;AAE9E,gHAAgH;AAChH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC/C;AAMD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,uBAAuB,CA2BhF"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Splits a tool handler's return value into the part the MODEL may see and the parts only a HUMAN
3
+ * may see.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * MCP's own model is that a tool result carries content blocks and the *host* decides which blocks
8
+ * reach the model and which are rendered for the person. Keeping them in one payload is the
9
+ * protocol; separating them is the host's job. Jini's daemon is the only actor standing in that
10
+ * position — the spawned coding-agent CLI is not an MCP-UI host, and `okResult()`
11
+ * (`@jini-ai/mcp`'s `tool-protocol.ts`) `JSON.stringify`s whatever it is handed into a single text
12
+ * block. So a UI resource left in a handler's return value becomes ordinary model-visible context.
13
+ *
14
+ * That is not hypothetical. A host's `content_post_delete` tool returned
15
+ * `{ content: [textBlock, uiResource] }` where the resource's inline `<script>` held a single-use
16
+ * confirmation token, and the tool's own description told the model the token "is never shown to
17
+ * you". The model could read it out of its own tool result and approve its own deletion. The split
18
+ * this module performs is what makes that structurally impossible.
19
+ *
20
+ * ## Whitelist, not blacklist — the security boundary
21
+ *
22
+ * {@link splitToolResultSurfaces} does NOT look for known UI shapes and strip them. It keeps only
23
+ * block types explicitly known to be model-safe (`text`) and routes **everything else** to the
24
+ * human channel, including block types that did not exist when this was written.
25
+ *
26
+ * The direction of that default is the whole point. A blacklist fails OPEN: the first time the
27
+ * MCP-UI spec adds a block type this file has never heard of, an unrecognised block sails through
28
+ * into model context — exactly the leak being fixed, reintroduced silently. A whitelist fails
29
+ * CLOSED: an unrecognised block is withheld from the model and rendered for the human, so the worst
30
+ * case is a surface that renders oddly rather than a secret that escapes.
31
+ *
32
+ * **Do not "improve" this by enumerating UI types to remove.** If a new block type is genuinely
33
+ * model-safe, add it to {@link MODEL_VISIBLE_BLOCK_TYPES} deliberately, with a reason.
34
+ *
35
+ * ## What is deliberately left alone
36
+ *
37
+ * A return value that is not an MCP content envelope (`{ content: [...] }`) passes through
38
+ * untouched. The overwhelming majority of registered tools return plain JSON — a list of posts, an
39
+ * id, a count — and are not making a model-vs-human distinction at all. Rewriting those would be a
40
+ * behavioural change to every tool in the registry in service of a problem they do not have.
41
+ */
42
+ /**
43
+ * Block `type` values a tool result may show the model.
44
+ *
45
+ * `text` and `image`, deliberately so — see this module's header before adding to it. `image` was
46
+ * added rather than left to the fail-closed default because it is genuinely model-safe in a way an
47
+ * MCP-UI resource is not: `ImageContent` is MCP's own vision-input mechanism, defined exactly so a
48
+ * tool result can put picture bytes in front of the model (`assistant_demo_image`'s whole reason to
49
+ * exist, in Tovu, is proving that round trip). That is a different property from "renders a UI a
50
+ * human clicks" — an image block carries no interactive surface and no host-side secret the way an
51
+ * embedded confirmation dialog can, so withholding it here would not close a leak, it would just
52
+ * break the feature. Anything else absent from this list is still treated as human-only.
53
+ */
54
+ export const MODEL_VISIBLE_BLOCK_TYPES = ['text', 'image'];
55
+ function isRecord(value) {
56
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
57
+ }
58
+ /**
59
+ * Partitions an MCP content envelope into model-visible and human-only halves.
60
+ *
61
+ * @param output - A tool handler's raw return value.
62
+ * @returns `modelOutput` (safe to return to the agent) and `surfaces` (human-only blocks). A
63
+ * non-envelope value is passed through by reference with no surfaces.
64
+ * @complexity O(n) in the number of content blocks.
65
+ */
66
+ export function splitToolResultSurfaces(output) {
67
+ if (!isRecord(output))
68
+ return { modelOutput: output, surfaces: [] };
69
+ const content = output['content'];
70
+ if (!Array.isArray(content))
71
+ return { modelOutput: output, surfaces: [] };
72
+ const modelBlocks = [];
73
+ const surfaces = [];
74
+ for (const block of content) {
75
+ // A non-record block carries no `type` to check, so it cannot be shown to be model-safe.
76
+ // Withheld rather than passed through, per this module's fail-closed rule.
77
+ if (!isRecord(block)) {
78
+ surfaces.push({ type: 'unknown', value: block });
79
+ continue;
80
+ }
81
+ const type = block['type'];
82
+ if (typeof type === 'string' && MODEL_VISIBLE_BLOCK_TYPES.includes(type)) {
83
+ modelBlocks.push(block);
84
+ continue;
85
+ }
86
+ surfaces.push(block);
87
+ }
88
+ if (surfaces.length === 0)
89
+ return { modelOutput: output, surfaces: [] };
90
+ return { modelOutput: { ...output, content: modelBlocks }, surfaces };
91
+ }
92
+ //# sourceMappingURL=tool-result-surfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result-surfaces.js","sourceRoot":"","sources":["../src/tool-result-surfaces.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkB9E,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAe;IACrD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAEpE,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAE1E,MAAM,WAAW,GAAc,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAsB,EAAE,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,yFAAyF;QACzF,2EAA2E;QAC3E,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACjD,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,yBAAyB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACzE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAExE,OAAO,EAAE,WAAW,EAAE,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC;AACxE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@jini-ai/daemon",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
+ "description": "The stateful agent daemon runtime: run lifecycle, durable event-log port, the tool-execution boundary, agent executor, terminal sessions, and routines.",
4
5
  "license": "Apache-2.0",
5
6
  "repository": {
6
7
  "type": "git",
@@ -10,10 +11,10 @@
10
11
  "jini": {
11
12
  "domain": "server",
12
13
  "kind": "runtime",
13
- "runtime": "node",
14
- "admission": "locked"
14
+ "runtime": "node"
15
15
  },
16
16
  "type": "module",
17
+ "sideEffects": false,
17
18
  "main": "./dist/index.js",
18
19
  "types": "./dist/index.d.ts",
19
20
  "exports": {
@@ -36,14 +37,22 @@
36
37
  "registry": "https://registry.npmjs.org"
37
38
  },
38
39
  "dependencies": {
39
- "node-pty": "^1.1.0",
40
- "@jini-ai/core": "0.1.2",
41
- "@jini-ai/platform": "0.1.2",
42
- "@jini-ai/agent-runtime": "0.2.1",
43
- "@jini-ai/protocol": "0.1.2"
40
+ "@jini-ai/core": "0.3.0",
41
+ "@jini-ai/platform": "0.3.0",
42
+ "@jini-ai/agent-runtime": "0.3.0",
43
+ "@jini-ai/protocol": "0.3.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@vitest/coverage-v8": "^2.1.9"
46
+ "@vitest/coverage-v8": "^2.1.9",
47
+ "node-pty": "^1.1.0"
48
+ },
49
+ "peerDependencies": {
50
+ "node-pty": "^1.1.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "node-pty": {
54
+ "optional": true
55
+ }
47
56
  },
48
57
  "scripts": {
49
58
  "build": "tsc -p tsconfig.json",