@schlenkr/ragents 0.1.14 → 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.
Files changed (113) hide show
  1. package/apps/server/package.json +1 -1
  2. package/apps/server/src/api/contracts.ts +19 -1
  3. package/apps/server/src/api/core-methods.ts +12 -0
  4. package/apps/server/src/chat-handler.ts +19 -0
  5. package/apps/server/src/host-api.json +2 -0
  6. package/apps/server/src/host-api.ts +2 -2
  7. package/apps/server/src/plugin-build/build.ts +1 -1
  8. package/apps/server/src/plugin-support/actor-programs/app-project.ts +61 -17
  9. package/apps/server/src/plugin-support/actor-programs/contract.ts +18 -1
  10. package/apps/server/src/plugin-support/actor-programs/service.ts +22 -1
  11. package/apps/server/src/plugin-support/plugin-folder.ts +14 -1
  12. package/apps/server/src/plugin-support/run-scripts.ts +27 -2
  13. package/apps/server/src/profile/compose.ts +2 -0
  14. package/apps/server/src/provider.ts +23 -4
  15. package/apps/server/src/ragents/chat-projection.ts +7 -0
  16. package/apps/server/src/ragents/global-chat.ts +5 -1
  17. package/apps/server/src/ragents/run-script-tools.ts +66 -0
  18. package/apps/server/src/ragents/session.ts +122 -60
  19. package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-BslIwn_y.js → highlighted-body-KPVGNVTW-BwD4uYEe.js} +1 -1
  20. package/apps/web/dist/assets/{main-BUN8m8SW.js → main-KBrc5Q77.js} +1 -1
  21. package/apps/web/dist/assets/{mermaid-HWGCJPDP-B1erZlFM.js → mermaid-HWGCJPDP-CSCkaArr.js} +88 -88
  22. package/apps/web/dist/assets/runPanel-BjG5NBKm.js +26 -0
  23. package/apps/web/dist/help/guide-clients.html +21 -4
  24. package/apps/web/dist/help/guide-clients.md +23 -4
  25. package/apps/web/dist/help/guide-plugins.html +12 -2
  26. package/apps/web/dist/help/guide-plugins.md +9 -2
  27. package/apps/web/dist/help/guide-programs.html +8 -2
  28. package/apps/web/dist/help/guide-programs.md +9 -2
  29. package/apps/web/dist/help/guide-runtime.html +3 -2
  30. package/apps/web/dist/help/guide-runtime.md +3 -2
  31. package/apps/web/dist/help/index.html +1 -1
  32. package/apps/web/dist/help/mini-app.css +7 -2
  33. package/apps/web/dist/host-web.json +6 -6
  34. package/apps/web/dist/index.html +2 -2
  35. package/apps/web/dist/run-panel.html +2 -2
  36. package/apps/web/package.json +1 -1
  37. package/apps/web/src/run-panel/RunPanelApp.tsx +1 -1
  38. package/apps/web/src/run-panel/RunPanelMenu.tsx +47 -11
  39. package/bundles/ragents.activity/ragents-bundle.json +1 -1
  40. package/bundles/ragents.actor-programs/ragents-bundle.json +4 -2
  41. package/bundles/ragents.actor-programs/server/index.js +437 -24
  42. package/bundles/ragents.actor-programs/server/index.js.map +4 -4
  43. package/bundles/ragents.actor-programs/web/index.js.map +1 -1
  44. package/bundles/ragents.ask/ragents-bundle.json +2 -2
  45. package/bundles/ragents.ask/server/index.js +1 -0
  46. package/bundles/ragents.ask/server/index.js.map +2 -2
  47. package/bundles/ragents.browser/ragents-bundle.json +1 -1
  48. package/bundles/ragents.documents/ragents-bundle.json +1 -1
  49. package/bundles/ragents.lsp-fsharp/ragents-bundle.json +1 -1
  50. package/bundles/ragents.lsp-roslyn/ragents-bundle.json +1 -1
  51. package/bundles/ragents.lsp-typescript/ragents-bundle.json +1 -1
  52. package/bundles/ragents.model-relay/ragents-bundle.json +1 -1
  53. package/bundles/ragents.orchestration/ragents-bundle.json +7 -4
  54. package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js +11 -0
  55. package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js.map +7 -0
  56. package/bundles/ragents.orchestration/server/exports/server/contract.js +9 -0
  57. package/bundles/ragents.orchestration/server/exports/server/contract.js.map +7 -0
  58. package/bundles/ragents.orchestration/server/index.js +49 -3
  59. package/bundles/ragents.orchestration/server/index.js.map +2 -2
  60. package/bundles/ragents.orchestration/surface.hbs +5 -0
  61. package/bundles/ragents.overseer/ragents-bundle.json +1 -1
  62. package/bundles/ragents.processes/ragents-bundle.json +1 -1
  63. package/bundles/ragents.product/ragents-bundle.json +1 -1
  64. package/bundles/ragents.profile-distribution/ragents-bundle.json +1 -1
  65. package/bundles/ragents.reference/actors/notebook/package.json +16 -0
  66. package/bundles/ragents.reference/actors/notebook/src/client.tsx +31 -0
  67. package/bundles/ragents.reference/actors/notebook/src/server.ts +27 -0
  68. package/bundles/ragents.reference/actors/notebook/tests/program.test.ts +27 -0
  69. package/bundles/ragents.reference/ragents-bundle.json +4 -3
  70. package/bundles/ragents.reference/run-scripts/quick-note/RUN.md +14 -0
  71. package/bundles/ragents.reference/run-scripts/quick-note/package.json +9 -0
  72. package/bundles/ragents.reference/run-scripts/quick-note/src/server.ts +29 -0
  73. package/bundles/ragents.reference/run-scripts/quick-note/tests/program.test.ts +43 -0
  74. package/bundles/ragents.reference/run-scripts/run-roster/RUN.md +19 -0
  75. package/bundles/ragents.reference/run-scripts/run-roster/package.json +9 -0
  76. package/bundles/ragents.reference/run-scripts/run-roster/src/server.ts +26 -0
  77. package/bundles/ragents.reference/run-scripts/run-roster/tests/program.test.ts +47 -0
  78. package/bundles/ragents.todo/ragents-bundle.json +1 -1
  79. package/bundles/ragents.transcript/ragents-bundle.json +1 -1
  80. package/bundles/ragents.watch/ragents-bundle.json +1 -1
  81. package/bundles/ragents.workspace/ragents-bundle.json +1 -1
  82. package/package.json +3 -3
  83. package/packages/ragents/src/domain/vocabulary.ts +4 -0
  84. package/packages/ragents/src/index.ts +3 -1
  85. package/packages/ragents/src/plugin-host.ts +62 -10
  86. package/packages/ragents/src/plugin-types.ts +11 -0
  87. package/packages/ragents/src/runtime/guards.ts +2 -1
  88. package/packages/ragents/src/typescript/async-compiler.ts +11 -1
  89. package/plugins/ragents.actor-programs/server/index.ts +3 -0
  90. package/plugins/ragents.actor-programs/server/runtime.ts +424 -28
  91. package/plugins/ragents.actor-programs/server/script-state.ts +54 -0
  92. package/plugins/ragents.actor-programs/server/tool-contributor.ts +2 -0
  93. package/plugins/ragents.ask/server/ask-tool.ts +1 -0
  94. package/plugins/ragents.orchestration/ragents-plugin.json +1 -0
  95. package/plugins/ragents.orchestration/server/contract.ts +14 -0
  96. package/plugins/ragents.orchestration/server/index.ts +4 -1
  97. package/plugins/ragents.orchestration/server/surface-tool.ts +58 -2
  98. package/plugins/ragents.orchestration/surface.hbs +5 -0
  99. package/plugins/ragents.reference/actors/notebook/package.json +16 -0
  100. package/plugins/ragents.reference/actors/notebook/src/client.tsx +31 -0
  101. package/plugins/ragents.reference/actors/notebook/src/server.ts +27 -0
  102. package/plugins/ragents.reference/actors/notebook/tests/program.test.ts +27 -0
  103. package/plugins/ragents.reference/run-scripts/quick-note/RUN.md +14 -0
  104. package/plugins/ragents.reference/run-scripts/quick-note/package.json +9 -0
  105. package/plugins/ragents.reference/run-scripts/quick-note/src/server.ts +29 -0
  106. package/plugins/ragents.reference/run-scripts/quick-note/tests/program.test.ts +43 -0
  107. package/plugins/ragents.reference/run-scripts/run-roster/RUN.md +19 -0
  108. package/plugins/ragents.reference/run-scripts/run-roster/package.json +9 -0
  109. package/plugins/ragents.reference/run-scripts/run-roster/src/server.ts +26 -0
  110. package/plugins/ragents.reference/run-scripts/run-roster/tests/program.test.ts +47 -0
  111. package/scripts/agent/agent-cli.ts +46 -2
  112. package/scripts/package/ragents.mjs +3 -1
  113. package/apps/web/dist/assets/runPanel-s8gGpr9M.js +0 -21
