@junejs/server 0.1.2-dev.9 → 0.1.2

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.
@@ -10,8 +10,7 @@
10
10
  import {
11
11
  AgentSession,
12
12
  withSystem,
13
- type EventSink,
14
- type TurnEvent,
13
+ type Broadcaster,
15
14
  type Model,
16
15
  type Msg,
17
16
  type Runtime,
@@ -20,7 +19,6 @@ import {
20
19
  } from "@junejs/core/agent-runtime";
21
20
  import { channelFetch, type AgentDefinition, type ChannelContext } from "@junejs/core/agent-config";
22
21
  import { openLocalSqliteSync, type SyncSqlite } from "./sqlite-driver";
23
- import { assertCoreRuntimeVersion } from "./core-version";
24
22
 
25
23
  function initSchema(db: SyncSqlite) {
26
24
  db.exec(`CREATE TABLE IF NOT EXISTS agent_sessions (session_id TEXT PRIMARY KEY, status TEXT)`);
@@ -47,8 +45,8 @@ class SqliteSessionStore implements SessionStore {
47
45
  return (this.db.query("SELECT body FROM agent_messages WHERE session_id = ? ORDER BY seq").all(this.sid) as { body: string }[])
48
46
  .map((r) => JSON.parse(r.body));
49
47
  }
50
- hasOpeningMessage(turnId: string): boolean {
51
- return this.messages().some((m) => (m.role === "user" || m.role === "trigger") && m.turnId === turnId);
48
+ hasUserTurn(turnId: string): boolean {
49
+ return this.messages().some((m) => m.role === "user" && m.turnId === turnId);
52
50
  }
53
51
  getStep(id: string): unknown | undefined {
54
52
  const r = this.db.query("SELECT output FROM agent_steps WHERE session_id = ? AND id = ?").get(this.sid, id) as { output: string } | undefined;
@@ -57,9 +55,6 @@ class SqliteSessionStore implements SessionStore {
57
55
  putStep(id: string, output: unknown) {
58
56
  this.db.query("INSERT INTO agent_steps (session_id, id, output) VALUES (?, ?, ?)").run(this.sid, id, JSON.stringify(output));
59
57
  }
60
- delStep(id: string) {
61
- this.db.query("DELETE FROM agent_steps WHERE session_id = ? AND id = ?").run(this.sid, id);
62
- }
63
58
  getStatus(): string {
64
59
  return (this.db.query("SELECT status FROM agent_sessions WHERE session_id = ?").get(this.sid) as { status: string } | undefined)?.status ?? "new";
65
60
  }
@@ -82,15 +77,15 @@ class SqliteSessionStore implements SessionStore {
82
77
  unwrap<H = unknown>(): H { return this.db as unknown as H; }
83
78
  }
84
79
 
85
- class InProcEventSink implements EventSink {
86
- private subs = new Set<(e: TurnEvent) => void>();
87
- emit(e: TurnEvent) { this.subs.forEach((cb) => { try { cb(e); } catch { /* a bad subscriber must not break emit */ } }); }
88
- subscribe(cb: (e: TurnEvent) => void): () => void { this.subs.add(cb); return () => this.subs.delete(cb); }
80
+ class InProcBroadcaster implements Broadcaster {
81
+ private subs = new Set<(t: string) => void>();
82
+ publish(turnId: string) { this.subs.forEach((cb) => { try { cb(turnId); } catch { /* a bad subscriber must not break publish */ } }); }
83
+ subscribe(cb: (t: string) => void): () => void { this.subs.add(cb); return () => this.subs.delete(cb); }
89
84
  }
90
85
 
91
86
  // `instructions` (the agent's system prompt) is injected into the model per turn
92
87
  // by the runtime (withSystem) — single-sourced on the def, not baked into `model`.
93
- export type AgentDef = { model: Model; tools: Tool[]; instructions?: string; channelInstructions?: Record<string, string> };
88
+ export type AgentDef = { model: Model; tools: Tool[]; instructions?: string };
94
89
 
95
90
  // The native Runtime: a registry of agent definitions over one SQLite handle,
96
91
  // handing out (and memoizing) an AgentSession actor per (agent, id).
@@ -100,7 +95,6 @@ export class NativeRuntime implements Runtime {
100
95
  private readonly db: SyncSqlite;
101
96
 
102
97
  constructor(agents: Record<string, AgentDef>, db: SyncSqlite) {
103
- assertCoreRuntimeVersion("NativeRuntime"); // #94: fail power-on, not mid-turn
104
98
  this.agents = agents;
105
99
  this.db = db;
106
100
  initSchema(db);
@@ -113,7 +107,7 @@ export class NativeRuntime implements Runtime {
113
107
  const def = this.agents[agent];
114
108
  if (!def) throw new Error(`unknown agent: ${agent}`);
115
109
  const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
116
- a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcEventSink(), model, def.tools, this, def.channelInstructions);
110
+ a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcBroadcaster(), model, def.tools, this);
117
111
  this.actors.set(key, a);
118
112
  }
119
113
  return a;
@@ -141,10 +135,9 @@ class MemorySessionStore implements SessionStore {
141
135
  private status = "new";
142
136
  appendMessage(m: Msg) { this.msgs.push(m); }
143
137
  messages(): Msg[] { return this.msgs.slice(); }
144
- hasOpeningMessage(turnId: string): boolean { return this.msgs.some((m) => (m.role === "user" || m.role === "trigger") && m.turnId === turnId); }
138
+ hasUserTurn(turnId: string): boolean { return this.msgs.some((m) => m.role === "user" && m.turnId === turnId); }
145
139
  getStep(id: string): unknown | undefined { return this.steps.has(id) ? this.steps.get(id) : undefined; }
146
140
  putStep(id: string, output: unknown) { this.steps.set(id, output); }
147
- delStep(id: string) { this.steps.delete(id); }
148
141
  getStatus(): string { return this.status; }
149
142
  setStatus(s: string) { this.status = s; }
150
143
  tx<T>(fn: () => T): T { return fn(); } // no rollback: an in-memory store is not a durability tier
@@ -156,7 +149,6 @@ export class MemoryRuntime implements Runtime {
156
149
  private stores = new Map<string, MemorySessionStore>();
157
150
  private readonly agents: Record<string, AgentDef>;
158
151
  constructor(agents: Record<string, AgentDef>) {
159
- assertCoreRuntimeVersion("MemoryRuntime"); // #94: fail power-on, not mid-turn
160
152
  this.agents = agents;
161
153
  }
162
154
  session(agent: string, id: string): AgentSession {
@@ -167,7 +159,7 @@ export class MemoryRuntime implements Runtime {
167
159
  if (!def) throw new Error(`unknown agent: ${agent}`);
168
160
  const store = new MemorySessionStore();
169
161
  this.stores.set(key, store);
170
- a = new AgentSession(agent, id, store, new InProcEventSink(), def.model, def.tools, this);
162
+ a = new AgentSession(agent, id, store, new InProcBroadcaster(), def.model, def.tools, this);
171
163
  this.actors.set(key, a);
172
164
  }
173
165
  return a;
@@ -203,7 +195,7 @@ export async function createAgentRuntime(
203
195
  export function mountAgent(
204
196
  agent: AgentDefinition,
205
197
  runtime: Runtime,
206
- opts: { chatPath?: string; channels?: boolean; services?: unknown } = {},
198
+ opts: { chatPath?: string; channels?: boolean } = {},
207
199
  ): {
208
200
  surface: (req: Request) => Promise<Response | null>;
209
201
  fetch: (req: Request) => Promise<Response | null>;
@@ -214,14 +206,8 @@ export function mountAgent(
214
206
  const channelsOn = opts.channels ?? true;
215
207
  const ctx: ChannelContext = {
216
208
  agent,
217
- services: opts.services, // same DI bag reachable from channel hooks (parity with durableChannelSurface)
218
209
  run: (message, o) =>
219
- runtime.session(agent.name, o?.session ?? "default").turn({ turnId: o?.turnId, userText: message, event: o?.event, trigger: o?.trigger }),
220
- // FIRE-AND-FORGET (#77): start() without awaiting the result. Native has no waitUntil
221
- // ceiling (Node keeps floating promises alive), but the seam is the same so a channel
222
- // written against ctx.runDetached behaves identically on both targets.
223
- runDetached: async (message, o) =>
224
- runtime.session(agent.name, o?.session ?? "default").start({ turnId: o?.turnId, userText: message, event: o?.event, trigger: o?.trigger }),
210
+ runtime.session(agent.name, o?.session ?? "default").turn({ turnId: o?.turnId, userText: message }),
225
211
  };
226
212
  const channels = channelFetch(agent, ctx);
227
213
  const surface = async (req: Request): Promise<Response | null> => {
package/src/worker.ts CHANGED
@@ -274,8 +274,11 @@ export function withAssets(
274
274
  }
275
275
 
276
276
  // 2. Static assets (prerendered HTML/.md/.json, /client.js, hashed CSS)
277
- // served direct.
278
- if (assets) {
277
+ // served direct — for reads only. Assets never answer a write, and on
278
+ // workerd handing the incoming request to ASSETS.fetch() consumes its
279
+ // body: a POST that 404s here would reach the pipeline with a used stream
280
+ // (POST /mcp → -32700 Parse error, an action's input gone).
281
+ if (assets && (request.method === "GET" || request.method === "HEAD")) {
279
282
  const a = await assets.fetch(request);
280
283
  if (a.status !== 404) {
281
284
  const ct = a.headers.get("content-type") ?? "";
@@ -1,5 +0,0 @@
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 };
@@ -1,12 +0,0 @@
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 };
@@ -1,34 +0,0 @@
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
- }