@junejs/server 0.1.2-dev.7 → 1.0.0-dev.10
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/dist/agent-durable.d.ts +4 -4
- package/dist/agent-durable.js +7 -3
- package/dist/agent-native.js +3 -0
- package/dist/core-version.d.ts +5 -0
- package/dist/core-version.js +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/{pipeline-CCp9idQ4.d.ts → pipeline-cCR6MGNK.d.ts} +8 -0
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -1
- package/dist/worker.d.ts +1 -1
- package/package.json +5 -4
- package/src/agent-durable.ts +18 -8
- package/src/agent-native.ts +3 -0
- package/src/core-version.ts +34 -0
- package/src/pipeline.ts +14 -3
package/dist/agent-durable.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Channel, ChannelFactory } from "@junejs/core/agent-config";
|
|
2
|
-
import { InboundEvent, Model, Msg, ProactiveTrigger, SessionStore, Tool, TurnEvent } from "@junejs/core/agent-runtime";
|
|
2
|
+
import { InboundEvent, Model, Msg, ProactiveTrigger, SessionStore, Tool, TurnError, TurnEvent, TurnFailurePhase } from "@junejs/core/agent-runtime";
|
|
3
3
|
import { Resources } from "@junejs/core/resources";
|
|
4
4
|
//#region src/agent-durable.d.ts
|
|
5
5
|
type SqlStorageValue = ArrayBuffer | string | number | null;
|
|
@@ -53,9 +53,9 @@ type DoAgentDef = {
|
|
|
53
53
|
services?: unknown;
|
|
54
54
|
onTurnError?: (failure: {
|
|
55
55
|
turnId: string;
|
|
56
|
-
error:
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
error: TurnError;
|
|
57
|
+
phase?: TurnFailurePhase;
|
|
58
|
+
step?: string;
|
|
59
59
|
}) => void | Promise<void>;
|
|
60
60
|
};
|
|
61
61
|
declare class AgentDurableObject {
|
package/dist/agent-durable.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertCoreRuntimeVersion } from "./core-version.js";
|
|
1
2
|
import { channelDispatch, resolveChannel } from "@junejs/core/agent-config";
|
|
2
3
|
import { AgentSession, ResumeAuthorizationError, withSystem } from "@junejs/core/agent-runtime";
|
|
3
4
|
import { ensureScope, runInScope } from "@junejs/db";
|
|
@@ -83,6 +84,7 @@ var AgentDurableObject = class {
|
|
|
83
84
|
resources;
|
|
84
85
|
services;
|
|
85
86
|
constructor(state, def) {
|
|
87
|
+
assertCoreRuntimeVersion(`AgentDurableObject(${def.name ?? "agent"})`);
|
|
86
88
|
const store = new DoSessionStore(state.storage);
|
|
87
89
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
88
90
|
this.resources = def.resources ?? {};
|
|
@@ -91,11 +93,13 @@ var AgentDurableObject = class {
|
|
|
91
93
|
const sink = new InProcEventSink();
|
|
92
94
|
sink.subscribe((e) => {
|
|
93
95
|
if (e.type !== "turn.failed") return;
|
|
94
|
-
const defaultLog = () => console.error(`[june] agent "${name}" turn ${e.turnId} failed: ${e.error.message}`);
|
|
96
|
+
const defaultLog = () => console.error(`[june] agent "${name}" turn ${e.turnId} failed${e.step ? ` at ${e.step}` : ""}: ${e.error.stack ?? e.error.message}${e.error.causeChain?.length ? `\ncaused by: ${e.error.causeChain.join(" ← ")}` : ""}`);
|
|
95
97
|
if (def.onTurnError) try {
|
|
96
98
|
const out = def.onTurnError({
|
|
97
99
|
turnId: e.turnId,
|
|
98
|
-
error: e.error
|
|
100
|
+
error: e.error,
|
|
101
|
+
phase: e.phase,
|
|
102
|
+
step: e.step
|
|
99
103
|
});
|
|
100
104
|
if (out && typeof out.then === "function") out.then(void 0, (hookErr) => {
|
|
101
105
|
console.error(`[june] agent "${name}": onTurnError hook rejected:`, hookErr);
|
|
@@ -262,7 +266,7 @@ async function sseTurnFinalText(res) {
|
|
|
262
266
|
if (!line) continue;
|
|
263
267
|
const e = JSON.parse(line.slice(5).trim());
|
|
264
268
|
if (e.type === "turn.completed") return e.text;
|
|
265
|
-
if (e.type === "turn.failed") throw new Error(e.error.message);
|
|
269
|
+
if (e.type === "turn.failed") throw new Error(e.error.message, { cause: e.error });
|
|
266
270
|
}
|
|
267
271
|
if (done) break;
|
|
268
272
|
}
|
package/dist/agent-native.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertCoreRuntimeVersion } from "./core-version.js";
|
|
1
2
|
import { openLocalSqliteSync } from "./sqlite-driver.js";
|
|
2
3
|
import { channelFetch } from "@junejs/core/agent-config";
|
|
3
4
|
import { AgentSession, withSystem } from "@junejs/core/agent-runtime";
|
|
@@ -73,6 +74,7 @@ var NativeRuntime = class {
|
|
|
73
74
|
agents;
|
|
74
75
|
db;
|
|
75
76
|
constructor(agents, db) {
|
|
77
|
+
assertCoreRuntimeVersion("NativeRuntime");
|
|
76
78
|
this.agents = agents;
|
|
77
79
|
this.db = db;
|
|
78
80
|
initSchema(db);
|
|
@@ -131,6 +133,7 @@ var MemoryRuntime = class {
|
|
|
131
133
|
stores = /* @__PURE__ */ new Map();
|
|
132
134
|
agents;
|
|
133
135
|
constructor(agents) {
|
|
136
|
+
assertCoreRuntimeVersion("MemoryRuntime");
|
|
134
137
|
this.agents = agents;
|
|
135
138
|
}
|
|
136
139
|
session(agent, id) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
//#region src/core-version.d.ts
|
|
2
|
+
declare function assertCoreRuntimeVersion(surface: string): void;
|
|
3
|
+
declare function checkCoreRuntimeVersion(surface: string, actual: number | undefined, expected?: number): void;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { assertCoreRuntimeVersion, checkCoreRuntimeVersion };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as coreRuntime from "@junejs/core/agent-runtime";
|
|
2
|
+
//#region src/core-version.ts
|
|
3
|
+
const EXPECTED_CORE_RUNTIME_API = 1;
|
|
4
|
+
function assertCoreRuntimeVersion(surface) {
|
|
5
|
+
checkCoreRuntimeVersion(surface, coreRuntime.RUNTIME_API_VERSION);
|
|
6
|
+
}
|
|
7
|
+
function checkCoreRuntimeVersion(surface, actual, expected = EXPECTED_CORE_RUNTIME_API) {
|
|
8
|
+
if (actual === expected) return;
|
|
9
|
+
throw new Error(`${surface}: @junejs/server is built against @junejs/core runtime API v${expected}, but the @junejs/core it resolved provides ${actual === void 0 ? "no RUNTIME_API_VERSION (a pre-0.1.1-dev.16 core)" : `v${actual}`} — two copies of @junejs/core are likely installed (one nested under @junejs/server). Align the app's @junejs/core with @junejs/server's peer range, then reinstall/dedupe so one copy remains.`);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { assertCoreRuntimeVersion, checkCoreRuntimeVersion };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AdapterCapabilities, AdapterEmitContext, AdapterEntry, JuneAdapter, ResourcePlan, deno, staticSite, vercel, workers } from "./adapter.js";
|
|
2
2
|
import { CreateAppOptions, JuneApp, createApp } from "./app.js";
|
|
3
3
|
import { R2Bucket, localBlob, r2 } from "./blob.js";
|
|
4
|
-
import { a as Pipeline, d as createPipeline, n as LayoutComponent, o as PipelineConfig, s as Resolved, u as RouteResolver } from "./pipeline-
|
|
4
|
+
import { a as Pipeline, d as createPipeline, n as LayoutComponent, o as PipelineConfig, s as Resolved, u as RouteResolver } from "./pipeline-cCR6MGNK.js";
|
|
5
5
|
import { WorkerManifest, createWorker } from "./worker.js";
|
|
6
6
|
import { BuildResult, buildManifest, freezeConfig, generateContent, juneBuild, scanRoutes } from "./build.js";
|
|
7
7
|
import { loadJuneConfig } from "./config-loader.js";
|
|
@@ -4,6 +4,7 @@ import React from "react";
|
|
|
4
4
|
import { DocumentConfig } from "@junejs/core/document";
|
|
5
5
|
import { I18nConfig } from "@junejs/core/i18n";
|
|
6
6
|
import { Resources } from "@junejs/core/resources";
|
|
7
|
+
import { Principal, Session } from "@junejs/core/context";
|
|
7
8
|
//#region src/pipeline.d.ts
|
|
8
9
|
type LayoutComponent = React.ComponentType<{
|
|
9
10
|
children: React.ReactNode;
|
|
@@ -32,6 +33,13 @@ type PipelineConfig = {
|
|
|
32
33
|
resolve: RouteResolver;
|
|
33
34
|
resources?: () => Promise<Resources> | Resources;
|
|
34
35
|
services?: () => Promise<unknown> | unknown;
|
|
36
|
+
identity?: (request: Request) => Promise<{
|
|
37
|
+
user?: Principal;
|
|
38
|
+
session?: Session;
|
|
39
|
+
}> | {
|
|
40
|
+
user?: Principal;
|
|
41
|
+
session?: Session;
|
|
42
|
+
};
|
|
35
43
|
earlyHints?: string[];
|
|
36
44
|
htmlCacheControl?: string;
|
|
37
45
|
notFoundComponent?: React.ComponentType<{
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as Pipeline, c as ResolvedResource, d as createPipeline, i as MiddlewareHandler, l as ResourceHandler, n as LayoutComponent, o as PipelineConfig, r as LoadingComponent, s as Resolved, t as ExtraHandler, u as RouteResolver } from "./pipeline-
|
|
1
|
+
import { a as Pipeline, c as ResolvedResource, d as createPipeline, i as MiddlewareHandler, l as ResourceHandler, n as LayoutComponent, o as PipelineConfig, r as LoadingComponent, s as Resolved, t as ExtraHandler, u as RouteResolver } from "./pipeline-cCR6MGNK.js";
|
|
2
2
|
export { ExtraHandler, LayoutComponent, LoadingComponent, MiddlewareHandler, Pipeline, PipelineConfig, Resolved, ResolvedResource, ResourceHandler, RouteResolver, createPipeline };
|
package/dist/pipeline.js
CHANGED
|
@@ -171,7 +171,10 @@ function createPipeline(cfg) {
|
|
|
171
171
|
const url = new URL(request.url);
|
|
172
172
|
if (url.pathname === "/mcp") {
|
|
173
173
|
if (!agent.mcp) return notFoundResponse("view", url.pathname);
|
|
174
|
-
return mcpHandler(request, {
|
|
174
|
+
return mcpHandler(request, {
|
|
175
|
+
request,
|
|
176
|
+
...cfg.identity ? await cfg.identity(request) : void 0
|
|
177
|
+
});
|
|
175
178
|
}
|
|
176
179
|
if (request.method === "GET" && agent.discovery) {
|
|
177
180
|
const d = await discovery(url);
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as ResourceHandler, n as LayoutComponent, r as LoadingComponent, t as ExtraHandler } from "./pipeline-
|
|
1
|
+
import { l as ResourceHandler, n as LayoutComponent, r as LoadingComponent, t as ExtraHandler } from "./pipeline-cCR6MGNK.js";
|
|
2
2
|
import { BrandedRoute } from "@junejs/core/route";
|
|
3
3
|
import { AgentConfig } from "@junejs/core/config";
|
|
4
4
|
import React from "react";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-dev.10",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -125,14 +125,14 @@
|
|
|
125
125
|
"build": "tsdown"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@junejs/
|
|
129
|
-
"@junejs/db": ">=0.0.34-dev.0",
|
|
128
|
+
"@junejs/db": ">=0.0.34-dev.1",
|
|
130
129
|
"@momiji-rs/sparkdown": "^0.0.4",
|
|
131
130
|
"lightningcss": "^1.27.0",
|
|
132
131
|
"oxc-parser": "^0.137.0",
|
|
133
132
|
"rolldown": "^1.1.0"
|
|
134
133
|
},
|
|
135
134
|
"peerDependencies": {
|
|
135
|
+
"@junejs/core": ">=0.2.0-dev.26",
|
|
136
136
|
"react": "^19.2.0",
|
|
137
137
|
"react-dom": "^19.2.0",
|
|
138
138
|
"react-server-dom-webpack": "19.2.7",
|
|
@@ -156,7 +156,8 @@
|
|
|
156
156
|
},
|
|
157
157
|
"repository": {
|
|
158
158
|
"type": "git",
|
|
159
|
-
"url": "https://github.com/junebuild/june"
|
|
159
|
+
"url": "git+https://github.com/junebuild/june.git",
|
|
160
|
+
"directory": "packages/june"
|
|
160
161
|
},
|
|
161
162
|
"devDependencies": {
|
|
162
163
|
"@libsql/client": "^0.17.3",
|
package/src/agent-durable.ts
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
type SessionStore,
|
|
29
29
|
type ProactiveTrigger,
|
|
30
30
|
type Tool,
|
|
31
|
+
type TurnError,
|
|
32
|
+
type TurnFailurePhase,
|
|
31
33
|
} from "@junejs/core/agent-runtime";
|
|
32
34
|
import type { Resources } from "@junejs/core/resources";
|
|
33
35
|
import {
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
type ChannelFactory,
|
|
40
42
|
} from "@junejs/core/agent-config";
|
|
41
43
|
import { ensureScope, runInScope } from "@junejs/db";
|
|
44
|
+
import { assertCoreRuntimeVersion } from "./core-version";
|
|
42
45
|
|
|
43
46
|
// ── minimal structural Cloudflare surface (no @cloudflare/workers-types dep) ──
|
|
44
47
|
// SqlStorage row values are exactly what workerd's SQLite returns. Mirroring
|
|
@@ -185,11 +188,14 @@ export type DoAgentDef = {
|
|
|
185
188
|
// Called on every turn.failed for this session — the app's seam for routing turn
|
|
186
189
|
// failures to its own telemetry (Sentry, a ledger, …). Providing it REPLACES the
|
|
187
190
|
// default console.error; if the hook throws (or an async hook rejects), the default
|
|
188
|
-
// log fires anyway (both errors), so a failure is never silent.
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
|
|
191
|
+
// log fires anyway (both errors), so a failure is never silent. `error` carries the
|
|
192
|
+
// full serialized failure (stack, cause chain — see TurnError, #96), and phase/step
|
|
193
|
+
// name the engine step that was in flight — for a DETACHED turn this hook is the
|
|
194
|
+
// only failure-surfacing path, so nothing may be flattened before it fires. It
|
|
195
|
+
// happens to run inside the failed turn's request scope today (turn.failed is
|
|
196
|
+
// emitted mid-turn and ALS propagates), but that is NOT contract — don't rely on
|
|
197
|
+
// ambient db/services; close over what you need.
|
|
198
|
+
onTurnError?: (failure: { turnId: string; error: TurnError; phase?: TurnFailurePhase; step?: string }) => void | Promise<void>;
|
|
193
199
|
};
|
|
194
200
|
|
|
195
201
|
// The agent runtime INSIDE a Durable Object. A plain class (constructor takes the
|
|
@@ -226,6 +232,7 @@ export class AgentDurableObject {
|
|
|
226
232
|
private readonly resources: Resources;
|
|
227
233
|
private readonly services: unknown;
|
|
228
234
|
constructor(state: DurableObjectState, def: DoAgentDef) {
|
|
235
|
+
assertCoreRuntimeVersion(`AgentDurableObject(${def.name ?? "agent"})`); // #94: fail power-on, not mid-turn
|
|
229
236
|
const store = new DoSessionStore(state.storage);
|
|
230
237
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
231
238
|
this.resources = def.resources ?? {};
|
|
@@ -241,10 +248,13 @@ export class AgentDurableObject {
|
|
|
241
248
|
const sink = new InProcEventSink();
|
|
242
249
|
sink.subscribe((e) => {
|
|
243
250
|
if (e.type !== "turn.failed") return;
|
|
244
|
-
|
|
251
|
+
// The stack already opens with the message; log it INSTEAD of the message line
|
|
252
|
+
// when present so wrangler tail shows one coherent trace, not the message twice.
|
|
253
|
+
const defaultLog = () =>
|
|
254
|
+
console.error(`[june] agent "${name}" turn ${e.turnId} failed${e.step ? ` at ${e.step}` : ""}: ${e.error.stack ?? e.error.message}${e.error.causeChain?.length ? `\ncaused by: ${e.error.causeChain.join(" ← ")}` : ""}`);
|
|
245
255
|
if (def.onTurnError) {
|
|
246
256
|
try {
|
|
247
|
-
const out = def.onTurnError({ turnId: e.turnId, error: e.error });
|
|
257
|
+
const out = def.onTurnError({ turnId: e.turnId, error: e.error, phase: e.phase, step: e.step });
|
|
248
258
|
// An async hook (e.g. `await sendToSentry(...)`) types as void but returns a
|
|
249
259
|
// Promise — a rejection there must hit the same fallback as a sync throw, or
|
|
250
260
|
// the failure goes silent again (plus an unhandled rejection). No waitUntil
|
|
@@ -469,7 +479,7 @@ export async function sseTurnFinalText(res: Response): Promise<string> {
|
|
|
469
479
|
if (!line) continue;
|
|
470
480
|
const e = JSON.parse(line.slice(5).trim()) as TurnEvent;
|
|
471
481
|
if (e.type === "turn.completed") return e.text;
|
|
472
|
-
if (e.type === "turn.failed") throw new Error(e.error.message);
|
|
482
|
+
if (e.type === "turn.failed") throw new Error(e.error.message, { cause: e.error }); // full TurnError rides along
|
|
473
483
|
}
|
|
474
484
|
if (done) break;
|
|
475
485
|
}
|
package/src/agent-native.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "@junejs/core/agent-runtime";
|
|
21
21
|
import { channelFetch, type AgentDefinition, type ChannelContext } from "@junejs/core/agent-config";
|
|
22
22
|
import { openLocalSqliteSync, type SyncSqlite } from "./sqlite-driver";
|
|
23
|
+
import { assertCoreRuntimeVersion } from "./core-version";
|
|
23
24
|
|
|
24
25
|
function initSchema(db: SyncSqlite) {
|
|
25
26
|
db.exec(`CREATE TABLE IF NOT EXISTS agent_sessions (session_id TEXT PRIMARY KEY, status TEXT)`);
|
|
@@ -99,6 +100,7 @@ export class NativeRuntime implements Runtime {
|
|
|
99
100
|
private readonly db: SyncSqlite;
|
|
100
101
|
|
|
101
102
|
constructor(agents: Record<string, AgentDef>, db: SyncSqlite) {
|
|
103
|
+
assertCoreRuntimeVersion("NativeRuntime"); // #94: fail power-on, not mid-turn
|
|
102
104
|
this.agents = agents;
|
|
103
105
|
this.db = db;
|
|
104
106
|
initSchema(db);
|
|
@@ -154,6 +156,7 @@ export class MemoryRuntime implements Runtime {
|
|
|
154
156
|
private stores = new Map<string, MemorySessionStore>();
|
|
155
157
|
private readonly agents: Record<string, AgentDef>;
|
|
156
158
|
constructor(agents: Record<string, AgentDef>) {
|
|
159
|
+
assertCoreRuntimeVersion("MemoryRuntime"); // #94: fail power-on, not mid-turn
|
|
157
160
|
this.agents = agents;
|
|
158
161
|
}
|
|
159
162
|
session(agent: string, id: string): AgentSession {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// core-version.ts — the #94 belt-and-braces: fail at POWER-ON when the @junejs/core
|
|
2
|
+
// copy this server resolved is not the one it was built for.
|
|
3
|
+
//
|
|
4
|
+
// The primary fix is @junejs/core being a peerDependency (the app's single resolution
|
|
5
|
+
// always wins), but a nested second copy can still appear (npm legacy peer handling,
|
|
6
|
+
// a stale lockfile, a transitive pin). When it does, the failure without this check
|
|
7
|
+
// is far from the cause and mid-turn ("sink.emit is not a function", first seen in
|
|
8
|
+
// crisp-agent production); with it, construction throws with both versions named.
|
|
9
|
+
//
|
|
10
|
+
// Namespace import + optional read on purpose: a PRE-#94 core has no
|
|
11
|
+
// RUNTIME_API_VERSION export, and a named import of a missing export would die at
|
|
12
|
+
// module link time with an unhelpful SyntaxError — exactly the confusion this
|
|
13
|
+
// module exists to remove.
|
|
14
|
+
import * as coreRuntime from "@junejs/core/agent-runtime";
|
|
15
|
+
|
|
16
|
+
// The contract number this server tree is built against. Bump in lockstep with
|
|
17
|
+
// core's RUNTIME_API_VERSION whenever the server↔core runtime contract changes.
|
|
18
|
+
const EXPECTED_CORE_RUNTIME_API = 1;
|
|
19
|
+
|
|
20
|
+
export function assertCoreRuntimeVersion(surface: string): void {
|
|
21
|
+
checkCoreRuntimeVersion(surface, (coreRuntime as { RUNTIME_API_VERSION?: number }).RUNTIME_API_VERSION);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The pure check, split out so the mismatch paths are unit-testable (the ambient
|
|
25
|
+
// read above can only ever see the workspace's own — matching — core).
|
|
26
|
+
export function checkCoreRuntimeVersion(surface: string, actual: number | undefined, expected: number = EXPECTED_CORE_RUNTIME_API): void {
|
|
27
|
+
if (actual === expected) return;
|
|
28
|
+
throw new Error(
|
|
29
|
+
`${surface}: @junejs/server is built against @junejs/core runtime API v${expected}, ` +
|
|
30
|
+
`but the @junejs/core it resolved provides ${actual === undefined ? "no RUNTIME_API_VERSION (a pre-0.1.1-dev.16 core)" : `v${actual}`} — ` +
|
|
31
|
+
`two copies of @junejs/core are likely installed (one nested under @junejs/server). ` +
|
|
32
|
+
`Align the app's @junejs/core with @junejs/server's peer range, then reinstall/dedupe so one copy remains.`,
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/pipeline.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
sitemapXml,
|
|
35
35
|
} from "@junejs/core/discovery";
|
|
36
36
|
import { mcpHandler, mcpTools } from "@junejs/core/mcp";
|
|
37
|
+
import type { Principal, Session } from "@junejs/core/context";
|
|
37
38
|
|
|
38
39
|
import { ensureScope, runInScope, setRequestLocale } from "@junejs/db";
|
|
39
40
|
import type { AgentConfig } from "@junejs/core/config";
|
|
@@ -126,6 +127,14 @@ export type PipelineConfig = {
|
|
|
126
127
|
// `currentServices()` resolves in loaders/views/actions. Absent → currentServices()
|
|
127
128
|
// is undefined. This is the Worker-side twin of a Durable Object seeding services.
|
|
128
129
|
services?: () => Promise<unknown> | unknown;
|
|
130
|
+
// Resolve the request's authenticated identity (the auth integration's job —
|
|
131
|
+
// e.g. Better Auth reading the session cookie). The result rides every
|
|
132
|
+
// ActionContext the pipeline builds, starting with the /mcp mount, so
|
|
133
|
+
// requiresPrincipal actions and per-call connection auth see the SAME
|
|
134
|
+
// principal a UI dispatch would. Absent → anonymous (identity-gated actions
|
|
135
|
+
// reject); a THROWING resolver propagates as a server error — it must never
|
|
136
|
+
// silently degrade to anonymous.
|
|
137
|
+
identity?: (request: Request) => Promise<{ user?: Principal; session?: Session }> | { user?: Principal; session?: Session };
|
|
129
138
|
earlyHints?: string[];
|
|
130
139
|
htmlCacheControl?: string;
|
|
131
140
|
notFoundComponent?: React.ComponentType<{ pathname: string }>;
|
|
@@ -473,9 +482,11 @@ export function createPipeline(cfg: PipelineConfig): Pipeline {
|
|
|
473
482
|
if (url.pathname === "/mcp") {
|
|
474
483
|
if (!agent.mcp) return notFoundResponse("view", url.pathname);
|
|
475
484
|
// The agent's tool calls run inside the same request scope, so an action's
|
|
476
|
-
// ambient `db` is the SAME resource the UI uses
|
|
477
|
-
// the same principal
|
|
478
|
-
|
|
485
|
+
// ambient `db` is the SAME resource the UI uses, and — via cfg.identity —
|
|
486
|
+
// the same principal: requiresPrincipal actions and per-call connection
|
|
487
|
+
// auth are live on this surface. ctx carries identity only — not resources.
|
|
488
|
+
const identity = cfg.identity ? await cfg.identity(request) : undefined;
|
|
489
|
+
return mcpHandler(request, { request, ...identity });
|
|
479
490
|
}
|
|
480
491
|
if (request.method === "GET" && agent.discovery) {
|
|
481
492
|
const d = await discovery(url);
|