@jini-ai/daemon 0.3.8 → 0.5.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 (173) hide show
  1. package/CHANGELOG.md +119 -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/artifacts/index.d.ts +14 -0
  22. package/dist/artifacts/index.js +14 -0
  23. package/dist/artifacts/manifest.d.ts +90 -0
  24. package/dist/artifacts/manifest.js +229 -0
  25. package/dist/artifacts/publication-guard.d.ts +28 -0
  26. package/dist/artifacts/publication-guard.js +77 -0
  27. package/dist/artifacts/runtime-compat.d.ts +37 -0
  28. package/dist/artifacts/runtime-compat.js +33 -0
  29. package/dist/artifacts/store.d.ts +85 -0
  30. package/dist/artifacts/store.js +92 -0
  31. package/dist/artifacts/stub-guard.d.ts +73 -0
  32. package/dist/artifacts/stub-guard.js +198 -0
  33. package/dist/artifacts/text-suppression.d.ts +68 -0
  34. package/dist/artifacts/text-suppression.js +184 -0
  35. package/dist/close-status.d.ts +11 -8
  36. package/dist/close-status.js +31 -19
  37. package/dist/continuation/continuation-transport.d.ts +3 -1
  38. package/dist/continuation/continuation-transport.js +1 -1
  39. package/dist/continuation/journal.d.ts +12 -5
  40. package/dist/continuation/journal.js +4 -4
  41. package/dist/continuation/run-scoped-context-store.d.ts +13 -4
  42. package/dist/continuation/run-scoped-context-store.js +6 -6
  43. package/dist/continuation/run-start-handler.d.ts +1 -1
  44. package/dist/continuation/run-start-handler.js +3 -16
  45. package/dist/delegated-tool-bridge.d.ts +7 -3
  46. package/dist/delegated-tool-bridge.js +32 -31
  47. package/dist/event-log.d.ts +7 -5
  48. package/dist/event-log.js +12 -11
  49. package/dist/frontend-capability-tools.d.ts +1 -1
  50. package/dist/frontend-capability-tools.js +3 -2
  51. package/dist/frontend-session-registry.d.ts +32 -9
  52. package/dist/frontend-session-registry.js +12 -11
  53. package/dist/http/active-context.d.ts +105 -0
  54. package/dist/http/active-context.js +67 -0
  55. package/dist/http/agents.d.ts +68 -0
  56. package/dist/http/agents.js +12 -0
  57. package/dist/http/attachments.d.ts +618 -0
  58. package/dist/http/attachments.js +1034 -0
  59. package/dist/http/cancel-owned-runs.d.ts +49 -0
  60. package/dist/http/cancel-owned-runs.js +35 -0
  61. package/dist/http/component-catalog.d.ts +84 -0
  62. package/dist/http/component-catalog.js +64 -0
  63. package/dist/http/connectors.d.ts +209 -0
  64. package/dist/http/connectors.js +409 -0
  65. package/dist/http/daemon-status.d.ts +76 -0
  66. package/dist/http/daemon-status.js +55 -0
  67. package/dist/http/db-ops.d.ts +123 -0
  68. package/dist/http/db-ops.js +189 -0
  69. package/dist/http/delegated-tools.d.ts +113 -0
  70. package/dist/http/delegated-tools.js +230 -0
  71. package/dist/http/frontend-control.d.ts +136 -0
  72. package/dist/http/frontend-control.js +79 -0
  73. package/dist/http/frontend-sessions.d.ts +110 -0
  74. package/dist/http/frontend-sessions.js +180 -0
  75. package/dist/http/health.d.ts +95 -0
  76. package/dist/http/health.js +40 -0
  77. package/dist/http/host-tools.d.ts +214 -0
  78. package/dist/http/host-tools.js +329 -0
  79. package/dist/http/index.d.ts +52 -0
  80. package/dist/http/index.js +27 -0
  81. package/dist/http/media.d.ts +146 -0
  82. package/dist/http/media.js +346 -0
  83. package/dist/http/memory.d.ts +265 -0
  84. package/dist/http/memory.js +328 -0
  85. package/dist/http/model-proxy.d.ts +49 -0
  86. package/dist/http/model-proxy.js +642 -0
  87. package/dist/http/remote-run-events.d.ts +70 -0
  88. package/dist/http/remote-run-events.js +224 -0
  89. package/dist/http/research.d.ts +48 -0
  90. package/dist/http/research.js +315 -0
  91. package/dist/http/route-manifest.d.ts +35 -0
  92. package/dist/http/route-manifest.js +103 -0
  93. package/dist/http/routines.d.ts +120 -0
  94. package/dist/http/routines.js +298 -0
  95. package/dist/http/run-credentials.d.ts +73 -0
  96. package/dist/http/run-credentials.js +71 -0
  97. package/dist/http/runs.d.ts +91 -0
  98. package/dist/http/runs.js +221 -0
  99. package/dist/http/terminals.d.ts +138 -0
  100. package/dist/http/terminals.js +325 -0
  101. package/dist/http/tool-catalog.d.ts +78 -0
  102. package/dist/http/tool-catalog.js +66 -0
  103. package/dist/http/workspace-root.d.ts +77 -0
  104. package/dist/http/workspace-root.js +63 -0
  105. package/dist/http/xai.d.ts +90 -0
  106. package/dist/http/xai.js +655 -0
  107. package/dist/image-prompt-delivery.d.ts +13 -3
  108. package/dist/image-prompt-delivery.js +5 -5
  109. package/dist/index.d.ts +556 -4
  110. package/dist/index.js +556 -4
  111. package/dist/legacy-data-migration.d.ts +59 -8
  112. package/dist/legacy-data-migration.js +38 -33
  113. package/dist/read-only-tools.d.ts +130 -0
  114. package/dist/read-only-tools.js +115 -0
  115. package/dist/remote-tool-bridge.d.ts +11 -5
  116. package/dist/remote-tool-bridge.js +15 -15
  117. package/dist/routines/index.d.ts +1 -1
  118. package/dist/routines/index.js +1 -1
  119. package/dist/routines/routine-store.d.ts +34 -12
  120. package/dist/routines/routine-store.js +21 -20
  121. package/dist/routines/schedule.d.ts +48 -10
  122. package/dist/routines/schedule.js +21 -21
  123. package/dist/routines/scheduler.d.ts +32 -11
  124. package/dist/routines/scheduler.js +61 -57
  125. package/dist/routines/types.d.ts +10 -3
  126. package/dist/run/core/result.d.ts +6 -2
  127. package/dist/run/core/result.js +3 -3
  128. package/dist/run/core/retry.d.ts +17 -4
  129. package/dist/run/core/retry.js +7 -11
  130. package/dist/run/diagnostics/diagnostics.d.ts +14 -4
  131. package/dist/run/diagnostics/diagnostics.js +7 -7
  132. package/dist/run/index.d.ts +1 -1
  133. package/dist/run/index.js +1 -1
  134. package/dist/run-credentials.d.ts +138 -0
  135. package/dist/run-credentials.js +152 -0
  136. package/dist/run-lifecycle.d.ts +92 -33
  137. package/dist/run-lifecycle.js +81 -74
  138. package/dist/scheduler.d.ts +8 -0
  139. package/dist/scheduler.js +2 -0
  140. package/dist/session-coordination.d.ts +115 -0
  141. package/dist/session-coordination.js +111 -0
  142. package/dist/store/agent-sessions/errors.d.ts +8 -0
  143. package/dist/store/agent-sessions/errors.js +10 -0
  144. package/dist/store/agent-sessions/index.d.ts +12 -0
  145. package/dist/store/agent-sessions/index.js +19 -0
  146. package/dist/store/agent-sessions/pglite.d.ts +12 -0
  147. package/dist/store/agent-sessions/pglite.js +7 -0
  148. package/dist/store/agent-sessions/ports.d.ts +24 -0
  149. package/dist/store/agent-sessions/ports.js +2 -0
  150. package/dist/store/agent-sessions/postgres.d.ts +12 -0
  151. package/dist/store/agent-sessions/postgres.js +7 -0
  152. package/dist/store/agent-sessions/schema.d.ts +3 -0
  153. package/dist/store/agent-sessions/schema.js +23 -0
  154. package/dist/store/agent-sessions/sql.d.ts +35 -0
  155. package/dist/store/agent-sessions/sql.js +59 -0
  156. package/dist/store/agent-sessions/sqlite.d.ts +117 -0
  157. package/dist/store/agent-sessions/sqlite.js +128 -0
  158. package/dist/store/event-log/sqlite.d.ts +75 -0
  159. package/dist/store/event-log/sqlite.js +230 -0
  160. package/dist/surface-exchanges.d.ts +97 -0
  161. package/dist/surface-exchanges.js +153 -0
  162. package/dist/terminal-session.d.ts +43 -11
  163. package/dist/terminal-session.js +22 -20
  164. package/dist/tokens.js +4 -4
  165. package/dist/tool-audit.d.ts +114 -0
  166. package/dist/tool-audit.js +95 -0
  167. package/dist/tool-executor.d.ts +22 -7
  168. package/dist/tool-executor.js +16 -16
  169. package/dist/tool-result-media.d.ts +3 -1
  170. package/dist/tool-result-media.js +1 -1
  171. package/dist/tool-result-surfaces.d.ts +3 -1
  172. package/dist/tool-result-surfaces.js +1 -1
  173. package/package.json +127 -9