@@ -0,0 +1,27 @@
1
+ import { defineActor } from "@ragents/server";
2
+ import { Type } from "typebox";
3
+
4
+ const KEPT_NOTES = 50;
5
+
6
+ const contract = {
7
+ state: Type.Object({ notes: Type.Optional(Type.Array(Type.String())) }, { additionalProperties: false }),
8
+ functions: {
9
+ clear: { label: "Clear notes", input: Type.Object({}, { additionalProperties: false }), output: Type.Object({ cleared: Type.Integer() }, { additionalProperties: false }) },
10
+ },
11
+ input: { capabilities: [] },
12
+ } as const;
13
+
14
+ export default defineActor(contract, {
15
+ functions: {
16
+ clear: (_input, context) => {
17
+ const cleared = context.state.read().notes?.length ?? 0;
18
+ context.state.replace({ notes: [] });
19
+ return { cleared };
20
+ },
21
+ },
22
+ onInput: (input, context) => {
23
+ const note = input.content.trim();
24
+ if (!note) throw new Error("A note needs text.");
25
+ context.state.replace({ notes: [note, ...context.state.read().notes ?? []].slice(0, KEPT_NOTES) });
26
+ },
27
+ });
@@ -0,0 +1,27 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createTestContext } from "@ragents/server/testing";
4
+ import program from "../src/server.ts";
5
+
6
+ const input = (content: string) => ({ id: "input-1", content, artifactIds: [], sourceEventIds: [], subscriptionId: null, event: null });
7
+
8
+ test("keeps notes newest first and at most fifty", async () => {
9
+ const context = createTestContext<{ notes?: string[] }>({ state: { notes: Array.from({ length: 50 }, (_, index) => `old ${index}`) } });
10
+ await program.onInput!(input(" new note "), context);
11
+ const notes = context.state.read().notes!;
12
+ assert.equal(notes.length, 50);
13
+ assert.equal(notes[0], "new note");
14
+ assert.equal(notes.at(-1), "old 48");
15
+ });
16
+
17
+ test("an empty note is refused and changes nothing", async () => {
18
+ const context = createTestContext<{ notes?: string[] }>({ state: { notes: ["kept"] } });
19
+ await assert.rejects(async () => program.onInput!(input(" "), context), /needs text/);
20
+ assert.deepEqual(context.state.read(), { notes: ["kept"] });
21
+ });
22
+
23
+ test("clear empties the notebook and names how many notes it removed", async () => {
24
+ const context = createTestContext<{ notes?: string[] }>({ state: { notes: ["one", "two"] } });
25
+ assert.deepEqual(await program.functions.clear({}, context), { cleared: 2 });
26
+ assert.deepEqual(context.state.read(), { notes: [] });
27
+ });
@@ -20,8 +20,8 @@
20
20
  ]
