@schlenkr/ragents 0.1.13 → 0.1.15
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 +61 -17
- 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 +31 -6
- 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/server/src/title-compactor.ts +1 -1
- package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-CpPi6QLr.js → highlighted-body-KPVGNVTW-BwD4uYEe.js} +1 -1
- package/apps/web/dist/assets/main-KBrc5Q77.js +11 -0
- package/apps/web/dist/assets/{mermaid-HWGCJPDP-q-mf1kLN.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 +16 -2
- package/apps/web/dist/host-web.json +13 -13
- 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/App.tsx +12 -1
- package/apps/web/src/PluginRegistry.tsx +2 -2
- package/apps/web/src/SessionList.tsx +7 -6
- package/apps/web/src/SettingsModal.tsx +3 -3
- package/apps/web/src/actor-programs/client-ui/FilePicker.tsx +1 -1
- package/apps/web/src/api.ts +2 -0
- package/apps/web/src/run-overview.ts +1 -1
- package/apps/web/src/run-panel/RunPanelApp.tsx +1 -1
- package/apps/web/src/run-panel/RunPanelMenu.tsx +47 -11
- package/apps/web/src/ui/relative-time.ts +1 -1
- 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 +2 -2
- package/bundles/ragents.documents/web/classes.json +1 -1
- package/bundles/ragents.documents/web/index.js +1 -1
- package/bundles/ragents.documents/web/index.js.map +2 -2
- 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.orchestration/web/index.js +3 -3
- package/bundles/ragents.orchestration/web/index.js.map +2 -2
- package/bundles/ragents.overseer/ragents-bundle.json +4 -3
- package/bundles/ragents.overseer/web/classes.json +1 -1
- package/bundles/ragents.overseer/web/index.js +1 -1
- package/bundles/ragents.overseer/web/index.js.map +4 -4
- package/bundles/ragents.processes/ragents-bundle.json +2 -2
- package/bundles/ragents.processes/web/classes.json +1 -1
- package/bundles/ragents.processes/web/index.js +1 -1
- package/bundles/ragents.processes/web/index.js.map +2 -2
- 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 +2 -2
- package/bundles/ragents.workspace/web/classes.json +1 -1
- package/bundles/ragents.workspace/web/index.js +1 -1
- package/bundles/ragents.workspace/web/index.js.map +2 -2
- 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.documents/web/DocumentViewer.tsx +2 -2
- 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.orchestration/web/ExecutionsPanel.tsx +1 -1
- package/plugins/ragents.orchestration/web/FlowInspector.tsx +1 -1
- package/plugins/ragents.orchestration/web/JournalStatus.tsx +1 -1
- package/plugins/ragents.orchestration/web/format.ts +1 -1
- package/plugins/ragents.overseer/web/index.tsx +51 -3
- package/plugins/ragents.processes/web/processes.ts +1 -1
- 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/plugins/ragents.workspace/web/FileBrowser.tsx +1 -1
- package/scripts/agent/agent-cli.ts +46 -2
- package/scripts/package/ragents.mjs +3 -1
- package/apps/web/dist/assets/main-DJmCHGzM.js +0 -11
- package/apps/web/dist/assets/runPanel-nk_aMxSn.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)$/;
|
|
@@ -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
|
});
|
|
@@ -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
|
};
|
|
@@ -4,7 +4,7 @@ import { existsSync, statSync } from "node:fs";
|
|
|
4
4
|
import { chmod, mkdir, open, readFile, readdir, realpath, rename, rm } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import type { ChatSessionLike, ChatSessionProvider, RunListScope, SessionInfo } from "./chat-handler.js";
|
|
7
|
-
import { DomainError, isRunId, unrestrictedAccess, type AccessContext, type HttpRouteContribution, type Journal, type JournalLoadFailure, type MethodContribution, type PluginHost, type ServiceToken, type SessionStartedContext } from "@ragents/engine";
|
|
7
|
+
import { canStartEntry, createAccessContext, DomainError, isRunId, unrestrictedAccess, type AccessContext, type HttpRouteContribution, type Journal, type JournalLoadFailure, type MethodContribution, type PluginHost, type ServiceToken, type SessionStartedContext } from "@ragents/engine";
|
|
8
8
|
import { WORKSPACE_EXECUTOR_VERSION } from "@ragents/workspace-executor";
|
|
9
9
|
import { assertRunRights, assertRunWorkspaceAccess, runIdInPath, runListScope, runReachable, type GlobalRunPolicy, type RunAccessPolicy } from "./api/rights.js";
|
|
10
10
|
import { configuredAnonymousUser, configuredUsers } from "./config-file.js";
|
|
@@ -42,7 +42,7 @@ import { RunChatSession } from "./ragents/session.js";
|
|
|
42
42
|
import { globalChatToken, globalRunPolicyOf, type GlobalChatPolicy, type ManagedRunStart, type RunManagement } from "./ragents/global-chat.js";
|
|
43
43
|
import { workspaceRuntimeToken, type SessionWorkspace, type WorkspaceTransfer } from "./ragents/workspace-runtime.js";
|
|
44
44
|
import { settingsResponse, skillDetailResponse, type SettingsResponse, type SettingsSkillDetail } from "./settings.js";
|
|
45
|
-
import { createTitleCompactor } from "./title-compactor.js";
|
|
45
|
+
import { createTitleCompactor, MAX_TITLE_CHARS } from "./title-compactor.js";
|
|
46
46
|
import { ModelRuntime } from "@ragents/agent";
|
|
47
47
|
import { TitleSettingsStore } from "./title-settings.js";
|
|
48
48
|
import { sandboxServicesToken } from "./plugin-support/workspace-sandbox-host.js";
|
|
@@ -59,6 +59,8 @@ type ListedSession = SessionInfo & {
|
|
|
59
59
|
running: boolean;
|
|
60
60
|
/** Whether the caller reaches the run's workspace; otherwise UIs hide what needs it. */
|
|
61
61
|
workspaceAccessible: boolean;
|
|
62
|
+
/** Display name of the user who created the run; missing for a run created without sign-in. */
|
|
63
|
+
ownerLabel?: string;
|
|
62
64
|
metadata?: Readonly<Record<string, unknown>>;
|
|
63
65
|
metadataUnavailable?: Readonly<Record<string, string>>;
|
|
64
66
|
};
|
|
@@ -246,9 +248,9 @@ export class RunSessionProvider implements ChatSessionProvider {
|
|
|
246
248
|
scriptEntryFor: (entryId) => globalPolicy ? undefined : this.plugins.startEntries.scriptPackage(entryId),
|
|
247
249
|
startEntryFor: (entryId) => globalPolicy ? undefined : this.plugins.startEntries.entry(entryId),
|
|
248
250
|
actorPrograms: {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
installScript: (...args) => this.plugins.service(actorProgramsToken).installScript(...args),
|
|
252
|
+
enqueueStart: (...args) => this.plugins.service(actorProgramsToken).enqueueStart(...args),
|
|
253
|
+
isScriptActor: (runId, actorId) => this.plugins.optionalService(actorProgramsToken)?.isScriptActor(runId, actorId) ?? false,
|
|
252
254
|
},
|
|
253
255
|
});
|
|
254
256
|
this.sessions.set(id, session);
|
|
@@ -295,6 +297,8 @@ export class RunSessionProvider implements ChatSessionProvider {
|
|
|
295
297
|
const isCoordinator = (runId: string) => this.plugins.optionalService(globalChatToken)?.isCoordinator(runId) ?? false;
|
|
296
298
|
const listed = engine.journal.runIds().filter((runId) => !isCoordinator(runId)
|
|
297
299
|
&& !this.deleteRequested.has(runId) && !this.deleted.has(runId) && visible(runId));
|
|
300
|
+
const anonymousUser = configuredAnonymousUser();
|
|
301
|
+
const userLabels = new Map([...configuredUsers() ?? [], ...anonymousUser ? [anonymousUser] : []].map((user) => [user.id, user.label]));
|
|
298
302
|
const described = await Promise.all(listed.map(async (runId): Promise<ListedSession | undefined> => {
|
|
299
303
|
const state = engine.journal.stateOf(runId);
|
|
300
304
|
if (!state) return undefined;
|
|
@@ -317,6 +321,7 @@ export class RunSessionProvider implements ChatSessionProvider {
|
|
|
317
321
|
running: [...state.actors.values()].some((actor) => actor.kind !== "human" && engine.scheduler.isRunning(runId, actor.id))
|
|
318
322
|
|| (this.sessions.get(runId)?.running ?? false),
|
|
319
323
|
workspaceAccessible: workspace,
|
|
324
|
+
...(state.ownerUserId !== null ? { ownerLabel: userLabels.get(state.ownerUserId) ?? state.ownerUserId } : {}),
|
|
320
325
|
metadata: metadata.values,
|
|
321
326
|
...(Object.keys(metadata.unavailable).length > 0 ? { metadataUnavailable: metadata.unavailable } : {}),
|
|
322
327
|
};
|
|
@@ -358,9 +363,28 @@ export class RunSessionProvider implements ChatSessionProvider {
|
|
|
358
363
|
},
|
|
359
364
|
stop: (runId) => this.openSession(runId).stop(),
|
|
360
365
|
resetGlobal: (runId) => this.resetGlobalConversation(runId),
|
|
366
|
+
scripts: (runId) => {
|
|
367
|
+
const access = this.ownerAccess(runId);
|
|
368
|
+
return this.openSession(runId).runScripts(this.plugins.startEntries.describe().filter((entry) => canStartEntry(access, entry.id)), access.user?.id ?? null);
|
|
369
|
+
},
|
|
370
|
+
startScript: (runId, entryId, input, startedBy) => {
|
|
371
|
+
const access = this.ownerAccess(runId);
|
|
372
|
+
if (!canStartEntry(access, entryId)) throw new DomainError("access-denied", `The run script ${entryId} is not enabled for the owner of this run.`, 403);
|
|
373
|
+
return this.openSession(runId).startAndWait(entryId, input, access.user ? { id: access.user.id, label: access.user.label } : undefined, startedBy);
|
|
374
|
+
},
|
|
361
375
|
};
|
|
362
376
|
}
|
|
363
377
|
|
|
378
|
+
/** The owner's access as a user of this profile; the run's own actors start run scripts with it. */
|
|
379
|
+
private ownerAccess(runId: string): AccessContext {
|
|
380
|
+
this.ensureUsable(runId);
|
|
381
|
+
const users = configuredUsers();
|
|
382
|
+
const ownerUserId = this.requireEngine().journal.stateOf(runId)?.ownerUserId ?? null;
|
|
383
|
+
return createAccessContext(ownerUserId === null
|
|
384
|
+
? { enabled: users !== undefined, user: users ? null : configuredAnonymousUser() ?? null }
|
|
385
|
+
: { enabled: true, user: users?.find((user) => user.id === ownerUserId) ?? null });
|
|
386
|
+
}
|
|
387
|
+
|
|
364
388
|
private async createManagedRun(start: ManagedRunStart): Promise<string> {
|
|
365
389
|
const local = start.kind === "package" ? runScriptFromDirectory(start.directory) : undefined;
|
|
366
390
|
const id = randomUUID();
|
|
@@ -924,5 +948,6 @@ const lockedSession = (failure: JournalLoadFailure, journal: Journal): ListedSes
|
|
|
924
948
|
const sessionTitle = (text: string | undefined): string | undefined => {
|
|
925
949
|
const lines = text?.split(/\r?\n/).map((line) => line.trim()).filter(Boolean) ?? [];
|
|
926
950
|
if (lines.length === 0) return undefined;
|
|
927
|
-
|
|
951
|
+
const title = lines[0]?.startsWith("[Test:") && lines[1] ? `${lines[0]} ${lines[1]}` : lines[0] ?? "";
|
|
952
|
+
return title.length > MAX_TITLE_CHARS ? `${title.slice(0, MAX_TITLE_CHARS - 3)}...` : title;
|
|
928
953
|
};
|
|
@@ -11,6 +11,8 @@ export interface ChatProjectionScope {
|
|
|
11
11
|
primaryActorId: string;
|
|
12
12
|
ownerId: string;
|
|
13
13
|
labelOf: (actorId: string) => string | undefined;
|
|
14
|
+
/** The handle of a TypeScript actor a run script installed; the chat shows its runtime output too. */
|
|
15
|
+
scriptActorHandle?: (actorId: string) => string | undefined;
|
|
14
16
|
turnOf: (turnId: string) => Pick<Turn, "actorId" | "toolCalls">;
|
|
15
17
|
/** Whether this command also interrupted a turn of the actor; its stop then repeats no reason. */
|
|
16
18
|
interruptedByCommand: (commandId: string, actorId: string) => boolean;
|
|
@@ -51,6 +53,11 @@ export const chatEventsOf = (event: JournalEvent, scope: ChatProjectionScope, cu
|
|
|
51
53
|
if (event.type === "turn.interrupted")
|
|
52
54
|
return scope.turnOf(event.payload.turnId).actorId === scope.primaryActorId ? journalChatEventsOf(event, cursor, interruptedIn(scope)) : [];
|
|
53
55
|
|
|
56
|
+
if (event.type === "runtime.output.recorded" && event.actorId !== scope.primaryActorId) {
|
|
57
|
+
const handle = scope.scriptActorHandle?.(event.actorId);
|
|
58
|
+
return handle ? [{ kind: "system", text: `@${handle}: ${event.payload.text}`, at }] : [];
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
if (event.actorId !== scope.primaryActorId) return [];
|
|
55
62
|
|
|
56
63
|
if (event.type === "plugin.state-replaced" || event.type === "plugin.state-patched") return [{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { serviceToken, type AccessContext, type CatalogModel, type ExecutableActor, type JournalEvent, type JsonValue, type ModelSelection, type Orchestration, type RunView } from "@ragents/engine";
|
|
2
2
|
import type { GlobalRunPolicy } from "../api/rights.js";
|
|
3
|
-
import type { ChatUser, SessionInfo } from "../chat-handler.js";
|
|
3
|
+
import type { ChatUser, RunScriptListing, SessionInfo, StartedScript } from "../chat-handler.js";
|
|
4
4
|
|
|
5
5
|
export interface GlobalChatPolicy {
|
|
6
6
|
/** A user's coordinator; without sign-in (null) there is exactly one. */
|
|
@@ -49,6 +49,10 @@ export interface RunManagement {
|
|
|
49
49
|
send: (runId: string, message: string, access: AccessContext) => Promise<void>;
|
|
50
50
|
stop: (runId: string) => Promise<void>;
|
|
51
51
|
resetGlobal: (runId: string) => Promise<void>;
|
|
52
|
+
/** The run scripts the run's owner may start, and whether each can start in the run now. */
|
|
53
|
+
scripts: (runId: string) => readonly RunScriptListing[];
|
|
54
|
+
/** Starts a run script in the run as its owner would; startedBy is the actor that receives its result. */
|
|
55
|
+
startScript: (runId: string, entryId: string, input: JsonValue | null, startedBy: string) => Promise<StartedScript>;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
export const globalChatToken = serviceToken<GlobalChatPolicy>("host.global-chat");
|