@schlenkr/ragents 0.1.6 → 0.1.7

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 (145) hide show
  1. package/apps/server/src/chat-handler.ts +2 -0
  2. package/apps/server/src/config.ts +6 -0
  3. package/apps/server/src/host-api.json +1 -0
  4. package/apps/server/src/layout.ts +0 -15
  5. package/apps/server/src/plugin-support/agent-tool.ts +2 -3
  6. package/apps/server/src/provider.ts +34 -11
  7. package/apps/server/src/ragents/actor-chat-history.ts +1 -0
  8. package/apps/server/src/ragents/engine.ts +5 -13
  9. package/apps/server/src/ragents/journal-chat-events.ts +2 -0
  10. package/apps/server/src/ragents/run-owner.ts +3 -2
  11. package/apps/server/src/ragents/session.ts +3 -3
  12. package/apps/server/src/run-preparation.ts +14 -18
  13. package/apps/server/src/run-transfer.ts +65 -4
  14. package/apps/server/src/settings.ts +2 -4
  15. package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-B9qYBARS.js → highlighted-body-KPVGNVTW-CFhTJEmH.js} +1 -1
  16. package/apps/web/dist/assets/{main-BCjvD2Hh.js → main-DlzW4t4c.js} +1 -1
  17. package/apps/web/dist/assets/{mermaid-HWGCJPDP-CvYYFK_n.js → mermaid-HWGCJPDP-kxkB991i.js} +69 -69
  18. package/apps/web/dist/assets/{runPanel-CG7kRMJh.js → runPanel-BJEFQ__w.js} +1 -1
  19. package/apps/web/dist/help/guide-clients.html +13 -4
  20. package/apps/web/dist/help/guide-clients.md +14 -4
  21. package/apps/web/dist/help/guide-distributed.html +8 -3
  22. package/apps/web/dist/help/guide-distributed.md +8 -3
  23. package/apps/web/dist/help/guide-runtime.html +18 -12
  24. package/apps/web/dist/help/guide-runtime.md +19 -12
  25. package/apps/web/dist/help/mini-app.css +23 -3
  26. package/apps/web/dist/host-web.json +3 -3
  27. package/apps/web/dist/index.html +2 -2
  28. package/apps/web/dist/run-panel.html +2 -2
  29. package/apps/web/src/SessionList.tsx +16 -8
  30. package/apps/web/src/api.ts +2 -0
  31. package/apps/web/src/panel/PanelPage.tsx +1 -1
  32. package/apps/web/src/panel/RunLine.tsx +4 -2
  33. package/apps/web/src/panel/contract.ts +2 -0
  34. package/bundles/ragents.activity/ragents-bundle.json +1 -1
  35. package/bundles/ragents.actor-programs/ragents-bundle.json +1 -1
  36. package/bundles/ragents.ask/ragents-bundle.json +1 -1
  37. package/bundles/ragents.browser/ragents-bundle.json +1 -1
  38. package/bundles/ragents.documents/ragents-bundle.json +1 -1
  39. package/bundles/ragents.lsp-fsharp/ragents-bundle.json +1 -1
  40. package/bundles/ragents.lsp-roslyn/ragents-bundle.json +1 -1
  41. package/bundles/ragents.lsp-typescript/ragents-bundle.json +1 -1
  42. package/bundles/ragents.model-relay/ragents-bundle.json +1 -1
  43. package/bundles/ragents.orchestration/ragents-bundle.json +1 -1
  44. package/bundles/ragents.overseer/ragents-bundle.json +1 -1
  45. package/bundles/ragents.processes/ragents-bundle.json +1 -1
  46. package/bundles/ragents.product/ragents-bundle.json +1 -1
  47. package/bundles/ragents.profile-distribution/ragents-bundle.json +1 -1
  48. package/bundles/ragents.reference/ragents-bundle.json +2 -2
  49. package/bundles/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
  50. package/bundles/ragents.reference/run-scripts/word-game/tests/program.test.ts +3 -3
  51. package/bundles/ragents.todo/ragents-bundle.json +1 -1
  52. package/bundles/ragents.transcript/ragents-bundle.json +1 -1
  53. package/bundles/ragents.watch/ragents-bundle.json +1 -1
  54. package/bundles/ragents.workspace/ragents-bundle.json +1 -1
  55. package/package.json +2 -2
  56. package/packages/agent/dist/core/agent-settings.d.ts +2 -2
  57. package/packages/agent/dist/core/compaction/compaction.d.ts +16 -50
  58. package/packages/agent/dist/core/context-log.d.ts +25 -0
  59. package/packages/agent/dist/core/messages.d.ts +3 -10
  60. package/packages/agent/dist/core/tool-definition.d.ts +17 -0
  61. package/packages/agent/dist/core/tools/bash.d.ts +2 -7
  62. package/packages/agent/dist/core/tools/edit.d.ts +1 -1
  63. package/packages/agent/dist/core/tools/read.d.ts +1 -1
  64. package/packages/agent/dist/core/tools/write.d.ts +1 -1
  65. package/packages/agent/dist/index.d.ts +7 -8
  66. package/packages/agent/dist/loop/agent.d.ts +2 -6
  67. package/packages/agent/package.json +1 -1
  68. package/packages/agent/src/core/agent-settings.ts +2 -2
  69. package/packages/agent/src/core/compaction/compaction.ts +49 -175
  70. package/packages/agent/src/core/context-log.ts +49 -0
  71. package/packages/agent/src/core/messages.ts +5 -11
  72. package/packages/agent/src/core/tool-definition.ts +29 -0
  73. package/packages/agent/src/core/tools/bash.ts +2 -9
  74. package/packages/agent/src/core/tools/edit.ts +2 -11
  75. package/packages/agent/src/core/tools/read.ts +3 -18
  76. package/packages/agent/src/core/tools/write.ts +1 -5
  77. package/packages/agent/src/index.ts +28 -14
  78. package/packages/agent/src/loop/agent-loop.ts +1 -2
  79. package/packages/agent/src/loop/agent.ts +8 -2
  80. package/packages/ai/dist/api/ai-sdk.d.ts +2 -0
  81. package/packages/ai/dist/types.d.ts +2 -0
  82. package/packages/ai/src/api/ai-sdk-messages.ts +15 -10
  83. package/packages/ai/src/api/ai-sdk.ts +11 -0
  84. package/packages/ai/src/api/transform-messages.ts +1 -1
  85. package/packages/ai/src/types.ts +2 -0
  86. package/packages/ai/src/utils/retry.ts +14 -0
  87. package/packages/ragents/src/agents/actor-input.ts +15 -0
  88. package/packages/ragents/src/agents/model-context.ts +360 -0
  89. package/packages/ragents/src/agents/scheduler.ts +3 -7
  90. package/packages/ragents/src/agents/tools.ts +2 -2
  91. package/packages/ragents/src/agents/toolset.ts +47 -20
  92. package/packages/ragents/src/agents/workspaces.ts +0 -6
  93. package/packages/ragents/src/domain/event-semantics.ts +51 -0
  94. package/packages/ragents/src/domain/event-validation.ts +166 -0
  95. package/packages/ragents/src/domain/events.ts +72 -0
  96. package/packages/ragents/src/domain/model.ts +4 -2
  97. package/packages/ragents/src/domain/projection.ts +9 -0
  98. package/packages/ragents/src/domain/tool-result-text.ts +9 -0
  99. package/packages/ragents/src/drivers/agent-hooks.ts +25 -38
  100. package/packages/ragents/src/drivers/agent-runtime.ts +118 -933
  101. package/packages/ragents/src/drivers/agent-turn.ts +735 -0
  102. package/packages/ragents/src/drivers/agent.ts +9 -7
  103. package/packages/ragents/src/drivers/skill-preload.ts +53 -63
  104. package/packages/ragents/src/drivers/types.ts +21 -5
  105. package/packages/ragents/src/index.ts +7 -6
  106. package/packages/ragents/src/plugin-host.ts +3 -4
  107. package/packages/ragents/src/runtime/decisions/actors.ts +10 -0
  108. package/packages/ragents/src/runtime/decisions/turns.ts +66 -7
  109. package/packages/ragents/src/runtime/journal-storage.ts +7 -4
  110. package/packages/ragents/src/runtime/journal.ts +35 -0
  111. package/packages/ragents/src/runtime/orchestration.ts +140 -9
  112. package/packages/workspace-executor/src/sandbox-tools.ts +10 -11
  113. package/plugins/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
  114. package/plugins/ragents.reference/run-scripts/word-game/tests/program.test.ts +3 -3
  115. package/scripts/agent/journal.ts +5 -3
  116. package/packages/agent/dist/core/agent-session-runtime.d.ts +0 -33
  117. package/packages/agent/dist/core/agent-session-services.d.ts +0 -35
  118. package/packages/agent/dist/core/agent-session.d.ts +0 -234
  119. package/packages/agent/dist/core/auth-guidance.d.ts +0 -4
  120. package/packages/agent/dist/core/defaults.d.ts +0 -3
  121. package/packages/agent/dist/core/extensions/index.d.ts +0 -11
  122. package/packages/agent/dist/core/extensions/loader.d.ts +0 -12
  123. package/packages/agent/dist/core/extensions/runner.d.ts +0 -39
  124. package/packages/agent/dist/core/extensions/types.d.ts +0 -156
  125. package/packages/agent/dist/core/extensions/wrapper.d.ts +0 -12
  126. package/packages/agent/dist/core/resource-loader.d.ts +0 -39
  127. package/packages/agent/dist/core/sdk.d.ts +0 -38
  128. package/packages/agent/dist/core/session-manager.d.ts +0 -189
  129. package/packages/agent/dist/core/source-info.d.ts +0 -16
  130. package/packages/agent/dist/core/system-prompt.d.ts +0 -21
  131. package/packages/agent/src/core/agent-session-runtime.ts +0 -94
  132. package/packages/agent/src/core/agent-session-services.ts +0 -74
  133. package/packages/agent/src/core/agent-session.ts +0 -1108
  134. package/packages/agent/src/core/auth-guidance.ts +0 -14
  135. package/packages/agent/src/core/defaults.ts +0 -3
  136. package/packages/agent/src/core/extensions/index.ts +0 -38
  137. package/packages/agent/src/core/extensions/loader.ts +0 -125
  138. package/packages/agent/src/core/extensions/runner.ts +0 -211
  139. package/packages/agent/src/core/extensions/types.ts +0 -197
  140. package/packages/agent/src/core/extensions/wrapper.ts +0 -24
  141. package/packages/agent/src/core/resource-loader.ts +0 -142
  142. package/packages/agent/src/core/sdk.ts +0 -123
  143. package/packages/agent/src/core/session-manager.ts +0 -743
  144. package/packages/agent/src/core/source-info.ts +0 -28
  145. package/packages/agent/src/core/system-prompt.ts +0 -108
