@jini-ai/daemon 0.1.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 (142) hide show
  1. package/LICENSE +201 -0
  2. package/dist/agent-executor.d.ts +458 -0
  3. package/dist/agent-executor.d.ts.map +1 -0
  4. package/dist/agent-executor.js +1277 -0
  5. package/dist/agent-executor.js.map +1 -0
  6. package/dist/artifacts/index.d.ts +14 -0
  7. package/dist/artifacts/index.d.ts.map +1 -0
  8. package/dist/artifacts/index.js +14 -0
  9. package/dist/artifacts/index.js.map +1 -0
  10. package/dist/artifacts/manifest.d.ts +90 -0
  11. package/dist/artifacts/manifest.d.ts.map +1 -0
  12. package/dist/artifacts/manifest.js +229 -0
  13. package/dist/artifacts/manifest.js.map +1 -0
  14. package/dist/artifacts/publication-guard.d.ts +28 -0
  15. package/dist/artifacts/publication-guard.d.ts.map +1 -0
  16. package/dist/artifacts/publication-guard.js +77 -0
  17. package/dist/artifacts/publication-guard.js.map +1 -0
  18. package/dist/artifacts/runtime-compat.d.ts +37 -0
  19. package/dist/artifacts/runtime-compat.d.ts.map +1 -0
  20. package/dist/artifacts/runtime-compat.js +33 -0
  21. package/dist/artifacts/runtime-compat.js.map +1 -0
  22. package/dist/artifacts/store.d.ts +85 -0
  23. package/dist/artifacts/store.d.ts.map +1 -0
  24. package/dist/artifacts/store.js +92 -0
  25. package/dist/artifacts/store.js.map +1 -0
  26. package/dist/artifacts/stub-guard.d.ts +73 -0
  27. package/dist/artifacts/stub-guard.d.ts.map +1 -0
  28. package/dist/artifacts/stub-guard.js +198 -0
  29. package/dist/artifacts/stub-guard.js.map +1 -0
  30. package/dist/artifacts/text-suppression.d.ts +68 -0
  31. package/dist/artifacts/text-suppression.d.ts.map +1 -0
  32. package/dist/artifacts/text-suppression.js +184 -0
  33. package/dist/artifacts/text-suppression.js.map +1 -0
  34. package/dist/close-status.d.ts +86 -0
  35. package/dist/close-status.d.ts.map +1 -0
  36. package/dist/close-status.js +91 -0
  37. package/dist/close-status.js.map +1 -0
  38. package/dist/continuation/continuation-transport.d.ts +41 -0
  39. package/dist/continuation/continuation-transport.d.ts.map +1 -0
  40. package/dist/continuation/continuation-transport.js +18 -0
  41. package/dist/continuation/continuation-transport.js.map +1 -0
  42. package/dist/continuation/index.d.ts +4 -0
  43. package/dist/continuation/index.d.ts.map +1 -0
  44. package/dist/continuation/index.js +4 -0
  45. package/dist/continuation/index.js.map +1 -0
  46. package/dist/continuation/journal.d.ts +37 -0
  47. package/dist/continuation/journal.d.ts.map +1 -0
  48. package/dist/continuation/journal.js +23 -0
  49. package/dist/continuation/journal.js.map +1 -0
  50. package/dist/continuation/run-start-handler.d.ts +75 -0
  51. package/dist/continuation/run-start-handler.d.ts.map +1 -0
  52. package/dist/continuation/run-start-handler.js +32 -0
  53. package/dist/continuation/run-start-handler.js.map +1 -0
  54. package/dist/delegated-tool-bridge.d.ts +53 -0
  55. package/dist/delegated-tool-bridge.d.ts.map +1 -0
  56. package/dist/delegated-tool-bridge.js +92 -0
  57. package/dist/delegated-tool-bridge.js.map +1 -0
  58. package/dist/event-log.d.ts +134 -0
  59. package/dist/event-log.d.ts.map +1 -0
  60. package/dist/event-log.js +108 -0
  61. package/dist/event-log.js.map +1 -0
  62. package/dist/frontend-capability-tools.d.ts +102 -0
  63. package/dist/frontend-capability-tools.d.ts.map +1 -0
  64. package/dist/frontend-capability-tools.js +68 -0
  65. package/dist/frontend-capability-tools.js.map +1 -0
  66. package/dist/frontend-session-registry.d.ts +127 -0
  67. package/dist/frontend-session-registry.d.ts.map +1 -0
  68. package/dist/frontend-session-registry.js +180 -0
  69. package/dist/frontend-session-registry.js.map +1 -0
  70. package/dist/index.d.ts +38 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +38 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/legacy-data-migration.d.ts +104 -0
  75. package/dist/legacy-data-migration.d.ts.map +1 -0
  76. package/dist/legacy-data-migration.js +239 -0
  77. package/dist/legacy-data-migration.js.map +1 -0
  78. package/dist/routines/index.d.ts +12 -0
  79. package/dist/routines/index.d.ts.map +1 -0
  80. package/dist/routines/index.js +12 -0
  81. package/dist/routines/index.js.map +1 -0
  82. package/dist/routines/routine-store.d.ts +77 -0
  83. package/dist/routines/routine-store.d.ts.map +1 -0
  84. package/dist/routines/routine-store.js +162 -0
  85. package/dist/routines/routine-store.js.map +1 -0
  86. package/dist/routines/schedule.d.ts +72 -0
  87. package/dist/routines/schedule.d.ts.map +1 -0
  88. package/dist/routines/schedule.js +263 -0
  89. package/dist/routines/schedule.js.map +1 -0
  90. package/dist/routines/scheduler.d.ts +34 -0
  91. package/dist/routines/scheduler.d.ts.map +1 -0
  92. package/dist/routines/scheduler.js +330 -0
  93. package/dist/routines/scheduler.js.map +1 -0
  94. package/dist/routines/types.d.ts +131 -0
  95. package/dist/routines/types.d.ts.map +1 -0
  96. package/dist/routines/types.js +9 -0
  97. package/dist/routines/types.js.map +1 -0
  98. package/dist/run/core/failure-taxonomy.d.ts +27 -0
  99. package/dist/run/core/failure-taxonomy.d.ts.map +1 -0
  100. package/dist/run/core/failure-taxonomy.js +16 -0
  101. package/dist/run/core/failure-taxonomy.js.map +1 -0
  102. package/dist/run/core/index.d.ts +5 -0
  103. package/dist/run/core/index.d.ts.map +1 -0
  104. package/dist/run/core/index.js +5 -0
  105. package/dist/run/core/index.js.map +1 -0
  106. package/dist/run/core/result.d.ts +25 -0
  107. package/dist/run/core/result.d.ts.map +1 -0
  108. package/dist/run/core/result.js +52 -0
  109. package/dist/run/core/result.js.map +1 -0
  110. package/dist/run/core/retry.d.ts +106 -0
  111. package/dist/run/core/retry.d.ts.map +1 -0
  112. package/dist/run/core/retry.js +210 -0
  113. package/dist/run/core/retry.js.map +1 -0
  114. package/dist/run/diagnostics/diagnostics.d.ts +86 -0
  115. package/dist/run/diagnostics/diagnostics.d.ts.map +1 -0
  116. package/dist/run/diagnostics/diagnostics.js +211 -0
  117. package/dist/run/diagnostics/diagnostics.js.map +1 -0
  118. package/dist/run/diagnostics/index.d.ts +3 -0
  119. package/dist/run/diagnostics/index.d.ts.map +1 -0
  120. package/dist/run/diagnostics/index.js +3 -0
  121. package/dist/run/diagnostics/index.js.map +1 -0
  122. package/dist/run/index.d.ts +16 -0
  123. package/dist/run/index.d.ts.map +1 -0
  124. package/dist/run/index.js +16 -0
  125. package/dist/run/index.js.map +1 -0
  126. package/dist/run-lifecycle.d.ts +118 -0
  127. package/dist/run-lifecycle.d.ts.map +1 -0
  128. package/dist/run-lifecycle.js +451 -0
  129. package/dist/run-lifecycle.js.map +1 -0
  130. package/dist/terminal-session.d.ts +139 -0
  131. package/dist/terminal-session.d.ts.map +1 -0
  132. package/dist/terminal-session.js +316 -0
  133. package/dist/terminal-session.js.map +1 -0
  134. package/dist/tokens.d.ts +9 -0
  135. package/dist/tokens.d.ts.map +1 -0
  136. package/dist/tokens.js +19 -0
  137. package/dist/tokens.js.map +1 -0
  138. package/dist/tool-executor.d.ts +97 -0
  139. package/dist/tool-executor.d.ts.map +1 -0
  140. package/dist/tool-executor.js +198 -0
  141. package/dist/tool-executor.js.map +1 -0
  142. package/package.json +51 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Builds the default `RunStartHandler`-shaped driver: resolves the real