21
21
  }
22
22
  },
23
- "revision": "809c8770905fa2a4d557425abf27aee27a863aec8a80655b96e40bd82307f493",
24
- "sourceRevision": "abe068f9695702223dd49b2169ecca54e5a82fd4b37d02346d0c9df3582cff87",
23
+ "revision": "2c684e9daab9b2ffe4c002c03d4fa8549b2d4dd0b20513fb5eec0e4f10a396ee",
24
+ "sourceRevision": "0f2f0bb43908bed73e743ed21d07d35abed4bcc84563c893c5c32117ad559ade",
25
25
  "server": "server/index.js",
26
26
  "web": {
27
27
  "entry": "web/index.js",
@@ -34,6 +34,7 @@
34
34
  "uses": [],
35
35
  "assets": [
36
36
  "skills",
37
- "run-scripts"
37
+ "run-scripts",
38
+ "actors"
38
39
  ]
39
40
  }
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Add a quick note
3
+ description: "A prepared one-step script shows a shared actor package: it adds a note to the notebook it shares with the roster check, in a new or a running run."
4
+ order: 180
5
+ coordinator: false
6
+ embeddable: true
7
+ shared-programs: notebook
8
+ tags: Run scripts, Concept demo, TypeScript actors
9
+ ---
10
+
11
+ The start value `{ "text": "..." }` is the note; without one, the script notes when it was
12
+ started. The script makes the shared notebook active with `actor_program_ensure`, sends it the
13
+ note, places the notebook's view with `canvas_layout_place`, and ends the start with
14
+ `context.finish`. Whether the roster check or this script comes first, the run has one notebook.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "quick-note",
3
+ "private": true,
4
+ "type": "module",
5
+ "ragents": {
6
+ "title": "Quick note",
7
+ "backend": "src/server.ts"
8
+ }
9
+ }
@@ -0,0 +1,29 @@
1
+ import { defineActor } from "@ragents/server";
2
+ import { Type } from "typebox";
3
+
4
+ const contract = {
5
+ state: Type.Object({}, { additionalProperties: false }),
6
+ functions: {},
7
+ input: { capabilities: ["actor_program_ensure", "actor_input", "canvas_layout_place"] },
8
+ } as const;
9
+
10
+ const noteOf = (input: unknown, now: string): string => {
11
+ if (input === null) return `Started at ${now}.`;
12
+ const text = typeof input === "object" && input !== null && "text" in input ? (input as { text: unknown }).text : undefined;
13
+ if (typeof text !== "string" || !text.trim()) throw new Error('The start value is { "text": "..." } with the note, or none.');
14
+ return text.trim();
15
+ };
16
+
17
+ export default defineActor(contract, {
18
+ functions: {},
19
+ onInput: () => {
20
+ throw new Error("The quick note answers only starts; start it again with the note as its start value.");
21
+ },
22
+ onStart: async (start, context) => {
23
+ const note = noteOf(start.input, context.std.now());
24
+ const notebook = await context.functions.actor_program_ensure({ name: "notebook" });
25
+ await context.functions.actor_input({ actor: `@${notebook.handle}`, content: note });
26
+ await context.functions.canvas_layout_place({ entity: "app:notebook/main" });
27
+ context.finish({ note, notebook: notebook.status }, { summary: `Noted: ${note}` });
28
+ },
29
+ });
@@ -0,0 +1,43 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createTestContext } from "@ragents/server/testing";
4
+ import program from "../src/server.ts";
5
+
6
+ const start = (input: unknown) => ({ input, options: {}, embedded: true, startedBy: "owner-id", count: 1 });
7
+
8
+ const note = (status: "active" | "installed" = "installed") => {
9
+ const calls: { name: string; input: unknown }[] = [];
10
+ const record = <T>(name: string, answer: T) => (input: unknown): T => { calls.push({ name, input }); return answer; };
11
+ const context = createTestContext<Record<string, never>>({ state: {}, functions: {
12
+ actor_program_ensure: record("actor_program_ensure", { actorId: "id-notebook", handle: "notebook", status }),
13
+ actor_input: record("actor_input", []),
14
+ canvas_layout_place: record("canvas_layout_place", { placed: status === "installed" }),
15
+ } });
16
+ return { calls, context };
17
+ };
18
+
19
+ test("sends the note to the shared notebook and reports it", async () => {
20
+ const { calls, context } = note();
21
+ await program.onStart!(start({ text: " Ask about the budget " }), context);
22
+ assert.deepEqual(calls.map((call) => [call.name, call.input]), [
23
+ ["actor_program_ensure", { name: "notebook" }],
24
+ ["actor_input", { actor: "@notebook", content: "Ask about the budget" }],
25
+ ["canvas_layout_place", { entity: "app:notebook/main" }],
26
+ ]);
27
+ assert.deepEqual(context.finished, [{ result: { note: "Ask about the budget", notebook: "installed" }, summary: "Noted: Ask about the budget" }]);
28
+ });
29
+
30
+ test("without a start value it notes when it started; an invalid value changes nothing", async () => {
31
+ const { calls, context } = note("active");
32
+ await program.onStart!(start(null), context);
33
+ assert.match(String((calls[1]!.input as { content: string }).content), /^Started at /);
34
+ const invalid = note();
35
+ await assert.rejects(async () => program.onStart!(start({ text: "" }), invalid.context), /start value/);
36
+ assert.deepEqual(invalid.calls, []);
37
+ assert.deepEqual(invalid.context.finished, []);
38
+ });
39
+
40
+ test("an ordinary message is refused", async () => {
41
+ const { context } = note();
42
+ await assert.rejects(async () => program.onInput!({ id: "input-1", content: "hello", artifactIds: [], sourceEventIds: [], subscriptionId: null, event: null }, context), /answers only starts/);
43
+ });
@@ -0,0 +1,19 @@
1
+ ---
2
+ title: Take stock of the run
3
+ description: "A prepared check shows a run script that also joins a running run. It lists the other participants, notes them in the shared notebook, and reports them back as its result."
4
+ order: 170
5
+ coordinator: false
6
+ embeddable: true
7
+ shared-programs: notebook
8
+ tags: Run scripts, Concept demo, TypeScript actors
9
+ ---
10
+
11
+ The TypeScript program reads the participants with `actor_list` and ends each start with
12
+ `context.finish`. The owner reads the summary in the chat; a coordinator that starts it through
13
+ `run_script_start` receives summary and list as a message. Started inside a running run through the
14
+ run menu, `ragents script`, or the coordinator, it changes neither the primary actor nor the tiles,
15
+ and a repeated start reuses the same actor. Started as a new run, it is the run's only participant.
16
+
17
+ It shares the notebook with the quick note script: `shared-programs: notebook` copies the plugin's
18
+ shared package into the run, `actor_program_ensure` makes it active once, and
19
+ `canvas_layout_place` puts its view next to what is arranged.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "run-roster",
3
+ "private": true,
4
+ "type": "module",
5
+ "ragents": {
6
+ "title": "Run roster",
7
+ "backend": "src/server.ts"
8
+ }
9
+ }
@@ -0,0 +1,26 @@
1
+ import { defineActor } from "@ragents/server";
2
+ import { Type } from "typebox";
3
+
4
+ const contract = {
5
+ state: Type.Object({ reports: Type.Optional(Type.Number()) }, { additionalProperties: false }),
6
+ functions: {},
7
+ input: { capabilities: ["actor_list", "actor_program_ensure", "actor_input", "canvas_layout_place"] },
8
+ } as const;
9
+
10
+ export default defineActor(contract, {
11
+ functions: {},
12
+ onInput: () => {
13
+ throw new Error("The roster answers only starts; start it again from the run menu or with run_script_start.");
14
+ },
15
+ onStart: async (_start, context) => {
16
+ const actors = await context.functions.actor_list({});
17
+ const others = actors.filter((actor) => actor.kind !== "human" && actor.handle !== context.actor.handle);
18
+ const handles = others.map((actor) => `@${actor.handle}`);
19
+ const summary = handles.length === 0 ? "No other participants yet." : `${handles.length} participant${handles.length === 1 ? "" : "s"}: ${handles.join(", ")}.`;
20
+ const notebook = await context.functions.actor_program_ensure({ name: "notebook" });
21
+ await context.functions.actor_input({ actor: `@${notebook.handle}`, content: `Roster: ${summary}` });
22
+ await context.functions.canvas_layout_place({ entity: "app:notebook/main" });
23
+ context.state.replace({ reports: (context.state.read().reports ?? 0) + 1 });
24
+ context.finish({ actors: others.map(({ handle, kind, lifecycle }) => ({ handle, kind, lifecycle })) }, { summary });
25
+ },
26
+ });
@@ -0,0 +1,47 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createTestContext } from "@ragents/server/testing";
4
+ import program from "../src/server.ts";
5
+
6
+ const start = { input: null, options: {}, embedded: true, startedBy: "owner-id", count: 1 };
7
+ const actor = (handle: string, kind: "human" | "agent" | "script", lifecycle = "idle") =>
8
+ ({ id: `id-${handle}`, handle, displayName: handle, kind, lifecycle, createdBy: null, description: null, toolCount: null });
9
+
10
+ const roster = (actors: ReturnType<typeof actor>[], state: { reports?: number } = {}) => {
11
+ const calls: { name: string; input: unknown }[] = [];
12
+ const record = <T>(name: string, answer: T) => (input: unknown): T => { calls.push({ name, input }); return answer; };
13
+ const context = createTestContext<{ reports?: number }>({ state, functions: {
14
+ actor_list: record("actor_list", actors),
15
+ actor_program_ensure: record("actor_program_ensure", { actorId: "id-notebook", handle: "notebook", status: "active" as const }),
16
+ actor_input: record("actor_input", []),
17
+ canvas_layout_place: record("canvas_layout_place", { placed: true }),
18
+ } });
19
+ return { calls, context };
20
+ };
21
+
22
+ test("reports the other participants as result and summary and notes them in the shared notebook", async () => {
23
+ const { calls, context } = roster([actor("owner", "human"), actor("coordinator", "agent", "running"), actor("helper", "agent"), actor("test", "script")]);
24
+ await program.onStart!(start, context);
25
+ assert.deepEqual(context.finished, [{
26
+ result: { actors: [{ handle: "coordinator", kind: "agent", lifecycle: "running" }, { handle: "helper", kind: "agent", lifecycle: "idle" }] },
27
+ summary: "2 participants: @coordinator, @helper.",
28
+ }]);
29
+ assert.deepEqual(calls.slice(1).map((call) => [call.name, call.input]), [
30
+ ["actor_program_ensure", { name: "notebook" }],
31
+ ["actor_input", { actor: "@notebook", content: "Roster: 2 participants: @coordinator, @helper." }],
32
+ ["canvas_layout_place", { entity: "app:notebook/main" }],
33
+ ]);
34
+ assert.deepEqual(context.state.read(), { reports: 1 });
35
+ });
36
+
37
+ test("a run without other participants still gets a result, and every start counts", async () => {
38
+ const { context } = roster([actor("owner", "human"), actor("test", "script")], { reports: 2 });
39
+ await program.onStart!({ ...start, embedded: false, count: 3 }, context);
40
+ assert.deepEqual(context.finished, [{ result: { actors: [] }, summary: "No other participants yet." }]);
41
+ assert.deepEqual(context.state.read(), { reports: 3 });
42
+ });
43
+
44
+ test("an ordinary message is refused", async () => {
45
+ const { context } = roster([]);
46
+ await assert.rejects(async () => program.onInput!({ id: "input-1", content: "hello", artifactIds: [], sourceEventIds: [], subscriptionId: null, event: null }, context), /answers only starts/);
47
+ });
@@ -33,7 +33,7 @@
33
33
  }
