@jini-ai/server 0.5.1 → 0.6.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.
@@ -232,6 +232,9 @@ export function createBuiltInFeatures(options = {}) {
232
232
  registerRunRoutes({ app: app, deps: {
233
233
  lifecycle: services.context.kernel.lifecycle,
234
234
  ...(onStarted === undefined ? {} : { onStarted }),
235
+ // A mid-run message reaches whichever live run the kernel's own executor is driving;
236
+ // a run some other driver owns reads as not-running, never as delivered.
237
+ deliverUserMessage: (input) => services.context.kernel.agentExecutor.sendUserMessage(input),
235
238
  }, adapter: services.context.adapter });
236
239
  },
237
240
  }),
@@ -8,19 +8,24 @@ import { type SqliteDb, type SqliteSyncOpener } from '@jini-ai/db/sqlite';
8
8
  export declare function migrate({ db }: {
9
9
  db: SqliteDb;
10
10
  }): void;
11
- /**
12
- * Opens/caches <dataDir>/app.sqlite using the injected opener, WAL and foreign keys.
13
- * A new path closes the previous singleton; setup failure closes only the newly acquired handle.
14
- * @throws The original host acquisition/schema failure, or a missing opener.
15
- * @complexity O(1) acquisition/bootstrap calls, independent of stored row count.
16
- */
17
- export declare function openDatabase({ projectRoot, open }: {
11
+ /** Creates one owned connection cache; other hosts' paths and handles are independent.
12
+ * Returns lazy open/close ports; creation performs no I/O. @complexity O(1). */
13
+ export declare function createLegacySqliteStorage(_required: Record<string, never>, _optional?: Record<string, never>): {
14
+ openDatabase: ({ projectRoot, open }: {
15
+ projectRoot: string;
16
+ open: SqliteSyncOpener;
17
+ }, options?: {
18
+ dataDir?: string;
19
+ filesystem?: Pick<typeof fs, "mkdirSync">;
20
+ }) => SqliteDb;
21
+ closeDatabase: () => void;
22
+ };
23
+ export declare const openDatabase: ({ projectRoot, open }: {
18
24
  projectRoot: string;
19
25
  open: SqliteSyncOpener;
20
26
  }, options?: {
21
27
  dataDir?: string;
22
- filesystem?: Pick<typeof fs, 'mkdirSync'>;
23
- }): SqliteDb;
24
- /** Close the active owned singleton once; clearing state first permits a clean retry after failure. */
25
- export declare function closeDatabase(): void;
28
+ filesystem?: Pick<typeof fs, "mkdirSync">;
29
+ }) => SqliteDb;
30
+ export declare const closeDatabase: () => void;
26
31
  //# sourceMappingURL=sqlite.d.ts.map
@@ -1,12 +1,10 @@
1
- /** Local app.sqlite singleton policy; the host explicitly supplies its SQLite driver. */
1
+ /** Host-owned app.sqlite lifecycle; the host explicitly supplies its SQLite driver. */
2
2
  import path from 'node:path';
3
3
  import fs from 'node:fs';
4
4
  import { openSqliteConnection } from '@jini-ai/db/sqlite';
5
5
  import { LEGACY_CHAT_DDL } from '@jini-ai/chat/store/legacy/sqlite';
6
6
  import { LEGACY_AGENT_SESSIONS_DDL } from '@jini-ai/daemon/store/agent-sessions/sqlite';
7
7
  import { LEGACY_PROJECTS_DDL } from '../../store/projects/sqlite.js';