3
+ * agent input via `options.resolveRunInput`, then hands it straight to
4
+ * `options.agentExecutor.run()`. A host wires this in as
5
+ * `RunHttpDeps.onStarted` (or `CreateLocalNodeDaemonConfig.onRunStarted`)
6
+ * instead of writing its own driver from scratch — it still owns
7
+ * `resolveRunInput` itself (gap 2 stays host-owned), but no longer needs to
8
+ * know `AgentExecutor`'s call shape or wire cancellation/journaling by hand.
9
+ * @param options.agentExecutor - The executor this handler drives. Any byte-journaling is the executor's own concern (see `CreateAgentExecutorOptions.journal`) — this handler does not journal directly.
10
+ * @param options.resolveRunInput - Host-owned composition seam — see module doc.
11
+ * @returns A handler structurally assignable to `@jini-ai/http`'s `RunStartHandler`.
12
+ * @throws Whatever `resolveRunInput` or `agentExecutor.run()` throw — `@jini-ai/http`'s `runStartRoute` already treats a rejecting `onStarted` as a failed run (finishes it, reports the internal error), so this handler deliberately does not swallow either failure itself.
13
+ * @complexity O(1) plus `resolveRunInput`'s and `agentExecutor.run()`'s own costs.
14
+ * @overallScore 100/100
15
+ */
16
+ export function createDefaultRunStartHandler(options) {
17
+ return async (context) => {
18
+ const resolved = await options.resolveRunInput({
19
+ runId: context.run.id,
20
+ contextRef: context.request.contextRef,
21
+ ...(context.request.agentId !== undefined ? { agentId: context.request.agentId } : {}),
22
+ });
23
+ await options.agentExecutor.run({
24
+ runId: context.run.id,
25
+ agentId: resolved.agentId,
26
+ prompt: resolved.prompt,
27
+ cwd: resolved.cwd,
28
+ ...(resolved.env !== undefined ? { env: resolved.env } : {}),
29
+ });
30
+ };
31
+ }
32
+ //# sourceMappingURL=run-start-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-start-handler.js","sourceRoot":"","sources":["../../src/continuation/run-start-handler.ts"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,4BAA4B,CAC1C,OAA4C;IAE5C,OAAO,KAAK,EAAE,OAA8B,EAAiB,EAAE;QAC7D,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC;YAC7C,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE;YACrB,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU;YACtC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvF,CAAC,CAAC;QACH,MAAM,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC;YAC9B,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE;YACrB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,GAAG,CAAC,QAAQ,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7D,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `DelegatedToolBridge` — the execution path for agents/protocols that ask
3
+ * Jini to run a registered tool on their behalf. It is deliberately separate
4
+ * from ACP's `session/request_permission`: ACP authorizes an agent's *native*
5
+ * tool loop, whereas this bridge invokes Jini's `ToolExecutor` and therefore
6
+ * enforces Jini's registry policy, confirmation, timeout, cancellation, and
7
+ * audit trail before any registered handler runs.
8
+ */
9
+ import type { Principal } from '@jini-ai/core';
10
+ import type { RunLifecycle } from './run-lifecycle.js';
11
+ import type { ToolExecutionResult, ToolExecutor } from './tool-executor.js';
12
+ /** A tool request received through a Jini-owned delegated-execution protocol. */
13
+ export interface DelegatedToolInvocation {
14
+ /** The already-started run that owns the request. */
15
+ readonly runId: string;
16
+ /** Stable agent-side correlation id, mirrored in run `tool_use`/`tool_result` events. */
17
+ readonly toolUseId: string;
18
+ /** Jini registry id — never an agent-vendor-specific tool name. */
19
+ readonly toolId: string;
20
+ readonly principal: Principal;
21
+ readonly input: unknown;
22
+ /** Optional transport disconnect/abort signal, combined with run cancellation. */
23
+ readonly signal?: AbortSignal;
24
+ }
25
+ export interface DelegatedToolBridge {
26
+ /**
27
+ * Emits a canonical `tool_use`, executes through `ToolExecutor`, then emits
28
+ * exactly one matching `tool_result`. Unknown tool ids remain programmer or
29
+ * routing errors from `ToolExecutor` and are rethrown after their error
30
+ * result is recorded.
31
+ */
32
+ execute(invocation: DelegatedToolInvocation): Promise<ToolExecutionResult>;
33
+ }
34
+ export interface CreateDelegatedToolBridgeOptions {
35
+ readonly lifecycle: RunLifecycle;
36
+ readonly toolExecutor: ToolExecutor;
37
+ }
38
+ /** Converts arbitrary registered-tool output into the string-bearing run protocol. */
39
+ export declare function serializeDelegatedToolOutput(output: unknown): string;
40
+ /**
41
+ * Maps a `ToolExecutionResult`'s status to the string a delegated-tool caller (this bridge, or
42
+ * gap 3's stdin-tool-result injector in `agent-executor.ts`) reports back as the tool's visible
43
+ * output/failure reason. Exported so both real callers share one mapping rather than drifting.
44
+ */
45
+ export declare function resultContent(result: ToolExecutionResult): string;
46
+ /**
47
+ * Creates the transport-neutral bridge used by future ACP-delegate, MCP, or
48
+ * other host protocols. This module does not invent a server transport: a
49
+ * concrete protocol calls this bridge after it has decoded and authenticated a
50
+ * delegated tool request.
51
+ */
52
+ export declare function createDelegatedToolBridge(options: CreateDelegatedToolBridgeOptions): DelegatedToolBridge;
53
+ //# sourceMappingURL=delegated-tool-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegated-tool-bridge.d.ts","sourceRoot":"","sources":["../src/delegated-tool-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAU,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE5E,iFAAiF;AACjF,MAAM,WAAW,uBAAuB;IACtC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,uBAAuB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC5E;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;CACrC;AAMD,sFAAsF;AACtF,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CASpE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAejE;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,mBAAmB,CA8CxG"}
@@ -0,0 +1,92 @@
1
+ function errorMessage(error) {
2
+ return error instanceof Error ? error.message : String(error);
3
+ }
4
+ /** Converts arbitrary registered-tool output into the string-bearing run protocol. */
5
+ export function serializeDelegatedToolOutput(output) {
6
+ if (typeof output === 'string')
7
+ return output;
8
+ if (output === undefined)
9
+ return '';
10
+ try {
11
+ const serialized = JSON.stringify(output);
12
+ return serialized === undefined ? String(output) : serialized;
13
+ }
14
+ catch {
15
+ return String(output);
16
+ }
17
+ }
18
+ /**
19
+ * Maps a `ToolExecutionResult`'s status to the string a delegated-tool caller (this bridge, or
20
+ * gap 3's stdin-tool-result injector in `agent-executor.ts`) reports back as the tool's visible
21
+ * output/failure reason. Exported so both real callers share one mapping rather than drifting.
22
+ */
23
+ export function resultContent(result) {
24
+ switch (result.status) {
25
+ case 'completed':
26
+ return serializeDelegatedToolOutput(result.output);
27
+ case 'denied':
28
+ return 'Tool execution denied by policy.';
29
+ case 'confirmation-denied':
30
+ return 'Tool execution denied during confirmation.';
31
+ case 'timed-out':
32
+ return 'Tool execution timed out.';
33
+ case 'cancelled':
34
+ return 'Tool execution cancelled.';
35
+ case 'failed':
36
+ return result.error ?? 'Tool execution failed.';
37
+ }
38
+ }
39
+ /**
40
+ * Creates the transport-neutral bridge used by future ACP-delegate, MCP, or
41
+ * other host protocols. This module does not invent a server transport: a
42
+ * concrete protocol calls this bridge after it has decoded and authenticated a
43
+ * delegated tool request.
44
+ */
45
+ export function createDelegatedToolBridge(options) {
46
+ const { lifecycle, toolExecutor } = options;
47
+ async function execute(invocation) {
48
+ const { runId, toolUseId, toolId, principal, input } = invocation;
49
+ await lifecycle.emit(runId, {
50
+ event: 'agent',
51
+ data: { type: 'tool_use', id: toolUseId, name: toolId, input },
52
+ });
53
+ const controller = new AbortController();
54
+ const unsubscribeCancel = lifecycle.onCancelRequested(runId, () => controller.abort());
55
+ const abortFromTransport = () => controller.abort();
56
+ if (invocation.signal) {
57
+ if (invocation.signal.aborted)
58
+ controller.abort();
59
+ else
60
+ invocation.signal.addEventListener('abort', abortFromTransport, { once: true });
61
+ }
62
+ const run = { id: runId };
63
+ try {
64
+ try {
65
+ const result = await toolExecutor.execute(principal, run, toolId, input, controller.signal);
66
+ await lifecycle.emit(runId, {
67
+ event: 'agent',
68
+ data: {
69
+ type: 'tool_result',
70
+ toolUseId,
71
+ content: resultContent(result),
72
+ ...(result.status === 'completed' ? {} : { isError: true }),
73
+ },
74
+ });
75
+ return result;
76
+ }
77
+ catch (error) {
78
+ await lifecycle.emit(runId, {
79
+ event: 'agent',
80
+ data: { type: 'tool_result', toolUseId, content: errorMessage(error), isError: true },
81
+ });
82
+ throw error;
83
+ }
84
+ }
85
+ finally {
86
+ unsubscribeCancel();
87
+ invocation.signal?.removeEventListener('abort', abortFromTransport);
88
+ }
89
+ }
90
+ return { execute };
91
+ }
92
+ //# sourceMappingURL=delegated-tool-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegated-tool-bridge.js","sourceRoot":"","sources":["../src/delegated-tool-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * `EventLog` — the durable event-log kernel port (extraction-plan §12 C1).
3
+ *
4
+ * OD's own event durability is split across three tiers today: a ~2000-event
5
+ * in-memory ring (`apps/daemon/src/runtimes/runs.ts` on the researched
6
+ * `arch/server-startserver-endgame` branch), a durable copy written into the
7
+ * product's own `messages.events_json` column, and a best-effort JSONL tail
8
+ * file that nothing ever reads back for replay. Because the durable half
9
+ * lives outside any port, a non-OD consumer that only wires the in-memory
10
+ * ring silently loses in-flight output on a long-run reload. `EventLog` is
11
+ * the fix: a single, storage-agnostic port that any adapter (this package's
12
+ * in-memory reference implementation, or `@jini-ai/sqlite`'s future durable
13
+ * adapter — task 8) can satisfy. Every method returns a `Promise` even
14
+ * though the in-memory implementation resolves synchronously, so a real
15
+ * persistent adapter is a drop-in swap rather than an API break
16
+ * (extraction-plan §2.6: "ports are async-only from day one").
17
+ */
18
+ /** One durably-ordered record in a run's event log. */
19
+ export interface EventLogEntry<Payload = unknown> {
20
+ /** Monotonic per-run cursor, assigned by the log — never by the caller. */
21
+ readonly id: string;
22
+ readonly event: string;
23
+ readonly data: Payload;
24
+ /** Epoch-ms wall-clock time the entry was recorded. */
25
+ readonly recordedAt: number;
26
+ }
27
+ export interface EventLogAppendInput<Payload = unknown> {
28
+ readonly runId: string;
29
+ readonly event: string;
30
+ readonly data: Payload;
31
+ /**
32
+ * Optional producer-supplied dedup token. Appending twice with the same
33
+ * `dedupeKey` for the same run returns the original entry unchanged rather
34
+ * than recording a second one — this is what makes at-least-once producers
35
+ * (a retried emit after a timeout, a driver that re-delivers on
36
+ * reconnect) safe to layer on top of an otherwise plain ordered log.
37
+ */
38
+ readonly dedupeKey?: string;
39
+ }
40
+ /**
41
+ * Result of `EventLog.replay`. `'ok'` and `'unknown-run'` are the two happy
42
+ * paths (a fresh run and a run this log has never heard of are both valid,
43
+ * un-exceptional states). `'replay-gap'` is the case OD's own ring buffer
44
+ * gets wrong today (see the module doc): the requested cursor references
45
+ * events that have already been evicted, so silently returning "whatever is
46
+ * still buffered" would hand the caller a stream with an undetectable hole
47
+ * in it. Making the gap a distinguishable result forces every caller
48
+ * (a transport reconnect handler, a test) to decide explicitly how to
49
+ * recover (full resync, error to the end user, etc.) instead of trusting
50
+ * data that looks contiguous but isn't. `'invalid-cursor'` covers a
51
+ * non-numeric cursor string, which is a caller/transport bug rather than a
52
+ * storage-shape problem and should not be conflated with a real gap.
53
+ */
54
+ export type EventLogReplayResult<Payload = unknown> = {
55
+ readonly kind: 'ok';
56
+ readonly entries: readonly EventLogEntry<Payload>[];
57
+ /**
58
+ * `true` only when `afterCursor` was `null` (a first-time subscribe) AND this run's
59
+ * earliest entries have already been evicted, so `entries` starts after cursor 1 rather
60
+ * than at the true beginning. Omitted (not `false`) on every non-truncated result, so
61
+ * existing exact-match assertions against untruncated replays are unaffected. This does
62
+ * NOT make a first-time null-cursor replay a `'replay-gap'` — that would break the
63
+ * documented "nothing was ever promised to a caller that never asked" contract below and
64
+ * turn every legitimate first-time subscribe of a long-lived, intentionally-bounded run
65
+ * into a hard error. It exists so a caller that *cares* (a dashboard, a consumer with its
66
+ * own durability expectations) can distinguish "this run only ever had N events" from
67
+ * "this run had more, but some were evicted before I asked" instead of the two being
68
+ * silently indistinguishable on the wire.
69
+ */
70
+ readonly truncated?: true;
71
+ } | {
72
+ readonly kind: 'replay-gap';
73
+ readonly requestedCursor: string;
74
+ /** The oldest cursor the log can still furnish, or `null` if the run's log is currently empty. */
75
+ readonly oldestAvailableCursor: string | null;
76
+ } | {
77
+ readonly kind: 'invalid-cursor';
78
+ readonly requestedCursor: string;
79
+ } | {
80
+ readonly kind: 'unknown-run';
81
+ };
82
+ /**
83
+ * A replayable, ordered, per-run event log. Kernel port — `@jini-ai/daemon`
84
+ * ships `createInMemoryEventLog` as the reference implementation; a durable
85
+ * adapter (`@jini-ai/sqlite`, task 8) implements the same interface.
86
+ */
87
+ export interface EventLog {
88
+ /**
89
+ * Appends one event to `input.runId`'s ordered log.
90
+ *
91
+ * @returns The recorded entry (with its assigned cursor `id`), or the
92
+ * original entry unchanged if `input.dedupeKey` matches a prior append.
93
+ */
94
+ append<Payload>(input: EventLogAppendInput<Payload>): Promise<EventLogEntry<Payload>>;
95
+ /**
96
+ * Returns every entry recorded after `afterCursor` for `runId`, or a
97
+ * distinguishable non-`'ok'` result — see {@link EventLogReplayResult}.
98
+ * `afterCursor: null` means "from the beginning of whatever is still
99
+ * retained" (a first-time subscribe, not a reconnect, so a run whose
100
+ * earliest events were already evicted before this call is not a gap —
101
+ * nothing was ever promised to this caller).
102
+ */
103
+ replay(runId: string, afterCursor: string | null): Promise<EventLogReplayResult>;
104
+ /** Lists every run for which this log retains durable state. Used by a host at boot to rehydrate its `RunLifecycle` index. */
105
+ listRunIds(): Promise<readonly string[]>;
106
+ /** Discards all retained state for `runId` (e.g. once a terminal run's retention window has passed). */
107
+ drop(runId: string): Promise<void>;
108
+ }
109
+ export interface InMemoryEventLogOptions {
110
+ /**
111
+ * Maximum entries retained per run before the oldest are evicted. Eviction is opt-in: if
112
+ * omitted, retention is unbounded and nothing is ever silently dropped. Pass an explicit
113
+ * value only when bounded memory (or, for `@jini-ai/sqlite`, bounded disk) is a deliberate
114
+ * choice — the caller then owns the tradeoff, rather than inheriting a hidden 2000-entry
115
+ * cap OD's own in-memory ring happened to use.
116
+ */
117
+ readonly maxEntriesPerRun?: number;
118
+ }
119
+ /**
120
+ * Reference `EventLog` implementation: a per-run FIFO array, optionally capped at
121
+ * `maxEntriesPerRun` (opt-in — see {@link InMemoryEventLogOptions}), functionally a ring
122
+ * buffer once a cap is set (oldest entries evicted once the cap is exceeded) without needing
123
+ * an actual circular-index structure at this scale. This is the in-memory half only — no
124
+ * durable copy — matching this task's scope (a real persistent adapter is `@jini-ai/sqlite`'s
125
+ * job).
126
+ *
127
+ * @param options.maxEntriesPerRun - Retention cap per run, see {@link InMemoryEventLogOptions}.
128
+ * @returns An `EventLog` port implementation.
129
+ * @complexity `append`/`drop` are O(1) amortized (O(k) only on the eviction
130
+ * splice, k = entries over cap, which is normally 1). `replay` is O(n) in
131
+ * the number of retained entries for the run.
132
+ */
133
+ export declare function createInMemoryEventLog(options?: InMemoryEventLogOptions): EventLog;
134
+ //# sourceMappingURL=event-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../src/event-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,uDAAuD;AACvD,MAAM,WAAW,aAAa,CAAC,OAAO,GAAG,OAAO;IAC9C,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,uDAAuD;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB,CAAC,OAAO,GAAG,OAAO;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,oBAAoB,CAAC,OAAO,GAAG,OAAO,IAC9C;IACE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;IACpD;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC;CAC3B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kGAAkG;IAClG,QAAQ,CAAC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAErC;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,mBAAmB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACtF;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjF,8HAA8H;IAC9H,UAAU,IAAI,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IACzC,wGAAwG;IACxG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,QAAQ,CAgFtF"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `EventLog` — the durable event-log kernel port (extraction-plan §12 C1).
3
+ *
4
+ * OD's own event durability is split across three tiers today: a ~2000-event
5
+ * in-memory ring (`apps/daemon/src/runtimes/runs.ts` on the researched
6
+ * `arch/server-startserver-endgame` branch), a durable copy written into the
7
+ * product's own `messages.events_json` column, and a best-effort JSONL tail
8
+ * file that nothing ever reads back for replay. Because the durable half
9
+ * lives outside any port, a non-OD consumer that only wires the in-memory
10
+ * ring silently loses in-flight output on a long-run reload. `EventLog` is
11
+ * the fix: a single, storage-agnostic port that any adapter (this package's
12
+ * in-memory reference implementation, or `@jini-ai/sqlite`'s future durable
13
+ * adapter — task 8) can satisfy. Every method returns a `Promise` even
14
+ * though the in-memory implementation resolves synchronously, so a real
15
+ * persistent adapter is a drop-in swap rather than an API break
16
+ * (extraction-plan §2.6: "ports are async-only from day one").
17
+ */
18
+ /**
19
+ * Reference `EventLog` implementation: a per-run FIFO array, optionally capped at
20
+ * `maxEntriesPerRun` (opt-in — see {@link InMemoryEventLogOptions}), functionally a ring
21
+ * buffer once a cap is set (oldest entries evicted once the cap is exceeded) without needing
22
+ * an actual circular-index structure at this scale. This is the in-memory half only — no
23
+ * durable copy — matching this task's scope (a real persistent adapter is `@jini-ai/sqlite`'s
24
+ * job).
25
+ *
26
+ * @param options.maxEntriesPerRun - Retention cap per run, see {@link InMemoryEventLogOptions}.
27
+ * @returns An `EventLog` port implementation.
28
+ * @complexity `append`/`drop` are O(1) amortized (O(k) only on the eviction
29
+ * splice, k = entries over cap, which is normally 1). `replay` is O(n) in
30
+ * the number of retained entries for the run.
31
+ */
32
+ export function createInMemoryEventLog(options = {}) {
33
+ const maxEntriesPerRun = options.maxEntriesPerRun;
34
+ const runs = new Map();
35
+ function getOrCreateRunLog(runId) {
36
+ let runLog = runs.get(runId);
37
+ if (!runLog) {
38
+ runLog = { entries: [], nextId: 1, dedupeIndex: new Map() };
39
+ runs.set(runId, runLog);
40
+ }
41
+ return runLog;
42
+ }
43
+ return {
44
+ async append(input) {
45
+ const runLog = getOrCreateRunLog(input.runId);
46
+ if (input.dedupeKey !== undefined) {
47
+ const existing = runLog.dedupeIndex.get(input.dedupeKey);
48
+ if (existing) {
49
+ return existing;
50
+ }
51
+ }
52
+ const entry = {
53
+ id: String(runLog.nextId),
54
+ event: input.event,
55
+ data: input.data,
56
+ recordedAt: Date.now(),
57
+ };
58
+ runLog.nextId += 1;
59
+ runLog.entries.push(entry);
60
+ if (input.dedupeKey !== undefined) {
61
+ runLog.dedupeIndex.set(input.dedupeKey, entry);
62
+ }
63
+ if (maxEntriesPerRun !== undefined && runLog.entries.length > maxEntriesPerRun) {
64
+ runLog.entries.splice(0, runLog.entries.length - maxEntriesPerRun);
65
+ }
66
+ return entry;
67
+ },
68
+ async replay(runId, afterCursor) {
69
+ const runLog = runs.get(runId);
70
+ if (!runLog) {
71
+ return { kind: 'unknown-run' };
72
+ }
73
+ if (afterCursor === null) {
74
+ const oldest = runLog.entries[0];
75
+ const truncated = oldest !== undefined && Number(oldest.id) > 1;
76
+ return {
77
+ kind: 'ok',
78
+ entries: runLog.entries.slice(),
79
+ ...(truncated ? { truncated: true } : {}),
80
+ };
81
+ }
82
+ const afterCursorNum = Number(afterCursor);
83
+ if (!Number.isFinite(afterCursorNum)) {
84
+ return { kind: 'invalid-cursor', requestedCursor: afterCursor };
85
+ }
86
+ const oldestRetained = runLog.entries[0];
87
+ const oldestRetainedId = oldestRetained ? Number(oldestRetained.id) : runLog.nextId;
88
+ if (afterCursorNum < oldestRetainedId - 1) {
89
+ return {
90
+ kind: 'replay-gap',
91
+ requestedCursor: afterCursor,
92
+ oldestAvailableCursor: oldestRetained ? oldestRetained.id : null,
93
+ };
94
+ }
95
+ return {
96
+ kind: 'ok',
97
+ entries: runLog.entries.filter((entry) => Number(entry.id) > afterCursorNum),
98
+ };
99
+ },
100
+ async listRunIds() {
101
+ return Array.from(runs.keys()).sort();
102
+ },
103
+ async drop(runId) {
104
+ runs.delete(runId);
105
+ },
106
+ };
107
+ }
108
+ //# sourceMappingURL=event-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-log.js","sourceRoot":"","sources":["../src/event-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @module frontend-capability-tools
3
+ *
4
+ * The half of frontend control that makes it *reachable*. `FrontendSessionRegistry` can route a
5
+ * call to the surface bound to a run, but a registry is not something an agent can call — only a
6
+ * registered tool is. This module projects a frontend's capability manifest into
7
+ * `{descriptor, handler, policy}` triples a host registers into the same `ToolRegistry` its
8
+ * server-side tools live in.
9
+ *
10
+ * That projection is the entire point, and it is deliberately the ONLY door. Each handler is
11
+ * invoked by `ToolExecutor` *after* authorization, confirmation, timeout, cancellation, output
12
+ * truncation and audit have already run, and its whole body is "route this call and await the
13
+ * answer". A route that called `registry.invoke` directly would reach the same tab having skipped
14
+ * every one of those, which is precisely the second, weaker execution path this layering exists to
15
+ * prevent. There is no reason to add one: anything that can execute a tool can execute these.
16
+ *
17
+ * **Typed structurally, not against the frontend packages.** A capability spec here is anything
18
+ * with an `id` and a `description` — `@jini-ai/agentic`'s `CapabilityDef` satisfies it without
19
+ * knowing this module exists. Importing that type would point a server package at the browser
20
+ * vocabulary and invert the one-way edge the engine's layering depends on; the same reasoning as
21
+ * `@jini-ai/core`'s `RunRef`.
22
+ *
23
+ * **Every registration carries a timeout by default.** A surface that never answers — a
24
+ * backgrounded tab, a closed laptop, a tab killed between the call and the reply — is the normal
25
+ * case here, not the exotic one, and `FrontendSessionRegistry.invoke` only rejects when its
26
+ * caller's signal aborts. Without a `timeoutMs` on the descriptor that signal never fires and the
27
+ * run waits forever, so the default is applied rather than left to each host to remember.
28
+ */
29
+ import type { ToolPolicy, ToolRegistration } from '@jini-ai/core';
30
+ import type { FrontendSessionRegistry } from './frontend-session-registry.js';
31
+ /**
32
+ * Deny-by-default `ToolPolicy` for frontend capabilities, matching
33
+ * `terminal-session.ts`'s `denyAllTerminalCreatePolicy` and `db-ops.ts`'s `denyAllDaemonDbPolicy`.
34
+ *
35
+ * A permissive default was rejected for the same reason as those: these tools drive a real user's
36
+ * real screen — clicking controls, filling fields, navigating away from what they were reading —
37
+ * and a host must opt into that explicitly, not acquire it merely by registering a manifest.
38
+ */
39
+ export declare const denyAllFrontendCapabilityPolicy: ToolPolicy;
40
+ /**
41
+ * How long a handler waits for the surface before `ToolExecutor` aborts it and reports
42
+ * `'timed-out'`. Generous enough for a slow render or a busy main thread, short enough that a tab
43
+ * that is never coming back fails the tool call rather than the whole run.
44
+ */
45
+ export declare const DEFAULT_FRONTEND_CAPABILITY_TIMEOUT_MS = 30000;
46
+ /**
47
+ * The minimum a manifest entry must describe. Structural on purpose — see the module doc.
48
+ *
49
+ * Every field here exists on `@jini-ai/agentic`'s `CapabilityDef`, so a manifest satisfies this
50
+ * without conversion. The first version of this type accepted only `id`, `description`, and
51
+ * `requiresConfirmation` and silently dropped the rest, which made the projection **lossy**: a
52
+ * registered capability could no longer say what arguments it took or how dangerous it was, so
53
+ * nothing downstream could describe it to an agent or reason about it. Discovery cannot be correct
54
+ * on top of a projection that has already thrown the answer away.
55
+ */
56
+ export interface FrontendCapabilitySpec {
57
+ readonly id: string;
58
+ readonly description: string;
59
+ /** Surfaced on the descriptor so `ToolExecutor`'s confirmation gate enforces it. */
60
+ readonly requiresConfirmation?: boolean;
61
+ /** Carried onto `ToolDescriptor.inputSchema` so a discovery surface can state the arguments. */
62
+ readonly inputSchema?: unknown;
63
+ }
64
+ /**
65
+ * Deliberately NOT projected yet: `CapabilityDef`'s `risk` and `surface`.
66
+ *
67
+ * They belong on whatever a discovery surface returns — `risk` labels how dangerous a capability
68
+ * is, and `surface` says whether "no frontend is bound" is a temporary condition or a permanent
69
+ * refusal, which decides whether a caller should wait or give up. But nothing reads them until
70
+ * that surface exists, and a public type carrying fields no code consumes is indistinguishable
71
+ * from one carrying fields whose consumer was deleted. They graduate here when the catalog that
72
+ * needs them lands, together with the decision about whether they belong on `ToolDescriptor` (and
73
+ * so on `ToolRegistry.list()`) or are joined from the host's manifest.
74
+ *
75
+ * A host still loses nothing today: a `CapabilityDef` satisfies {@link FrontendCapabilitySpec}
76
+ * structurally with those fields intact on the value it already holds.
77
+ */
78
+ export interface CreateFrontendCapabilityRegistrationsOptions {
79
+ readonly registry: FrontendSessionRegistry;
80
+ /** The capabilities to expose. Ids become tool ids verbatim, so an agent names `page.click`. */
81
+ readonly capabilities: readonly FrontendCapabilitySpec[];
82
+ /** @default {@link denyAllFrontendCapabilityPolicy} — see its doc for why a permissive default was rejected. */
83
+ readonly policy?: ToolPolicy;
84
+ /** @default {@link DEFAULT_FRONTEND_CAPABILITY_TIMEOUT_MS} — see the module doc for why this is defaulted rather than optional. */
85
+ readonly timeoutMs?: number;
86
+ /** Passed through to every descriptor. Frontend output is untrusted text; a host that reads it back into a prompt should bound it. */
87
+ readonly maxOutputBytes?: number;
88
+ }
89
+ /**
90
+ * Builds one `ToolRegistration` per capability, each routing through `registry.invoke` on the
91
+ * calling run's bound surface.
92
+ *
93
+ * @param options - The registry to route through, the manifest to expose, and the gate to apply.
94
+ * @returns Registrations in manifest order, ready to hand to a `ToolRegistry` (or to
95
+ * `@jini-ai/node-host`'s `toolRegistrations` config option).
96
+ * @throws Never. A duplicate id is reported by `ToolRegistry.register` at registration time,
97
+ * naming the id — this function does not pre-empt that with a second, differently-worded error.
98
+ *
99
+ * @complexity O(n) in `capabilities`; each handler is O(1) plus the round trip to the surface.
100
+ */
101
+ export declare function createFrontendCapabilityRegistrations(options: CreateFrontendCapabilityRegistrationsOptions): readonly ToolRegistration[];
102
+ //# sourceMappingURL=frontend-capability-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontend-capability-tools.d.ts","sourceRoot":"","sources":["../src/frontend-capability-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,UAE7C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sCAAsC,QAAS,CAAC;AAE7D;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC,gGAAgG;IAChG,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,4CAA4C;IAC3D,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;IAC3C,gGAAgG;IAChG,QAAQ,CAAC,YAAY,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACzD,gHAAgH;IAChH,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,mIAAmI;IACnI,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,sIAAsI;IACtI,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAoBD;;;;;;;;;;;GAWG;AACH,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,4CAA4C,GACpD,SAAS,gBAAgB,EAAE,CAkC7B"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Deny-by-default `ToolPolicy` for frontend capabilities, matching
3
+ * `terminal-session.ts`'s `denyAllTerminalCreatePolicy` and `db-ops.ts`'s `denyAllDaemonDbPolicy`.
4
+ *
5
+ * A permissive default was rejected for the same reason as those: these tools drive a real user's
6
+ * real screen — clicking controls, filling fields, navigating away from what they were reading —
7
+ * and a host must opt into that explicitly, not acquire it merely by registering a manifest.
8
+ */
9
+ export const denyAllFrontendCapabilityPolicy = {
10
+ authorize: () => 'deny',
11
+ };
12
+ /**
13
+ * How long a handler waits for the surface before `ToolExecutor` aborts it and reports
14
+ * `'timed-out'`. Generous enough for a slow render or a busy main thread, short enough that a tab
15
+ * that is never coming back fails the tool call rather than the whole run.
16
+ */
17
+ export const DEFAULT_FRONTEND_CAPABILITY_TIMEOUT_MS = 30_000;
18
+ /**
19
+ * Normalizes a tool's `unknown` input into the record `FrontendSessionRegistry.invoke` takes.
20
+ *
21
+ * A missing input is `{}` — several capabilities legitimately take no arguments, and forcing a
22
+ * caller to send an empty object to use them would be noise. Anything else non-object is a caller
23
+ * error reported by name, not silently coerced into `{}`, which would turn "you sent a string"
24
+ * into the much more confusing "the capability says a required field is missing".
25
+ */
26
+ function toCapabilityInput(capabilityId, input) {
27
+ if (input === undefined || input === null)
28
+ return {};
29
+ if (typeof input !== 'object' || Array.isArray(input)) {
30
+ throw new Error(`${capabilityId}: input must be a JSON object, received ${Array.isArray(input) ? 'array' : typeof input}`);
31
+ }
32
+ return input;
33
+ }
34
+ /**
35
+ * Builds one `ToolRegistration` per capability, each routing through `registry.invoke` on the
36
+ * calling run's bound surface.
37
+ *
38
+ * @param options - The registry to route through, the manifest to expose, and the gate to apply.
39
+ * @returns Registrations in manifest order, ready to hand to a `ToolRegistry` (or to
40
+ * `@jini-ai/node-host`'s `toolRegistrations` config option).
41
+ * @throws Never. A duplicate id is reported by `ToolRegistry.register` at registration time,
42
+ * naming the id — this function does not pre-empt that with a second, differently-worded error.
43
+ *
44
+ * @complexity O(n) in `capabilities`; each handler is O(1) plus the round trip to the surface.
45
+ */
46
+ export function createFrontendCapabilityRegistrations(options) {
47
+ const { registry, capabilities, policy = denyAllFrontendCapabilityPolicy, timeoutMs = DEFAULT_FRONTEND_CAPABILITY_TIMEOUT_MS, maxOutputBytes, } = options;
48
+ return capabilities.map((capability) => ({
49
+ descriptor: {
50
+ id: capability.id,
51
+ description: capability.description,
52
+ timeoutMs,
53
+ ...(capability.requiresConfirmation !== undefined
54
+ ? { requiresConfirmation: capability.requiresConfirmation }
55
+ : {}),
56
+ // Absent rather than `undefined`: a descriptor that *has* an `inputSchema` key holding
57
+ // `undefined` is indistinguishable from one describing a schemaless tool once serialized,
58
+ // and a catalog would render "takes no arguments" for a capability that takes several.
59
+ ...(capability.inputSchema !== undefined ? { inputSchema: capability.inputSchema } : {}),
60
+ ...(maxOutputBytes !== undefined ? { maxOutputBytes } : {}),
61
+ },
62
+ policy,
63
+ // `ctx.signal` is `ToolExecutor`'s own timeout/cancel signal, so the descriptor's `timeoutMs`
64
+ // and a cancelled run both reach the pending invocation without this module owning a timer.
65
+ handler: async (ctx) => registry.invoke(ctx.run.id, capability.id, toCapabilityInput(capability.id, ctx.input), ctx.signal),
66
+ }));
67
+ }
68
+ //# sourceMappingURL=frontend-capability-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontend-capability-tools.js","sourceRoot":"","sources":["../src/frontend-capability-tools.ts"],"names":[],"mappings":"AA+BA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAe;IACzD,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM;CACxB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,sCAAsC,GAAG,MAAM,CAAC;AAgD7D;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,YAAoB,EAAE,KAAc;IAC7D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,GAAG,YAAY,2CAA2C,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAC1G,CAAC;IACJ,CAAC;IACD,OAAO,KAAgC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qCAAqC,CACnD,OAAqD;IAErD,MAAM,EACJ,QAAQ,EACR,YAAY,EACZ,MAAM,GAAG,+BAA+B,EACxC,SAAS,GAAG,sCAAsC,EAClD,cAAc,GACf,GAAG,OAAO,CAAC;IAEZ,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACvC,UAAU,EAAE;YACV,EAAE,EAAE,UAAU,CAAC,EAAE;YACjB,WAAW,EAAE,UAAU,CAAC,WAAW;YACnC,SAAS;YACT,GAAG,CAAC,UAAU,CAAC,oBAAoB,KAAK,SAAS;gBAC/C,CAAC,CAAC,EAAE,oBAAoB,EAAE,UAAU,CAAC,oBAAoB,EAAE;gBAC3D,CAAC,CAAC,EAAE,CAAC;YACP,uFAAuF;YACvF,0FAA0F;YAC1F,uFAAuF;YACvF,GAAG,CAAC,UAAU,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxF,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D;QACD,MAAM;QACN,8FAA8F;QAC9F,4FAA4F;QAC5F,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CACrB,QAAQ,CAAC,MAAM,CACb,GAAG,CAAC,GAAG,CAAC,EAAE,EACV,UAAU,CAAC,EAAE,EACb,iBAAiB,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,EAC3C,GAAG,CAAC,MAAM,CACX;KACJ,CAAC,CAAC,CAAC;AACN,CAAC"}