@jini-ai/daemon 0.6.15 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +14 -4
- package/dist/agent-executor/codex/isolation.js +2 -3
- package/dist/agent-executor/contracts.d.ts +1 -1
- package/dist/agent-executor/contracts.js +0 -1
- package/dist/agent-executor/event-translation.d.ts +3 -3
- package/dist/agent-executor/event-translation.js +4 -4
- package/dist/agent-executor/events.js +1 -3
- package/dist/agent-executor/index.d.ts +5 -5
- package/dist/agent-executor/index.js +5 -5
- package/dist/ai-trace-lifecycle.d.ts +1 -1
- package/dist/ai-trace-lifecycle.js +1 -1
- package/dist/core/tool-audit.js +11 -7
- package/dist/frontend-capability-tools.d.ts +2 -0
- package/dist/frontend-capability-tools.js +7 -1
- package/dist/frontend-session-registry.d.ts +42 -3
- package/dist/frontend-session-registry.js +61 -9
- package/dist/http/frontend-control.d.ts +13 -0
- package/dist/http/frontend-control.js +14 -3
- package/dist/http/frontend-guest-scope.d.ts +32 -0
- package/dist/http/frontend-guest-scope.js +63 -0
- package/dist/http/frontend-sessions.d.ts +29 -6
- package/dist/http/frontend-sessions.js +106 -28
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/store/event-log/sqlite-kernel.js +2 -1
- package/dist/terminal-session.d.ts +51 -20
- package/dist/terminal-session.js +6 -89
- package/dist/terminal.adapter.d.ts +22 -0
- package/dist/terminal.adapter.js +47 -0
- package/dist/tool-executor.d.ts +4 -2
- package/dist/tool-executor.js +50 -14
- package/dist/tool-result-media.d.ts +4 -0
- package/dist/tool-result-media.js +3 -2
- package/package.json +29 -38
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in native PTY adapter. Import from `@jini-ai/daemon/terminal` and inject
|
|
3
|
+
* `() => loadRealSpawnPty({})` into the session manager's `loadSpawnPty` port.
|
|
4
|
+
*/
|
|
5
|
+
import { loadOptionalPeer } from '@jini-ai/core/optional-peer';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { ensureSpawnHelperExecutable, spawnHelperCandidatePaths, } from '@jini-ai/platform';
|
|
8
|
+
const daemonRequire = createRequire(import.meta.url);
|
|
9
|
+
/** Adapts a real `node-pty` `IPty` to `@jini-ai/platform`'s narrower `PtyProcess` port — explicit per-member mapping rather than relying on structural assignability, matching this package's established "explicit adapter, no implicit coercion" convention (e.g. `agent-executor.ts`'s `toStringEnvRecord`). */
|
|
10
|
+
function toPtyProcess(pty) {
|
|
11
|
+
return {
|
|
12
|
+
onData: (cb) => {
|
|
13
|
+
pty.onData(cb);
|
|
14
|
+
},
|
|
15
|
+
onExit: (cb) => {
|
|
16
|
+
pty.onExit(cb);
|
|
17
|
+
},
|
|
18
|
+
write: (input) => pty.write(input),
|
|
19
|
+
resize: (cols, rows) => pty.resize(cols, rows),
|
|
20
|
+
kill: (signal) => pty.kill(signal),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The real, `node-pty`-backed {@link PtySpawn}. Dynamically imports
|
|
25
|
+
* `node-pty` (mirroring OD's origin `await import('node-pty')`, not a
|
|
26
|
+
* static top-level import) so a host process whose platform/environment
|
|
27
|
+
* lacks a usable `node-pty` addon can still boot — only an actual
|
|
28
|
+
* `terminal.create` call fails, cleanly, rather than crashing at
|
|
29
|
+
* module-eval time. Before the first spawn, repairs `node-pty`'s bundled
|
|
30
|
+
* `spawn-helper` binary's executable bit (pnpm unpacks it non-executable —
|
|
31
|
+
* see `@jini-ai/platform`'s `terminal.ts` module doc for the full story) via
|
|
32
|
+
* that package's own `spawnHelperCandidatePaths`/`ensureSpawnHelperExecutable`,
|
|
33
|
+
* but with the resolver anchored at **this** module
|
|
34
|
+
* (`createRequire(import.meta.url)` here, not `@jini-ai/platform`'s own
|
|
35
|
+
* default): `@jini-ai/platform` deliberately does not depend on `node-pty`, so
|
|
36
|
+
* its default `require.resolve('node-pty')` would not find the copy this
|
|
37
|
+
* package (`@jini-ai/daemon`) actually declares — this is exactly the
|
|
38
|
+
* resolver injection seam `spawnHelperCandidatePaths` was built for.
|
|
39
|
+
* @throws A feature-specific load error with the original resolution/ABI error as `cause`.
|
|
40
|
+
* @complexity O(1) adapter work; module loading and helper repair belong to the platform.
|
|
41
|
+
*/
|
|
42
|
+
export async function loadRealSpawnPty(_requiredArgs, _optionalArgs = {}) {
|
|
43
|
+
ensureSpawnHelperExecutable(spawnHelperCandidatePaths({ resolve: (specifier) => daemonRequire.resolve(specifier) }));
|
|
44
|
+
const nodePty = await loadOptionalPeer({ feature: 'Interactive terminals', dependency: 'node-pty', load: () => import('node-pty') });
|
|
45
|
+
return (shell, args, options) => toPtyProcess(nodePty.spawn(shell, args, options));
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=terminal.adapter.js.map
|
package/dist/tool-executor.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface ToolConfirmationRequest {
|
|
|
17
17
|
* The transport-supplied seam for the two touchpoints a headless kernel
|
|
18
18
|
* cannot render itself. Both are optional: a tool whose `ToolPolicy`
|
|
19
19
|
* always resolves definitively needs no `onAuthorize`, and a tool with
|
|
20
|
-
* `requiresConfirmation` unset
|
|
20
|
+
* `requiresConfirmation` unset has no default prompt; a host may strengthen a single call with `requireConfirmation: true`.
|
|
21
21
|
*/
|
|
22
22
|
export interface ExecutionDelegate {
|
|
23
23
|
/**
|
|
@@ -42,7 +42,7 @@ export interface ExecutionDelegate {
|
|
|
42
42
|
*/
|
|
43
43
|
onConfirm?(request: ToolConfirmationRequest): ConfirmationDecision | Promise<ConfirmationDecision> | void;
|
|
44
44
|
}
|
|
45
|
-
export type ToolExecutionPhase = 'requested' | 'authorized' | 'denied' | 'confirmed' | 'confirmation-denied' | 'started' | 'completed' | 'failed' | 'timed-out' | 'cancelled';
|
|
45
|
+
export type ToolExecutionPhase = 'requested' | 'authorized' | 'denied' | 'confirmed' | 'approval-recorded' | 'confirmation-denied' | 'started' | 'completed' | 'failed' | 'timed-out' | 'cancelled';
|
|
46
46
|
export interface ToolExecutionAuditEvent {
|
|
47
47
|
readonly phase: ToolExecutionPhase;
|
|
48
48
|
readonly at: number;
|
|
@@ -85,6 +85,7 @@ export interface ToolExecutor {
|
|
|
85
85
|
* @param emitSurface - Transport-supplied seam letting the handler push a human-only surface into
|
|
86
86
|
* the run's event stream *before* this call settles. Omit for a caller with no run event stream;
|
|
87
87
|
* see `@jini-ai/core`'s `SurfaceEmitter` for why a handler that waits on a human needs it.
|
|
88
|
+
* @param requireConfirmation - Strengthens this call only; cannot bypass a descriptor confirmation.
|
|
88
89
|
* @throws {ToolInputError} If `toolId` isn't registered, with up to eight registered prefix matches.
|
|
89
90
|
*/
|
|
90
91
|
execute(args: {
|
|
@@ -95,6 +96,7 @@ export interface ToolExecutor {
|
|
|
95
96
|
}, optionalArgs?: {
|
|
96
97
|
readonly signal?: AbortSignal;
|
|
97
98
|
readonly emitSurface?: SurfaceEmitter;
|
|
99
|
+
readonly requireConfirmation?: true;
|
|
98
100
|
}): Promise<ToolExecutionResult>;
|
|
99
101
|
/** @throws If `executionId` has no confirmation currently pending (already resumed, never required one, or unknown). */
|
|
100
102
|
resumeConfirmation(args: {
|
package/dist/tool-executor.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* answer gets back in.
|
|
47
47
|
*/
|
|
48
48
|
import { randomUUID } from 'node:crypto';
|
|
49
|
-
import { ToolInputError, withNormalizationNotes, ToolInputNormalizationError, isReadOnlyTool, } from '@jini-ai/core';
|
|
49
|
+
import { ToolInputError, APPROVAL_WAIT_TIMEOUT_MS, withNormalizationNotes, ToolInputNormalizationError, isReadOnlyTool, } from '@jini-ai/core';
|
|
50
50
|
import { authorizeToolInvocation } from '@jini-ai/core/composition';
|
|
51
51
|
import { toolCandidates } from './core/tool-candidates.js';
|
|
52
52
|
/**
|
|
@@ -206,15 +206,45 @@ export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
206
206
|
* what distinguishes a `timed-out` result from a `cancelled` one.
|
|
207
207
|
*/
|
|
208
208
|
function startTimeout(controller, timeoutMs) {
|
|
209
|
-
|
|
210
|
-
return { handle: undefined, timedOut: () => false };
|
|
209
|
+
let handle;
|
|
211
210
|
let timedOut = false;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
let waiting = false;
|
|
212
|
+
let approvalWindowUsed = false;
|
|
213
|
+
let remaining = timeoutMs;
|
|
214
|
+
let started = Date.now();
|
|
215
|
+
const stop = () => {
|
|
216
|
+
if (handle === undefined)
|
|
217
|
+
return;
|
|
218
|
+
clearTimeout(handle);
|
|
219
|
+
handle = undefined;
|
|
220
|
+
};
|
|
221
|
+
const arm = (duration) => {
|
|
222
|
+
stop();
|
|
223
|
+
started = Date.now();
|
|
224
|
+
if (duration === undefined)
|
|
225
|
+
return;
|
|
226
|
+
handle = setTimeout(() => { timedOut = true; controller.abort(); }, duration);
|
|
227
|
+
handle.unref?.();
|
|
228
|
+
};
|
|
229
|
+
arm(remaining);
|
|
230
|
+
return {
|
|
231
|
+
stop,
|
|
232
|
+
timedOut: () => timedOut,
|
|
233
|
+
/** Human waiting is bounded independently and cannot replenish the execution budget.
|
|
234
|
+
* Duplicate progress frames cannot extend either deadline. */
|
|
235
|
+
approval: ({ pending }) => {
|
|
236
|
+
if (pending === waiting || controller.signal.aborted)
|
|
237
|
+
return;
|
|
238
|
+
if (pending && approvalWindowUsed)
|
|
239
|
+
return;
|
|
240
|
+
if (pending)
|
|
241
|
+
approvalWindowUsed = true;
|
|
242
|
+
if (pending && remaining !== undefined)
|
|
243
|
+
remaining = Math.max(0, remaining - (Date.now() - started));
|
|
244
|
+
waiting = pending;
|
|
245
|
+
arm(pending ? APPROVAL_WAIT_TIMEOUT_MS : remaining);
|
|
246
|
+
},
|
|
247
|
+
};
|
|
218
248
|
}
|
|
219
249
|
/** Records a terminal transition that carries no detail and returns its bare result. */
|
|
220
250
|
function terminal({ executionId, status }) {
|
|
@@ -406,8 +436,8 @@ export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
406
436
|
// the catch). Repeating one call avoids leaving an uncoverable branch
|
|
407
437
|
// behind instead of writing a contrived test or suppressing it.
|
|
408
438
|
const cleanup = () => {
|
|
409
|
-
|
|
410
|
-
|
|
439
|
+
timeout.stop();
|
|
440
|
+
approvalStateChange({ run, executionId, toolId: call.toolId, pending: false });
|
|
411
441
|
};
|
|
412
442
|
appendEvent(executionId, 'started');
|
|
413
443
|
try {
|
|
@@ -422,7 +452,13 @@ export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
422
452
|
signal: controller.signal,
|
|
423
453
|
}, {
|
|
424
454
|
...surfaceOption(emitSurface),
|
|
425
|
-
onApprovalStateChange: ({ pending }) =>
|
|
455
|
+
onApprovalStateChange: ({ pending, decision }) => {
|
|
456
|
+
// The tab owns human confirmation; retain its outcome without opening a chat surface.
|
|
457
|
+
if (decision)
|
|
458
|
+
appendEvent(executionId, 'approval-recorded', `frontend approval: ${decision}`);
|
|
459
|
+
timeout.approval({ pending });
|
|
460
|
+
approvalStateChange({ run, executionId, toolId: call.toolId, pending });
|
|
461
|
+
},
|
|
426
462
|
invokeTool: nestedInvoker({ principal, run, toolId: call.toolId, invocationRegistry: call.invocationRegistry, signal: controller.signal }, { emitSurface }),
|
|
427
463
|
});
|
|
428
464
|
cleanup();
|
|
@@ -433,7 +469,7 @@ export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
433
469
|
return failureResult({ executionId, err, timedOut: timeout.timedOut(), aborted: controller.signal.aborted }, { normalizationNotes, guidance });
|
|
434
470
|
}
|
|
435
471
|
}
|
|
436
|
-
async function execute({ principal, run, toolId, input }, { signal, emitSurface, invocationRegistry = registry } = {}) {
|
|
472
|
+
async function execute({ principal, run, toolId, input }, { signal, emitSurface, invocationRegistry = registry, requireConfirmation } = {}) {
|
|
437
473
|
// Existence is checked separately from — and before — authorization so the audit record can
|
|
438
474
|
// be opened before the (possibly slow, possibly throwing) authorization gate runs, matching
|
|
439
475
|
// pre-authorizeToolInvocation timing: `requested` used to precede `policy.authorize`, and a
|
|
@@ -494,7 +530,7 @@ export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
494
530
|
}
|
|
495
531
|
const admitted = gate.allowed;
|
|
496
532
|
const { descriptor } = admitted;
|
|
497
|
-
if (descriptor.requiresConfirmation) {
|
|
533
|
+
if (descriptor.requiresConfirmation || requireConfirmation) {
|
|
498
534
|
const declined = await confirmGate({ executionId, principal, run, tool: descriptor, input: admitted.input });
|
|
499
535
|
if (declined) {
|
|
500
536
|
cleanupCancellation();
|
|
@@ -42,6 +42,10 @@ export interface ToolResultImageBlock {
|
|
|
42
42
|
readonly mimeType: string;
|
|
43
43
|
/** Base64-encoded image bytes. */
|
|
44
44
|
readonly data: string;
|
|
45
|
+
/** Optional caption and alt text for a chat renderer. Not an MCP field; MCP clients ignore it. */
|
|
46
|
+
readonly title?: string;
|
|
47
|
+
/** Optional link the chat opens on click (e.g. the full-size file). The renderer still checks it. */
|
|
48
|
+
readonly href?: string;
|
|
45
49
|
}
|
|
46
50
|
/** A tool result content block `media` may carry. Modeled on MCP's own content-block vocabulary —
|
|
47
51
|
* see this module's doc for why that vocabulary is reused rather than a fourth one invented. */
|
|
@@ -8,10 +8,11 @@ function isRecord(value) {
|
|
|
8
8
|
function asImageBlock(block) {
|
|
9
9
|
if (!isRecord(block) || block['type'] !== 'image')
|
|
10
10
|
return undefined;
|
|
11
|
-
const { mimeType, data } = block;
|
|
11
|
+
const { mimeType, data, title, href } = block;
|
|
12
12
|
if (typeof mimeType !== 'string' || typeof data !== 'string')
|
|
13
13
|
return undefined;
|
|
14
|
-
|
|
14
|
+
// Untrusted extras are kept only as strings, so a caption or link can never smuggle an object.
|
|
15
|
+
return { type: 'image', mimeType, data, ...(typeof title === 'string' ? { title } : {}), ...(typeof href === 'string' ? { href } : {}) };
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
18
|
* Splits a tool handler's raw return value into its recognized media blocks and the remainder,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.17",
|
|
4
4
|
"description": "The stateful agent daemon runtime: run lifecycle, durable event-log port, the tool-execution boundary, agent executor, terminal sessions, and routines.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"runtime": "node",
|
|
15
15
|
"entries": {
|
|
16
16
|
".": "node",
|
|
17
|
+
"./terminal": "node",
|
|
17
18
|
"./store/event-log/sqlite": "node",
|
|
18
19
|
"./store/agent-sessions": "universal",
|
|
19
20
|
"./store/agent-sessions/sqlite": "node",
|
|
@@ -34,6 +35,12 @@
|
|
|
34
35
|
"src/surface-exchanges.ts",
|
|
35
36
|
"src/scheduler.ts"
|
|
36
37
|
]
|
|
38
|
+
},
|
|
39
|
+
"optionalPeers": {
|
|
40
|
+
"node-pty": "opt-in: import @jini-ai/daemon/terminal — Interactive terminals could not load node-pty. Install node-pty to enable this feature, or check its installed version and native binaries.",
|
|
41
|
+
"better-sqlite3": "opt-in: sqlite storage — SQLite storage could not load better-sqlite3. Install better-sqlite3 to enable this feature, or check its installed version and native binaries.",
|
|
42
|
+
"@electric-sql/pglite": "opt-in: embedded postgresql storage — Embedded PostgreSQL storage could not load @electric-sql/pglite. Install @electric-sql/pglite to enable this feature, or check its installed version and native binaries.",
|
|
43
|
+
"pg": "opt-in: postgresql storage — PostgreSQL storage could not load pg. Install pg to enable this feature, or check its installed version and native binaries."
|
|
37
44
|
}
|
|
38
45
|
},
|
|
39
46
|
"type": "module",
|
|
@@ -46,6 +53,11 @@
|
|
|
46
53
|
"import": "./dist/index.js",
|
|
47
54
|
"default": "./dist/index.js"
|
|
48
55
|
},
|
|
56
|
+
"./terminal": {
|
|
57
|
+
"types": "./dist/terminal.adapter.d.ts",
|
|
58
|
+
"import": "./dist/terminal.adapter.js",
|
|
59
|
+
"default": "./dist/terminal.adapter.js"
|
|
60
|
+
},
|
|
49
61
|
"./store/event-log/sqlite": {
|
|
50
62
|
"types": "./dist/store/event-log/sqlite.d.ts",
|
|
51
63
|
"import": "./dist/store/event-log/sqlite.js",
|
|
@@ -140,44 +152,35 @@
|
|
|
140
152
|
"@types/pg": "8.23.1",
|
|
141
153
|
"express": "^4.21.0",
|
|
142
154
|
"@types/express": "^4.17.21",
|
|
143
|
-
"@jini-ai/
|
|
144
|
-
"@jini-ai/
|
|
145
|
-
"@jini-ai/
|
|
146
|
-
"@jini-ai/core": "^0.6.
|
|
147
|
-
"@jini-ai/
|
|
148
|
-
"@jini-ai/
|
|
149
|
-
"@jini-ai/
|
|
155
|
+
"@jini-ai/http-kit": "0.6.17",
|
|
156
|
+
"@jini-ai/chat": "0.6.17",
|
|
157
|
+
"@jini-ai/db": "0.6.17",
|
|
158
|
+
"@jini-ai/core": "^0.6.17",
|
|
159
|
+
"@jini-ai/agent-runtime": "^0.6.17",
|
|
160
|
+
"@jini-ai/platform": "^0.6.17",
|
|
161
|
+
"@jini-ai/protocol": "^0.6.17"
|
|
150
162
|
},
|
|
151
163
|
"dependencies": {
|
|
152
|
-
"
|
|
153
|
-
"
|
|
164
|
+
"smol-toml": "1.9.1",
|
|
165
|
+
"kysely": "^0.29.6",
|
|
166
|
+
"@jini-ai/core": "^0.6.17",
|
|
167
|
+
"@jini-ai/db": "^0.6.17",
|
|
168
|
+
"@jini-ai/platform": "^0.6.17",
|
|
169
|
+
"@jini-ai/agent-runtime": "^0.6.17",
|
|
170
|
+
"@jini-ai/protocol": "^0.6.17",
|
|
171
|
+
"@jini-ai/http-kit": "^0.6.17"
|
|
154
172
|
},
|
|
155
173
|
"peerDependencies": {
|
|
156
|
-
"smol-toml": "1.9.1",
|
|
157
174
|
"node-pty": "^1.1.0",
|
|
158
|
-
"@jini-ai/db": "^0.6.10",
|
|
159
|
-
"kysely": "^0.29.6",
|
|
160
175
|
"better-sqlite3": "^13.0.0",
|
|
161
176
|
"@electric-sql/pglite": "0.5.8",
|
|
162
177
|
"pg": "^8.23.0",
|
|
163
|
-
"
|
|
164
|
-
"express": "^4.21.0 || ^5.0.0",
|
|
165
|
-
"@jini-ai/agent-runtime": "^0.6.10",
|
|
166
|
-
"@jini-ai/platform": "^0.6.10"
|
|
178
|
+
"express": "^4.21.0 || ^5.0.0"
|
|
167
179
|
},
|
|
168
180
|
"peerDependenciesMeta": {
|
|
169
|
-
"smol-toml": {
|
|
170
|
-
"optional": true
|
|
171
|
-
},
|
|
172
181
|
"node-pty": {
|
|
173
182
|
"optional": true
|
|
174
183
|
},
|
|
175
|
-
"@jini-ai/db": {
|
|
176
|
-
"optional": true
|
|
177
|
-
},
|
|
178
|
-
"kysely": {
|
|
179
|
-
"optional": true
|
|
180
|
-
},
|
|
181
184
|
"better-sqlite3": {
|
|
182
185
|
"optional": true
|
|
183
186
|
},
|
|
@@ -186,18 +189,6 @@
|
|
|
186
189
|
},
|
|
187
190
|
"pg": {
|
|
188
191
|
"optional": true
|
|
189
|
-
},
|
|
190
|
-
"@jini-ai/http-kit": {
|
|
191
|
-
"optional": true
|
|
192
|
-
},
|
|
193
|
-
"express": {
|
|
194
|
-
"optional": true
|
|
195
|
-
},
|
|
196
|
-
"@jini-ai/agent-runtime": {
|
|
197
|
-
"optional": true
|
|
198
|
-
},
|
|
199
|
-
"@jini-ai/platform": {
|
|
200
|
-
"optional": true
|
|
201
192
|
}
|
|
202
193
|
},
|
|
203
194
|
"scripts": {
|