8
- let dbInstance = null;
9
- let dbFile = null;
10
8
  /**
11
9
  * Explicit idempotent bootstrap of unchanged legacy tables; no versioned migration ledger.
12
10
  * @param db Borrowed host handle; this operation neither opens nor closes it.
@@ -21,43 +19,54 @@ let dbFile = null;
21
19
  export function migrate({ db }) {
22
20
  db.exec(LEGACY_PROJECTS_DDL + LEGACY_CHAT_DDL + LEGACY_AGENT_SESSIONS_DDL);
23
21
  }
24
- /**
25
- * Opens/caches <dataDir>/app.sqlite using the injected opener, WAL and foreign keys.
26
- * A new path closes the previous singleton; setup failure closes only the newly acquired handle.
27
- * @throws The original host acquisition/schema failure, or a missing opener.
28
- * @complexity O(1) acquisition/bootstrap calls, independent of stored row count.
29
- */
30
- export function openDatabase({ projectRoot, open }, options = {}) {
31
- // Keep the historical refusal text for JavaScript callers that omit the required port.
32
- if (!open)
33
- throw new Error('openDatabase: inject options.open');
34
- const dir = options.dataDir ? path.resolve(options.dataDir) : path.join(projectRoot, '.jini');
35
- const file = path.join(dir, 'app.sqlite');
36
- if (dbInstance && dbFile === file)
37
- return dbInstance;
38
- closeDatabase();
39
- (options.filesystem ?? fs).mkdirSync(dir, { recursive: true });
40
- let db;
41
- try {
42
- db = openSqliteConnection({ filePath: file, open({ filePath }, settings) { db = open(filePath, settings); return db; } }, { pragmas: ['journal_mode = WAL', 'foreign_keys = ON'] });
43
- migrate({ db });
44
- dbInstance = db;
45
- dbFile = file;
46
- return db;
47
- }
48
- catch (error) {
22
+ /** Creates one owned connection cache; other hosts' paths and handles are independent.
23
+ * Returns lazy open/close ports; creation performs no I/O. @complexity O(1). */
24
+ export function createLegacySqliteStorage(_required, _optional = {}) {
25
+ let dbInstance = null;
26
+ let dbFile = null;
27
+ /**
28
+ * Opens/caches <dataDir>/app.sqlite using the injected opener, WAL and foreign keys.
29
+ * A new path closes the previous handle owned by this storage instance; setup failure closes only the newly acquired handle.
30
+ * @throws The original host acquisition/schema failure, or a missing opener.
31
+ * @complexity O(1) acquisition/bootstrap calls, independent of stored row count.
32
+ */
33
+ function openDatabase({ projectRoot, open }, options = {}) {
34
+ // Keep the historical refusal text for JavaScript callers that omit the required port.
35
+ if (!open)
36
+ throw new Error('openDatabase: inject options.open');
37
+ const dir = options.dataDir ? path.resolve(options.dataDir) : path.join(projectRoot, '.jini');
38
+ const file = path.join(dir, 'app.sqlite');
39
+ if (dbInstance && dbFile === file)
40
+ return dbInstance;
41
+ closeDatabase();
42
+ (options.filesystem ?? fs).mkdirSync(dir, { recursive: true });
43
+ let db;
49
44
  try {
50
- db?.close();
45
+ db = openSqliteConnection({ filePath: file, open({ filePath }, settings) { db = open(filePath, settings); return db; } }, { pragmas: ['journal_mode = WAL', 'foreign_keys = ON'] });
46
+ migrate({ db });
47
+ dbInstance = db;
48
+ dbFile = file;
49
+ return db;
50
+ }
51
+ catch (error) {
52
+ try {
53
+ db?.close();
54
+ }
55
+ catch { /* Keep the original bootstrap failure. */ }
56
+ throw error;
51
57
  }
52
- catch { /* Keep the original bootstrap failure. */ }
53
- throw error;
54
58
  }
59
+ /** Close the active owned handle once; clearing state first permits a clean retry after failure. */
60
+ function closeDatabase() {
61
+ const db = dbInstance;
62
+ dbInstance = null;
63
+ dbFile = null;
64
+ db?.close();
65
+ }
66
+ return { openDatabase, closeDatabase };
55
67
  }
56
- /** Close the active owned singleton once; clearing state first permits a clean retry after failure. */
57
- export function closeDatabase() {
58
- const db = dbInstance;
59
- dbInstance = null;
60
- dbFile = null;
61
- db?.close();
62
- }
68
+ // Compatibility facade for existing hosts; new hosts inject their own storage instance.
69
+ const defaultStorage = createLegacySqliteStorage({});
70
+ export const openDatabase = defaultStorage.openDatabase;
71
+ export const closeDatabase = defaultStorage.closeDatabase;
63
72
  //# sourceMappingURL=sqlite.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/server",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -65,27 +65,27 @@
65
65
  "@vitest/coverage-v8": "^2.1.9",
66
66
  "better-sqlite3": "^13.0.0",
67
67
  "express": "^4.21.0",
68
- "@jini-ai/core": "0.4.2",
69
- "@jini-ai/http-kit": "0.4.1",
70
- "@jini-ai/daemon": "0.5.4",
71
- "@jini-ai/agent-runtime": "0.4.2",
68
+ "@jini-ai/agent-runtime": "0.4.5",
72
69
  "@jini-ai/sidecar": "0.4.1",
73
- "@jini-ai/db": "0.3.1",
74
- "@jini-ai/chat": "0.5.5",
70
+ "@jini-ai/daemon": "0.6.1",
71
+ "@jini-ai/db": "0.3.2",
72
+ "@jini-ai/http-kit": "0.4.1",
73
+ "@jini-ai/core": "0.4.3",
74
+ "@jini-ai/chat": "0.5.8",
75
75
  "@jini-ai/registry": "0.5.2"
76
76
  },
77
77
  "dependencies": {
78
- "@jini-ai/core": "^0.4.2"
78
+ "@jini-ai/core": "^0.4.3"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "better-sqlite3": "^13.0.0",
82
- "@jini-ai/agent-runtime": "^0.4.1",
83
- "@jini-ai/daemon": "^0.5.4",
82
+ "@jini-ai/agent-runtime": "^0.4.5",
83
+ "@jini-ai/daemon": "^0.6.1",
84
84
  "@jini-ai/http-kit": "^0.4.0",
85
85
  "@jini-ai/sidecar": "^0.4.0",
86
86
  "express": "^4.21.0",
87
- "@jini-ai/db": "^0.3.0",
88
- "@jini-ai/chat": "^0.5.4",
87
+ "@jini-ai/db": "^0.3.2",
88
+ "@jini-ai/chat": "^0.5.8",
89
89
  "@jini-ai/registry": "^0.5.1"
90
90
  },
91
91
  "peerDependenciesMeta": {
@@ -1,76 +0,0 @@
1
- import type { ToolPolicy, ToolRegistration } from '@jini-ai/core';
2
- import { type FrontendCapabilitySpec } from '@jini-ai/daemon';
3
- import { type RunCreateRequest, type RunStartHandler } from '@jini-ai/http-kit';
4
- import type { LocalNodeHttpExtension } from './create-local-node-daemon.js';
5
- export interface FrontendBindErrorContext {
6
- readonly runId: string;
7
- readonly error: unknown;
8
- }
9
- export interface CreateFrontendControlOptions {
10
- /**
11
- * The capabilities to expose. `@jini-ai/agentic`'s `PAGE_CAPABILITIES` and `@jini-ai/chat-core`'s
12
- * `CHAT_CAPABILITIES` satisfy this structurally — the engine never imports that vocabulary
13
- * (see `frontend-capability-tools.ts`'s module doc for why the edge points one way).
14
- */
15
- readonly capabilities: readonly FrontendCapabilitySpec[];
16
- /**
17
- * Reads this run's bind token out of the host's own run request, or returns `undefined` when the
18
- * run has no originating surface.
19
- *
20
- * Required, and deliberately not defaulted. A default would have to guess where a host keeps the
21
- * token, and guessing wrong fails *open* in the confusing direction: the run starts, the agent
22
- * gets its tools, and every page call fails later with "no frontend is bound" — a symptom that
23
- * looks like a browser problem and is actually a config one.
24
- *
25
- * Returning `undefined` is normal, not an error. A run started from a CLI has no surface, and
26
- * capabilities whose `surface` is `'server'` do not need one.
27
- */
28
- readonly resolveBindToken: (request: RunCreateRequest) => string | undefined;
29
- /** @default `@jini-ai/daemon`'s `denyAllFrontendCapabilityPolicy` — a host grants access explicitly. */
30
- readonly policy?: ToolPolicy;
31
- /** @default `@jini-ai/daemon`'s `DEFAULT_FRONTEND_CAPABILITY_TIMEOUT_MS`. */
32
- readonly timeoutMs?: number;
33
- /** Applied to every registration. Frontend output is untrusted page text; bound it. */
34
- readonly maxOutputBytes?: number;
35
- /**
36
- * Host-owned sink for a bind failure. Defaults to `console.error`.
37
- *
38
- * A failed bind does not fail the run. The run is still legitimate — the agent simply cannot
39
- * drive the page, and every capability call it makes will say so by name. Killing a run because
40
- * one optional channel could not be established would turn a degraded session into no session.
41
- */
42
- readonly onBindError?: (context: FrontendBindErrorContext) => void;
43
- }
44
- export interface FrontendControl {
45
- /** Mounts the frontend-session stream and response routes. Pass in `httpExtensions`. */
46
- readonly httpExtension: LocalNodeHttpExtension;
47
- /** One gated tool per capability. Pass in `toolRegistrations`. */
48
- readonly toolRegistrations: readonly ToolRegistration[];
49
- /**
50
- * Binds each starting run to the surface that originated it, and releases the binding when the
51
- * run reaches a terminal state so a long-lived tab does not accumulate dead bindings.
52
- *
53
- * Compose it with whatever the host already does on run start — this hook only binds, it never
54
- * executes anything:
55
- *
56
- * ```ts
57
- * onRunStarted: (context) => {
58
- * frontend.bindOnStarted(context);
59
- * void startMyAgent(context);
60
- * }
61
- * ```
62
- */
63
- readonly bindOnStarted: RunStartHandler;
64
- }
65
- /**
66
- * Builds the frontend-control bundle.
67
- *
68
- * @param options - Capabilities to expose, how to find the bind token, and the gate to apply.
69
- * @returns An HTTP extension, tool registrations, and a run-start bind hook — never the registry.
70
- * @throws Never at construction. A duplicate capability id throws later, when the registrations are
71
- * registered, naming the id (`ToolRegistry.register`'s own error).
72
- *
73
- * @complexity O(n) in `capabilities`.
74
- */
75
- export declare function createFrontendControl(options: CreateFrontendControlOptions): FrontendControl;
76
- //# sourceMappingURL=frontend-control.d.ts.map
@@ -1,77 +0,0 @@
1
- import { createFrontendCapabilityRegistrations, createFrontendSessionRegistry, } from '@jini-ai/daemon';
2
- import { registerFrontendSessionRoutes, } from '@jini-ai/http-kit';
3
- function defaultBindErrorSink(context) {
4
- // eslint-disable-next-line no-console
5
- console.error(`[@jini-ai/server] could not bind run "${context.runId}" to a frontend surface`, context.error);
6
- }
7
- /**
8
- * Builds the frontend-control bundle.
9
- *
10
- * @param options - Capabilities to expose, how to find the bind token, and the gate to apply.
11
- * @returns An HTTP extension, tool registrations, and a run-start bind hook — never the registry.
12
- * @throws Never at construction. A duplicate capability id throws later, when the registrations are
13
- * registered, naming the id (`ToolRegistry.register`'s own error).
14
- *
15
- * @complexity O(n) in `capabilities`.
16
- */
17
- export function createFrontendControl(options) {
18
- // Closed over, never exposed. See the module doc: reaching this is reaching an ungated `invoke`.
19
- const registry = createFrontendSessionRegistry();
20
- const onBindError = options.onBindError ?? defaultBindErrorSink;
21
- const toolRegistrations = createFrontendCapabilityRegistrations({
22
- registry,
23
- capabilities: options.capabilities,
24
- ...(options.policy !== undefined ? { policy: options.policy } : {}),
25
- ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
26
- ...(options.maxOutputBytes !== undefined ? { maxOutputBytes: options.maxOutputBytes } : {}),
27
- });
28
- const httpExtension = (app, context) => {
29
- registerFrontendSessionRoutes(app, { registry }, context.adapter);
30
- };
31
- /**
32
- * The error boundary the `onBindError` contract actually needs. A `RunStartHandler` throwing is
33
- * not a no-op: `@jini-ai/http-kit`'s run-start route catches it, marks the run `failed` and answers
34
- * 500 — so anything inside this hook that can throw can produce exactly the killed run that
35
- * contract promises never to cause. `resolveBindToken` is host-supplied and reads an opaque host
36
- * blob (a `JSON.parse` away from throwing), and the sink itself is host-supplied too. A sink that
37
- * throws leaves nowhere left to report, so that one is swallowed rather than escalated.
38
- */
39
- const reportBindError = (context) => {
40
- try {
41
- onBindError(context);
42
- }
43
- catch {
44
- // Deliberately terminal: the reporting channel is what failed.
45
- }
46
- };
47
- const bindOnStarted = ({ request, run, lifecycle }) => {
48
- let bindToken;
49
- try {
50
- bindToken = options.resolveBindToken(request);
51
- }
52
- catch (error) {
53
- reportBindError({ runId: run.id, error });
54
- return;
55
- }
56
- if (bindToken === undefined)
57
- return;
58
- let release;
59
- try {
60
- release = registry.bindRunByToken(run.id, bindToken);
61
- }
62
- catch (error) {
63
- reportBindError({ runId: run.id, error });
64
- return;
65
- }
66
- // Release on terminal so a tab that outlives many runs does not accumulate bindings for runs
67
- // that ended. `waitForTerminal` rejecting is itself a reason to release, not to keep waiting,
68
- // so both paths run it — and the whole chain is swallowed, because a bookkeeping failure must
69
- // never surface as an unhandled rejection that takes the process down.
70
- void lifecycle
71
- .waitForTerminal(run.id)
72
- .then(release, release)
73
- .catch(() => undefined);
74
- };
75
- return { httpExtension, toolRegistrations, bindOnStarted };
76
- }
77
- //# sourceMappingURL=frontend-control.js.map