@junejs/server 0.1.2-dev.9 → 0.1.3

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.
@@ -1,5 +1,5 @@
1
1
  import { Channel, ChannelFactory } from "@junejs/core/agent-config";
2
- import { InboundEvent, Model, Msg, ProactiveTrigger, SessionStore, Tool, TurnError, TurnEvent, TurnFailurePhase } from "@junejs/core/agent-runtime";
2
+ import { Model, Msg, SessionStore, Tool } 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;
@@ -30,14 +30,11 @@ declare class DoSessionStore implements SessionStore {
30
30
  private get sql();
31
31
  appendMessage(m: Msg): void;
32
32
  messages(): Msg[];
33
- hasOpeningMessage(turnId: string): boolean;
33
+ hasUserTurn(turnId: string): boolean;
34
34
  getStep(id: string): unknown | undefined;
35
35
  putStep(id: string, output: unknown): void;
36
- delStep(id: string): void;
37
36
  getStatus(): string;
38
37
  setStatus(s: string): void;
39
- getSessionKey(): string | undefined;
40
- setSessionKey(key: string): void;
41
38
  tx<T>(fn: () => T): T;
42
39
  unwrap<H = unknown>(): H;
43
40
  }
@@ -46,53 +43,21 @@ type DoAgentDef = {
46
43
  model: Model;
47
44
  tools: Tool[];
48
45
  instructions?: string;
49
- channelInstructions?: Record<string, string>;
50
- channels?: (Channel | ChannelFactory)[];
51
- env?: unknown;
52
46
  resources?: Resources;
53
47
  services?: unknown;
54
- onTurnError?: (failure: {
55
- turnId: string;
56
- error: TurnError;
57
- phase?: TurnFailurePhase;
58
- step?: string;
59
- }) => void | Promise<void>;
60
48
  };
61
49
  declare class AgentDurableObject {
62
50
  private session;
63
- private sessionKey;
64
- private readonly name;
65
- private readonly store;
66
- private readonly sink;
67
- private readonly model;
68
- private readonly tools;
69
- private readonly channelInstructions?;
70
51
  private readonly resources;
71
52
  private readonly services;
72
53
  constructor(state: DurableObjectState, def: DoAgentDef);
73
- private resolveSession;
74
54
  turn(input: {
75
55
  turnId?: string;
76
56
  userText: string;
77
- event?: InboundEvent;
78
- trigger?: ProactiveTrigger;
79
- session?: string;
80
57
  }): Promise<string>;
81
- start(input: {
82
- turnId?: string;
83
- userText: string;
84
- event?: InboundEvent;
85
- trigger?: ProactiveTrigger;
86
- session?: string;
87
- }): Promise<{
88
- turnId: string;
89
- }>;
90
58
  transcript(): import("@junejs/core/agent-runtime").Turn[];
91
59
  fetch(req: Request): Promise<Response>;
92
60
  }
93
- declare function sseTurnFinalText(res: Response): Promise<string>;
94
- declare function sseTurnEvents(res: Response): AsyncIterable<TurnEvent>;
95
- declare const SESSION_HEADER = "x-june-session";
96
61
  declare function durableFetch(namespace: DurableObjectNamespace, agent: string, session: string, req: Request): Promise<Response>;
