@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 +7 -1
- package/dist/adapter.js +62 -5
- package/dist/agent-compile.d.ts +39 -0
- package/dist/agent-compile.js +145 -0
- package/dist/agent-discover.d.ts +3 -2
- package/dist/agent-discover.js +30 -44
- package/dist/agent-durable.d.ts +13 -4
- package/dist/agent-durable.js +45 -9
- package/dist/build.js +74 -9
- package/dist/client-bundle.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/tsconfig-jsx.d.ts +2 -1
- package/dist/tsconfig-jsx.js +1 -1
- package/dist/worker.d.ts +9 -4
- package/dist/worker.js +28 -9
- package/package.json +13 -3
- package/src/adapter.ts +84 -3
- package/src/agent-compile.ts +271 -0
- package/src/agent-discover.ts +71 -60
- package/src/agent-durable.ts +92 -16
- package/src/build.ts +127 -10
- package/src/index.ts +8 -0
- package/src/tsconfig-jsx.ts +2 -2
- package/src/worker.ts +66 -18
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
|
-
|
|
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 };
|
package/dist/agent-discover.d.ts
CHANGED
|
@@ -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 };
|
package/dist/agent-discover.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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") :
|
|
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
|
|
33
|
+
if (mod.default) channels[basename(f, ".ts")] = mod.default;
|
|
52
34
|
}
|
|
53
|
-
const
|
|
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)
|
|
44
|
+
if (mod.default) connections.push(mod.default);
|
|
57
45
|
}
|
|
58
|
-
|
|
59
|
-
|
|
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 };
|
package/dist/agent-durable.d.ts
CHANGED
|
@@ -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
|
|
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: {
|
package/dist/agent-durable.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
306
|
+
resources,
|
|
271
307
|
services: this.services
|
|
272
308
|
}, () => resumeChannel.deliverResume(target, events, { session: this.sessionKey }))).catch(logRenderFailure).finally(() => {
|
|
273
309
|
it.return?.();
|