@jini-ai/daemon 0.3.8 → 0.5.1

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 (159) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +50 -8
  3. package/dist/agent-executor/contracts.d.ts +736 -0
  4. package/dist/agent-executor/contracts.js +28 -0
  5. package/dist/agent-executor/event-translation.d.ts +87 -0
  6. package/dist/agent-executor/event-translation.js +230 -0
  7. package/dist/agent-executor/events.d.ts +190 -0
  8. package/dist/agent-executor/events.js +848 -0
  9. package/dist/agent-executor/handoff.d.ts +18 -0
  10. package/dist/agent-executor/handoff.js +16 -0
  11. package/dist/agent-executor/index.d.ts +98 -0
  12. package/dist/agent-executor/index.js +98 -0
  13. package/dist/agent-executor/launch.d.ts +375 -0
  14. package/dist/agent-executor/launch.js +1001 -0
  15. package/dist/agent-executor/mcp-bridge.d.ts +338 -0
  16. package/dist/agent-executor/mcp-bridge.js +835 -0
  17. package/dist/agent-executor/values.d.ts +5 -0
  18. package/dist/agent-executor/values.js +13 -0
  19. package/dist/agent-executor.d.ts +375 -176
  20. package/dist/agent-executor.js +388 -335
  21. package/dist/close-status.d.ts +11 -8
  22. package/dist/close-status.js +31 -19
  23. package/dist/continuation/continuation-transport.d.ts +3 -1
  24. package/dist/continuation/continuation-transport.js +1 -1
  25. package/dist/continuation/journal.d.ts +12 -5
  26. package/dist/continuation/journal.js +4 -4
  27. package/dist/continuation/run-scoped-context-store.d.ts +13 -4
  28. package/dist/continuation/run-scoped-context-store.js +6 -6
  29. package/dist/continuation/run-start-handler.d.ts +1 -1
  30. package/dist/continuation/run-start-handler.js +3 -16
  31. package/dist/delegated-tool-bridge.d.ts +7 -3
  32. package/dist/delegated-tool-bridge.js +32 -31
  33. package/dist/event-log.d.ts +7 -5
  34. package/dist/event-log.js +12 -11
  35. package/dist/frontend-capability-tools.d.ts +1 -1
  36. package/dist/frontend-capability-tools.js +3 -2
  37. package/dist/frontend-session-registry.d.ts +32 -9
  38. package/dist/frontend-session-registry.js +12 -11
  39. package/dist/http/active-context.d.ts +105 -0
  40. package/dist/http/active-context.js +67 -0
  41. package/dist/http/agents.d.ts +68 -0
  42. package/dist/http/agents.js +12 -0
  43. package/dist/http/attachments.d.ts +618 -0
  44. package/dist/http/attachments.js +1034 -0
  45. package/dist/http/cancel-owned-runs.d.ts +49 -0
  46. package/dist/http/cancel-owned-runs.js +35 -0
  47. package/dist/http/component-catalog.d.ts +84 -0
  48. package/dist/http/component-catalog.js +64 -0
  49. package/dist/http/connectors.d.ts +209 -0
  50. package/dist/http/connectors.js +409 -0
  51. package/dist/http/daemon-status.d.ts +76 -0
  52. package/dist/http/daemon-status.js +55 -0
  53. package/dist/http/db-ops.d.ts +123 -0
  54. package/dist/http/db-ops.js +189 -0
  55. package/dist/http/delegated-tools.d.ts +113 -0
  56. package/dist/http/delegated-tools.js +230 -0
  57. package/dist/http/frontend-control.d.ts +136 -0
  58. package/dist/http/frontend-control.js +79 -0
  59. package/dist/http/frontend-sessions.d.ts +110 -0
  60. package/dist/http/frontend-sessions.js +180 -0
  61. package/dist/http/health.d.ts +95 -0
  62. package/dist/http/health.js +40 -0
  63. package/dist/http/host-tools.d.ts +214 -0
  64. package/dist/http/host-tools.js +329 -0
  65. package/dist/http/index.d.ts +52 -0
  66. package/dist/http/index.js +27 -0
  67. package/dist/http/media.d.ts +146 -0
  68. package/dist/http/media.js +346 -0
  69. package/dist/http/memory.d.ts +265 -0
  70. package/dist/http/memory.js +328 -0
  71. package/dist/http/model-proxy.d.ts +49 -0
  72. package/dist/http/model-proxy.js +642 -0
  73. package/dist/http/remote-run-events.d.ts +70 -0
  74. package/dist/http/remote-run-events.js +224 -0
  75. package/dist/http/research.d.ts +48 -0
  76. package/dist/http/research.js +315 -0
  77. package/dist/http/route-manifest.d.ts +35 -0
  78. package/dist/http/route-manifest.js +103 -0
  79. package/dist/http/routines.d.ts +120 -0
  80. package/dist/http/routines.js +298 -0
  81. package/dist/http/run-credentials.d.ts +73 -0
  82. package/dist/http/run-credentials.js +71 -0
  83. package/dist/http/runs.d.ts +91 -0
  84. package/dist/http/runs.js +221 -0
  85. package/dist/http/terminals.d.ts +138 -0
  86. package/dist/http/terminals.js +325 -0
  87. package/dist/http/tool-catalog.d.ts +78 -0
  88. package/dist/http/tool-catalog.js +66 -0
  89. package/dist/http/workspace-root.d.ts +77 -0
  90. package/dist/http/workspace-root.js +63 -0
  91. package/dist/http/xai.d.ts +90 -0
  92. package/dist/http/xai.js +655 -0
  93. package/dist/image-prompt-delivery.d.ts +13 -3
  94. package/dist/image-prompt-delivery.js +5 -5
  95. package/dist/index.d.ts +556 -4
  96. package/dist/index.js +556 -4
  97. package/dist/legacy-data-migration.d.ts +59 -8
  98. package/dist/legacy-data-migration.js +38 -33
  99. package/dist/read-only-tools.d.ts +130 -0
  100. package/dist/read-only-tools.js +115 -0
  101. package/dist/remote-tool-bridge.d.ts +11 -5
  102. package/dist/remote-tool-bridge.js +15 -15
  103. package/dist/routines/index.d.ts +1 -1
  104. package/dist/routines/index.js +1 -1
  105. package/dist/routines/routine-store.d.ts +34 -12
  106. package/dist/routines/routine-store.js +21 -20
  107. package/dist/routines/schedule.d.ts +48 -10
  108. package/dist/routines/schedule.js +21 -21
  109. package/dist/routines/scheduler.d.ts +32 -11
  110. package/dist/routines/scheduler.js +61 -57
  111. package/dist/routines/types.d.ts +10 -3
  112. package/dist/run/core/result.d.ts +6 -2
  113. package/dist/run/core/result.js +3 -3
  114. package/dist/run/core/retry.d.ts +17 -4
  115. package/dist/run/core/retry.js +7 -11
  116. package/dist/run/diagnostics/diagnostics.d.ts +14 -4
  117. package/dist/run/diagnostics/diagnostics.js +7 -7
  118. package/dist/run/index.d.ts +1 -1
  119. package/dist/run/index.js +1 -1
  120. package/dist/run-credentials.d.ts +138 -0
  121. package/dist/run-credentials.js +152 -0
  122. package/dist/run-lifecycle.d.ts +92 -33
  123. package/dist/run-lifecycle.js +81 -74
  124. package/dist/scheduler.d.ts +8 -0
  125. package/dist/scheduler.js +2 -0
  126. package/dist/session-coordination.d.ts +115 -0
  127. package/dist/session-coordination.js +111 -0
  128. package/dist/store/agent-sessions/errors.d.ts +8 -0
  129. package/dist/store/agent-sessions/errors.js +10 -0
  130. package/dist/store/agent-sessions/index.d.ts +12 -0
  131. package/dist/store/agent-sessions/index.js +19 -0
  132. package/dist/store/agent-sessions/pglite.d.ts +12 -0
  133. package/dist/store/agent-sessions/pglite.js +7 -0
  134. package/dist/store/agent-sessions/ports.d.ts +24 -0
  135. package/dist/store/agent-sessions/ports.js +2 -0
  136. package/dist/store/agent-sessions/postgres.d.ts +12 -0
  137. package/dist/store/agent-sessions/postgres.js +7 -0
  138. package/dist/store/agent-sessions/schema.d.ts +3 -0
  139. package/dist/store/agent-sessions/schema.js +23 -0
  140. package/dist/store/agent-sessions/sql.d.ts +35 -0
  141. package/dist/store/agent-sessions/sql.js +59 -0
  142. package/dist/store/agent-sessions/sqlite.d.ts +117 -0
  143. package/dist/store/agent-sessions/sqlite.js +128 -0
  144. package/dist/store/event-log/sqlite.d.ts +75 -0
  145. package/dist/store/event-log/sqlite.js +230 -0
  146. package/dist/surface-exchanges.d.ts +97 -0
  147. package/dist/surface-exchanges.js +153 -0
  148. package/dist/terminal-session.d.ts +43 -11
  149. package/dist/terminal-session.js +22 -20
  150. package/dist/tokens.js +4 -4
  151. package/dist/tool-audit.d.ts +114 -0
  152. package/dist/tool-audit.js +95 -0
  153. package/dist/tool-executor.d.ts +22 -7
  154. package/dist/tool-executor.js +16 -16
  155. package/dist/tool-result-media.d.ts +3 -1
  156. package/dist/tool-result-media.js +1 -1
  157. package/dist/tool-result-surfaces.d.ts +3 -1
  158. package/dist/tool-result-surfaces.js +1 -1
  159. package/package.json +127 -9