@@ -10,12 +10,13 @@ export type {
10
10
  AgentRuntimeContext,
11
11
  AgentRuntimeDiagnostic,
12
12
  AgentRuntimeManagerOptions,
13
- AgentSessionStore,
14
13
  } from "./agent-runtime.ts";
15
- export { AgentRuntimeManager, turnDispatcherExtensionName } from "./agent-runtime.ts";
14
+ export { AgentRuntimeManager } from "./agent-runtime.ts";
15
+ export { agentHookOf, type AgentHook } from "./agent-hooks.ts";
16
16
  export {
17
- createSkillPreloadExtension,
18
- skillPreloadExtensionName,
17
+ createSkillPreload,
18
+ skillPreloadHookName,
19
+ type SkillPreload,
19
20
  type SkillPreloadConfiguration,
20
21
  type SkillPreloadOptions,
21
22
  type SkillCatalogEntry,
@@ -23,17 +24,18 @@ export {
23
24
  type SkillSelector,
24
25
  } from "./skill-preload.ts";
25
26
 
26
- export type AgentSessionDriverOptions = Omit<AgentRuntimeManagerOptions, "modelRuntime"> & {
27
+ export type AgentLoopDriverOptions = Omit<AgentRuntimeManagerOptions, "modelRuntime"> & {
27
28
  modelRuntime?: ModelRuntime | Promise<ModelRuntime>;
28
29
  };
29
30
 
30
- export class AgentSessionDriver implements AgentDriver<"agent"> {
31
+ /** The driver of LLM actors: the agent loop on the model context the journal holds. */
32
+ export class AgentLoopDriver implements AgentDriver<"agent"> {
31
33
  readonly kind = "agent" as const;
32
34
  readonly supportsPlainLlm = true;
33
35
  readonly #modelRuntime: Promise<ModelRuntime>;
34
36
  readonly #runtimeManager: AgentRuntimeManager;
35
37
 
36
- constructor(options: AgentSessionDriverOptions = {}) {
38
+ constructor(options: AgentLoopDriverOptions = {}) {
37
39
  this.#modelRuntime = Promise.resolve(options.modelRuntime ?? ModelRuntime.create());
38
40
  this.#runtimeManager = new AgentRuntimeManager({ ...options, modelRuntime: this.#modelRuntime });
39
41
  }
@@ -1,10 +1,10 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
 
3
- import { stripFrontmatter, type InlineExtension, type ModelRuntime, type Skill } from "@ragents/agent";
3
+ import { stripFrontmatter, type ModelRuntime, type Skill } from "@ragents/agent";
4
4
 
5
5
  import type { TurnUsage } from "../domain/model.ts";
6
6
 
7
- export const skillPreloadExtensionName = "ragents-skill-preload";
7
+ export const skillPreloadHookName = "ragents-skill-preload";
8
8
 
9
9
  export type SkillCatalogEntry = {
10
10
  name: string;
@@ -130,64 +130,54 @@ const preloadedSkills = async (skills: readonly Skill[]): Promise<string> => {
130
130
  ].join("\n\n");
131
131
  };
132
132
 
133
- export const createSkillPreloadExtension = (options: SkillPreloadOptions): InlineExtension => ({
134
- name: skillPreloadExtensionName,
135
- factory: (agent) => {
136
- agent.on("before_agent_start", async (event, context) => {
137
- try {
138
- const skills = event.systemPromptOptions.skills ?? [];
139
-
140
- if (skills.length === 0)
141
- return;
142
-
143
- const explicit = explicitSkillNames(event.prompt, skills);
144
- let names: readonly string[] | null = explicit.length > 0 ? explicit : null;
145
- let candidates = skills;
146
-
147
- if (explicit.length === 0) {
148
- candidates = skills.filter((skill) => !skill.disableModelInvocation);
149
- const catalog = candidates.map(({ name, description }) => ({ name, description }));
150
-
151
- if (catalog.length === 0)
152
- return;
153
-
154
- const selector = options.selector ?? (context.model
155
- ? agentSelector(
156
- options.modelRuntime,
157
- context.model,
158
- options.maxSelectionTokens ?? 100,
159
- options.onUsage,
160
- )
161
- : null);
162
-
163
- if (!selector)
164
- return;
165
-
166
- names = await selector({
167
- prompt: event.prompt,
168
- skills: catalog,
169
- signal: context.signal,
170
- });
171
- }
172
-
173
- if (!names || names.length === 0)
174
- return;
175
-
176
- const selected = names
177
- .map((name) => candidates.find((skill) => skill.name === name))
178
- .filter((skill): skill is Skill => skill !== undefined);
179
-
180
- if (selected.length === 0)
181
- return;
182
-
183
- return {
184
- systemPrompt: `${event.systemPrompt}\n\n${await preloadedSkills(selected)}`,
185
- };
186
- } catch (error) {
187
- options.onDiagnostic?.(
188
- `Agent skill preloading fell back to the skill catalog: ${error instanceof Error ? error.message : String(error)}`,
189
- );
190
- }
191
- });
192
- },
193
- });
133
+ export type SkillPreloadRequest = {
134
+ prompt: string;
135
+ skills: readonly Skill[];
136
+ model: ReturnType<ModelRuntime["getModels"]>[number];
137
+ signal: AbortSignal | undefined;
138
+ };
139
+
140
+ /** Selects skills for the task of a turn and returns their bodies as a section of this turn's system prompt, or undefined. */
141
+ export type SkillPreload = (request: SkillPreloadRequest) => Promise<string | undefined>;
142
+
143
+ export const createSkillPreload = (options: SkillPreloadOptions): SkillPreload => async ({ prompt, skills, model, signal }) => {
144
+ try {
145
+ if (skills.length === 0)
146
+ return undefined;
147
+
148
+ const explicit = explicitSkillNames(prompt, skills);
149
+ let names: readonly string[] | null = explicit.length > 0 ? explicit : null;
150
+ let candidates = skills;
151
+
152
+ if (explicit.length === 0) {
153
+ candidates = skills.filter((skill) => !skill.disableModelInvocation);
154
+ const catalog = candidates.map(({ name, description }) => ({ name, description }));
155
+
156
+ if (catalog.length === 0)
157
+ return undefined;
158
+
159
+ const selector = options.selector ?? agentSelector(
160
+ options.modelRuntime,
161
+ model,
162
+ options.maxSelectionTokens ?? 100,
163
+ options.onUsage,
164
+ );
165
+
166
+ names = await selector({ prompt, skills: catalog, signal });
167
+ }
168
+
169
+ if (!names || names.length === 0)
170
+ return undefined;
171
+
172
+ const selected = names
173
+ .map((name) => candidates.find((skill) => skill.name === name))
174
+ .filter((skill): skill is Skill => skill !== undefined);
175
+
176
+ return selected.length === 0 ? undefined : await preloadedSkills(selected);
177
+ } catch (error) {
178
+ options.onDiagnostic?.(
179
+ `Agent skill preloading fell back to the skill catalog: ${error instanceof Error ? error.message : String(error)}`,
180
+ );
181
+ return undefined;
182
+ }
183
+ };
@@ -1,4 +1,9 @@
1
+ import type { ImageContent, TextContent, UserContent } from "@ragents/ai";
2
+
1
3
  import type { AgentDriverKind, ModelSelection } from "../domain/driver.ts";
4
+ import type { EventPayloads } from "../domain/events.ts";
5
+ import type { CompletedModelStep } from "../runtime/decisions/turns.ts";
6
+ import type { ModelContext } from "../agents/model-context.ts";
2
7
  import type { JsonValue } from "../domain/json.ts";
3
8
  import type { TurnUsage } from "../domain/model.ts";
4
9
  import type { DeliveredInput } from "../agents/delivery.ts";
@@ -24,20 +29,33 @@ type TurnDriverFacts = {
24
29
  agent: {
25
30
  driverKind: "agent";
26
31
  selection: ModelSelection;
27
- forkOf: string | null;
28
32
  /** `modelContext` names the model context the result enters, see `ToolScope.modelContext`; without it file tools track no seen state. */
29
33
  invoke: (toolCallId: string, name: string, input: JsonValue, modelContext?: string) => Promise<ToolInvocation>;
30
34
  /** Claims the pending inputs that may join this turn now, in journal order; empty once the turn ends or aborts. */
31
35
  claimSteering: () => readonly SteeredInput[];
36
+ /** The model context of this agent as the journal holds it now. */
37
+ modelContext: () => ModelContext;
38
+ /** Records what enters the model context; only a recorded entry is context. */
39
+ recordContext: (entry: ModelContextRecord) => void;
40
+ /** What a hook kept for this agent, in the journal as plugin state of the actor. */
41
+ hookState: {
42
+ kept: (hookId: string) => JsonValue | undefined;
43
+ keep: (hookId: string, value: JsonValue) => void;
44
+ };
32
45
  };
33
46
  };
34
47
 
35
48
  export type DriverEvent =
36
- | { kind: "assistant"; text: string }
37
49
  | { kind: "assistant-interrupted"; text: string }
38
- | { kind: "reasoning"; text: string }
39
50
  | { kind: "runtime"; text: string };
40
51
 
52
+ /** What a driver writes into the model context of its agent, in the order the model sees it. */
53
+ export type ModelContextRecord =
54
+ | { kind: "input"; inputId: string | null; content: string | readonly UserContent[] }
55
+ | { kind: "step"; step: CompletedModelStep }
56
+ | { kind: "tool-result"; toolCallId: string; toolName: string; isError: boolean; content: readonly (TextContent | ImageContent)[] }
57
+ | { kind: "compaction"; compaction: Omit<EventPayloads["context.compacted"], "turnId"> };
58
+
41
59
  export type DriverToolEvent =
42
60
  | { kind: "started"; id: string; name: string; input: JsonValue }
43
61
  | { kind: "completed"; id: string; name: string; output: JsonValue }
@@ -60,8 +78,6 @@ export type TurnRequest<Kind extends AutomatedDriverKind = AutomatedDriverKind>
60
78
  systemPrompt: string;
61
79
  /** The working directory of the workspace tools, as the model sees it; it may lie on another machine. */
62
80
  workspace: string;
63
- /** The folder on this machine for the driver's own work, resolved only when a driver needs it. */
64
- runtimeDirectory: () => Promise<string>;
65
81
  /** Stores an attached file under attachments/ of the workspace, where the workspace tools read it; returns its name there. */
66
82
  storeAttachment: (name: string, content: Uint8Array) => Promise<string>;
67
83
  tools: readonly RunFunction[];
@@ -91,18 +91,19 @@ export { TurnToolset, schemaComplaints, type ToolInvocation } from "./agents/too
91
91
  export { claimTurn, workingActorFrom, type ClaimedTurn } from "./agents/turn.ts";
92
92
  export { FixedWorkspaces, type Workspaces } from "./agents/workspaces.ts";
93
93
  export {
94
- AgentSessionDriver,
94
+ AgentLoopDriver,
95
95
  AgentRuntimeManager,
96
- createSkillPreloadExtension,
97
- skillPreloadExtensionName,
98
- turnDispatcherExtensionName,
99
- type AgentSessionDriverOptions,
96
+ agentHookOf,
97
+ createSkillPreload,
98
+ skillPreloadHookName,
99
+ type AgentHook,
100
+ type AgentLoopDriverOptions,
100
101
  type AgentRuntimeContext,
101
102
  type AgentRuntimeDiagnostic,
102
103
  type AgentRuntimeManagerOptions,
104
+ type SkillPreload,
103
105
  type SkillPreloadConfiguration,
104
106
  type SkillPreloadOptions,
105
- type AgentSessionStore,
106
107
  type SkillCatalogEntry,
107
108
  type SkillSelectionRequest,
108
109
  type SkillSelector,
@@ -1,7 +1,6 @@
1
1
  import path from "node:path";
2
2
  import type { IncomingMessage, ServerResponse } from "node:http";
3
- import type { InlineExtension } from "@ragents/agent";
4
- import { agentHookExtension } from "./drivers/agent-hooks.ts";
3
+ import { agentHookOf, type AgentHook } from "./drivers/agent-hooks.ts";
5
4
  import { Value } from "typebox/value";
6
5
  import { canStartEntry, defaultHttpRights, isAccessRight, unrestrictedAccess, type AccessContext } from "./access.ts";
7
6
  import { DomainError } from "./runtime/domain-error.ts";
@@ -230,8 +229,8 @@ export class AgentContributionRegistry {
230
229
  this.#contributions.register(owner, contributions);
231
230
  }
232
231
 
233
- resolve(context: AgentContributionContext): readonly InlineExtension[] {
234
- return this.#contributions.entries().map(({ value }) => agentHookExtension(value, context));
232
+ resolve(context: AgentContributionContext): readonly AgentHook[] {
233
+ return this.#contributions.entries().map(({ value }) => agentHookOf(value, context));
235
234
  }
236
235
 
237
236
  describe(): readonly PublicAgentHookContribution[] {
@@ -132,6 +132,16 @@ const forkSourceOf = (state: RunState, reference: string): string => {
132
132
  if (source.execution.driver.kind !== "agent")
133
133
  throw new DomainError("invalid-value", `Fork source ${reference} has no model context to fork.`, 400);
134
134
 
135
+ const finishedContext = [...state.turns.values()].some((turn) =>
136
+ turn.actorId === source.id && turn.status !== "running" && state.contextTurns.has(turn.id));
137
+
138
+ if (source.forkOf === null && !finishedContext)
139
+ throw new DomainError(
140
+ "fork-without-turn",
141
+ `Fork source ${reference} has no model context from a finished turn yet; a fork takes the context of finished turns only, never of the running one.`,
142
+ 409,
143
+ );
144
+
135
145
  return source.id;
136
146
  };
137
147
 
@@ -1,3 +1,4 @@
1
+ import type { EventPayloads, ModelInputContent, ModelStepBlock, ModelToolResultPart, UncommittedEvent } from "../../domain/events.ts";
1
2
  import type { JsonValue } from "../../domain/json.ts";
2
3
  import { isPendingActorInput, type TurnUsage } from "../../domain/model.ts";
3
4
  import { event, type Decision } from "../command.ts";
@@ -77,7 +78,7 @@ export const steerInputs =
77
78
  };
78
79
 
79
80
  const appendText = (
80
- type: "model.output.completed" | "model.output.interrupted" | "model.reasoning.completed" | "runtime.output.recorded",
81
+ type: "model.output.interrupted" | "runtime.output.recorded",
81
82
  actorId: string,
82
83
  input: { turnId: string; text: string },
83
84
  ): Decision => (state, context) => {
@@ -86,18 +87,76 @@ const appendText = (
86
87
  return [event(context, { type, payload: { turnId: input.turnId, text: clean(input.text, "text") } })];
87
88
  };
88
89
 
89
- export const appendModelOutput = (actorId: string, input: { turnId: string; text: string }): Decision =>
90
- appendText("model.output.completed", actorId, input);
91
-
92
90
  export const appendInterruptedModelOutput = (actorId: string, input: { turnId: string; text: string }): Decision =>
93
91
  appendText("model.output.interrupted", actorId, input);
94
92
 
95
- export const appendModelReasoning = (actorId: string, input: { turnId: string; text: string }): Decision =>
96
- appendText("model.reasoning.completed", actorId, input);
97
-
98
93
  export const appendRuntimeOutput = (actorId: string, input: { turnId: string; text: string }): Decision =>
99
94
  appendText("runtime.output.recorded", actorId, input);
100
95
 
96
+ /** The input exactly as the model received it, at its place between the model steps. */
97
+ export const presentModelInput =
98
+ (actorId: string, input: { turnId: string; inputId: string | null; content: ModelInputContent }): Decision =>
99
+ (state, context) => {
100
+ runningTurn(state, context, actorId, input.turnId);
101
+
102
+ return [event(context, { type: "model.input.presented", payload: input })];
103
+ };
104
+
105
+ export type CompletedModelStep = Omit<EventPayloads["model.step.completed"], "turnId" | "content"> & {
106
+ content: readonly (
107
+ | { type: "text"; text: string; textSignature?: string }
108
+ | { type: "thinking"; thinking: string; thinkingSignature?: string; redacted?: boolean }
109
+ | Extract<ModelStepBlock, { type: "toolCall" }>
110
+ )[];
111
+ };
112
+
113
+ /** One command: the text and reasoning a step made visible as observation events, then the step itself, which does not repeat them. */
114
+ export const completeModelStep =
115
+ (actorId: string, input: { turnId: string; step: CompletedModelStep }): Decision =>
116
+ (state, context) => {
117
+ runningTurn(state, context, actorId, input.turnId);
118
+ const observed: UncommittedEvent[] = [];
119
+ const content = input.step.content.map((block): ModelStepBlock => {
120
+ if (block.type === "text" && block.text.trim()) {
121
+ observed.push(event(context, { type: "model.output.completed", payload: { turnId: input.turnId, text: block.text } }));
122
+ const { text: _text, ...rest } = block;
123
+ return rest;
124
+ }
125
+
126
+ if (block.type === "thinking" && block.thinking.trim()) {
127
+ observed.push(event(context, { type: "model.reasoning.completed", payload: { turnId: input.turnId, text: block.thinking } }));
128
+ const { thinking: _thinking, ...rest } = block;
129
+ return rest;
130
+ }
131
+
132
+ return { ...block };
133
+ });
134
+
135
+ return [
136
+ ...observed,
137
+ event(context, { type: "model.step.completed", payload: { turnId: input.turnId, ...input.step, content } }),
138
+ ];
139
+ };
140
+
141
+ export const presentToolResult =
142
+ (
143
+ actorId: string,
144
+ input: { turnId: string; toolCallId: string; toolName: string; isError: boolean; content?: ModelToolResultPart[] },
145
+ ): Decision =>
146
+ (state, context) => {
147
+ runningTurn(state, context, actorId, input.turnId);
148
+
149
+ return [event(context, { type: "model.tool-result.presented", payload: input })];
150
+ };
151
+
152
+ export const compactContext =
153
+ (actorId: string, input: EventPayloads["context.compacted"]): Decision =>
154
+ (state, context) => {
155
+ runningTurn(state, context, actorId, input.turnId);
156
+
157
+ return [event(context, { type: "context.compacted", payload: input })];
158
+ };
159
+
101
160
  export const startToolCall =
102
161
  (actorId: string, input: { turnId: string; toolCallId: string; name: string; input: JsonValue; ignoredFields?: string[] }): Decision =>
103
162
  (state, context) => {
@@ -8,9 +8,9 @@ import { isRunId } from "../domain/portable-id.ts";
8
8
  import { syncDirectory } from "./durable-fs.ts";
9
9
  import type { CommandRecord, JournalCommand } from "./journal.ts";
10
10
 
11
- /** Rises whenever an older stand would reject newly written lines; the encoding is unchanged since 4, so 4 stays readable. */
12
- export const journalStorageVersion = 6;
13
- const readableJournalStorageVersions: readonly unknown[] = [4, 5, journalStorageVersion];
11
+ /** Rises whenever an older stand would reject newly written lines; the journal reads only this version. */
12
+ export const journalStorageVersion = 7;
13
+ const readableJournalStorageVersions: readonly unknown[] = [journalStorageVersion];
14
14
  export const journalPayloadThresholdBytes = 4096;
15
15
 
16
16
  type PayloadReference = { sha256: string; bytes: number };
@@ -161,7 +161,10 @@ export const parseJournalRecord = (line: string, runDirectory: string, location:
161
161
  const record = objectOf(parsed, location);
162
162
 
163
163
  if (!readableJournalStorageVersions.includes(record.formatVersion))
164
- throw new Error(`${location} has unsupported journal format version ${String(record.formatVersion)}.`);
164
+ throw new Error(`${location} has unsupported journal format version ${String(record.formatVersion)}.${
165
+ typeof record.formatVersion === "number" && record.formatVersion < journalStorageVersion
166
+ ? " Journals before format 7 hold no model context and are not migrated."
167
+ : ""}`);
165
168
 
166
169
  exactKeys(record, location, ["formatVersion", "runId", "command", "occurredAt", "events"]);
167
170
 
@@ -346,6 +346,12 @@ export class Journal {
346
346
  return [...this.#loadFailures.values()];
347
347
  }
348
348
 
349
+ /** Every locked run with its cause: journals rejected at load and runs isolated by a write failure. */
350
+ unavailableRuns(): readonly JournalLoadFailure[] {
351
+ return [...new Set([...this.#loadFailures.keys(), ...this.#writeFailures.keys()])]
352
+ .flatMap((runId) => this.failureOf(runId) ?? []);
353
+ }
354
+
349
355
  failureOf(runId: string): JournalLoadFailure | null {
350
356
  const failure = this.#loadFailures.get(runId);
351
357
  const writeFailure = this.#writeFailures.get(runId);
@@ -369,6 +375,34 @@ export class Journal {
369
375
  return [...(this.#runs.get(runId)?.events ?? [])];
370
376
  }
371
377
 
378
+ /** The events after the given sequence; the sequence counts from 1 without gaps. */
379
+ eventsSince(runId: RunId, sequence: number): JournalEvent[] {
380
+ this.assertRunAvailable(runId);
381
+ return (this.#runs.get(runId)?.events ?? []).slice(sequence);
382
+ }
383
+
384
+ /** The events from the newest back, without copying the journal; a caller stops as soon as it has what it needs. */
385
+ *recentEvents(runId: RunId): Generator<JournalEvent> {
386
+ this.assertRunAvailable(runId);
387
+ const events = this.#runs.get(runId)?.events ?? [];
388
+
389
+ for (let index = events.length - 1; index >= 0; index--)
390
+ yield events[index]!;
391
+ }
392
+
393
+ /** A value read from the current state without cloning all of it; `read` must neither keep nor change the state. */
394
+ select<Value>(runId: RunId, read: (state: RunState) => Value): Value | null {
395
+ this.assertRunAvailable(runId);
396
+ const state = this.#runs.get(runId)?.state;
397
+
398
+ return state ? clone(read(state)) : null;
399
+ }
400
+
401
+ firstEventId(runId: RunId): string | undefined {
402
+ this.assertRunAvailable(runId);
403
+ return this.#runs.get(runId)?.events[0]?.eventId;
404
+ }
405
+
372
406
  records(runId: RunId): readonly CommandRecord[] {
373
407
  this.assertRunAvailable(runId);
374
408
  return [...(this.#runs.get(runId)?.records ?? [])];
@@ -571,6 +605,7 @@ export class Journal {
571
605
  const semantics = log ? {
572
606
  events: new Map(log.semantics.events),
573
607
  sourcedToolCalls: new Set(log.semantics.sourcedToolCalls),
608
+ presented: new Set(log.semantics.presented),
574
609
  } : eventSemanticContext();
575
610
  let state: RunDraft | null = log?.state ? forkProjection(log.state) : null;
576
611