34
34
  },
35
35
  "revision": "5e18b249f3c8b53b61820ef05da59823d58163c6962e6d836dcb37c05034aad0",
36
- "sourceRevision": "cdb1c230450ed69e96cebcee9da392c02edb499417fc7e2de657b2f89c36c379",
36
+ "sourceRevision": "e69f899d1733ba54341bfc2fd8c6adbf6025126a4b21a74bdd8206378e86b87e",
37
37
  "server": "server/index.js",
38
38
  "web": {
39
39
  "entry": "web/index.js",
@@ -23,7 +23,7 @@
23
23
  "web": {}
24
24
  },
25
25
  "revision": "e3bb513053f6385f9d78d314d28302324095a5163c4e1563df6fc8b3251305f7",
26
- "sourceRevision": "ba610549f1f1b3e87940617518d45b4bc918362fb9c20283b2a5061877bed103",
26
+ "sourceRevision": "7df3be99d8024c7878a0afe2bcd698909b6389f4484d98f84a0e3bdead954ab4",
27
27
  "server": "server/index.js",
28
28
  "exports": {
29
29
  "server": {},
@@ -25,7 +25,7 @@
25
25
  "web": {}
26
26
  },
27
27
  "revision": "007ee50ab186e3070930f23e9795210fa36c723365657325d5440cc2f0b81d60",