@@ -0,0 +1,123 @@
1
+ import type { Express } from 'express';
2
+ import type { Principal, ToolPolicy, ToolRegistration } from '@jini-ai/core';
3
+ import type { ToolExecutor } from '../index.js';
4
+ import { type AdapterContext } from '@jini-ai/http-kit';
5
+ /** The `ToolRegistry` id `GET /api/daemon/db` executes through. */
6
+ export declare const DB_INSPECT_TOOL_ID = "daemon.db.inspect";
7
+ /** The `ToolRegistry` id `POST /api/daemon/db/verify` executes through. */
8
+ export declare const DB_VERIFY_TOOL_ID = "daemon.db.verify";
9
+ /** The `ToolRegistry` id `POST /api/daemon/db/vacuum` executes through. */
10
+ export declare const DB_VACUUM_TOOL_ID = "daemon.db.vacuum";
11
+ export interface DaemonDbTableInfo {
12
+ readonly name: string;
13
+ readonly rowCount: number;
14
+ }
15
+ /** Structurally identical to `@jini-ai/sqlite-chat`'s `DaemonDbStatusReport` — defined locally so this package incurs no `better-sqlite3` dependency. */
16
+ export interface DaemonDbStatusReport {
17
+ readonly kind: 'sqlite' | 'postgres';
18
+ readonly location: string;
19
+ readonly sizeBytes: number;
20
+ readonly schemaVersion: number | null;
21
+ readonly tables: readonly DaemonDbTableInfo[];
22
+ readonly generatedAt: number;
23
+ }
24
+ export type DbIntegrityIssueKind = 'integrity' | 'foreign_key';
25
+ export interface DbIntegrityIssue {
26
+ readonly kind: DbIntegrityIssueKind;
27
+ readonly message: string;
28
+ }
29
+ /** Structurally identical to `@jini-ai/sqlite-chat`'s `DbIntegrityReport`. */
30
+ export interface DbIntegrityReport {
31
+ readonly ok: boolean;
32
+ readonly mode: 'integrity_check' | 'quick_check';
33
+ readonly issues: readonly DbIntegrityIssue[];
34
+ readonly elapsedMs: number;
35
+ readonly generatedAt: number;
36
+ }
37
+ export interface DaemonDbVacuumResult {
38
+ readonly ok: true;
39
+ readonly beforeBytes: number;
40
+ readonly afterBytes: number;
41
+ readonly reclaimedBytes: number;
42
+ readonly elapsedMs: number;
43
+ }
44
+ /** The real collaborator a host injects — typically backed by `@jini-ai/sqlite-chat`'s `inspectSqliteDatabase`/`verifySqliteIntegrity` plus a small `vacuum` wrapper around `db.exec('VACUUM')`. */
45
+ export interface DaemonDbOperations {
46
+ inspect(): Promise<DaemonDbStatusReport> | DaemonDbStatusReport;
47
+ verify({ quick }: {
48
+ readonly quick: boolean;
49
+ }, _optional?: Record<string, never>): Promise<DbIntegrityReport> | DbIntegrityReport;
50
+ vacuum(): Promise<DaemonDbVacuumResult> | DaemonDbVacuumResult;
51
+ }
52
+ /**
53
+ * Deny-by-default `ToolPolicy` shared by all three DB tools — every call is
54
+ * denied, unconditionally, regardless of principal. Matching
55
+ * `@jini-ai/deploy`'s `denyAllDeployPublishPolicy` precedent: a host must
56
+ * explicitly opt in with its own policy (e.g. role-gated, or "same principal
57
+ * that owns the daemon process") rather than getting a working DB inspector
58
+ * for free merely by registering the tools. `inspect`/`verify` disclose
59
+ * internal schema and row-count information; `vacuum` rewrites the database
60
+ * file in place — none of the three has a safe "mostly harmless" default the
61
+ * way a pure read-only, non-sensitive tool might.
62
+ */
63
+ export declare const denyAllDaemonDbPolicy: ToolPolicy;
64
+ export interface CreateDaemonDbToolRegistrationsOptions {
65
+ readonly operations: DaemonDbOperations;
66
+ }
67
+ export interface DaemonDbToolRegistrations {
68
+ readonly inspect: ToolRegistration;
69
+ readonly verify: ToolRegistration;
70
+ readonly vacuum: ToolRegistration;
71
+ }
72
+ /**
73
+ * Builds the three `{descriptor, handler, policy}` triples a host registers
74
+ * against `@jini-ai/core`'s `ToolRegistry` so the DB operations become
75
+ * reachable only via `ToolExecutor.execute(principal, run, toolId, input)` —
76
+ * never by a route calling `operations.inspect()`/`verify()`/`vacuum()`
77
+ * directly, which would bypass authorization and the audit trail entirely.
78
+ *
79
+ * @complexity O(1) to build the three registrations; each handler's own cost is `operations`'s.
80
+ * @overallScore 100/100
81
+ */
82
+ export declare function createDaemonDbToolRegistrations(requiredArgs: CreateDaemonDbToolRegistrationsOptions, optionalArgs?: {
83
+ /** Defaults to {@link denyAllDaemonDbPolicy} — see its doc for why a permissive default was rejected. Applies to all three tools; pass distinct `ToolRegistration.policy` values by registering them individually if a host wants per-operation rules. */
84
+ readonly policy?: ToolPolicy;
85
+ readonly requiresConfirmation?: boolean;
86
+ readonly timeoutMs?: number;
87
+ }): DaemonDbToolRegistrations;
88
+ /** Diagnostic detail for an internal-error response the public API deliberately does not disclose (SEC-005, matching `runs.ts`'s `RunInternalErrorContext`): a `failed`/`timed-out`/`cancelled` tool execution can embed schema names, file paths, or a raw SQLite error message. */
89
+ export interface DaemonDbInternalErrorContext {
90
+ readonly source: 'db-inspect' | 'db-verify' | 'db-vacuum';
91
+ readonly correlationId: string;
92
+ readonly error: unknown;
93
+ }
94
+ export interface DaemonDbHttpDeps {
95
+ readonly toolExecutor: ToolExecutor;
96
+ /**
97
+ * The identity these routes execute tool calls as. A fixed, host-supplied
98
+ * value rather than derived per-request — this transport has no session/
99
+ * identity subsystem of its own (see `packages/http/archived provenance ledger`'s
100
+ * `ExecutionDelegate` design-decision note); a host that needs per-request
101
+ * principals should resolve one from its own auth layer and construct
102
+ * `DaemonDbHttpDeps` per request, or supply a `ToolPolicy` that inspects
103
+ * something other than `Principal` to make its decision.
104
+ */
105
+ readonly principal: Principal;
106
+ /** Host-owned sink for the real exception behind a generic `INTERNAL_ERROR` response (SEC-005). Defaults to `console.error`. */
107
+ readonly onInternalError?: (context: DaemonDbInternalErrorContext) => void;
108
+ }
109
+ /** `GET /api/daemon/db` — the database inventory (schema version, tables, row counts, size). */
110
+ export declare const daemonDbInspectRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, DaemonDbStatusReport, DaemonDbHttpDeps>;
111
+ /** `POST /api/daemon/db/verify?quick=` — `PRAGMA integrity_check`/`quick_check` plus `foreign_key_check`. */
112
+ export declare const daemonDbVerifyRoute: import("@jini-ai/http-kit").JsonRouteSpec<{
113
+ quick: boolean;
114
+ }, DbIntegrityReport, DaemonDbHttpDeps>;
115
+ /** `POST /api/daemon/db/vacuum` — runs `VACUUM` and reports bytes reclaimed. */
116
+ export declare const daemonDbVacuumRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, DaemonDbVacuumResult, DaemonDbHttpDeps>;
117
+ /** Mounts all three DB-ops routes on `app`. A pack's `http({ app, services })` calls this directly. */
118
+ export declare function registerDaemonDbRoutes({ app, deps, adapter }: {
119
+ readonly app: Express;
120
+ readonly deps: DaemonDbHttpDeps;
121
+ readonly adapter: AdapterContext;
122
+ }, _optional?: Record<string, never>): void;
123
+ //# sourceMappingURL=db-ops.d.ts.map
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @module db-ops
3
+ *
4
+ * Daemon DB inspect/verify/vacuum operations, wired through the
5
+ * tool-execution boundary (`@jini-ai/core`'s `tool-registry.ts` + `@jini-ai/daemon`'s
6
+ * `tool-executor.ts`) rather than called directly from a route handler.
7
+ * Ported from OD's `apps/daemon/src/routes/daemon.ts` `GET /api/daemon/db`,
8
+ * `POST /api/daemon/db/verify`, `POST /api/daemon/db/vacuum` (see
9
+ * `archived provenance ledger`'s routes-classification table, row `#12 daemon.ts`: "generic
10
+ * in shape but depend on a separate `storage/db-inspect.ts` port not built
11
+ * this round" — that port now exists as `@jini-ai/sqlite-chat`'s `db-inspect.ts`).
12
+ *
13
+ * This module does **not** depend on `@jini-ai/sqlite-chat` or `better-sqlite3` at
14
+ * all: `DaemonDbOperations` is a plain injected interface (structurally
15
+ * compatible with `@jini-ai/sqlite-chat`'s `inspectSqliteDatabase`/
16
+ * `verifySqliteIntegrity` return shapes, so a caller can wire those in with
17
+ * zero adapter code) — the same "caller supplies the real collaborator"
18
+ * convention `daemon-status.ts`/`host-tools.ts` already established in this
19
+ * package.
20
+ *
21
+ * **Why the tool-execution boundary, not a plain route handler calling
22
+ * `DaemonDbOperations` directly:** these three operations reveal internal
23
+ * schema/row-count/file-size information (`inspect`/`verify`) and rewrite the
24
+ * database file in place (`vacuum`) — real security stakes the task brief
25
+ * that added this module named explicitly. OD's own origin gated only
26
+ * `verify`/`vacuum` behind `requireLocalDaemonRequest` and left `GET
27
+ * /api/daemon/db` completely ungated — a real pre-existing gap, not carried
28
+ * forward here. Per this repo's binding tool-execution-boundary precedent
29
+ * (`packages/deploy/src/tool.ts`'s `deploy.publish`, `packages/daemon/src/
30
+ * delegated-tool-bridge.ts`), the actual work happens only inside a
31
+ * `ToolHandler` `ToolExecutor.execute` invokes after `ToolPolicy.authorize`
32
+ * allows it — never directly from `registerDaemonDbRoutes`' handlers. All
33
+ * three routes also keep `requireSameOrigin: true` as a first line of
34
+ * defense (matching `daemon-status.ts`'s shutdown route), but that transport
35
+ * gate is defense in depth, not a substitute for the tool-execution
36
+ * boundary — see {@link denyAllDaemonDbPolicy}'s doc for why the default
37
+ * policy denies every call rather than allowing one.
38
+ */
39
+ import { randomUUID } from 'node:crypto';
40
+ import { createApiError } from '@jini-ai/protocol';
41
+ import { defineJsonRoute, mountJsonRoute } from '@jini-ai/http-kit';
42
+ import { validationError } from '@jini-ai/http-kit';
43
+ import { err, ok } from '@jini-ai/http-kit';
44
+ /** The `ToolRegistry` id `GET /api/daemon/db` executes through. */
45
+ export const DB_INSPECT_TOOL_ID = 'daemon.db.inspect';
46
+ /** The `ToolRegistry` id `POST /api/daemon/db/verify` executes through. */
47
+ export const DB_VERIFY_TOOL_ID = 'daemon.db.verify';
48
+ /** The `ToolRegistry` id `POST /api/daemon/db/vacuum` executes through. */
49
+ export const DB_VACUUM_TOOL_ID = 'daemon.db.vacuum';
50
+ /**
51
+ * Deny-by-default `ToolPolicy` shared by all three DB tools — every call is
52
+ * denied, unconditionally, regardless of principal. Matching
53
+ * `@jini-ai/deploy`'s `denyAllDeployPublishPolicy` precedent: a host must
54
+ * explicitly opt in with its own policy (e.g. role-gated, or "same principal
55
+ * that owns the daemon process") rather than getting a working DB inspector
56
+ * for free merely by registering the tools. `inspect`/`verify` disclose
57
+ * internal schema and row-count information; `vacuum` rewrites the database
58
+ * file in place — none of the three has a safe "mostly harmless" default the
59
+ * way a pure read-only, non-sensitive tool might.
60
+ */
61
+ export const denyAllDaemonDbPolicy = {
62
+ authorize: () => 'deny',
63
+ };
64
+ function isRecord(value) {
65
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
66
+ }
67
+ /**
68
+ * Builds the three `{descriptor, handler, policy}` triples a host registers
69
+ * against `@jini-ai/core`'s `ToolRegistry` so the DB operations become
70
+ * reachable only via `ToolExecutor.execute(principal, run, toolId, input)` —
71
+ * never by a route calling `operations.inspect()`/`verify()`/`vacuum()`
72
+ * directly, which would bypass authorization and the audit trail entirely.
73
+ *
74
+ * @complexity O(1) to build the three registrations; each handler's own cost is `operations`'s.
75
+ * @overallScore 100/100
76
+ */
77
+ export function createDaemonDbToolRegistrations(requiredArgs, optionalArgs = {}) {
78
+ const { operations } = requiredArgs;
79
+ const { policy = denyAllDaemonDbPolicy, requiresConfirmation, timeoutMs } = optionalArgs;
80
+ const descriptorExtras = {
81
+ ...(requiresConfirmation !== undefined ? { requiresConfirmation } : {}),
82
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
83
+ };
84
+ return {
85
+ inspect: {
86
+ descriptor: {
87
+ id: DB_INSPECT_TOOL_ID,
88
+ description: 'Inspects the daemon SQLite database: schema version, table list, row counts, on-disk size.',
89
+ ...descriptorExtras,
90
+ },
91
+ policy,
92
+ handler: async () => operations.inspect(),
93
+ },
94
+ verify: {
95
+ descriptor: {
96
+ id: DB_VERIFY_TOOL_ID,
97
+ description: 'Runs a SQLite integrity/foreign-key check against the daemon database.',
98
+ ...descriptorExtras,
99
+ },
100
+ policy,
101
+ handler: async (ctx) => {
102
+ const input = isRecord(ctx.input) ? ctx.input : {};
103
+ return operations.verify({ quick: input.quick === true });
104
+ },
105
+ },
106
+ vacuum: {
107
+ descriptor: {
108
+ id: DB_VACUUM_TOOL_ID,
109
+ description: 'Runs SQLite VACUUM against the daemon database and reports reclaimed bytes.',
110
+ ...descriptorExtras,
111
+ },
112
+ policy,
113
+ handler: async () => operations.vacuum(),
114
+ },
115
+ };
116
+ }
117
+ function defaultDaemonDbInternalErrorSink(context) {
118
+ // eslint-disable-next-line no-console
119
+ console.error(`[@jini-ai/http-kit] internal error (${context.source}, correlationId=${context.correlationId})`, context.error);
120
+ }
121
+ /** Every DB-ops call gets its own opaque, single-use `RunRef` — these are one-off operations requests, not steps of a longer-lived agent run, so there is no real run to attach the audit trail to. */
122
+ function freshRun() {
123
+ return { id: randomUUID() };
124
+ }
125
+ function toolResultToApiResult(deps, source, result) {
126
+ switch (result.status) {
127
+ case 'completed':
128
+ return ok({ value: result.output });
129
+ case 'denied':
130
+ return err({ error: createApiError({ code: 'TOOL_OPERATION_DENIED', message: 'this operation was denied by policy' }) });
131
+ case 'confirmation-denied':
132
+ return err({ error: createApiError({ code: 'TOOL_OPERATION_DENIED', message: 'this operation was denied during confirmation' }) });
133
+ case 'timed-out':
134
+ case 'cancelled':
135
+ case 'failed': {
136
+ const correlationId = randomUUID();
137
+ const sink = deps.onInternalError ?? defaultDaemonDbInternalErrorSink;
138
+ sink({ source, correlationId, error: result.status === 'failed' ? result.error : result.status });
139
+ return err({ error: createApiError({ code: 'INTERNAL_ERROR', message: 'an internal error occurred' }, { requestId: correlationId }) });
140
+ }
141
+ }
142
+ }
143
+ /** `GET /api/daemon/db` — the database inventory (schema version, tables, row counts, size). */
144
+ export const daemonDbInspectRoute = defineJsonRoute({
145
+ method: 'get',
146
+ path: '/api/daemon/db',
147
+ parse: () => ok({ value: undefined }),
148
+ handle: async ({ input: _input, deps }) => {
149
+ const result = await deps.toolExecutor.execute({ principal: deps.principal, run: freshRun(), toolId: DB_INSPECT_TOOL_ID, input: undefined });
150
+ return toolResultToApiResult(deps, 'db-inspect', result);
151
+ },
152
+ }, { requireSameOrigin: true });
153
+ function parseDbVerifyInput(input) {
154
+ const raw = input.query.quick;
155
+ if (raw === undefined)
156
+ return ok({ value: { quick: false } });
157
+ if (typeof raw !== 'string') {
158
+ return err({ error: validationError({ message: 'quick must be a single query-string value when provided' }) });
159
+ }
160
+ const normalized = raw.toLowerCase();
161
+ return ok({ value: { quick: normalized === '1' || normalized === 'true' } });
162
+ }
163
+ /** `POST /api/daemon/db/verify?quick=` — `PRAGMA integrity_check`/`quick_check` plus `foreign_key_check`. */
164
+ export const daemonDbVerifyRoute = defineJsonRoute({
165
+ method: 'post',
166
+ path: '/api/daemon/db/verify',
167
+ parse: parseDbVerifyInput,
168
+ handle: async ({ input, deps }) => {
169
+ const result = await deps.toolExecutor.execute({ principal: deps.principal, run: freshRun(), toolId: DB_VERIFY_TOOL_ID, input });
170
+ return toolResultToApiResult(deps, 'db-verify', result);
171
+ },
172
+ }, { requireSameOrigin: true });
173
+ /** `POST /api/daemon/db/vacuum` — runs `VACUUM` and reports bytes reclaimed. */
174
+ export const daemonDbVacuumRoute = defineJsonRoute({
175
+ method: 'post',
176
+ path: '/api/daemon/db/vacuum',
177
+ parse: () => ok({ value: undefined }),
178
+ handle: async ({ input: _input, deps }) => {
179
+ const result = await deps.toolExecutor.execute({ principal: deps.principal, run: freshRun(), toolId: DB_VACUUM_TOOL_ID, input: undefined });
180
+ return toolResultToApiResult(deps, 'db-vacuum', result);
181
+ },
182
+ }, { requireSameOrigin: true });
183
+ /** Mounts all three DB-ops routes on `app`. A pack's `http({ app, services })` calls this directly. */
184
+ export function registerDaemonDbRoutes({ app, deps, adapter }, _optional = {}) {
185
+ mountJsonRoute({ app, spec: daemonDbInspectRoute, deps, adapter });
186
+ mountJsonRoute({ app, spec: daemonDbVerifyRoute, deps, adapter });
187
+ mountJsonRoute({ app, spec: daemonDbVacuumRoute, deps, adapter });
188
+ }
189
+ //# sourceMappingURL=db-ops.js.map
@@ -0,0 +1,113 @@
1
+ import type { Express } from 'express';
2
+ import type { Principal, ToolRegistry } from '@jini-ai/core';
3
+ import { type RunLifecycle, type ToolExecutionResult, type ToolExecutor } from '../index.js';
4
+ import { type AdapterContext } from '@jini-ai/http-kit';
5
+ export interface DelegatedToolExecuteRequest {
6
+ readonly runId: string;
7
+ readonly toolUseId: string;
8
+ readonly toolId: string;
9
+ readonly input?: unknown;
10
+ /**
11
+ * When `true`, the call is refused unless `toolId`'s registration declares itself read-only
12
+ * (`@jini-ai/core`'s `isReadOnlyTool`). Set by a caller that has already promised ITS own caller
13
+ * that the surface only reads — `@jini-ai/mcp`'s `execute_readonly_delegated_tool`, whose
14
+ * `readOnlyHint: true` annotation is exactly such a promise.
15
+ *
16
+ * The constraint is enforced HERE rather than in the calling process because only this side can
17
+ * resolve a tool id against the live `ToolRegistry`. A caller-side check would be a claim; this
18
+ * is the fact.
19
+ *
20
+ * Omitted means unconstrained, which is what every existing caller sends — the original
21
+ * `execute_delegated_tool` path is untouched by this field's existence.
22
+ */
23
+ readonly requireReadOnly?: boolean;
24
+ }
25
+ export interface DelegatedToolExecuteResponse {
26
+ readonly result: ToolExecutionResult;
27
+ }
28
+ /**
29
+ * Diagnostic detail for an internal-error response the public API deliberately does not
30
+ * disclose (SEC-005), matching `runs.ts`'s `RunInternalErrorContext` precedent: a thrown
31
+ * `ToolExecutor`/registry failure can embed tool-handler internals no HTTP caller should see.
32
+ */
33
+ export interface DelegatedToolsInternalErrorContext {
34
+ readonly source: 'delegated-tool-execute' | 'resolve-principal';
35
+ readonly runId: string;
36
+ readonly toolId: string;
37
+ readonly correlationId: string;
38
+ readonly error: unknown;
39
+ /**
40
+ * The settled status when the failure is a `ToolExecutionResult` this route will not show as-is
41
+ * (`timed-out`, `cancelled`, or a `failed` the host did not mark model-safe); absent when `error`
42
+ * is a thrown exception. Lets a host word a timeout differently from a crash without parsing
43
+ * `error`, which for a settled result is only `result.error ?? result.status`.
44
+ */
45
+ readonly status?: 'timed-out' | 'cancelled' | 'failed';
46
+ }
47
+ export interface DelegatedToolsHttpDeps {
48
+ readonly lifecycle: RunLifecycle;
49
+ readonly toolExecutor: ToolExecutor;
50
+ /** Host-owned: resolves the `Principal` a given delegated-tool-call request executes as. Mandatory — see module doc; there is no safe default identity this package could assume on a host's behalf. */
51
+ readonly resolvePrincipal: (requiredArgs: {
52
+ readonly request: DelegatedToolExecuteRequest;
53
+ }) => Principal | Promise<Principal>;
54
+ /** Host-owned sink for the real exception behind a generic `INTERNAL_ERROR` response (SEC-005). Defaults to `console.error`. */
55
+ readonly onInternalError?: (context: DelegatedToolsInternalErrorContext) => void;
56
+ /**
57
+ * The same `ToolRegistry` `deps.toolExecutor` was built over, supplied so a
58
+ * {@link DelegatedToolExecuteRequest.requireReadOnly} call can be checked against the descriptor
59
+ * that will actually run. Descriptors only — this route never gains a way to reach a handler.
60
+ *
61
+ * Optional so that mounting this route stays a non-breaking one-liner for every host that
62
+ * predates the constraint. A host that omits it does not get a WEAKER gate: a `requireReadOnly`
63
+ * call it cannot verify is refused outright (the configured refusal message), never
64
+ * waived. Unconstrained calls behave identically with or without it.
65
+ */
66
+ readonly toolRegistry?: ToolRegistry;
67
+ /**
68
+ * Host opt-in: returns `true` for a `failed` result whose `error` text the HOST has already made
69
+ * safe to show the model — secret values blanked, an error id minted — so this route answers it
70
+ * with `422 TOOL_EXECUTION_FAILED` carrying that text, not the SEC-005-redacted 500. The model then
71
+ * sees why the tool failed (e.g. "no frontend is bound…") instead of an opaque INTERNAL_ERROR.
72
+ *
73
+ * Only consulted for `status: 'failed'` with a non-empty `error`; every other outcome, and every
74
+ * failure it returns `false` for, keeps the existing mapping. Omitted (the default), nothing
75
+ * changes — a host that has no redaction layer must not wire this, because this route does no
76
+ * redaction of its own on this path.
77
+ */
78
+ readonly isModelSafeToolFailure?: ({ result }: {
79
+ result: ToolExecutionResult;
80
+ }) => boolean;
81
+ /**
82
+ * Host opt-in: the model-safe text to send in place of `'an internal error occurred'` for every
83
+ * failure this route would otherwise answer with the SEC-005-redacted `500 INTERNAL_ERROR` — a
84
+ * thrown executor (e.g. an unknown tool id), a throwing `resolvePrincipal`, `timed-out`,
85
+ * `cancelled`, and a `failed` result `isModelSafeToolFailure` did not vouch for. Without it, those
86
+ * reach the model as a bare "INTERNAL_ERROR" it can neither act on nor report.
87
+ *
88
+ * The response keeps `500 INTERNAL_ERROR` and its `requestId`; only `message` changes, and
89
+ * `onInternalError` still receives the raw failure first. Returning `undefined` or `''` keeps the
90
+ * generic message for that failure, and so does a describer that throws — a broken describer can
91
+ * never turn a 500 into a leak or a crash. Omitted (the default), nothing changes. The host owns
92
+ * redaction here exactly as for `isModelSafeToolFailure`: this route sends the returned text
93
+ * verbatim.
94
+ */
95
+ readonly describeInternalError?: (context: DelegatedToolsInternalErrorContext) => string | undefined;
96
+ }
97
+ /**
98
+ * `POST /api/delegated-tool-calls` — executes one delegated tool call against an already-started
99
+ * run. Maps `ToolExecutionResult.status` to the HTTP response via `toolExecutionResultToApiResult`
100
+ * above — mirroring `db-ops.ts`'s precedent for the identical status union rather than treating
101
+ * every business outcome as a `200`. A genuinely unexpected throw (an unregistered `toolId` — a
102
+ * routing/programming error per `ToolExecutor.execute`'s own contract — or a race where the run
103
+ * became terminal between this route's existence check and the bridge's first emitted event)
104
+ * reaches the same SEC-005 redaction path.
105
+ */
106
+ export declare const delegatedToolExecuteRoute: import("@jini-ai/http-kit").JsonRouteSpec<DelegatedToolExecuteRequest, DelegatedToolExecuteResponse, DelegatedToolsHttpDeps>;
107
+ /** Mounts `POST /api/delegated-tool-calls` on `app`. A pack's `http(app, services)` calls this directly. */
108
+ export declare function registerDelegatedToolRoutes({ app, deps, adapter }: {
109
+ readonly app: Express;
110
+ readonly deps: DelegatedToolsHttpDeps;
111
+ readonly adapter: AdapterContext;
112
+ }, _optional?: Record<string, never>): void;
113
+ //# sourceMappingURL=delegated-tools.d.ts.map
@@ -0,0 +1,230 @@
1
+ /**
2
+ * @module delegated-tools
3
+ *
4
+ * `POST /api/delegated-tool-calls` — the daemon-side half of gap 3's MCP-callback continuation
5
+ * transport spike (see `packages/daemon/archived provenance ledger`'s "run/chat orchestration gap 3, part 1"
6
+ * addition and this package's own dated section in `archived provenance ledger`). The swarm-consensus Final
7
+ * Recommendation asked for exactly this round trip: "inject the already-shipped MCP host into
8
+ * one MCP-capable CLI's launch config, prove a tool round-trip through the existing
9
+ * `delegated-tool-bridge.ts`." This route is that round trip's daemon-side half — an MCP server
10
+ * subprocess spawned alongside a `claude` run (`packages/mcp/src/bin/serve.ts`, injected via
11
+ * `packages/daemon/src/agent-executor.ts`'s optional `mcpJsonInjection` config) calls back into
12
+ * the daemon over loopback HTTP (`packages/mcp/src/server/daemon-client.ts`) with
13
+ * `{runId, toolUseId, toolId, input}`; this route decodes that request and calls the
14
+ * already-shipped, already-tested `createDelegatedToolBridge`
15
+ * (`packages/daemon/src/delegated-tool-bridge.ts`), which is the ONLY execution path from here:
16
+ * every injected byte still routes through `ToolExecutor`'s deny-by-default gate — no parallel
17
+ * authorization mechanism is introduced by this route.
18
+ *
19
+ * `resolvePrincipal` is host-owned and has no default (mirrors gap 3's own resolved
20
+ * human-in-the-loop answer — an explicit host-supplied allowlist/policy, never an invented
21
+ * mechanism): whatever `Principal` a host resolves for a given delegated-tool-call request is
22
+ * exactly what flows into `ToolExecutor.execute`'s own `ToolPolicy.authorize`/
23
+ * `ExecutionDelegate` gates. This route does not itself decide who is allowed to do what — it
24
+ * only decides *which run* a request may act against (an unknown or not-yet-started `runId` is
25
+ * rejected with `404` before the bridge is ever invoked, same precedent as `runs.ts`'s
26
+ * `runCancelRoute`).
27
+ */
28
+ import { randomUUID } from 'node:crypto';
29
+ import { createApiError } from '@jini-ai/protocol';
30
+ import { createDelegatedToolBridge } from '../index.js';
31
+ import { defineJsonRoute, mountJsonRoute } from '@jini-ai/http-kit';
32
+ import { validationError } from '@jini-ai/http-kit';
33
+ import { err, ok } from '@jini-ai/http-kit';
34
+ import * as readOnlyTools from '../read-only-tools.js';
35
+ /** Route preflight shares the same registration decision as nested dispatch. */
36
+ function checkReadOnlyConstraint(deps, input) {
37
+ if (input.requireReadOnly !== true)
38
+ return null;
39
+ const refusal = readOnlyTools.checkReadOnlyTool({
40
+ toolId: input.toolId, registry: deps.toolRegistry, messages: readOnlyTools.defaultDaemonMessages.readOnly,
41
+ });
42
+ if (refusal === null)
43
+ return null;
44
+ return createApiError({ code: 'TOOL_OPERATION_DENIED', message: refusal });
45
+ }
46
+ /** Logs the real failure server-side and returns the generic, correlation-id-bearing public error (SEC-005: never the raw exception). */
47
+ function defaultInternalErrorSink(context) {
48
+ // eslint-disable-next-line no-console
49
+ console.error(`[@jini-ai/http-kit] internal error (${context.source}, correlationId=${context.correlationId})`, context.error);
50
+ }
51
+ /**
52
+ * The host's {@link DelegatedToolsHttpDeps.describeInternalError} text for `context`, or `undefined`
53
+ * when there is no describer, it declines (`undefined`/`''`), or it throws.
54
+ *
55
+ * @complexity O(1) plus the describer's own cost.
56
+ */
57
+ function describeSafely(deps, context) {
58
+ if (deps.describeInternalError === undefined)
59
+ return undefined;
60
+ try {
61
+ const text = deps.describeInternalError(context);
62
+ return typeof text === 'string' && text.length > 0 ? text : undefined;
63
+ }
64
+ catch (error) {
65
+ // eslint-disable-next-line no-console
66
+ console.error(`[@jini-ai/http-kit] describeInternalError threw (correlationId=${context.correlationId})`, error);
67
+ return undefined;
68
+ }
69
+ }
70
+ function reportInternalError(deps, source, error, runId, toolId, status) {
71
+ const correlationId = randomUUID();
72
+ const context = {
73
+ source,
74
+ runId,
75
+ toolId,
76
+ correlationId,
77
+ error,
78
+ ...(status === undefined ? {} : { status }),
79
+ };
80
+ const sink = deps.onInternalError ?? defaultInternalErrorSink;
81
+ sink(context);
82
+ const message = describeSafely(deps, context) ?? 'an internal error occurred';
83
+ return createApiError({ code: 'INTERNAL_ERROR', message: message }, { requestId: correlationId });
84
+ }
85
+ function isRecord(value) {
86
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
87
+ }
88
+ function requireNonEmptyString(body, key) {
89
+ const value = body[key];
90
+ return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
91
+ }
92
+ function parseDelegatedToolExecute(input) {
93
+ if (!isRecord(input.body))
94
+ return err({ error: validationError({ message: 'body must be a JSON object' }) });
95
+ const runId = requireNonEmptyString(input.body, 'runId');
96
+ if (runId === undefined) {
97
+ return err({ error: validationError({ message: 'runId must be a non-empty string' }, { issues: [{ path: 'runId', message: 'required non-empty string' }] }) });
98
+ }
99
+ const toolUseId = requireNonEmptyString(input.body, 'toolUseId');
100
+ if (toolUseId === undefined) {
101
+ return err({ error: validationError({ message: 'toolUseId must be a non-empty string' }, { issues: [{ path: 'toolUseId', message: 'required non-empty string' }] }) });
102
+ }
103
+ const toolId = requireNonEmptyString(input.body, 'toolId');
104
+ if (toolId === undefined) {
105
+ return err({ error: validationError({ message: 'toolId must be a non-empty string' }, { issues: [{ path: 'toolId', message: 'required non-empty string' }] }) });
106
+ }
107
+ // Spread-in rather than always-present so an unconstrained body parses to exactly the object it
108
+ // parsed to before this field existed — the existing gateway's request shape is byte-identical,
109
+ // which is what the "additive only" constraint means at the wire.
110
+ //
111
+ // Only a literal `true` arms the constraint. Anything else (absent, `false`, a truthy string) is
112
+ // "unconstrained", never a parse error: this field can only ever TIGHTEN a call, so a malformed
113
+ // value that fell through to the normal gateway is the same outcome as not sending it at all.
114
+ const requireReadOnly = input.body['requireReadOnly'] === true ? { requireReadOnly: true } : {};
115
+ return ok({ value: { runId, toolUseId, toolId, input: input.body.input, ...requireReadOnly } });
116
+ }
117
+ /**
118
+ * Maps a settled `ToolExecutionResult` to the wire `Result` — the same status mapping
119
+ * `db-ops.ts`'s `toolResultToApiResult` establishes for this identical `ToolExecutionResult`
120
+ * union, kept consistent here rather than reinvented: `completed` → `200 {result}`,
121
+ * `denied`/`confirmation-denied` → `403 TOOL_OPERATION_DENIED`, `timed-out`/`cancelled` → a
122
+ * SEC-005-redacted `500 INTERNAL_ERROR` (the real status/error goes to `onInternalError`, never the
123
+ * wire — unless the host supplies `deps.describeInternalError`, whose text then replaces the generic
124
+ * message on every redacted 500 below).
125
+ *
126
+ * `failed` splits in two, on `result.errorKind` (`@jini-ai/daemon`'s `ToolExecutor` sets it from
127
+ * whether the handler threw `@jini-ai/core`'s `ToolInputError`): `'validation'` means the CALLER's
128
+ * input was the problem — a wrong/missing/malformed field name, a `themeId` that doesn't exist, a
129
+ * path escaping its theme — so it is reported as a real `400 BAD_REQUEST` carrying the handler's own
130
+ * actionable message (the same one a model reads to retry correctly), not redacted. Everything else
131
+ * (`'internal'`, or no `errorKind` at all — e.g. an older `ToolExecutor` build) stays the SEC-005
132
+ * redacted 500 path, because a handler that threw for a reason OTHER than "your input was bad" can
133
+ * embed exactly the kind of internal detail that path exists to keep off the wire — unless the host
134
+ * vouches for this exact result via `deps.isModelSafeToolFailure` (it has already redacted the text
135
+ * itself), in which case it is a `422 TOOL_EXECUTION_FAILED` carrying the host's text verbatim.
136
+ */
137
+ function toolExecutionResultToApiResult(deps, runId, toolId, result) {
138
+ switch (result.status) {
139
+ case 'completed':
140
+ return ok({ value: { result } });
141
+ case 'denied':
142
+ return err({ error: createApiError({ code: 'TOOL_OPERATION_DENIED', message: 'this operation was denied by policy' }) });
143
+ case 'confirmation-denied':
144
+ return err({ error: createApiError({ code: 'TOOL_OPERATION_DENIED', message: 'this operation was denied during confirmation' }) });
145
+ case 'timed-out':
146
+ case 'cancelled':
147
+ return err({ error: reportInternalError(deps, 'delegated-tool-execute', result.status, runId, toolId, result.status) });
148
+ case 'failed':
149
+ if (result.errorKind === 'validation') {
150
+ return err({ error: createApiError({ code: 'BAD_REQUEST', message: result.error ?? 'invalid tool input' }) });
151
+ }
152
+ if (result.error && deps.isModelSafeToolFailure?.({ result }) === true) {
153
+ return err({ error: createApiError({ code: 'TOOL_EXECUTION_FAILED', message: result.error }) });
154
+ }
155
+ return err({ error: reportInternalError(deps, 'delegated-tool-execute', result.error ?? result.status, runId, toolId, 'failed') });
156
+ }
157
+ }
158
+ /**
159
+ * `POST /api/delegated-tool-calls` — executes one delegated tool call against an already-started
160
+ * run. Maps `ToolExecutionResult.status` to the HTTP response via `toolExecutionResultToApiResult`
161
+ * above — mirroring `db-ops.ts`'s precedent for the identical status union rather than treating
162
+ * every business outcome as a `200`. A genuinely unexpected throw (an unregistered `toolId` — a
163
+ * routing/programming error per `ToolExecutor.execute`'s own contract — or a race where the run
164
+ * became terminal between this route's existence check and the bridge's first emitted event)
165
+ * reaches the same SEC-005 redaction path.
166
+ */
167
+ export const delegatedToolExecuteRoute = defineJsonRoute({
168
+ method: 'post', path: '/api/delegated-tool-calls', parse: parseDelegatedToolExecute, handle: async ({ input, deps }, { signal } = {}) => {
169
+ const run = await deps.lifecycle.get({ runId: input.runId });
170
+ if (run === undefined) {
171
+ return err({ error: createApiError({ code: 'NOT_FOUND', message: `run "${input.runId}" was not found` }) });
172
+ }
173
+ // Before `resolvePrincipal`, and long before the bridge: a refused call must cost nothing
174
+ // downstream and must leave no trace of an execution that was never going to happen. It sits
175
+ // after the run-existence check only so that a bad `runId` still answers 404 the way it always
176
+ // did, regardless of the constraint.
177
+ const readOnlyRefusal = checkReadOnlyConstraint(deps, input);
178
+ if (readOnlyRefusal !== null)
179
+ return err({ error: readOnlyRefusal });
180
+ let principal;
181
+ try {
182
+ principal = await deps.resolvePrincipal({ request: input });
183
+ }
184
+ catch (error) {
185
+ return err({ error: reportInternalError(deps, 'resolve-principal', error, input.runId, input.toolId) });
186
+ }
187
+ // Attenuates the resolved principal itself for a `requireReadOnly` call, so the constraint
188
+ // survives past this route: a consumer's own `ToolExecutor` composition may dispatch a tool id
189
+ // one or more hops past the one checked above (a nested/recovery dispatch using this same
190
+ // principal), and only the principal — not this request's `requireReadOnly` flag, which dies
191
+ // at this function's return — reaches that far. See `constrainPrincipalToReadOnlyTools`'s own
192
+ // doc. A no-op for every existing unconstrained caller.
193
+ if (input.requireReadOnly === true) {
194
+ principal = readOnlyTools.constrainPrincipalToReadOnlyTools({ principal });
195
+ }
196
+ // Composes the SAME enforcement decorator this package exports for a host's own use, here,
197
+ // around whatever `ToolExecutor` the host supplied — a second, independent check of the OUTER
198
+ // `toolId` `checkReadOnlyConstraint` already ran (never a WEAKER outcome than that check, only
199
+ // ever redundant with it; see `withReadOnlyToolConstraint`'s own doc for why this alone cannot
200
+ // close a nested dispatch a host's own inner decorator issues). Skipped entirely for an
201
+ // unconstrained call so the pre-existing gateway's executor reference is untouched.
202
+ const toolExecutor = input.requireReadOnly === true
203
+ ? readOnlyTools.withReadOnlyToolConstraint({ inner: deps.toolExecutor, registry: deps.toolRegistry, messages: readOnlyTools.defaultDaemonMessages.readOnly, idGenerator: { newId: randomUUID } })
204
+ : deps.toolExecutor;
205
+ const bridge = createDelegatedToolBridge({ lifecycle: deps.lifecycle, toolExecutor });
206
+ try {
207
+ // `signal` carries the caller's HTTP connection dropping — see `mountJsonRoute`
208
+ // (`adapter.ts`). The bridge already combines it with the run's own cancellation
209
+ // (`DelegatedToolInvocation.signal`'s doc); this is the wire that was missing, not new
210
+ // behaviour in the bridge or `ToolExecutor`.
211
+ const result = await bridge.execute({
212
+ runId: input.runId,
213
+ toolUseId: input.toolUseId,
214
+ toolId: input.toolId,
215
+ principal,
216
+ input: input.input,
217
+ ...(signal !== undefined ? { signal } : {}),
218
+ });
219
+ return toolExecutionResultToApiResult(deps, input.runId, input.toolId, result);
220
+ }
221
+ catch (error) {
222
+ return err({ error: reportInternalError(deps, 'delegated-tool-execute', error, input.runId, input.toolId) });
223
+ }
224
+ }
225
+ }, { requireSameOrigin: true });
226
+ /** Mounts `POST /api/delegated-tool-calls` on `app`. A pack's `http(app, services)` calls this directly. */
227
+ export function registerDelegatedToolRoutes({ app, deps, adapter }, _optional = {}) {
228
+ mountJsonRoute({ app, spec: delegatedToolExecuteRoute, deps, adapter });
229
+ }
230
+ //# sourceMappingURL=delegated-tools.js.map