@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
|
@@ -10,6 +10,7 @@ const metadata = [
|
|
|
10
10
|
{ name: "actor_program_remove", label: "Remove Actor Program", description: "Detach functions and views and stop the backend.", longDescription: "A TypeScript actor is stopped; an LLM actor retains its agent behavior." },
|
|
11
11
|
{ name: "actor_view_set_visibility", label: "Show or hide Actor View", description: "Set surface visibility by package-name/view-key or @handle/view-key.", longDescription: "Use names you chose; the server resolves the view ID. A unique view title also works." },
|
|
12
12
|
{ name: "actor_program_diagnostics", label: "Actor Program Diagnostics", description: "Read the last project diagnostics.", longDescription: "Changed errors are automatically supplied before the next model request." },
|
|
13
|
+
{ name: "actor_program_ensure", label: "Ensure Actor Program", description: "Make a package active once: return it when active, restart its stopped actor, activate it when installed, or install a shared package of the profile.", longDescription: "Safe to call on every start: an active package is returned unchanged and not rebuilt. status names what happened. actor_program_activate instead rebuilds and reactivates." },
|
|
13
14
|
] as const;
|
|
14
15
|
export const actorProgramManagementToolNames = metadata.map((entry) => entry.name);
|
|
15
16
|
export const actorProgramToolsAvailable = defineToolAvailability({ availability: "conditional", availabilityDetail: "For executable actors with agent.spawn and plugin.state.write." }, actor => actor.kind !== "human" && holdsUsable(actor, "agent.spawn") && holdsUsable(actor, "plugin.state.write"));
|
|
@@ -22,6 +23,7 @@ const managementTools = (runtime: ActorProgramRuntime, diagnostics: Diagnostics)
|
|
|
22
23
|
defineRunFunction({ ...metadata[3], schema: Type.Object({ name }, { additionalProperties: false }), resultSchema: Type.Object({ removed: Type.String() }, { additionalProperties: false }), available: actorProgramToolsAvailable, executionMode: "sequential", run: (scope, id, input) => runtime.remove(scope.context(id), scope.caller.runId, input.name) }),
|
|
23
24
|
defineRunFunction({ ...metadata[4], schema: Type.Object({ view: Type.String({ minLength: 1, description: "package-name/view-key or @handle/view-key of an activated view, without the surface entity prefix app:. No generated IDs needed." }), visible: Type.Boolean() }, { additionalProperties: false }), resultSchema: Type.Object({ view: Type.String(), visible: Type.Boolean() }, { additionalProperties: false }), available: actorProgramToolsAvailable, executionMode: "sequential", run: (scope, id, input) => runtime.setVisibility(scope.context(id), scope.caller.runId, input.view, input.visible) }),
|
|
24
25
|
defineRunFunction({ ...metadata[5], schema: Type.Object({ name: Type.Optional(name) }, { additionalProperties: false }), resultSchema: Type.String(), available: actorProgramToolsAvailable, executionMode: "sequential", run: (scope, _id, input) => diagnostics.latest(scope.caller.runId, scope.caller.actorId, input.name) }),
|
|
26
|
+
defineRunFunction({ ...metadata[6], schema: Type.Object({ name }, { additionalProperties: false }), resultSchema: Type.Object({ actorId: Type.String(), handle: Type.String(), status: Type.Union([Type.Literal("active"), Type.Literal("restarted"), Type.Literal("activated"), Type.Literal("installed")]) }, { additionalProperties: false }), available: actorProgramToolsAvailable, executionMode: "sequential", run: (scope, id, input) => runtime.ensure(scope.context(id), scope.caller.runId, input.name, scope.signal) }),
|
|
25
27
|
];
|
|
26
28
|
export const createActorProgramToolContributors = (runtime: ActorProgramRuntime, diagnostics: Diagnostics): readonly ToolContributor[] => [
|
|
27
29
|
{ name: "ragents.actor-programs.management", descriptors: metadata.map((item) => toolDescriptorFrom(item, actorProgramToolsAvailable)), tools: () => managementTools(runtime, diagnostics) },
|
|
@@ -6,6 +6,7 @@ import type { AskService } from "./contract.js";
|
|
|
6
6
|
export const askToolMetadata = {
|
|
7
7
|
name: "ask_user",
|
|
8
8
|
label: "Question",
|
|
9
|
+
nativeTool: true,
|
|
9
10
|
description: "Asks the user for a required decision with answer options and waits for the answer.",
|
|
10
11
|
longDescription: "Always use this tool when you need a decision from the user "
|
|
11
12
|
+ "(e.g. choosing a branch or a time range), instead of only asking the question as text. "
|
|
@@ -92,7 +92,7 @@ const formatTime = (iso: string) => {
|
|
|
92
92
|
const date = new Date(iso);
|
|
93
93
|
return Number.isNaN(date.getTime())
|
|
94
94
|
? iso
|
|
95
|
-
: date.toLocaleString(
|
|
95
|
+
: date.toLocaleString(undefined, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
export const documentMessagesFrom = (
|
|
@@ -182,7 +182,7 @@ export function DocumentToolCall({ active, document, onOpen, status }: DocumentT
|
|
|
182
182
|
<span className="text-[0.62rem] font-semibold tracking-[0.05em] text-primary uppercase">Document</span>
|
|
183
183
|
<strong className="truncate text-[0.84rem] leading-[1.35]" title={document.title}>{document.title}</strong>
|
|
184
184
|
<span className="text-[0.7rem] leading-[1.35] text-muted-foreground">
|
|
185
|
-
{formatLabels[document.format]}, {lineCount.toLocaleString(
|
|
185
|
+
{formatLabels[document.format]}, {lineCount.toLocaleString()} {lineCount === 1 ? "line" : "lines"}
|
|
186
186
|
</span>
|
|
187
187
|
</span>
|
|
188
188
|
<span className="flex items-center gap-1 text-[0.7rem] font-semibold text-primary group-data-[status=error]/call:text-destructive">
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { serviceToken, type CommandContext, type ServiceToken } from "@ragents/engine";
|
|
2
|
+
|
|
3
|
+
export interface SurfacePlacementRequest {
|
|
4
|
+
entity: string;
|
|
5
|
+
direction?: "horizontal" | "vertical";
|
|
6
|
+
weight?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Places an entity next to a run's surface layout, as canvas_layout_place does; an entity the layout shows already stays. */
|
|
10
|
+
export interface SurfacePlacement {
|
|
11
|
+
place: (context: CommandContext, runId: string, request: SurfacePlacementRequest) => { placed: boolean };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const surfacePlacementToken: ServiceToken<SurfacePlacement> = serviceToken("ragents.orchestration.surface-placement");
|
|
@@ -11,7 +11,8 @@ import { runtimeProviderToken } from "@ragents/host/ragents/host-services.js";
|
|
|
11
11
|
import { pluginAsset } from "@ragents/host/plugin-support/plugin-folder.js";
|
|
12
12
|
import type { PluginModule } from "@ragents/host/plugin-support/plugin-module.js";
|
|
13
13
|
import { boundToTools, handlebarsPrompt } from "@ragents/host/plugin-support/prompt.js";
|
|
14
|
-
import { createSurfaceToolContributor } from "./surface-tool.js";
|
|
14
|
+
import { createSurfaceToolContributor, placeOnSurface } from "./surface-tool.js";
|
|
15
|
+
import { surfacePlacementToken } from "./contract.js";
|
|
15
16
|
import { createRunStopContributor } from "./run-stop-tool.js";
|
|
16
17
|
import { runManagementToken } from "@ragents/host/ragents/global-chat.js";
|
|
17
18
|
|
|
@@ -19,6 +20,7 @@ const orchestrationPlugin: RAgentsPlugin = {
|
|
|
19
20
|
manifest: { id: "ragents.orchestration" },
|
|
20
21
|
register: (host) => {
|
|
21
22
|
host.functions(createSurfaceToolContributor());
|
|
23
|
+
host.provide(surfacePlacementToken, placeOnSurface(host.service(runtimeProviderToken)));
|
|
22
24
|
host.functions(createRunStopContributor(
|
|
23
25
|
(runId) => host.service(runManagementToken)().stop(runId),
|
|
24
26
|
(error) => console.error("Run stop by the coordinator failed", error),
|
|
@@ -53,6 +55,7 @@ const orchestrationPlugin: RAgentsPlugin = {
|
|
|
53
55
|
boundToTools(
|
|
54
56
|
handlebarsPrompt("ragents.orchestration.surface", 650, pluginAsset("ragents.orchestration", "surface.hbs")),
|
|
55
57
|
"canvas_layout_replace",
|
|
58
|
+
"canvas_layout_place",
|
|
56
59
|
),
|
|
57
60
|
);
|
|
58
61
|
},
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
defineRunFunction,
|
|
6
6
|
defineToolAvailability,
|
|
7
7
|
holdsUsable,
|
|
8
|
+
type CommandContext,
|
|
9
|
+
type Orchestration,
|
|
8
10
|
type RunFunction,
|
|
9
11
|
type RunView,
|
|
10
12
|
type ToolContributor,
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
type SurfaceLayout,
|
|
19
21
|
} from "../contract.js";
|
|
20
22
|
import { surfaceTileEntities, mapSurfaceTileEntities } from "../tiled-layout.js";
|
|
23
|
+
import type { SurfacePlacement, SurfacePlacementRequest } from "./contract.js";
|
|
21
24
|
|
|
22
25
|
export const surfaceToolMetadata = {
|
|
23
26
|
name: "canvas_layout_replace",
|
|
@@ -34,6 +37,17 @@ export const surfaceToolMetadata = {
|
|
|
34
37
|
+ "Participants you do not place stay reachable through the run header.",
|
|
35
38
|
} as const;
|
|
36
39
|
|
|
40
|
+
export const placeToolMetadata = {
|
|
41
|
+
name: "canvas_layout_place",
|
|
42
|
+
label: "Place on Surface",
|
|
43
|
+
description: "Place one actor or mini-app next to the current surface layout instead of replacing it.",
|
|
44
|
+
longDescription: "entity is {entity} of a tile: '@helper' or 'app:@workspace/main'. The current arrangement becomes one side of a new split "
|
|
45
|
+
+ "and the entity the other: direction 'horizontal' (default) puts it to the right, 'vertical' below. weight is its share against "
|
|
46
|
+
+ "the existing arrangement's weight 1, default 1. An entity the layout already shows stays where it is; placed is then false. "
|
|
47
|
+
+ "On an empty surface the entity becomes the only tile. Use it when you join a running run, such as a run script started with "
|
|
48
|
+
+ "start.embedded, so that the tiles others arranged stay.",
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
37
51
|
export const canDesignSurface = defineToolAvailability({
|
|
38
52
|
availability: "conditional",
|
|
39
53
|
availabilityDetail: "Only for agents and TypeScript actors with the capability plugin.state.write.",
|
|
@@ -117,6 +131,48 @@ export const checkLayoutAgainstRun = (view: RunView, input: SurfaceLayout): Surf
|
|
|
117
131
|
return layout;
|
|
118
132
|
};
|
|
119
133
|
|
|
134
|
+
const currentLayout = (view: RunView): SurfaceLayout => {
|
|
135
|
+
const state = view.pluginStates.find((entry) => entry.pluginId === ORCHESTRATION_PLUGIN_ID && entry.scope.kind === "run")?.state;
|
|
136
|
+
return state === undefined || state === null ? { root: null } : surfaceLayoutOf(state);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** Splits the root once: the existing arrangement keeps weight 1, the placed tile gets its own. */
|
|
140
|
+
const placed = (view: RunView, request: SurfacePlacementRequest): SurfaceLayout | undefined => {
|
|
141
|
+
const current = currentLayout(view);
|
|
142
|
+
const [tile] = surfaceTileEntities(checkLayoutAgainstRun(view, parsedLayout({ root: { entity: request.entity } })).root);
|
|
143
|
+
if (surfaceTileEntities(current.root).includes(tile!)) return undefined;
|
|
144
|
+
const root = current.root === null ? { entity: tile! } : {
|
|
145
|
+
direction: request.direction ?? "horizontal",
|
|
146
|
+
weights: [1, request.weight ?? 1],
|
|
147
|
+
children: [current.root, { entity: tile! }],
|
|
148
|
+
};
|
|
149
|
+
return parsedLayout({ root });
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const placeOnSurface = (runtime: () => Orchestration): SurfacePlacement => ({
|
|
153
|
+
place: (context: CommandContext, runId: string, request: SurfacePlacementRequest) => {
|
|
154
|
+
const layout = placed(runtime().view(runId), request);
|
|
155
|
+
if (!layout) return { placed: false };
|
|
156
|
+
runtime().replacePluginState(context, runId, { pluginId: ORCHESTRATION_PLUGIN_ID, scope: { kind: "run" }, state: layout });
|
|
157
|
+
return { placed: true };
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const placeSchema = Type.Object({
|
|
162
|
+
entity: Type.String({ minLength: 1, description: "Actor @handle or activated mini-app app:@handle/view-key or app:program-name/view-key" }),
|
|
163
|
+
direction: Type.Optional(Type.Union([Type.Literal("horizontal"), Type.Literal("vertical")], { description: "horizontal: to the right of the current layout (default); vertical: below it" })),
|
|
164
|
+
weight: Type.Optional(Type.Number({ exclusiveMinimum: 0, description: "Share of the placed tile against the existing arrangement's weight 1; default 1" })),
|
|
165
|
+
}, { additionalProperties: false });
|
|
166
|
+
|
|
167
|
+
export const createPlaceTool = (): RunFunction =>
|
|
168
|
+
defineRunFunction({
|
|
169
|
+
...placeToolMetadata,
|
|
170
|
+
schema: placeSchema,
|
|
171
|
+
resultSchema: Type.Object({ placed: Type.Boolean() }, { additionalProperties: false }),
|
|
172
|
+
available: canDesignSurface,
|
|
173
|
+
run: ({ runtime, caller, context }, toolCallId, input) => placeOnSurface(() => runtime).place(context(toolCallId), caller.runId, input),
|
|
174
|
+
});
|
|
175
|
+
|
|
120
176
|
export const createSurfaceTool = (): RunFunction =>
|
|
121
177
|
defineRunFunction({
|
|
122
178
|
...surfaceToolMetadata,
|
|
@@ -137,6 +193,6 @@ export const createSurfaceTool = (): RunFunction =>
|
|
|
137
193
|
|
|
138
194
|
export const createSurfaceToolContributor = (): ToolContributor => ({
|
|
139
195
|
name: "ragents.orchestration.surface",
|
|
140
|
-
descriptors: [toolDescriptorFrom(surfaceToolMetadata, canDesignSurface)],
|
|
141
|
-
tools: () => [createSurfaceTool()],
|
|
196
|
+
descriptors: [toolDescriptorFrom(surfaceToolMetadata, canDesignSurface), toolDescriptorFrom(placeToolMetadata, canDesignSurface)],
|
|
197
|
+
tools: () => [createSurfaceTool(), createPlaceTool()],
|
|
142
198
|
});
|
|
@@ -19,6 +19,11 @@ App left, chat right at 50:50:
|
|
|
19
19
|
]}}. Resolve the actual actor and app names before calling; the example names are not existing
|
|
20
20
|
participants.
|
|
21
21
|
|
|
22
|
+
To add one tile without replacing what is arranged, `context.functions.canvas_layout_place({ entity })`
|
|
23
|
+
splits the current layout once and puts the entity to its right, or below with `direction: "vertical"`;
|
|
24
|
+
`weight` is its share against the existing arrangement's weight 1. An entity the layout shows already
|
|
25
|
+
stays where it is. A run script started inside a running run (`start.embedded`) uses place, not replace.
|
|
26
|
+
|
|
22
27
|
An actor tile carries {entity:"@helper",chatInput:false} when its chat composer should stay hidden.
|
|
23
28
|
That changes neither permissions nor the inspector. Mini-app tiles have no chatInput.
|
|
24
29
|
|
|
@@ -91,7 +91,7 @@ function ExecutionEntry({ execution, now }: { execution: TypeScriptExecution; no
|
|
|
91
91
|
<span className="truncate font-[650]" title={`${execution.actorName} (${execution.actorId})`}>@{execution.actorHandle}</span>
|
|
92
92
|
<Badge className={`h-auto rounded-[5px] px-[5px] py-0.5 text-[0.64rem] ${statusBadgeClass[execution.status] ?? "bg-secondary text-muted-foreground"}`}>{executionStatusLabel(execution.status)}</Badge>
|
|
93
93
|
<span className="col-span-full truncate font-mono text-[0.68rem]" title={preview}>{preview}</span>
|
|
94
|
-
<span className="col-span-full flex flex-wrap justify-between gap-x-3 gap-y-1.5 text-[0.65rem] tabular-nums text-muted-foreground"><time dateTime={execution.startedAt} title={new Date(execution.startedAt).toLocaleString(
|
|
94
|
+
<span className="col-span-full flex flex-wrap justify-between gap-x-3 gap-y-1.5 text-[0.65rem] tabular-nums text-muted-foreground"><time dateTime={execution.startedAt} title={new Date(execution.startedAt).toLocaleString()}>{new Date(execution.startedAt).toLocaleString(undefined, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" })}</time><span>{executionDuration(execution, now)}</span></span>
|
|
95
95
|
</summary>
|
|
96
96
|
{expanded && <div className={contentClass}>
|
|
97
97
|
{execution.path && <p className="mt-2.5 grid gap-1 text-[0.68rem] text-muted-foreground"><span>Path</span><code className="[overflow-wrap:anywhere]">{execution.path}</code></p>}
|
|
@@ -128,7 +128,7 @@ const runCostUsd = (view: RunView): number | undefined => {
|
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
const formatCostUsd = (value: number) =>
|
|
131
|
-
`${value.toLocaleString(
|
|
131
|
+
`${value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: value > 0 && value < 0.005 ? 4 : 2 })} USD`;
|
|
132
132
|
|
|
133
133
|
interface RunIndex {
|
|
134
134
|
view: RunView;
|
|
@@ -80,7 +80,7 @@ function JournalEntry({ actor, event }: { actor: string | undefined; event: Jour
|
|
|
80
80
|
<span className="text-muted-foreground tabular-nums">{event.sequence}</span>
|
|
81
81
|
<span className="font-mono [overflow-wrap:anywhere]">{event.type}</span>
|
|
82
82
|
<span className="truncate max-[550px]:col-start-2 max-[550px]:row-start-2" title={event.actorId}>{actor ? `@${actor}` : event.actorId}</span>
|
|
83
|
-
<time className="text-muted-foreground tabular-nums max-[550px]:col-start-3 max-[550px]:row-start-1" dateTime={event.occurredAt}>{new Date(event.occurredAt).toLocaleTimeString(
|
|
83
|
+
<time className="text-muted-foreground tabular-nums max-[550px]:col-start-3 max-[550px]:row-start-1" dateTime={event.occurredAt}>{new Date(event.occurredAt).toLocaleTimeString()}</time>
|
|
84
84
|
</summary>
|
|
85
85
|
{expanded && <SourceCode className="m-0 mb-2 text-[0.68rem]" content={JSON.stringify(event, null, 2)} language="json" path="event.json" />}
|
|
86
86
|
</details>;
|
|
@@ -3,7 +3,7 @@ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogH
|
|
|
3
3
|
import { RunModalContext } from "@ragents/web/ui/dialog";
|
|
4
4
|
import { XIcon } from "lucide-react";
|
|
5
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
-
import { ChatMessages, ChatInputToolbar, type ChatInputHandle, type ChatEvent } from "quassel";
|
|
6
|
+
import { ChatMessages, ChatInputToolbar, ChatPanel, type ChatInputHandle, type ChatEvent } from "quassel";
|
|
7
7
|
import { useChat } from "@ragents/web/chat/useChat";
|
|
8
8
|
import { useAttachmentCapabilities } from "@ragents/web/chat/useAttachmentCapabilities";
|
|
9
9
|
import { ChatStepsProvider, type OverviewPanelContext, type WebPlugin } from "@ragents/web/PluginRegistry";
|
|
@@ -30,7 +30,7 @@ const interruptCoordinator = async (runId: string) => {
|
|
|
30
30
|
const errorClass = "mx-4 my-2 flex-none text-[0.8rem] text-destructive";
|
|
31
31
|
|
|
32
32
|
/** Every user has their own coordinator; only the server knows its id. */
|
|
33
|
-
function
|
|
33
|
+
function useCoordinator() {
|
|
34
34
|
const user = useAccess().user?.id;
|
|
35
35
|
const [coordinator, setCoordinator] = useState<{ runId?: string; error?: string }>({});
|
|
36
36
|
useEffect(() => {
|
|
@@ -42,6 +42,11 @@ function OverseerToolbar(context: OverviewPanelContext) {
|
|
|
42
42
|
);
|
|
43
43
|
return () => { current = false; };
|
|
44
44
|
}, [user]);
|
|
45
|
+
return coordinator;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function OverseerToolbar(context: OverviewPanelContext) {
|
|
49
|
+
const coordinator = useCoordinator();
|
|
45
50
|
if (!coordinator.runId) {
|
|
46
51
|
return <div className={toolbarClass} data-slot="overseer-toolbar" role="status">
|
|
47
52
|
{coordinator.error && <span className="text-[0.7rem] font-bold text-destructive" title={coordinator.error} aria-label={coordinator.error}>!</span>}
|
|
@@ -226,8 +231,51 @@ function OverseerConversation({ open, onOpen, onClose, onBusy, userLocation, run
|
|
|
226
231
|
</div>;
|
|
227
232
|
}
|
|
228
233
|
|
|
234
|
+
/** The coordinator's chat fills the main area while no run is open. */
|
|
235
|
+
function OverseerIdleView({ userLocation }: OverviewPanelContext) {
|
|
236
|
+
const coordinator = useCoordinator();
|
|
237
|
+
if (!coordinator.runId) {
|
|
238
|
+
return <p className={coordinator.error ? "m-auto p-6 text-center text-destructive" : "m-auto p-6 text-center text-muted-foreground"} role="status">
|
|
239
|
+
{coordinator.error ?? "Connecting to the global coordinator ..."}
|
|
240
|
+
</p>;
|
|
241
|
+
}
|
|
242
|
+
return <ChatStepsProvider policy={overseerChatDisplayPolicy} storageKeyPrefix={overseerChatStorageKeyPrefix}>
|
|
243
|
+
<OverseerIdleChat key={coordinator.runId} runId={coordinator.runId} userLocation={userLocation} />
|
|
244
|
+
</ChatStepsProvider>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function OverseerIdleChat({ runId, userLocation }: { runId: string; userLocation: OverviewPanelContext["userLocation"] }) {
|
|
248
|
+
const writable = useAccess().can("ragents.overseer.write");
|
|
249
|
+
const chat = useChat(runId);
|
|
250
|
+
const chatView = useChatViewSettings(runId, "primary", "coordinator");
|
|
251
|
+
const modelState = useModelSettings();
|
|
252
|
+
const attachments = useAttachmentCapabilities(runId, "primary", JSON.stringify(modelState.settings && [modelState.settings.provider, modelState.settings.model]));
|
|
253
|
+
const messages = useMemo(() => withToolSummaries(chat.messages), [chat.messages]);
|
|
254
|
+
const [error, setError] = useState<string>();
|
|
255
|
+
const problem = error ?? (chat.connected ? undefined : "No connection. Your draft is kept; sending is disabled.");
|
|
256
|
+
return <ChatPanel className="flex h-full min-h-0 w-full min-w-0 flex-1 flex-col overflow-hidden" maxWidth="880px"
|
|
257
|
+
composer={<div className="[--qsl-input-card-radius:var(--radius-lg)]">
|
|
258
|
+
{problem && <p className="text-[0.8rem] text-destructive" role="alert">{problem}</p>}
|
|
259
|
+
<ChatInputToolbar {...attachments} disabled={!writable} sendDisabled={!chat.connected || modelState.status === "saving"} maxRows={6} rows={1}
|
|
260
|
+
onErrorChange={setError} onSend={(text, attachments) => chat.send(text, attachments, userLocation)}
|
|
261
|
+
onStop={writable && chat.running ? () => { setError(undefined); void interruptCoordinator(runId).catch((cause: unknown) => setError(cause instanceof Error ? cause.message : String(cause))); } : undefined}
|
|
262
|
+
running={chat.running}
|
|
263
|
+
texts={{ placeholder: writable ? "Ask the global coordinator" : "Read access to the global coordinator", steeringPlaceholder: "Ask the global coordinator" }}
|
|
264
|
+
toolbarLeft={<ChatViewSwitches settings={chatView} />}
|
|
265
|
+
/>
|
|
266
|
+
</div>}>
|
|
267
|
+
<ChatMessages detailMode={chatView.detailMode}
|
|
268
|
+
emptyState={<div className="m-auto max-w-[480px] p-8 text-center text-[0.9rem] leading-[1.6] text-muted-foreground max-md:p-5"><strong className="text-foreground">Global coordinator</strong><p>No run is open. Ask about your runs or give a task; open or start a run through the overview at the top left.</p></div>}
|
|
269
|
+
messages={messages} running={chat.running} showTimestamps={chatView.showTimestamps} stepsExpandable={chatView.stepsExpandable}
|
|
270
|
+
/>
|
|
271
|
+
</ChatPanel>;
|
|
272
|
+
}
|
|
273
|
+
|
|
229
274
|
export const webPlugin: WebPlugin = {
|
|
230
275
|
id: OVERSEER_PLUGIN_ID,
|
|
231
276
|
settings: [{ category: "models", order: 10, readRight: "ragents.overseer.read", id: `${OVERSEER_PLUGIN_ID}.model`, label: "Global coordinator", Settings: ModelSettings }],
|
|
232
|
-
overviewPanels: [
|
|
277
|
+
overviewPanels: [
|
|
278
|
+
{ id: `${OVERSEER_PLUGIN_ID}.chat`, placement: "toolbar", order: 100, readRight: "ragents.overseer.read", Panel: OverseerToolbar },
|
|
279
|
+
{ id: `${OVERSEER_PLUGIN_ID}.idle`, placement: "idle", order: 100, readRight: "ragents.overseer.read", Panel: OverseerIdleView },
|
|
280
|
+
],
|
|
233
281
|
};
|
|
@@ -54,7 +54,7 @@ const originText = (origin: RunProcess["origin"]): string =>
|
|
|
54
54
|
|
|
55
55
|
const timeText = (iso: string): string => {
|
|
56
56
|
const parsed = new Date(iso);
|
|
57
|
-
return Number.isNaN(parsed.getTime()) ? iso : parsed.toLocaleTimeString(
|
|
57
|
+
return Number.isNaN(parsed.getTime()) ? iso : parsed.toLocaleTimeString();
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export const titleOf = (process: RunProcess): string => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "notebook",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"ragents": {
|
|
6
|
+
"title": "Notebook",
|
|
7
|
+
"description": "Keeps the notes that run scripts send it, newest first.",
|
|
8
|
+
"backend": "src/server.ts",
|
|
9
|
+
"views": [
|
|
10
|
+
{
|
|
11
|
+
"id": "main",
|
|
12
|
+
"client": "src/client.tsx"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { context, useAppState } from "@ragents/client";
|
|
4
|
+
import * as UI from "@ragents/client/ui";
|
|
5
|
+
|
|
6
|
+
const App = () => {
|
|
7
|
+
const state = useAppState();
|
|
8
|
+
const notes = state.notes ?? [];
|
|
9
|
+
const [status, setStatus] = React.useState("Run scripts add their notes here.");
|
|
10
|
+
|
|
11
|
+
const clear = async (): Promise<void> => {
|
|
12
|
+
const answer = await context.capabilities.call("clear", {});
|
|
13
|
+
setStatus(`Cleared ${answer.cleared} ${answer.cleared === 1 ? "note" : "notes"}.`);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<UI.AppLayout title="Notebook" description={<>{notes.length} {notes.length === 1 ? "note" : "notes"}</>}>
|
|
18
|
+
<UI.Stack>
|
|
19
|
+
<p className="text-muted-foreground" role="status">{status}</p>
|
|
20
|
+
<ul className="border-t border-border">
|
|
21
|
+
{notes.map((note, index) => <li className="min-h-8 border-b border-border-soft py-1.5 [overflow-wrap:anywhere]" key={index}>{note}</li>)}
|
|
22
|
+
</ul>
|
|
23
|
+
<UI.Button disabled={notes.length === 0} onClick={() => void clear()} variant="outline">Clear</UI.Button>
|
|
24
|
+
</UI.Stack>
|
|
25
|
+
</UI.AppLayout>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const root = document.getElementById("root");
|
|
30
|
+
if (!root) throw new Error("The #root element is missing.");
|
|
31
|
+
createRoot(root).render(<App />);
|
|
@@ -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
|
+
});
|
|
@@ -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,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,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
|
+
});
|