@@ -0,0 +1,230 @@
1
+ import { createSystemClock } from '@jini-ai/core/primitives';
2
+ /**
3
+ * `createSqliteEventLog` — the durable `EventLog` adapter (extraction-plan §8 task 8,
4
+ * §2.6: "`@jini-ai/daemon/store/event-log/sqlite` is the default adapter... an adapter conformance suite covers
5
+ * transactions/ordering/cursor-durability/cancellation/migrations").
6
+ *
7
+ * Implements the protocol's two-object `EventLog` call shape, with the same distinguishable `'replay-gap'` result,
8
+ * same never-reused monotonic per-run cursor allocation, same FIFO eviction at
9
+ * `maxEntriesPerRun` — see `createInMemoryEventLog` in `@jini-ai/daemon` for the
10
+ * reference behavior this mirrors. `better-sqlite3` is synchronous; every public
11
+ * method still returns a `Promise` per extraction-plan §2.6 ("ports are async-only
12
+ * from day one... a real persistent adapter is a drop-in swap"), and every
13
+ * multi-statement operation (`append`'s dedupe-check + insert + eviction) runs
14
+ * inside a `better-sqlite3` transaction for atomicity.
15
+ *
16
+ * Three deliberate divergences from the in-memory reference adapter, all scoped to this durable
17
+ * adapter only: (1) `maxEntriesPerRun` defaults to {@link DEFAULT_MAX_ENTRIES_PER_RUN} (2000)
18
+ * instead of unbounded — see `SqliteEventLogOptions.maxEntriesPerRun`'s doc; (2) `replay`'s
19
+ * `afterCursor` is validated up front and throws for anything other than `null` or a
20
+ * non-negative-safe-integer string, instead of returning a `'invalid-cursor'` result — see
21
+ * `assertValidReplayCursor`; (3) dedupe evidence is retained only while its entry remains in the
22
+ * FIFO window. Memory keeps dedupe evidence until drop, including evicted entries. This durable
23
+ * adapter preserves its existing bounded SQL/schema behavior rather than adding a hidden archive.
24
+ */
25
+ import { openSqliteConnection } from '@jini-ai/db/sqlite';
26
+ /**
27
+ * Default per-run retention cap applied when `SqliteEventLogOptions.maxEntriesPerRun` is
28
+ * omitted — see that option's doc for why a durable adapter defaults to a bound instead of
29
+ * mirroring the in-memory reference adapter's unbounded-by-default behavior.
30
+ */
31
+ export const DEFAULT_MAX_ENTRIES_PER_RUN = 2000;
32
+ /** @internal Validates and resolves `options.maxEntriesPerRun`, applying the documented default. */
33
+ function resolveMaxEntriesPerRun(value) {
34
+ if (value === undefined)
35
+ return DEFAULT_MAX_ENTRIES_PER_RUN;
36
+ if (!Number.isSafeInteger(value) || value < 0) {
37
+ throw new Error(`createSqliteEventLog: options.maxEntriesPerRun must be a non-negative safe integer, got ${value}`);
38
+ }
39
+ return value;
40
+ }
41
+ /** @internal Validates a `replay(runId, afterCursor)` cursor. `null` ("from the beginning") is always valid. */
42
+ function assertValidReplayCursor(afterCursor) {
43
+ if (afterCursor === null)
44
+ return;
45
+ const n = Number(afterCursor);
46
+ if (!Number.isSafeInteger(n) || n < 0) {
47
+ throw new Error(`createSqliteEventLog: invalid replay cursor ${JSON.stringify(afterCursor)} — must be a non-negative safe integer string`);
48
+ }
49
+ }
50
+ function rowToEntry(row) {
51
+ return {
52
+ id: String(row.cursor),
53
+ event: row.event,
54
+ data: JSON.parse(row.data),
55
+ recordedAt: row.recorded_at,
56
+ };
57
+ }
58
+ /**
59
+ * Borrows the caller's connection and ensures the dedicated event-log schema idempotently.
60
+ * Closing this adapter never closes a borrowed handle or changes its pragmas.
61
+ * Use openSqliteEventLog with an explicit file/opener to acquire an owned connection.
62
+ * @throws Host SQL/setup failures, or a missing borrowed connection.
63
+ * @complexity Setup O(1) statements; append costs depend on the retained per-run cap.
64
+ */
65
+ export function createSqliteEventLog(input, options = {}) {
66
+ resolveMaxEntriesPerRun(options.maxEntriesPerRun);
67
+ if (!input || typeof input !== 'object' || !input.db) {
68
+ throw new Error('createSqliteEventLog: pass a borrowed SqliteDb');
69
+ }
70
+ return bindSqliteEventLog(input.db, options);
71
+ }
72
+ /**
73
+ * Acquires a connection through the host opener; setup failure closes that connection.
74
+ * Successful close is idempotent and closes only this owned handle.
75
+ * @param input File and host-owned opener; no ambient driver is loaded.
76
+ * @returns Durable event log with an owned closer.
77
+ * @throws Original acquisition/schema/prepare failure; cleanup never masks it.
78
+ * @complexity O(1) acquisition calls; subsequent costs are the borrowed adapter's.
79
+ */
80
+ export function openSqliteEventLog(input, options = {}) {
81
+ resolveMaxEntriesPerRun(options.maxEntriesPerRun);
82
+ let db;
83
+ try {
84
+ db = openSqliteConnection({ filePath: input.file, open(file, settings) {
85
+ db = input.open(file, settings);
86
+ return db;
87
+ }, pragmas: ['journal_mode = WAL'] });
88
+ const log = bindSqliteEventLog(db, options);
89
+ let closed = false;
90
+ return { ...log, async close(_args) { if (closed)
91
+ return; closed = true; db.close(); } };
92
+ }
93
+ catch (error) {
94
+ try {
95
+ db?.close();
96
+ }
97
+ catch { /* Preserve the setup failure. */ }
98
+ throw error;
99
+ }
100
+ }
101
+ /** Binds the unchanged cursor/dedupe/retention SQL to a borrowed synchronous connection. */
102
+ function bindSqliteEventLog(db, options) {
103
+ const maxEntriesPerRun = resolveMaxEntriesPerRun(options.maxEntriesPerRun);
104
+ const clock = options.clock ?? createSystemClock();
105
+ db.exec(`
106
+ CREATE TABLE IF NOT EXISTS jini_event_log_runs (
107
+ run_id TEXT PRIMARY KEY,
108
+ next_cursor INTEGER NOT NULL
109
+ );
110
+
111
+ CREATE TABLE IF NOT EXISTS jini_event_log_entries (
112
+ run_id TEXT NOT NULL,
113
+ cursor INTEGER NOT NULL,
114
+ event TEXT NOT NULL,
115
+ data TEXT NOT NULL,
116
+ recorded_at INTEGER NOT NULL,
117
+ dedupe_key TEXT,
118
+ PRIMARY KEY (run_id, cursor)
119
+ );
120
+
121
+ CREATE INDEX IF NOT EXISTS idx_jini_event_log_entries_dedupe
122
+ ON jini_event_log_entries (run_id, dedupe_key)
123
+ WHERE dedupe_key IS NOT NULL;
124
+ `);
125
+ const getRunStmt = db.prepare('SELECT run_id, next_cursor FROM jini_event_log_runs WHERE run_id = ?');
126
+ const insertRunStmt = db.prepare('INSERT INTO jini_event_log_runs (run_id, next_cursor) VALUES (?, ?)');
127
+ const updateNextCursorStmt = db.prepare('UPDATE jini_event_log_runs SET next_cursor = ? WHERE run_id = ?');
128
+ const getByDedupeStmt = db.prepare('SELECT * FROM jini_event_log_entries WHERE run_id = ? AND dedupe_key = ?');
129
+ const insertEntryStmt = db.prepare('INSERT INTO jini_event_log_entries (run_id, cursor, event, data, recorded_at, dedupe_key) VALUES (?, ?, ?, ?, ?, ?)');
130
+ const countEntriesStmt = db.prepare('SELECT COUNT(*) AS count FROM jini_event_log_entries WHERE run_id = ?');
131
+ const evictOldestStmt = db.prepare(`
132
+ DELETE FROM jini_event_log_entries
133
+ WHERE run_id = ? AND cursor IN (
134
+ SELECT cursor FROM jini_event_log_entries WHERE run_id = ? ORDER BY cursor ASC LIMIT ?
135
+ )
136
+ `);
137
+ const selectAllStmt = db.prepare('SELECT * FROM jini_event_log_entries WHERE run_id = ? ORDER BY cursor ASC');
138
+ const selectAfterStmt = db.prepare('SELECT * FROM jini_event_log_entries WHERE run_id = ? AND cursor > ? ORDER BY cursor ASC');
139
+ const selectOldestStmt = db.prepare('SELECT * FROM jini_event_log_entries WHERE run_id = ? ORDER BY cursor ASC LIMIT 1');
140
+ const selectRunIdsStmt = db.prepare('SELECT run_id FROM jini_event_log_runs ORDER BY run_id ASC');
141
+ const deleteRunEntriesStmt = db.prepare('DELETE FROM jini_event_log_entries WHERE run_id = ?');
142
+ const deleteRunStmt = db.prepare('DELETE FROM jini_event_log_runs WHERE run_id = ?');
143
+ // `db.transaction()` does not preserve a wrapped callback's own generic parameter (the
144
+ // returned `Transaction` type is non-generic), so this runs on `unknown` payloads and the
145
+ // public `append<Payload>` method below casts at the boundary — the same pattern
146
+ // `createInMemoryEventLog` uses for its dedupe-hit early return.
147
+ const appendTxn = db.transaction((input) => {
148
+ let runRow = getRunStmt.get(input.runId);
149
+ if (!runRow) {
150
+ insertRunStmt.run(input.runId, 1);
151
+ runRow = { run_id: input.runId, next_cursor: 1 };
152
+ }
153
+ if (input.dedupeKey !== undefined) {
154
+ const existing = getByDedupeStmt.get(input.runId, input.dedupeKey);
155
+ if (existing) {
156
+ return rowToEntry(existing);
157
+ }
158
+ }
159
+ const cursor = runRow.next_cursor;
160
+ const recordedAt = clock.nowMs();
161
+ insertEntryStmt.run(input.runId, cursor, input.event, JSON.stringify(input.data ?? null), recordedAt, input.dedupeKey ?? null);
162
+ updateNextCursorStmt.run(cursor + 1, input.runId);
163
+ const { count } = countEntriesStmt.get(input.runId);
164
+ if (count > maxEntriesPerRun) {
165
+ evictOldestStmt.run(input.runId, input.runId, count - maxEntriesPerRun);
166
+ }
167
+ return {
168
+ id: String(cursor),
169
+ event: input.event,
170
+ data: input.data,
171
+ recordedAt,
172
+ };
173
+ });
174
+ function replaySync(runId, afterCursor) {
175
+ // Validated up front, independent of whether `runId` is known: a malformed cursor is a
176
+ // caller/transport bug regardless of run state, so it throws rather than being folded into
177
+ // (or masked by) an `'unknown-run'`/`'replay-gap'` result.
178
+ assertValidReplayCursor(afterCursor);
179
+ const runRow = getRunStmt.get(runId);
180
+ if (!runRow) {
181
+ return { kind: 'unknown-run' };
182
+ }
183
+ if (afterCursor === null) {
184
+ const oldestRow = selectOldestStmt.get(runId);
185
+ const truncated = oldestRow !== undefined && oldestRow.cursor > 1;
186
+ return {
187
+ kind: 'ok',
188
+ entries: selectAllStmt.all(runId).map(rowToEntry),
189
+ ...(truncated ? { truncated: true } : {}),
190
+ };
191
+ }
192
+ // Safe: assertValidReplayCursor already proved this parses to a non-negative safe integer.
193
+ const afterCursorNum = Number(afterCursor);
194
+ const oldestRow = selectOldestStmt.get(runId);
195
+ const oldestRetainedId = oldestRow ? oldestRow.cursor : runRow.next_cursor;
196
+ if (afterCursorNum < oldestRetainedId - 1) {
197
+ return {
198
+ kind: 'replay-gap',
199
+ requestedCursor: afterCursor,
200
+ oldestAvailableCursor: oldestRow ? String(oldestRow.cursor) : null,
201
+ };
202
+ }
203
+ return {
204
+ kind: 'ok',
205
+ entries: selectAfterStmt.all(runId, afterCursorNum).map(rowToEntry),
206
+ };
207
+ }
208
+ const dropTxn = db.transaction((runId) => {
209
+ deleteRunEntriesStmt.run(runId);
210
+ deleteRunStmt.run(runId);
211
+ });
212
+ return {
213
+ async append(input, options = {}) {
214
+ return appendTxn({ ...input, dedupeKey: options.dedupeKey });
215
+ },
216
+ async replay({ runId, afterCursor }) {
217
+ return replaySync(runId, afterCursor);
218
+ },
219
+ async listRunIds(_args) {
220
+ return selectRunIdsStmt.all().map((row) => row.run_id);
221
+ },
222
+ async drop({ runId }) {
223
+ dropTxn(runId);
224
+ },
225
+ async close(_args) {
226
+ // Borrowed handle: the host owns its lifetime.
227
+ },
228
+ };
229
+ }
230
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1,97 @@
1
+ import type { Clock, IdGenerator } from '@jini-ai/core/primitives';
2
+ import type { SurfaceEmission, SurfaceEmitter } from '@jini-ai/core';
3
+ import type { SchedulerPort } from './scheduler.js';
4
+ export type { SchedulerPort } from './scheduler.js';
5
+ export declare const SURFACE_EXCHANGE_ID_PARAM = "__exchangeId";
6
+ export declare const SURFACE_DISMISSED_PARAM = "__dismissed";
7
+ export declare const SURFACE_TYPED_ANSWER_PARAM = "__typedAnswer";
8
+ export declare const DEFAULT_SURFACE_IDLE_TTL_MS: number;
9
+ export declare const DEFAULT_SURFACE_MAX_LIFETIME_MS: number;
10
+ export type SurfaceAnswerChannel = string;
11
+ export interface SurfaceExchangeBinding {
12
+ readonly toolId: string;
13
+ readonly principalId: string;
14
+ readonly channel?: SurfaceAnswerChannel;
15
+ }
16
+ export type SurfaceMessage = {
17
+ status: 'received';
18
+ params: Record<string, unknown>;
19
+ } | {
20
+ status: 'expired';
21
+ } | {
22
+ status: 'abandoned';
23
+ };
24
+ export type SurfaceDeliveryRejectionReason = 'unknown-or-closed' | 'binding-mismatch';
25
+ export type DeliverResult = {
26
+ ok: true;
27
+ } | {
28
+ ok: false;
29
+ reason: SurfaceDeliveryRejectionReason;
30
+ };
31
+ export interface SurfaceExchange {
32
+ readonly id: string;
33
+ send(args: {
34
+ emission: SurfaceEmission;
35
+ }): Promise<void>;
36
+ receive(_args: Record<string, never>): Promise<SurfaceMessage>;
37
+ close(_args: Record<string, never>): void;
38
+ }
39
+ export interface SurfaceDeliverySpec {
40
+ exchangeId: string;
41
+ params: Record<string, unknown>;
42
+ principalId: string;
43
+ toolId?: string;
44
+ channel?: SurfaceAnswerChannel;
45
+ }
46
+ export interface SurfaceExchangeStore {
47
+ open(args: {
48
+ binding: SurfaceExchangeBinding;
49
+ emit: SurfaceEmitter;
50
+ }): SurfaceExchange;
51
+ deliver(requiredArgs: Pick<SurfaceDeliverySpec, "exchangeId" | "params" | "principalId">, optionalArgs?: Pick<SurfaceDeliverySpec, "toolId" | "channel">): DeliverResult;
52
+ findTypedAnswerTarget(args: {
53
+ principalId: string;
54
+ toolId: string;
55
+ }): string | undefined;
56
+ size(): number;
57
+ }
58
+ /** In-process held calls. Inject a scheduler that unrefs its timers where appropriate. */
59
+ export declare function createSurfaceExchangeStore(deps: {
60
+ scheduler: SchedulerPort;
61
+ clock: Clock;
62
+ idGenerator: IdGenerator;
63
+ defaultChannel: SurfaceAnswerChannel;
64
+ }, { idleTtlMs, maxLifetimeMs }?: {
65
+ idleTtlMs?: number;
66
+ maxLifetimeMs?: number;
67
+ }): SurfaceExchangeStore;
68
+ export declare function askOnce({ exchange, emission }: {
69
+ exchange: SurfaceExchange;
70
+ emission: SurfaceEmission;
71
+ }): Promise<SurfaceMessage>;
72
+ export declare function askThenReport<T>({ exchange, confirmationEmission, handle }: {
73
+ exchange: SurfaceExchange;
74
+ confirmationEmission: SurfaceEmission;
75
+ handle: (answer: SurfaceMessage) => Promise<{
76
+ result: T;
77
+ outcome?: SurfaceEmission;
78
+ }>;
79
+ }): Promise<T>;
80
+ export type ConfirmationOutcome = {
81
+ confirmed: true;
82
+ } | {
83
+ confirmed: false;
84
+ reason: 'declined' | 'expired' | 'abandoned';
85
+ };
86
+ /** Typed prose is never consent; only an explicit literal decision grants approval. */
87
+ export declare function classifyConfirmationAnswer({ answer }: {
88
+ answer: SurfaceMessage;
89
+ }): ConfirmationOutcome;
90
+ export declare function resolveConfirmationDecision(args: {
91
+ exchange: SurfaceExchange;
92
+ emission: SurfaceEmission;
93
+ }): Promise<ConfirmationOutcome>;
94
+ export interface AssistantSurfaceDeps {
95
+ readonly surfaceExchanges: SurfaceExchangeStore;
96
+ }
97
+ //# sourceMappingURL=surface-exchanges.d.ts.map
@@ -0,0 +1,153 @@
1
+ export const SURFACE_EXCHANGE_ID_PARAM = '__exchangeId';
2
+ export const SURFACE_DISMISSED_PARAM = '__dismissed';
3
+ export const SURFACE_TYPED_ANSWER_PARAM = '__typedAnswer';
4
+ export const DEFAULT_SURFACE_IDLE_TTL_MS = 5 * 60 * 1000;
5
+ export const DEFAULT_SURFACE_MAX_LIFETIME_MS = 5.5 * 60 * 1000;
6
+ /** In-process held calls. Inject a scheduler that unrefs its timers where appropriate. */
7
+ export function createSurfaceExchangeStore(deps, { idleTtlMs = DEFAULT_SURFACE_IDLE_TTL_MS, maxLifetimeMs = DEFAULT_SURFACE_MAX_LIFETIME_MS } = {}) {
8
+ if (!Number.isFinite(idleTtlMs) || idleTtlMs <= 0 || !Number.isFinite(maxLifetimeMs) || maxLifetimeMs <= 0)
9
+ throw new Error('surface deadlines must be finite and positive');
10
+ const openExchanges = new Map();
11
+ return {
12
+ open({ binding, emit }) {
13
+ const id = deps.idGenerator.newId();
14
+ if (!id || openExchanges.has(id))
15
+ throw new Error('surface exchange ID must be unique and nonempty');
16
+ const inbox = [];
17
+ const waiters = [];
18
+ let terminal;
19
+ let cancelIdle;
20
+ let cancelLifetime;
21
+ const lifetimeDeadline = deps.clock.nowMs() + maxLifetimeMs;
22
+ let idleDeadline = deps.clock.nowMs() + idleTtlMs;
23
+ function end(reason) {
24
+ if (terminal)
25
+ return;
26
+ terminal = reason;
27
+ cancelIdle?.();
28
+ cancelLifetime?.();
29
+ openExchanges.delete(id);
30
+ while (waiters.length > 0)
31
+ waiters.shift()(reason);
32
+ }
33
+ function expireIfDue() {
34
+ const at = deps.clock.nowMs();
35
+ if (at >= idleDeadline || at >= lifetimeDeadline)
36
+ end({ status: 'expired' });
37
+ }
38
+ function armIdle() {
39
+ if (terminal)
40
+ return;
41
+ cancelIdle?.();
42
+ idleDeadline = deps.clock.nowMs() + idleTtlMs;
43
+ cancelIdle = deps.scheduler.schedule({ delayMs: idleTtlMs, callback: () => end({ status: 'expired' }) });
44
+ }
45
+ // Register only after both timers are installed, so scheduler failure leaves no discoverable exchange.
46
+ try {
47
+ cancelLifetime = deps.scheduler.schedule({ delayMs: maxLifetimeMs, callback: () => end({ status: 'expired' }) });
48
+ armIdle();
49
+ }
50
+ catch (error) {
51
+ cancelLifetime?.();
52
+ cancelIdle?.();
53
+ throw error;
54
+ }
55
+ openExchanges.set(id, {
56
+ binding: { ...binding },
57
+ accept(params) {
58
+ expireIfDue();
59
+ if (terminal)
60
+ return;
61
+ armIdle();
62
+ const waiter = waiters.shift();
63
+ if (waiter)
64
+ waiter({ status: 'received', params });
65
+ else
66
+ inbox.push(params);
67
+ },
68
+ });
69
+ return {
70
+ id,
71
+ async send({ emission }) {
72
+ expireIfDue();
73
+ if (terminal)
74
+ throw new Error(`surface exchange ${id} has already ended`);
75
+ armIdle();
76
+ await emit(emission);
77
+ },
78
+ async receive(_args) {
79
+ expireIfDue();
80
+ // Preserve the original FIFO contract: already delivered answers precede terminal status.
81
+ const buffered = inbox.shift();
82
+ if (buffered) {
83
+ armIdle();
84
+ return { status: 'received', params: buffered };
85
+ }
86
+ if (terminal)
87
+ return terminal;
88
+ return new Promise(resolve => waiters.push(resolve));
89
+ },
90
+ close(_args) { end({ status: 'abandoned' }); },
91
+ };
92
+ },
93
+ deliver(requiredArgs, optionalArgs = {}) {
94
+ const spec = { ...requiredArgs, ...optionalArgs };
95
+ const entry = openExchanges.get(spec.exchangeId);
96
+ if (!entry)
97
+ return { ok: false, reason: 'unknown-or-closed' };
98
+ if ((spec.toolId !== undefined && entry.binding.toolId !== spec.toolId)
99
+ || (entry.binding.channel ?? deps.defaultChannel) !== (spec.channel ?? deps.defaultChannel)
100
+ || entry.binding.principalId !== spec.principalId)
101
+ return { ok: false, reason: 'binding-mismatch' };
102
+ entry.accept(spec.params);
103
+ return openExchanges.has(spec.exchangeId) ? { ok: true } : { ok: false, reason: 'unknown-or-closed' };
104
+ },
105
+ findTypedAnswerTarget({ principalId, toolId }) {
106
+ let found;
107
+ for (const [id, entry] of openExchanges) {
108
+ if (entry.binding.principalId !== principalId || entry.binding.toolId !== toolId)
109
+ continue;
110
+ if (found !== undefined)
111
+ return undefined;
112
+ found = id;
113
+ }
114
+ return found;
115
+ },
116
+ size: () => openExchanges.size,
117
+ };
118
+ }
119
+ export async function askOnce({ exchange, emission }) {
120
+ try {
121
+ await exchange.send({ emission });
122
+ return await exchange.receive({});
123
+ }
124
+ finally {
125
+ exchange.close({});
126
+ }
127
+ }
128
+ export async function askThenReport({ exchange, confirmationEmission, handle }) {
129
+ try {
130
+ await exchange.send({ emission: confirmationEmission });
131
+ const { result, outcome } = await handle(await exchange.receive({}));
132
+ if (outcome !== undefined) {
133
+ try {
134
+ await exchange.send({ emission: outcome });
135
+ }
136
+ catch { /* Model result remains authoritative. */ }
137
+ }
138
+ return result;
139
+ }
140
+ finally {
141
+ exchange.close({});
142
+ }
143
+ }
144
+ /** Typed prose is never consent; only an explicit literal decision grants approval. */
145
+ export function classifyConfirmationAnswer({ answer }) {
146
+ if (answer.status !== 'received')
147
+ return { confirmed: false, reason: answer.status };
148
+ return answer.params['decision'] === 'confirm' ? { confirmed: true } : { confirmed: false, reason: 'declined' };
149
+ }
150
+ export async function resolveConfirmationDecision(args) {
151
+ return classifyConfirmationAnswer({ answer: await askOnce(args) });
152
+ }
153
+ //# sourceMappingURL=surface-exchanges.js.map
@@ -30,7 +30,7 @@ export declare const denyAllTerminalCreatePolicy: ToolPolicy;
30
30
  * package (`@jini-ai/daemon`) actually declares — this is exactly the
