@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,66 @@
1
+ import { Type } from "typebox";
2
+ import { defineRunFunction, defineToolAvailability, holdsUsable, type JsonValue, type PluginHost, type ToolContributor } from "@ragents/engine";
3
+ import { toolDescriptorFrom } from "../plugin-support/agent-tool.js";
4
+ import { runManagementToken, type RunManagement } from "./global-chat.js";
5
+
6
+ const available = defineToolAvailability({
7
+ availability: "conditional",
8
+ availabilityDetail: "Only with the capability script.start, which the coordinator holds and passes to no agent it spawns.",
9
+ requiredCapabilities: ["script.start"],
10
+ }, (actor) => actor.kind !== "human" && holdsUsable(actor, "script.start"));
11
+
12
+ const listMetadata = {
13
+ name: "run_script_list",
14
+ label: "List run scripts",
15
+ description: "The run scripts of this profile that can join this run: entry, title, description, and whether each can start now or why not.",
16
+ } as const;
17
+
18
+ const startMetadata = {
19
+ name: "run_script_start",
20
+ label: "Start run script",
21
+ description: "Start a run script from run_script_list inside this run; returns its actor handle and which start of it this is.",
22
+ longDescription: "entry is the entry from run_script_list; input is the script's start value, if it takes one. The script joins the run "
23
+ + "without changing the primary actor; a repeated start reuses its actor. When it finishes a start, you receive its summary and result "
24
+ + "as a message; do not wait or poll for it in the same turn.",
25
+ } as const;
26
+
27
+ const listing = Type.Array(Type.Object({
28
+ entry: Type.String(),
29
+ title: Type.String(),
30
+ description: Type.String(),
31
+ available: Type.Boolean(),
32
+ reason: Type.Optional(Type.String()),
33
+ }, { additionalProperties: false }));
34
+
35
+ export const createRunScriptToolContributor = (management: () => RunManagement): ToolContributor => {
36
+ const list = defineRunFunction({
37
+ ...listMetadata,
38
+ schema: Type.Object({}, { additionalProperties: false }),
39
+ resultSchema: listing,
40
+ available,
41
+ run: ({ caller }) => management().scripts(caller.runId).map(({ id, ...script }) => ({ entry: id, ...script })),
42
+ });
43
+ const start = defineRunFunction({
44
+ ...startMetadata,
45
+ schema: Type.Object({
46
+ entry: Type.String({ minLength: 1, description: "The entry from run_script_list" }),
47
+ input: Type.Optional(Type.Unknown({ description: "Start value of the script; omit it for none" })),
48
+ }, { additionalProperties: false }),
49
+ resultSchema: Type.Object({ handle: Type.String(), count: Type.Integer({ minimum: 1 }) }, { additionalProperties: false }),
50
+ available,
51
+ run: async ({ caller }, _toolCallId, input) => {
52
+ const started = await management().startScript(caller.runId, input.entry, (input.input ?? null) as JsonValue | null, caller.actorId);
53
+ return { handle: started.handle, count: started.count };
54
+ },
55
+ });
56
+ return {
57
+ name: "ragents.run-scripts",
58
+ descriptors: [toolDescriptorFrom(listMetadata, available), toolDescriptorFrom(startMetadata, available)],
59
+ tools: () => [list, start],
60
+ };
61
+ };
62
+
63
+ /** The profile registers them once while composing; they act on the caller's own run. */
64
+ export const registerRunScriptFunctions = (host: PluginHost): void => {
65
+ host.tools.register("ragents.runtime", [createRunScriptToolContributor(() => host.service(runManagementToken)())]);
66
+ };
@@ -1,15 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdir, writeFile } from "node:fs/promises";
3
- import path from "node:path";
4
2
  import { isDeepStrictEqual } from "node:util";
5
3
  import { parseChatAttachments, type ChatAttachment, type ChatAttachmentInput, type ChatEvent, type ChatStartupStatus } from "quassel/events";
6
4
  import { attachmentContentPath } from "../api/contracts.js";
