@junejs/server 1.0.0-dev.14 → 1.0.0-dev.19

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/adapter.d.ts CHANGED
@@ -4,6 +4,7 @@ type AdapterCapabilities = {
4
4
  runtime: "edge" | "node" | "serverless" | "native" | "static";
5
5
  persistentConnections: boolean;
6
6
  assets: "platform" | "server" | "none";
7
+ durableObjects?: boolean;
7
8
  };
8
9
  type AdapterEntry = {
9
10
  imports: string[];
@@ -14,6 +15,10 @@ type ResourcePlan = {
14
15
  binding: string;
15
16
  databaseName: string;
16
17
  };
18
+ agent?: {
19
+ binding: string;
20
+ className: string;
21
+ };
17
22
  };
18
23
  type AdapterEmitContext = {
19
24
  appRoot: string;
@@ -39,6 +44,7 @@ interface JuneAdapter {
39
44
  }): AdapterEntry;
40
45
  emit(ctx: AdapterEmitContext): Promise<void>;
41
46
  }
47
+ declare function hasDurableBinding(text: string, file: string, binding: string, className: string): boolean;
42
48
  declare function workers(opts?: {
43
49
  name?: string;
44
50
  domain?: string;
@@ -53,4 +59,4 @@ declare function deno(opts?: {
53
59
  }): JuneAdapter;
54
60
  declare function staticSite(): JuneAdapter;
55
61
  //#endregion
56
- export { AdapterCapabilities, AdapterEmitContext, AdapterEntry, JuneAdapter, ResourcePlan, deno, staticSite, vercel, workers };
62
+ export { AdapterCapabilities, AdapterEmitContext, AdapterEntry, JuneAdapter, ResourcePlan, deno, hasDurableBinding, staticSite, vercel, workers };
package/dist/adapter.js CHANGED
@@ -1,14 +1,51 @@
1
+ import { stripJsonc } from "./tsconfig-jsx.js";
1
2
  import { existsSync } from "node:fs";
2
- import { copyFile, cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
3
- import { dirname, join } from "node:path";
3
+ import { copyFile, cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, join } from "node:path";
4
5
  //#region src/adapter.ts
6
+ function hasDurableBinding(text, file, binding, className) {
7
+ if (file.endsWith(".toml")) {
8
+ const lines = text.split("\n").map((line) => {
9
+ let inString = false;
10
+ for (let i = 0; i < line.length; i++) {
11
+ const ch = line[i];
12
+ if (ch === "\"" && line[i - 1] !== "\\") inString = !inString;
13
+ else if (ch === "#" && !inString) return line.slice(0, i);
14
+ }
15
+ return line;
16
+ });
17
+ let inBindings = false;
18
+ let name;
19
+ let cls;
20
+ const matches = () => name === binding && cls === className;
21
+ for (const line of lines) {
22
+ const t = line.trim();
23
+ if (t.startsWith("[")) {
24
+ if (inBindings && matches()) return true;
25
+ inBindings = /^\[\[\s*durable_objects\.bindings\s*\]\]$/.test(t);
26
+ name = cls = void 0;
27
+ continue;
28
+ }
29
+ if (!inBindings) continue;
30
+ const kv = t.match(/^(name|class_name)\s*=\s*"([^"]*)"/);
31
+ if (kv) kv[1] === "name" ? name = kv[2] : cls = kv[2];
32
+ }
33
+ return inBindings && matches();
34
+ }
35
+ try {
36
+ return (JSON.parse(stripJsonc(text)).durable_objects?.bindings ?? []).some((b) => b.name === binding && b.class_name === className);
37
+ } catch {
38
+ return text.includes(className);
39
+ }
40
+ }
5
41
  function workers(opts) {
6
42
  return {
7
43
  name: "workers",
8
44
  capabilities: {
9
45
  runtime: "edge",
10
46
  persistentConnections: true,
11
- assets: "platform"
47
+ assets: "platform",
48
+ durableObjects: true
12
49
  },
13
50
  conditions: [
14
51
  "source",
@@ -25,7 +62,17 @@ function workers(opts) {
25
62
  };
26
63
  },
27
64
  async emit({ appRoot, outDir, hasAssets, config, plan, defaultName }) {
28
- if (existsSync(join(appRoot, "wrangler.toml")) || existsSync(join(appRoot, "wrangler.jsonc"))) return;
65
+ const own = ["wrangler.toml", "wrangler.jsonc"].map((f) => join(appRoot, f)).find(existsSync);
66
+ if (own) {
67
+ if (plan.agent) {
68
+ const cls = plan.agent.className;
69
+ if (!hasDurableBinding(await readFile(own, "utf8"), own, plan.agent.binding, cls)) {
70
+ const snippet = own.endsWith(".toml") ? `[[durable_objects.bindings]]\nname = "${plan.agent.binding}"\nclass_name = "${cls}"\n\n[[migrations]]\ntag = "v1"\nnew_sqlite_classes = ["${cls}"]` : `"durable_objects": { "bindings": [{ "name": "${plan.agent.binding}", "class_name": "${cls}" }] },\n"migrations": [{ "tag": "v1", "new_sqlite_classes": ["${cls}"] }]`;
71
+ console.warn(`[june build] durable agent built, but ${basename(own)} doesn't bind it — add:\n${snippet}`);
72
+ }
73
+ }
74
+ return;
75
+ }
29
76
  const deployCfg = config.deploy;
30
77
  await writeFile(join(outDir, "wrangler.jsonc"), JSON.stringify({
31
78
  name: opts?.name ?? deployCfg?.name ?? defaultName,
@@ -42,6 +89,16 @@ function workers(opts) {
42
89
  database_name: plan.db.databaseName,
43
90
  database_id: ""
44
91
  }] } : {},
92
+ ...plan.agent ? {
93
+ durable_objects: { bindings: [{
94
+ name: plan.agent.binding,
95
+ class_name: plan.agent.className
96
+ }] },
97
+ migrations: [{
98
+ tag: "v1",
99
+ new_sqlite_classes: [plan.agent.className]
100
+ }]
101
+ } : {},
45
102
  ...opts?.domain ?? deployCfg?.domain ? { routes: [{
46
103
  pattern: opts?.domain ?? deployCfg?.domain,
47
104
  custom_domain: true
@@ -208,4 +265,4 @@ function staticSite() {
208
265
  };
209
266
  }
210
267
  //#endregion
211
- export { deno, staticSite, vercel, workers };
268
+ export { deno, hasDurableBinding, staticSite, vercel, workers };
@@ -0,0 +1,39 @@
1
+ //#region src/agent-compile.d.ts
2
+ declare const AGENT_MODULE_FILE = "_agent.gen.ts";
3
+ type AgentDirScan = {
4
+ dir: string;
5
+ name: string;
6
+ hasConfig: boolean;
7
+ instructions: string | null;
8
+ surfaceVariants: {
9
+ source: string;
10
+ raw: string;
11
+ }[];
12
+ tools: string[];
13
+ skills: {
14
+ name: string;
15
+ raw: string;
16
+ }[];
17
+ channels: string[];
18
+ channelOverlays: {
19
+ source: string;
20
+ raw: string;
21
+ }[];
22
+ connections: string[];
23
+ };
24
+ declare function scanAgentDir(dir: string): AgentDirScan | null;
25
+ type EmitOptions = {
26
+ tsExtensions?: boolean;
27
+ };
28
+ declare function emitAgentModule(scan: AgentDirScan, opts?: EmitOptions): string;
29
+ declare function findAgentDir(root: string, dirName?: string): string | null;
30
+ declare function generateAgentModule(dir: string, opts?: {
31
+ check?: boolean;
32
+ } & EmitOptions): {
33
+ file: string;
34
+ code: string;
35
+ stale: boolean;
36
+ written: boolean;
37
+ } | null;
38
+ //#endregion
39
+ export { AGENT_MODULE_FILE, AgentDirScan, EmitOptions, emitAgentModule, findAgentDir, generateAgentModule, scanAgentDir };
@@ -0,0 +1,145 @@
1
+ import { stripJsonc } from "./tsconfig-jsx.js";
2
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
+ //#region src/agent-compile.ts
5
+ const AGENT_MODULE_FILE = "_agent.gen.ts";
6
+ function list(dir, ext) {
7
+ if (!existsSync(dir)) return [];
8
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(ext) && !e.name.startsWith("_")).map((e) => e.name).sort();
9
+ }
10
+ function scanAgentDir(dir) {
11
+ if (!existsSync(dir)) return null;
12
+ const instructionsFile = join(dir, "instructions.md");
13
+ const scan = {
14
+ dir,
15
+ name: basename(dir),
16
+ hasConfig: existsSync(join(dir, "agent.ts")),
17
+ instructions: existsSync(instructionsFile) ? readFileSync(instructionsFile, "utf8") : null,
18
+ surfaceVariants: readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name.match(/^instructions\.([a-z0-9_-]+)\.md$/)).filter((m) => m !== null).sort((a, b) => a[1].localeCompare(b[1])).map((m) => ({
19
+ source: m[1],
20
+ raw: readFileSync(join(dir, m[0]), "utf8")
21
+ })),
22
+ tools: list(join(dir, "tools"), ".ts").map((f) => `./tools/${f}`),
23
+ skills: list(join(dir, "skills"), ".md").map((f) => ({
24
+ name: basename(f, ".md"),
25
+ raw: readFileSync(join(dir, "skills", f), "utf8")
26
+ })),
27
+ channels: list(join(dir, "channels"), ".ts").map((f) => `./channels/${f}`),
28
+ channelOverlays: list(join(dir, "channels"), ".md").map((f) => ({
29
+ source: basename(f, ".md"),
30
+ raw: readFileSync(join(dir, "channels", f), "utf8")
31
+ })),
32
+ connections: list(join(dir, "connections"), ".ts").map((f) => `./connections/${f}`)
33
+ };
34
+ return !scan.hasConfig && scan.instructions === null && !scan.tools.length && !scan.skills.length && !scan.channels.length && !scan.surfaceVariants.length && !scan.channelOverlays.length && !scan.connections.length ? null : scan;
35
+ }
36
+ function identFor(prefix, spec, taken) {
37
+ const stem = basename(spec).replace(/\.ts$/, "").replace(/[^A-Za-z0-9]/g, "_");
38
+ let id = `${prefix}_${stem}`;
39
+ for (let i = 2; taken.has(id); i++) id = `${prefix}_${stem}_${i}`;
40
+ taken.add(id);
41
+ return id;
42
+ }
43
+ function emitAgentModule(scan, opts) {
44
+ const spec = (s) => JSON.stringify(opts?.tsExtensions ? s : s.replace(/\.ts$/, ""));
45
+ const taken = /* @__PURE__ */ new Set();
46
+ const imports = [];
47
+ const toolIds = scan.tools.map((s) => {
48
+ const id = identFor("tool", s, taken);
49
+ imports.push(`import ${id} from ${spec(s)};`);
50
+ return id;
51
+ });
52
+ const channelIds = scan.channels.map((s) => {
53
+ const id = identFor("channel", s, taken);
54
+ imports.push(`import ${id} from ${spec(s)};`);
55
+ return {
56
+ key: basename(s, ".ts"),
57
+ id
58
+ };
59
+ });
60
+ const connectionIds = scan.connections.map((s) => {
61
+ const id = identFor("connection", s, taken);
62
+ imports.push(`import ${id} from ${spec(s)};`);
63
+ return id;
64
+ });
65
+ if (scan.hasConfig) imports.push(`import config from ${spec("./agent.ts")};`);
66
+ const lines = [
67
+ "// AUTO-GENERATED by June — do not edit. Compiled from this agent/ directory so",
68
+ "// the definition mounts without filesystem discovery (workerd has no fs).",
69
+ "// Regenerate with `june gen` after adding, removing, or editing agent files.",
70
+ ...imports,
71
+ scan.skills.length ? `import { parseSkill, type AgentModule } from "@junejs/core/agent-config";` : `import type { AgentModule } from "@junejs/core/agent-config";`,
72
+ ""
73
+ ];
74
+ if (!scan.hasConfig) lines.push(`const config = { name: ${JSON.stringify(scan.name)} };`, "");
75
+ lines.push("export const agentModule: AgentModule = {", " config,", ` instructions: ${JSON.stringify(scan.instructions ?? "")},`, scan.surfaceVariants.length ? ` surfaceInstructions: {\n${scan.surfaceVariants.map((v) => ` ${JSON.stringify(v.source)}: ${JSON.stringify(v.raw)},`).join("\n")}\n },` : " surfaceInstructions: {},", ` tools: [${toolIds.join(", ")}],`, scan.skills.length ? ` skills: [\n${scan.skills.map((s) => ` parseSkill(${JSON.stringify(s.name)}, ${JSON.stringify(s.raw)}),`).join("\n")}\n ],` : " skills: [],", channelIds.length ? ` channels: { ${channelIds.map((c) => `${JSON.stringify(c.key)}: ${c.id}`).join(", ")} },` : " channels: {},", scan.channelOverlays.length ? ` channelInstructions: {\n${scan.channelOverlays.map((o) => ` ${JSON.stringify(o.source)}: ${JSON.stringify(o.raw)},`).join("\n")}\n },` : " channelInstructions: {},", ` connections: [${connectionIds.join(", ")}],`, "};", "export default agentModule;", "");
76
+ return lines.join("\n");
77
+ }
78
+ function findAgentDir(root, dirName = "agent") {
79
+ for (const candidate of [join(root, "app", dirName), join(root, dirName)]) if (existsSync(candidate)) return candidate;
80
+ return null;
81
+ }
82
+ function readTsconfig(path) {
83
+ try {
84
+ return JSON.parse(stripJsonc(readFileSync(path, "utf8")));
85
+ } catch {
86
+ return;
87
+ }
88
+ }
89
+ function resolveExtends(fromDir, spec) {
90
+ const withJson = (p) => existsSync(p) ? p : !p.endsWith(".json") && existsSync(`${p}.json`) ? `${p}.json` : void 0;
91
+ if (spec.startsWith(".")) return withJson(resolve(fromDir, spec));
92
+ for (let d = fromDir;;) {
93
+ const candidate = withJson(join(d, "node_modules", spec));
94
+ if (candidate) return candidate;
95
+ const parent = dirname(d);
96
+ if (parent === d) return void 0;
97
+ d = parent;
98
+ }
99
+ }
100
+ function resolveAllowTsExtensions(path, depth) {
101
+ if (depth > 8) return void 0;
102
+ const tc = readTsconfig(path);
103
+ if (!tc) return void 0;
104
+ const own = tc.compilerOptions?.allowImportingTsExtensions;
105
+ if (own !== void 0) return own;
106
+ const bases = typeof tc.extends === "string" ? [tc.extends] : [...tc.extends ?? []].reverse();
107
+ for (const spec of bases) {
108
+ const base = resolveExtends(dirname(path), spec);
109
+ const inherited = base ? resolveAllowTsExtensions(base, depth + 1) : void 0;
110
+ if (inherited !== void 0) return inherited;
111
+ }
112
+ }
113
+ function sniffTsExtensions(dir) {
114
+ for (let d = dir;;) {
115
+ const candidate = join(d, "tsconfig.json");
116
+ if (existsSync(candidate)) return resolveAllowTsExtensions(candidate, 0) ?? false;
117
+ const parent = dirname(d);
118
+ if (parent === d) return false;
119
+ d = parent;
120
+ }
121
+ }
122
+ function generateAgentModule(dir, opts) {
123
+ const scan = scanAgentDir(dir);
124
+ if (!scan) return null;
125
+ for (const o of scan.channelOverlays) console.warn(`[june] deprecated: channels/${o.source}.md — move it to instructions.${o.source}.md at the agent root (per-surface behavior is agent-level; see junebuild/june#149).`);
126
+ for (const e of readdirSync(dir, { withFileTypes: true })) if (e.isFile() && /^instructions\..+\.md$/.test(e.name) && !/^instructions\.[a-z0-9_-]+\.md$/.test(e.name)) console.warn(`[june] ${e.name}: unrecognized instructions variant — only single-segment sources (instructions.<source>.md) are parsed today; locale variants are a planned follow-up (junebuild/june#149). This file is ignored.`);
127
+ const code = emitAgentModule(scan, { tsExtensions: opts?.tsExtensions ?? sniffTsExtensions(dir) });
128
+ const file = join(dir, AGENT_MODULE_FILE);
129
+ const stale = (existsSync(file) ? readFileSync(file, "utf8") : null) !== code;
130
+ if (opts?.check) return {
131
+ file,
132
+ code,
133
+ stale,
134
+ written: false
135
+ };
136
+ if (stale) writeFileSync(file, code);
137
+ return {
138
+ file,
139
+ code,
140
+ stale,
141
+ written: stale
142
+ };
143
+ }
144
+ //#endregion
145
+ export { AGENT_MODULE_FILE, emitAgentModule, findAgentDir, generateAgentModule, scanAgentDir };
@@ -1,5 +1,6 @@
1
- import { AgentDefinition } from "@junejs/core/agent-config";
1
+ import { AgentDefinition, AgentModule } from "@junejs/core/agent-config";
2
2
  //#region src/agent-discover.d.ts
3
+ declare function discoverAgentModule(dir: string): Promise<AgentModule>;
3
4
  declare function discoverAgent(dir: string): Promise<AgentDefinition>;
4
5
  //#endregion
5
- export { discoverAgent };
6
+ export { discoverAgent, discoverAgentModule };
@@ -2,42 +2,24 @@ import { existsSync } from "node:fs";
2
2
  import { readFile, readdir } from "node:fs/promises";
3
3
  import { basename, extname, join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
- import { defineAgent, resolveChannel } from "@junejs/core/agent-config";
6
- import { connectAll } from "@junejs/core/connections";
5
+ import { assembleAgent, parseSkill } from "@junejs/core/agent-config";
7
6
  //#region src/agent-discover.ts
8
7
  async function scan(dir, ext) {
9
8
  if (!existsSync(dir)) return [];
10
- return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isFile() && extname(e.name) === ext).map((e) => join(dir, e.name)).sort();
9
+ return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isFile() && extname(e.name) === ext && !e.name.startsWith("_")).map((e) => join(dir, e.name)).sort();
11
10
  }
12
- function parseSkill(name, text) {
13
- if (text.startsWith("---")) {
14
- const end = text.indexOf("\n---", 3);
15
- if (end !== -1) {
16
- const front = text.slice(3, end);
17
- const body = text.slice(end + 4).replace(/^\s*\n/, "");
18
- const meta = {};
19
- for (const line of front.split("\n")) {
20
- const m = line.match(/^\s*(\w+)\s*:\s*(.+?)\s*$/);
21
- if (m) meta[m[1]] = m[2];
22
- }
23
- return {
24
- name: meta.name ?? name,
25
- description: meta.description ?? "",
26
- body
27
- };
28
- }
29
- }
30
- return {
31
- name,
32
- description: (text.split("\n").find((l) => l.trim()) ?? "").replace(/^#\s*/, ""),
33
- body: text
34
- };
35
- }
36
- async function discoverAgent(dir) {
11
+ async function discoverAgentModule(dir) {
37
12
  const configFile = join(dir, "agent.ts");
38
13
  const config = existsSync(configFile) ? (await import(pathToFileURL(configFile).href)).default : { name: basename(dir) };
39
14
  const instructionsFile = join(dir, "instructions.md");
40
- const instructions = existsSync(instructionsFile) ? await readFile(instructionsFile, "utf8") : config.instructions ?? "";
15
+ const instructions = existsSync(instructionsFile) ? await readFile(instructionsFile, "utf8") : "";
16
+ const surfaceInstructions = {};
17
+ if (existsSync(dir)) for (const e of await readdir(dir, { withFileTypes: true })) {
18
+ if (!e.isFile() || !/^instructions\..+\.md$/.test(e.name)) continue;
19
+ const m = e.name.match(/^instructions\.([a-z0-9_-]+)\.md$/);
20
+ if (m) surfaceInstructions[m[1]] = await readFile(join(dir, e.name), "utf8");
21
+ else console.warn(`[june] ${e.name}: unrecognized instructions variant — only single-segment sources (instructions.<source>.md) are parsed today; locale variants are a planned follow-up (junebuild/june#149). This file is ignored.`);
22
+ }
41
23
  const tools = [];
42
24
  for (const f of await scan(join(dir, "tools"), ".ts")) {
43
25
  const mod = await import(pathToFileURL(f).href);
@@ -45,31 +27,35 @@ async function discoverAgent(dir) {
45
27
  }
46
28
  const skills = [];
47
29
  for (const f of await scan(join(dir, "skills"), ".md")) skills.push(parseSkill(basename(f, ".md"), await readFile(f, "utf8")));
48
- const channels = [];
30
+ const channels = {};
49
31
  for (const f of await scan(join(dir, "channels"), ".ts")) {
50
32
  const mod = await import(pathToFileURL(f).href);
51
- if (mod.default) channels.push(resolveChannel(mod.default, process.env));
33
+ if (mod.default) channels[basename(f, ".ts")] = mod.default;
52
34
  }
53
- const connectionDefs = [];
35
+ const channelInstructions = {};
36
+ for (const f of await scan(join(dir, "channels"), ".md")) {
37
+ const source = basename(f, ".md");
38
+ console.warn(`[june] deprecated: channels/${source}.md — move it to instructions.${source}.md at the agent root (per-surface behavior is agent-level; see junebuild/june#149).`);
39
+ channelInstructions[source] = await readFile(f, "utf8");
40
+ }
41
+ const connections = [];
54
42
  for (const f of await scan(join(dir, "connections"), ".ts")) {
55
43
  const mod = await import(pathToFileURL(f).href);
56
- if (mod.default) connectionDefs.push(mod.default);
44
+ if (mod.default) connections.push(mod.default);
57
45
  }
58
- const { actions: connectionActions, report: connections } = connectionDefs.length ? await connectAll(connectionDefs) : {
59
- actions: [],
60
- report: []
61
- };
62
- tools.push(...connectionActions);
63
- return defineAgent({
64
- name: config.name,
65
- model: config.model,
66
- description: config.description,
46
+ return {
47
+ config,
67
48
  instructions,
49
+ surfaceInstructions,
68
50
  tools,
69
51
  skills,
70
52
  channels,
53
+ channelInstructions,
71
54
  connections
72
- });
55
+ };
56
+ }
57
+ async function discoverAgent(dir) {
58
+ return assembleAgent(await discoverAgentModule(dir), process.env);
73
59
  }
74
60
  //#endregion
75
- export { discoverAgent };
61
+ export { discoverAgent, discoverAgentModule };
@@ -1,5 +1,6 @@
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 { ChannelPolicy, InboundEvent, Model, Msg, ProactiveTrigger, SessionStore, Tool, TurnError, TurnEvent, TurnFailurePhase } from "@junejs/core/agent-runtime";
3
+ import { Connection } from "@junejs/core/connections";
3
4
  import { Resources } from "@junejs/core/resources";
4
5
  //#region src/agent-durable.d.ts
5
6
  type SqlStorageValue = ArrayBuffer | string | number | null;
@@ -47,10 +48,11 @@ type DoAgentDef = {
47
48
  model: Model;
48
49
  tools: Tool[];
49
50
  instructions?: string;
50
- channelInstructions?: Record<string, string>;
51
+ channelInstructions?: Record<string, string | ChannelPolicy>;
51
52
  channels?: (Channel | ChannelFactory)[];
52
53
  env?: unknown;
53
- resources?: Resources;
54
+ resources?: Resources | ((env: unknown) => Promise<Resources> | Resources);
55
+ connections?: Connection[];
54
56
  services?: unknown;
55
57
  onTurnError?: (failure: {
56
58
  turnId: string;
@@ -69,8 +71,15 @@ declare class AgentDurableObject {
69
71
  private readonly tools;
70
72
  private readonly channels;
71
73
  private readonly channelInstructions?;
72
- private readonly resources;
74
+ private readonly resourcesInput;
75
+ private resourcesOpened;
76
+ private readonly connectionsInput;
77
+ private connectionsOpened;
78
+ private readonly doEnv;
73
79
  private readonly services;
80
+ private resolveResources;
81
+ private resolveConnections;
82
+ private ready;
74
83
  constructor(state: DurableObjectState, def: DoAgentDef);
75
84
  private resolveSession;
76
85
  turn(input: {
@@ -1,7 +1,8 @@
1
1
  import { isolateLocal } from "./isolate-local.js";
2
2
  import { assertCoreRuntimeVersion } from "./core-version.js";
3
- import { DeliverUnsupportedError, channelDispatch, resolveChannel } from "@junejs/core/agent-config";
3
+ import { DeliverUnsupportedError, actionToTool, channelDispatch, resolveChannel } from "@junejs/core/agent-config";
4
4
  import { AgentSession, ResumeAuthorizationError, withSystem } from "@junejs/core/agent-runtime";
5
+ import { connectAll } from "@junejs/core/connections";
5
6
  import { ensureScope, runInScope } from "@junejs/db";
6
7
  //#region src/agent-durable.ts
7
8
  var DoSessionStore = class {
@@ -99,13 +100,40 @@ var AgentDurableObject = class {
99
100
  tools;
100
101
  channels;
101
102
  channelInstructions;
102
- resources;
103
+ resourcesInput;
104
+ resourcesOpened;
105
+ connectionsInput;
106
+ connectionsOpened;
107
+ doEnv;
103
108
  services;
109
+ resolveResources() {
110
+ return this.resourcesOpened ??= Promise.resolve(typeof this.resourcesInput === "function" ? this.resourcesInput(this.doEnv) : this.resourcesInput ?? {});
111
+ }
112
+ resolveConnections() {
113
+ return this.connectionsOpened ??= (async () => {
114
+ if (!this.connectionsInput?.length) return;
115
+ const { actions, report } = await connectAll(this.connectionsInput);
116
+ for (const r of report) if (r.error) console.error(`[june] agent "${this.name}": connection "${r.name}" (${r.url}) failed: ${r.error}`);
117
+ const seen = new Set(this.tools.map((t) => t.spec.name));
118
+ for (const a of actions) {
119
+ const tool = actionToTool(a);
120
+ if (seen.has(tool.spec.name)) throw new Error(`AgentDurableObject(${this.name}): duplicate tool name "${tool.spec.name}" from a connection — rename so dispatch is unambiguous.`);
121
+ seen.add(tool.spec.name);
122
+ this.tools.push(tool);
123
+ }
124
+ })();
125
+ }
126
+ async ready() {
127
+ const [resources] = await Promise.all([this.resolveResources(), this.resolveConnections()]);
128
+ return resources;
129
+ }
104
130
  constructor(state, def) {
105
131
  assertCoreRuntimeVersion(`AgentDurableObject(${def.name ?? "agent"})`);
106
132
  const store = new DoSessionStore(state.storage);
107
133
  const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
108
- this.resources = def.resources ?? {};
134
+ this.resourcesInput = def.resources;
135
+ this.connectionsInput = def.connections;
136
+ this.doEnv = def.env;
109
137
  this.services = def.services;
110
138
  const name = def.name ?? "agent";
111
139
  const sink = new InProcEventSink();
@@ -145,6 +173,10 @@ var AgentDurableObject = class {
145
173
  this.tools = tools;
146
174
  this.channels = channels;
147
175
  this.channelInstructions = def.channelInstructions;
176
+ if (def.channelInstructions) {
177
+ const names = new Set(channels.map((c) => c.name));
178
+ for (const source of Object.keys(def.channelInstructions)) if (!names.has(source)) console.warn(`[june] agent "${name}": surface policy "${source}" matches no channel wired into this DO (${[...names].join(", ") || "none"}) — it will never fire.`);
179
+ }
148
180
  }
149
181
  resolveSession(key) {
150
182
  if (key !== void 0) assertSessionKey(key);
@@ -162,17 +194,19 @@ var AgentDurableObject = class {
162
194
  }
163
195
  async turn(input) {
164
196
  await ensureScope();
197
+ const resources = await this.ready();
165
198
  const session = this.resolveSession(input.session);
166
199
  return runInScope({
167
- resources: this.resources,
200
+ resources,
168
201
  services: this.services
169
202
  }, () => session.turn(input));
170
203
  }
171
204
  async start(input) {
172
205
  await ensureScope();
206
+ const resources = await this.ready();
173
207
  const session = this.resolveSession(input.session);
174
208
  return runInScope({
175
- resources: this.resources,
209
+ resources,
176
210
  services: this.services
177
211
  }, () => session.start(input));
178
212
  }
@@ -198,12 +232,13 @@ var AgentDurableObject = class {
198
232
  deliverChannel = this.channels.find((c) => c.name === event.source);
199
233
  if (!deliverChannel?.deliver) return Response.json({ error: `agent "${this.name}": no deliver()-capable channel named "${event.source}" is wired into this DO (DoAgentDef.channels) — the turn was NOT started` }, { status: 501 });
200
234
  }
235
+ const resources = await this.ready();
201
236
  let session;
202
237
  let started;
203
238
  try {
204
239
  session = this.resolveSession(key);
205
240
  started = runInScope({
206
- resources: this.resources,
241
+ resources,
207
242
  services: this.services
208
243
  }, () => session.start({
209
244
  userText,
@@ -227,7 +262,7 @@ var AgentDurableObject = class {
227
262
  recipientTeamId: event.teamId
228
263
  };
229
264
  Promise.resolve().then(() => runInScope({
230
- resources: this.resources,
265
+ resources,
231
266
  services: this.services
232
267
  }, () => deliverChannel.deliver(target, events, { session: this.sessionKey }))).catch(logRenderFailure).finally(() => {
233
268
  it.return?.();
@@ -250,11 +285,12 @@ var AgentDurableObject = class {
250
285
  resumeChannel = this.channels.find((c) => c.name === source);
251
286
  if (!resumeChannel?.deliverResume) return Response.json({ error: `agent "${this.name}": no deliverResume()-capable channel named "${source}" is wired into this DO (DoAgentDef.channels) — the answer was NOT applied` }, { status: 501 });
252
287
  }
288
+ const resources = await this.ready();
253
289
  let session;
254
290
  try {
255
291
  session = this.resolveSession(key);
256
292
  runInScope({
257
- resources: this.resources,
293
+ resources,
258
294
  services: this.services
259
295
  }, () => session.resume(turnId, inputId, input, { by }));
260
296
  } catch (err) {
@@ -267,7 +303,7 @@ var AgentDurableObject = class {
267
303
  const it = observeTurnEvents(session, turnId)[Symbol.asyncIterator]();
268
304
  const events = { [Symbol.asyncIterator]: () => it };
269
305
  Promise.resolve().then(() => runInScope({
270
- resources: this.resources,
306
+ resources,
271
307
  services: this.services
272
308
  }, () => resumeChannel.deliverResume(target, events, { session: this.sessionKey }))).catch(logRenderFailure).finally(() => {
273
309
  it.return?.();