@jini-ai/daemon 0.3.7 → 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.
- package/CHANGELOG.md +119 -0
- package/README.md +50 -8
- package/dist/agent-executor/contracts.d.ts +736 -0
- package/dist/agent-executor/contracts.js +28 -0
- package/dist/agent-executor/event-translation.d.ts +87 -0
- package/dist/agent-executor/event-translation.js +230 -0
- package/dist/agent-executor/events.d.ts +190 -0
- package/dist/agent-executor/events.js +848 -0
- package/dist/agent-executor/handoff.d.ts +18 -0
- package/dist/agent-executor/handoff.js +16 -0
- package/dist/agent-executor/index.d.ts +98 -0
- package/dist/agent-executor/index.js +98 -0
- package/dist/agent-executor/launch.d.ts +375 -0
- package/dist/agent-executor/launch.js +1001 -0
- package/dist/agent-executor/mcp-bridge.d.ts +338 -0
- package/dist/agent-executor/mcp-bridge.js +835 -0
- package/dist/agent-executor/values.d.ts +5 -0
- package/dist/agent-executor/values.js +13 -0
- package/dist/agent-executor.d.ts +382 -177
- package/dist/agent-executor.js +408 -344
- package/dist/close-status.d.ts +11 -8
- package/dist/close-status.js +31 -19
- package/dist/continuation/continuation-transport.d.ts +3 -1
- package/dist/continuation/continuation-transport.js +1 -1
- package/dist/continuation/journal.d.ts +12 -5
- package/dist/continuation/journal.js +4 -4
- package/dist/continuation/run-scoped-context-store.d.ts +13 -4
- package/dist/continuation/run-scoped-context-store.js +6 -6
- package/dist/continuation/run-start-handler.d.ts +1 -1
- package/dist/continuation/run-start-handler.js +3 -16
- package/dist/delegated-tool-bridge.d.ts +7 -3
- package/dist/delegated-tool-bridge.js +32 -31
- package/dist/event-log.d.ts +7 -5
- package/dist/event-log.js +12 -11
- package/dist/frontend-capability-tools.d.ts +1 -1
- package/dist/frontend-capability-tools.js +3 -2
- package/dist/frontend-session-registry.d.ts +32 -9
- package/dist/frontend-session-registry.js +12 -11
- package/dist/http/active-context.d.ts +105 -0
- package/dist/http/active-context.js +67 -0
- package/dist/http/agents.d.ts +68 -0
- package/dist/http/agents.js +12 -0
- package/dist/http/attachments.d.ts +618 -0
- package/dist/http/attachments.js +1034 -0
- package/dist/http/cancel-owned-runs.d.ts +49 -0
- package/dist/http/cancel-owned-runs.js +35 -0
- package/dist/http/component-catalog.d.ts +84 -0
- package/dist/http/component-catalog.js +64 -0
- package/dist/http/connectors.d.ts +209 -0
- package/dist/http/connectors.js +409 -0
- package/dist/http/daemon-status.d.ts +76 -0
- package/dist/http/daemon-status.js +55 -0
- package/dist/http/db-ops.d.ts +123 -0
- package/dist/http/db-ops.js +189 -0
- package/dist/http/delegated-tools.d.ts +113 -0
- package/dist/http/delegated-tools.js +230 -0
- package/dist/http/frontend-control.d.ts +136 -0
- package/dist/http/frontend-control.js +79 -0
- package/dist/http/frontend-sessions.d.ts +110 -0
- package/dist/http/frontend-sessions.js +180 -0
- package/dist/http/health.d.ts +95 -0
- package/dist/http/health.js +40 -0
- package/dist/http/host-tools.d.ts +214 -0
- package/dist/http/host-tools.js +329 -0
- package/dist/http/index.d.ts +52 -0
- package/dist/http/index.js +27 -0
- package/dist/http/media.d.ts +146 -0
- package/dist/http/media.js +346 -0
- package/dist/http/memory.d.ts +265 -0
- package/dist/http/memory.js +328 -0
- package/dist/http/model-proxy.d.ts +49 -0
- package/dist/http/model-proxy.js +642 -0
- package/dist/http/remote-run-events.d.ts +70 -0
- package/dist/http/remote-run-events.js +224 -0
- package/dist/http/research.d.ts +48 -0
- package/dist/http/research.js +315 -0
- package/dist/http/route-manifest.d.ts +35 -0
- package/dist/http/route-manifest.js +103 -0
- package/dist/http/routines.d.ts +120 -0
- package/dist/http/routines.js +298 -0
- package/dist/http/run-credentials.d.ts +73 -0
- package/dist/http/run-credentials.js +71 -0
- package/dist/http/runs.d.ts +91 -0
- package/dist/http/runs.js +221 -0
- package/dist/http/terminals.d.ts +138 -0
- package/dist/http/terminals.js +325 -0
- package/dist/http/tool-catalog.d.ts +78 -0
- package/dist/http/tool-catalog.js +66 -0
- package/dist/http/workspace-root.d.ts +77 -0
- package/dist/http/workspace-root.js +63 -0
- package/dist/http/xai.d.ts +90 -0
- package/dist/http/xai.js +655 -0
- package/dist/image-prompt-delivery.d.ts +13 -3
- package/dist/image-prompt-delivery.js +5 -5
- package/dist/index.d.ts +556 -4
- package/dist/index.js +556 -4
- package/dist/legacy-data-migration.d.ts +59 -8
- package/dist/legacy-data-migration.js +38 -33
- package/dist/read-only-tools.d.ts +130 -0
- package/dist/read-only-tools.js +115 -0
- package/dist/remote-tool-bridge.d.ts +11 -5
- package/dist/remote-tool-bridge.js +15 -15
- package/dist/routines/index.d.ts +1 -1
- package/dist/routines/index.js +1 -1
- package/dist/routines/routine-store.d.ts +34 -12
- package/dist/routines/routine-store.js +21 -20
- package/dist/routines/schedule.d.ts +48 -10
- package/dist/routines/schedule.js +21 -21
- package/dist/routines/scheduler.d.ts +32 -11
- package/dist/routines/scheduler.js +61 -57
- package/dist/routines/types.d.ts +10 -3
- package/dist/run/core/result.d.ts +6 -2
- package/dist/run/core/result.js +3 -3
- package/dist/run/core/retry.d.ts +17 -4
- package/dist/run/core/retry.js +7 -11
- package/dist/run/diagnostics/diagnostics.d.ts +14 -4
- package/dist/run/diagnostics/diagnostics.js +7 -7
- package/dist/run/index.d.ts +1 -1
- package/dist/run/index.js +1 -1
- package/dist/run-credentials.d.ts +138 -0
- package/dist/run-credentials.js +152 -0
- package/dist/run-lifecycle.d.ts +92 -33
- package/dist/run-lifecycle.js +86 -73
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.js +2 -0
- package/dist/session-coordination.d.ts +115 -0
- package/dist/session-coordination.js +111 -0
- package/dist/store/agent-sessions/errors.d.ts +8 -0
- package/dist/store/agent-sessions/errors.js +10 -0
- package/dist/store/agent-sessions/index.d.ts +12 -0
- package/dist/store/agent-sessions/index.js +19 -0
- package/dist/store/agent-sessions/pglite.d.ts +12 -0
- package/dist/store/agent-sessions/pglite.js +7 -0
- package/dist/store/agent-sessions/ports.d.ts +24 -0
- package/dist/store/agent-sessions/ports.js +2 -0
- package/dist/store/agent-sessions/postgres.d.ts +12 -0
- package/dist/store/agent-sessions/postgres.js +7 -0
- package/dist/store/agent-sessions/schema.d.ts +3 -0
- package/dist/store/agent-sessions/schema.js +23 -0
- package/dist/store/agent-sessions/sql.d.ts +35 -0
- package/dist/store/agent-sessions/sql.js +59 -0
- package/dist/store/agent-sessions/sqlite.d.ts +117 -0
- package/dist/store/agent-sessions/sqlite.js +128 -0
- package/dist/store/event-log/sqlite.d.ts +75 -0
- package/dist/store/event-log/sqlite.js +230 -0
- package/dist/surface-exchanges.d.ts +97 -0
- package/dist/surface-exchanges.js +153 -0
- package/dist/terminal-session.d.ts +43 -11
- package/dist/terminal-session.js +22 -20
- package/dist/tokens.js +4 -4
- package/dist/tool-audit.d.ts +114 -0
- package/dist/tool-audit.js +95 -0
- package/dist/tool-executor.d.ts +22 -7
- package/dist/tool-executor.js +16 -16
- package/dist/tool-result-media.d.ts +3 -1
- package/dist/tool-result-media.js +1 -1
- package/dist/tool-result-surfaces.d.ts +3 -1
- package/dist/tool-result-surfaces.js +1 -1
- package/package.json +127 -9
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RouteRegistration } from '@jini-ai/http-kit';
|
|
2
|
+
/**
|
|
3
|
+
* Route families described by this manifest, keyed by the same feature id `@jini-ai/server`'s built-in
|
|
4
|
+
* feature catalog uses — so a host that enables features by name can look routes up by that same name.
|
|
5
|
+
*/
|
|
6
|
+
export declare const JINI_ROUTE_MANIFEST: Readonly<Record<string, readonly RouteRegistration[]>>;
|
|
7
|
+
/**
|
|
8
|
+
* The routes one family mounts.
|
|
9
|
+
*
|
|
10
|
+
* @param family - A feature id, e.g. `'runs'`.
|
|
11
|
+
* @returns That family's registrations, or `undefined` when the family is not described by this
|
|
12
|
+
* manifest. The distinction matters: a caller building a proxy allow-list must treat `undefined` as
|
|
13
|
+
* "look it up yourself", never as "this family has no routes" — see this module's scope note.
|
|
14
|
+
* @complexity O(1).
|
|
15
|
+
* @overallScore 100/100
|
|
16
|
+
*/
|
|
17
|
+
export declare function routeFamilyManifest({ family }: {
|
|
18
|
+
readonly family: string;
|
|
19
|
+
}, _optional?: Record<string, never>): readonly RouteRegistration[] | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Every route across every family this manifest describes, de-duplicated.
|
|
22
|
+
*
|
|
23
|
+
* De-duplication is real rather than defensive: `health` intentionally mounts both `/health` and
|
|
24
|
+
* `/api/health` as separate routes, and a future family could legitimately share a path with another.
|
|
25
|
+
*
|
|
26
|
+
* @param families - Family ids to include. Unknown ids contribute nothing (rather than throwing), so a
|
|
27
|
+
* caller can pass its enabled-feature list verbatim without pre-filtering it.
|
|
28
|
+
* @returns One entry per distinct `METHOD PATH`, in first-seen order.
|
|
29
|
+
* @complexity O(n) in the total routes across the named families.
|
|
30
|
+
* @overallScore 100/100
|
|
31
|
+
*/
|
|
32
|
+
export declare function manifestRoutesForFamilies({ families }: {
|
|
33
|
+
readonly families: readonly string[];
|
|
34
|
+
}, _optional?: Record<string, never>): readonly RouteRegistration[];
|
|
35
|
+
//# sourceMappingURL=route-manifest.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module route-manifest
|
|
3
|
+
*
|
|
4
|
+
* Which `{method, path}` pairs each route family mounts, as inert data a caller can read *without*
|
|
5
|
+
* mounting anything.
|
|
6
|
+
*
|
|
7
|
+
* **The problem this solves.** A host that puts a reverse proxy in front of a Jini daemon in another
|
|
8
|
+
* process has to know that daemon's route list to forward anything. With no published inventory, the
|
|
9
|
+
* only way to build one is to copy path strings by hand — and a hand-copied list silently falls behind
|
|
10
|
+
* the moment a family gains a route. That has already happened at least once in a real consumer: a
|
|
11
|
+
* proxy shipped without `GET /api/runs` and the daemon's list endpoint was simply unreachable, 404-ing
|
|
12
|
+
* at the host's own router with nothing to indicate the host was the cause.
|
|
13
|
+
*
|
|
14
|
+
* **Why this file cannot itself drift.** It declares no method or path literals. Every entry is derived
|
|
15
|
+
* from the very `JsonRouteSpec` constant the family's `register*Routes` function mounts, so a spec's
|
|
16
|
+
* path change moves the manifest with it, automatically. The single exception is the SSE run-event
|
|
17
|
+
* route, which is registered with a bare `app.get` rather than a spec — and it contributes the
|
|
18
|
+
* already-exported `RUN_EVENTS_ROUTE_PATH` constant rather than a copy of it.
|
|
19
|
+
*
|
|
20
|
+
* That leaves exactly one failure mode: a family gains a route and nobody adds it *here*. The paired
|
|
21
|
+
* test (`__tests__/route-manifest.test.ts`) closes that by mounting each declared family's real
|
|
22
|
+
* registrar onto a recording app and asserting the manifest matches what actually registered — so a
|
|
23
|
+
* missing entry fails a test rather than reaching a consumer.
|
|
24
|
+
*
|
|
25
|
+
* **Scope, stated honestly: this covers the families a sidecar consumer proxies**, not all 19 the
|
|
26
|
+
* package can mount. {@link routeFamilyManifest} returns `undefined` for anything undeclared rather
|
|
27
|
+
* than an empty list, so "not described here" is distinguishable from "has no routes" — a proxy must
|
|
28
|
+
* never conclude a family is empty when it is merely undocumented. Adding a family is a matter of
|
|
29
|
+
* listing its exported specs below; the test then holds it correct.
|
|
30
|
+
*/
|
|
31
|
+
import { agentListRoute, agentRescanRoute } from './agents.js';
|
|
32
|
+
import { delegatedToolExecuteRoute } from './delegated-tools.js';
|
|
33
|
+
import { apiHealthRoute, apiReadyRoute, apiVersionInfoRoute, healthRoute, readyRoute, versionInfoRoute, } from './health.js';
|
|
34
|
+
import { RUN_EVENTS_ROUTE_PATH, runCancelRoute, runListRoute, runStartRoute, runStatusRoute, } from './runs.js';
|
|
35
|
+
import { toolCatalogDescribeRoute, toolCatalogSearchRoute } from './tool-catalog.js';
|
|
36
|
+
/**
|
|
37
|
+
* Projects route specs onto `RouteRegistration`s, upper-casing the method so entries compare directly
|
|
38
|
+
* against {@link getRouteRegistrationInventory}'s output.
|
|
39
|
+
*
|
|
40
|
+
* @param specs - Route specs, passed by reference so no path literal is ever restated.
|
|
41
|
+
* @returns One registration per spec, in argument order.
|
|
42
|
+
* @complexity O(n) in the number of specs.
|
|
43
|
+
* @overallScore 100/100
|
|
44
|
+
*/
|
|
45
|
+
function fromSpecs(...specs) {
|
|
46
|
+
return specs.map((spec) => ({ method: spec.method.toUpperCase(), path: spec.path }));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Route families described by this manifest, keyed by the same feature id `@jini-ai/server`'s built-in
|
|
50
|
+
* feature catalog uses — so a host that enables features by name can look routes up by that same name.
|
|
51
|
+
*/
|
|
52
|
+
export const JINI_ROUTE_MANIFEST = {
|
|
53
|
+
health: fromSpecs(healthRoute, apiHealthRoute, readyRoute, apiReadyRoute, versionInfoRoute, apiVersionInfoRoute),
|
|
54
|
+
runs: [
|
|
55
|
+
...fromSpecs(runStartRoute, runListRoute, runStatusRoute, runCancelRoute),
|
|
56
|
+
// Registered with a bare `app.get` rather than a route spec (by `registerRunRoutes` itself), so
|
|
57
|
+
// it contributes its exported path constant instead of a copied literal.
|
|
58
|
+
{ method: 'GET', path: RUN_EVENTS_ROUTE_PATH },
|
|
59
|
+
],
|
|
60
|
+
agents: fromSpecs(agentListRoute, agentRescanRoute),
|
|
61
|
+
toolCatalog: fromSpecs(toolCatalogSearchRoute, toolCatalogDescribeRoute),
|
|
62
|
+
delegatedToolCalls: fromSpecs(delegatedToolExecuteRoute),
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The routes one family mounts.
|
|
66
|
+
*
|
|
67
|
+
* @param family - A feature id, e.g. `'runs'`.
|
|
68
|
+
* @returns That family's registrations, or `undefined` when the family is not described by this
|
|
69
|
+
* manifest. The distinction matters: a caller building a proxy allow-list must treat `undefined` as
|
|
70
|
+
* "look it up yourself", never as "this family has no routes" — see this module's scope note.
|
|
71
|
+
* @complexity O(1).
|
|
72
|
+
* @overallScore 100/100
|
|
73
|
+
*/
|
|
74
|
+
export function routeFamilyManifest({ family }, _optional = {}) {
|
|
75
|
+
return JINI_ROUTE_MANIFEST[family];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Every route across every family this manifest describes, de-duplicated.
|
|
79
|
+
*
|
|
80
|
+
* De-duplication is real rather than defensive: `health` intentionally mounts both `/health` and
|
|
81
|
+
* `/api/health` as separate routes, and a future family could legitimately share a path with another.
|
|
82
|
+
*
|
|
83
|
+
* @param families - Family ids to include. Unknown ids contribute nothing (rather than throwing), so a
|
|
84
|
+
* caller can pass its enabled-feature list verbatim without pre-filtering it.
|
|
85
|
+
* @returns One entry per distinct `METHOD PATH`, in first-seen order.
|
|
86
|
+
* @complexity O(n) in the total routes across the named families.
|
|
87
|
+
* @overallScore 100/100
|
|
88
|
+
*/
|
|
89
|
+
export function manifestRoutesForFamilies({ families }, _optional = {}) {
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
const routes = [];
|
|
92
|
+
for (const family of families) {
|
|
93
|
+
for (const route of JINI_ROUTE_MANIFEST[family] ?? []) {
|
|
94
|
+
const key = `${route.method} ${route.path}`;
|
|
95
|
+
if (seen.has(key))
|
|
96
|
+
continue;
|
|
97
|
+
seen.add(key);
|
|
98
|
+
routes.push(route);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return routes;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=route-manifest.js.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module routines
|
|
3
|
+
*
|
|
4
|
+
* Routine CRUD + run-history HTTP routes: `GET/POST /api/routines`, `GET/PATCH/DELETE
|
|
5
|
+
* /api/routines/:id`, `POST /api/routines/:id/run`, `GET /api/routines/:id/runs`. Calls into the
|
|
6
|
+
* `RoutineStore`/`RoutineService`-shaped ports `@jini-ai/daemon` ships (see that package's
|
|
7
|
+
* `routines/` module — `routine-store.ts` for the CRUD+history port, `scheduler.ts` for the
|
|
8
|
+
* scheduling engine) the same way `runs.ts` calls into `RunLifecycle` — no business logic lives
|
|
9
|
+
* in this file itself.
|
|
10
|
+
*
|
|
11
|
+
* Ported from OD's `apps/daemon/src/routes/routine.ts` (348 lines, `refactor/web-memory-slice`
|
|
12
|
+
* branch) per the decisions in `ADS-memory/reports/proposals/
|
|
13
|
+
* PROP-http-route-packs-automation-routines-2026-07-21.md` (Finding 2). That proposal identified
|
|
14
|
+
* two separable gaps blocking this route pack: (1) no scheduler existed anywhere in this repo —
|
|
15
|
+
* closed by porting `routines.ts` into `@jini-ai/daemon/src/routines/scheduler.ts` this same pass;
|
|
16
|
+
* (2) no `RoutineStore` persistence port existed for the CRUD side — closed by
|
|
17
|
+
* `@jini-ai/daemon/src/routines/routine-store.ts`, designed the same way `EventLog` is designed.
|
|
18
|
+
* This file is the third piece: the actual HTTP routes, now that both ports exist.
|
|
19
|
+
*
|
|
20
|
+
* **Confirmed bug fixed while porting** (per the proposal's own finding, not carried forward):
|
|
21
|
+
* OD's `GET /api/routines/:id`, `DELETE /api/routines/:id`, and `GET /api/routines/:id/runs` (OD
|
|
22
|
+
* source lines 236/268/292) had no try/catch, unlike every sibling handler in the same file. This
|
|
23
|
+
* port does not reproduce that gap — but not via a per-route try/catch either: every route here
|
|
24
|
+
* is mounted through `adapter.ts`'s `mountJsonRoute`, whose own top-level try/catch already wraps
|
|
25
|
+
* every route's `parse`/`handle` invocation (the same mechanism `runs.ts`/`memory.ts` rely on),
|
|
26
|
+
* so the bug is structurally impossible to reintroduce rather than merely patched at three call
|
|
27
|
+
* sites.
|
|
28
|
+
*
|
|
29
|
+
* **Deliberately NOT ported** (see the proposal's Finding 1 and the "built-in template scoping"
|
|
30
|
+
* decision it required a human call on):
|
|
31
|
+
* - `GET /api/automation-templates` / `GET /api/automation-templates/:id` — these depend on
|
|
32
|
+
* `automation-templates.ts`, which is not ported (see below); none of the routine CRUD/run-now/
|
|
33
|
+
* run-history routes below need it to function.
|
|
34
|
+
* - `POST /api/routines/:id/runs/:runId/crystallize` — depends on `ingestAutomationSource`
|
|
35
|
+
* (`automation-ingestions.ts`), explicitly scoped by the proposal as "a dedicated follow-up
|
|
36
|
+
* task the same size and shape as `@jini-ai/memory`'s own original port," not attempted here.
|
|
37
|
+
* - `automation-templates.ts` itself, including its CRUD-adjacent lookup machinery: the decision
|
|
38
|
+
* already made for this pass is to ship **zero built-in automation templates** (matching this
|
|
39
|
+
* repo's established precedent of keeping product-authored content host-owned — `@jini-ai/memory`'s
|
|
40
|
+
* prompt-composition, `@jini-ai/deploy`'s config-path resolution, etc.) and, since no route in this
|
|
41
|
+
* file needs any part of that module to function, none of it — content or generic shape — is
|
|
42
|
+
* ported this pass.
|
|
43
|
+
* - `automation-proposals.ts` / `automation-ingestions.ts` — untouched, per the proposal's Finding
|
|
44
|
+
* 1 explicit scoping and this task's brief.
|
|
45
|
+
*
|
|
46
|
+
* **Design note — why `target.mode === 'reuse'` project-existence checking is optional, injected
|
|
47
|
+
* DI, not a hard dependency**: OD's original unconditionally called `getProject(db, projectId)`
|
|
48
|
+
* to validate a reuse target. This module has no project concept of its own (matching
|
|
49
|
+
* `active-context.ts`'s `resolveResource` precedent for the same category of coupling), so
|
|
50
|
+
* `RoutineHttpDeps.projectExists` is an optional injected predicate: a host that has a project
|
|
51
|
+
* store supplies it and reuse targets are validated against it exactly like OD did; a host with
|
|
52
|
+
* no such concept (or that wants to defer the check) can omit it, and reuse targets are accepted
|
|
53
|
+
* without existence-checking rather than forcing every consumer to fabricate one.
|
|
54
|
+
*
|
|
55
|
+
* **Design note — why `POST /api/routines/:id/run`'s `run` field can be `null` even on success**:
|
|
56
|
+
* `RoutineStore` deliberately does not record runs (see `routine-store.ts`'s module doc: run
|
|
57
|
+
* *writing* stays the scheduler's own separately-injected `RoutinePersistence` concern, matching
|
|
58
|
+
* OD's own architectural split between the two). This route calls `store.getLatestRun(id)` after
|
|
59
|
+
* `scheduler.runNow(id)` resolves — accurate only if a host has bridged the scheduler's
|
|
60
|
+
* `RoutinePersistence.insertRun`/`updateRun` writes into the same store instance (documented,
|
|
61
|
+
* host-level integration wiring, out of this port's scope, the same way `runs.ts`'s `onStarted`
|
|
62
|
+
* driver is host-supplied). `projectId`/`conversationId`/`agentRunId` on the response always
|
|
63
|
+
* reflect the real, just-started run regardless of that wiring, since those come directly from
|
|
64
|
+
* `scheduler.runNow`'s own return value.
|
|
65
|
+
*/
|
|
66
|
+
import type { Express } from 'express';
|
|
67
|
+
import { type Routine, type RoutineCreateInput, type RoutineRun, type RoutineService, type RoutineStore, type RoutineUpdateInput } from '../index.js';
|
|
68
|
+
import { type AdapterContext } from '@jini-ai/http-kit';
|
|
69
|
+
/** The scheduler surface these routes need — a structural subset of `RoutineService`, matching OD's own `RoutineRoutesService` narrowing (`Pick<RoutineService, 'nextRunAt' | 'rescheduleOne' | 'runNow' | 'unschedule'>`). Wiring a real `RoutineService` in satisfies this with zero adapter code. */
|
|
70
|
+
export type RoutineScheduler = Pick<RoutineService, 'nextRunAt' | 'rescheduleOne' | 'runNow' | 'unschedule'>;
|
|
71
|
+
export interface RoutineHttpDeps {
|
|
72
|
+
readonly store: RoutineStore;
|
|
73
|
+
readonly scheduler: RoutineScheduler;
|
|
74
|
+
/** Validates a `target: {mode: 'reuse', projectId}`'s `projectId` before create/update accept it. Omit to skip existence-checking entirely — see this module's doc comment. */
|
|
75
|
+
readonly projectExists?: ({ projectId }: {
|
|
76
|
+
projectId: string;
|
|
77
|
+
}) => boolean | Promise<boolean>;
|
|
78
|
+
}
|
|
79
|
+
interface RoutineUpdateRequest {
|
|
80
|
+
readonly id: string;
|
|
81
|
+
readonly patch: RoutineUpdateInput;
|
|
82
|
+
}
|
|
83
|
+
interface RoutineRunsQuery {
|
|
84
|
+
readonly id: string;
|
|
85
|
+
readonly limit: number;
|
|
86
|
+
}
|
|
87
|
+
export interface RoutineListResponse {
|
|
88
|
+
readonly routines: readonly Routine[];
|
|
89
|
+
}
|
|
90
|
+
export declare const routineListRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, RoutineListResponse, RoutineHttpDeps>;
|
|
91
|
+
export interface RoutineResponse {
|
|
92
|
+
readonly routine: Routine;
|
|
93
|
+
}
|
|
94
|
+
export declare const routineCreateRoute: import("@jini-ai/http-kit").JsonRouteSpec<RoutineCreateInput, RoutineResponse, RoutineHttpDeps>;
|
|
95
|
+
export declare const routineGetRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, RoutineResponse, RoutineHttpDeps>;
|
|
96
|
+
export declare const routineUpdateRoute: import("@jini-ai/http-kit").JsonRouteSpec<RoutineUpdateRequest, RoutineResponse, RoutineHttpDeps>;
|
|
97
|
+
export interface RoutineDeleteResponse {
|
|
98
|
+
readonly ok: true;
|
|
99
|
+
}
|
|
100
|
+
export declare const routineDeleteRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, RoutineDeleteResponse, RoutineHttpDeps>;
|
|
101
|
+
export interface RoutineRunNowResponse {
|
|
102
|
+
readonly routine: Routine | null;
|
|
103
|
+
readonly run: RoutineRun | null;
|
|
104
|
+
readonly projectId: string;
|
|
105
|
+
readonly conversationId: string;
|
|
106
|
+
readonly agentRunId: string;
|
|
107
|
+
}
|
|
108
|
+
export declare const routineRunNowRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, RoutineRunNowResponse, RoutineHttpDeps>;
|
|
109
|
+
export interface RoutineRunsResponse {
|
|
110
|
+
readonly runs: readonly RoutineRun[];
|
|
111
|
+
}
|
|
112
|
+
export declare const routineRunsListRoute: import("@jini-ai/http-kit").JsonRouteSpec<RoutineRunsQuery, RoutineRunsResponse, RoutineHttpDeps>;
|
|
113
|
+
/** Mounts every routine route on `app`. A pack's `http(app, services)` calls this directly. */
|
|
114
|
+
export declare function registerRoutineRoutes({ app, deps, adapter }: {
|
|
115
|
+
readonly app: Express;
|
|
116
|
+
readonly deps: RoutineHttpDeps;
|
|
117
|
+
readonly adapter: AdapterContext;
|
|
118
|
+
}, _optional?: Record<string, never>): void;
|
|
119
|
+
export {};
|
|
120
|
+
//# sourceMappingURL=routines.d.ts.map
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { validateSchedule, validateTarget, } from '../index.js';
|
|
2
|
+
import { createApiError } from '@jini-ai/protocol';
|
|
3
|
+
import { defineJsonRoute, mountJsonRoute } from '@jini-ai/http-kit';
|
|
4
|
+
import { validationError } from '@jini-ai/http-kit';
|
|
5
|
+
import { err, ok } from '@jini-ai/http-kit';
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function errorMessage(error) {
|
|
10
|
+
return error instanceof Error ? error.message : String(error);
|
|
11
|
+
}
|
|
12
|
+
/** Overlays the scheduler's live `nextRunAt` onto a routine fetched from the store — `RoutineStore` itself has no scheduler knowledge (see `routine-store.ts`'s module doc), matching OD's own `routineFromDb` two-step (fetch, then overlay `routineService.nextRunAt`). */
|
|
13
|
+
function attachNextRunAt(routine, scheduler) {
|
|
14
|
+
return { ...routine, nextRunAt: scheduler.nextRunAt({ routineId: routine.id })?.getTime() ?? null };
|
|
15
|
+
}
|
|
16
|
+
async function checkTargetProjectExists(target, deps) {
|
|
17
|
+
if (target.mode !== 'reuse' || !deps.projectExists)
|
|
18
|
+
return null;
|
|
19
|
+
const exists = await deps.projectExists({ projectId: target.projectId });
|
|
20
|
+
return exists ? null : createApiError({ code: 'NOT_FOUND', message: `target project ${target.projectId} not found` });
|
|
21
|
+
}
|
|
22
|
+
function parseRoutineId(input) {
|
|
23
|
+
const id = input.params.id;
|
|
24
|
+
return typeof id === 'string' && id.length > 0 ? ok({ value: id }) : err({ error: validationError({ message: 'id must be a non-empty path parameter' }) });
|
|
25
|
+
}
|
|
26
|
+
function parseNonEmptyString(value, field) {
|
|
27
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
28
|
+
return err({ error: validationError({ message: `${field} is required` }, { issues: [{ path: field, message: 'required non-empty string' }] }) });
|
|
29
|
+
}
|
|
30
|
+
return ok({ value: value.trim() });
|
|
31
|
+
}
|
|
32
|
+
/** Present-but-non-string/empty is rejected; absent (`undefined`) or explicit `null` are both accepted (clearing the field on update). */
|
|
33
|
+
function parseOptionalNullableString(body, field) {
|
|
34
|
+
const value = body[field];
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return ok({ value: undefined });
|
|
37
|
+
if (value === null)
|
|
38
|
+
return ok({ value: null });
|
|
39
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
40
|
+
return err({ error: validationError({ message: `${field} must be a non-empty string or null when provided` }) });
|
|
41
|
+
}
|
|
42
|
+
return ok({ value });
|
|
43
|
+
}
|
|
44
|
+
function parseSchedule(value) {
|
|
45
|
+
try {
|
|
46
|
+
validateSchedule({ schedule: value });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return err({ error: validationError({ message: errorMessage(error) }) });
|
|
50
|
+
}
|
|
51
|
+
return ok({ value: value });
|
|
52
|
+
}
|
|
53
|
+
function parseTarget(value) {
|
|
54
|
+
try {
|
|
55
|
+
validateTarget({ target: value });
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return err({ error: validationError({ message: errorMessage(error) }) });
|
|
59
|
+
}
|
|
60
|
+
return ok({ value: value });
|
|
61
|
+
}
|
|
62
|
+
/** Validates and de-duplicates one `context.*` array field, trimming entries and dropping blanks — mirrors OD's `cleanStringList`. */
|
|
63
|
+
function cleanStringList(value, field) {
|
|
64
|
+
if (!Array.isArray(value))
|
|
65
|
+
return err({ error: validationError({ message: `${field} must be an array` }) });
|
|
66
|
+
const out = [];
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
for (const item of value) {
|
|
69
|
+
if (typeof item !== 'string')
|
|
70
|
+
return err({ error: validationError({ message: `${field} must contain strings` }) });
|
|
71
|
+
const trimmed = item.trim();
|
|
72
|
+
if (!trimmed || seen.has(trimmed))
|
|
73
|
+
continue;
|
|
74
|
+
seen.add(trimmed);
|
|
75
|
+
out.push(trimmed);
|
|
76
|
+
}
|
|
77
|
+
return ok({ value: out });
|
|
78
|
+
}
|
|
79
|
+
const CONTEXT_FIELDS = ['skillIds', 'pluginIds', 'mcpServerIds', 'connectorIds'];
|
|
80
|
+
/** Mirrors OD's `normalizeRoutineContext`: an explicitly-empty array for a field is dropped entirely (not kept as `[]`) so a routine's stored context only ever carries fields that actually scope something. */
|
|
81
|
+
function parseContextSelection(value) {
|
|
82
|
+
if (value === undefined || value === null)
|
|
83
|
+
return ok({ value: {} });
|
|
84
|
+
if (!isRecord(value))
|
|
85
|
+
return err({ error: validationError({ message: 'context must be an object' }) });
|
|
86
|
+
const context = {};
|
|
87
|
+
for (const field of CONTEXT_FIELDS) {
|
|
88
|
+
const raw = value[field];
|
|
89
|
+
if (raw === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
const cleaned = cleanStringList(raw, `context.${field}`);
|
|
92
|
+
if (!cleaned.ok)
|
|
93
|
+
return cleaned;
|
|
94
|
+
if (cleaned.value.length > 0)
|
|
95
|
+
context[field] = cleaned.value;
|
|
96
|
+
}
|
|
97
|
+
return ok({ value: context });
|
|
98
|
+
}
|
|
99
|
+
function parseRoutineCreate(input) {
|
|
100
|
+
if (!isRecord(input.body))
|
|
101
|
+
return err({ error: validationError({ message: 'body must be a JSON object' }) });
|
|
102
|
+
const name = parseNonEmptyString(input.body.name, 'name');
|
|
103
|
+
if (!name.ok)
|
|
104
|
+
return name;
|
|
105
|
+
const prompt = parseNonEmptyString(input.body.prompt, 'prompt');
|
|
106
|
+
if (!prompt.ok)
|
|
107
|
+
return prompt;
|
|
108
|
+
const schedule = parseSchedule(input.body.schedule);
|
|
109
|
+
if (!schedule.ok)
|
|
110
|
+
return schedule;
|
|
111
|
+
const target = parseTarget(input.body.target);
|
|
112
|
+
if (!target.ok)
|
|
113
|
+
return target;
|
|
114
|
+
const skillId = parseOptionalNullableString(input.body, 'skillId');
|
|
115
|
+
if (!skillId.ok)
|
|
116
|
+
return skillId;
|
|
117
|
+
const agentId = parseOptionalNullableString(input.body, 'agentId');
|
|
118
|
+
if (!agentId.ok)
|
|
119
|
+
return agentId;
|
|
120
|
+
const context = parseContextSelection(input.body.context);
|
|
121
|
+
if (!context.ok)
|
|
122
|
+
return context;
|
|
123
|
+
const enabled = input.body.enabled;
|
|
124
|
+
if (enabled !== undefined && typeof enabled !== 'boolean') {
|
|
125
|
+
return err({ error: validationError({ message: 'enabled must be a boolean when provided' }) });
|
|
126
|
+
}
|
|
127
|
+
return ok({
|
|
128
|
+
value: {
|
|
129
|
+
name: name.value,
|
|
130
|
+
prompt: prompt.value,
|
|
131
|
+
schedule: schedule.value,
|
|
132
|
+
target: target.value,
|
|
133
|
+
...(skillId.value !== undefined ? { skillId: skillId.value } : {}),
|
|
134
|
+
...(agentId.value !== undefined ? { agentId: agentId.value } : {}),
|
|
135
|
+
context: context.value,
|
|
136
|
+
...(enabled !== undefined ? { enabled } : {}),
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function parseRoutineUpdate(input) {
|
|
141
|
+
const idResult = parseRoutineId(input);
|
|
142
|
+
if (!idResult.ok)
|
|
143
|
+
return idResult;
|
|
144
|
+
if (!isRecord(input.body))
|
|
145
|
+
return err({ error: validationError({ message: 'body must be a JSON object' }) });
|
|
146
|
+
const body = input.body;
|
|
147
|
+
// `RoutineUpdateInput`'s fields are `readonly` (an immutable port-input contract) — built here
|
|
148
|
+
// as a mutable local shape field-by-field, then handed back as that readonly type, which is a
|
|
149
|
+
// safe widening (mutable -> readonly), not the reverse.
|
|
150
|
+
const patch = {};
|
|
151
|
+
if (body.name !== undefined) {
|
|
152
|
+
const name = parseNonEmptyString(body.name, 'name');
|
|
153
|
+
if (!name.ok)
|
|
154
|
+
return name;
|
|
155
|
+
patch.name = name.value;
|
|
156
|
+
}
|
|
157
|
+
if (body.prompt !== undefined) {
|
|
158
|
+
const prompt = parseNonEmptyString(body.prompt, 'prompt');
|
|
159
|
+
if (!prompt.ok)
|
|
160
|
+
return prompt;
|
|
161
|
+
patch.prompt = prompt.value;
|
|
162
|
+
}
|
|
163
|
+
if (body.schedule !== undefined) {
|
|
164
|
+
const schedule = parseSchedule(body.schedule);
|
|
165
|
+
if (!schedule.ok)
|
|
166
|
+
return schedule;
|
|
167
|
+
patch.schedule = schedule.value;
|
|
168
|
+
}
|
|
169
|
+
if (body.target !== undefined) {
|
|
170
|
+
const target = parseTarget(body.target);
|
|
171
|
+
if (!target.ok)
|
|
172
|
+
return target;
|
|
173
|
+
patch.target = target.value;
|
|
174
|
+
}
|
|
175
|
+
if (body.skillId !== undefined) {
|
|
176
|
+
const skillId = parseOptionalNullableString(body, 'skillId');
|
|
177
|
+
if (!skillId.ok)
|
|
178
|
+
return skillId;
|
|
179
|
+
patch.skillId = skillId.value ?? null;
|
|
180
|
+
}
|
|
181
|
+
if (body.agentId !== undefined) {
|
|
182
|
+
const agentId = parseOptionalNullableString(body, 'agentId');
|
|
183
|
+
if (!agentId.ok)
|
|
184
|
+
return agentId;
|
|
185
|
+
patch.agentId = agentId.value ?? null;
|
|
186
|
+
}
|
|
187
|
+
if (body.context !== undefined) {
|
|
188
|
+
const context = parseContextSelection(body.context);
|
|
189
|
+
if (!context.ok)
|
|
190
|
+
return context;
|
|
191
|
+
patch.context = context.value;
|
|
192
|
+
}
|
|
193
|
+
if (body.enabled !== undefined) {
|
|
194
|
+
if (typeof body.enabled !== 'boolean')
|
|
195
|
+
return err({ error: validationError({ message: 'enabled must be a boolean when provided' }) });
|
|
196
|
+
patch.enabled = body.enabled;
|
|
197
|
+
}
|
|
198
|
+
return ok({ value: { id: idResult.value, patch } });
|
|
199
|
+
}
|
|
200
|
+
function parseRoutineRunsQuery(input) {
|
|
201
|
+
const idResult = parseRoutineId(input);
|
|
202
|
+
if (!idResult.ok)
|
|
203
|
+
return idResult;
|
|
204
|
+
const raw = input.query.limit;
|
|
205
|
+
const rawValue = Array.isArray(raw) ? raw[0] : raw;
|
|
206
|
+
// Retains OD's ceiling/floor and fallback for NaN/0/absent. Unlike its `|| 20` expression,
|
|
207
|
+
// explicitly fall back for negatives too: a truthy negative must not defeat the documented default.
|
|
208
|
+
const parsed = Number(rawValue);
|
|
209
|
+
const numeric = parsed > 0 ? parsed : 20;
|
|
210
|
+
const limit = Math.min(100, Math.max(1, numeric));
|
|
211
|
+
return ok({ value: { id: idResult.value, limit } });
|
|
212
|
+
}
|
|
213
|
+
export const routineListRoute = defineJsonRoute({
|
|
214
|
+
method: 'get', path: '/api/routines', parse: () => ok({ value: undefined }), handle: async ({ input: _input, deps }) => {
|
|
215
|
+
const routines = await deps.store.list();
|
|
216
|
+
return ok({ value: { routines: routines.map((routine) => attachNextRunAt(routine, deps.scheduler)) } });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
export const routineCreateRoute = defineJsonRoute({
|
|
220
|
+
method: 'post', path: '/api/routines', parse: parseRoutineCreate, handle: async ({ input, deps }) => {
|
|
221
|
+
const targetError = await checkTargetProjectExists(input.target, deps);
|
|
222
|
+
if (targetError)
|
|
223
|
+
return err({ error: targetError });
|
|
224
|
+
const routine = await deps.store.create(input);
|
|
225
|
+
deps.scheduler.rescheduleOne({ routineId: routine.id });
|
|
226
|
+
return ok({ value: { routine: attachNextRunAt(routine, deps.scheduler) } });
|
|
227
|
+
}
|
|
228
|
+
}, { requireSameOrigin: true, successStatus: 201 });
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// GET/PATCH/DELETE /api/routines/:id
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
export const routineGetRoute = defineJsonRoute({
|
|
233
|
+
method: 'get', path: '/api/routines/:id', parse: parseRoutineId, handle: async ({ input: id, deps }) => {
|
|
234
|
+
const routine = await deps.store.get({ id });
|
|
235
|
+
return routine === null
|
|
236
|
+
? err({ error: createApiError({ code: 'NOT_FOUND', message: 'routine not found' }) })
|
|
237
|
+
: ok({ value: { routine: attachNextRunAt(routine, deps.scheduler) } });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
export const routineUpdateRoute = defineJsonRoute({
|
|
241
|
+
method: 'patch', path: '/api/routines/:id', parse: parseRoutineUpdate, handle: async ({ input: { id, patch }, deps }) => {
|
|
242
|
+
if (patch.target !== undefined) {
|
|
243
|
+
const targetError = await checkTargetProjectExists(patch.target, deps);
|
|
244
|
+
if (targetError)
|
|
245
|
+
return err({ error: targetError });
|
|
246
|
+
}
|
|
247
|
+
const routine = await deps.store.update({ id, patch });
|
|
248
|
+
if (routine === null)
|
|
249
|
+
return err({ error: createApiError({ code: 'NOT_FOUND', message: 'routine not found' }) });
|
|
250
|
+
deps.scheduler.rescheduleOne({ routineId: id });
|
|
251
|
+
return ok({ value: { routine: attachNextRunAt(routine, deps.scheduler) } });
|
|
252
|
+
}
|
|
253
|
+
}, { requireSameOrigin: true });
|
|
254
|
+
export const routineDeleteRoute = defineJsonRoute({
|
|
255
|
+
method: 'delete', path: '/api/routines/:id', parse: parseRoutineId, handle: async ({ input: id, deps }) => {
|
|
256
|
+
deps.scheduler.unschedule({ routineId: id });
|
|
257
|
+
const removed = await deps.store.delete({ id });
|
|
258
|
+
return removed ? ok({ value: { ok: true } }) : err({ error: createApiError({ code: 'NOT_FOUND', message: 'routine not found' }) });
|
|
259
|
+
}
|
|
260
|
+
}, { requireSameOrigin: true });
|
|
261
|
+
export const routineRunNowRoute = defineJsonRoute({
|
|
262
|
+
method: 'post', path: '/api/routines/:id/run', parse: parseRoutineId, handle: async ({ input: id, deps }) => {
|
|
263
|
+
const existing = await deps.store.get({ id });
|
|
264
|
+
if (existing === null)
|
|
265
|
+
return err({ error: createApiError({ code: 'NOT_FOUND', message: 'routine not found' }) });
|
|
266
|
+
const started = await deps.scheduler.runNow({ routineId: id });
|
|
267
|
+
const [routine, run] = await Promise.all([deps.store.get({ id }), deps.store.getLatestRun({ routineId: id })]);
|
|
268
|
+
return ok({
|
|
269
|
+
value: {
|
|
270
|
+
routine: routine ? attachNextRunAt(routine, deps.scheduler) : null,
|
|
271
|
+
run,
|
|
272
|
+
projectId: started.projectId,
|
|
273
|
+
conversationId: started.conversationId,
|
|
274
|
+
agentRunId: started.agentRunId,
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}, { requireSameOrigin: true, successStatus: 202 });
|
|
279
|
+
export const routineRunsListRoute = defineJsonRoute({
|
|
280
|
+
method: 'get', path: '/api/routines/:id/runs', parse: parseRoutineRunsQuery, handle: async ({ input: { id, limit }, deps }) => {
|
|
281
|
+
const existing = await deps.store.get({ id });
|
|
282
|
+
if (existing === null)
|
|
283
|
+
return err({ error: createApiError({ code: 'NOT_FOUND', message: 'routine not found' }) });
|
|
284
|
+
const runs = await deps.store.listRuns({ routineId: id, limit });
|
|
285
|
+
return ok({ value: { runs } });
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
/** Mounts every routine route on `app`. A pack's `http(app, services)` calls this directly. */
|
|
289
|
+
export function registerRoutineRoutes({ app, deps, adapter }, _optional = {}) {
|
|
290
|
+
mountJsonRoute({ app, spec: routineListRoute, deps, adapter });
|
|
291
|
+
mountJsonRoute({ app, spec: routineCreateRoute, deps, adapter });
|
|
292
|
+
mountJsonRoute({ app, spec: routineGetRoute, deps, adapter });
|
|
293
|
+
mountJsonRoute({ app, spec: routineUpdateRoute, deps, adapter });
|
|
294
|
+
mountJsonRoute({ app, spec: routineDeleteRoute, deps, adapter });
|
|
295
|
+
mountJsonRoute({ app, spec: routineRunNowRoute, deps, adapter });
|
|
296
|
+
mountJsonRoute({ app, spec: routineRunsListRoute, deps, adapter });
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=routines.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
/** Structural result of the daemon's transport-independent credential/ownership policies. */
|
|
3
|
+
export type RunAccessDecision = {
|
|
4
|
+
readonly allowed: true;
|
|
5
|
+
readonly principalHeader?: {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly value: string;
|
|
8
|
+
};
|
|
9
|
+
} | {
|
|
10
|
+
readonly allowed: false;
|
|
11
|
+
readonly status: 400 | 401 | 403 | 404 | 503;
|
|
12
|
+
readonly body: unknown;
|
|
13
|
+
};
|
|
14
|
+
export interface DaemonAuthRequired {
|
|
15
|
+
readonly authorizationHeaderName: string;
|
|
16
|
+
/** Bind the daemon policy's environment, crypto, route and credential ports at composition. */
|
|
17
|
+
readonly authorize: (required: {
|
|
18
|
+
request: {
|
|
19
|
+
method: string;
|
|
20
|
+
path: string;
|
|
21
|
+
headers: Readonly<Record<string, string | undefined>>;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
};
|
|
24
|
+
}, optional: {
|
|
25
|
+
validateDelegatedRunId: boolean;
|
|
26
|
+
}) => RunAccessDecision;
|
|
27
|
+
}
|
|
28
|
+
/** Mount before JSON parsing, then again on delegated calls after parsing with body validation.
|
|
29
|
+
* Only the allowed policy result may replace the principal header; trusted proxy headers remain intact.
|
|
30
|
+
* Policy exceptions go to Express's error boundary. No request body is accessed in the first gate.
|
|
31
|
+
* @complexity O(1) adapter work, excluding the injected authorization policy.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createDaemonAuthMiddleware({ authorize, authorizationHeaderName }: DaemonAuthRequired, { validateDelegatedRunId }?: {
|
|
34
|
+
validateDelegatedRunId?: boolean;
|
|
35
|
+
}): RequestHandler;
|
|
36
|
+
export interface RunOwnershipRequired {
|
|
37
|
+
readonly principalHeaderName: string;
|
|
38
|
+
readonly isEventStream: (required: {
|
|
39
|
+
path: string;
|
|
40
|
+
}) => boolean;
|
|
41
|
+
/** Bind authorizeRunOwnership's registry, run lookup and header policy at composition. */
|
|
42
|
+
readonly authorize: (required: {
|
|
43
|
+
runId: string | undefined;
|
|
44
|
+
principalId: string | undefined;
|
|
45
|
+
eventStream: boolean;
|
|
46
|
+
}) => Promise<RunAccessDecision>;
|
|
47
|
+
}
|
|
48
|
+
/** Mount after authentication on routes with a runId parameter. Denial envelopes pass through intact.
|
|
49
|
+
* @complexity O(1) adapter work plus the injected ownership lookup; no storage or identity defaults.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createRunOwnershipMiddleware({ authorize, principalHeaderName, isEventStream }: RunOwnershipRequired): RequestHandler;
|
|
52
|
+
export interface OwnedRunListRequired<Run> {
|
|
53
|
+
readonly principalHeaderName: string;
|
|
54
|
+
readonly listRuns: (required: Record<string, never>, optional: {
|
|
55
|
+
contextRef?: string;
|
|
56
|
+
}) => Promise<readonly Run[]>;
|
|
57
|
+
/** Bind listOwnedRuns's registry and principal-header policy at composition. */
|
|
58
|
+
readonly filterOwnedRuns: (required: {
|
|
59
|
+
runs: readonly Run[];
|
|
60
|
+
principalId: string | undefined;
|
|
61
|
+
}) => {
|
|
62
|
+
allowed: true;
|
|
63
|
+
runs: Run[];
|
|
64
|
+
} | Extract<RunAccessDecision, {
|
|
65
|
+
allowed: false;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
/** List only the authenticated principal's runs, after the host's context filter.
|
|
69
|
+
* Missing principals receive the existing 401 envelope before the list port is called.
|
|
70
|
+
* @complexity O(n) policy filtering for n listed runs; O(1) adapter space excluding results.
|
|
71
|
+
*/
|
|
72
|
+
export declare function createOwnedRunListHandler<Run>({ listRuns, filterOwnedRuns, principalHeaderName }: OwnedRunListRequired<Run>): RequestHandler;
|
|
73
|
+
//# sourceMappingURL=run-credentials.d.ts.map
|