7
- import type { ChatSessionLike, ChatUser } from "../chat-handler.js";
5
+ import type { ChatSessionLike, ChatUser, RunScriptListing, StartedScript } from "../chat-handler.js";
8
6
  import {
9
7
  assertJsonValue,
10
8
  attachmentInputKind,
11
9
  capabilityNames,
12
10
  DomainError,
11
+ firstHandCapabilities,
13
12
  isThinkingLevel,
14
13
  PluginStateProjection,
15
14
  resolveExecution,
@@ -25,7 +24,7 @@ import {
25
24
  type AgentExecution,
26
25
  type ModelSelection,
27
26
  } from "@ragents/engine";
28
- import type { ActorProgramsService } from "../plugin-support/actor-programs/service.js";
27
+ import type { ActivatedActorProgram, ActorProgramsService } from "../plugin-support/actor-programs/service.js";
29
28
  import { modelLabel } from "../plugin-support/model-aliases.js";
30
29
  import type { StartOptionState } from "../plugin-support/start-options-contract.js";
31
30
  import type { Engine } from "./engine.js";
@@ -59,7 +58,7 @@ const coordinatorGrants = (): CapabilityGrant[] =>
59
58
  capabilityNames.map((capability) => ({
60
59
  capability,
61
60
  scope: { kind: "run" as const },
62
- delegable: true,
61
+ delegable: !firstHandCapabilities.includes(capability),
63
62
  }));
64
63
 
65
64
  const primaryActorOf = (view: RunView) => view.primaryActorId
@@ -70,6 +69,9 @@ const messageOf = (error: unknown): string => error instanceof Error ? error.mes
70
69
 
71
70
  const userIdOf = (user: ChatUser | undefined): string | null => user?.id ?? null;
72
71
 
72
+ const startValueOf = (input: unknown): JsonValue | null =>
73
+ input === undefined || input === null ? null : (assertJsonValue(input, "Start value of the run script"), input);
74
+
73
75
  /** Who starts a run, through which template, and which start options it fixes. */
74
76
  interface StartChoice {
75
77
  readonly userId: string | null;
@@ -83,6 +85,8 @@ const freeChoice = (userId: string | null): StartChoice => ({ userId, entry: nul
83
85
  /** A script template together with the package the host installs when it is clicked. */
84
86
  export type RunScriptStart = RunScriptPackage & { entry: PublicStartEntry };
85
87
 
88
+ export type RunScriptPrograms = Pick<ActorProgramsService, "installScript" | "enqueueStart" | "isScriptActor">;
89
+
86
90
  export interface RunChatSessionOptions {
87
91
  engine: Engine;
88
92
  id: string;
@@ -99,7 +103,7 @@ export interface RunChatSessionOptions {
99
103
  started: (id: string, startEntry: SessionStartedContext["startEntry"]) => Promise<void>;
100
104
  scriptEntryFor: (entryId: string) => RunScriptStart | undefined;
101
105
  startEntryFor: (entryId: string) => PublicStartEntry | undefined;
102
- actorPrograms: ActorProgramsService;
106
+ actorPrograms: RunScriptPrograms;
103
107
  }
104
108
 
105
109
  export class RunChatSession implements ChatSessionLike {
@@ -117,7 +121,7 @@ export class RunChatSession implements ChatSessionLike {
117
121
  readonly #started: (id: string, startEntry: SessionStartedContext["startEntry"]) => Promise<void>;
118
122
  readonly #scriptEntryFor: (entryId: string) => RunScriptStart | undefined;
119
123
  readonly #startEntryFor: (entryId: string) => PublicStartEntry | undefined;
120
- readonly #actorPrograms: ActorProgramsService;
124
+ readonly #actorPrograms: RunScriptPrograms;
121
125
  readonly #events: ChatEvent[] = [];
122
126
  readonly #listeners = new Set<(event: ChatEvent) => void>();
123
127
  readonly #liveTurns = new Map<string, LiveTurn>();
@@ -127,6 +131,8 @@ export class RunChatSession implements ChatSessionLike {
127
131
  #primaryActorId: string | undefined;
128
132
  #activeLiveTurnId: string | undefined;
129
133
  #starting: Promise<void> | undefined;
134
+ #scriptQueue: Promise<unknown> = Promise.resolve();
135
+ readonly #scriptStarts = new Set<AbortController>();
130
136
  #startup: ChatStartupStatus | undefined;
131
137
  #startCancellation: AbortController | undefined;
132
138
  #unsubscribeJournal: (() => void) | undefined;
@@ -232,7 +238,7 @@ export class RunChatSession implements ChatSessionLike {
232
238
  const chosen = this.startLocked ? storedStartOption(state, optionId) : this.#startValues.get(optionId);
233
239
  if (chosen !== undefined && !isDeepStrictEqual(chosen, accepted)) {
234
240
  throw new DomainError("start-option-fixed", this.startLocked
235
- ? `The template "${entry.title}" fixes the start option ${optionId}, but this run already has a different value there.`
241
+ ? `The template "${entry.title}" fixes the start option ${optionId} to ${JSON.stringify(accepted)}, but this run already has a different value there: ${JSON.stringify(chosen)}.`
236
242
  : `The template "${entry.title}" fixes the start option ${optionId}, but a different value is chosen. Undo the choice or start without this template.`, 409);
237
243
  }
238
244
  return [optionId, accepted] as const;
@@ -319,24 +325,47 @@ export class RunChatSession implements ChatSessionLike {
319
325
  return { attachment: this.#attachmentInfo(artifact), content };
320
326
  }
321
327
 
322
- /** Starts the run through a script template: no message, the script's first turn builds the run. */
328
+ /** Starts a run script: in a new run its first turn builds the run, in a running run only an embeddable one joins it. */
323
329
  start(entryId: string, input: unknown, user?: ChatUser): void {
324
- this.#track(() => this.#start(entryId, input, user));
330
+ void this.#track(() => this.#start(entryId, input, user));
331
+ }
332
+
333
+ /** startedBy is the actor that receives the script's result; without it, the owner. */
334
+ async startAndWait(entryId: string, input: unknown, user?: ChatUser, startedBy?: string): Promise<StartedScript> {
335
+ return this.#track(() => this.#start(entryId, input, user, startedBy));
336
+ }
337
+
338
+ /** The script templates among these entries, and whether each can start in this run now. */
339
+ runScripts(entries: readonly PublicStartEntry[], userId: string | null): RunScriptListing[] {
340
+ const started = this.#runStarted();
341
+ return entries.flatMap((entry) => {
342
+ const found = this.#scriptEntryFor(entry.id);
343
+ if (!found) return [];
344
+ const reason = !started ? undefined
345
+ : !found.embeddable ? "It starts only a new run; its RUN.md does not set embeddable: true."
346
+ : this.#optionConflict(found.entry, userId);
347
+ return [{ id: entry.id, title: entry.title, description: entry.description, available: reason === undefined, ...(reason ? { reason } : {}) }];
348
+ });
325
349
  }
326
350
 
327
- async startAndWait(entryId: string, input: unknown, user?: ChatUser): Promise<void> {
328
- await this.#track(() => this.#start(entryId, input, user));
351
+ #optionConflict(entry: PublicStartEntry, userId: string | null): string | undefined {
352
+ try {
353
+ this.#startChoice(entry, userId);
354
+ return undefined;
355
+ } catch (error) {
356
+ return messageOf(error);
357
+ }
329
358
  }
330
359
 
331
- async startPackageAndWait(script: RunScriptStart, input: unknown, user?: ChatUser): Promise<void> {
332
- await this.#track(() => this.#startPackage(() => script, input, user));
360
+ async startPackageAndWait(script: RunScriptStart, input: unknown, user?: ChatUser): Promise<StartedScript> {
361
+ return this.#track(() => this.#startPackage(() => script, input, user));
333
362
  }
334
363
 
335
- #track(work: () => Promise<void>, reportError = true): Promise<void> {
364
+ #track<T>(work: () => Promise<T>, reportError = true): Promise<T> {
336
365
  if (this.#disposed) throw new Error("The run was deleted");
337
366
  this.#assertUsable(this.id);
338
367
  const operation = work();
339
- const tracked = operation.catch((error: unknown) => {
368
+ const tracked = operation.then(() => undefined, (error: unknown) => {
340
369
  if (reportError && !this.#disposed) this.#emit({ kind: "system", text: `Error: ${messageOf(error)}` });
341
370
  });
342
371
  this.#pendingSends.add(tracked);
@@ -356,8 +385,9 @@ export class RunChatSession implements ChatSessionLike {
356
385
  if (this.#startCancellation && !this.#startCancellation.signal.aborted) {
357
386
  const message = "The start was cancelled by the operator.";
358
387
  this.#startCancellation.abort(new DomainError("run-start-aborted", message, 409));
359
- this.#setStartup({ status: "failed", message });
388
+ if (this.#startup?.status === "preparing") this.#setStartup({ status: "failed", message });
360
389
  }
390
+ for (const controller of this.#scriptStarts) controller.abort(new DomainError("run-start-aborted", "The start was cancelled by the operator.", 409));
361
391
  try {
362
392
  if (this.#engine.journal.stateOf(this.id)) {
363
393
  await this.#engine.stopRun(this.id, {
@@ -386,6 +416,7 @@ export class RunChatSession implements ChatSessionLike {
386
416
  if (this.#engine.journal.stateOf(this.id)) throw new Error("The old journal must be removed before the conversation reset");
387
417
  this.#startCancellation?.abort(new Error("The start preparation was reset."));
388
418
  this.#startCancellation = undefined;
419
+ for (const controller of this.#scriptStarts) controller.abort(new Error("The start preparation was reset."));
389
420
  this.#startup = undefined;
390
421
  this.#unsubscribeJournal?.();
391
422
  this.#unsubscribeLive?.();
@@ -409,6 +440,7 @@ export class RunChatSession implements ChatSessionLike {
409
440
  this.#disposed = true;
410
441
  this.#startCancellation?.abort(new Error("The run was deleted"));
411
442
  this.#startCancellation = undefined;
443
+ for (const controller of this.#scriptStarts) controller.abort(new Error("The run was deleted"));
412
444
  this.#startup = undefined;
413
445
  this.#unsubscribeJournal?.();
414
446
  this.#unsubscribeLive?.();
@@ -581,32 +613,35 @@ export class RunChatSession implements ChatSessionLike {
581
613
  return this.#starting;
582
614
  }
583
615
 
584
- async #start(entryId: string, input: unknown, user?: ChatUser): Promise<void> {
585
- await this.#startPackage(() => {
616
+ #start(entryId: string, input: unknown, user?: ChatUser, startedBy?: string): Promise<StartedScript> {
617
+ return this.#startPackage(() => {
586
618
  const found = this.#scriptEntryFor(entryId);
587
619
  if (!found) throw new DomainError("entry-unknown", `The template ${entryId} is not a script template of this profile.`, 404);
588
620
  return found;
589
- }, input, user);
621
+ }, input, user, startedBy);
590
622
  }
591
623
 
592
- async #startPackage(resolve: () => RunScriptStart, input: unknown, user?: ChatUser): Promise<void> {
624
+ #runStarted(): boolean {
625
+ return this.startLocked && this.#engine.runtime.view(this.id).actors.some((actor) => actor.kind !== "human");
626
+ }
627
+
628
+ async #startPackage(resolve: () => RunScriptStart, input: unknown, user?: ChatUser, startedBy?: string): Promise<StartedScript> {
629
+ if (this.#runStarted()) return this.#startInRun(resolve(), input, user, startedBy);
593
630
  if (this.#starting) throw new DomainError("run-starting", "The run is being started.", 409);
594
- if (this.startLocked && this.#engine.runtime.view(this.id).actors.some((actor) => actor.kind !== "human")) {
595
- throw new DomainError("run-started", "The run is already running; a run script only starts a new run.", 409);
596
- }
597
631
  const cancellation = new AbortController();
598
632
  this.#startCancellation = cancellation;
633
+ let started: StartedScript | undefined;
599
634
  const operation = this.#exclusiveStart(async () => {
600
635
  const { signal } = cancellation;
601
636
  try {
602
637
  signal.throwIfAborted();
603
638
  const found = resolve();
604
639
  const choice = this.#startChoice(found.entry, userIdOf(user));
605
- const startValue: JsonValue | null = input === undefined || input === null ? null : (assertJsonValue(input, "Start value of the run script"), input);
640
+ const startValue = startValueOf(input);
606
641
  await this.#prepare(this.id, this.#emitSystem());
607
642
  signal.throwIfAborted();
608
643
  this.#assertUsable(this.id);
609
- await this.#startWithScript(found, startValue, signal, user, choice);
644
+ started = await this.#startWithScript(found, startValue, signal, user, choice);
610
645
  signal.throwIfAborted();
611
646
  this.#setStartup(undefined);
612
647
  } catch (error) {
@@ -620,6 +655,41 @@ export class RunChatSession implements ChatSessionLike {
620
655
  });
621
656
  this.#setStartup({ status: "preparing", message: "Preparing run." });
622
657
  await operation;
658
+ if (!started) throw new Error("The run script start ended without a result");
659
+ return started;
660
+ }
661
+
662
+ /** Starts in a running run wait for each other; each checks what can refuse it before the run changes, and none touches the primary actor or the startup status. */
663
+ async #startInRun(found: RunScriptStart, input: unknown, user?: ChatUser, startedBy?: string): Promise<StartedScript> {
664
+ if (!found.embeddable) {
665
+ throw new DomainError("run-started", `The run is already running; the run script "${found.entry.title}" only starts a new run. A run script starts inside a running run only if its RUN.md sets embeddable: true.`, 409);
666
+ }
667
+ const startValue = startValueOf(input);
668
+ const cancellation = new AbortController();
669
+ this.#scriptStarts.add(cancellation);
670
+ const operation = this.#scriptQueue.then(async () => {
671
+ const { signal } = cancellation;
672
+ signal.throwIfAborted();
673
+ await this.#starting?.catch(() => undefined);
674
+ signal.throwIfAborted();
675
+ const choice = this.#startChoice(found.entry, userIdOf(user));
676
+ await this.#prepare(this.id, this.#emitSystem());
677
+ signal.throwIfAborted();
678
+ this.#assertUsable(this.id);
679
+ await this.#prepareWorkspace(this.id, this.#emitSystem());
680
+ signal.throwIfAborted();
681
+ this.#assertUsable(this.id);
682
+ const installed = await this.#installScript(found, signal);
683
+ signal.throwIfAborted();
684
+ this.#assertUsable(this.id);
685
+ return this.#enqueueStart(found, installed, startValue, choice, true, startedBy);
686
+ });
687
+ this.#scriptQueue = operation.catch(() => undefined);
688
+ try {
689
+ return await operation;
690
+ } finally {
691
+ this.#scriptStarts.delete(cancellation);
692
+ }
623
693
  }
624
694
 
625
695
  async #startRun(user: ChatUser | undefined, choice: StartChoice): Promise<void> {
@@ -632,8 +702,8 @@ export class RunChatSession implements ChatSessionLike {
632
702
  this.#assertUsable(this.id);
633
703
  }
634
704
 
635
- async #startWithScript(found: RunScriptStart, input: JsonValue | null, signal: AbortSignal, user: ChatUser | undefined, choice: StartChoice): Promise<void> {
636
- const { entry, ...script } = found;
705
+ async #startWithScript(found: RunScriptStart, input: JsonValue | null, signal: AbortSignal, user: ChatUser | undefined, choice: StartChoice): Promise<StartedScript> {
706
+ const { entry } = found;
637
707
  this.#createRunIfNeeded(this.#initialTitle ?? entry.title, user, choice);
638
708
  this.#assertUsable(this.id);
639
709
  this.#setStartup({ status: "preparing", message: "Preparing working directory." });
@@ -643,50 +713,40 @@ export class RunChatSession implements ChatSessionLike {
643
713
  this.#assertUsable(this.id);
644
714
  this.#setStartup({ status: "preparing", message: "Preparing UI." });
645
715
  signal.throwIfAborted();
646
- await this.#copyPrograms(script.programs, signal);
647
- signal.throwIfAborted();
648
- const state = this.#engine.runtime.state(this.id);
649
- const installed = await this.#actorPrograms.importPackage(
650
- {actorId: state.ownerId, commandId: `run-script:${this.id}:${script.handle}`},
651
- this.id, script.handle, script.files, signal, entry.id,
652
- );
716
+ const installed = await this.#installScript(found, signal);
653
717
  signal.throwIfAborted();
654
718
  this.#assertUsable(this.id);
655
- if (script.coordinator) this.#ensureCoordinatorAsPrimary(choice);
719
+ if (found.coordinator) this.#ensureCoordinatorAsPrimary(choice);
656
720
  else {
721
+ const state = this.#engine.runtime.state(this.id);
657
722
  this.#engine.runtime.selectPrimaryActor({actorId: state.ownerId, commandId: `run-script-primary:${this.id}`}, this.id, installed.actorId);
658
723
  this.#bind(installed.actorId);
659
724
  }
660
725
  await this.#started(this.id, choice.entry);
661
726
  this.#assertUsable(this.id);
662
- const options = Object.fromEntries(this.#engine.startOptions.entries()
663
- .map((option) => [option.option.id, this.#startValueOf(option, choice)]));
664
727
  signal.throwIfAborted();
665
- this.#engine.runtime.enqueueInput(
666
- { actorId: state.ownerId, commandId: `run-script-input:${this.id}:${script.handle}` },
667
- this.id,
668
- { actorId: installed.actorId, content: JSON.stringify({ input, options }) },
728
+ const started = this.#enqueueStart(found, installed, input, choice, false);
729
+ if (found.coordinator) this.#emit({ kind: "system", text: `The run script "${entry.title}" runs as @${installed.actorHandle} and sets up the run.` });
730
+ return started;
731
+ }
732
+
733
+ #installScript(found: RunScriptStart, signal: AbortSignal): Promise<ActivatedActorProgram> {
734
+ const { entry, handle, files, programs, sharedPrograms = [] } = found;
735
+ return this.#actorPrograms.installScript(
736
+ {actorId: this.#engine.runtime.state(this.id).ownerId, commandId: `run-script:${this.id}:${handle}:${randomUUID()}`},
737
+ this.id, {entryId: entry.id, handle, files, programs, sharedPrograms}, signal,
669
738
  );
670
- if (script.coordinator) {
671
- this.#emit({ kind: "system", text: `The run script "${entry.title}" runs as @${installed.actorHandle} and sets up the run.` });
672
- return;
673
- }
674
739
  }
675
740
 
676
- async #copyPrograms(programs: RunScriptPackage["programs"], signal: AbortSignal): Promise<void> {
677
- if (programs.length === 0) return;
678
- const root = await this.#actorPrograms.workspaceDirectory(this.id);
679
- for (const program of programs) {
680
- signal.throwIfAborted();
681
- const target = path.join(root, program.name);
682
- await mkdir(target);
683
- for (const file of program.files) {
684
- signal.throwIfAborted();
685
- const destination = path.join(target, file.path);
686
- await mkdir(path.dirname(destination), {recursive: true});
687
- await writeFile(destination, file.content, {flag: "wx"});
688
- }
689
- }
741
+ #enqueueStart(found: RunScriptStart, installed: ActivatedActorProgram, input: JsonValue | null, choice: StartChoice, embedded: boolean, startedBy?: string): StartedScript {
742
+ const ownerId = this.#engine.runtime.state(this.id).ownerId;
743
+ const options = Object.fromEntries(this.#engine.startOptions.entries()
744
+ .map((option) => [option.option.id, this.#startValueOf(option, choice)]));
745
+ const { count } = this.#actorPrograms.enqueueStart(
746
+ {actorId: ownerId, commandId: `run-script-input:${this.id}:${found.handle}:${randomUUID()}`},
747
+ this.id, found.handle, {content: JSON.stringify({ input, options }), embedded, startedBy: startedBy ?? ownerId},
748
+ );
749
+ return { actorId: installed.actorId, handle: installed.actorHandle, count };
690
750
  }
691
751
 
692
752
  #createRunIfNeeded(title: string, user: ChatUser | undefined, choice: StartChoice): void {
@@ -904,6 +964,8 @@ export class RunChatSession implements ChatSessionLike {
904
964
  primaryActorId,
905
965
  ownerId: this.#engine.runtime.state(this.id).ownerId,
906
966
  labelOf: (actorId) => this.#engine.runtime.state(this.id).actors.get(actorId)?.displayName,
967
+ scriptActorHandle: (actorId) => this.#actorPrograms.isScriptActor(this.id, actorId)
968
+ ? this.#engine.runtime.select(this.id, (state) => state.actors.get(actorId)?.handle) : undefined,
907
969
  turnOf: (turnId) => {
908
970
  const turn = this.#engine.runtime.state(this.id).turns.get(turnId);
909
971
  if (!turn) throw new Error(`The turn ${turnId} is missing from run ${this.id}`);
@@ -1 +1 @@
1
- import{r,S as d,E as p,j as E,c as f}from"./mermaid-HWGCJPDP-B1erZlFM.js";var j=({code:i,language:e,maxHeight:o,raw:a,className:u,startLine:m,lineNumbers:n,...h})=>{let{shikiTheme:l}=r.useContext(d),t=p(),[c,s]=r.useState(a);return r.useEffect(()=>{if(!t){s(a);return}let g=t.highlight({code:i,language:e,themes:l},x=>{s(x)});g&&s(g)},[i,e,l,t,a]),E.jsx(f,{className:u,language:e,lineNumbers:n,maxHeight:o,result:c,startLine:m,...h})};export{j as HighlightedCodeBlockBody};
1
+ import{r,S as d,E as p,j as E,c as f}from"./mermaid-HWGCJPDP-CSCkaArr.js";var j=({code:i,language:e,maxHeight:o,raw:a,className:u,startLine:m,lineNumbers:n,...h})=>{let{shikiTheme:l}=r.useContext(d),t=p(),[c,s]=r.useState(a);return r.useEffect(()=>{if(!t){s(a);return}let g=t.highlight({code:i,language:e,themes:l},x=>{s(x)});g&&s(g)},[i,e,l,t,a]),E.jsx(f,{className:u,language:e,lineNumbers:n,maxHeight:o,result:c,startLine:m,...h})};export{j as HighlightedCodeBlockBody};
@@ -1,4 +1,4 @@
1
- import{a as ue,r as s,j as e,D as fe,b as pe,d as ee,B as g,X as Le,M as Ie,C as xe,e as _e,f as Be,g as He,h as Ue,u as Fe,i as qe,k as Te,l as Ke,m as $e,A as We,n as Qe,o as Ve,U as Ge,P as Xe,W as Ye,R as Je,p as le,q as J,s as Ze,t as et,v as tt,w as st,x as nt,y as at,z as rt,Q as ot,F as it,G as lt}from"./mermaid-HWGCJPDP-B1erZlFM.js";/**
1
+ import{a as ue,r as s,j as e,D as fe,b as pe,d as ee,B as g,X as Le,M as Ie,C as xe,e as _e,f as Be,g as He,h as Ue,u as Fe,i as qe,k as Te,l as Ke,m as $e,A as We,n as Qe,o as Ve,U as Ge,P as Xe,W as Ye,R as Je,p as le,q as J,s as Ze,t as et,v as tt,w as st,x as nt,y as at,z as rt,Q as ot,F as it,G as lt}from"./mermaid-HWGCJPDP-CSCkaArr.js";/**
2
2
  * @license lucide-react v1.46.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.