97
62
  declare function durableAgentSurface(getNamespace: () => DurableObjectNamespace | undefined, opts: {
98
63
  agentName: string;
@@ -102,8 +67,7 @@ declare function durableChannelSurface(getNamespace: () => DurableObjectNamespac
102
67
  agentName: string;
103
68
  channels: (Channel | ChannelFactory)[];
104
69
  env: unknown;
105
- services?: (env: unknown) => unknown;
106
70
  waitUntil?: (p: Promise<unknown>) => void;
107
71
  }): (req: Request) => Promise<Response | null>;
108
72
  //#endregion
109
- export { AgentDurableObject, DoAgentDef, DoSessionStore, DurableObjectNamespace, DurableObjectState, DurableObjectStub, DurableStorage, SESSION_HEADER, SqlStorage, SqlStorageCursor, SqlStorageValue, durableAgentSurface, durableChannelSurface, durableFetch, sseTurnEvents, sseTurnFinalText };
73
+ export { AgentDurableObject, DoAgentDef, DoSessionStore, DurableObjectNamespace, DurableObjectState, DurableObjectStub, DurableStorage, SqlStorage, SqlStorageCursor, SqlStorageValue, durableAgentSurface, durableChannelSurface, durableFetch };
@@ -1,6 +1,5 @@
1
- import { assertCoreRuntimeVersion } from "./core-version.js";
2
1
  import { channelDispatch, resolveChannel } from "@junejs/core/agent-config";
3
- import { AgentSession, ResumeAuthorizationError, withSystem } from "@junejs/core/agent-runtime";
2
+ import { AgentSession, withSystem } from "@junejs/core/agent-runtime";
4
3
  import { ensureScope, runInScope } from "@junejs/db";
5
4
  //#region src/agent-durable.ts
6
5
  var DoSessionStore = class {
@@ -21,8 +20,8 @@ var DoSessionStore = class {
21
20
  messages() {
22
21
  return this.sql.exec("SELECT body FROM agent_messages ORDER BY seq").toArray().map((r) => JSON.parse(r.body));
23
22
  }
24
- hasOpeningMessage(turnId) {
25
- return this.messages().some((m) => (m.role === "user" || m.role === "trigger") && m.turnId === turnId);
23
+ hasUserTurn(turnId) {
24
+ return this.messages().some((m) => m.role === "user" && m.turnId === turnId);
26
25
  }
27
26
  getStep(id) {
28
27
  const rows = this.sql.exec("SELECT output FROM agent_steps WHERE id = ?", id).toArray();
@@ -31,9 +30,6 @@ var DoSessionStore = class {
31
30
  putStep(id, output) {
32
31
  this.sql.exec("INSERT INTO agent_steps (id, output) VALUES (?, ?)", id, JSON.stringify(output));
33
32
  }
34
- delStep(id) {
35
- this.sql.exec("DELETE FROM agent_steps WHERE id = ?", id);
36
- }
37
33
  getStatus() {
38
34
  const rows = this.sql.exec("SELECT v FROM agent_meta WHERE k = 'status'").toArray();
39
35
  return rows.length ? rows[0].v : "new";
@@ -41,13 +37,6 @@ var DoSessionStore = class {
41
37
  setStatus(s) {
42
38
  this.sql.exec("INSERT INTO agent_meta (k, v) VALUES ('status', ?) ON CONFLICT(k) DO UPDATE SET v = ?", s, s);
43
39
  }
44
- getSessionKey() {
45
- const rows = this.sql.exec("SELECT v FROM agent_meta WHERE k = 'session_key'").toArray();
46
- return rows.length ? rows[0].v : void 0;
47
- }
48
- setSessionKey(key) {
49
- this.sql.exec("INSERT INTO agent_meta (k, v) VALUES ('session_key', ?) ON CONFLICT(k) DO UPDATE SET v = ?", key, key);
50
- }
51
40
  tx(fn) {
52
41
  return this.storage.transactionSync(fn);
53
42
  }
@@ -55,12 +44,12 @@ var DoSessionStore = class {
55
44
  return this.sql;
56
45
  }
57
46
  };
58
- var InProcEventSink = class {
47
+ var InProcBroadcaster = class {
59
48
  subs = /* @__PURE__ */ new Set();
60
- emit(e) {
49
+ publish(turnId) {
61
50
  this.subs.forEach((cb) => {
62
51
  try {
63
- cb(e);
52
+ cb(turnId);
64
53
  } catch {}
65
54
  });
66
55
  }
@@ -74,235 +63,40 @@ const crossDoUnsupported = { session() {
74
63
  } };
75
64
  var AgentDurableObject = class {
76
65
  session;
77
- sessionKey;
78
- name;
79
- store;
80
- sink;
81
- model;
82
- tools;
83
- channelInstructions;
84
66
  resources;
85
67
  services;
86
68
  constructor(state, def) {
87
- assertCoreRuntimeVersion(`AgentDurableObject(${def.name ?? "agent"})`);
88
69
  const store = new DoSessionStore(state.storage);
89
70
  const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
90
71
  this.resources = def.resources ?? {};
91
72
  this.services = def.services;
92
- const name = def.name ?? "agent";
93
- const sink = new InProcEventSink();
94
- sink.subscribe((e) => {
95
- if (e.type !== "turn.failed") return;
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(" ← ")}` : ""}`);
97
- if (def.onTurnError) try {
98
- const out = def.onTurnError({
99
- turnId: e.turnId,
100
- error: e.error,
101
- phase: e.phase,
102
- step: e.step
103
- });
104
- if (out && typeof out.then === "function") out.then(void 0, (hookErr) => {
105
- console.error(`[june] agent "${name}": onTurnError hook rejected:`, hookErr);
106
- defaultLog();
107
- });
108
- return;
109
- } catch (hookErr) {
110
- console.error(`[june] agent "${name}": onTurnError hook threw:`, hookErr);
111
- }
112
- defaultLog();
113
- });
114
- const channelTools = (def.channels ?? []).flatMap((c) => resolveChannel(c, def.env).tools?.() ?? []);
115
- const tools = [...def.tools];
116
- const seen = new Set(tools.map((t) => t.spec.name));
117
- for (const t of channelTools) {
118
- if (seen.has(t.spec.name)) throw new Error(`AgentDurableObject(${def.name ?? "agent"}): duplicate tool name "${t.spec.name}" from a channel — rename so dispatch is unambiguous.`);
119
- seen.add(t.spec.name);
120
- tools.push(t);
121
- }
122
- this.name = name;
123
- this.store = store;
124
- this.sink = sink;
125
- this.model = model;
126
- this.tools = tools;
127
- this.channelInstructions = def.channelInstructions;
128
- }
129
- resolveSession(key) {
130
- if (key !== void 0) assertSessionKey(key);
131
- if (this.session) {
132
- if (key !== void 0 && this.sessionKey !== key) throw new Error(this.sessionKey === "self" ? `agent "${this.name}": request session "${key}" arrived, but this session is bound to the placeholder "self" (first used without a real key) — carry the key on every path (route through durableFetch, or pass turn({ session }) on the direct API)` : `agent "${this.name}": request session "${key}" does not match this object's session "${this.sessionKey}" — one DO is one session; address each session by its own key (durableFetch, or turn({ session }) on the direct API)`);
133
- return this.session;
134
- }
135
- const stored = this.store.getSessionKey();
136
- if (key !== void 0 && stored !== void 0 && key !== stored) throw new Error(`agent "${this.name}": request session "${key}" does not match this object's session "${stored}" — one DO is one session; address each session by its own key (durableFetch, or turn({ session }) on the direct API)`);
137
- const resolved = key ?? stored ?? "self";
138
- if (key !== void 0 && key !== "self" && stored === void 0) this.store.setSessionKey(key);
139
- this.sessionKey = resolved;
140
- this.session = new AgentSession(this.name, resolved, this.store, this.sink, this.model, this.tools, crossDoUnsupported, this.channelInstructions);
141
- return this.session;
73
+ this.session = new AgentSession(def.name ?? "agent", "self", store, new InProcBroadcaster(), model, def.tools, crossDoUnsupported);
142
74
  }
143
75
  async turn(input) {
144
76
  await ensureScope();
145
- const session = this.resolveSession(input.session);
146
77
  return runInScope({
147
78
  resources: this.resources,
148
79
  services: this.services
149
- }, () => session.turn(input));
150
- }
151
- async start(input) {
152
- await ensureScope();
153
- const session = this.resolveSession(input.session);
154
- return runInScope({
155
- resources: this.resources,
156
- services: this.services
157
- }, () => session.start(input));
80
+ }, () => this.session.turn(input));
158
81
  }
159
82
  transcript() {
160
- if (this.session) return this.session.transcript();
161
- if (this.store.getSessionKey() !== void 0) return this.resolveSession().transcript();
162
- return new AgentSession(this.name, "self", this.store, this.sink, this.model, this.tools, crossDoUnsupported, this.channelInstructions).transcript();
83
+ return this.session.transcript();
163
84
  }
164
85
  async fetch(req) {
165
86
  const url = new URL(req.url);
166
- const key = req.headers.get("x-june-session") ?? void 0;
167
87
  if (req.method === "POST" && url.pathname.endsWith("/turn")) {
168
- const { userText, turnId, event, trigger } = await req.json();
169
- await ensureScope();
170
- let session;
171
- let started;
172
- try {
173
- session = this.resolveSession(key);
174
- started = runInScope({
175
- resources: this.resources,
176
- services: this.services
177
- }, () => session.start({
178
- userText,
179
- turnId,
180
- event,
181
- trigger
182
- }));
183
- } catch (err) {
184
- return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
185
- }
186
- if (url.searchParams.get("detach") === "1") return Response.json({ turnId: started.turnId }, { status: 202 });
187
- return new Response(sseTurnStream(session, started.turnId), { headers: SSE_HEADERS });
88
+ const { userText, turnId } = await req.json();
89
+ return Response.json({ text: await this.turn({
90
+ userText,
91
+ turnId
92
+ }) });
188
93
  }
189
- if (req.method === "POST" && url.pathname.endsWith("/resume")) {
190
- const { turnId, inputId, input, by } = await req.json();
191
- await ensureScope();
192
- let session;
193
- try {
194
- session = this.resolveSession(key);
195
- runInScope({
196
- resources: this.resources,
197
- services: this.services
198
- }, () => session.resume(turnId, inputId, input, { by }));
199
- } catch (err) {
200
- const message = err instanceof Error ? err.message : String(err);
201
- return Response.json({ error: message }, { status: err instanceof ResumeAuthorizationError ? 403 : 409 });
202
- }
203
- return new Response(sseTurnStream(session, turnId), { headers: SSE_HEADERS });
204
- }
205
- if (url.pathname.endsWith("/transcript")) {
206
- if (key !== void 0) try {
207
- return Response.json({ transcript: this.resolveSession(key).transcript() });
208
- } catch (err) {
209
- return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
210
- }
211
- return Response.json({ transcript: this.transcript() });
212
- }
213
- return new Response("agent DO — POST /turn, POST /resume, or GET /transcript", { status: 404 });
94
+ if (url.pathname.endsWith("/transcript")) return Response.json({ transcript: this.transcript() });
95
+ return new Response("agent DO — POST /turn or GET /transcript", { status: 404 });
214
96
  }
215
97
  };
216
- const SSE_HEADERS = {
217
- "content-type": "text/event-stream",
218
- "cache-control": "no-store"
219
- };
220
- function sseTurnStream(session, turnId) {
221
- const enc = new TextEncoder();
222
- let unsub;
223
- let heartbeat;
224
- const stop = () => {
225
- unsub?.();
226
- clearInterval(heartbeat);
227
- };
228
- return new ReadableStream({
229
- start(controller) {
230
- heartbeat = setInterval(() => {
231
- try {
232
- controller.enqueue(enc.encode(":hb\n\n"));
233
- } catch {
234
- clearInterval(heartbeat);
235
- }
236
- }, 2e4);
237
- unsub = session.observe((e) => {
238
- controller.enqueue(enc.encode(`data: ${JSON.stringify(e)}\n\n`));
239
- if (e.type === "turn.completed" || e.type === "turn.failed" || e.type === "input.requested") {
240
- stop();
241
- controller.close();
242
- }
243
- }, { turnId });
244
- },
245
- cancel() {
246
- stop();
247
- }
248
- });
249
- }
250
- async function sseTurnFinalText(res) {
251
- const ct = res.headers.get("content-type") ?? "";
252
- if (!res.body || !ct.includes("text/event-stream")) {
253
- const detail = res.body ? (await res.text()).slice(0, 200) : "no body";
254
- throw new Error(`turn stream: expected an SSE response, got ${ct || "no content-type"} (status ${res.status}): ${detail}`);
255
- }
256
- const reader = res.body.getReader();
257
- const dec = new TextDecoder();
258
- let buf = "";
259
- for (;;) {
260
- const { done, value } = await reader.read();
261
- if (value) buf += dec.decode(value, { stream: true });
262
- let i;
263
- while ((i = buf.indexOf("\n\n")) >= 0) {
264
- const line = buf.slice(0, i).split("\n").find((l) => l.startsWith("data:"));
265
- buf = buf.slice(i + 2);
266
- if (!line) continue;
267
- const e = JSON.parse(line.slice(5).trim());
268
- if (e.type === "turn.completed") return e.text;
269
- if (e.type === "turn.failed") throw new Error(e.error.message, { cause: e.error });
270
- }
271
- if (done) break;
272
- }
273
- throw new Error("turn stream ended without a terminal event");
274
- }
275
- async function* sseTurnEvents(res) {
276
- const ct = res.headers.get("content-type") ?? "";
277
- if (!res.body || !ct.includes("text/event-stream")) {
278
- const detail = res.body ? (await res.text()).slice(0, 200) : "no body";
279
- throw new Error(`turn stream: expected an SSE response, got ${ct || "no content-type"} (status ${res.status}): ${detail}`);
280
- }
281
- const reader = res.body.getReader();
282
- const dec = new TextDecoder();
283
- let buf = "";
284
- for (;;) {
285
- const { done, value } = await reader.read();
286
- if (value) buf += dec.decode(value, { stream: true });
287
- let i;
288
- while ((i = buf.indexOf("\n\n")) >= 0) {
289
- const line = buf.slice(0, i).split("\n").find((l) => l.startsWith("data:"));
290
- buf = buf.slice(i + 2);
291
- if (line) yield JSON.parse(line.slice(5).trim());
292
- }
293
- if (done) break;
294
- }
295
- }
296
- const SESSION_HEADER = "x-june-session";
297
- const SESSION_KEY_RE = /^[\x20-\x7E]+$/;
298
- function assertSessionKey(session) {
299
- if (typeof session !== "string" || !SESSION_KEY_RE.test(session)) throw new Error(`invalid session key ${JSON.stringify(session)} — a session key must be a non-empty printable-ASCII string (it rides an HTTP header and the DO name)`);
300
- }
301
98
  function durableFetch(namespace, agent, session, req) {
302
- assertSessionKey(session);
303
- const headers = new Headers(req.headers);
304
- headers.set(SESSION_HEADER, session);
305
- return namespace.get(namespace.idFromName(`${agent}:${session}`)).fetch(new Request(req, { headers }));
99
+ return namespace.get(namespace.idFromName(`${agent}:${session}`)).fetch(req);
306
100
  }
307
101
  function durableAgentSurface(getNamespace, opts) {
308
102
  const chatPath = opts.chatPath ?? "/message";
@@ -312,17 +106,10 @@ function durableAgentSurface(getNamespace, opts) {
312
106
  const namespace = getNamespace();
313
107
  if (!namespace) return null;
314
108
  const { message, session } = await req.json();
315
- if (session !== void 0) try {
316
- assertSessionKey(session);
317
- } catch (err) {
318
- return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });
319
- }
320
- const res = await durableFetch(namespace, opts.agentName, session ?? "default", new Request("https://do/turn", {
109
+ return durableFetch(namespace, opts.agentName, session ?? "default", new Request("https://do/turn", {
321
110
  method: "POST",
322
111
  body: JSON.stringify({ userText: message })
323
112
  }));
324
- if (req.headers.get("accept")?.includes("text/event-stream")) return res;
325
- return Response.json({ text: await sseTurnFinalText(res) });
326
113
  };
327
114
  }
328
115
  function durableChannelSurface(getNamespace, opts) {
@@ -335,78 +122,20 @@ function durableChannelSurface(getNamespace, opts) {
335
122
  channels: [],
336
123
  connections: []
337
124
  },
338
- services: opts.services?.(opts.env),
339
125
  waitUntil: opts.waitUntil,
340
126
  run: async (message, o) => {
341
127
  const namespace = getNamespace();
342
128
  if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
343
- return sseTurnFinalText(await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn", {
129
+ const { text } = await (await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn", {
344
130
  method: "POST",
345
- body: serializeTurn(message, o)
346
- })));
347
- },
348
- runDetached: async (message, o) => {
349
- const namespace = getNamespace();
350
- if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
351
- const res = await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn?detach=1", {
352
- method: "POST",
353
- body: serializeTurn(message, o)
354
- }));
355
- if (res.status !== 202) {
356
- const detail = (await res.text()).slice(0, 200);
357
- throw new Error(`runDetached: turn was not accepted (status ${res.status}): ${detail}`);
358
- }
359
- return await res.json();
360
- },
361
- runStream: async function* (message, o) {
362
- const namespace = getNamespace();
363
- if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
364
- yield* sseTurnEvents(await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn", {
365
- method: "POST",
366
- body: serializeTurn(message, o)
367
- })));
368
- },
369
- resumeStream: async function* (o) {
370
- const namespace = getNamespace();
371
- if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
372
- yield* sseTurnEvents(await durableFetch(namespace, opts.agentName, o.session ?? "default", new Request("https://do/resume", {
373
- method: "POST",
374
- body: serializeResume(o)
375
- })));
131
+ body: JSON.stringify({
132
+ userText: message,
133
+ turnId: o?.turnId
134
+ })
135
+ }))).json();
136
+ return text;
376
137
  }
