@schlenkr/ragents 0.1.14 → 0.1.16
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/apps/server/package.json +1 -1
- package/apps/server/src/api/contracts.ts +19 -1
- package/apps/server/src/api/core-methods.ts +12 -0
- package/apps/server/src/chat-handler.ts +19 -0
- package/apps/server/src/host-api.json +2 -0
- package/apps/server/src/host-api.ts +2 -2
- package/apps/server/src/plugin-build/build.ts +1 -1
- package/apps/server/src/plugin-support/actor-programs/app-project.ts +62 -18
- package/apps/server/src/plugin-support/actor-programs/client-contracts.ts +47 -8
- package/apps/server/src/plugin-support/actor-programs/contract.ts +18 -1
- package/apps/server/src/plugin-support/actor-programs/service.ts +22 -1
- package/apps/server/src/plugin-support/plugin-folder.ts +14 -1
- package/apps/server/src/plugin-support/run-scripts.ts +27 -2
- package/apps/server/src/profile/compose.ts +2 -0
- package/apps/server/src/provider.ts +23 -4
- package/apps/server/src/ragents/chat-projection.ts +7 -0
- package/apps/server/src/ragents/global-chat.ts +5 -1
- package/apps/server/src/ragents/run-script-tools.ts +66 -0
- package/apps/server/src/ragents/session.ts +122 -60
- package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-BslIwn_y.js → highlighted-body-KPVGNVTW-BwD4uYEe.js} +1 -1
- package/apps/web/dist/assets/{main-BUN8m8SW.js → main-KBrc5Q77.js} +1 -1
- package/apps/web/dist/assets/{mermaid-HWGCJPDP-B1erZlFM.js → mermaid-HWGCJPDP-CSCkaArr.js} +88 -88
- package/apps/web/dist/assets/runPanel-BjG5NBKm.js +26 -0
- package/apps/web/dist/help/guide-clients.html +21 -4
- package/apps/web/dist/help/guide-clients.md +23 -4
- package/apps/web/dist/help/guide-plugins.html +12 -2
- package/apps/web/dist/help/guide-plugins.md +9 -2
- package/apps/web/dist/help/guide-programs.html +8 -2
- package/apps/web/dist/help/guide-programs.md +9 -2
- package/apps/web/dist/help/guide-runtime.html +3 -2
- package/apps/web/dist/help/guide-runtime.md +3 -2
- package/apps/web/dist/help/index.html +1 -1
- package/apps/web/dist/help/mini-app.css +7 -2
- package/apps/web/dist/host-web.json +6 -6
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/run-panel.html +2 -2
- package/apps/web/package.json +1 -1
- package/apps/web/src/run-panel/RunPanelApp.tsx +1 -1
- package/apps/web/src/run-panel/RunPanelMenu.tsx +47 -11
- package/bundles/ragents.activity/ragents-bundle.json +1 -1
- package/bundles/ragents.actor-programs/ragents-bundle.json +4 -2
- package/bundles/ragents.actor-programs/server/index.js +437 -24
- package/bundles/ragents.actor-programs/server/index.js.map +4 -4
- package/bundles/ragents.actor-programs/web/index.js.map +1 -1
- package/bundles/ragents.ask/ragents-bundle.json +2 -2
- package/bundles/ragents.ask/server/index.js +1 -0
- package/bundles/ragents.ask/server/index.js.map +2 -2
- package/bundles/ragents.browser/ragents-bundle.json +1 -1
- package/bundles/ragents.documents/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +1 -1
- package/bundles/ragents.model-relay/ragents-bundle.json +1 -1
- package/bundles/ragents.orchestration/ragents-bundle.json +7 -4
- package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js +11 -0
- package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js.map +7 -0
- package/bundles/ragents.orchestration/server/exports/server/contract.js +9 -0
- package/bundles/ragents.orchestration/server/exports/server/contract.js.map +7 -0
- package/bundles/ragents.orchestration/server/index.js +49 -3
- package/bundles/ragents.orchestration/server/index.js.map +2 -2
- package/bundles/ragents.orchestration/surface.hbs +5 -0
- package/bundles/ragents.overseer/ragents-bundle.json +1 -1
- package/bundles/ragents.processes/ragents-bundle.json +1 -1
- package/bundles/ragents.product/ragents-bundle.json +1 -1
- package/bundles/ragents.profile-distribution/ragents-bundle.json +1 -1
- package/bundles/ragents.reference/actors/notebook/package.json +16 -0
- package/bundles/ragents.reference/actors/notebook/src/client.tsx +31 -0
- package/bundles/ragents.reference/actors/notebook/src/server.ts +27 -0
- package/bundles/ragents.reference/actors/notebook/tests/program.test.ts +27 -0
- package/bundles/ragents.reference/ragents-bundle.json +4 -3
- package/bundles/ragents.reference/run-scripts/quick-note/RUN.md +14 -0
- package/bundles/ragents.reference/run-scripts/quick-note/package.json +9 -0
- package/bundles/ragents.reference/run-scripts/quick-note/src/server.ts +29 -0
- package/bundles/ragents.reference/run-scripts/quick-note/tests/program.test.ts +43 -0
- package/bundles/ragents.reference/run-scripts/run-roster/RUN.md +19 -0
- package/bundles/ragents.reference/run-scripts/run-roster/package.json +9 -0
- package/bundles/ragents.reference/run-scripts/run-roster/src/server.ts +26 -0
- package/bundles/ragents.reference/run-scripts/run-roster/tests/program.test.ts +47 -0
- package/bundles/ragents.todo/ragents-bundle.json +1 -1
- package/bundles/ragents.transcript/ragents-bundle.json +1 -1
- package/bundles/ragents.watch/ragents-bundle.json +1 -1
- package/bundles/ragents.workspace/ragents-bundle.json +1 -1
- package/package.json +3 -3
- package/packages/ragents/src/domain/vocabulary.ts +4 -0
- package/packages/ragents/src/index.ts +3 -1
- package/packages/ragents/src/plugin-host.ts +62 -10
- package/packages/ragents/src/plugin-types.ts +11 -0
- package/packages/ragents/src/runtime/guards.ts +2 -1
- package/packages/ragents/src/typescript/async-compiler.ts +11 -1
- package/plugins/ragents.actor-programs/server/index.ts +3 -0
- package/plugins/ragents.actor-programs/server/runtime.ts +424 -28
- package/plugins/ragents.actor-programs/server/script-state.ts +54 -0
- package/plugins/ragents.actor-programs/server/tool-contributor.ts +2 -0
- package/plugins/ragents.ask/server/ask-tool.ts +1 -0
- package/plugins/ragents.orchestration/ragents-plugin.json +1 -0
- package/plugins/ragents.orchestration/server/contract.ts +14 -0
- package/plugins/ragents.orchestration/server/index.ts +4 -1
- package/plugins/ragents.orchestration/server/surface-tool.ts +58 -2
- package/plugins/ragents.orchestration/surface.hbs +5 -0
- package/plugins/ragents.reference/actors/notebook/package.json +16 -0
- package/plugins/ragents.reference/actors/notebook/src/client.tsx +31 -0
- package/plugins/ragents.reference/actors/notebook/src/server.ts +27 -0
- package/plugins/ragents.reference/actors/notebook/tests/program.test.ts +27 -0
- package/plugins/ragents.reference/run-scripts/quick-note/RUN.md +14 -0
- package/plugins/ragents.reference/run-scripts/quick-note/package.json +9 -0
- package/plugins/ragents.reference/run-scripts/quick-note/src/server.ts +29 -0
- package/plugins/ragents.reference/run-scripts/quick-note/tests/program.test.ts +43 -0
- package/plugins/ragents.reference/run-scripts/run-roster/RUN.md +19 -0
- package/plugins/ragents.reference/run-scripts/run-roster/package.json +9 -0
- package/plugins/ragents.reference/run-scripts/run-roster/src/server.ts +26 -0
- package/plugins/ragents.reference/run-scripts/run-roster/tests/program.test.ts +47 -0
- package/scripts/agent/agent-cli.ts +46 -2
- package/scripts/package/ragents.mjs +3 -1
- package/apps/web/dist/assets/runPanel-s8gGpr9M.js +0 -21
package/apps/server/package.json
CHANGED
|
@@ -44,6 +44,24 @@ export const coreContracts = {
|
|
|
44
44
|
input: Type.Object({ runId }, { additionalProperties: false }),
|
|
45
45
|
result: Type.Null(),
|
|
46
46
|
}),
|
|
47
|
+
scripts: defineOperation({
|
|
48
|
+
id: "ragents.runs.scripts",
|
|
49
|
+
description: "The run scripts the caller may start, and for a running run whether each can start there now: available, otherwise reason (not embeddable, a fixed start option differs from the run's). Rights: runs.read, runs.write.",
|
|
50
|
+
input: Type.Object({ runId }, { additionalProperties: false }),
|
|
51
|
+
result: Type.Array(Type.Object({
|
|
52
|
+
id: Type.String(),
|
|
53
|
+
title: Type.String(),
|
|
54
|
+
description: Type.String(),
|
|
55
|
+
available: Type.Boolean(),
|
|
56
|
+
reason: Type.Optional(Type.String()),
|
|
57
|
+
}, { additionalProperties: false })),
|
|
58
|
+
}),
|
|
59
|
+
startScript: defineOperation({
|
|
60
|
+
id: "ragents.runs.startScript",
|
|
61
|
+
description: "Start a run script and wait until its actor has the start input; errors come back to the caller instead of the chat. A new run starts as with ragents.chat.start. In a running run only a script whose RUN.md sets embeddable: true starts, its fixed start options must match the run's, and the primary actor stays; a repeated start reuses the script's actor. Result: the script actor and which start of its package in the run this was. Rights: runs.read, runs.write and the template's release.",
|
|
62
|
+
input: Type.Object({ runId, entry: Type.String({ minLength: 1 }), input: Type.Optional(Type.Any()) }, { additionalProperties: false }),
|
|
63
|
+
result: Type.Object({ actorId: Type.String(), handle: Type.String(), count: Type.Integer({ minimum: 1 }) }, { additionalProperties: false }),
|
|
64
|
+
}),
|
|
47
65
|
},
|
|
48
66
|
chat: {
|
|
49
67
|
send: defineOperation({
|
|
@@ -66,7 +84,7 @@ export const coreContracts = {
|
|
|
66
84
|
}),
|
|
67
85
|
start: defineOperation({
|
|
68
86
|
id: "ragents.chat.start",
|
|
69
|
-
description: "Start a run through a script template, without a message; the start options the template fixes apply. Rights: runs.read, runs.write and the template's release.",
|
|
87
|
+
description: "Start a run through a script template, without a message; the start options the template fixes apply. Returns once the start is accepted; progress and errors appear in the chat. In a running run it starts an embeddable script like ragents.runs.startScript. Rights: runs.read, runs.write and the template's release.",
|
|
70
88
|
input: Type.Object({ runId, entry: Type.String({ minLength: 1 }), input: Type.Optional(Type.Any()) }, { additionalProperties: false }),
|
|
71
89
|
result: Type.Null(),
|
|
72
90
|
}),
|
|
@@ -114,6 +114,18 @@ export const coreMethods = (sources: CoreMethodSources): MethodContribution[] =>
|
|
|
114
114
|
current.start(trimmed, input === undefined ? null : input, userOf(access));
|
|
115
115
|
return null;
|
|
116
116
|
}),
|
|
117
|
+
implement(coreContracts.runs.scripts, async ({ runId }, { access }) => {
|
|
118
|
+
const current = await session(access, runId, "write");
|
|
119
|
+
if (!current.runScripts) throw new DomainError("script-start-unavailable", "This run cannot start run scripts.", 404);
|
|
120
|
+
return current.runScripts(sources.plugins.startEntries.describe().filter((entry) => canStartEntry(access, entry.id)), userIdOf(access));
|
|
121
|
+
}),
|
|
122
|
+
implement(coreContracts.runs.startScript, async ({ runId, entry, input }, { access }) => {
|
|
123
|
+
const trimmed = entry.trim();
|
|
124
|
+
if (!canStartEntry(access, trimmed)) throw new DomainError("access-denied", "This setup is not enabled for this access.", 403);
|
|
125
|
+
const current = await session(access, runId, "write");
|
|
126
|
+
if (!current.startAndWait) throw new DomainError("script-start-unavailable", "This run cannot start run scripts.", 404);
|
|
127
|
+
return current.startAndWait(trimmed, input === undefined ? null : input, userOf(access));
|
|
128
|
+
}),
|
|
117
129
|
implement(coreContracts.chat.stop, async ({ runId }, { access }) => {
|
|
118
130
|
const current = await session(access, runId, "stop");
|
|
119
131
|
await current.stop();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** The contracts of the runs that core methods and delivery work against. */
|
|
2
2
|
import type { ChatAttachment, ChatAttachmentInput, ChatEvent } from "quassel/events";
|
|
3
|
+
import type { PublicStartEntry } from "@ragents/engine";
|
|
3
4
|
import type { ActorConversations } from "./ragents/actor-chat-history.js";
|
|
4
5
|
|
|
5
6
|
export interface SessionInfo {
|
|
@@ -18,6 +19,22 @@ export interface ChatUser {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/** What the HTTP adapter needs from a run. */
|
|
22
|
+
/** A started run script: its actor and which start of its package in the run this was. */
|
|
23
|
+
export interface StartedScript {
|
|
24
|
+
readonly actorId: string;
|
|
25
|
+
readonly handle: string;
|
|
26
|
+
readonly count: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A run script as a run lists it: whether it can start there now, and otherwise why not. */
|
|
30
|
+
export interface RunScriptListing {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly title: string;
|
|
33
|
+
readonly description: string;
|
|
34
|
+
readonly available: boolean;
|
|
35
|
+
readonly reason?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
21
38
|
export interface ChatSessionLike {
|
|
22
39
|
readonly running: boolean;
|
|
23
40
|
subscribe(listener: (event: ChatEvent) => void): () => void;
|
|
@@ -27,6 +44,8 @@ export interface ChatSessionLike {
|
|
|
27
44
|
capabilities?(actor: string, userId: string | null): Promise<{ input: string[]; model: string }>;
|
|
28
45
|
attachment?(artifactId: string): { attachment: ChatAttachment; content: Uint8Array };
|
|
29
46
|
start(entryId: string, input: unknown, user?: ChatUser): void;
|
|
47
|
+
startAndWait?(entryId: string, input: unknown, user?: ChatUser, startedBy?: string): Promise<StartedScript>;
|
|
48
|
+
runScripts?(entries: readonly PublicStartEntry[], userId: string | null): RunScriptListing[];
|
|
30
49
|
stop(): void | Promise<void>;
|
|
31
50
|
}
|
|
32
51
|
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"holdsUsable",
|
|
26
26
|
"implement",
|
|
27
27
|
"implementChannel",
|
|
28
|
+
"inheritedGrants",
|
|
28
29
|
"isRunId",
|
|
29
30
|
"isThinkingLevel",
|
|
30
31
|
"modelToolDescriptors",
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
"@ragents/host/plugin-support/actor-programs/app-project": [
|
|
78
79
|
"compileAppBackend",
|
|
79
80
|
"installServerSdk",
|
|
81
|
+
"packageIdentity",
|
|
80
82
|
"prepareAppProject",
|
|
81
83
|
"prepareAppWorkspace",
|
|
82
84
|
"projectSourceFiles",
|
|
@@ -13,7 +13,7 @@ export const hostApi: { readonly server: HostApiModules; readonly web: HostApiMo
|
|
|
13
13
|
"DomainError", "RPC_ERROR_CODES", "RpcError", "ScriptDriver", "ToolRegistry", "actorByHandle",
|
|
14
14
|
"actorByReference", "actorDescriptionMaxLength", "actorInputSchema", "agentTools", "assertJsonValue", "builtinProfiles", "canonicalHash",
|
|
15
15
|
"compileVirtualTypeScriptAsync", "defineRunFunction", "defineToolAvailability", "emptyUsage",
|
|
16
|
-
"enqueueActorInput", "eventResultSchemaOf", "handleKey", "holdsUsable", "implement", "implementChannel",
|
|
16
|
+
"enqueueActorInput", "eventResultSchemaOf", "handleKey", "holdsUsable", "implement", "implementChannel", "inheritedGrants",
|
|
17
17
|
"isRunId", "isThinkingLevel", "modelToolDescriptors", "pluginStateAt", "runCapabilityBindingHash",
|
|
18
18
|
"runCapabilityContractHash", "schemaComplaints", "scriptInputOf", "serviceToken",
|
|
19
19
|
],
|
|
@@ -31,7 +31,7 @@ export const hostApi: { readonly server: HostApiModules; readonly web: HostApiMo
|
|
|
31
31
|
],
|
|
32
32
|
"@ragents/host/host-version": ["readHostApiVersion", "readHostVersion", "readPackageVersion"],
|
|
33
33
|
"@ragents/host/plugin-support/actor-programs/app-project": [
|
|
34
|
-
"compileAppBackend", "installServerSdk", "prepareAppProject", "prepareAppWorkspace", "projectSourceFiles",
|
|
34
|
+
"compileAppBackend", "installServerSdk", "packageIdentity", "prepareAppProject", "prepareAppWorkspace", "projectSourceFiles",
|
|
35
35
|
"readAppPackage", "typecheckServerProject",
|
|
36
36
|
],
|
|
37
37
|
"@ragents/host/plugin-support/actor-programs/client-compiler": ["compileClientProject", "installClientSdk"],
|
|
@@ -39,7 +39,7 @@ export interface PluginBuildOptions {
|
|
|
39
39
|
readonly onlyOutdated?: boolean;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const DEFAULT_ASSET_FOLDERS = ["skills", "prompts", "run-scripts"] as const;
|
|
42
|
+
const DEFAULT_ASSET_FOLDERS = ["skills", "prompts", "run-scripts", "actors"] as const;
|
|
43
43
|
const SKIPPED_ASSET_FILES = new Set([".DS_Store"]);
|
|
44
44
|
const WEB_FILE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".avif", ".ico", ".woff", ".woff2", ".ttf", ".otf"];
|
|
45
45
|
const CODE_EXTENSION = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
@@ -13,7 +13,7 @@ import { runtimeLibraries, webRuntimeLibraries } from "./runtime-libraries.js";
|
|
|
13
13
|
const webRequire = createRequire(new URL("../../../../web/package.json", import.meta.url));
|
|
14
14
|
const rootRequire = createRequire(new URL("../../../../../package.json", import.meta.url));
|
|
15
15
|
const serverRequire = createRequire(new URL("../../../package.json", import.meta.url));
|
|
16
|
-
const serverTypeRoot =
|
|
16
|
+
const serverTypeRoot = path.dirname(path.dirname(serverRequire.resolve("@types/node/package.json")));
|
|
17
17
|
const libraryDirectory = (name: string): string => {
|
|
18
18
|
const resolver = (webRuntimeLibraries as readonly string[]).includes(name) ? webRequire
|
|
19
19
|
: name === "@types/node" || name === "tsx" ? serverRequire : rootRequire;
|
|
@@ -107,8 +107,10 @@ export function createTestContextWithContracts(options, contracts) {
|
|
|
107
107
|
if(!Value.Check(contract.resultSchema,result))throw new Error("Test response violates the contract of "+name+": "+schemaComplaints(contract.resultSchema,result,"result"));
|
|
108
108
|
return result;
|
|
109
109
|
}};
|
|
110
|
+
const finished = [];
|
|
110
111
|
return {...createRunContext({runId:"test",invocationId:"test",invocationKind:"tool",principal:{id:"test",kind:"service"},state:statePort,capabilities,log:()=>{},signal:controller.signal}),
|
|
111
|
-
actor:{id:"test-actor",handle:"test"},std:createScriptStd({turnStartedAt:"2026-01-01T00:00:00.000Z",idPrefix:"test",state:statePort,capabilities})
|
|
112
|
+
actor:{id:"test-actor",handle:"test"},std:createScriptStd({turnStartedAt:"2026-01-01T00:00:00.000Z",idPrefix:"test",state:statePort,capabilities}),
|
|
113
|
+
finished,finish:(result,options={})=>{assertJsonValue(result===undefined?null:result,"Test finish result");finished.push({result:result===undefined?null:result,...options});}};
|
|
112
114
|
}`, resolveDir:process.cwd(),sourcefile:"actor-testing.ts",loader:"ts"},
|
|
113
115
|
bundle:true,platform:"node",format:"esm",target:"node22",packages:"external",write:false,
|
|
114
116
|
}).then(result=>result.outputFiles![0]!.text);
|
|
@@ -134,6 +136,8 @@ export interface RunContext<State> {
|
|
|
134
136
|
readonly functions: {readonly [Name in keyof CapabilityContracts]: (...args: {} extends CapabilityContracts[Name]['input'] ? [input?: CapabilityContracts[Name]['input']] : [input: CapabilityContracts[Name]['input']]) => Promise<CapabilityContracts[Name]['output']>};
|
|
135
137
|
log(value: unknown): void;
|
|
136
138
|
throwIfAborted(): void;
|
|
139
|
+
/** Ends a start of this run script with a JSON result; the host delivers it once to whoever started it. Only in onStart, onInput and onResult; outside onStart, name the start. */
|
|
140
|
+
finish(result: unknown, options?: { readonly summary?: string; readonly start?: number }): void;
|
|
137
141
|
}
|
|
138
142
|
${scriptStdDeclarations}
|
|
139
143
|
export interface ActorInput {
|
|
@@ -142,10 +146,15 @@ export interface ActorInput {
|
|
|
142
146
|
readonly event: { readonly type: string; readonly eventId: string; readonly sequence: number; readonly occurredAt: string;
|
|
143
147
|
readonly sourceActorId: string | null; readonly sourceActorHandle: string | null; readonly payload: {readonly text?:string; readonly [key:string]:unknown} } | null;
|
|
144
148
|
}
|
|
149
|
+
export interface ActorStart {
|
|
150
|
+
readonly input: unknown; readonly options: Readonly<Record<string, unknown>>;
|
|
151
|
+
readonly embedded: boolean; readonly startedBy: string; readonly count: number;
|
|
152
|
+
}
|
|
153
|
+
export interface ActorResult { readonly handle: string; readonly count: number; readonly result: unknown; readonly summary?: string }
|
|
145
154
|
export interface ActorFunction { label: string; description?: string; input: TSchema; output: TSchema; capabilities?: readonly string[]; confirmation?: string; tool?: { name: string; targets?: readonly string[]; card?: boolean } }
|
|
146
155
|
export interface ActorContract { state: TSchema; functions: Readonly<Record<string, ActorFunction>>; input?: {capabilities?: readonly string[]} }
|
|
147
156
|
export type ActorFunctions<C extends ActorContract> = { [K in keyof C['functions']]: (input: Static<C['functions'][K]['input']>, context: RunContext<Static<C['state']>>) => Static<C['functions'][K]['output']> | Promise<Static<C['functions'][K]['output']>> };
|
|
148
|
-
export type ActorImplementation<C extends ActorContract> = {functions: ActorFunctions<C>} & (C extends {input: unknown} ? {onInput: (input: ActorInput, context: RunContext<Static<C['state']>>) => void | Promise<void>} : {onInput?: never});
|
|
157
|
+
export type ActorImplementation<C extends ActorContract> = {functions: ActorFunctions<C>} & (C extends {input: unknown} ? {onInput: (input: ActorInput, context: RunContext<Static<C['state']>>) => void | Promise<void>; onStart?: (start: ActorStart, context: RunContext<Static<C['state']>>) => void | Promise<void>; onResult?: (result: ActorResult, context: RunContext<Static<C['state']>>) => void | Promise<void>} : {onInput?: never; onStart?: never; onResult?: never});
|
|
149
158
|
export declare function defineActor<const C extends ActorContract>(contract: C, implementation: ActorImplementation<C>): {contract:C} & ActorImplementation<C>;
|
|
150
159
|
`;
|
|
151
160
|
};
|
|
@@ -193,24 +202,45 @@ export const installServerSdk = async (directory: string, capabilities: readonly
|
|
|
193
202
|
await writeFile(path.join(sdk, "index.js"), sdkRuntime);
|
|
194
203
|
await writeFile(path.join(sdk, "index.d.ts"), declarations);
|
|
195
204
|
await writeFile(path.join(sdk, "testing.js"), (await testingRuntime()) + `\nexport function createTestContext(options) { return createTestContextWithContracts(options, ${JSON.stringify(capabilities)}); }\n`);
|
|
196
|
-
await writeFile(path.join(sdk, "testing.d.ts"), "import type { RunContext, CapabilityContracts } from './index.js';\ntype Mocks = {[Name in keyof CapabilityContracts]?: (input: CapabilityContracts[Name]['input']) => CapabilityContracts[Name]['output'] | Promise<CapabilityContracts[Name]['output']>};\nexport declare function createTestContext<State>(options: {state: State; functions?: Mocks}): RunContext<State
|
|
205
|
+
await writeFile(path.join(sdk, "testing.d.ts"), "import type { RunContext, CapabilityContracts } from './index.js';\ntype Mocks = {[Name in keyof CapabilityContracts]?: (input: CapabilityContracts[Name]['input']) => CapabilityContracts[Name]['output'] | Promise<CapabilityContracts[Name]['output']>};\ntype Finished = {readonly result: unknown; readonly summary?: string; readonly start?: number};\nexport declare function createTestContext<State>(options: {state: State; functions?: Mocks}): RunContext<State> & {readonly finished: readonly Finished[]};\n");
|
|
197
206
|
};
|
|
198
207
|
|
|
208
|
+
/** The tsconfig files the host writes into a package that brings none of its own. */
|
|
209
|
+
const generatedConfigs = (pkg: Pick<AppPackage, "backend" | "views">): Readonly<Record<string, string>> => ({
|
|
210
|
+
"tsconfig.client.json": json({ extends: "./node_modules/@ragents/client/tsconfig.json", include: (pkg.views ?? []).map(view => view.client) }),
|
|
211
|
+
"tsconfig.server.json": json({ compilerOptions: {
|
|
212
|
+
target: "ES2022", module: "ESNext", moduleResolution: "Bundler", strict: true, noEmit: true,
|
|
213
|
+
skipLibCheck: true, types: ["node"], lib: ["ES2022"], allowImportingTsExtensions: true,
|
|
214
|
+
}, include: [...(pkg.backend ? [pkg.backend] : []), "tests/**/*.ts"] }),
|
|
215
|
+
"tsconfig.json": json({ files: [], references: [{ path: "./tsconfig.client.json" }, { path: "./tsconfig.server.json" }] }),
|
|
216
|
+
});
|
|
217
|
+
|
|
199
218
|
export const prepareAppProject = async (directory: string): Promise<void> => {
|
|
200
219
|
await prepareAppDependencies(directory);
|
|
201
220
|
await installServerSdk(directory);
|
|
202
221
|
const pkg = await readAppPackage(directory);
|
|
203
|
-
const
|
|
204
|
-
await writeFile(path.join(directory, name),
|
|
222
|
+
for (const [name, content] of Object.entries(generatedConfigs(pkg))) {
|
|
223
|
+
await writeFile(path.join(directory, name), content, { flag: "wx" }).catch((error: NodeJS.ErrnoException) => {
|
|
205
224
|
if (error.code !== "EEXIST") throw error;
|
|
206
225
|
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/** What a package is, independent of the tsconfig files the host generated into it: two packages with the same identity have the same sources. */
|
|
230
|
+
export const packageIdentity = (files: readonly { path: string; content: string }[]): string => {
|
|
231
|
+
const normalized = files.map((file) => ({ path: file.path.split(path.sep).join("/"), content: file.content }));
|
|
232
|
+
const generated = generatedConfigsOf(normalized.find((file) => file.path === "package.json")?.content);
|
|
233
|
+
return canonicalHash(normalized.filter((file) => generated[file.path] !== file.content).sort((left, right) => left.path.localeCompare(right.path)));
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/** A package.json that does not parse has no generated configs; its activation reports the error. */
|
|
237
|
+
const generatedConfigsOf = (packageJson: string | undefined): Readonly<Record<string, string>> => {
|
|
238
|
+
try {
|
|
239
|
+
const pkg = (JSON.parse(packageJson ?? "{}") as { ragents?: Pick<AppPackage, "backend" | "views"> }).ragents;
|
|
240
|
+
return pkg ? generatedConfigs(pkg) : {};
|
|
241
|
+
} catch {
|
|
242
|
+
return {};
|
|
243
|
+
}
|
|
214
244
|
};
|
|
215
245
|
|
|
216
246
|
export const readAppPackage = async (directory: string): Promise<AppPackage> => {
|
|
@@ -261,13 +291,27 @@ export const compileAppBackend = async (options: {
|
|
|
261
291
|
export const describe = () => {
|
|
262
292
|
for (const name of Object.keys(actor.contract.functions ?? {})) if (typeof actor.functions?.[name] !== 'function') throw new Error('Missing function ' + name);
|
|
263
293
|
if (Boolean(actor.contract.input) !== (typeof actor.onInput === 'function')) throw new Error('input and onInput must be present together.');
|
|
294
|
+
for (const name of ['onStart', 'onResult']) if (actor[name] !== undefined && (typeof actor[name] !== 'function' || !actor.contract.input)) throw new Error(name + ' needs input and onInput.');
|
|
264
295
|
return JSON.parse(JSON.stringify(actor.contract));
|
|
265
296
|
};
|
|
266
|
-
export const handle = (request, context) => {
|
|
267
|
-
if (request.kind
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
export const handle = async (request, context) => {
|
|
298
|
+
if (request.kind !== 'input') {
|
|
299
|
+
const handler = actor.functions[request.functionId];
|
|
300
|
+
if (typeof handler !== 'function') throw new Error('Unknown actor function ' + request.functionId);
|
|
301
|
+
return handler(request.input, { ...context, finish: () => { throw new Error('finish works only in onStart, onInput and onResult; a function passes the result as an input to its own actor.'); } });
|
|
302
|
+
}
|
|
303
|
+
if (!actor.onInput) throw new Error('This actor does not process input.');
|
|
304
|
+
const finishes = [];
|
|
305
|
+
const finish = (result, options = {}) => {
|
|
306
|
+
const start = options.start ?? request.start?.count;
|
|
307
|
+
if (!Number.isInteger(start)) throw new Error('finish needs options.start, the count of the start it ends, outside onStart.');
|
|
308
|
+
finishes.push({ start, result: result === undefined ? null : result, ...(options.summary === undefined ? {} : { summary: options.summary }) });
|
|
309
|
+
};
|
|
310
|
+
const handlerContext = { ...context, finish };
|
|
311
|
+
if (request.result && actor.onResult) await actor.onResult(request.result, handlerContext);
|
|
312
|
+
else if (request.start && actor.onStart) await actor.onStart(request.start, handlerContext);
|
|
313
|
+
else await actor.onInput(request.input, handlerContext);
|
|
314
|
+
return { finishes };
|
|
271
315
|
};`, resolveDir: options.directory, sourcefile: "actor-entry.ts", loader: "ts" },
|
|
272
316
|
absWorkingDir: options.directory, platform: "node", format: "esm", target: "node22", bundle: true, packages: "external", write: false,
|
|
273
317
|
});
|
|
@@ -1,16 +1,55 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import ts from "typescript";
|
|
4
5
|
|
|
5
|
-
function
|
|
6
|
-
const
|
|
6
|
+
export function clientUiContractPaths(directory: string, paths: typeof path.posix = path) {
|
|
7
|
+
const canonical = (name: string) => paths.resolve(name).replaceAll(paths.sep, "/").replace(/^[a-z]:/, (drive) => drive.toUpperCase());
|
|
8
|
+
const repoRoot = canonical(directory);
|
|
9
|
+
const root = canonical(paths.join(paths.parse(repoRoot).root, "__ragents_ui_contract__"));
|
|
10
|
+
const contains = (base: string, name: string) => name === base || name.startsWith(`${base}/`);
|
|
11
|
+
const physical = (name: string) => {
|
|
12
|
+
const file = canonical(name);
|
|
13
|
+
return paths.normalize(contains(root, file) ? repoRoot + file.slice(root.length) : file);
|
|
14
|
+
};
|
|
15
|
+
const virtual = (name: string) => {
|
|
16
|
+
const file = canonical(name);
|
|
17
|
+
return contains(repoRoot, file) ? root + file.slice(repoRoot.length) : file;
|
|
18
|
+
};
|
|
19
|
+
return { root, physical, virtual, isVirtual: (name: string) => contains(root, canonical(name)) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readContracts(directory: string): { names: string[]; files: Record<string, string> } {
|
|
23
|
+
const { root, physical, virtual, isVirtual } = clientUiContractPaths(realpathSync(directory));
|
|
24
|
+
const filename = `${root}/apps/web/src/actor-programs/client-ui/contracts.d.ts`;
|
|
7
25
|
const options: ts.CompilerOptions = {
|
|
8
26
|
target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext,
|
|
9
27
|
moduleResolution: ts.ModuleResolutionKind.Bundler, jsx: ts.JsxEmit.ReactJSX,
|
|
10
28
|
strict: true, skipLibCheck: true, esModuleInterop: true,
|
|
11
29
|
declaration: true, emitDeclarationOnly: true,
|
|
12
30
|
};
|
|
13
|
-
|
|
31
|
+
// TypeScript must see public sources outside node_modules, including during declaration naming.
|
|
32
|
+
const host = ts.createCompilerHost(options);
|
|
33
|
+
host.readFile = (name) => ts.sys.readFile(physical(name));
|
|
34
|
+
host.fileExists = (name) => ts.sys.fileExists(physical(name));
|
|
35
|
+
host.directoryExists = (name) => ts.sys.directoryExists(physical(name));
|
|
36
|
+
host.getDirectories = (name) => ts.sys.getDirectories(physical(name));
|
|
37
|
+
host.realpath = (name) => virtual(ts.sys.realpath!(physical(name)));
|
|
38
|
+
host.getSourceFile = (name, languageVersion) => {
|
|
39
|
+
const text = host.readFile(name);
|
|
40
|
+
return text === undefined ? undefined : ts.createSourceFile(virtual(name), text, languageVersion, true);
|
|
41
|
+
};
|
|
42
|
+
const resolveModule = (name: string, containingFile: string) => {
|
|
43
|
+
const mapped = ts.resolveModuleName(name, containingFile, options, host).resolvedModule;
|
|
44
|
+
if (mapped) return mapped;
|
|
45
|
+
const resolved = ts.resolveModuleName(name, physical(containingFile), options, ts.sys).resolvedModule;
|
|
46
|
+
if (!resolved) return undefined;
|
|
47
|
+
const resolvedFileName = virtual(resolved.resolvedFileName);
|
|
48
|
+
return { ...resolved, resolvedFileName, isExternalLibraryImport: isVirtual(resolvedFileName)
|
|
49
|
+
? path.posix.relative(root, resolvedFileName).split("/").includes("node_modules") : resolved.isExternalLibraryImport };
|
|
50
|
+
};
|
|
51
|
+
host.resolveModuleNames = (names, containingFile) => names.map((name) => resolveModule(name, containingFile));
|
|
52
|
+
const program = ts.createProgram([filename], options, host);
|
|
14
53
|
const source = program.getSourceFile(filename);
|
|
15
54
|
if (!source) throw new Error("The public UI contract is missing.");
|
|
16
55
|
const checker = program.getTypeChecker();
|
|
@@ -27,8 +66,8 @@ function readContracts(repoRoot: string): { names: string[]; files: Record<strin
|
|
|
27
66
|
const collect = (file: ts.SourceFile) => {
|
|
28
67
|
if (visited.has(file.fileName)) return;
|
|
29
68
|
visited.add(file.fileName);
|
|
30
|
-
const relative = path.relative(
|
|
31
|
-
if (
|
|
69
|
+
const relative = path.posix.relative(root, file.fileName);
|
|
70
|
+
if (!isVirtual(file.fileName) || relative.includes("node_modules/")) throw new Error("A local UI type reference leaves the public repository sources.");
|
|
32
71
|
diagnostics.push(...program.getSyntacticDiagnostics(file), ...program.getSemanticDiagnostics(file));
|
|
33
72
|
let text = file.isDeclarationFile ? file.text : "";
|
|
34
73
|
if (!file.isDeclarationFile) {
|
|
@@ -41,13 +80,13 @@ function readContracts(repoRoot: string): { names: string[]; files: Record<strin
|
|
|
41
80
|
const references = ts.preProcessFile(text, true, true);
|
|
42
81
|
for (const reference of references.importedFiles) {
|
|
43
82
|
if (!reference.fileName.startsWith(".")) continue;
|
|
44
|
-
const resolved =
|
|
83
|
+
const resolved = resolveModule(reference.fileName, file.fileName);
|
|
45
84
|
const dependency = resolved && program.getSourceFile(resolved.resolvedFileName);
|
|
46
85
|
if (!dependency) throw new Error(`Unresolvable local UI type reference: ${relative}: ${reference.fileName}`);
|
|
47
86
|
collect(dependency);
|
|
48
87
|
}
|
|
49
88
|
for (const reference of declaration.referencedFiles) {
|
|
50
|
-
const dependency = program.getSourceFile(path.resolve(path.dirname(file.fileName), reference.fileName));
|
|
89
|
+
const dependency = program.getSourceFile(path.posix.resolve(path.posix.dirname(file.fileName), reference.fileName));
|
|
51
90
|
if (!dependency) throw new Error(`Unresolvable UI type file reference: ${relative}: ${reference.fileName}`);
|
|
52
91
|
collect(dependency);
|
|
53
92
|
}
|
|
@@ -76,7 +115,7 @@ export function readClientUiComponentContracts(component: string, repoRoot?: str
|
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
export function readClientUiExportContracts(component: string, repoRoot?: string): Record<string, string> {
|
|
79
|
-
const root = repoRoot ?? fileURLToPath(new URL("../../../../../", import.meta.url));
|
|
118
|
+
const root = realpathSync(repoRoot ?? fileURLToPath(new URL("../../../../../", import.meta.url)));
|
|
80
119
|
const catalog = contracts(repoRoot);
|
|
81
120
|
const files = new Map(Object.entries(catalog.files).map(([name, text]) => [path.resolve(root, name), text]));
|
|
82
121
|
const options: ts.CompilerOptions = { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler, strict: true, skipLibCheck: true };
|
|
@@ -47,7 +47,24 @@ export interface ActorProgramDefinition {
|
|
|
47
47
|
views: ActorViewDefinition[];
|
|
48
48
|
}
|
|
49
49
|
export interface ActorProgramState { version: 1; program: ActorProgramDefinition | null; }
|
|
50
|
-
|
|
50
|
+
/** A start that its program received and has not finished yet. */
|
|
51
|
+
export interface ActorScriptOpenStart { readonly count: number; readonly startedBy: string; }
|
|
52
|
+
/** Where the host took a package from: a run script's template or a plugin's shared packages. */
|
|
53
|
+
export type ActorPackageOrigin = { readonly kind: "script"; readonly entryId: string } | { readonly kind: "shared"; readonly pluginId: string };
|
|
54
|
+
/** identity is the packageIdentity of the sources the host installed; a program whose sources differ is no longer this origin's. */
|
|
55
|
+
export interface ActorScriptPackage { readonly origin: ActorPackageOrigin; readonly identity: string; readonly count: number; readonly open: readonly ActorScriptOpenStart[]; }
|
|
56
|
+
/** An input the host queues for a program to recognize, keyed by the command that queues it: a start, or the result of a start for its starter. */
|
|
57
|
+
export type ActorScriptDelivery = { readonly commandId: string; readonly actorId: string } & (
|
|
58
|
+
| { readonly kind: "start"; readonly name: string; readonly count: number; readonly embedded: boolean; readonly startedBy: string }
|
|
59
|
+
| { readonly kind: "result" });
|
|
60
|
+
/** Who an active program is, for plugins that authorize by it: a shared package, a run script's package, or a package created in the run. */
|
|
61
|
+
export type ActorProgramOrigin = ActorPackageOrigin | { readonly kind: "run"; readonly installedBy: string };
|
|
62
|
+
export interface ActorProgramIdentity { readonly name: string; readonly actorId: string; readonly revision: string; readonly origin: ActorProgramOrigin; }
|
|
63
|
+
export interface ActorScriptState {
|
|
64
|
+
readonly version: 2;
|
|
65
|
+
readonly packages: Readonly<Record<string, ActorScriptPackage>>;
|
|
66
|
+
readonly deliveries: readonly ActorScriptDelivery[];
|
|
67
|
+
}
|
|
51
68
|
|
|
52
69
|
export const resolveActorView = (programs: readonly ActorProgramDefinition[], reference: string) => {
|
|
53
70
|
const normalized = reference.trim().toLowerCase();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { serviceToken, type ActorProgramExecutor, type CommandContext } from "@ragents/engine";
|
|
2
|
+
import type { ActorProgramIdentity } from "./contract.js";
|
|
2
3
|
export interface ActorProgramSource {
|
|
3
4
|
path: string;
|
|
4
5
|
content: string;
|
|
@@ -9,8 +10,28 @@ export interface ActivatedActorProgram {
|
|
|
9
10
|
actorHandle: string;
|
|
10
11
|
views: number;
|
|
11
12
|
}
|
|
13
|
+
export interface RunScriptSources {
|
|
14
|
+
entryId: string;
|
|
15
|
+
handle: string;
|
|
16
|
+
files: readonly ActorProgramSource[];
|
|
17
|
+
programs: readonly { name: string; files: readonly ActorProgramSource[] }[];
|
|
18
|
+
sharedPrograms: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface RunScriptStartInput {
|
|
21
|
+
content: string;
|
|
22
|
+
embedded: boolean;
|
|
23
|
+
startedBy: string;
|
|
24
|
+
}
|
|
12
25
|
export interface ActorProgramsService extends ActorProgramExecutor {
|
|
13
26
|
workspaceDirectory(runId: string): Promise<string>;
|
|
14
|
-
importPackage(context: CommandContext, runId: string, name: string, files: readonly ActorProgramSource[], signal?: AbortSignal
|
|
27
|
+
importPackage(context: CommandContext, runId: string, name: string, files: readonly ActorProgramSource[], signal?: AbortSignal): Promise<ActivatedActorProgram>;
|
|
28
|
+
/** Installs a run script with its bundled programs, or reuses what the same script installed before; a failure leaves the run unchanged. */
|
|
29
|
+
installScript(context: CommandContext, runId: string, script: RunScriptSources, signal?: AbortSignal): Promise<ActivatedActorProgram>;
|
|
30
|
+
/** Queues a start input for the installed script so that its program recognizes it as a start; an embedded start also places its main view. */
|
|
31
|
+
enqueueStart(context: CommandContext, runId: string, handle: string, start: RunScriptStartInput): { count: number };
|
|
32
|
+
/** Whether a run script installed this TypeScript actor's package in the run. */
|
|
33
|
+
isScriptActor(runId: string, actorId: string): boolean;
|
|
34
|
+
/** Which package the actor runs and where it came from; a package changed in the run counts as created in the run. Authorize by this, never by handle. */
|
|
35
|
+
programOf(runId: string, actorId: string): ActorProgramIdentity | undefined;
|
|
15
36
|
}
|
|
16
37
|
export const actorProgramsToken = serviceToken<ActorProgramsService>("ragents.actor-programs.runtime");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type {
|
|
4
|
+
ActorPackageContribution,
|
|
4
5
|
PluginRegistration,
|
|
5
6
|
RAgentsPlugin,
|
|
6
7
|
SkillContribution,
|
|
@@ -8,7 +9,7 @@ import type {
|
|
|
8
9
|
} from "@ragents/engine";
|
|
9
10
|
import { skillsFromDirectory, type FolderSkills } from "./skills.js";
|
|
10
11
|
import { pluginFolder } from "./plugins-root.js";
|
|
11
|
-
import { runScriptsFromDirectory, type ScriptStartEntry } from "./run-scripts.js";
|
|
12
|
+
import { actorPackagesFromDirectory, runScriptsFromDirectory, type ScriptStartEntry } from "./run-scripts.js";
|
|
12
13
|
import { systemPromptOptionsFromDirectory, type SystemPromptOption } from "./system-prompts.js";
|
|
13
14
|
|
|
14
15
|
export { pluginFolder };
|
|
@@ -37,6 +38,11 @@ export const folderRunScripts = (
|
|
|
37
38
|
return directory ? runScriptsFromDirectory(directory, pluginId) : [];
|
|
38
39
|
};
|
|
39
40
|
|
|
41
|
+
export const folderActorPackages = (folder: string): readonly ActorPackageContribution[] => {
|
|
42
|
+
const directory = assetFolder(folder, "actors");
|
|
43
|
+
return directory ? actorPackagesFromDirectory(directory) : [];
|
|
44
|
+
};
|
|
45
|
+
|
|
40
46
|
export const folderSkillPaths = (folder: string): readonly string[] => folderSkills(folder, path.basename(folder)).paths;
|
|
41
47
|
|
|
42
48
|
export const folderSystemPrompts = (folder: string): readonly SystemPromptOption[] => {
|
|
@@ -48,6 +54,7 @@ export const withFolderAssets = (plugin: RAgentsPlugin): RAgentsPlugin => ({
|
|
|
48
54
|
manifest: plugin.manifest,
|
|
49
55
|
register: (host) => {
|
|
50
56
|
const explicitEntries: StartEntryContribution[] = [];
|
|
57
|
+
const explicitPackages: ActorPackageContribution[] = [];
|
|
51
58
|
const explicitSkills: SkillContribution[] = [];
|
|
52
59
|
const observed: PluginRegistration = {
|
|
53
60
|
...host,
|
|
@@ -55,6 +62,10 @@ export const withFolderAssets = (plugin: RAgentsPlugin): RAgentsPlugin => ({
|
|
|
55
62
|
explicitEntries.push(...contributions);
|
|
56
63
|
host.startEntries(...contributions);
|
|
57
64
|
},
|
|
65
|
+
actorPackages: (...contributions) => {
|
|
66
|
+
explicitPackages.push(...contributions);
|
|
67
|
+
host.actorPackages(...contributions);
|
|
68
|
+
},
|
|
58
69
|
skills: (...contributions) => {
|
|
59
70
|
explicitSkills.push(...contributions);
|
|
60
71
|
host.skills(...contributions);
|
|
@@ -69,6 +80,8 @@ export const withFolderAssets = (plugin: RAgentsPlugin): RAgentsPlugin => ({
|
|
|
69
80
|
...folderRunScripts(folder, plugin.manifest.id),
|
|
70
81
|
].filter((entry) => !explicitEntries.some((explicit) => explicit.id === entry.id));
|
|
71
82
|
if (entries.length > 0) host.startEntries(...entries);
|
|
83
|
+
const packages = folderActorPackages(folder).filter((entry) => !explicitPackages.some((explicit) => explicit.name === entry.name));
|
|
84
|
+
if (packages.length > 0) host.actorPackages(...packages);
|
|
72
85
|
|
|
73
86
|
const skillPaths = skills.paths;
|
|
74
87
|
if (skillPaths.length === 0) return;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { ActorProgramFile, JsonValue, StartEntryContribution } from "@ragents/engine";
|
|
3
|
+
import type { ActorPackageContribution, ActorProgramFile, JsonValue, StartEntryContribution } from "@ragents/engine";
|
|
4
4
|
import { flag, frontMatterOf, numberOf, tagsOf } from "./front-matter.js";
|
|
5
5
|
|
|
6
6
|
export type ScriptStartEntry = Extract<StartEntryContribution, { action: "script" }>;
|
|
7
7
|
|
|
8
8
|
const PACKAGE_NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
9
|
-
const RUN_FIELDS = ["title", "description", "order", "guide", "tags", "coordinator", "category", "fixed-start-options"];
|
|
9
|
+
const RUN_FIELDS = ["title", "description", "order", "guide", "tags", "coordinator", "embeddable", "shared-programs", "category", "fixed-start-options"];
|
|
10
10
|
|
|
11
11
|
const requiredFile = (directory: string, name: string): string => {
|
|
12
12
|
const file = path.join(directory, name);
|
|
@@ -14,6 +14,16 @@ const requiredFile = (directory: string, name: string): string => {
|
|
|
14
14
|
return file;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
/** The header line shared-programs names shared actor packages of the profile, separated by commas. */
|
|
18
|
+
const sharedProgramsOf = (file: string, value: string | undefined): readonly string[] | undefined => {
|
|
19
|
+
if (value === undefined) return undefined;
|
|
20
|
+
const names = value.split(",").map((name) => name.trim());
|
|
21
|
+
if (names.some((name) => !PACKAGE_NAME.test(name)) || new Set(names).size !== names.length) {
|
|
22
|
+
throw new Error(`${file}: shared-programs must name each shared actor package once, separated by commas`);
|
|
23
|
+
}
|
|
24
|
+
return names;
|
|
25
|
+
};
|
|
26
|
+
|
|
17
27
|
/** The header line fixed-start-options is a JSON object on one line: start option id to fixed value. */
|
|
18
28
|
const fixedStartOptionsOf = (file: string, value: string | undefined): Readonly<Record<string, JsonValue>> | undefined => {
|
|
19
29
|
if (value === undefined) return undefined;
|
|
@@ -60,6 +70,7 @@ const runScriptFrom = (directory: string, name: string, id: string): ScriptStart
|
|
|
60
70
|
const category = fields.get("category");
|
|
61
71
|
if (category !== undefined && !category.trim()) throw new Error(`${runFile}: category is empty`);
|
|
62
72
|
const fixedStartOptions = fixedStartOptionsOf(runFile, fields.get("fixed-start-options"));
|
|
73
|
+
const sharedPrograms = sharedProgramsOf(runFile, fields.get("shared-programs"));
|
|
63
74
|
const programsDirectory = path.join(directory, "actors");
|
|
64
75
|
const programsStats = statSync(programsDirectory, {throwIfNoEntry: false});
|
|
65
76
|
if (programsStats && !programsStats.isDirectory()) throw new Error(`${programsDirectory} must be a directory`);
|
|
@@ -80,6 +91,8 @@ const runScriptFrom = (directory: string, name: string, id: string): ScriptStart
|
|
|
80
91
|
script: {
|
|
81
92
|
handle: name,
|
|
82
93
|
coordinator: flag(runFile, "coordinator", fields.get("coordinator"), true),
|
|
94
|
+
embeddable: flag(runFile, "embeddable", fields.get("embeddable")),
|
|
95
|
+
...(sharedPrograms !== undefined ? { sharedPrograms } : {}),
|
|
83
96
|
files: filesOf(directory),
|
|
84
97
|
programs,
|
|
85
98
|
},
|
|
@@ -98,6 +111,18 @@ export const runScriptsFromDirectory = (directory: string, idPrefix: string): re
|
|
|
98
111
|
return packages.map((name) => runScriptFrom(path.join(resolved, name), name, `${idPrefix}.${name}`));
|
|
99
112
|
};
|
|
100
113
|
|
|
114
|
+
/** Every folder is one shared actor package, named after the folder. */
|
|
115
|
+
export const actorPackagesFromDirectory = (directory: string): readonly ActorPackageContribution[] => {
|
|
116
|
+
const resolved = path.resolve(directory);
|
|
117
|
+
const entries = readdirSync(resolved, {withFileTypes: true}).sort((left, right) => left.name.localeCompare(right.name));
|
|
118
|
+
const stray = entries.filter((entry) => !entry.isDirectory()).map((entry) => entry.name);
|
|
119
|
+
if (stray.length > 0) throw new Error(`${resolved} contains files instead of actor package folders: ${stray.join(", ")}`);
|
|
120
|
+
return entries.map((entry) => {
|
|
121
|
+
if (!PACKAGE_NAME.test(entry.name)) throw new Error(`${resolved}: the folder name ${entry.name} is not a package name (lowercase letters, digits, hyphen)`);
|
|
122
|
+
return {name: entry.name, files: filesOf(path.join(resolved, entry.name))};
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
101
126
|
export const runScriptFromDirectory = (directory: string): ScriptStartEntry => {
|
|
102
127
|
if (!path.isAbsolute(directory)) throw new Error("The run script package needs an absolute server file path");
|
|
103
128
|
const resolved = path.resolve(directory);
|
|
@@ -6,6 +6,7 @@ import { aliasCompletionModel, aliasCompletionModelToken } from "../plugin-suppo
|
|
|
6
6
|
import { withFolderAssets } from "../plugin-support/plugin-folder.js";
|
|
7
7
|
import type { PluginModule } from "../plugin-support/plugin-module.js";
|
|
8
8
|
import { runManagementToken } from "../ragents/global-chat.js";
|
|
9
|
+
import { registerRunScriptFunctions } from "../ragents/run-script-tools.js";
|
|
9
10
|
import { registerTypeScriptFunctions } from "../ragents/typescript-tools.js";
|
|
10
11
|
import {
|
|
11
12
|
executorContributionsToken,
|
|
@@ -84,6 +85,7 @@ export const composeProfile = (options: ProfileComposition, bridges: HostBridges
|
|
|
84
85
|
}));
|
|
85
86
|
}
|
|
86
87
|
registerTypeScriptFunctions(host);
|
|
88
|
+
registerRunScriptFunctions(host);
|
|
87
89
|
host.seal();
|
|
88
90
|
return host;
|
|
89
91
|
};
|