28
- "sourceRevision": "ebb0ba3e7ca11870abab5787c259c3d2ad3b36247caeb4b4efd9bcceebbb7047",
28
+ "sourceRevision": "1ce434ee7a1752f72e3d7160d895130948b8bcd5bba466f5555fff6ff278cd19",
29
29
  "server": "server/index.js",
30
30
  "exports": {
31
31
  "server": {
@@ -137,7 +137,7 @@
137
137
  }
138
138
  },
139
139
  "revision": "db9370b7b3449c44be49986b074806351b857c430963aa878e209d2d5c9ba4f8",
140
- "sourceRevision": "b86182fd6493e5628df38ed1669e857bd16742645380f76bacd7514446be3514",
140
+ "sourceRevision": "f91d81de973c9c7b46f1882a8cb4f067fbdf72c2e7617f24f8865f92d9ed05bc",
141
141
  "server": "server/index.js",
142
142
  "web": {
143
143
  "entry": "web/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schlenkr/ragents",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "RAgents host: server, engine, plugins and tools without a source checkout",
5
5
  "keywords": [
6
6
  "agents",
@@ -55,7 +55,7 @@
55
55
  "lucide-react": "1.46.0",
56
56
  "partial-json": "0.1.7",
57
57
  "playwright-core": "1.61.1",
58
- "quassel": "0.4.0",
58
+ "quassel": "0.4.2",
59
59
  "react": "19.2.8",
60
60
  "react-diff-view": "3.3.3",
61
61
  "react-dom": "19.2.8",
@@ -70,6 +70,6 @@
70
70
  "yaml": "2.9.0"
71
71
  },
72
72
  "ragents": {
73
- "hostVersion": "7b70e01ffa7c5e09ad393aa630cedb17fb4c2472"
73
+ "hostVersion": "9d43afb1f899a9b2bf01b46d9173d1ea901c9088"
74
74
  }
75
75
  }
@@ -8,10 +8,14 @@ export const capabilityNames = [
8
8
  "workspace.use",
9
9
  "execution.stopOwned",
10
10
  "run.configure",
11
+ "script.start",
11
12
  ] as const;
12
13
 
13
14
  export type CapabilityName = (typeof capabilityNames)[number];
14
15
 
16
+ /** Whoever gets one of these from the owner holds it without passing it on, so it never reaches the agents they spawn. */
17
+ export const firstHandCapabilities: readonly CapabilityName[] = ["script.start"];
18
+
15
19
  export const isCapabilityName = (value: unknown): value is CapabilityName =>
16
20
  typeof value === "string" && (capabilityNames as readonly string[]).includes(value);
17
21
 
