@junejs/server 1.0.0-dev.17 → 1.0.0-dev.20
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-DcZ-W1-7.d.ts +62 -0
- package/dist/adapter.d.ts +1 -61
- package/dist/agent-compile.d.ts +4 -0
- package/dist/agent-compile.js +8 -2
- package/dist/agent-discover.js +13 -1
- package/dist/agent-durable.d.ts +8 -2
- package/dist/agent-durable.js +31 -5
- package/dist/build.d.ts +5 -26
- package/dist/build.js +13 -279
- package/dist/config-freeze-DwI-Rmvq.d.ts +17 -0
- package/dist/config-freeze.d.ts +2 -0
- package/dist/config-freeze.js +51 -0
- package/dist/connection-auth.d.ts +39 -0
- package/dist/connection-auth.js +31 -0
- package/dist/content-freeze-CyiW2fRK.d.ts +4 -0
- package/dist/content-freeze.d.ts +2 -0
- package/dist/content-freeze.js +83 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +7 -2
- package/dist/manifest-gcNWqWoI.d.ts +11 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +111 -0
- package/dist/route-scan-DMlwoESy.d.ts +13 -0
- package/dist/route-scan.d.ts +2 -0
- package/dist/route-scan.js +55 -0
- package/dist/worker-BkFHGtxp.d.ts +61 -0
- package/dist/worker.d.ts +2 -56
- package/dist/worker.js +28 -9
- package/package.json +2 -2
- package/src/agent-compile.ts +26 -0
- package/src/agent-discover.ts +44 -15
- package/src/agent-durable.ts +63 -8
- package/src/build.ts +31 -443
- package/src/config-freeze.ts +104 -0
- package/src/connection-auth.ts +101 -0
- package/src/content-freeze.ts +151 -0
- package/src/index.ts +14 -0
- package/src/manifest.ts +128 -0
- package/src/route-scan.ts +84 -0
- package/src/worker.ts +66 -18
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { findMiddlewareFile } from "./router.js";
|
|
2
|
+
import { resolveBoundary } from "./segment.js";
|
|
3
|
+
import { freezeConfig } from "./config-freeze.js";
|
|
4
|
+
import { scanAppRoutes } from "./route-scan.js";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { basename, join } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { routeFromModule } from "@junejs/core/route";
|
|
9
|
+
//#region src/manifest.ts
|
|
10
|
+
async function importLayout(file) {
|
|
11
|
+
const mod = await import(pathToFileURL(file).href);
|
|
12
|
+
return typeof mod.default === "function" ? {
|
|
13
|
+
component: mod.default,
|
|
14
|
+
boundary: mod.segmentBoundary === true
|
|
15
|
+
} : null;
|
|
16
|
+
}
|
|
17
|
+
async function buildManifest(appRoot) {
|
|
18
|
+
const appDir = join(appRoot, "app");
|
|
19
|
+
const frozen = await freezeConfig(appRoot);
|
|
20
|
+
const scanned = await scanAppRoutes(appRoot);
|
|
21
|
+
const layoutCache = /* @__PURE__ */ new Map();
|
|
22
|
+
const loadCached = async (f) => {
|
|
23
|
+
if (!layoutCache.has(f)) layoutCache.set(f, await importLayout(f));
|
|
24
|
+
return layoutCache.get(f) ?? null;
|
|
25
|
+
};
|
|
26
|
+
const componentsFor = async (files) => {
|
|
27
|
+
const items = [];
|
|
28
|
+
for (const f of files) {
|
|
29
|
+
const c = await loadCached(f);
|
|
30
|
+
items.push({
|
|
31
|
+
file: f,
|
|
32
|
+
entry: c?.component ?? null,
|
|
33
|
+
boundary: !!c?.boundary
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return resolveBoundary(items);
|
|
37
|
+
};
|
|
38
|
+
const loadingCache = /* @__PURE__ */ new Map();
|
|
39
|
+
const loadingFor = async (file) => {
|
|
40
|
+
if (!file) return void 0;
|
|
41
|
+
if (!loadingCache.has(file)) {
|
|
42
|
+
const loaded = await importLayout(file);
|
|
43
|
+
loadingCache.set(file, loaded ? loaded.component : null);
|
|
44
|
+
}
|
|
45
|
+
return loadingCache.get(file) ?? void 0;
|
|
46
|
+
};
|
|
47
|
+
const routes = {};
|
|
48
|
+
const dynamicRoutes = [];
|
|
49
|
+
const resourceRoutes = [];
|
|
50
|
+
const layoutChains = {};
|
|
51
|
+
const layoutBoundaries = {};
|
|
52
|
+
const loadings = {};
|
|
53
|
+
for (const r of scanned) {
|
|
54
|
+
const mod = await import(pathToFileURL(r.file).href);
|
|
55
|
+
if (r.resource) {
|
|
56
|
+
const handler = mod.default;
|
|
57
|
+
if (typeof handler === "function") resourceRoutes.push({
|
|
58
|
+
pattern: r.path,
|
|
59
|
+
handler
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const def = routeFromModule(mod);
|
|
64
|
+
if (!def) continue;
|
|
65
|
+
const { chain, boundaryIndex, key } = await componentsFor(r.layouts);
|
|
66
|
+
const loading = await loadingFor(r.loading);
|
|
67
|
+
if (r.dynamic) {
|
|
68
|
+
dynamicRoutes.push({
|
|
69
|
+
pattern: r.path,
|
|
70
|
+
def
|
|
71
|
+
});
|
|
72
|
+
layoutChains[r.path] = chain;
|
|
73
|
+
} else {
|
|
74
|
+
routes[r.path] = def;
|
|
75
|
+
layoutChains[r.path] = chain;
|
|
76
|
+
}
|
|
77
|
+
if (boundaryIndex !== null && key !== null) layoutBoundaries[r.path] = {
|
|
78
|
+
index: boundaryIndex,
|
|
79
|
+
key
|
|
80
|
+
};
|
|
81
|
+
if (loading) loadings[r.path] = loading;
|
|
82
|
+
}
|
|
83
|
+
let extra;
|
|
84
|
+
const extraFile = findMiddlewareFile(appDir);
|
|
85
|
+
if (extraFile) {
|
|
86
|
+
const mod = await import(pathToFileURL(extraFile).href);
|
|
87
|
+
if (typeof mod.default === "function") extra = mod.default;
|
|
88
|
+
}
|
|
89
|
+
let agentName;
|
|
90
|
+
const agentDir = join(appDir, frozen.agent.runtime.dir);
|
|
91
|
+
if (frozen.agent.runtime.enabled && existsSync(agentDir)) {
|
|
92
|
+
const cfgFile = join(agentDir, "agent.ts");
|
|
93
|
+
agentName = (existsSync(cfgFile) ? (await import(pathToFileURL(cfgFile).href)).default : void 0)?.name ?? basename(agentDir);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
routes,
|
|
97
|
+
dynamicRoutes,
|
|
98
|
+
resourceRoutes,
|
|
99
|
+
layoutChains,
|
|
100
|
+
layoutBoundaries,
|
|
101
|
+
loadings,
|
|
102
|
+
document: frozen.document,
|
|
103
|
+
agent: frozen.agent,
|
|
104
|
+
agentName,
|
|
105
|
+
i18n: frozen.i18n,
|
|
106
|
+
earlyHints: frozen.earlyHints,
|
|
107
|
+
extra
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { buildManifest, importLayout };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/route-scan.d.ts
|
|
2
|
+
type RouteEntry = {
|
|
3
|
+
path: string;
|
|
4
|
+
file: string;
|
|
5
|
+
dynamic: boolean;
|
|
6
|
+
resource?: boolean;
|
|
7
|
+
layouts: string[];
|
|
8
|
+
loading?: string;
|
|
9
|
+
};
|
|
10
|
+
declare function scanRoutes(appDir: string, dir?: string, layouts?: string[], out?: RouteEntry[], loading?: string): Promise<RouteEntry[]>;
|
|
11
|
+
declare function scanAppRoutes(appRoot: string): Promise<RouteEntry[]>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { scanAppRoutes as n, scanRoutes as r, RouteEntry as t };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { basename, join, relative, sep } from "node:path";
|
|
4
|
+
//#region src/route-scan.ts
|
|
5
|
+
const PAGE_BASENAMES = /* @__PURE__ */ new Set(["page", "index"]);
|
|
6
|
+
const ROUTE_EXTS = [
|
|
7
|
+
".tsx",
|
|
8
|
+
".jsx",
|
|
9
|
+
".ts",
|
|
10
|
+
".js"
|
|
11
|
+
];
|
|
12
|
+
const isRouteGroup = (name) => /^\(.+\)$/.test(name);
|
|
13
|
+
function segmentFile(dir, base) {
|
|
14
|
+
return ROUTE_EXTS.map((e) => join(dir, `${base}${e}`)).find(existsSync);
|
|
15
|
+
}
|
|
16
|
+
async function scanRoutes(appDir, dir = appDir, layouts = [], out = [], loading) {
|
|
17
|
+
const ownLayout = segmentFile(dir, "layout");
|
|
18
|
+
const chain = ownLayout ? [...layouts, ownLayout] : layouts;
|
|
19
|
+
const nearestLoading = segmentFile(dir, "loading") ?? loading;
|
|
20
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
21
|
+
for (const e of entries) {
|
|
22
|
+
if (e.name.startsWith("_") || e.name.startsWith(".")) continue;
|
|
23
|
+
const full = join(dir, e.name);
|
|
24
|
+
if (e.isDirectory()) {
|
|
25
|
+
await scanRoutes(appDir, full, chain, out, nearestLoading);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const ext = e.name.match(/\.[^.]+$/)?.[0] ?? "";
|
|
29
|
+
if (!ROUTE_EXTS.includes(ext)) continue;
|
|
30
|
+
const base = basename(e.name, ext);
|
|
31
|
+
const resource = base === "route";
|
|
32
|
+
if (!PAGE_BASENAMES.has(base) && !resource) continue;
|
|
33
|
+
const relDir = relative(appDir, dir);
|
|
34
|
+
const path = "/" + (relDir === "" ? [] : relDir.split(sep).filter((s) => !isRouteGroup(s))).join("/");
|
|
35
|
+
out.push({
|
|
36
|
+
path: path === "/" ? "/" : path,
|
|
37
|
+
file: full,
|
|
38
|
+
dynamic: /\[.+\]/.test(path),
|
|
39
|
+
resource,
|
|
40
|
+
layouts: chain,
|
|
41
|
+
loading: nearestLoading
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
async function scanAppRoutes(appRoot) {
|
|
47
|
+
const appDir = join(appRoot, "app");
|
|
48
|
+
const juneRoutesDir = join(appRoot, ".june", "routes");
|
|
49
|
+
const appRoutes = await scanRoutes(appDir);
|
|
50
|
+
const frameworkRoutes = existsSync(juneRoutesDir) ? await scanRoutes(juneRoutesDir) : [];
|
|
51
|
+
const appPaths = new Set(appRoutes.map((r) => r.path));
|
|
52
|
+
return [...appRoutes, ...frameworkRoutes.filter((r) => !appPaths.has(r.path))].sort((a, b) => a.path.localeCompare(b.path));
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { scanAppRoutes, scanRoutes };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { l as ResourceHandler, n as LayoutComponent, r as LoadingComponent, t as ExtraHandler } from "./pipeline-cCR6MGNK.js";
|
|
2
|
+
import { Channel, ChannelFactory } from "@junejs/core/agent-config";
|
|
3
|
+
import { BrandedRoute } from "@junejs/core/route";
|
|
4
|
+
import { AgentConfig } from "@junejs/core/config";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { DocumentConfig } from "@junejs/core/document";
|
|
7
|
+
import { I18nConfig } from "@junejs/core/i18n";
|
|
8
|
+
import { Resources } from "@junejs/core/resources";
|
|
9
|
+
//#region src/worker.d.ts
|
|
10
|
+
type WorkerExecutionContext = {
|
|
11
|
+
waitUntil?(p: Promise<unknown>): void;
|
|
12
|
+
};
|
|
13
|
+
type WorkerManifest = {
|
|
14
|
+
routes: Record<string, BrandedRoute>;
|
|
15
|
+
dynamicRoutes?: Array<{
|
|
16
|
+
pattern: string;
|
|
17
|
+
def: BrandedRoute;
|
|
18
|
+
}>;
|
|
19
|
+
resourceRoutes?: Array<{
|
|
20
|
+
pattern: string;
|
|
21
|
+
handler: ResourceHandler;
|
|
22
|
+
}>;
|
|
23
|
+
layoutChains?: Record<string, LayoutComponent[]>;
|
|
24
|
+
layoutBoundaries?: Record<string, {
|
|
25
|
+
index: number;
|
|
26
|
+
key: string;
|
|
27
|
+
}>;
|
|
28
|
+
loadings?: Record<string, LoadingComponent>;
|
|
29
|
+
document: DocumentConfig;
|
|
30
|
+
agent: AgentConfig;
|
|
31
|
+
agentName?: string;
|
|
32
|
+
agentChannels?: (Channel | ChannelFactory)[];
|
|
33
|
+
i18n?: I18nConfig;
|
|
34
|
+
earlyHints?: string[];
|
|
35
|
+
htmlCacheControl?: string;
|
|
36
|
+
notFound?: React.ComponentType<{
|
|
37
|
+
pathname: string;
|
|
38
|
+
}>;
|
|
39
|
+
extra?: ExtraHandler;
|
|
40
|
+
resources?: (env?: unknown) => Promise<Resources> | Resources;
|
|
41
|
+
services?: (env?: unknown) => unknown;
|
|
42
|
+
};
|
|
43
|
+
declare function createWorker(manifest: WorkerManifest): {
|
|
44
|
+
fetch(request: Request, env?: unknown, ctx?: WorkerExecutionContext): Promise<Response>;
|
|
45
|
+
};
|
|
46
|
+
type AssetEnv = {
|
|
47
|
+
ASSETS?: {
|
|
48
|
+
fetch(request: Request): Promise<Response>;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
type FetchPipeline = {
|
|
52
|
+
fetch(request: Request, env?: unknown, ctx?: WorkerExecutionContext): Promise<Response>;
|
|
53
|
+
};
|
|
54
|
+
declare function withAssets(pipeline: FetchPipeline, opts?: {
|
|
55
|
+
link?: string | null;
|
|
56
|
+
}): {
|
|
57
|
+
fetch(request: Request, env?: AssetEnv, ctx?: WorkerExecutionContext): Promise<Response>;
|
|
58
|
+
};
|
|
59
|
+
declare function withDenoAssets(pipeline: FetchPipeline): (request: Request) => Promise<Response>;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { withDenoAssets as a, withAssets as i, WorkerManifest as n, createWorker as r, WorkerExecutionContext as t };
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,56 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { AgentConfig } from "@junejs/core/config";
|
|
4
|
-
import React from "react";
|
|
5
|
-
import { DocumentConfig } from "@junejs/core/document";
|
|
6
|
-
import { I18nConfig } from "@junejs/core/i18n";
|
|
7
|
-
import { Resources } from "@junejs/core/resources";
|
|
8
|
-
//#region src/worker.d.ts
|
|
9
|
-
type WorkerManifest = {
|
|
10
|
-
routes: Record<string, BrandedRoute>;
|
|
11
|
-
dynamicRoutes?: Array<{
|
|
12
|
-
pattern: string;
|
|
13
|
-
def: BrandedRoute;
|
|
14
|
-
}>;
|
|
15
|
-
resourceRoutes?: Array<{
|
|
16
|
-
pattern: string;
|
|
17
|
-
handler: ResourceHandler;
|
|
18
|
-
}>;
|
|
19
|
-
layoutChains?: Record<string, LayoutComponent[]>;
|
|
20
|
-
layoutBoundaries?: Record<string, {
|
|
21
|
-
index: number;
|
|
22
|
-
key: string;
|
|
23
|
-
}>;
|
|
24
|
-
loadings?: Record<string, LoadingComponent>;
|
|
25
|
-
document: DocumentConfig;
|
|
26
|
-
agent: AgentConfig;
|
|
27
|
-
agentName?: string;
|
|
28
|
-
i18n?: I18nConfig;
|
|
29
|
-
earlyHints?: string[];
|
|
30
|
-
htmlCacheControl?: string;
|
|
31
|
-
notFound?: React.ComponentType<{
|
|
32
|
-
pathname: string;
|
|
33
|
-
}>;
|
|
34
|
-
extra?: ExtraHandler;
|
|
35
|
-
resources?: (env?: unknown) => Promise<Resources> | Resources;
|
|
36
|
-
services?: (env?: unknown) => unknown;
|
|
37
|
-
};
|
|
38
|
-
declare function createWorker(manifest: WorkerManifest): {
|
|
39
|
-
fetch(request: Request, env?: unknown): Promise<Response>;
|
|
40
|
-
};
|
|
41
|
-
type AssetEnv = {
|
|
42
|
-
ASSETS?: {
|
|
43
|
-
fetch(request: Request): Promise<Response>;
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
type FetchPipeline = {
|
|
47
|
-
fetch(request: Request, env?: unknown): Promise<Response>;
|
|
48
|
-
};
|
|
49
|
-
declare function withAssets(pipeline: FetchPipeline, opts?: {
|
|
50
|
-
link?: string | null;
|
|
51
|
-
}): {
|
|
52
|
-
fetch(request: Request, env?: AssetEnv): Promise<Response>;
|
|
53
|
-
};
|
|
54
|
-
declare function withDenoAssets(pipeline: FetchPipeline): (request: Request) => Promise<Response>;
|
|
55
|
-
//#endregion
|
|
56
|
-
export { WorkerManifest, createWorker, withAssets, withDenoAssets };
|
|
1
|
+
import { a as withDenoAssets, i as withAssets, n as WorkerManifest, r as createWorker, t as WorkerExecutionContext } from "./worker-BkFHGtxp.js";
|
|
2
|
+
export { WorkerExecutionContext, WorkerManifest, createWorker, withAssets, withDenoAssets };
|
package/dist/worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { durableAgentSurface } from "./agent-durable.js";
|
|
1
|
+
import { durableAgentSurface, durableChannelSurface } from "./agent-durable.js";
|
|
2
2
|
import { createPipeline } from "./pipeline.js";
|
|
3
3
|
import { RESERVED_PREFIX, contentTypeFor, safeRelativePath } from "./static-files.js";
|
|
4
4
|
//#region src/worker.ts
|
|
@@ -61,15 +61,30 @@ function createWorker(manifest) {
|
|
|
61
61
|
const loadingFor = (key) => manifest.loadings?.[key];
|
|
62
62
|
const boundaryFor = (key) => manifest.layoutBoundaries?.[key] ?? null;
|
|
63
63
|
let currentEnv;
|
|
64
|
+
const requestLocals = /* @__PURE__ */ new WeakMap();
|
|
64
65
|
const provider = manifest.resources;
|
|
65
66
|
const servicesProvider = manifest.services;
|
|
66
67
|
let servicesCache;
|
|
67
68
|
const agentSurface = manifest.agent.runtime.enabled && manifest.agentName ? (() => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
const agentName = manifest.agentName;
|
|
70
|
+
const wantChannels = manifest.agent.runtime.channels !== false && (manifest.agentChannels?.length ?? 0) > 0;
|
|
71
|
+
return async (req) => {
|
|
72
|
+
const locals = requestLocals.get(req);
|
|
73
|
+
const ns = () => (locals?.env)?.AGENT;
|
|
74
|
+
const chatRes = await durableAgentSurface(ns, {
|
|
75
|
+
agentName,
|
|
76
|
+
chatPath: manifest.agent.runtime.chat.path
|
|
77
|
+
})(req);
|
|
78
|
+
if (chatRes) return chatRes;
|
|
79
|
+
if (!wantChannels) return null;
|
|
80
|
+
return durableChannelSurface(ns, {
|
|
81
|
+
agentName,
|
|
82
|
+
channels: manifest.agentChannels,
|
|
83
|
+
env: locals?.env,
|
|
84
|
+
services: servicesProvider,
|
|
85
|
+
waitUntil: locals?.ctx?.waitUntil?.bind(locals.ctx)
|
|
86
|
+
})(req);
|
|
87
|
+
};
|
|
73
88
|
})() : void 0;
|
|
74
89
|
const pipeline = createPipeline({
|
|
75
90
|
docConfig: manifest.document,
|
|
@@ -130,14 +145,18 @@ function createWorker(manifest) {
|
|
|
130
145
|
return null;
|
|
131
146
|
}
|
|
132
147
|
});
|
|
133
|
-
return { fetch(request, env) {
|
|
148
|
+
return { fetch(request, env, ctx) {
|
|
134
149
|
currentEnv = env;
|
|
150
|
+
requestLocals.set(request, {
|
|
151
|
+
env,
|
|
152
|
+
ctx
|
|
153
|
+
});
|
|
135
154
|
return pipeline.fetch(request);
|
|
136
155
|
} };
|
|
137
156
|
}
|
|
138
157
|
const estimateTokens = (s) => String(Math.ceil(s.length / 4));
|
|
139
158
|
function withAssets(pipeline, opts = {}) {
|
|
140
|
-
return { async fetch(request, env) {
|
|
159
|
+
return { async fetch(request, env, ctx) {
|
|
141
160
|
const assets = env?.ASSETS;
|
|
142
161
|
const url = new URL(request.url);
|
|
143
162
|
const isPagePath = !/\.[a-z0-9]+$/i.test(url.pathname);
|
|
@@ -177,7 +196,7 @@ function withAssets(pipeline, opts = {}) {
|
|
|
177
196
|
return a;
|
|
178
197
|
}
|
|
179
198
|
}
|
|
180
|
-
return pipeline.fetch(request, env);
|
|
199
|
+
return pipeline.fetch(request, env, ctx);
|
|
181
200
|
} };
|
|
182
201
|
}
|
|
183
202
|
function withDenoAssets(pipeline) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.20",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"rolldown": "^1.1.0"
|
|
139
139
|
},
|
|
140
140
|
"peerDependencies": {
|
|
141
|
-
"@junejs/core": ">=0.2.0-dev.
|
|
141
|
+
"@junejs/core": ">=0.2.0-dev.38",
|
|
142
142
|
"react": "^19.2.0",
|
|
143
143
|
"react-dom": "^19.2.0",
|
|
144
144
|
"react-server-dom-webpack": "19.2.7",
|
package/src/agent-compile.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type AgentDirScan = {
|
|
|
31
31
|
name: string;
|
|
32
32
|
hasConfig: boolean;
|
|
33
33
|
instructions: string | null;
|
|
34
|
+
// instructions.<source>.md at the agent root — per-surface variants (#149).
|
|
35
|
+
surfaceVariants: { source: string; raw: string }[];
|
|
34
36
|
tools: string[]; // relative specifiers, e.g. "./tools/create_order.ts"
|
|
35
37
|
skills: { name: string; raw: string }[];
|
|
36
38
|
channels: string[]; // "./channels/slack.ts"
|
|
@@ -59,6 +61,12 @@ export function scanAgentDir(dir: string): AgentDirScan | null {
|
|
|
59
61
|
name: basename(dir),
|
|
60
62
|
hasConfig: existsSync(join(dir, "agent.ts")),
|
|
61
63
|
instructions: existsSync(instructionsFile) ? readFileSync(instructionsFile, "utf8") : null,
|
|
64
|
+
surfaceVariants: readdirSync(dir, { withFileTypes: true })
|
|
65
|
+
.filter((e) => e.isFile())
|
|
66
|
+
.map((e) => e.name.match(/^instructions\.([a-z0-9_-]+)\.md$/))
|
|
67
|
+
.filter((m): m is RegExpMatchArray => m !== null)
|
|
68
|
+
.sort((a, b) => a[1]!.localeCompare(b[1]!))
|
|
69
|
+
.map((m) => ({ source: m[1]!, raw: readFileSync(join(dir, m[0]!), "utf8") })),
|
|
62
70
|
tools: list(join(dir, "tools"), ".ts").map((f) => `./tools/${f}`),
|
|
63
71
|
skills: list(join(dir, "skills"), ".md").map((f) => ({
|
|
64
72
|
name: basename(f, ".md"),
|
|
@@ -77,6 +85,7 @@ export function scanAgentDir(dir: string): AgentDirScan | null {
|
|
|
77
85
|
!scan.tools.length &&
|
|
78
86
|
!scan.skills.length &&
|
|
79
87
|
!scan.channels.length &&
|
|
88
|
+
!scan.surfaceVariants.length &&
|
|
80
89
|
!scan.channelOverlays.length &&
|
|
81
90
|
!scan.connections.length;
|
|
82
91
|
return empty ? null : scan;
|
|
@@ -139,6 +148,9 @@ export function emitAgentModule(scan: AgentDirScan, opts?: EmitOptions): string
|
|
|
139
148
|
"export const agentModule: AgentModule = {",
|
|
140
149
|
" config,",
|
|
141
150
|
` instructions: ${JSON.stringify(scan.instructions ?? "")},`,
|
|
151
|
+
scan.surfaceVariants.length
|
|
152
|
+
? ` surfaceInstructions: {\n${scan.surfaceVariants.map((v) => ` ${JSON.stringify(v.source)}: ${JSON.stringify(v.raw)},`).join("\n")}\n },`
|
|
153
|
+
: " surfaceInstructions: {},",
|
|
142
154
|
` tools: [${toolIds.join(", ")}],`,
|
|
143
155
|
scan.skills.length
|
|
144
156
|
? ` skills: [\n${scan.skills.map((s) => ` parseSkill(${JSON.stringify(s.name)}, ${JSON.stringify(s.raw)}),`).join("\n")}\n ],`
|
|
@@ -235,6 +247,20 @@ export function generateAgentModule(
|
|
|
235
247
|
): { file: string; code: string; stale: boolean; written: boolean } | null {
|
|
236
248
|
const scan = scanAgentDir(dir);
|
|
237
249
|
if (!scan) return null;
|
|
250
|
+
for (const o of scan.channelOverlays) {
|
|
251
|
+
console.warn(
|
|
252
|
+
`[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).`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
// A variant-shaped file the grammar doesn't parse yet must never be silently
|
|
256
|
+
// inert (the same failure class this whole convention exists to kill).
|
|
257
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
258
|
+
if (e.isFile() && /^instructions\..+\.md$/.test(e.name) && !/^instructions\.[a-z0-9_-]+\.md$/.test(e.name)) {
|
|
259
|
+
console.warn(
|
|
260
|
+
`[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.`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
238
264
|
const code = emitAgentModule(scan, { tsExtensions: opts?.tsExtensions ?? sniffTsExtensions(dir) });
|
|
239
265
|
const file = join(dir, AGENT_MODULE_FILE);
|
|
240
266
|
const existing = existsSync(file) ? readFileSync(file, "utf8") : null;
|
package/src/agent-discover.ts
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
// An agent is a DIRECTORY, not a config object. Location determines function:
|
|
4
4
|
//
|
|
5
5
|
// agent/
|
|
6
|
-
// agent.ts
|
|
7
|
-
// instructions.md
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
6
|
+
// agent.ts → default-exports a plain config { name, model?, surfaces? }
|
|
7
|
+
// instructions.md → base system prompt
|
|
8
|
+
// instructions.<source>.md → per-surface instruction variant (#149)
|
|
9
|
+
// tools/*.ts → each default-exports a defineAction (a tool),
|
|
10
|
+
// OR an array of defineActions (an integration
|
|
11
|
+
// shipping several tools, e.g. googleDriveTools())
|
|
12
|
+
// skills/*.md → each a procedure, loaded on demand (progressive disclosure)
|
|
13
|
+
// channels/*.ts → each default-exports a Channel (an inbound edge — pure transport)
|
|
14
|
+
// channels/*.md → DEPRECATED overlay location (warned; use instructions.<source>.md)
|
|
15
|
+
// connections/*.ts → each default-exports a Connection (an outbound tool source)
|
|
13
16
|
//
|
|
14
17
|
// There is no central registry to keep in sync — the directory IS the manifest.
|
|
15
18
|
// Because tools are `defineAction`s, the SAME directory is also an MCP server and
|
|
@@ -23,8 +26,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
23
26
|
import { basename, extname, join } from "node:path";
|
|
24
27
|
import { pathToFileURL } from "node:url";
|
|
25
28
|
|
|
26
|
-
import type
|
|
27
|
-
import { assembleAgent, parseSkill, type AgentConfigFile, type AgentDefinition, type AgentModule, type Channel, type ChannelFactory, type Skill } from "@junejs/core/agent-config";
|
|
29
|
+
import { assembleAgent, parseSkill, type AgentConfigFile, type AgentDefinition, type AgentModule, type Channel, type ChannelFactory, type Skill, type ToolEntry } from "@junejs/core/agent-config";
|
|
28
30
|
import type { Connection } from "@junejs/core/connections";
|
|
29
31
|
|
|
30
32
|
// `_`-prefixed files are private by convention (mirrors the app/ router) — most
|
|
@@ -51,10 +53,32 @@ export async function discoverAgentModule(dir: string): Promise<AgentModule> {
|
|
|
51
53
|
const instructionsFile = join(dir, "instructions.md");
|
|
52
54
|
const instructions = existsSync(instructionsFile) ? await readFile(instructionsFile, "utf8") : "";
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
// instructions.<source>.md — the agent's per-surface instruction variants
|
|
57
|
+
// (#149): applied to turns arriving through that source, composed per
|
|
58
|
+
// config.surfaces (append by default, "replace" to stand alone).
|
|
59
|
+
const surfaceInstructions: Record<string, string> = {};
|
|
60
|
+
if (existsSync(dir)) {
|
|
61
|
+
for (const e of await readdir(dir, { withFileTypes: true })) {
|
|
62
|
+
if (!e.isFile() || !/^instructions\..+\.md$/.test(e.name)) continue;
|
|
63
|
+
const m = e.name.match(/^instructions\.([a-z0-9_-]+)\.md$/);
|
|
64
|
+
if (m) {
|
|
65
|
+
surfaceInstructions[m[1]!] = await readFile(join(dir, e.name), "utf8");
|
|
66
|
+
} else {
|
|
67
|
+
// A variant-shaped file the grammar doesn't parse yet (e.g. a locale
|
|
68
|
+
// suffix, instructions.slack.zh-TW.md) must never be SILENTLY inert.
|
|
69
|
+
console.warn(
|
|
70
|
+
`[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.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// A tool file default-exports one tool OR an array of tools (an integration
|
|
77
|
+
// like googleDriveTools()); assembly flattens arrays.
|
|
78
|
+
const tools: ToolEntry[] = [];
|
|
55
79
|
for (const f of await scan(join(dir, "tools"), ".ts")) {
|
|
56
80
|
const mod = await import(pathToFileURL(f).href);
|
|
57
|
-
if (mod.default) tools.push(mod.default as
|
|
81
|
+
if (mod.default) tools.push(mod.default as ToolEntry);
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
const skills: Skill[] = [];
|
|
@@ -71,11 +95,16 @@ export async function discoverAgentModule(dir: string): Promise<AgentModule> {
|
|
|
71
95
|
if (mod.default) channels[basename(f, ".ts")] = mod.default as Channel | ChannelFactory;
|
|
72
96
|
}
|
|
73
97
|
|
|
74
|
-
// channels/<source>.md —
|
|
75
|
-
//
|
|
98
|
+
// channels/<source>.md — the DEPRECATED overlay convention (#149): behavior
|
|
99
|
+
// prose belongs at the agent level (instructions.<source>.md), not beside
|
|
100
|
+
// transport code. Still honored for one dev-series, loudly.
|
|
76
101
|
const channelInstructions: Record<string, string> = {};
|
|
77
102
|
for (const f of await scan(join(dir, "channels"), ".md")) {
|
|
78
|
-
|
|
103
|
+
const source = basename(f, ".md");
|
|
104
|
+
console.warn(
|
|
105
|
+
`[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).`,
|
|
106
|
+
);
|
|
107
|
+
channelInstructions[source] = await readFile(f, "utf8");
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
// connections/*.ts — outbound: definitions of external MCP/OpenAPI servers;
|
|
@@ -86,7 +115,7 @@ export async function discoverAgentModule(dir: string): Promise<AgentModule> {
|
|
|
86
115
|
if (mod.default) connections.push(mod.default as Connection);
|
|
87
116
|
}
|
|
88
117
|
|
|
89
|
-
return { config, instructions, tools, skills, channels, channelInstructions, connections };
|
|
118
|
+
return { config, instructions, surfaceInstructions, tools, skills, channels, channelInstructions, connections };
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
// Discover an agent from its directory, returning the assembled AgentDefinition
|