31
31
  * resolver injection seam `spawnHelperCandidatePaths` was built for.
32
32
  */
33
- export declare function loadRealSpawnPty(): Promise<PtySpawn>;
33
+ export declare function loadRealSpawnPty(_requiredArgs: Record<string, never>): Promise<PtySpawn>;
34
34
  /** The public, principal-neutral session snapshot this module exposes — deliberately its own shape rather than re-exporting `@jini-ai/platform`'s `TerminalSession` verbatim, since that type carries an OD-shaped per-session grouping key (see `@jini-ai/platform`'s `terminal.ts`) this module replaces entirely with the caller-supplied `resourceRef` (see {@link CreateTerminalSessionOptions}), tracked only in this module's own metadata and never forwarded to `@jini-ai/platform` at all — this package's own identifier-neutrality lint (`__tests__/identifier-lint.test.ts`) forbids that origin field's exact name from appearing anywhere in this package's source. */
35
35
  export interface TerminalSessionInfo {
36
36
  readonly id: string;
@@ -81,17 +81,49 @@ export type TerminalSessionActionResult = {
81
81
  export type TerminalSessionAttachResult = 'attached' | 'ended' | 'not-found';
82
82
  export interface TerminalSessionManager {
83
83
  /** The one call gated through `ToolExecutor` (see module doc §2) — this method is what `createTerminalToolRegistrations`'s handler calls, never invoked directly by a route. */
84
- create(principal: Principal, options: CreateTerminalSessionOptions): Promise<TerminalSessionInfo>;
85
- get(principal: Principal, id: string): TerminalSessionAccessResult;
84
+ create(args: {
85
+ readonly principal: Principal;
86
+ readonly options: CreateTerminalSessionOptions;
87
+ }): Promise<TerminalSessionInfo>;
88
+ get(args: {
89
+ readonly principal: Principal;
90
+ readonly id: string;
91
+ }): TerminalSessionAccessResult;
86
92
  /** Sessions the given principal owns, optionally narrowed by `resourceRef`. Never returns another principal's sessions. */
87
- list(principal: Principal, filter?: TerminalSessionListFilter): readonly TerminalSessionInfo[];
88
- write(principal: Principal, id: string, input: string): Promise<TerminalSessionActionResult>;
89
- resize(principal: Principal, id: string, cols: number, rows: number): Promise<TerminalSessionActionResult>;
90
- kill(principal: Principal, id: string, signal?: string): Promise<TerminalSessionActionResult>;
93
+ list(args: {
94
+ readonly principal: Principal;
95
+ }, optionalArgs?: {
96
+ readonly filter?: TerminalSessionListFilter;
97
+ }): readonly TerminalSessionInfo[];
98
+ write(args: {
99
+ readonly principal: Principal;
100
+ readonly id: string;
101
+ readonly input: string;
102
+ }): Promise<TerminalSessionActionResult>;
103
+ resize(args: {
104
+ readonly principal: Principal;
105
+ readonly id: string;
106
+ readonly cols: number;
107
+ readonly rows: number;
108
+ }): Promise<TerminalSessionActionResult>;
109
+ kill(args: {
110
+ readonly principal: Principal;
111
+ readonly id: string;
112
+ }, optionalArgs?: {
113
+ readonly signal?: string;
114
+ }): Promise<TerminalSessionActionResult>;
91
115
  /** Replays scrollback after `lastEventId` and attaches `sink` for live output — ownership-checked the same as `write`/`resize`/`kill`, since reading a session's output is exactly as sensitive as writing to it. */
92
- attach(principal: Principal, id: string, lastEventId: number, sink: TerminalSseSink): TerminalSessionAttachResult;
116
+ attach(args: {
117
+ readonly principal: Principal;
118
+ readonly id: string;
119
+ readonly lastEventId: number;
120
+ readonly sink: TerminalSseSink;
121
+ }): TerminalSessionAttachResult;
93
122
  /** No ownership check: removing a sink that was never attached (e.g. a foreign/unknown id) is already a safe no-op at the `@jini-ai/platform` layer, and detach carries no capability of its own. */
94
- detach(id: string, sink: TerminalSseSink): void;
123
+ detach(args: {
124
+ readonly id: string;
125
+ readonly sink: TerminalSseSink;
126
+ }): void;
95
127
  shutdownActive(options?: {
96
128
  graceMs?: number;
97
129
  }): Promise<void>;
@@ -115,7 +147,7 @@ export interface CreateTerminalSessionManagerOptions {
115
147
  * the kill/write/resize lock, and (via {@link createTerminalToolRegistrations})
116
148
  * `ToolExecutor` gating for creation. See module doc for the full design.
117
149
  */
118
- export declare function createTerminalSessionManager(options?: CreateTerminalSessionManagerOptions): TerminalSessionManager;
150
+ export declare function createTerminalSessionManager(requiredArgs: Record<string, never>, optionalArgs?: Pick<CreateTerminalSessionManagerOptions, "terminalService" | "loadSpawnPty" | "maxEvents" | "maxBufferBytes" | "exitTailBytes" | "flushIntervalMs" | "flushThresholdBytes" | "ttlMs" | "shutdownGraceMs">): TerminalSessionManager;
119
151
  export interface CreateTerminalToolRegistrationsOptions {
120
152
  readonly manager: TerminalSessionManager;
121
153
  /** @default {@link denyAllTerminalCreatePolicy} — see its doc for why a permissive default was rejected. */
@@ -135,5 +167,5 @@ export interface TerminalToolRegistrations {
135
167
  * audit trail (matching `db-ops.ts`'s `createDaemonDbToolRegistrations`
136
168
  * precedent).
137
169
  */
138
- export declare function createTerminalToolRegistrations(options: CreateTerminalToolRegistrationsOptions): TerminalToolRegistrations;
170
+ export declare function createTerminalToolRegistrations(requiredArgs: Pick<CreateTerminalToolRegistrationsOptions, "manager">, optionalArgs?: Pick<CreateTerminalToolRegistrationsOptions, "policy" | "requiresConfirmation" | "timeoutMs">): TerminalToolRegistrations;
139
171
  //# sourceMappingURL=terminal-session.d.ts.map