377
138
  });
378
139
  }
379
- function serializeResume(o) {
380
- try {
381
- return JSON.stringify({
382
- turnId: o.turnId,
383
- inputId: o.inputId,
384
- input: o.input,
385
- by: o.by
386
- });
387
- } catch (err) {
388
- throw new Error(`resumeStream: input is not JSON-serializable (${err.message}) — a resume answer must round-trip to the DO`);
389
- }
390
- }
391
- function serializeTurn(userText, o) {
392
- const payload = {
393
- userText,
394
- turnId: o?.turnId,
395
- event: o?.event,
396
- trigger: o?.trigger
397
- };
398
- try {
399
- return JSON.stringify(payload);
400
- } catch {
401
- const event = o?.event ? {
402
- ...o.event,
403
- raw: void 0
404
- } : void 0;
405
- return JSON.stringify({
406
- ...payload,
407
- event
408
- });
409
- }
410
- }
411
140
  //#endregion
412
- export { AgentDurableObject, DoSessionStore, SESSION_HEADER, durableAgentSurface, durableChannelSurface, durableFetch, sseTurnEvents, sseTurnFinalText };
141
+ export { AgentDurableObject, DoSessionStore, durableAgentSurface, durableChannelSurface, durableFetch };
@@ -6,7 +6,6 @@ type AgentDef = {
6
6
  model: Model;
7
7
  tools: Tool[];
8
8
  instructions?: string;
9
- channelInstructions?: Record<string, string>;
10
9
  };