@@ -32,6 +32,7 @@ export {
32
32
  SessionMetadataContributionRegistry,
33
33
  SkillContributionRegistry,
34
34
  StartEntryContributionRegistry,
35
+ ActorPackageContributionRegistry,
35
36
  StartOptionContributionRegistry,
36
37
  StorageRegistry,
37
38
  ToolContributionRegistry,
@@ -123,10 +124,11 @@ export {
123
124
  } from "./drivers/types.ts";
124
125
  export * from "./domain/model.ts";
125
126
  export { isPortableId, isRunId } from "./domain/portable-id.ts";
126
- export { capabilityNames, isCapabilityName, type CapabilityName } from "./domain/vocabulary.ts";
127
+ export { capabilityNames, firstHandCapabilities, isCapabilityName, type CapabilityName } from "./domain/vocabulary.ts";
127
128
  export { DirectoryArtifactContents, MemoryArtifactContents, type ArtifactContents } from "./runtime/artifacts.ts";
128
129
  export type { CommandContext } from "./runtime/command.ts";
129
130
  export { DomainError } from "./runtime/domain-error.ts";
131
+ export { inheritedGrants } from "./runtime/guards.ts";
130
132
  export { throwFailures, throwRejected } from "./runtime/failures.ts";
131
133
  export type { JournalEvent } from "./domain/events.ts";
132
134
  export { Journal, journalFormatVersion, type CommandRecord, type JournalLoadFailure } from "./runtime/journal.ts";
@@ -52,6 +52,8 @@ import type {
52
52
  SkillContribution,
53
53
  RunScriptPackage,
54
54
  StartEntryContribution,
55
+ ActorPackageContribution,
56
+ ActorProgramFile,
55
57
  StartOptionContext,
56
58
  StartOptionContribution,
57
59
  ScriptContribution,
@@ -442,6 +444,7 @@ export class SkillContributionRegistry {
442
444
  const SKILL_NAME = /^[a-z0-9][a-z0-9-]*$/;
443
445
  const GUIDE_ID = /^[a-z0-9][a-z0-9.-]*$/;
444
446
  const SCRIPT_HANDLE = /^[a-z0-9][a-z0-9-]*$/;
447
+ const ACTOR_PACKAGE_NAME = /^[a-z][a-z0-9-]{0,63}$/;
445
448
 
446
449
  const requireText = (entry: { id: unknown }, value: unknown, field: string): void => {
447
450
  if (typeof value !== "string" || !value.trim()) {
@@ -456,33 +459,38 @@ const assertKnownFields = (entry: { id: unknown }, value: object, fields: readon
456
459
  }
457
460
  };
458
461
 
459
- const validateProgramFiles = (entry: StartEntryContribution, files: unknown): void => {
462
+ const validateProgramFiles = (owner: string, files: unknown): void => {
460
463
  if (!Array.isArray(files) || files.length === 0)
461
- throw new Error(`Run script ${entry.id}: files needs package files`);
464
+ throw new Error(`${owner}: files needs package files`);
462
465
  const names = new Set<string>();
463
466
  for (const file of files) {
464
467
  if (typeof file !== "object" || file === null || typeof file.path !== "string" || typeof file.content !== "string")
465
- throw new Error(`Run script ${entry.id}: every file needs path and content`);
466
- assertKnownFields(entry, file, ["path", "content"], "Package file");
468
+ throw new Error(`${owner}: every file needs path and content`);
469
+ assertKnownFields({ id: owner }, file, ["path", "content"], "Package file of");
467
470
  if (!file.path || file.path.includes("\\") || file.path.includes("\0") || path.posix.isAbsolute(file.path)
468
471
  || file.path.split("/").some((part: string) => !part || part === "." || part === ".."))
469
- throw new Error(`Run script ${entry.id}: invalid package path ${file.path}`);
470
- if (names.has(file.path)) throw new Error(`Run script ${entry.id}: file ${file.path} is duplicated`);
472
+ throw new Error(`${owner}: invalid package path ${file.path}`);
473
+ if (names.has(file.path)) throw new Error(`${owner}: file ${file.path} is duplicated`);
471
474
  names.add(file.path);
472
475
  }
473
- if (!names.has("package.json")) throw new Error(`Run script ${entry.id}: package.json is missing`);
476
+ if (!names.has("package.json")) throw new Error(`${owner}: package.json is missing`);
474
477
  };
475
478
 
476
479
  const validateScriptPackage = (entry: StartEntryContribution, script: unknown): void => {
477
480
  if (typeof script !== "object" || script === null)
478
481
  throw new Error(`Template ${entry.id} has no run script package`);
479
- assertKnownFields(entry, script, ["handle", "coordinator", "files", "programs"], "Run script");
482
+ assertKnownFields(entry, script, ["handle", "coordinator", "embeddable", "sharedPrograms", "files", "programs"], "Run script");
480
483
  const value = script as Record<string, unknown>;
481
484
  if (typeof value.handle !== "string" || !SCRIPT_HANDLE.test(value.handle))
482
485
  throw new Error(`Run script ${entry.id}: handle must be a handle like roundtable (lowercase letters, digits, hyphen)`);
483
486
  if (typeof value.coordinator !== "boolean")
484
487
  throw new Error(`Run script ${entry.id}: coordinator must be true or false`);
485
- validateProgramFiles(entry, value.files);
488
+ if (value.embeddable !== undefined && typeof value.embeddable !== "boolean")
489
+ throw new Error(`Run script ${entry.id}: embeddable must be true or false`);
490
+ const shared = value.sharedPrograms;
491
+ if (shared !== undefined && (!Array.isArray(shared) || shared.some((name) => typeof name !== "string" || !ACTOR_PACKAGE_NAME.test(name)) || new Set(shared).size !== shared.length))
492
+ throw new Error(`Run script ${entry.id}: sharedPrograms must name each shared actor package once`);
493
+ validateProgramFiles(`Run script ${entry.id}`, value.files);
486
494
  if (!Array.isArray(value.programs)) throw new Error(`Run script ${entry.id}: programs must be a list`);
487
495
  const names = new Set<string>();
488
496
  for (const program of value.programs) {
@@ -492,7 +500,7 @@ const validateScriptPackage = (entry: StartEntryContribution, script: unknown):
492
500
  if (program.name === value.handle || names.has(program.name))
493
501
  throw new Error(`Run script ${entry.id}: program ${program.name} is duplicated`);
494
502
  names.add(program.name);
495
- validateProgramFiles(entry, program.files);
503
+ validateProgramFiles(`Run script ${entry.id}`, program.files);
496
504
  }
497
505
  };
498
506
 
@@ -555,6 +563,29 @@ const skillNameOf = (directory: string): string => directory.split(/[\\/]/).filt
555
563
  const byOrderThenId = (left: { order?: number; id: string }, right: { order?: number; id: string }): number =>
556
564
  (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id);
557
565
 
566
+ /** Actor packages a plugin shares with the run scripts of the profile; one name, one package. */
567
+ export class ActorPackageContributionRegistry {
568
+ readonly #packages = new ContributionRegistry<ActorPackageContribution & { id: string }>("Shared actor package");
569
+
570
+ register(owner: string, packages: readonly ActorPackageContribution[]): void {
571
+ for (const entry of packages) {
572
+ if (typeof entry.name !== "string" || !ACTOR_PACKAGE_NAME.test(entry.name))
573
+ throw new Error(`Shared actor package ${String(entry.name)} of ${owner}: the name must be a package name like notes (lowercase letters, digits, hyphen)`);
574
+ validateProgramFiles(`Shared actor package ${entry.name}`, entry.files);
575
+ }
576
+ this.#packages.register(owner, packages.map((entry) => ({ id: entry.name, name: entry.name, files: entry.files })));
577
+ }
578
+
579
+ get(name: string): { pluginId: string; files: readonly ActorProgramFile[] } | undefined {
580
+ const found = this.#packages.entries().find(({ value }) => value.name === name);
581
+ return found ? { pluginId: found.owner, files: found.value.files } : undefined;
582
+ }
583
+
584
+ names(): readonly string[] {
585
+ return this.#packages.entries().map(({ value }) => value.name);
586
+ }
587
+ }
588
+
558
589
  /** Skills with a start prompt and executable run scripts share one registry. */
559
590
  export class StartEntryContributionRegistry {
560
591
  readonly #entries = new ContributionRegistry<StartEntryContribution>("Template");
@@ -614,6 +645,24 @@ export class StartEntryContributionRegistry {
614
645
  return this.describe().find((candidate) => candidate.id === entryId);
615
646
  }
616
647
 
648
+ /** Names share one namespace per run: a shared package is neither a script's handle nor its bundled program, and every name a script needs exists. */
649
+ assertSharedPrograms(packages: ActorPackageContributionRegistry): void {
650
+ for (const { value } of this.#entries.entries()) {
651
+ if (value.action !== "script") continue;
652
+ const script = value.script;
653
+ const clash = [script.handle, ...script.programs.map((program) => program.name)].find((name) => packages.get(name));
654
+ if (clash !== undefined) {
655
+ throw new Error(`Run script ${value.id} uses the name ${clash}, which is also the shared actor package of ${packages.get(clash)!.pluginId}; `
656
+ + "run script handles, their bundled programs and shared packages share one namespace per run");
657
+ }
658
+ const missing = (script.sharedPrograms ?? []).filter((name) => !packages.get(name));
659
+ if (missing.length > 0) {
660
+ throw new Error(`Run script ${value.id} needs the shared actor packages ${missing.join(", ")}, which no plugin of this profile provides`
661
+ + (packages.names().length > 0 ? `; shared are ${packages.names().join(", ")}` : ""));
662
+ }
663
+ }
664
+ }
665
+
617
666
  /** The run script behind a script template; undefined for unknown ids and templates of another action. */
618
667
  scriptPackage(entryId: string): (RunScriptPackage & { entry: PublicStartEntry }) | undefined {
619
668
  const found = this.#entries.entries().find(({ value }) => value.id === entryId);
@@ -1164,6 +1213,7 @@ export class PluginHost {
1164
1213
  readonly prompts = new PromptContributionRegistry(this.runConditions);
1165
1214
  readonly skills = new SkillContributionRegistry(this.runConditions);
1166
1215
  readonly startEntries = new StartEntryContributionRegistry();
1216
+ readonly actorPackages = new ActorPackageContributionRegistry();
1167
1217
  readonly profiles = new ProfileContributionRegistry();
1168
1218
  readonly script = new ScriptContributionRegistry();
1169
1219
  readonly lifecycle = new LifecycleContributionRegistry();
@@ -1220,6 +1270,7 @@ export class PluginHost {
1220
1270
  sessionMetadata: (...entries) => this.sessionMetadata.register(manifest.id, entries),
1221
1271
  skills: (...entries) => this.skills.register(manifest.id, entries),
1222
1272
  startEntries: (...entries) => this.startEntries.register(manifest.id, entries),
1273
+ actorPackages: (...entries) => this.actorPackages.register(manifest.id, entries),
1223
1274
  startOptions: (...entries) => this.startOptions.register(manifest.id, entries),
1224
1275
  accessProjections: (...entries) => this.accessProjections.register(manifest.id, entries),
1225
1276
  functions: (...entries) => this.tools.registerFunctions(manifest.id, entries),
@@ -1240,6 +1291,7 @@ export class PluginHost {
1240
1291
  }
1241
1292
  }
1242
1293
  this.startEntries.assertFixedStartOptionsKnown(this.startOptions);
1294
+ this.startEntries.assertSharedPrograms(this.actorPackages);
1243
1295
  const defaultStartEntry = this.#defaultStartEntry;
1244
1296
  if (defaultStartEntry !== undefined) {
1245
1297
  const entries = this.startEntries.describe().map((entry) => entry.id);
@@ -242,10 +242,20 @@ export interface BundledActorProgram {
242
242
  export interface RunScriptPackage {
243
243
  handle: string;
244
244
  coordinator: boolean;
245
+ /** Whether the script may also start inside a run that is already running; without it, only a new run. */
246
+ embeddable?: boolean;
247
+ /** Shared actor packages of the profile that the host copies into the run before the setup, by name. */
248
+ sharedPrograms?: readonly string[];
245
249
  files: readonly ActorProgramFile[];
246
250
  programs: readonly BundledActorProgram[];
247
251
  }
248
252
 
253
+ /** An actor package that a plugin shares with the run scripts of the profile; its name is the package name in every run. */
254
+ export interface ActorPackageContribution {
255
+ name: string;
256
+ files: readonly ActorProgramFile[];
257
+ }
258
+
249
259
  export type StartEntryContribution = StartEntryBase & (
250
260
  | { action: "skill"; skill: string; category: string; prompt: string }
251
261
  /** A run script may name a category of its own; without one the start pages group it as a run script. */
@@ -448,6 +458,7 @@ export interface PluginRegistration {
448
458
  sessionMetadata: (...contributions: readonly SessionMetadataContribution[]) => void;
449
459
  skills: (...contributions: readonly SkillContribution[]) => void;
450
460
  startEntries: (...contributions: readonly StartEntryContribution[]) => void;
461
+ actorPackages: (...contributions: readonly ActorPackageContribution[]) => void;
451
462
  startOptions: (...contributions: readonly StartOptionContribution[]) => void;
452
463
  /** How accesses without runs.inspect see the states of this plugin and their chat events, per state id. */
453
464
  accessProjections: (...contributions: readonly AccessProjectionContribution[]) => void;
@@ -11,6 +11,7 @@ import type {
11
11
  Turn,
12
12
  } from "../domain/model.ts";
13
13
  import { actorByReference, handleKey } from "../domain/actor-reference.ts";
14
+ import { firstHandCapabilities } from "../domain/vocabulary.ts";
14
15
  import { DomainError } from "./domain-error.ts";
15
16
 
16
17
  export const clean = (value: string, name: string) => {
@@ -167,7 +168,7 @@ export const inheritedGrants = (
167
168
  scope: grant.scope.kind === "run"
168
169
  ? { kind: "run" as const }
169
170
  : { kind: "workspace" as const, path: grant.scope.path },
170
- delegable: true,
171
+ delegable: !firstHandCapabilities.includes(grant.capability),
171
172
  }));
172
173
  };
173
174
 
@@ -51,9 +51,19 @@ const pool: PooledWorker[] = [];
51
51
  const workerWaiters: WorkerWaiter[] = [];
52
52
  let nextRequestId = 0;
53
53
 
54
+ const LOADER_FLAGS = ["--import", "--loader", "--experimental-loader", "--require", "-r"] as const;
55
+
56
+ /** Only the loader flags of the host (such as --import tsx); Node strips no types under node_modules, and debugger or memory flags stay behind. */
57
+ const loaderArguments = (execArgv: readonly string[]): readonly string[] =>
58
+ execArgv.flatMap((argument, index) => {
59
+ if (LOADER_FLAGS.some((flag) => argument.startsWith(`${flag}=`))) return [argument];
60
+ if (LOADER_FLAGS.some((flag) => argument === flag) && index + 1 < execArgv.length) return [argument, execArgv[index + 1]!];
61
+ return [];
62
+ });
63
+
54
64
  const spawnWorker = (): PooledWorker => {
55
65
  const worker = new Worker(new URL("./compiler-worker.ts", import.meta.url), {
56
- execArgv: [],
66
+ execArgv: [...loaderArguments(process.execArgv)],
57
67
  name: "ragents-typescript-compiler",
58
68
  resourceLimits: COMPILER_RESOURCE_LIMITS,
59
69
  });
@@ -3,6 +3,7 @@ import type { PluginHost, RAgentsPlugin } from "@ragents/engine";
3
3
  import type { PluginModule } from "@ragents/host/plugin-support/plugin-module.js";
4
4
  import { runtimeProviderToken, runGuardToken } from "@ragents/host/ragents/host-services.js";
5
5
  import { askServiceToken } from "@ragents/plugins/ragents.ask/server/contract.js";
6
+ import { surfacePlacementToken } from "@ragents/plugins/ragents.orchestration/server/contract.js";
6
7
  import { agentToolsFrom } from "./agent-tools.js";
7
8
  import { createActorProgramMethods } from "./methods.js";
8
9
  import { createMiniAppFrameRoutes, miniAppsApiPrefix } from "./routes.js";
@@ -26,6 +27,8 @@ const actorProgramsPlugin = (pluginHost: PluginHost): RAgentsPlugin => ({
26
27
  const script = pluginHost.startEntries.scriptPackage(entryId);
27
28
  return !script ? undefined : script.handle === name ? script.files : script.programs.find((program) => program.name === name)?.files;
28
29
  },
30
+ sharedPackage: (name) => pluginHost.actorPackages.get(name),
31
+ placeView: (context, runId, entity) => { host.service(surfacePlacementToken).place(context, runId, { entity }); },
29
32
  });
30
33
  host.provide(actorProgramsToken, runtime);
31
34
  const diagnostics = createProjectDiagnostics({