@electric-ax/agents 0.4.16 → 0.4.18
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/dist/entrypoint.js +306 -35
- package/dist/index.cjs +305 -33
- package/dist/index.d.cts +75 -31
- package/dist/index.d.ts +75 -31
- package/dist/index.js +307 -36
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,36 @@ import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core"
|
|
|
6
6
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
7
7
|
import { ChangeEvent } from "@durable-streams/state";
|
|
8
8
|
|
|
9
|
+
//#region src/model-catalog.d.ts
|
|
10
|
+
type BuiltinModelProvider = AvailableProvider;
|
|
11
|
+
type BuiltinModelInput = `text` | `image`;
|
|
12
|
+
interface BuiltinModelChoice {
|
|
13
|
+
provider: BuiltinModelProvider;
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
value: string;
|
|
17
|
+
reasoning: boolean;
|
|
18
|
+
input: Array<BuiltinModelInput>;
|
|
19
|
+
}
|
|
20
|
+
interface BuiltinModelCatalog {
|
|
21
|
+
choices: Array<BuiltinModelChoice>;
|
|
22
|
+
defaultChoice: BuiltinModelChoice;
|
|
23
|
+
}
|
|
24
|
+
interface BuiltinModelCatalogOptions {
|
|
25
|
+
allowMockFallback?: boolean;
|
|
26
|
+
enabledModelValues?: ReadonlyArray<string> | null;
|
|
27
|
+
}
|
|
28
|
+
declare const REASONING_EFFORT_VALUES: readonly ["auto", "minimal", "low", "medium", "high"];
|
|
29
|
+
type BuiltinReasoningEffort = (typeof REASONING_EFFORT_VALUES)[number];
|
|
30
|
+
type ExplicitReasoningEffort = Exclude<BuiltinReasoningEffort, `auto`>;
|
|
31
|
+
type BuiltinAgentModelConfig = Pick<AgentConfig, `model` | `provider` | `onPayload` | `getApiKey`> & {
|
|
32
|
+
reasoningEffort?: ExplicitReasoningEffort;
|
|
33
|
+
};
|
|
34
|
+
declare function builtinModelProviderLabel(provider: BuiltinModelProvider): string;
|
|
35
|
+
declare function listBuiltinModelChoices(providers: ReadonlyArray<BuiltinModelProvider>): Array<BuiltinModelChoice>;
|
|
36
|
+
declare function resolveBuiltinModelConfig(catalog: BuiltinModelCatalog, args: Readonly<Record<string, unknown>>): BuiltinAgentModelConfig;
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
9
39
|
//#region src/bootstrap.d.ts
|
|
10
40
|
declare const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = "/_electric/builtin-agent-handler";
|
|
11
41
|
interface AgentHandlerResult {
|
|
@@ -21,8 +51,38 @@ interface AgentHandlerResult {
|
|
|
21
51
|
* die with the process, which would leave containers running.
|
|
22
52
|
*/
|
|
23
53
|
shutdownSandboxes: (() => Promise<void>) | null;
|
|
54
|
+
/**
|
|
55
|
+
* Model catalog the built-in agents resolve `model` args against — lets
|
|
56
|
+
* embedders register sibling agent types with the same model resolution.
|
|
57
|
+
*/
|
|
58
|
+
modelCatalog: BuiltinModelCatalog;
|
|
24
59
|
}
|
|
25
60
|
type BuiltinElectricToolsFactory = NonNullable<ProcessWakeConfig[`createElectricTools`]>;
|
|
61
|
+
/** Mount spec mirroring `DockerSandboxOpts['extraMounts']` items. */
|
|
62
|
+
interface BuiltinDockerSandboxMount {
|
|
63
|
+
hostPath: string;
|
|
64
|
+
containerPath: string;
|
|
65
|
+
readOnly?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Embedder customization for the built-in `docker` sandbox profile.
|
|
69
|
+
* Threads straight into `dockerSandbox()` (which already supports these);
|
|
70
|
+
* custom `extraMounts` are appended after the working-directory mount.
|
|
71
|
+
* These are embedder/operator-trust inputs: `extraMounts` is subject to the
|
|
72
|
+
* runtime's docker-socket guard, and `env` is passed verbatim into the
|
|
73
|
+
* container.
|
|
74
|
+
*
|
|
75
|
+
* Note: custom `extraMounts` must not target the working-directory container
|
|
76
|
+
* path (`/work`) — it collides with the cwd mount and fails at container-create
|
|
77
|
+
* time with an opaque docker error.
|
|
78
|
+
*/
|
|
79
|
+
interface BuiltinDockerSandboxOptions {
|
|
80
|
+
/** Digest-pinned image unless `allowFloatingTag` is set. */
|
|
81
|
+
image?: string;
|
|
82
|
+
allowFloatingTag?: boolean;
|
|
83
|
+
env?: Record<string, string>;
|
|
84
|
+
extraMounts?: Array<BuiltinDockerSandboxMount>;
|
|
85
|
+
}
|
|
26
86
|
interface BuiltinAgentHandlerOptions {
|
|
27
87
|
agentServerUrl: string;
|
|
28
88
|
serveEndpoint?: string;
|
|
@@ -36,6 +96,8 @@ interface BuiltinAgentHandlerOptions {
|
|
|
36
96
|
serverHeaders?: HeadersProvider;
|
|
37
97
|
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
38
98
|
createElectricTools?: BuiltinElectricToolsFactory;
|
|
99
|
+
/** Customize the built-in `docker` sandbox profile (image, env, mounts). */
|
|
100
|
+
dockerSandbox?: BuiltinDockerSandboxOptions;
|
|
39
101
|
}
|
|
40
102
|
declare function createBuiltinElectricTools(custom?: BuiltinElectricToolsFactory): BuiltinElectricToolsFactory;
|
|
41
103
|
declare function createBuiltinAgentHandler(options: BuiltinAgentHandlerOptions): Promise<AgentHandlerResult | null>;
|
|
@@ -45,6 +107,12 @@ declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
|
45
107
|
|
|
46
108
|
//#endregion
|
|
47
109
|
//#region src/durable-streams-cache.d.ts
|
|
110
|
+
/**
|
|
111
|
+
* Merge the profile's working-directory mount with embedder docker options
|
|
112
|
+
* into the option fragment spread into `dockerSandbox()`. An internal helper:
|
|
113
|
+
* exported from this module so the unit test can import it, but intentionally
|
|
114
|
+
* not re-exported from `index.ts` (not part of the package's public API).
|
|
115
|
+
*/
|
|
48
116
|
type DurableStreamsFetchCacheOptions = false | {
|
|
49
117
|
store?: `memory` | `sqlite`;
|
|
50
118
|
sqliteLocation?: string;
|
|
@@ -160,40 +228,15 @@ declare function runBuiltinAgentsEntrypoint({
|
|
|
160
228
|
url: string;
|
|
161
229
|
}>;
|
|
162
230
|
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region src/model-catalog.d.ts
|
|
165
|
-
type BuiltinModelProvider = AvailableProvider;
|
|
166
|
-
type BuiltinModelInput = `text` | `image`;
|
|
167
|
-
interface BuiltinModelChoice {
|
|
168
|
-
provider: BuiltinModelProvider;
|
|
169
|
-
id: string;
|
|
170
|
-
label: string;
|
|
171
|
-
value: string;
|
|
172
|
-
reasoning: boolean;
|
|
173
|
-
input: Array<BuiltinModelInput>;
|
|
174
|
-
}
|
|
175
|
-
interface BuiltinModelCatalog {
|
|
176
|
-
choices: Array<BuiltinModelChoice>;
|
|
177
|
-
defaultChoice: BuiltinModelChoice;
|
|
178
|
-
}
|
|
179
|
-
interface BuiltinModelCatalogOptions {
|
|
180
|
-
allowMockFallback?: boolean;
|
|
181
|
-
enabledModelValues?: ReadonlyArray<string> | null;
|
|
182
|
-
}
|
|
183
|
-
declare const REASONING_EFFORT_VALUES: readonly ["auto", "minimal", "low", "medium", "high"];
|
|
184
|
-
type BuiltinReasoningEffort = (typeof REASONING_EFFORT_VALUES)[number];
|
|
185
|
-
type ExplicitReasoningEffort = Exclude<BuiltinReasoningEffort, `auto`>;
|
|
186
|
-
type BuiltinAgentModelConfig = Pick<AgentConfig, `model` | `provider` | `onPayload` | `getApiKey`> & {
|
|
187
|
-
reasoningEffort?: ExplicitReasoningEffort;
|
|
188
|
-
};
|
|
189
|
-
declare function builtinModelProviderLabel(provider: BuiltinModelProvider): string;
|
|
190
|
-
declare function listBuiltinModelChoices(providers: ReadonlyArray<BuiltinModelProvider>): Array<BuiltinModelChoice>;
|
|
191
|
-
declare function resolveBuiltinModelConfig(catalog: BuiltinModelCatalog, args: Readonly<Record<string, unknown>>): BuiltinAgentModelConfig;
|
|
192
|
-
|
|
193
231
|
//#endregion
|
|
194
232
|
//#region src/agents/horton.d.ts
|
|
195
233
|
declare const HORTON_MODEL = "claude-sonnet-4-6";
|
|
196
234
|
declare function generateTitle(userMessage: string, llmCall: (prompt: string) => Promise<string>, onFallback?: (reason: string) => void): Promise<string>;
|
|
235
|
+
interface ActiveGoalPromptInfo {
|
|
236
|
+
objective: string;
|
|
237
|
+
tokenBudget: number | null;
|
|
238
|
+
tokensUsed: number;
|
|
239
|
+
}
|
|
197
240
|
declare function buildHortonSystemPrompt(workingDirectory: string, opts?: {
|
|
198
241
|
hasDocsSupport?: boolean;
|
|
199
242
|
hasEventSourceTools?: boolean;
|
|
@@ -202,6 +245,7 @@ declare function buildHortonSystemPrompt(workingDirectory: string, opts?: {
|
|
|
202
245
|
docsUrl?: string;
|
|
203
246
|
modelProvider?: string;
|
|
204
247
|
modelId?: string;
|
|
248
|
+
activeGoal?: ActiveGoalPromptInfo;
|
|
205
249
|
}): string;
|
|
206
250
|
declare function createHortonTools(sandbox: Sandbox, ctx: HandlerContext, readSet: Set<string>, opts?: {
|
|
207
251
|
docsSearchTool?: AgentTool$1;
|
|
@@ -254,4 +298,4 @@ declare function createHortonDocsSupport(workingDirectory: string, opts?: {
|
|
|
254
298
|
}): HortonDocsSupport | null;
|
|
255
299
|
|
|
256
300
|
//#endregion
|
|
257
|
-
export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinElectricToolsFactory, BuiltinModelCatalogOptions, BuiltinModelChoice, BuiltinModelProvider, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, builtinModelProviderLabel, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createForkTool, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, listBuiltinModelChoices, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
|
301
|
+
export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentModelConfig, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinDockerSandboxMount, BuiltinDockerSandboxOptions, BuiltinElectricToolsFactory, BuiltinModelCatalog, BuiltinModelCatalogOptions, BuiltinModelChoice, BuiltinModelProvider, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, builtinModelProviderLabel, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createForkTool, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, listBuiltinModelChoices, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, resolveBuiltinModelConfig, runBuiltinAgentsEntrypoint };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mergeElectricPrincipalHeader } from "./server-headers-KD5yHFYT.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { MOONSHOT_API_BASE_URL, MOONSHOT_PROVIDER, appendPathToUrl, buildSkillSlashCommands, completeWithLowCostModel, createContextSkillLoader, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillsRegistry, db, detectAvailableProviders, getMoonshotApiKey, getMoonshotModel, getMoonshotModels, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
|
|
5
|
-
import { braveSearchTool, braveSearchTool as braveSearchTool$1, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createScheduleTools, createSendTool, createWriteTool } from "@electric-ax/agents-runtime/tools";
|
|
4
|
+
import { GOAL_SLASH_COMMAND, MOONSHOT_API_BASE_URL, MOONSHOT_PROVIDER, appendPathToUrl, buildSkillSlashCommands, commentsCollection, completeWithLowCostModel, createContextSkillLoader, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillsRegistry, db, detectAvailableProviders, dispatchGoalCommand, formatTokenCount, getMoonshotApiKey, getMoonshotModel, getMoonshotModels, isGoalCommandText, parseGoalCommand, pgSync, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
|
|
5
|
+
import { braveSearchTool, braveSearchTool as braveSearchTool$1, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createMarkGoalCompleteTool, createReadFileTool, createScheduleTools, createSendTool, createWriteTool } from "@electric-ax/agents-runtime/tools";
|
|
6
6
|
import { chooseDefaultSandbox, isE2BAvailable, lazySandbox, remoteSandbox } from "@electric-ax/agents-runtime/sandbox";
|
|
7
7
|
import fsSync from "node:fs";
|
|
8
8
|
import pino from "pino";
|
|
@@ -794,6 +794,69 @@ function createSpawnWorkerTool(ctx, modelConfig) {
|
|
|
794
794
|
};
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
+
//#endregion
|
|
798
|
+
//#region src/tools/observe-pg-sync.ts
|
|
799
|
+
function asToolResult(value) {
|
|
800
|
+
return {
|
|
801
|
+
content: [{
|
|
802
|
+
type: `text`,
|
|
803
|
+
text: typeof value === `string` ? value : JSON.stringify(value, null, 2)
|
|
804
|
+
}],
|
|
805
|
+
details: {}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const PgSyncOperation = Type.Union([
|
|
809
|
+
Type.Literal(`insert`),
|
|
810
|
+
Type.Literal(`update`),
|
|
811
|
+
Type.Literal(`delete`)
|
|
812
|
+
]);
|
|
813
|
+
function createObservePgSyncTool(ctx) {
|
|
814
|
+
return {
|
|
815
|
+
name: `observe_pg_sync`,
|
|
816
|
+
label: `Observe Postgres Sync`,
|
|
817
|
+
description: `Observe an Electric Postgres shape stream and wake this agent when matching row changes arrive.`,
|
|
818
|
+
parameters: Type.Object({
|
|
819
|
+
url: Type.Optional(Type.String({ description: `Optional Electric shape endpoint URL. Defaults to the server-configured pg-sync URL.` })),
|
|
820
|
+
table: Type.String({
|
|
821
|
+
minLength: 1,
|
|
822
|
+
pattern: `\\S`,
|
|
823
|
+
description: `Postgres table name to observe.`
|
|
824
|
+
}),
|
|
825
|
+
columns: Type.Optional(Type.Array(Type.String(), { description: `Optional list of columns to include in the shape.` })),
|
|
826
|
+
where: Type.Optional(Type.String({ description: `Optional Electric shape WHERE clause.` })),
|
|
827
|
+
params: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Record(Type.String(), Type.String())])),
|
|
828
|
+
replica: Type.Optional(Type.Union([Type.Literal(`default`), Type.Literal(`full`)])),
|
|
829
|
+
wake: Type.Optional(Type.Object({
|
|
830
|
+
ops: Type.Optional(Type.Array(PgSyncOperation)),
|
|
831
|
+
debounceMs: Type.Optional(Type.Number())
|
|
832
|
+
}, { additionalProperties: false }))
|
|
833
|
+
}),
|
|
834
|
+
execute: async (_toolCallId, params) => {
|
|
835
|
+
const args = params;
|
|
836
|
+
if (typeof args.table !== `string` || args.table.trim().length === 0) throw new Error(`table is required`);
|
|
837
|
+
const source = pgSync({
|
|
838
|
+
url: args.url,
|
|
839
|
+
table: args.table,
|
|
840
|
+
columns: args.columns,
|
|
841
|
+
where: args.where,
|
|
842
|
+
params: args.params,
|
|
843
|
+
replica: args.replica
|
|
844
|
+
});
|
|
845
|
+
const wake = {
|
|
846
|
+
on: `change`,
|
|
847
|
+
...args.wake?.ops ? { ops: args.wake.ops } : {},
|
|
848
|
+
...args.wake?.debounceMs !== void 0 ? { debounceMs: args.wake.debounceMs } : {}
|
|
849
|
+
};
|
|
850
|
+
await ctx.observe(source, { wake });
|
|
851
|
+
return asToolResult({
|
|
852
|
+
sourceRef: source.sourceRef,
|
|
853
|
+
streamUrl: source.streamUrl,
|
|
854
|
+
wake
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
|
|
797
860
|
//#endregion
|
|
798
861
|
//#region src/tools/fork.ts
|
|
799
862
|
function createForkTool(ctx) {
|
|
@@ -848,6 +911,49 @@ Omit 'entityUrl' to fork your own session. Pass a different session's URL to for
|
|
|
848
911
|
};
|
|
849
912
|
}
|
|
850
913
|
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/tools/set-title.ts
|
|
916
|
+
function createSetTitleTool(ctx) {
|
|
917
|
+
return {
|
|
918
|
+
name: `set_title`,
|
|
919
|
+
label: `Set Title`,
|
|
920
|
+
description: `Set the chat session title shown in the UI. Use this when the current title is missing, stale, misleading, or the user asks to rename the session. Provide a concise, human-readable title.`,
|
|
921
|
+
parameters: Type.Object({ title: Type.String({ description: `New session title. Whitespace is trimmed and the title must not be empty.` }) }),
|
|
922
|
+
execute: async (_toolCallId, params) => {
|
|
923
|
+
const { title } = params;
|
|
924
|
+
const trimmedTitle = typeof title === `string` ? title.trim() : ``;
|
|
925
|
+
if (trimmedTitle.length === 0) return {
|
|
926
|
+
content: [{
|
|
927
|
+
type: `text`,
|
|
928
|
+
text: `Error: title must be a non-empty string.`
|
|
929
|
+
}],
|
|
930
|
+
details: { updated: false }
|
|
931
|
+
};
|
|
932
|
+
try {
|
|
933
|
+
await ctx.setTag(`title`, trimmedTitle);
|
|
934
|
+
return {
|
|
935
|
+
content: [{
|
|
936
|
+
type: `text`,
|
|
937
|
+
text: `Session title set to “${trimmedTitle}”.`
|
|
938
|
+
}],
|
|
939
|
+
details: {
|
|
940
|
+
updated: true,
|
|
941
|
+
title: trimmedTitle
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
} catch (err) {
|
|
945
|
+
return {
|
|
946
|
+
content: [{
|
|
947
|
+
type: `text`,
|
|
948
|
+
text: `Error setting session title: ${err instanceof Error ? err.message : `Unknown error`}`
|
|
949
|
+
}],
|
|
950
|
+
details: { updated: false }
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
851
957
|
//#endregion
|
|
852
958
|
//#region src/model-catalog.ts
|
|
853
959
|
const MODEL_INPUTS_SCHEMA_DEF = `electricModelInputs`;
|
|
@@ -963,25 +1069,66 @@ function filterChoicesByEnabledModels(choices, values) {
|
|
|
963
1069
|
const filtered = choices.filter((choice) => enabled.has(choice.value));
|
|
964
1070
|
return filtered.length > 0 ? filtered : choices;
|
|
965
1071
|
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Anthropic-specific budget mapping for `reasoningEffort`.
|
|
1074
|
+
*
|
|
1075
|
+
* Anthropic's `thinking.budget_tokens` is a hard cap on tokens spent
|
|
1076
|
+
* inside the thinking block before the model must commit to its
|
|
1077
|
+
* answer. Docs require ≥ 1024; we scale from there. Numbers tuned so
|
|
1078
|
+
* `medium` is the spot most "show your work" requests land, and
|
|
1079
|
+
* `high` covers tougher reasoning without uncapped spend.
|
|
1080
|
+
*
|
|
1081
|
+
* Keep in sync with provider doc updates — Anthropic has shifted the
|
|
1082
|
+
* minimum once already (older models capped lower).
|
|
1083
|
+
*/
|
|
1084
|
+
const ANTHROPIC_THINKING_BUDGET_BY_EFFORT = {
|
|
1085
|
+
minimal: 1024,
|
|
1086
|
+
low: 2048,
|
|
1087
|
+
medium: 8192,
|
|
1088
|
+
high: 24576
|
|
1089
|
+
};
|
|
966
1090
|
function withProviderPayloadDefaults(config, choice, reasoningEffort) {
|
|
967
|
-
if (
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1091
|
+
if (!choice.reasoning) return config;
|
|
1092
|
+
if (choice.provider === `openai` || choice.provider === `openai-codex`) {
|
|
1093
|
+
const defaultEffort = choice.provider === `openai-codex` ? `low` : `minimal`;
|
|
1094
|
+
const effort = reasoningEffort === `minimal` && choice.provider === `openai-codex` ? `low` : reasoningEffort ?? defaultEffort;
|
|
1095
|
+
return {
|
|
1096
|
+
...config,
|
|
1097
|
+
onPayload: (payload) => {
|
|
1098
|
+
if (typeof payload !== `object` || payload === null) return void 0;
|
|
1099
|
+
const body = payload;
|
|
1100
|
+
const existingReasoning = typeof body.reasoning === `object` && body.reasoning !== null ? body.reasoning : {};
|
|
1101
|
+
return {
|
|
1102
|
+
...body,
|
|
1103
|
+
reasoning: {
|
|
1104
|
+
...existingReasoning,
|
|
1105
|
+
effort
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
if (choice.provider === `anthropic`) {
|
|
1112
|
+
const effectiveEffort = reasoningEffort ?? `minimal`;
|
|
1113
|
+
const budgetTokens = ANTHROPIC_THINKING_BUDGET_BY_EFFORT[effectiveEffort];
|
|
1114
|
+
return {
|
|
1115
|
+
...config,
|
|
1116
|
+
onPayload: (payload) => {
|
|
1117
|
+
if (typeof payload !== `object` || payload === null) return void 0;
|
|
1118
|
+
const body = payload;
|
|
1119
|
+
const existingThinking = typeof body.thinking === `object` && body.thinking !== null ? body.thinking : {};
|
|
1120
|
+
return {
|
|
1121
|
+
...body,
|
|
1122
|
+
thinking: {
|
|
1123
|
+
...existingThinking,
|
|
1124
|
+
type: `enabled`,
|
|
1125
|
+
budget_tokens: budgetTokens
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
return config;
|
|
985
1132
|
}
|
|
986
1133
|
function parseReasoningEffort(value) {
|
|
987
1134
|
return value === `minimal` || value === `low` || value === `medium` || value === `high` ? value : null;
|
|
@@ -1030,7 +1177,7 @@ function modelInputSchemaDefs(catalog) {
|
|
|
1030
1177
|
//#endregion
|
|
1031
1178
|
//#region src/agents/horton.ts
|
|
1032
1179
|
const HORTON_MODEL = `claude-sonnet-4-6`;
|
|
1033
|
-
const TITLE_SYSTEM_PROMPT = "You generate concise chat session
|
|
1180
|
+
const TITLE_SYSTEM_PROMPT = "You generate a concise 3-5 word chat session title from the user's first message. Respond with only the title — no quotes, punctuation, preamble, or explanation. The user may reference images, files, or attachments you cannot see; infer a title from their intent anyway. Never apologize or say anything is missing — always output a short title.";
|
|
1034
1181
|
const TITLE_USER_PROMPT = (userMessage) => `User request:\n${userMessage}`;
|
|
1035
1182
|
const TITLE_GENERATION_TIMEOUT_MS = 8e3;
|
|
1036
1183
|
const HORTON_SKILLS_SLASH_COMMAND_OWNER = `horton:skills`;
|
|
@@ -1124,12 +1271,16 @@ function withTimeout(promise, ms, description) {
|
|
|
1124
1271
|
if (timeout) clearTimeout(timeout);
|
|
1125
1272
|
});
|
|
1126
1273
|
}
|
|
1274
|
+
function looksLikeNonTitle(title) {
|
|
1275
|
+
if (title.split(/\s+/).filter(Boolean).length > 8) return true;
|
|
1276
|
+
return /[!?,]/.test(title);
|
|
1277
|
+
}
|
|
1127
1278
|
async function generateTitle(userMessage, llmCall, onFallback) {
|
|
1128
1279
|
try {
|
|
1129
1280
|
const raw = await llmCall(TITLE_USER_PROMPT(userMessage));
|
|
1130
1281
|
const title = raw.trim();
|
|
1131
|
-
if (title.length > 0) return title;
|
|
1132
|
-
onFallback?.(`empty LLM title response`);
|
|
1282
|
+
if (title.length > 0 && !looksLikeNonTitle(title)) return title;
|
|
1283
|
+
onFallback?.(title.length === 0 ? `empty LLM title response` : `non-title LLM response`);
|
|
1133
1284
|
return buildFallbackTitle(userMessage);
|
|
1134
1285
|
} catch (err) {
|
|
1135
1286
|
onFallback?.(err instanceof Error ? err.message : String(err));
|
|
@@ -1139,6 +1290,7 @@ async function generateTitle(userMessage, llmCall, onFallback) {
|
|
|
1139
1290
|
function buildHortonSystemPrompt(workingDirectory, opts = {}) {
|
|
1140
1291
|
const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
|
|
1141
1292
|
const eventSourceTools = opts.hasEventSourceTools ? `\n- list_event_sources: list external webhook/event feeds you can subscribe to, including available buckets and parameters\n- subscribe_event_source: subscribe yourself to one of those feeds or buckets so matching future events wake you\n- list_event_source_subscriptions: list your active event source subscriptions\n- unsubscribe_event_source: remove one of your event source subscriptions by id` : ``;
|
|
1293
|
+
const titleTool = `\n- set_title: set or rename this chat session's UI title`;
|
|
1142
1294
|
const scheduleTools = opts.hasScheduleTools ? `\n- upsert_cron_schedule: create or update a recurring cron wake for yourself. Always include payload with the concrete instruction/message you should receive when the cron fires.\n- delete_schedule: delete one of your cron or future-send schedules by stable id\n- list_schedules: list your manifest-backed cron and future-send schedules` : ``;
|
|
1143
1295
|
const skillsTools = opts.hasSkills ? `\n- use_skill: load a skill (knowledge, instructions, or a tutorial) into your context to help with the user's request\n- remove_skill: unload a skill from context when you're done with it` : ``;
|
|
1144
1296
|
const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents or this framework, ALWAYS use search_electric_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
|
|
@@ -1194,8 +1346,9 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
|
|
|
1194
1346
|
- fetch_url: fetch and convert a URL to markdown
|
|
1195
1347
|
- spawn_worker: dispatch a subagent for an isolated task
|
|
1196
1348
|
- fork: spawn a child session that inherits this conversation's history up to the latest completed response. Same parent-ownership model as spawn_worker — when the fork's next run finishes, you'll wake with its response.
|
|
1349
|
+
- observe_pg_sync: observe an Electric Postgres sync stream and wake on matching changes
|
|
1197
1350
|
- send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
|
|
1198
|
-
${eventSourceTools}${scheduleTools}${docsTools}${skillsTools}
|
|
1351
|
+
${eventSourceTools}${titleTool}${scheduleTools}${docsTools}${skillsTools}
|
|
1199
1352
|
|
|
1200
1353
|
# Working with files
|
|
1201
1354
|
- Prefer edit over write when modifying existing files.
|
|
@@ -1240,7 +1393,18 @@ Workflow when forking yourself for parallel exploration:
|
|
|
1240
1393
|
Report outcomes faithfully. If a command failed, say so with the relevant output. If you didn't run a verification step, say that rather than implying you did. Don't hedge confirmed results with unnecessary disclaimers.
|
|
1241
1394
|
|
|
1242
1395
|
Working directory: ${workingDirectory}
|
|
1243
|
-
The current year is ${new Date().getFullYear()}
|
|
1396
|
+
The current year is ${new Date().getFullYear()}.${buildGoalGuidance(opts.activeGoal)}`;
|
|
1397
|
+
}
|
|
1398
|
+
function buildGoalGuidance(goal) {
|
|
1399
|
+
if (!goal) return ``;
|
|
1400
|
+
const budgetLine = goal.tokenBudget === null ? `unlimited` : `${goal.tokensUsed} / ${goal.tokenBudget} tokens used`;
|
|
1401
|
+
return `
|
|
1402
|
+
|
|
1403
|
+
# Active goal
|
|
1404
|
+
- Objective: ${goal.objective}
|
|
1405
|
+
- Token budget: ${budgetLine}
|
|
1406
|
+
|
|
1407
|
+
The user set this goal with /goal set. Work autonomously toward it: do NOT ask the user clarifying questions or pause for confirmation — make reasonable assumptions and proceed. When you believe the goal is met, call the \`mark_goal_complete\` tool. If you hit a blocker that genuinely requires the user (e.g. credentials, a destructive action), call \`mark_goal_complete\` with a summary explaining what's needed. The runtime will abort this run automatically if you exceed the token budget.`;
|
|
1244
1408
|
}
|
|
1245
1409
|
function getToolName(tool) {
|
|
1246
1410
|
if (typeof tool !== `object` || tool === null) return null;
|
|
@@ -1262,7 +1426,10 @@ function createHortonTools(sandbox, ctx, readSet, opts = {}) {
|
|
|
1262
1426
|
})] : [createFetchUrlTool(sandbox)],
|
|
1263
1427
|
createSpawnWorkerTool(ctx, opts.modelConfig),
|
|
1264
1428
|
createForkTool(ctx),
|
|
1429
|
+
createObservePgSyncTool(ctx),
|
|
1430
|
+
createSetTitleTool(ctx),
|
|
1265
1431
|
createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }),
|
|
1432
|
+
...ctx.getGoal()?.status === `active` ? [createMarkGoalCompleteTool(ctx)] : [],
|
|
1266
1433
|
...opts.docsSearchTool ? [opts.docsSearchTool] : []
|
|
1267
1434
|
];
|
|
1268
1435
|
}
|
|
@@ -1331,11 +1498,58 @@ async function readAgentsMd(sandbox) {
|
|
|
1331
1498
|
return null;
|
|
1332
1499
|
}
|
|
1333
1500
|
}
|
|
1501
|
+
function extractWakeText(wake) {
|
|
1502
|
+
if (wake.type !== `inbox`) return null;
|
|
1503
|
+
const payload = wake.payload;
|
|
1504
|
+
if (typeof payload === `string`) return payload;
|
|
1505
|
+
if (payload && typeof payload === `object`) {
|
|
1506
|
+
const record = payload;
|
|
1507
|
+
if (typeof record.text === `string`) return record.text;
|
|
1508
|
+
if (typeof record.source === `string`) return record.source;
|
|
1509
|
+
}
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
async function tryHandleSlashCommand(ctx, wake) {
|
|
1513
|
+
const text = extractWakeText(wake);
|
|
1514
|
+
if (text === null) return false;
|
|
1515
|
+
if (isGoalCommandText(text)) {
|
|
1516
|
+
const command = parseGoalCommand(text);
|
|
1517
|
+
const result = dispatchGoalCommand(ctx, command);
|
|
1518
|
+
if (result.message) {
|
|
1519
|
+
serverLog.info(`[horton ${ctx.entityUrl}] ${result.message}`);
|
|
1520
|
+
writeSlashCommandReply(ctx, result.message);
|
|
1521
|
+
}
|
|
1522
|
+
if (command.kind === `set`) await kickoffGoalRun(ctx);
|
|
1523
|
+
return result.handled;
|
|
1524
|
+
}
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
const GOAL_KICKOFF_TEXT = `Start working toward the active goal now. Call \`mark_goal_complete\` when you believe it is done.`;
|
|
1528
|
+
async function kickoffGoalRun(ctx) {
|
|
1529
|
+
const goal = ctx.getGoal();
|
|
1530
|
+
if (!goal || goal.status !== `active`) return;
|
|
1531
|
+
try {
|
|
1532
|
+
await ctx.send(ctx.entityUrl, {
|
|
1533
|
+
kind: `goal_kickoff`,
|
|
1534
|
+
text: GOAL_KICKOFF_TEXT
|
|
1535
|
+
}, { type: `inbox` });
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
serverLog.warn(`[horton ${ctx.entityUrl}] failed to enqueue goal kickoff: ${err instanceof Error ? err.message : String(err)}`);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function writeSlashCommandReply(ctx, text) {
|
|
1541
|
+
try {
|
|
1542
|
+
ctx.replyText(text);
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
serverLog.warn(`[horton ${ctx.entityUrl}] failed to render slash command reply: ${err instanceof Error ? err.message : String(err)}`);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1334
1547
|
function createAssistantHandler(options) {
|
|
1335
1548
|
const { streamFn, docsSupport, docsSearchTool, skillsRegistry, modelCatalog, docsUrl } = options;
|
|
1336
1549
|
const skillLoader = createContextSkillLoader(skillsRegistry, { slashCommandOwner: HORTON_SKILLS_SLASH_COMMAND_OWNER });
|
|
1337
1550
|
const hasSkills = skillLoader.hasSkills;
|
|
1338
1551
|
return async function assistantHandler(ctx, wake) {
|
|
1552
|
+
if (await tryHandleSlashCommand(ctx, wake)) return;
|
|
1339
1553
|
const loadedSkills = await skillLoader.load(ctx);
|
|
1340
1554
|
const readSet = new Set();
|
|
1341
1555
|
const modelConfig = resolveBuiltinModelConfig(modelCatalog, ctx.args);
|
|
@@ -1428,6 +1642,26 @@ function createAssistantHandler(options) {
|
|
|
1428
1642
|
}
|
|
1429
1643
|
}
|
|
1430
1644
|
});
|
|
1645
|
+
const goal = ctx.getGoal();
|
|
1646
|
+
const enforcedGoal = goal && goal.status === `active` ? goal : void 0;
|
|
1647
|
+
const activeGoalPromptInfo = enforcedGoal ? {
|
|
1648
|
+
objective: enforcedGoal.objective,
|
|
1649
|
+
tokenBudget: enforcedGoal.tokenBudget,
|
|
1650
|
+
tokensUsed: enforcedGoal.tokensUsed
|
|
1651
|
+
} : void 0;
|
|
1652
|
+
const budgetAbort = new AbortController();
|
|
1653
|
+
let runTokensUsed = enforcedGoal?.tokensUsed ?? 0;
|
|
1654
|
+
let budgetTripped = false;
|
|
1655
|
+
const onStepEnd = enforcedGoal ? (stats) => {
|
|
1656
|
+
if (budgetTripped) return;
|
|
1657
|
+
runTokensUsed += stats.uncachedInput + stats.output;
|
|
1658
|
+
ctx.updateGoalUsage(runTokensUsed);
|
|
1659
|
+
if (enforcedGoal.tokenBudget !== null && runTokensUsed >= enforcedGoal.tokenBudget) {
|
|
1660
|
+
budgetTripped = true;
|
|
1661
|
+
serverLog.info(`[horton ${ctx.entityUrl}] goal budget exhausted (${runTokensUsed} tokens) — aborting run`);
|
|
1662
|
+
budgetAbort.abort();
|
|
1663
|
+
}
|
|
1664
|
+
} : void 0;
|
|
1431
1665
|
ctx.useAgent({
|
|
1432
1666
|
systemPrompt: buildHortonSystemPrompt(sandboxCwd, {
|
|
1433
1667
|
hasDocsSupport: Boolean(docsSupport),
|
|
@@ -1436,13 +1670,26 @@ function createAssistantHandler(options) {
|
|
|
1436
1670
|
modelProvider: modelConfig.provider,
|
|
1437
1671
|
modelId: String(modelConfig.model),
|
|
1438
1672
|
hasEventSourceTools,
|
|
1439
|
-
hasScheduleTools
|
|
1673
|
+
hasScheduleTools,
|
|
1674
|
+
...activeGoalPromptInfo && { activeGoal: activeGoalPromptInfo }
|
|
1440
1675
|
}),
|
|
1441
1676
|
...modelConfig,
|
|
1442
1677
|
tools,
|
|
1443
|
-
...streamFn && { streamFn }
|
|
1678
|
+
...streamFn && { streamFn },
|
|
1679
|
+
...onStepEnd && { onStepEnd }
|
|
1444
1680
|
});
|
|
1445
|
-
|
|
1681
|
+
try {
|
|
1682
|
+
await ctx.agent.run(void 0, budgetAbort.signal);
|
|
1683
|
+
} catch (err) {
|
|
1684
|
+
if (!budgetTripped) throw err;
|
|
1685
|
+
serverLog.info(`[horton ${ctx.entityUrl}] agent.run aborted by budget enforcement`);
|
|
1686
|
+
}
|
|
1687
|
+
if (enforcedGoal) ctx.updateGoalUsage(runTokensUsed, budgetTripped ? { status: `budget_limited` } : void 0);
|
|
1688
|
+
if (budgetTripped && enforcedGoal && enforcedGoal.tokenBudget !== null) {
|
|
1689
|
+
const budget = enforcedGoal.tokenBudget;
|
|
1690
|
+
const suggestedNext = Math.max(budget * 2, budget + 1e4);
|
|
1691
|
+
writeSlashCommandReply(ctx, `⚠️ Stopped — goal hit the token budget (${formatTokenCount(runTokensUsed)} / ${formatTokenCount(budget)} tokens used). Raise the budget with \`/goal set "..." --tokens ${formatTokenCount(suggestedNext)}\`, or call \`/goal complete\` to finalize.`);
|
|
1692
|
+
}
|
|
1446
1693
|
await titlePromise;
|
|
1447
1694
|
};
|
|
1448
1695
|
}
|
|
@@ -1482,7 +1729,8 @@ function registerHorton(registry, options) {
|
|
|
1482
1729
|
subject_value: `user`,
|
|
1483
1730
|
permission: `manage`
|
|
1484
1731
|
}],
|
|
1485
|
-
|
|
1732
|
+
state: { comments: commentsCollection },
|
|
1733
|
+
slashCommands: [GOAL_SLASH_COMMAND, ...buildSkillSlashCommands(skillsRegistry)],
|
|
1486
1734
|
handler: assistantHandler
|
|
1487
1735
|
});
|
|
1488
1736
|
return [`horton`];
|
|
@@ -1666,6 +1914,7 @@ function registerWorker(registry, options) {
|
|
|
1666
1914
|
subject_value: `user`,
|
|
1667
1915
|
permission: `manage`
|
|
1668
1916
|
}],
|
|
1917
|
+
state: { comments: commentsCollection },
|
|
1669
1918
|
async handler(ctx) {
|
|
1670
1919
|
const args = parseWorkerArgs(ctx.args);
|
|
1671
1920
|
const readSet = new Set();
|
|
@@ -1718,7 +1967,7 @@ function createBuiltinElectricTools(custom) {
|
|
|
1718
1967
|
};
|
|
1719
1968
|
}
|
|
1720
1969
|
async function createBuiltinAgentHandler(options) {
|
|
1721
|
-
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, enabledModelValues, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1970
|
+
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, enabledModelValues, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType, dockerSandbox: dockerSandboxOpts } = options;
|
|
1722
1971
|
const modelCatalog = await createBuiltinModelCatalog({
|
|
1723
1972
|
allowMockFallback: Boolean(streamFn),
|
|
1724
1973
|
enabledModelValues
|
|
@@ -1754,7 +2003,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1754
2003
|
modelCatalog
|
|
1755
2004
|
});
|
|
1756
2005
|
typeNames.push(`worker`);
|
|
1757
|
-
const { profiles: sandboxProfiles, shutdownSandboxes } = await buildBuiltinSandboxProfiles(cwd);
|
|
2006
|
+
const { profiles: sandboxProfiles, shutdownSandboxes } = await buildBuiltinSandboxProfiles(cwd, dockerSandboxOpts);
|
|
1758
2007
|
const runtime = createRuntimeHandler({
|
|
1759
2008
|
baseUrl: agentServerUrl,
|
|
1760
2009
|
serveEndpoint,
|
|
@@ -1774,7 +2023,8 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1774
2023
|
registry,
|
|
1775
2024
|
typeNames,
|
|
1776
2025
|
skillsRegistry,
|
|
1777
|
-
shutdownSandboxes
|
|
2026
|
+
shutdownSandboxes,
|
|
2027
|
+
modelCatalog
|
|
1778
2028
|
};
|
|
1779
2029
|
}
|
|
1780
2030
|
async function createAgentHandler(agentServerUrl, workingDirectory, streamFn, createElectricTools, serveEndpoint) {
|
|
@@ -1803,6 +2053,21 @@ function sweepOrphanedDockerSandboxesOnce(sweep) {
|
|
|
1803
2053
|
return dockerBootSweep;
|
|
1804
2054
|
}
|
|
1805
2055
|
/**
|
|
2056
|
+
* Merge the profile's working-directory mount with embedder docker options
|
|
2057
|
+
* into the option fragment spread into `dockerSandbox()`. An internal helper:
|
|
2058
|
+
* exported from this module so the unit test can import it, but intentionally
|
|
2059
|
+
* not re-exported from `index.ts` (not part of the package's public API).
|
|
2060
|
+
*/
|
|
2061
|
+
function resolveDockerSandboxOpts(cwdMount, custom) {
|
|
2062
|
+
const extraMounts = [...cwdMount ? [cwdMount] : [], ...custom?.extraMounts ?? []];
|
|
2063
|
+
return {
|
|
2064
|
+
...custom?.image !== void 0 && { image: custom.image },
|
|
2065
|
+
...custom?.allowFloatingTag !== void 0 && { allowFloatingTag: custom.allowFloatingTag },
|
|
2066
|
+
...custom?.env !== void 0 && { env: custom.env },
|
|
2067
|
+
...extraMounts.length > 0 && { extraMounts }
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
1806
2071
|
* Built-in sandbox profiles. `local` is always available. `docker` is
|
|
1807
2072
|
* gated on Docker being reachable so a user without Docker installed
|
|
1808
2073
|
* sees only what works — the UI never offers a non-functional choice.
|
|
@@ -1812,7 +2077,7 @@ function sweepOrphanedDockerSandboxesOnce(sweep) {
|
|
|
1812
2077
|
* server must run on shutdown (the providers' debounced idle teardowns die
|
|
1813
2078
|
* with the process).
|
|
1814
2079
|
*/
|
|
1815
|
-
async function buildBuiltinSandboxProfiles(workingDirectory) {
|
|
2080
|
+
async function buildBuiltinSandboxProfiles(workingDirectory, dockerOpts) {
|
|
1816
2081
|
const profiles = [{
|
|
1817
2082
|
name: `local`,
|
|
1818
2083
|
label: `Local`,
|
|
@@ -1837,11 +2102,11 @@ async function buildBuiltinSandboxProfiles(workingDirectory) {
|
|
|
1837
2102
|
workingDirectory: `/work`,
|
|
1838
2103
|
factory: () => dockerSandbox({
|
|
1839
2104
|
initialNetworkPolicy: { mode: `allow-all` },
|
|
1840
|
-
|
|
2105
|
+
...resolveDockerSandboxOpts(cwd ? {
|
|
1841
2106
|
hostPath: cwd,
|
|
1842
2107
|
containerPath: `/work`,
|
|
1843
2108
|
readOnly: false
|
|
1844
|
-
}
|
|
2109
|
+
} : void 0, dockerOpts),
|
|
1845
2110
|
sandboxKey,
|
|
1846
2111
|
persistent,
|
|
1847
2112
|
owner,
|
|
@@ -1888,13 +2153,19 @@ function resolveCwd(args, fallback) {
|
|
|
1888
2153
|
//#endregion
|
|
1889
2154
|
//#region src/durable-streams-cache.ts
|
|
1890
2155
|
const MEMORY_CACHE_SIZE_BYTES = 100 * 1024 * 1024;
|
|
2156
|
+
let installed = false;
|
|
1891
2157
|
function installDurableStreamsFetchCache(options = {}) {
|
|
1892
2158
|
if (options === false) return;
|
|
2159
|
+
if (installed) {
|
|
2160
|
+
console.warn(`[agents] installDurableStreamsFetchCache called more than once; ignoring`);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
1893
2163
|
const store = options.store === `sqlite` || options.sqliteLocation ? new cacheStores.SqliteCacheStore({
|
|
1894
2164
|
location: options.sqliteLocation,
|
|
1895
2165
|
maxCount: options.maxCount
|
|
1896
2166
|
}) : new cacheStores.MemoryCacheStore({ maxSize: MEMORY_CACHE_SIZE_BYTES });
|
|
1897
2167
|
setGlobalDispatcher(getGlobalDispatcher().compose(interceptors.cache({ store })));
|
|
2168
|
+
installed = true;
|
|
1898
2169
|
}
|
|
1899
2170
|
|
|
1900
2171
|
//#endregion
|
|
@@ -2221,4 +2492,4 @@ async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd
|
|
|
2221
2492
|
}
|
|
2222
2493
|
|
|
2223
2494
|
//#endregion
|
|
2224
|
-
export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, builtinModelProviderLabel, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createForkTool, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, listBuiltinModelChoices, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
|
2495
|
+
export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, builtinModelProviderLabel, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createForkTool, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, listBuiltinModelChoices, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, resolveBuiltinModelConfig, runBuiltinAgentsEntrypoint };
|