11
10
  declare class NativeRuntime implements Runtime {
12
11
  private actors;
@@ -31,7 +30,6 @@ declare function createAgentRuntime(agents: Record<string, AgentDef>, opts?: {
31
30
  declare function mountAgent(agent: AgentDefinition, runtime: Runtime, opts?: {
32
31
  chatPath?: string;
33
32
  channels?: boolean;
34
- services?: unknown;
35
33
  }): {
36
34
  surface: (req: Request) => Promise<Response | null>;
37
35
  fetch: (req: Request) => Promise<Response | null>;
@@ -1,4 +1,3 @@
1
- import { assertCoreRuntimeVersion } from "./core-version.js";
2
1
  import { openLocalSqliteSync } from "./sqlite-driver.js";
3
2
  import { channelFetch } from "@junejs/core/agent-config";
4
3
  import { AgentSession, withSystem } from "@junejs/core/agent-runtime";
@@ -21,8 +20,8 @@ var SqliteSessionStore = class {
21
20
  messages() {
22
21
  return this.db.query("SELECT body FROM agent_messages WHERE session_id = ? ORDER BY seq").all(this.sid).map((r) => JSON.parse(r.body));
23
22
  }
24
- hasOpeningMessage(turnId) {
25
- return this.messages().some((m) => (m.role === "user" || m.role === "trigger") && m.turnId === turnId);
23
+ hasUserTurn(turnId) {
24
+ return this.messages().some((m) => m.role === "user" && m.turnId === turnId);
26
25
  }
27
26
  getStep(id) {
28
27
  const r = this.db.query("SELECT output FROM agent_steps WHERE session_id = ? AND id = ?").get(this.sid, id);
@@ -31,9 +30,6 @@ var SqliteSessionStore = class {
31
30
  putStep(id, output) {
32
31
  this.db.query("INSERT INTO agent_steps (session_id, id, output) VALUES (?, ?, ?)").run(this.sid, id, JSON.stringify(output));
33
32
  }
34
- delStep(id) {
35
- this.db.query("DELETE FROM agent_steps WHERE session_id = ? AND id = ?").run(this.sid, id);
36
- }
37
33
  getStatus() {
38
34
  return this.db.query("SELECT status FROM agent_sessions WHERE session_id = ?").get(this.sid)?.status ?? "new";
39
35
  }
@@ -55,12 +51,12 @@ var SqliteSessionStore = class {
55
51
  return this.db;
56
52
  }
57
53
  };
58
- var InProcEventSink = class {
54
+ var InProcBroadcaster = class {
59
55
  subs = /* @__PURE__ */ new Set();
60
- emit(e) {
56
+ publish(turnId) {
61
57
  this.subs.forEach((cb) => {
62
58
  try {
63
- cb(e);
59
+ cb(turnId);
64
60
  } catch {}
65
61
  });
66
62
  }
@@ -74,7 +70,6 @@ var NativeRuntime = class {
74
70
  agents;
75
71
  db;
76
72
  constructor(agents, db) {
77
- assertCoreRuntimeVersion("NativeRuntime");
78
73
  this.agents = agents;
79
74
  this.db = db;
80
75
  initSchema(db);
@@ -86,7 +81,7 @@ var NativeRuntime = class {
86
81
  const def = this.agents[agent];
87
82
  if (!def) throw new Error(`unknown agent: ${agent}`);
88
83
  const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
89
- a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcEventSink(), model, def.tools, this, def.channelInstructions);
84
+ a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcBroadcaster(), model, def.tools, this);
90
85
  this.actors.set(key, a);
91
86
  }
92
87
  return a;
@@ -105,8 +100,8 @@ var MemorySessionStore = class {
105
100
  messages() {
106
101
  return this.msgs.slice();
107
102
  }
108
- hasOpeningMessage(turnId) {
109
- return this.msgs.some((m) => (m.role === "user" || m.role === "trigger") && m.turnId === turnId);
103
+ hasUserTurn(turnId) {
104
+ return this.msgs.some((m) => m.role === "user" && m.turnId === turnId);
110
105
  }
111
106
  getStep(id) {
112
107
  return this.steps.has(id) ? this.steps.get(id) : void 0;
@@ -114,9 +109,6 @@ var MemorySessionStore = class {
114
109
  putStep(id, output) {
115
110
  this.steps.set(id, output);
116
111
  }
117
- delStep(id) {
118
- this.steps.delete(id);
119
- }
120
112
  getStatus() {
121
113
  return this.status;
122
114
  }
@@ -133,7 +125,6 @@ var MemoryRuntime = class {
133
125
  stores = /* @__PURE__ */ new Map();
134
126
  agents;
135
127
  constructor(agents) {
136
- assertCoreRuntimeVersion("MemoryRuntime");
137
128
  this.agents = agents;
138
129
  }
139
130
  session(agent, id) {
@@ -144,7 +135,7 @@ var MemoryRuntime = class {
144
135
  if (!def) throw new Error(`unknown agent: ${agent}`);
145
136
  const store = new MemorySessionStore();
146
137
  this.stores.set(key, store);
147
- a = new AgentSession(agent, id, store, new InProcEventSink(), def.model, def.tools, this);
138
+ a = new AgentSession(agent, id, store, new InProcBroadcaster(), def.model, def.tools, this);
148
139
  this.actors.set(key, a);
149
140
  }
150
141
  return a;
@@ -161,18 +152,9 @@ function mountAgent(agent, runtime, opts = {}) {
161
152
  const channelsOn = opts.channels ?? true;
162
153
  const ctx = {
163
154
  agent,
164
- services: opts.services,
165
155
  run: (message, o) => runtime.session(agent.name, o?.session ?? "default").turn({
166
156
  turnId: o?.turnId,
167
- userText: message,
168
- event: o?.event,
169
- trigger: o?.trigger
170
- }),
171
- runDetached: async (message, o) => runtime.session(agent.name, o?.session ?? "default").start({
172
- turnId: o?.turnId,
173
- userText: message,
174
- event: o?.event,
175
- trigger: o?.trigger
157
+ userText: message
176
158
  })
177
159
  };
178
160
  const channels = channelFetch(agent, ctx);
package/dist/build.d.ts CHANGED
@@ -15,6 +15,7 @@ type RouteEntry = {
15
15
  file: string;
16
16
  dynamic: boolean;
17
17
  resource?: boolean;
18
+ generated?: boolean;
18
19
  layouts: string[];
19
20
  loading?: string;
20
21
  };
package/dist/build.js CHANGED
@@ -193,7 +193,11 @@ async function buildManifest(appRoot) {
193
193
  const appRoutes = await scanRoutes(appDir);
194
194
  const frameworkRoutes = existsSync(juneRoutesDir) ? await scanRoutes(juneRoutesDir) : [];
195
195
  const appPaths = new Set(appRoutes.map((r) => r.path));
196
- const scanned = [...appRoutes, ...frameworkRoutes.filter((r) => !appPaths.has(r.path))].sort((a, b) => a.path.localeCompare(b.path));
196
+ const scanned = [...appRoutes, ...frameworkRoutes.filter((r) => !appPaths.has(r.path)).map((r) => ({
197
+ ...r,
198
+ generated: true
199
+ }))].sort((a, b) => a.path.localeCompare(b.path));
200
+ const generatedRoutes = scanned.filter((r) => r.generated).map((r) => r.path);
197
201
  const layoutCache = /* @__PURE__ */ new Map();
198
202
  const loadCached = async (f) => {
199
203
  if (!layoutCache.has(f)) layoutCache.set(f, await importLayout(f));
@@ -266,6 +270,7 @@ async function buildManifest(appRoot) {
266
270
  routes,
267
271
  dynamicRoutes,
268
272
  resourceRoutes,
273
+ ...generatedRoutes.length ? { generatedRoutes } : {},
269
274
  layoutChains,
270
275
  layoutBoundaries,
271
276
  loadings,
@@ -304,7 +309,10 @@ async function juneBuild(appRoot, options = {}) {
304
309
  const appRoutes2 = await scanRoutes(appDir);
305
310
  const frameworkRoutes2 = existsSync(juneRoutesDir2) ? await scanRoutes(juneRoutesDir2) : [];
306
311
  const appPaths2 = new Set(appRoutes2.map((r) => r.path));
307
- const routes = [...appRoutes2, ...frameworkRoutes2.filter((r) => !appPaths2.has(r.path))].sort((a, b) => a.path.localeCompare(b.path));
312
+ const routes = [...appRoutes2, ...frameworkRoutes2.filter((r) => !appPaths2.has(r.path)).map((r) => ({
313
+ ...r,
314
+ generated: true
315
+ }))].sort((a, b) => a.path.localeCompare(b.path));
308
316
  if (routes.length === 0) throw new Error(`no page.* routes found under ${appDir} or .june/routes/`);
309
317
  const frozen = await freezeConfig(appRoot);
310
318
  if (frozen.i18n?.resolveLocale) console.warn("[june build] i18n.resolveLocale is not yet wired into the built worker (URL-pinned + built-in negotiation work; the hook runs in dev only).");
@@ -392,6 +400,8 @@ async function juneBuild(appRoot, options = {}) {
392
400
  if (r.loading) loadings.push(` ${JSON.stringify(r.path)}: ${loadingId(r.loading)},`);
393
401
  });
394
402
  const resourceRoutesField = resources.length ? `\n resourceRoutes: [\n${resources.join("\n")}\n ],` : "";
403
+ const generatedPaths = routes.filter((r) => r.generated).map((r) => r.path);
404
+ const generatedRoutesField = generatedPaths.length ? `\n generatedRoutes: ${JSON.stringify(generatedPaths)},` : "";
395
405
  const layoutBoundariesField = boundaries.length ? `\n layoutBoundaries: {\n${boundaries.join("\n")}\n },` : "";
396
406
  const builtExtraFile = findMiddlewareFile(appDir);
397
407
  if (builtExtraFile) imports.push(`import extra from ${JSON.stringify(importPath(genDir, builtExtraFile))};`);
@@ -427,7 +437,7 @@ ${statics.join("\n")}
427
437
  },
428
438
  dynamicRoutes: [
429
439
  ${dynamics.join("\n")}
430
- ],${resourceRoutesField}
440
+ ],${resourceRoutesField}${generatedRoutesField}
431
441
  layoutChains: {
432
442
  ${chains.join("\n")}
433
443
  },${layoutBoundariesField}