freecodego 0.1.6-alpha.2.4 → 0.1.7-alpha.2.1

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/bootstrap.js CHANGED
@@ -1,17 +1,18 @@
1
1
  // packages/freecodego/harness-plugin/lib/index.js
2
2
  import { createRequire as createRequire2 } from "node:module";
3
3
  import path, { basename as basename2, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join4, matchesGlob, normalize as normalize2, relative as relative2, resolve as resolve2, sep } from "node:path";
4
+ import { credentialRef as credentialRef2 } from "@deepseek-ai/dsh-credentials";
4
5
  import { constants, createCipheriv, createHash as createHash3, createHmac, createPublicKey, publicEncrypt, randomBytes, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
5
6
  import fs, { appendFile, copyFile as copyFile2, mkdir as mkdir4, mkdtemp, open as open2, readFile as readFile3, readdir as readdir2, readlink, realpath, rename as rename3, rm as rm2, stat as stat2, symlink, unlink, writeFile as writeFile3 } from "node:fs/promises";
6
7
  import { availableParallelism, homedir, platform as platform2, release, tmpdir } from "node:os";
7
8
  import { deflateRawSync, gunzipSync, inflateRawSync, inflateSync } from "node:zlib";
8
- import z2 from "@deepseek-ai/schemastery";
9
9
  import { appendFileSync, chmodSync, constants as constants$1, createWriteStream, existsSync as existsSync2, lstatSync, mkdirSync, readFileSync as readFileSync2, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
10
10
  import { writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
11
11
  import { expandHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
12
12
  import { execFile, spawn as spawn2 } from "node:child_process";
13
- import { BlockAssembler, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, ReasoningEffortId, ToolCallId, attributionHeaders, createAssistantMessage as createAssistantMessage2, createMessage, createToolResultMessage as createToolResultMessage2, createUserMessage } from "@deepseek-ai/dsh-llm";
13
+ import { BlockAssembler, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, ReasoningEffortId, ToolCallId, attributionHeaders, createAssistantMessage as createAssistantMessage2, createToolResultMessage as createToolResultMessage2, createUserMessage } from "@deepseek-ai/dsh-llm";
14
14
  import { promisify } from "node:util";
15
+ import z2 from "@deepseek-ai/schemastery";
15
16
  import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
16
17
 
17
18
  // packages/freecodego/native-runtime-host/lib/index.js
@@ -1296,8 +1297,7 @@ import { createServer } from "node:http";
1296
1297
  import "@deepseek-ai/dsh-token-meter/src/estimate.ts";
1297
1298
  import { apply } from "@deepseek-ai/dsh-mcp-client";
1298
1299
  import { apply as apply$1 } from "@deepseek-ai/dsh-skill-filesystem";
1299
- import { load } from "js-yaml";
1300
- import { credentialRef as credentialRef2 } from "@deepseek-ai/dsh-credentials";
1300
+ import yaml, { load } from "js-yaml";
1301
1301
  import { KNOWN_SESSION_EVENT_TYPES, SessionId } from "@deepseek-ai/dsh-session";
1302
1302
  import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
1303
1303
  import { setApprovalPolicy } from "@deepseek-ai/dsh-user-approval";
@@ -1559,9 +1559,9 @@ function asAttemptResult(value) {
1559
1559
  };
1560
1560
  }
1561
1561
  async function runGuardedRefresh(input) {
1562
- const classify = input.classify ?? classifyRefreshFailure;
1562
+ const classify2 = input.classify ?? classifyRefreshFailure;
1563
1563
  const controller = new AbortController();
1564
- const first = await attemptOnce(input.attempt, controller.signal, false, classify);
1564
+ const first = await attemptOnce(input.attempt, controller.signal, false, classify2);
1565
1565
  if (first.ok) return {
1566
1566
  ok: true,
1567
1567
  value: first.value,
@@ -1583,7 +1583,7 @@ async function runGuardedRefresh(input) {
1583
1583
  ...passthrough(first)
1584
1584
  };
1585
1585
  await (input.wait ?? defaultWait)(input.retryDelayMs ?? 1e3);
1586
- const second = await attemptOnce(input.attempt, controller.signal, true, classify);
1586
+ const second = await attemptOnce(input.attempt, controller.signal, true, classify2);
1587
1587
  if (second.ok) return {
1588
1588
  ok: true,
1589
1589
  value: second.value,
@@ -1602,7 +1602,7 @@ async function runGuardedRefresh(input) {
1602
1602
  function passthrough(result) {
1603
1603
  return result.ok || result.message === void 0 ? {} : { message: result.message };
1604
1604
  }
1605
- async function attemptOnce(attempt, signal, retry, classify) {
1605
+ async function attemptOnce(attempt, signal, retry, classify2) {
1606
1606
  try {
1607
1607
  const outcome = await attempt({
1608
1608
  signal,
@@ -1615,7 +1615,7 @@ async function attemptOnce(attempt, signal, retry, classify) {
1615
1615
  };
1616
1616
  return reported;
1617
1617
  } catch (error) {
1618
- const classified = classify(error);
1618
+ const classified = classify2(error);
1619
1619
  return {
1620
1620
  ok: false,
1621
1621
  kind: classified.kind,
@@ -3255,6 +3255,7 @@ function array(value, label) {
3255
3255
  }
3256
3256
 
3257
3257
  // packages/freecodego/harness-plugin/lib/index.js
3258
+ import { isVolatile } from "@deepseek-ai/cosmokit";
3258
3259
  import { Buffer as Buffer$1 } from "node:buffer";
3259
3260
  import { deriveTurnTokenUsage } from "@deepseek-ai/dsh-token-meter/client";
3260
3261
 
@@ -7632,20 +7633,6 @@ function walk$1(value, ancestors, onCycle) {
7632
7633
  ancestors.delete(value);
7633
7634
  }
7634
7635
  }
7635
- var FreeCodeGoGuardSettingsSchema = z2.object({
7636
- envReadGuardEnabled: z2.boolean().default(true),
7637
- doomLoopGuardEnabled: z2.boolean().default(true),
7638
- lspEnabled: z2.boolean().default(true),
7639
- commandPolicyEnabled: z2.boolean().default(true),
7640
- planModeEnabled: z2.boolean().default(true),
7641
- contextBudgetEnabled: z2.boolean().default(true),
7642
- cacheColdClearEnabled: z2.boolean().default(true),
7643
- cacheBreakAttributionEnabled: z2.boolean().default(true),
7644
- assistantLoopGuardEnabled: z2.boolean().default(true),
7645
- spillRecallEnabled: z2.boolean().default(true),
7646
- compactionFidelityEnabled: z2.boolean().default(true),
7647
- promptCompositionEnabled: z2.boolean().default(true)
7648
- });
7649
7636
  var SECRET_BASENAME = /^(?:\.env(?!\.(?:example|sample|template|dist)$)(?:\..+)?|\.credentials\.ya?ml(?!\.(?:example|sample|template|dist)$)(?:\..+)?|\.envrc|\.npmrc|\.netrc|\.pypirc|\.git-credentials|\.htpasswd|id_(?:rsa|dsa|ecdsa|ed25519)(?:_sk)?(?:\.pub)?|.+_rsa|.+_ed25519|.+\.pem|.+\.pfx|.+\.p12|.+\.keystore|secrets?\.json|secrets?\.ya?ml|credentials\.json)$/;
7650
7637
  var SECRET_DIRECTORIES = /* @__PURE__ */ new Set([".ssh", ".gnupg"]);
7651
7638
  var SECRET_FILE_IN_DIRECTORIES = [
@@ -10547,10 +10534,7 @@ function hookSignalOf(value) {
10547
10534
  }
10548
10535
  function hookContextMessage(text2) {
10549
10536
  return createUserMessage({
10550
- source: {
10551
- kind: "plugin",
10552
- plugin: "freecodego-hooks"
10553
- },
10537
+ source: { kind: "freecodego-hooks" },
10554
10538
  content: [{
10555
10539
  type: "text",
10556
10540
  text: text2
@@ -12267,6 +12251,9 @@ function isCancellationWording(message2) {
12267
12251
  }
12268
12252
  var ROUTE_TTL_MS = 1440 * 60 * 1e3;
12269
12253
  var DEFAULT_STREAM_IDLE_MS = 12e4;
12254
+ function bridgeRouteIdOf(baseURL) {
12255
+ return /\/anthropic\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\/v1)?\/?$/iu.exec(baseURL.trim())?.[1];
12256
+ }
12270
12257
  function callId$1(value) {
12271
12258
  return value;
12272
12259
  }
@@ -12356,6 +12343,22 @@ var ClaudeProtocolBridge = class {
12356
12343
  apiKey: this.secret
12357
12344
  };
12358
12345
  }
12346
+ /**
12347
+ * Whether this process still serves a route id.
12348
+ *
12349
+ * A route belongs to the listener that minted it: `dispose()` clears the map and
12350
+ * a new process mints new ids and a new port, so a saved endpoint whose id is
12351
+ * absent here answers nothing. That is the question a binding written before a
12352
+ * restart has to ask before it can be kept — see `web-search-binding.ts` — and
12353
+ * it is asked of the bridge rather than of a copy of the map, because only the
12354
+ * bridge knows when a route expired.
12355
+ * @param id - route id parsed off a saved endpoint ({@link bridgeRouteIdOf}).
12356
+ * @returns whether this bridge would answer for it.
12357
+ */
12358
+ hasRoute(id) {
12359
+ this.pruneRoutes();
12360
+ return this.routes.has(id);
12361
+ }
12359
12362
  /** Drop routes older than the TTL so a long-lived bridge cannot leak them. */
12360
12363
  pruneRoutes() {
12361
12364
  const cutoff = Date.now() - ROUTE_TTL_MS;
@@ -12917,8 +12920,7 @@ function estimateAnthropicInputTokens(body) {
12917
12920
  }
12918
12921
  function toResponsesGenerateOptions(route, body) {
12919
12922
  const messages = [];
12920
- if (typeof body.instructions === "string" && body.instructions.trim() !== "") messages.push(createMessage({
12921
- role: "user",
12923
+ if (typeof body.instructions === "string" && body.instructions.trim() !== "") messages.push(createUserMessage({
12922
12924
  content: [{
12923
12925
  type: "text",
12924
12926
  text: body.instructions
@@ -12928,8 +12930,7 @@ function toResponsesGenerateOptions(route, body) {
12928
12930
  if (Array.isArray(body.input)) for (const value of body.input) {
12929
12931
  const item = value;
12930
12932
  if (item.type === "function_call") {
12931
- messages.push(createMessage({
12932
- role: "assistant",
12933
+ messages.push(createAssistantMessage2({
12933
12934
  content: [{
12934
12935
  type: "tool-call",
12935
12936
  id: callId$1(String(item.call_id ?? item.id ?? "tool")),
@@ -12937,7 +12938,6 @@ function toResponsesGenerateOptions(route, body) {
12937
12938
  arguments: String(item.arguments ?? "")
12938
12939
  }],
12939
12940
  source: {
12940
- kind: "model",
12941
12941
  provider: "bridge",
12942
12942
  model: "bridge"
12943
12943
  }
@@ -12946,20 +12946,13 @@ function toResponsesGenerateOptions(route, body) {
12946
12946
  }
12947
12947
  if (item.type === "function_call_output") {
12948
12948
  const call = String(item.call_id ?? "tool");
12949
- messages.push(createMessage({
12950
- role: "user",
12949
+ messages.push(createToolResultMessage2({
12950
+ callId: callId$1(call),
12951
12951
  content: [{
12952
- type: "tool-result",
12953
- toolCallId: callId$1(call),
12954
- content: [{
12955
- type: "text",
12956
- text: typeof item.output === "string" ? item.output : JSON.stringify(item.output ?? "")
12957
- }]
12952
+ type: "text",
12953
+ text: typeof item.output === "string" ? item.output : JSON.stringify(item.output ?? "")
12958
12954
  }],
12959
- source: {
12960
- kind: "tool",
12961
- callId: callId$1(call)
12962
- }
12955
+ isError: item.is_error === true
12963
12956
  }));
12964
12957
  continue;
12965
12958
  }
@@ -12975,14 +12968,15 @@ function toResponsesGenerateOptions(route, body) {
12975
12968
  text: text2
12976
12969
  }] : [];
12977
12970
  });
12978
- if (blocks.length > 0) messages.push(createMessage({
12979
- role,
12971
+ if (blocks.length > 0) messages.push(role === "assistant" ? createAssistantMessage2({
12980
12972
  content: blocks,
12981
- source: role === "assistant" ? {
12982
- kind: "model",
12973
+ source: {
12983
12974
  provider: "bridge",
12984
12975
  model: "bridge"
12985
- } : { kind: "user" }
12976
+ }
12977
+ }) : createUserMessage({
12978
+ content: blocks,
12979
+ source: { kind: "user" }
12986
12980
  }));
12987
12981
  }
12988
12982
  const tools = Array.isArray(body.tools) ? body.tools.flatMap((value) => {
@@ -13007,45 +13001,65 @@ var BRIDGE_IMAGE_PLACEHOLDER = "[image attachment: not forwarded by the FreeCode
13007
13001
  function anthropicMessages(value) {
13008
13002
  const item = value;
13009
13003
  const role = item.role === "assistant" ? "assistant" : "user";
13010
- const blocks = (Array.isArray(item.content) ? item.content : [{
13004
+ const content = Array.isArray(item.content) ? item.content : [{
13011
13005
  type: "text",
13012
13006
  text: String(item.content ?? "")
13013
- }]).flatMap((block) => {
13007
+ }];
13008
+ const messages = [];
13009
+ const blocks = [];
13010
+ const flushTurn = () => {
13011
+ if (blocks.length === 0) return;
13012
+ messages.push(role === "assistant" ? createAssistantMessage2({
13013
+ content: blocks.splice(0),
13014
+ source: {
13015
+ provider: "bridge",
13016
+ model: "bridge"
13017
+ }
13018
+ }) : createUserMessage({
13019
+ content: blocks.splice(0),
13020
+ source: { kind: "user" }
13021
+ }));
13022
+ };
13023
+ for (const block of content) {
13014
13024
  const part = block;
13015
- if (part.type === "text") return [{
13016
- type: "text",
13017
- text: String(part.text ?? "")
13018
- }];
13019
- if (part.type === "tool_use") return [{
13020
- type: "tool-call",
13021
- id: callId$1(String(part.id ?? randomUUID3())),
13022
- name: String(part.name ?? "tool"),
13023
- arguments: JSON.stringify(part.input ?? {})
13024
- }];
13025
- if (part.type === "tool_result") return [{
13026
- type: "tool-result",
13027
- toolCallId: callId$1(String(part.tool_use_id ?? "tool")),
13028
- content: [{
13025
+ if (part.type === "tool_result") {
13026
+ flushTurn();
13027
+ messages.push(createToolResultMessage2({
13028
+ callId: callId$1(String(part.tool_use_id ?? "tool")),
13029
+ content: [{
13030
+ type: "text",
13031
+ text: toolResultText(part.content)
13032
+ }],
13033
+ isError: part.is_error === true
13034
+ }));
13035
+ continue;
13036
+ }
13037
+ if (part.type === "text") {
13038
+ blocks.push({
13029
13039
  type: "text",
13030
- text: toolResultText(part.content)
13031
- }]
13032
- }];
13033
- if (part.type === "image") return [{
13034
- type: "text",
13035
- text: BRIDGE_IMAGE_PLACEHOLDER
13036
- }];
13037
- return [];
13038
- });
13039
- if (blocks.length === 0) return [];
13040
- return [createMessage({
13041
- role,
13042
- content: blocks,
13043
- source: role === "assistant" ? {
13044
- kind: "model",
13045
- provider: "bridge",
13046
- model: "bridge"
13047
- } : { kind: "user" }
13048
- })];
13040
+ text: String(part.text ?? "")
13041
+ });
13042
+ continue;
13043
+ }
13044
+ if (part.type === "tool_use") {
13045
+ blocks.push({
13046
+ type: "tool-call",
13047
+ id: callId$1(String(part.id ?? randomUUID3())),
13048
+ name: String(part.name ?? "tool"),
13049
+ arguments: JSON.stringify(part.input ?? {})
13050
+ });
13051
+ continue;
13052
+ }
13053
+ if (part.type === "image") {
13054
+ blocks.push({
13055
+ type: "text",
13056
+ text: BRIDGE_IMAGE_PLACEHOLDER
13057
+ });
13058
+ continue;
13059
+ }
13060
+ }
13061
+ flushTurn();
13062
+ return messages;
13049
13063
  }
13050
13064
  function toolResultText(content) {
13051
13065
  if (typeof content === "string") return content;
@@ -13360,40 +13374,6 @@ async function inlineImageAttachmentBlocks(content, read) {
13360
13374
  function callId2(value) {
13361
13375
  return value;
13362
13376
  }
13363
- var FreeCodeGoCapabilitySettingsSchema = z2.object({
13364
- mcpEnabled: z2.boolean().default(false),
13365
- skillEnabled: z2.boolean().default(false),
13366
- voiceInputEnabled: z2.boolean().default(true),
13367
- sessionDeleteEnabled: z2.boolean().default(true),
13368
- modelCategories: z2.dict(z2.union([
13369
- z2.const("text"),
13370
- z2.const("image"),
13371
- z2.const("video"),
13372
- z2.const("audio")
13373
- ])).default({}),
13374
- mcpServers: z2.array(z2.object({
13375
- id: z2.string().min(1).max(80),
13376
- enabled: z2.boolean().default(true),
13377
- transport: z2.union([z2.const("stdio"), z2.const("streamable-http")]),
13378
- serverName: z2.string().pattern(SERVER_NAME$1),
13379
- command: z2.string().default(""),
13380
- args: z2.array(z2.string().max(4096)).default([]),
13381
- env: z2.dict(z2.string()).default({}),
13382
- cwd: z2.string().default(""),
13383
- url: z2.string().default(""),
13384
- headers: z2.dict(z2.string()).default({})
13385
- })).default([]),
13386
- skillRoots: z2.array(z2.object({
13387
- id: z2.string().min(1).max(80),
13388
- enabled: z2.boolean().default(true),
13389
- path: z2.string().min(1).max(4096)
13390
- })).default([]),
13391
- skillInvocationOverrides: z2.dict(z2.boolean()).default({}),
13392
- preferredSkillPlacement: z2.union([z2.object({
13393
- agent: z2.union([z2.const("harness"), z2.const("agents")]),
13394
- scope: z2.union([z2.const("project"), z2.const("user")])
13395
- }), z2.const(null)])
13396
- });
13397
13377
  var NO_PROJECT_ENTRIES = {
13398
13378
  mcpServers: [],
13399
13379
  skillRoots: []
@@ -14240,6 +14220,8 @@ function toMcpClientConfig(server) {
14240
14220
  var MAX_SOURCE_BYTES = 2e6;
14241
14221
  var MAX_SOURCE_FILES = 50;
14242
14222
  var RESOURCE_NAME = /^[A-Za-z0-9_.:/@-]{1,256}$/;
14223
+ var CONFLICT_PROTECTION_KEY = "pluginConflictProtectionEnabled";
14224
+ var NO_SETTINGS_PORT_CONFLICT_PROTECTION = false;
14243
14225
  var OWN_STAND_IN_MODULES = /* @__PURE__ */ new Set(["freecodego/agent-team", "freecodego/tool-agent-team"]);
14244
14226
  var OWN_STAND_IN_ENTRY_IDS = /* @__PURE__ */ new Set(["freecodego-agent-team", "freecodego-tool-agent-team"]);
14245
14227
  function isOfficialModuleName(moduleName) {
@@ -14250,27 +14232,6 @@ function isOwnStandIn(moduleName, entryId) {
14250
14232
  const tail = entryId.slice(entryId.lastIndexOf(":") + 1);
14251
14233
  return OWN_STAND_IN_ENTRY_IDS.has(tail);
14252
14234
  }
14253
- var FreeCodeGoPluginConflictSettingsSchema = z2.object({
14254
- pluginConflictProtectionEnabled: z2.boolean().default(false),
14255
- pluginConflictRecords: z2.array(z2.object({
14256
- id: z2.string().min(1).max(256),
14257
- detectedAt: z2.number(),
14258
- resource: z2.union([
14259
- z2.const("tool"),
14260
- z2.const("command"),
14261
- z2.const("settings"),
14262
- z2.const("route"),
14263
- z2.const("provider"),
14264
- z2.const("slot")
14265
- ]),
14266
- resourceName: z2.string().min(1).max(256),
14267
- disabledEntryId: z2.string().min(1).max(512),
14268
- disabledModuleName: z2.string().min(1).max(512),
14269
- keptEntryId: z2.string().min(1).max(512),
14270
- keptModuleName: z2.string().min(1).max(512),
14271
- yieldedToOfficial: z2.boolean()
14272
- })).default([])
14273
- });
14274
14235
  function scanPluginResourceClaims(source) {
14275
14236
  const claims = /* @__PURE__ */ new Map();
14276
14237
  const add = (resource, resourceName) => {
@@ -14316,7 +14277,7 @@ var FreeCodeGoPluginConflictGuard = class {
14316
14277
  this.ctx = ctx;
14317
14278
  this.settings = settings;
14318
14279
  this.records = settings?.get()?.pluginConflictRecords ?? [];
14319
- this.enabled = settings?.get()?.pluginConflictProtectionEnabled ?? bootstrapConflictProtectionEnabled();
14280
+ this.enabled = settings?.get()?.[CONFLICT_PROTECTION_KEY] ?? NO_SETTINGS_PORT_CONFLICT_PROTECTION;
14320
14281
  }
14321
14282
  /**
14322
14283
  * Install plugin-owned Loader lifecycle wrappers and seed active entry claims.
@@ -14351,13 +14312,17 @@ var FreeCodeGoPluginConflictGuard = class {
14351
14312
  };
14352
14313
  }, "freecodego: plugin conflict lifecycle guard");
14353
14314
  }
14354
- /** Attach settings after a pre-Loader bootstrap installation.
14315
+ /** Attach the settings port this guard reads the switch and its records through.
14316
+ *
14317
+ * The guard is a per-root singleton, so a later mount that carries a port re-points the
14318
+ * existing instance at the live document instead of leaving it on the answer it was
14319
+ * constructed with.
14355
14320
  * @param settings - the plugin settings port, when one is available.
14356
14321
  */
14357
14322
  configure(settings) {
14358
14323
  if (settings === void 0 || settings === this.settings) return;
14359
14324
  this.settings = settings;
14360
- this.enabled = settings.get()?.pluginConflictProtectionEnabled ?? bootstrapConflictProtectionEnabled();
14325
+ this.enabled = settings.get()?.[CONFLICT_PROTECTION_KEY] ?? NO_SETTINGS_PORT_CONFLICT_PROTECTION;
14361
14326
  if (this.started) this.installInterception();
14362
14327
  const persisted = settings.get()?.pluginConflictRecords ?? [];
14363
14328
  const merged = [...persisted, ...this.records.filter((record3) => !persisted.some((item) => sameConflict(item, record3)))].slice(-100);
@@ -14404,13 +14369,26 @@ var FreeCodeGoPluginConflictGuard = class {
14404
14369
  protectionEnabled() {
14405
14370
  return this.settings?.get()?.pluginConflictProtectionEnabled ?? this.enabled;
14406
14371
  }
14407
- /** Persist the global automatic-repair switch.
14372
+ /**
14373
+ * Persist the global automatic-repair switch, and start or stop acting on it.
14374
+ *
14375
+ * Recording the value is not enough in the on direction. A guard built while the switch was
14376
+ * off never installed its wrappers — that is deliberate, since leaving them in place would
14377
+ * keep this plugin reading every entry's package while the deployment had said not to — so a
14378
+ * guard that only wrote the new value would report protection as on while the running Loader
14379
+ * went on starting every duplicate, and would silently stay inert until the next boot. The
14380
+ * install has to come after the write resolves, because the live document is what
14381
+ * {@link protectionEnabled} reads back.
14382
+ *
14383
+ * The off direction needs nothing: the wrappers ask `protectionEnabled()` per entry, so a
14384
+ * switch turned off stops the answering without being uninstalled.
14408
14385
  * @param enabled - whether this capability is switched on.
14409
14386
  * @returns the plugin Conflict Status.
14410
14387
  */
14411
14388
  async setEnabled(enabled) {
14412
14389
  this.enabled = enabled;
14413
14390
  await this.settings?.update({ pluginConflictProtectionEnabled: enabled });
14391
+ if (enabled) this.installInterception();
14414
14392
  return this.snapshot();
14415
14393
  }
14416
14394
  async seed() {
@@ -14654,14 +14632,6 @@ function relativeModuleSpecifiers(source) {
14654
14632
  add(/\brequire\s*\(\s*(['"])(\.{1,2}\/[^'"]+)\1\s*\)/g);
14655
14633
  return [...specifiers];
14656
14634
  }
14657
- function bootstrapConflictProtectionEnabled() {
14658
- const settingsPath = join4(harnessHomeDirectory(), "settings.yaml");
14659
- try {
14660
- return readFileSync2(settingsPath, "utf8").match(/^freecodego-harness:\s*\r?\n(?:[ \t]+[^\r\n]*\r?\n)*?[ \t]+pluginConflictProtectionEnabled:\s*(true|false)\s*(?:#.*)?$/m)?.[1] === "true";
14661
- } catch {
14662
- return false;
14663
- }
14664
- }
14665
14635
  function sameConflict(left, right) {
14666
14636
  return left.resource === right.resource && left.resourceName === right.resourceName && left.disabledEntryId === right.disabledEntryId && left.disabledModuleName === right.disabledModuleName && left.keptEntryId === right.keptEntryId && left.keptModuleName === right.keptModuleName;
14667
14637
  }
@@ -14687,7 +14657,6 @@ var MAX_UNCONFIRMED_STARTUPS = 2;
14687
14657
  var DSH_INSTALL_TIMEOUT_MS = 15 * 6e4;
14688
14658
  var NPM_PACKAGE_RE$2 = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/i;
14689
14659
  var RELEASE_TAG_RE = /^(?:freecodego-)?v(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/u;
14690
- var FreeCodeGoPluginUpdateSettingsSchema = z2.object({ pluginUpdateChecksEnabled: z2.boolean().default(true) });
14691
14660
  var FreeCodeGoPluginUpdateService = class {
14692
14661
  options;
14693
14662
  packageName;
@@ -16973,10 +16942,7 @@ var FreeCodeGoAdvisorRuntime = class {
16973
16942
  const evidence = new AdvisorEvidenceCache((call, signal) => executeReviewTool(this.ctx, agent, call, signal));
16974
16943
  const findings = await Promise.all(Object.entries(COUNCIL_ROLES).map(async ([role, instruction]) => {
16975
16944
  const messages = [createUserMessage({
16976
- source: {
16977
- kind: "plugin",
16978
- plugin: "freecodego-advisor-council"
16979
- },
16945
+ source: { kind: "freecodego-advisor-council" },
16980
16946
  content: [{
16981
16947
  type: "text",
16982
16948
  text: `${reviewPrompt$2(delta, watchdog.instructions)}
@@ -17093,10 +17059,7 @@ Council perspective: ${role}. ${instruction}`
17093
17059
  const watchdog = await discoverWatchdog(agent.session.header.cwd);
17094
17060
  this.watchdogFilesBySession.set(String(agent.id), watchdog.files);
17095
17061
  const messages = [createUserMessage({
17096
- source: {
17097
- kind: "plugin",
17098
- plugin: "freecodego-advisor"
17099
- },
17062
+ source: { kind: "freecodego-advisor" },
17100
17063
  content: [{
17101
17064
  type: "text",
17102
17065
  text: reviewPrompt$2(delta, watchdog.instructions)
@@ -17235,10 +17198,7 @@ Perspective: ${perspective}`}`,
17235
17198
  cooldownUntilTurn: state.cooldownUntilTurn
17236
17199
  });
17237
17200
  const message2 = createUserMessage({
17238
- source: {
17239
- kind: "plugin",
17240
- plugin: "freecodego-advisor"
17241
- },
17201
+ source: { kind: "freecodego-advisor" },
17242
17202
  content: [{
17243
17203
  type: "text",
17244
17204
  text: `<advisory severity="${advice.severity}" guidance="weigh, do not blindly obey">
@@ -17351,7 +17311,6 @@ function messageText$2(message2) {
17351
17311
  return message2.content.map((block) => {
17352
17312
  if (block.type === "text" || block.type === "reasoning") return block.text;
17353
17313
  if (block.type === "tool-call") return `${block.name}(${block.arguments})`;
17354
- if (block.type === "tool-result") return block.content.map((item) => item.type === "text" ? item.text : `[${item.type}]`).join("\n");
17355
17314
  return `[${block.type}]`;
17356
17315
  }).join("\n");
17357
17316
  }
@@ -20943,10 +20902,7 @@ var FreeCodeGoEngineCouncil = class {
20943
20902
  try {
20944
20903
  signal.throwIfAborted();
20945
20904
  agent.followup(createUserMessage({
20946
- source: {
20947
- kind: "plugin",
20948
- plugin: "freecodego-engine-council"
20949
- },
20905
+ source: { kind: "freecodego-engine-council" },
20950
20906
  content: [{
20951
20907
  type: "text",
20952
20908
  text: prompt
@@ -21264,10 +21220,7 @@ function terminalReport(task, request, runtimes, state, quorum, error) {
21264
21220
  function injectCouncilHandoff(parent, report) {
21265
21221
  try {
21266
21222
  parent.inject(createUserMessage({
21267
- source: {
21268
- kind: "plugin",
21269
- plugin: "freecodego-engine-council"
21270
- },
21223
+ source: { kind: "freecodego-engine-council" },
21271
21224
  content: [{
21272
21225
  type: "text",
21273
21226
  text: "Engineering council " + report.id + " finished with state " + report.state + ". Read the durable report before proceeding. " + report.finalRecommendation
@@ -21337,8 +21290,9 @@ function sameRoutes(left, right) {
21337
21290
  return left.length === right.length && left.every((route, index) => route.provider === right[index]?.provider && route.model === right[index]?.model);
21338
21291
  }
21339
21292
  async function synchronizeSubagentModelRoutes(settings, llm) {
21340
- const current = settings.get(SUBAGENT_MODEL_SELECTION_NAMESPACE);
21341
- if (current === void 0) return [];
21293
+ const descriptor = settings.describe().find((row) => row.ns === SUBAGENT_MODEL_SELECTION_NAMESPACE);
21294
+ if (descriptor === void 0) return [];
21295
+ const current = descriptor.value;
21342
21296
  const previous = routesOf(current);
21343
21297
  const previousByProvider = /* @__PURE__ */ new Map();
21344
21298
  for (const route of previous) {
@@ -21381,7 +21335,7 @@ async function synchronizeSubagentModelRoutes(settings, llm) {
21381
21335
  allowedModels: filtered
21382
21336
  };
21383
21337
  if (currentRecord.enabled === void 0) patch.enabled = true;
21384
- if (!sameRoutes(previous, filtered) || currentRecord.enabled === void 0) await settings.update(SUBAGENT_MODEL_SELECTION_NAMESPACE, patch);
21338
+ if (!sameRoutes(previous, filtered) || currentRecord.enabled === void 0) await settings.update(SUBAGENT_MODEL_SELECTION_NAMESPACE, patch, descriptor.revision);
21385
21339
  return filtered;
21386
21340
  }
21387
21341
  var FreeCodeGoSubagentModelRouting = class {
@@ -21416,9 +21370,17 @@ var FreeCodeGoSubagentModelRouting = class {
21416
21370
  const llm = this.ctx.get("llm");
21417
21371
  if (settings === void 0 || llm === void 0) return;
21418
21372
  const operation = (async () => {
21373
+ let retries = 0;
21419
21374
  do {
21420
21375
  this.rerun = false;
21421
- await synchronizeSubagentModelRoutes(settings, llm);
21376
+ try {
21377
+ await synchronizeSubagentModelRoutes(settings, llm);
21378
+ retries = 0;
21379
+ } catch {
21380
+ if (retries >= 1 || this.disposed) break;
21381
+ retries += 1;
21382
+ this.rerun = true;
21383
+ }
21422
21384
  } while (this.rerun && !this.disposed);
21423
21385
  })().catch(() => void 0).finally(() => {
21424
21386
  if (this.pending === operation) this.pending = void 0;
@@ -26869,97 +26831,6 @@ var FreeCodeGoVerifyOnStop = class {
26869
26831
  }
26870
26832
  }
26871
26833
  };
26872
- var FreeCodeGoEngineeringSettingsSchema = z2.object({
26873
- engineeringEnabled: z2.boolean().default(false),
26874
- /**
26875
- * The rest of the bundled engineering library is opt-in: 23 audited Skills
26876
- * (the remainder of the authored set plus the vendored `mattpocock/skills`
26877
- * entries that are not in the default-on starter root) stay unmounted until
26878
- * the user turns this on.
26879
- */
26880
- engineeringSkillsEnabled: z2.boolean().default(false),
26881
- /**
26882
- * The starter set is on by default: ten Skills whose value does not depend on
26883
- * adopting a whole methodology. Four are disciplines the model applies on its
26884
- * own (evidence before completion claims, navigation before broad reading,
26885
- * planning multi-file work, root-cause debugging); the rest are `/name`
26886
- * entries a user reaches for on demand and that cost nothing until typed
26887
- * (a prompt-technique reference, the grilling interview and its primitive, a
26888
- * re-pitch escape hatch, a handoff document, and a questionnaire for
26889
- * decisions the agent cannot settle). They live in their own asset root so
26890
- * "all engineering Skills" stays the union of the roots — no skill file is
26891
- * duplicated, and no audit can disagree with a mount.
26892
- */
26893
- engineeringStarterSkillsEnabled: z2.boolean().default(true),
26894
- /**
26895
- * The vendored superpowers workflow pack is a separate opt-in: it is the
26896
- * auto-triggering, plan-then-dispatch methodology, so it must be a deliberate
26897
- * choice rather than a side effect of enabling the engineering disciplines.
26898
- */
26899
- engineeringSuperpowersSkillsEnabled: z2.boolean().default(false),
26900
- /**
26901
- * Injects a bounded capability map of the mounted Skills at session start.
26902
- * A default-off Skill library is invisible otherwise: the model never lists
26903
- * it, and a user who turned a pack on has no way to see what arrived.
26904
- */
26905
- engineeringSkillMapEnabled: z2.boolean().default(true),
26906
- /** Enables bounded project-declared verification after an approved council plan. */
26907
- engineeringQualityEnabled: z2.boolean().default(true),
26908
- engineeringMemoryEnabled: z2.boolean().default(true),
26909
- engineeringCouncilEnabled: z2.boolean().default(true),
26910
- engineeringCouncilDeepseekEnabled: z2.boolean().default(true),
26911
- engineeringCouncilCodexEnabled: z2.boolean().default(true),
26912
- engineeringCouncilClaudeEnabled: z2.boolean().default(true),
26913
- engineeringMemoryContextTokenBudget: z2.number().step(1).min(0).max(4e3).default(1200),
26914
- /**
26915
- * Default off, deliberately: the lexical rerank is free, deterministic, and
26916
- * already ranks by term coverage. Flipping this on spends one small model
26917
- * request per memory search to catch the memories that share no wording with
26918
- * the query — worth it for a user who searches by concept, wasted for one who
26919
- * greps for identifiers.
26920
- */
26921
- engineeringMemorySelectorEnabled: z2.boolean().default(false),
26922
- /**
26923
- * Default off, deliberately: this lets a model approve a pending action without
26924
- * asking the user. Off means every approval keeps prompting. On means an action
26925
- * whose arguments the reviewer can actually read may be cleared automatically —
26926
- * anything it refuses, cannot read, or declines to judge still goes to the user.
26927
- */
26928
- engineeringActionReviewEnabled: z2.boolean().default(false),
26929
- engineeringCodeGraphEnabled: z2.boolean().default(true),
26930
- engineeringCodeGraphAutoUpdate: z2.boolean().default(true),
26931
- engineeringGraphEngine: z2.union([
26932
- z2.const("auto"),
26933
- z2.const("graphify"),
26934
- z2.const("codegraph")
26935
- ]).default("auto"),
26936
- engineeringCouncilMaxRounds: z2.number().step(1).min(1).max(3).default(2),
26937
- engineeringCouncilTimeoutMs: z2.number().step(1).min(1e4).max(3e5).default(12e4),
26938
- engineeringCouncilQuorum: z2.number().step(1).min(1).max(3).default(2),
26939
- engineeringCouncilAutoRun: z2.boolean().default(false),
26940
- /**
26941
- * The three phases of the autonomous engineering loop. Each is a separate
26942
- * switch because they carry different risk: creating a goal only commits the
26943
- * user to finishing, while enabling the driver lets work continue with no
26944
- * human turn in between.
26945
- *
26946
- * - `engineeringLoopCapturePlan` — when the user approves a council decision,
26947
- * persist the approved plan as a durable Harness goal.
26948
- * - `engineeringLoopVerifyOnComplete` — when the active goal's driver reports
26949
- * it finished, run the declared verification stages and record the result.
26950
- * - `engineeringLoopAutoContinue` — let the Harness goal-round driver start
26951
- * the next round without a user turn. Off by default: a goal that continues
26952
- * unattended is the one behaviour a user must opt into explicitly.
26953
- */
26954
- engineeringLoopCapturePlan: z2.boolean().default(true),
26955
- engineeringLoopVerifyOnComplete: z2.boolean().default(true),
26956
- engineeringLoopAutoContinue: z2.boolean().default(false),
26957
- /** Round cap for a captured plan goal; bounds unattended continuation. */
26958
- engineeringLoopMaxGoalRounds: z2.number().step(1).min(1).max(256).default(24),
26959
- engineeringCouncilMaxTokens: z2.number().step(100).min(1200).max(2e4).default(3600),
26960
- engineeringCouncilMaxConcurrent: z2.number().step(1).min(1).max(8).default(2),
26961
- engineeringCouncilDecisionTtlMs: z2.number().step(6e4).min(6e4).max(10080 * 6e4).default(30 * 6e4)
26962
- });
26963
26834
  var GOAL_OBJECTIVE_MAX_CHARS = 8e3;
26964
26835
  var GOAL_EXCERPT_MAX_CHARS = 160;
26965
26836
  function goalPhaseOf(value) {
@@ -28486,10 +28357,7 @@ var FreeCodeGoEngineeringRegistry = class {
28486
28357
  fence.close
28487
28358
  ].join("\n");
28488
28359
  agent.inject(createUserMessage({
28489
- source: {
28490
- kind: "plugin",
28491
- plugin: "freecodego-engineering-memory"
28492
- },
28360
+ source: { kind: "freecodego-engineering-memory" },
28493
28361
  content: [{
28494
28362
  type: "text",
28495
28363
  text: text2
@@ -28526,10 +28394,7 @@ var FreeCodeGoEngineeringRegistry = class {
28526
28394
  if (built === void 0) return;
28527
28395
  this.skillMapBudget = built.metrics;
28528
28396
  agent.inject(createUserMessage({
28529
- source: {
28530
- kind: "plugin",
28531
- plugin: "freecodego-engineering-skills"
28532
- },
28397
+ source: { kind: "freecodego-engineering-skills" },
28533
28398
  content: [{
28534
28399
  type: "text",
28535
28400
  text: built.text
@@ -29899,7 +29764,7 @@ function selectGraphEngine(preference, graphifyInstalled, codeGraphInstalled) {
29899
29764
  }
29900
29765
  function normalizeSettings(value) {
29901
29766
  return {
29902
- engineeringEnabled: value?.engineeringEnabled === true,
29767
+ engineeringEnabled: value?.engineeringEnabled !== false,
29903
29768
  engineeringSkillsEnabled: value?.engineeringSkillsEnabled === true,
29904
29769
  engineeringStarterSkillsEnabled: value?.engineeringStarterSkillsEnabled !== false,
29905
29770
  engineeringSuperpowersSkillsEnabled: value?.engineeringSuperpowersSkillsEnabled === true,
@@ -29929,12 +29794,12 @@ function normalizeSettings(value) {
29929
29794
  engineeringCouncilDecisionTtlMs: boundedInteger$1(value?.engineeringCouncilDecisionTtlMs, 30 * 6e4, 6e4, 10080 * 6e4)
29930
29795
  };
29931
29796
  }
29932
- function pendingRecall(agent, plugin) {
29797
+ function pendingRecall(agent, producer) {
29933
29798
  try {
29934
29799
  const inbox = agent.inbox;
29935
29800
  if (inbox === void 0) return false;
29936
- const fromPlugin = (message2) => isRecord2(message2.source) && message2.source.kind === "plugin" && message2.source.plugin === plugin;
29937
- return (inbox.nextStep ?? []).some(fromPlugin) || (inbox.nextTurn ?? []).some(fromPlugin);
29801
+ const fromProducer = (message2) => isRecord2(message2.source) && message2.source.kind === producer;
29802
+ return (inbox.nextStep ?? []).some(fromProducer) || (inbox.nextTurn ?? []).some(fromProducer);
29938
29803
  } catch {
29939
29804
  return false;
29940
29805
  }
@@ -30955,14 +30820,6 @@ function describeCronExpression(expression) {
30955
30820
  }
30956
30821
  return `cron ${expression.source}`;
30957
30822
  }
30958
- var FreeCodeGoAutomationSettingsSchema = z2.object({
30959
- /** Master switch for declarative failure recovery. */
30960
- hookChainsEnabled: z2.boolean().default(true),
30961
- hookChainsMaxDepth: z2.number().step(1).min(0).max(10).default(2),
30962
- hookChainsCooldownMs: z2.number().step(1e3).min(0).max(1440 * 60 * 1e3).default(3e4),
30963
- /** Master switch for the calendar planner. */
30964
- scheduledTasksEnabled: z2.boolean().default(true)
30965
- });
30966
30823
  var HOOK_CHAINS_RELATIVE_PATH = join4(".freecodego", "hook-chains.json");
30967
30824
  function outcomeOfTurnEnd(reason) {
30968
30825
  switch (typeof reason === "object" && reason !== null ? reason.kind : void 0) {
@@ -31458,7 +31315,21 @@ function setEngineAvailability(agentEngines, id, availability) {
31458
31315
  } catch {
31459
31316
  }
31460
31317
  }
31318
+ var MODELS_SETTINGS_ENTRY = "llm-pi-ai";
31319
+ function peerSettings(ctx, entryId) {
31320
+ const settings = ctx.get("settings");
31321
+ if (typeof settings?.describe !== "function") return void 0;
31322
+ try {
31323
+ const descriptor = settings.describe().find((row) => row.ns === entryId);
31324
+ return descriptor === void 0 ? void 0 : maybeRecord(descriptor.value);
31325
+ } catch {
31326
+ return;
31327
+ }
31328
+ }
31461
31329
  var DONE = "[DONE]";
31330
+ function unsupported(what) {
31331
+ throw new LlmError(`${what} cannot be serialized on this wire`, "UNSUPPORTED_CONTENT");
31332
+ }
31462
31333
  async function* parseSse(stream) {
31463
31334
  const events = stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream());
31464
31335
  for await (const event of events) {
@@ -31561,8 +31432,8 @@ function serializeRequest(options, defaults = {}) {
31561
31432
  content: options.system
31562
31433
  });
31563
31434
  for (const message2 of options.messages) {
31564
- message2.content;
31565
31435
  if (message2.role === "system") {
31436
+ message2.content;
31566
31437
  messages.push({
31567
31438
  role: "system",
31568
31439
  content: text(message2.content)
@@ -31573,16 +31444,19 @@ function serializeRequest(options, defaults = {}) {
31573
31444
  messages.push(serializeAssistant(message2));
31574
31445
  continue;
31575
31446
  }
31576
- const results = message2.content.filter((block) => block.type === "tool-result");
31577
- const content = text(message2.content.filter((block) => block.type !== "tool-result"));
31578
- for (const result of results) messages.push({
31579
- role: "tool",
31580
- tool_call_id: result.toolCallId,
31581
- content: text(result.content) || "(no output)"
31582
- });
31583
- if (content !== "" || results.length === 0) messages.push({
31447
+ if (message2.role === "tool") {
31448
+ messages.push({
31449
+ role: "tool",
31450
+ tool_call_id: message2.toolCallId,
31451
+ content: text(message2.content) || "(no output)"
31452
+ });
31453
+ continue;
31454
+ }
31455
+ if (message2.role === "developer") unsupported("developer message");
31456
+ message2.content;
31457
+ messages.push({
31584
31458
  role: "user",
31585
- content
31459
+ content: text(message2.content)
31586
31460
  });
31587
31461
  }
31588
31462
  return requestWithMessages(options, messages, defaults);
@@ -31607,14 +31481,17 @@ async function serializeRequestWithInlineImages(options, images, defaults = {})
31607
31481
  messages.push(serializeAssistant(message2));
31608
31482
  continue;
31609
31483
  }
31610
- const results = message2.content.filter((block) => block.type === "tool-result");
31611
- const content = await inlineUserContent(message2.content.filter((block) => block.type !== "tool-result"), images);
31612
- for (const result of results) messages.push({
31613
- role: "tool",
31614
- tool_call_id: result.toolCallId,
31615
- content: text(result.content) || "(no output)"
31616
- });
31617
- if (content.length > 0 || results.length === 0) messages.push({
31484
+ if (message2.role === "tool") {
31485
+ messages.push({
31486
+ role: "tool",
31487
+ tool_call_id: message2.toolCallId,
31488
+ content: text(message2.content) || "(no output)"
31489
+ });
31490
+ continue;
31491
+ }
31492
+ if (message2.role === "developer") unsupported("developer message");
31493
+ const content = await inlineUserContent(message2.content, images);
31494
+ messages.push({
31618
31495
  role: "user",
31619
31496
  content: inlineUserWireContent(content)
31620
31497
  });
@@ -31870,13 +31747,6 @@ async function anthropicContent(message2, images) {
31870
31747
  case "tool-call":
31871
31748
  if (message2.role === "assistant") blocks.push(anthropicToolUse(block));
31872
31749
  break;
31873
- case "tool-result":
31874
- blocks.push({
31875
- type: "tool_result",
31876
- tool_use_id: block.toolCallId,
31877
- content: textOf$2(block.content) || "(no output)"
31878
- });
31879
- break;
31880
31750
  default:
31881
31751
  break;
31882
31752
  }
@@ -31886,19 +31756,38 @@ async function anthropicTurns(options, images) {
31886
31756
  const systems = [];
31887
31757
  if (options.system !== void 0 && options.system !== "") systems.push(options.system);
31888
31758
  const messages = [];
31759
+ const results = [];
31760
+ const flushResults = () => {
31761
+ if (results.length === 0) return;
31762
+ messages.push({
31763
+ role: "user",
31764
+ content: results.splice(0)
31765
+ });
31766
+ };
31889
31767
  for (const message2 of options.messages) {
31890
31768
  if (message2.role === "system") {
31891
31769
  const text2 = textOf$2(message2.content);
31892
31770
  if (text2 !== "") systems.push(text2);
31893
31771
  continue;
31894
31772
  }
31773
+ if (message2.role === "developer") unsupported("developer message");
31774
+ if (message2.role === "tool") {
31775
+ results.push({
31776
+ type: "tool_result",
31777
+ tool_use_id: message2.toolCallId,
31778
+ content: textOf$2(message2.content) || "(no output)"
31779
+ });
31780
+ continue;
31781
+ }
31895
31782
  const content = await anthropicContent(message2, images);
31896
31783
  if (content.length === 0) continue;
31784
+ flushResults();
31897
31785
  messages.push({
31898
31786
  role: message2.role === "assistant" ? "assistant" : "user",
31899
31787
  content
31900
31788
  });
31901
31789
  }
31790
+ flushResults();
31902
31791
  return {
31903
31792
  system: systems.length === 0 ? void 0 : systems.join("\n\n"),
31904
31793
  messages
@@ -32234,7 +32123,7 @@ var OpenAiCompatibleAdapter = class extends LlmAdapter {
32234
32123
  const detail = redactProviderDetail(await response.text().catch(() => ""));
32235
32124
  if (response.status === 400) onBadRequest?.(detail);
32236
32125
  if (response.status === 429) this.config.onRateLimited?.(this.config.providerName, response.status);
32237
- const rateHint = response.status === 429 ? this.config.rateLimitedHint?.(this.config.providerName) : void 0;
32126
+ const rateHint = this.config.rateLimitedHint?.(this.config.providerName, response.status, detail);
32238
32127
  throw new LlmError(`${this.config.providerName} provider request failed (HTTP ${response.status})${detail === "" ? "" : `: ${detail}`}${rateHint === void 0 ? "" : `
32239
32128
  ${rateHint}`}`, llmCodeForUpstreamStatus(response.status), { status: response.status });
32240
32129
  }
@@ -33347,21 +33236,29 @@ function audioMimeType(file) {
33347
33236
  const extension = path.extname(file).toLowerCase();
33348
33237
  return extension === ".webm" ? "audio/webm" : extension === ".ogg" || extension === ".opus" ? "audio/ogg" : extension === ".wav" ? "audio/wav" : extension === ".m4a" || extension === ".mp4" ? "audio/mp4" : extension === ".flac" ? "audio/flac" : "audio/mpeg";
33349
33238
  }
33350
- function registerMediaTools(host) {
33239
+ var MEDIA_GENERATION_TOOL_NAMES = {
33240
+ image: "freecodego_generate_image",
33241
+ video: "freecodego_generate_video"
33242
+ };
33243
+ var NO_MEDIA_TOOLS = {
33244
+ names: [],
33245
+ dispose: () => void 0
33246
+ };
33247
+ var GENERIC_MEDIA_OUTPUT = {
33248
+ schema: {
33249
+ type: "object",
33250
+ additionalProperties: true
33251
+ },
33252
+ render: (_args, value) => [{
33253
+ type: "text",
33254
+ text: JSON.stringify(value)
33255
+ }]
33256
+ };
33257
+ function registerGenerationTools(host) {
33351
33258
  const tools = host.ctx.get("tools");
33352
- if (tools === void 0) return;
33353
- const genericOutput = {
33354
- schema: {
33355
- type: "object",
33356
- additionalProperties: true
33357
- },
33358
- render: (_args, value) => [{
33359
- type: "text",
33360
- text: JSON.stringify(value)
33361
- }]
33362
- };
33363
- const disposeImage = tools.register(toolDefinition({
33364
- name: "freecodego_generate_image",
33259
+ if (tools === void 0) return NO_MEDIA_TOOLS;
33260
+ const imageTool = toolDefinition({
33261
+ name: MEDIA_GENERATION_TOOL_NAMES.image,
33365
33262
  description: "Generate an image with the user-selected default media model. Do not pass a model id. Pass images to edit or fuse existing pictures on the providers that accept sources. If that provider is unavailable, the Host safely tries another configured image model.",
33366
33263
  parameters: {
33367
33264
  type: "object",
@@ -33389,7 +33286,7 @@ function registerMediaTools(host) {
33389
33286
  additionalProperties: false
33390
33287
  },
33391
33288
  output: {
33392
- schema: genericOutput.schema,
33289
+ schema: GENERIC_MEDIA_OUTPUT.schema,
33393
33290
  render: (_args, value) => renderGeneratedImages(value)
33394
33291
  },
33395
33292
  execute: async (args, exec) => {
@@ -33399,9 +33296,9 @@ function registerMediaTools(host) {
33399
33296
  card: "generic",
33400
33297
  title: `Generate image: ${args.prompt}`
33401
33298
  })
33402
- }));
33403
- const disposeVideo = tools.register(toolDefinition({
33404
- name: "freecodego_generate_video",
33299
+ });
33300
+ const videoTool = toolDefinition({
33301
+ name: MEDIA_GENERATION_TOOL_NAMES.video,
33405
33302
  description: "Generate a video with the user-selected default media model. Do not pass a model id. If that provider is unavailable before task creation, the Host safely tries another configured video model.",
33406
33303
  parameters: {
33407
33304
  type: "object",
@@ -33440,7 +33337,7 @@ function registerMediaTools(host) {
33440
33337
  required: ["prompt"],
33441
33338
  additionalProperties: false
33442
33339
  },
33443
- output: genericOutput,
33340
+ output: GENERIC_MEDIA_OUTPUT,
33444
33341
  execute: async (args, exec) => {
33445
33342
  return host.generateVideoWithFallback(args, exec.signal);
33446
33343
  },
@@ -33448,8 +33345,21 @@ function registerMediaTools(host) {
33448
33345
  card: "generic",
33449
33346
  title: `Generate video: ${args.prompt}`
33450
33347
  })
33451
- }));
33452
- const disposeAudio = tools.register(toolDefinition({
33348
+ });
33349
+ const disposeImage = tools.register(imageTool);
33350
+ const disposeVideo = tools.register(videoTool);
33351
+ return {
33352
+ names: [imageTool.name, videoTool.name],
33353
+ dispose: () => {
33354
+ disposeImage();
33355
+ disposeVideo();
33356
+ }
33357
+ };
33358
+ }
33359
+ function registerAudioTools(host) {
33360
+ const tools = host.ctx.get("tools");
33361
+ if (tools === void 0) return NO_MEDIA_TOOLS;
33362
+ const audioTool = toolDefinition({
33453
33363
  name: "freecodego_generate_audio",
33454
33364
  description: "Generate speech/audio with the user-selected default media model. Do not pass a model id. The Host can fall back to another configured audio model when the selected provider is unavailable.",
33455
33365
  parameters: {
@@ -33474,7 +33384,7 @@ function registerMediaTools(host) {
33474
33384
  required: ["input"],
33475
33385
  additionalProperties: false
33476
33386
  },
33477
- output: genericOutput,
33387
+ output: GENERIC_MEDIA_OUTPUT,
33478
33388
  execute: async (args, exec) => {
33479
33389
  return host.generateAudioWithFallback(args, exec.agent?.session.header.cwd, exec.signal);
33480
33390
  },
@@ -33482,8 +33392,8 @@ function registerMediaTools(host) {
33482
33392
  card: "generic",
33483
33393
  title: "Generate audio with FreeCodeGo"
33484
33394
  })
33485
- }));
33486
- const disposeTranscribe = tools.register(toolDefinition({
33395
+ });
33396
+ const transcribeTool = toolDefinition({
33487
33397
  name: "freecodego_transcribe_audio",
33488
33398
  description: "Transcribe an audio file with the fixed free Groq whisper-large-v3-turbo model. The file must be inside the active workspace.",
33489
33399
  parameters: {
@@ -33499,7 +33409,7 @@ function registerMediaTools(host) {
33499
33409
  required: ["path"],
33500
33410
  additionalProperties: false
33501
33411
  },
33502
- output: genericOutput,
33412
+ output: GENERIC_MEDIA_OUTPUT,
33503
33413
  execute: async (args, exec) => {
33504
33414
  const cwd = exec.agent?.session.header.cwd;
33505
33415
  if (cwd === void 0) throw new Error("Audio transcription requires an active workspace");
@@ -33531,13 +33441,16 @@ function registerMediaTools(host) {
33531
33441
  card: "generic",
33532
33442
  title: `Transcribe audio: ${args.path}`
33533
33443
  })
33534
- }));
33535
- host.ctx.effect(() => () => {
33536
- disposeImage();
33537
- disposeVideo();
33538
- disposeAudio();
33539
- disposeTranscribe();
33540
- }, "freecodego: default media tools");
33444
+ });
33445
+ const disposeAudio = tools.register(audioTool);
33446
+ const disposeTranscribe = tools.register(transcribeTool);
33447
+ return {
33448
+ names: [audioTool.name, transcribeTool.name],
33449
+ dispose: () => {
33450
+ disposeAudio();
33451
+ disposeTranscribe();
33452
+ }
33453
+ };
33541
33454
  }
33542
33455
  async function generateImageWithFallback(host, args, signal) {
33543
33456
  return withMediaFallback(host, "image", signal, async (route) => {
@@ -33890,7 +33803,7 @@ function mediaRoute(host, selection) {
33890
33803
  };
33891
33804
  }
33892
33805
  async function configuredMediaConnection(host, route) {
33893
- const profile = asRecord(asRecord(asRecord(host.ctx.get("settings")?.get("llm-pi-ai")).providers)[route.provider]);
33806
+ const profile = asRecord(asRecord(asRecord(peerSettings(host.ctx, MODELS_SETTINGS_ENTRY)).providers)[route.provider]);
33894
33807
  const baseURL = asString(profile.baseURL) ?? defaultMediaBaseURL(route.provider);
33895
33808
  if (baseURL === void 0) throw new Error(`Media provider "${route.provider}" needs a Base URL in the Harness Models settings`);
33896
33809
  const reference = asString(profile.apiKeyEnv) ?? defaultMediaCredentialRef(route.provider);
@@ -34464,12 +34377,19 @@ function registerAdvisorTools(deps) {
34464
34377
  disposeTeamImplementation();
34465
34378
  }, "freecodego: Advisor and engineering council tools");
34466
34379
  }
34467
- function registerAgnesTools(deps) {
34380
+ var AGNES_MEDIA_TOOL_NAMES = {
34381
+ image: "agnes_generate_image",
34382
+ video: "agnes_generate_video"
34383
+ };
34384
+ function registerAgnesMediaTools(deps) {
34468
34385
  const client = deps.agnes;
34469
34386
  const tools = deps.ctx.get("tools");
34470
- if (client === void 0 || tools === void 0) return;
34387
+ if (client === void 0 || tools === void 0) return {
34388
+ names: [],
34389
+ dispose: () => void 0
34390
+ };
34471
34391
  const disposeImage = tools.register(toolDefinition({
34472
- name: "agnes_generate_image",
34392
+ name: AGNES_MEDIA_TOOL_NAMES.image,
34473
34393
  description: "Legacy image-generation tool name. Generate an image with the FreeCodeGo Settings image default; do not assume Agnes is selected. The Host routes this request through the configured default image model and falls back safely when unavailable.",
34474
34394
  parameters: {
34475
34395
  type: "object",
@@ -34505,7 +34425,7 @@ function registerAgnesTools(deps) {
34505
34425
  })
34506
34426
  }));
34507
34427
  const disposeVideo = tools.register(toolDefinition({
34508
- name: "agnes_generate_video",
34428
+ name: AGNES_MEDIA_TOOL_NAMES.video,
34509
34429
  description: "Legacy video-generation tool name. Generate a video with the FreeCodeGo Settings video default; do not assume Agnes is selected. The Host routes this request through the configured default video model and falls back safely when unavailable.",
34510
34430
  parameters: {
34511
34431
  type: "object",
@@ -34556,10 +34476,13 @@ function registerAgnesTools(deps) {
34556
34476
  title: `Generate video: ${args.prompt}`
34557
34477
  })
34558
34478
  }));
34559
- deps.ctx.effect(() => () => {
34560
- disposeImage();
34561
- disposeVideo();
34562
- }, "freecodego: Agnes media tools");
34479
+ return {
34480
+ names: [AGNES_MEDIA_TOOL_NAMES.image, AGNES_MEDIA_TOOL_NAMES.video],
34481
+ dispose: () => {
34482
+ disposeImage();
34483
+ disposeVideo();
34484
+ }
34485
+ };
34563
34486
  }
34564
34487
  var COMMUNITY_ASSET_HOSTS = /* @__PURE__ */ new Set([
34565
34488
  "raw.githubusercontent.com",
@@ -36721,6 +36644,102 @@ async function ensureFreeCodeGoAgentPreset() {
36721
36644
  return false;
36722
36645
  }
36723
36646
  }
36647
+ async function loaderSettled(ctx) {
36648
+ const loader = ctx?.root?.loader;
36649
+ if (typeof loader?.await !== "function") return;
36650
+ try {
36651
+ await loader.await.call(loader);
36652
+ } catch {
36653
+ }
36654
+ }
36655
+ function findUpstreamAgentPresets(ctx) {
36656
+ const get = ctx?.get;
36657
+ if (typeof get !== "function") return void 0;
36658
+ let candidate;
36659
+ try {
36660
+ candidate = get.call(ctx, "agentPresets");
36661
+ } catch {
36662
+ return;
36663
+ }
36664
+ const view = candidate;
36665
+ return typeof view?.list === "function" && typeof view?.register === "function" ? view : void 0;
36666
+ }
36667
+ var JS_EXPRESSION_TAG = "tag:yaml.org,2002:js";
36668
+ var PLATFORM_EXPRESSION = /^process\.platform\s*(===|!==)\s*'([^']+)'$/;
36669
+ var presetSchema = yaml.DEFAULT_SCHEMA.extend([new yaml.Type(JS_EXPRESSION_TAG, {
36670
+ kind: "scalar",
36671
+ construct: (source) => {
36672
+ const match = PLATFORM_EXPRESSION.exec(source.trim());
36673
+ if (match === null) throw new Error(`unsupported !!js expression in a bundled preset: ${source.trim()}`);
36674
+ return match[1] === "===" ? process.platform === match[2] : process.platform !== match[2];
36675
+ }
36676
+ })]);
36677
+ async function readBundledAgentPreset(presetId) {
36678
+ const source = presetSourceDirectory(presetId);
36679
+ if (source === void 0) return void 0;
36680
+ const metadata = maybeRecord(yaml.load(await readFile3(join4(source, "preset.yml"), "utf8"), { schema: presetSchema }));
36681
+ const loaded2 = yaml.load(await readFile3(join4(source, "agent.cordis.yml"), "utf8"), { schema: presetSchema });
36682
+ if (!Array.isArray(loaded2)) return void 0;
36683
+ const plugins = [];
36684
+ for (const row of loaded2) {
36685
+ const record3 = maybeRecord(row);
36686
+ if (record3 === void 0) return void 0;
36687
+ plugins.push(record3);
36688
+ }
36689
+ const name2 = metadata?.name;
36690
+ const description = metadata?.description;
36691
+ const order = metadata?.order;
36692
+ return {
36693
+ id: presetId,
36694
+ ...typeof name2 === "string" && name2 !== "" ? { name: name2 } : {},
36695
+ ...typeof description === "string" && description !== "" ? { description } : {},
36696
+ ...typeof order === "number" && Number.isFinite(order) ? { order } : {},
36697
+ plugins
36698
+ };
36699
+ }
36700
+ function installFreeCodeGoAgentPresets(ctx) {
36701
+ const disposers = [];
36702
+ let released = false;
36703
+ const keep = (unregister) => {
36704
+ if (released) {
36705
+ Promise.resolve(unregister()).catch(() => {
36706
+ });
36707
+ return;
36708
+ }
36709
+ disposers.push(unregister);
36710
+ };
36711
+ return {
36712
+ done: (async () => {
36713
+ await loaderSettled(ctx);
36714
+ const registry = findUpstreamAgentPresets(ctx);
36715
+ if (registry === void 0) {
36716
+ await ensureFreeCodeGoAgentPreset();
36717
+ return;
36718
+ }
36719
+ let declared;
36720
+ try {
36721
+ declared = new Set((await registry.list()).map((row) => row.id));
36722
+ } catch {
36723
+ declared = /* @__PURE__ */ new Set();
36724
+ }
36725
+ for (const presetId of BUNDLED_PRESET_IDS) {
36726
+ if (declared.has(presetId)) continue;
36727
+ const definition = await readBundledAgentPreset(presetId);
36728
+ if (definition === void 0) continue;
36729
+ try {
36730
+ keep(await registry.register(definition));
36731
+ } catch {
36732
+ }
36733
+ }
36734
+ })().catch(() => {
36735
+ }),
36736
+ release: () => {
36737
+ released = true;
36738
+ for (const unregister of disposers.splice(0)) Promise.resolve(unregister()).catch(() => {
36739
+ });
36740
+ }
36741
+ };
36742
+ }
36724
36743
  var DEFAULT_CAPACITY = 1e3;
36725
36744
  var DEFAULT_TTL_MS = 30 * 6e4;
36726
36745
  function computeKey(payload) {
@@ -42612,17 +42631,6 @@ Fix these and call the tool again; no child agent was started.`;
42612
42631
  var run$1 = promisify(execFile);
42613
42632
  var GIT_TIMEOUT_MS$1 = 1e4;
42614
42633
  var MAX_GIT_OUTPUT$1 = 4096;
42615
- var FreeCodeGoTrustSettingsSchema = z2.object({
42616
- /**
42617
- * Master switch for the whole gate.
42618
- *
42619
- * Off means every project-scoped surface is treated as trusted — the
42620
- * pre-gate behaviour, kept reachable so a deployment that already controls
42621
- * which repositories it opens is not forced to grant each one. It is
42622
- * deliberately not the default: the safe state must not be the opt-in one.
42623
- */
42624
- folderTrustEnabled: z2.boolean().default(true)
42625
- });
42626
42634
  var FOLDER_TRUST_ENV = "FREECODEGO_FOLDER_TRUST";
42627
42635
  function folderTrustEnabled(settings) {
42628
42636
  const raw = process.env[FOLDER_TRUST_ENV]?.trim().toLowerCase();
@@ -43359,10 +43367,7 @@ var AssistantLoopGuard = class {
43359
43367
  injectReminder(agent, finding) {
43360
43368
  try {
43361
43369
  agent.inject(createUserMessage({
43362
- source: {
43363
- kind: "plugin",
43364
- plugin: "freecodego-assistant-loop-guard"
43365
- },
43370
+ source: { kind: "freecodego-assistant-loop-guard" },
43366
43371
  content: [{
43367
43372
  type: "text",
43368
43373
  text: renderAssistantLoopReminder(finding)
@@ -44058,10 +44063,8 @@ function isHarnessPruned(message2) {
44058
44063
  return text2 !== void 0 && text2.includes("[... tool result middle pruned ...]");
44059
44064
  }
44060
44065
  function hasOnlyTextContent(message2) {
44061
- if (message2.role !== "user" || !Array.isArray(message2.content)) return false;
44062
- const outer = message2.content[0];
44063
- if (outer?.type !== "tool-result" || !Array.isArray(outer.content)) return false;
44064
- const blocks = outer.content;
44066
+ if (message2.role !== "tool" || !Array.isArray(message2.content)) return false;
44067
+ const blocks = message2.content;
44065
44068
  if (blocks.length === 0) return false;
44066
44069
  return blocks.every((block) => block?.type === "text");
44067
44070
  }
@@ -44072,17 +44075,13 @@ function isClearableResult(message2, tool2, clearable, clearedMarker) {
44072
44075
  return hasOnlyTextContent(message2);
44073
44076
  }
44074
44077
  function resultText(message2) {
44075
- if (message2.role !== "user" || !Array.isArray(message2.content)) return void 0;
44076
- const outer = message2.content[0];
44077
- if (outer?.type !== "tool-result" || !Array.isArray(outer.content)) return void 0;
44078
+ if (message2.role !== "tool" || !Array.isArray(message2.content)) return void 0;
44078
44079
  const texts = [];
44079
- for (const block of outer.content) if (block?.type === "text" && typeof block.text === "string") texts.push(block.text);
44080
+ for (const block of message2.content) if (block?.type === "text" && typeof block.text === "string") texts.push(block.text);
44080
44081
  return texts.join("\n");
44081
44082
  }
44082
44083
  function resultCallId(message2) {
44083
- if (message2.role !== "user" || !Array.isArray(message2.content)) return void 0;
44084
- const first = message2.content[0];
44085
- return first?.type === "tool-result" && typeof first.toolCallId === "string" ? first.toolCallId : void 0;
44084
+ return message2.role === "tool" && typeof message2.toolCallId === "string" ? message2.toolCallId : void 0;
44086
44085
  }
44087
44086
  function clearOldToolResults(messages, options = {}) {
44088
44087
  const clearable = options.clearableTools ?? CLEARABLE_TOOL_KINDS;
@@ -44133,12 +44132,8 @@ function clearOldToolResults(messages, options = {}) {
44133
44132
  return {
44134
44133
  ...message2,
44135
44134
  content: [{
44136
- type: "tool-result",
44137
- toolCallId: callId3,
44138
- content: [{
44139
- type: "text",
44140
- text: replacement
44141
- }]
44135
+ type: "text",
44136
+ text: replacement
44142
44137
  }]
44143
44138
  };
44144
44139
  }),
@@ -45093,7 +45088,7 @@ function messageText$1(content, depth = 4) {
45093
45088
  }
45094
45089
  function isSummaryEvent(data) {
45095
45090
  const message2 = data.message;
45096
- if (message2?.source?.kind === "compaction") return true;
45091
+ if (message2?.source?.kind === "compact-checkpoint") return true;
45097
45092
  const text2 = messageText$1(message2?.content);
45098
45093
  return text2.startsWith("<summary>") || text2.includes("<compacted-conversation");
45099
45094
  }
@@ -45416,10 +45411,7 @@ function installRehydration(ctx, deps) {
45416
45411
  const agent = ctx.agents?.get?.(String(session.id));
45417
45412
  if (agent === void 0 || typeof agent.inject !== "function") return;
45418
45413
  agent.inject(createUserMessage({
45419
- source: {
45420
- kind: "plugin",
45421
- plugin: "freecodego-rehydration"
45422
- },
45414
+ source: { kind: "freecodego-rehydration" },
45423
45415
  content: [{
45424
45416
  type: "text",
45425
45417
  text: body
@@ -45446,10 +45438,7 @@ function createMemorySelector(options) {
45446
45438
  provider: options.route.provider,
45447
45439
  model: options.route.model,
45448
45440
  messages: [createUserMessage({
45449
- source: {
45450
- kind: "plugin",
45451
- plugin: "freecodego-memory-recall"
45452
- },
45441
+ source: { kind: "freecodego-memory-recall" },
45453
45442
  content: [{
45454
45443
  type: "text",
45455
45444
  text: selectorPrompt(query2, excerpts, limit)
@@ -45823,36 +45812,40 @@ function sortEntries(entries) {
45823
45812
  return [...entries].sort((left, right) => left.name.localeCompare(right.name));
45824
45813
  }
45825
45814
  var FreeCodeGoPolicy = class {
45826
- settings;
45815
+ config;
45816
+ writer;
45827
45817
  /**
45828
- * @param settings - the registered namespace scope, or `undefined` when the
45829
- * composition has no settings service (headless SDK trees, tests). The
45830
- * policy is always constructible so consumers never branch on its presence;
45831
- * they branch on {@link configured} instead.
45818
+ * @param config - the plugin's resolved configuration, or `undefined` when the
45819
+ * composition supplies none (tests, a bare context). The policy is always
45820
+ * constructible so consumers never branch on its presence; they branch on
45821
+ * {@link configured} instead.
45822
+ * @param writer - the persistence step, absent when no settings service is mounted.
45832
45823
  */
45833
- constructor(settings) {
45834
- this.settings = settings;
45824
+ constructor(config, writer = void 0) {
45825
+ this.config = config;
45826
+ this.writer = writer;
45835
45827
  }
45836
- /** Whether a settings service was available when the plugin was built. */
45828
+ /** Whether a configuration was available when the plugin was built. */
45837
45829
  get configured() {
45838
- return this.settings !== void 0;
45830
+ return this.config !== void 0;
45839
45831
  }
45840
45832
  /**
45841
45833
  * The settings a behaviour read must use.
45842
45834
  *
45843
- * The only read of the registered scope in this package, which is what makes it
45844
- * the place a second configuration layer would have to be applied — see the
45845
- * module doc.
45846
- * @returns the resolved document, or `undefined` when no settings service exists.
45835
+ * The only read of the Config in this package, which is what makes it the place a
45836
+ * second configuration layer would have to be applied — see the module doc.
45837
+ * @returns the resolved document, or `undefined` when no configuration exists.
45847
45838
  */
45848
45839
  get() {
45849
- return this.settings?.get();
45840
+ return this.config === void 0 ? void 0 : resolveSettings(this.config);
45850
45841
  }
45851
45842
  /**
45852
45843
  * Apply a user-originated change.
45853
45844
  *
45854
- * Writes go to the registered scope, which is the only layer there is: nothing
45855
- * a user gesture can reach edits a document other than their own.
45845
+ * The write goes to this plugin's own profile entry, through the settings service that
45846
+ * owns validation, revisions, and the document write. A composition without that
45847
+ * service drops the patch rather than pretending to have stored it: the caller is a
45848
+ * user gesture whose failure the Web client reports from the RPC that carried it.
45856
45849
  * @param patch - the fields to merge into the settings document. Typed as the
45857
45850
  * harness types it (`object`) rather than as a `Partial` of the document,
45858
45851
  * because a patch is a *merge*: the caller that owns one settings domain
@@ -45860,9 +45853,17 @@ var FreeCodeGoPolicy = class {
45860
45853
  * force every caller to widen a patch it knows is already valid.
45861
45854
  */
45862
45855
  async update(patch) {
45863
- await this.settings?.update(patch);
45856
+ await this.writer?.update(patch);
45864
45857
  }
45865
45858
  };
45859
+ function resolveSettings(config) {
45860
+ const document = {};
45861
+ for (const [key, entry] of Object.entries(config)) {
45862
+ if (!isVolatile(entry)) continue;
45863
+ document[key] = entry.get();
45864
+ }
45865
+ return document;
45866
+ }
45866
45867
  var ROLLOUT_STAGES = [
45867
45868
  "off",
45868
45869
  "record_only",
@@ -46446,10 +46447,7 @@ function createMemoryDreamPlanner(options) {
46446
46447
  provider: options.route.provider,
46447
46448
  model: options.route.model,
46448
46449
  messages: [createUserMessage({
46449
- source: {
46450
- kind: "plugin",
46451
- plugin: "freecodego-memory-dream"
46452
- },
46450
+ source: { kind: "freecodego-memory-dream" },
46453
46451
  content: [{
46454
46452
  type: "text",
46455
46453
  text: dreamPrompt(request)
@@ -46706,10 +46704,7 @@ function createActionReviewer(input) {
46706
46704
  provider: input.route.provider,
46707
46705
  model: input.route.model,
46708
46706
  messages: [createUserMessage({
46709
- source: {
46710
- kind: "plugin",
46711
- plugin: "freecodego-action-review"
46712
- },
46707
+ source: { kind: "freecodego-action-review" },
46713
46708
  content: [{
46714
46709
  type: "text",
46715
46710
  text: reviewPrompt(composed, action)
@@ -46802,10 +46797,6 @@ function messageText(message2) {
46802
46797
  const block = item;
46803
46798
  if (block.type === "text" || block.type === "reasoning") return block.text ?? "";
46804
46799
  if (block.type === "tool-call") return `${block.name ?? ""}(${block.arguments ?? ""})`;
46805
- if (block.type === "tool-result") return (block.content ?? []).map((part) => {
46806
- const item2 = part;
46807
- return item2.type === "text" ? item2.text ?? "" : `[${item2.type ?? "unknown"}]`;
46808
- }).join("\n");
46809
46800
  return block.type === void 0 ? "" : `[${block.type}]`;
46810
46801
  }).join("\n");
46811
46802
  }
@@ -52093,7 +52084,12 @@ function imageInputModalities(id, name2) {
52093
52084
  return /(?:\bgpt[\s_.-]?(?:4o|4\.1|4\.5|5)\b|\bgemini\b|\bclaude\b|\bgrok\b|(?:[-_.]|\b)(?:vision|vl|4v)(?:[-_.]|\b))/iu.test(value) ? ["text", "image"] : ["text"];
52094
52085
  }
52095
52086
  var RATE_LIMIT_PROXY_HINT = "\u5F53\u524D IP \u53EF\u80FD\u88AB\u4E0A\u6E38\u9650\u6D41\uFF1A\u8BF7\u5C1D\u8BD5\u5173\u95ED\u672C\u5730/\u5168\u5C40\u4EE3\u7406\u540E\u91CD\u8BD5\u3002\nYour current IP may be rate-limited by the upstream: try disabling your local/global proxy and retry.";
52096
- var KILO_RATE_LIMIT_HINT = "Kilo \u514D\u8D39\u901A\u9053\u6309\u51FA\u53E3 IP \u9650\u6D41\uFF08\u5B98\u65B9\u914D\u989D\uFF1A\u6BCF\u5C0F\u65F6 200 \u6B21\uFF0C\u767B\u5F55\u4E0E\u5426\u90FD\u4E00\u6837\uFF09\uFF0C\u5F53\u524D\u7F51\u7EDC\u53EF\u80FD\u5DF2\u7528\u5B8C\uFF1A\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u66F4\u6362\u7F51\u7EDC\u51FA\u53E3\u3002\nKilo meters its free routes per egress IP (200 requests/hour, signed in or not) and this network may be out of budget: retry later, or use a different network egress.";
52087
+ var KILO_RATE_LIMIT_HINT = "Kilo \u514D\u8D39\u901A\u9053\u6309\u51FA\u53E3 IP \u9650\u6D41\uFF08\u5B98\u65B9\u914D\u989D\uFF1A\u6BCF\u5C0F\u65F6 200 \u6B21\uFF0C\u767B\u5F55\u4E0E\u5426\u90FD\u4E00\u6837\uFF09\uFF0C\u5F53\u524D\u7F51\u7EDC\u53EF\u80FD\u5DF2\u7528\u5B8C\uFF1A\u8BF7\u66F4\u6362\u8282\u70B9 IP \u540E\u91CD\u8BD5\uFF0C\u6216\u5173\u95ED\u4EE3\u7406\u3001\u7528\u771F\u5B9E IP \u8BBF\u95EE\u3002\nKilo meters its free routes per egress IP (200 requests/hour, signed in or not) and this network may be out of budget: switch to a different node IP, or disable your proxy and retry from your real IP.";
52088
+ var OPENCODE_FREE_TIER_HINT = 'OpenCode \u514D\u8D39\u989D\u5EA6\u6309\u51FA\u53E3 IP \u5224\u5B9A\uFF1A\u5F53\u524D\u8282\u70B9 IP \u88AB\u4E0A\u6E38\u5224\u4E3A\u975E\u5B98\u65B9\u5BA2\u6237\u7AEF\uFF08\u4E0A\u6E38\u539F\u6587\uFF1Afree tier can only be used from within OpenCode\uFF09\uFF0C\u4E0D\u662F\u6A21\u578B\u6216\u5BC6\u94A5\u7684\u95EE\u9898\u3002\u8BF7\u66F4\u6362\u8282\u70B9 IP \u540E\u91CD\u8BD5\uFF0C\u6216\u5173\u95ED\u4EE3\u7406\u3001\u7528\u771F\u5B9E IP \u8BBF\u95EE\u3002\nOpenCode decides its free tier by egress IP: this node address is not accepted as its own client (upstream: "free tier can only be used from within OpenCode"), so neither the model nor the key is the problem. Switch to a different node IP, or disable your proxy and retry from your real IP.';
52089
+ function isOpenCodeFreeTierRefusal(status, detail) {
52090
+ if (status !== 403) return false;
52091
+ return /FreeTierError|free tier can only be used/iu.test(detail);
52092
+ }
52097
52093
  var FREE_UPSTREAM_MODELS = [
52098
52094
  {
52099
52095
  id: "mimo-v2.5",
@@ -52497,7 +52493,10 @@ var FreeCodeGoManagedCatalogs = class {
52497
52493
  normalizeReasoningEffort: (effort) => isDirectReasoningEffort(effort) ? effort : void 0,
52498
52494
  defaultReasoningEffort: "off",
52499
52495
  reasoningEffortsForModel: () => DIRECT_REASONING_EFFORTS,
52500
- rateLimitedHint: () => RATE_LIMIT_PROXY_HINT,
52496
+ rateLimitedHint: (_provider, status, detail) => {
52497
+ if (isOpenCodeFreeTierRefusal(status, detail)) return OPENCODE_FREE_TIER_HINT;
52498
+ return status === 429 ? RATE_LIMIT_PROXY_HINT : void 0;
52499
+ },
52501
52500
  omitDefaultMaxTokens: true,
52502
52501
  omitMaxTokens: true,
52503
52502
  defaultContextWindow: 1e6,
@@ -52523,7 +52522,7 @@ var FreeCodeGoManagedCatalogs = class {
52523
52522
  headers: { "user-agent": "freecodego/kilo" }
52524
52523
  };
52525
52524
  },
52526
- rateLimitedHint: () => KILO_RATE_LIMIT_HINT,
52525
+ rateLimitedHint: (_provider, status) => status === 429 ? KILO_RATE_LIMIT_HINT : void 0,
52527
52526
  reasoningWire: "standard",
52528
52527
  normalizeReasoningEffort: (effort) => isDirectReasoningEffort(effort) ? effort : void 0,
52529
52528
  defaultReasoningEffort: "off",
@@ -52863,8 +52862,8 @@ var FreeCodeGoManagedCatalogs = class {
52863
52862
  /** Refresh user-visible gateway monitor facts without delaying model picker paint. */
52864
52863
  refreshGatewayHealthInBackground() {
52865
52864
  const endpoint = this.deps.gatewayBaseUrl();
52866
- const unsupported = this.gatewayHealthUnsupported;
52867
- if (unsupported !== void 0 && unsupported.endpoint === endpoint && Date.now() < unsupported.expiresAt || this.deps.api() === void 0 || this.deps.account() === void 0 || this.deps.account().snapshot().status !== "authenticated") return;
52865
+ const unsupported2 = this.gatewayHealthUnsupported;
52866
+ if (unsupported2 !== void 0 && unsupported2.endpoint === endpoint && Date.now() < unsupported2.expiresAt || this.deps.api() === void 0 || this.deps.account() === void 0 || this.deps.account().snapshot().status !== "authenticated") return;
52868
52867
  const now = Date.now();
52869
52868
  if (this.gatewayHealthPromise !== void 0 || (this.gatewayHealthCache?.expiresAt ?? 0) > now || this.gatewayHealthRefreshAfter > now) return;
52870
52869
  this.gatewayHealthRefreshAfter = now + GATEWAY_HEALTH_CACHE_TTL_MS;
@@ -55156,10 +55155,7 @@ function renderFile(file) {
55156
55155
  lines.push("</file>");
55157
55156
  return lines.join("\n");
55158
55157
  }
55159
- var REVIEW_MESSAGE_SOURCE = {
55160
- kind: "plugin",
55161
- plugin: "freecodego/review"
55162
- };
55158
+ var REVIEW_MESSAGE_SOURCE = { kind: "freecodego/review" };
55163
55159
  function createReviewModelPort(llm, resolveRoute2, sessionId) {
55164
55160
  return { async generate(request) {
55165
55161
  const route = resolveRoute2();
@@ -58048,10 +58044,7 @@ function wrapChild(handle, maxOutputChars) {
58048
58044
  return {
58049
58045
  async run(prompt) {
58050
58046
  handle.agent.followup(createUserMessage({
58051
- source: {
58052
- kind: "plugin",
58053
- plugin: "freecodego-review"
58054
- },
58047
+ source: { kind: "freecodego-review" },
58055
58048
  content: [{
58056
58049
  type: "text",
58057
58050
  text: prompt
@@ -58363,6 +58356,14 @@ function narrowTurnScope(turnPaths, changedPaths) {
58363
58356
  const changed = new Set(changedPaths);
58364
58357
  return turnPaths.every((path3) => changed.has(path3)) ? turnPaths : changedPaths;
58365
58358
  }
58359
+ function isCallbackTarget(target) {
58360
+ if (target === void 0) return false;
58361
+ try {
58362
+ return new URL(target, "http://127.0.0.1").pathname === TRAE_CALLBACK_PATH;
58363
+ } catch {
58364
+ return false;
58365
+ }
58366
+ }
58366
58367
  function completionPage(language) {
58367
58368
  const title = language === "zh" ? "TRAE \u6388\u6743\u5DF2\u6536\u5230" : "TRAE authorization received";
58368
58369
  return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body style="font-family:system-ui;margin:48px;color:#222"><h1 style="font-size:20px">${title}</h1><p>${language === "zh" ? "\u8BF7\u56DE\u5230\u5E94\u7528\u67E5\u770B\u767B\u5F55\u7ED3\u679C\u3002" : "Return to the app to see the result."}</p></body></html>`;
@@ -58370,7 +58371,7 @@ function completionPage(language) {
58370
58371
  async function startTraeCallbackListener(onCallback, language = "zh") {
58371
58372
  const server = createServer((request, response) => {
58372
58373
  const url = request.url === void 0 ? "/" : request.url;
58373
- if (!url.startsWith("/authorize")) {
58374
+ if (!isCallbackTarget(url)) {
58374
58375
  response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
58375
58376
  response.end("not found");
58376
58377
  return;
@@ -61248,10 +61249,7 @@ async function engineeringTeamDecision(host, sessionId, request) {
61248
61249
  if (goalId !== void 0) host.engineering.armApprovedPlanGoal(agent);
61249
61250
  }
61250
61251
  agent.inject(createUserMessage({
61251
- source: {
61252
- kind: "plugin",
61253
- plugin: "freecodego-engine-council"
61254
- },
61252
+ source: { kind: "freecodego-engine-council" },
61255
61253
  content: [{
61256
61254
  type: "text",
61257
61255
  text: decision.state === "approved" ? goalId === void 0 ? "The user approved engineering council " + decision.id + ". You may implement the reviewed plan, then run council verification." : "The user approved engineering council " + decision.id + '. The approved plan is now goal "' + goalId + '", which the Harness continues automatically while it stays active. Work the plan to completion, then run council verification.' : "The user rejected engineering council " + decision.id + ". Do not implement this plan; revise it or start a new council."
@@ -61283,10 +61281,7 @@ async function engineeringTeamVerify(host, sessionId, request) {
61283
61281
  }
61284
61282
  await host.engineCouncil.recordVerification(agent, input.id, result);
61285
61283
  agent.inject(createUserMessage({
61286
- source: {
61287
- kind: "plugin",
61288
- plugin: "freecodego-engine-council"
61289
- },
61284
+ source: { kind: "freecodego-engine-council" },
61290
61285
  content: [{
61291
61286
  type: "text",
61292
61287
  text: "Engineering council " + input.id + " verification is complete. Read the durable verification result and report failed, skipped, unavailable, or cancelled stages accurately."
@@ -61302,10 +61297,7 @@ async function engineeringTeamImplementation(host, sessionId, request) {
61302
61297
  const implementation = await host.engineCouncil.recordImplementation(agent, request.id, request.summary);
61303
61298
  engineeringTeamVerify(host, sessionId, { id: request.id }).catch(() => void 0);
61304
61299
  agent.inject(createUserMessage({
61305
- source: {
61306
- kind: "plugin",
61307
- plugin: "freecodego-engine-council"
61308
- },
61300
+ source: { kind: "freecodego-engine-council" },
61309
61301
  content: [{
61310
61302
  type: "text",
61311
61303
  text: `Implementation for engineering council ${request.id} was marked complete. Executed verification (build/types/lint/tests) is starting automatically; read its durable result when reporting completion.`
@@ -61535,6 +61527,73 @@ var WorkBuddyPoolService = class {
61535
61527
  return results;
61536
61528
  }
61537
61529
  };
61530
+ var WEB_SEARCH_API_KEY_REF = credentialRef2("FREECODEGO_WEB_SEARCH_API_KEY");
61531
+ var WEB_SEARCH_SETTINGS_NAMESPACE = "web-search-deepseek";
61532
+ function messageOf(error) {
61533
+ return error instanceof Error ? error.message : String(error);
61534
+ }
61535
+ function classify(section, servesBridgeRoute) {
61536
+ if (section === void 0) return {
61537
+ state: "none",
61538
+ alive: true
61539
+ };
61540
+ if (asString(section.apiKeyEnv) !== WEB_SEARCH_API_KEY_REF) return {
61541
+ state: "other",
61542
+ alive: true
61543
+ };
61544
+ const saved = asString(section.baseURL);
61545
+ if (saved === void 0) return {
61546
+ state: "other",
61547
+ alive: true
61548
+ };
61549
+ const routeId = bridgeRouteIdOf(saved);
61550
+ return routeId === void 0 ? {
61551
+ state: "durable",
61552
+ alive: true
61553
+ } : {
61554
+ state: "bridge",
61555
+ alive: servesBridgeRoute(routeId)
61556
+ };
61557
+ }
61558
+ async function webSearchBindingStatus(deps) {
61559
+ const status = classify(await deps.readSection(), deps.servesBridgeRoute);
61560
+ return deps.remembered === void 0 ? status : {
61561
+ ...status,
61562
+ remembered: deps.remembered
61563
+ };
61564
+ }
61565
+ async function repairWebSearchBinding(deps) {
61566
+ let section;
61567
+ try {
61568
+ section = await deps.readSection();
61569
+ } catch (error) {
61570
+ return {
61571
+ outcome: "failed",
61572
+ detail: messageOf(error)
61573
+ };
61574
+ }
61575
+ const status = classify(section, deps.servesBridgeRoute);
61576
+ if (status.state === "none") return { outcome: "unavailable" };
61577
+ if (status.state === "other") return { outcome: "not-applicable" };
61578
+ if (status.alive) return { outcome: "live" };
61579
+ const remembered = deps.remembered;
61580
+ if (remembered === void 0) return { outcome: "no-memory" };
61581
+ try {
61582
+ const binding = await deps.bind(remembered);
61583
+ await deps.writeCredential(binding.apiKeyEnv, binding.apiKey);
61584
+ await deps.writeSection({
61585
+ model: binding.model,
61586
+ baseURL: binding.baseURL,
61587
+ apiKeyEnv: binding.apiKeyEnv
61588
+ });
61589
+ return { outcome: "rebuilt" };
61590
+ } catch (error) {
61591
+ return {
61592
+ outcome: "failed",
61593
+ detail: messageOf(error)
61594
+ };
61595
+ }
61596
+ }
61538
61597
  var FREECODEGO_CLOUD_ORIGIN = "https://freecodego.com";
61539
61598
  async function setDefaultEngine(host, engine) {
61540
61599
  if (engine !== "deepseek" && engine !== "codex" && engine !== "claude") throw new Error("agent engine must be deepseek, codex, or claude");
@@ -61587,9 +61646,9 @@ async function setDefaultModel(host, model) {
61587
61646
  return { model: persisted };
61588
61647
  }
61589
61648
  function defaultAgentOptions(host) {
61590
- const configuredEngine = host.policy.get()?.defaultEngine ?? host.config.defaultEngine ?? "deepseek";
61649
+ const configuredEngine = host.policy.get()?.defaultEngine ?? "deepseek";
61591
61650
  const engine = configuredEngine === "codex" || configuredEngine === "claude" ? configuredEngine : "deepseek";
61592
- const model = host.policy.get()?.defaultModel ?? host.config.defaultModel;
61651
+ const model = host.policy.get()?.defaultModel;
61593
61652
  const normalizedModel = model?.trim();
61594
61653
  if (normalizedModel !== void 0 && AGNES_TEXT_MODEL_IDS.has(normalizedModel)) return {
61595
61654
  engine,
@@ -61799,6 +61858,26 @@ async function sessionDelete(host, sessionId) {
61799
61858
  host.ctx.emit("api-session/removed", id);
61800
61859
  return { deleted: true };
61801
61860
  }
61861
+ async function webSearchBind(host, input) {
61862
+ if (input === null || typeof input !== "object") throw new Error("web search binding must be an object");
61863
+ const provider = typeof input.provider === "string" ? input.provider.trim().toLowerCase() : "";
61864
+ const model = typeof input.model === "string" ? input.model.trim() : "";
61865
+ if (provider === "" || model === "") throw new Error("a provider and a model are required");
61866
+ const route = await host.catalogs.claudeGatewayForRoute({
61867
+ provider,
61868
+ modelId: model
61869
+ });
61870
+ const base = route.baseURL.replace(/\/+$/u, "");
61871
+ if (!/^https?:\/\//iu.test(base)) throw new Error(`Provider "${provider}" did not resolve an HTTP endpoint`);
61872
+ return {
61873
+ provider,
61874
+ model: route.model ?? model,
61875
+ baseURL: /\/v1$/u.test(base) ? base : `${base}/v1`,
61876
+ apiKeyEnv: WEB_SEARCH_API_KEY_REF,
61877
+ apiKey: route.apiKey,
61878
+ durable: !/^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(?::\d+)?(?:\/|$)/iu.test(base)
61879
+ };
61880
+ }
61802
61881
  async function capabilitiesSnapshot(host) {
61803
61882
  return host.capabilities.snapshot();
61804
61883
  }
@@ -62157,9 +62236,7 @@ function configuredProviderRoute(ctx, model) {
62157
62236
  model: model.slice(provider.length + 1)
62158
62237
  };
62159
62238
  }
62160
- const settings = ctx.get("settings");
62161
- if (typeof settings?.get !== "function") return void 0;
62162
- const providers = asRecord(asRecord(settings.get("llm-pi-ai")).providers);
62239
+ const providers = asRecord(asRecord(peerSettings(ctx, MODELS_SETTINGS_ENTRY)).providers);
62163
62240
  for (const [provider, profile] of Object.entries(providers)) {
62164
62241
  const row = asRecord(profile);
62165
62242
  if ((Array.isArray(row.models) ? row.models : []).some((entry) => asRecord(entry).id === model || String(asRecord(entry).id ?? "").toLowerCase() === model.toLowerCase())) return {
@@ -62443,7 +62520,7 @@ var MEMORY_CASES = [
62443
62520
  suite: "memory",
62444
62521
  claim: "A shared file is preferred over a shared session, because it is the actionable link.",
62445
62522
  measure: () => {
62446
- const classify = (sharedFile, sameTurn, sameSession) => sharedFile !== void 0 ? "shared-file" : sameTurn ? "same-turn" : sameSession ? "same-session" : "none";
62523
+ const classify2 = (sharedFile, sameTurn, sameSession) => sharedFile !== void 0 ? "shared-file" : sameTurn ? "same-turn" : sameSession ? "same-session" : "none";
62447
62524
  const cases = [
62448
62525
  [
62449
62526
  "shared-file",
@@ -62470,7 +62547,7 @@ var MEMORY_CASES = [
62470
62547
  true
62471
62548
  ]
62472
62549
  ];
62473
- const correct = cases.filter(([expected, file, turn, session]) => classify(file, turn, session) === expected).length;
62550
+ const correct = cases.filter(([expected, file, turn, session]) => classify2(file, turn, session) === expected).length;
62474
62551
  return {
62475
62552
  observed: correct / cases.length,
62476
62553
  required: 1,
@@ -62793,28 +62870,43 @@ var WIRE_CASES = [
62793
62870
  {
62794
62871
  id: "wire.tool-results-stay-adjacent",
62795
62872
  suite: "wire",
62796
- claim: "Tool results immediately follow their assistant frame; trailing text becomes a later user turn.",
62873
+ claim: "A tool result is its own tool-role message and follows the assistant frame that asked for it, with later text as a user turn.",
62797
62874
  measure: () => {
62798
62875
  const roles = wireMessages(serializeRequest({
62799
62876
  provider: "logfare",
62800
62877
  model: "gpt-5.6-sol",
62801
- messages: [createUserMessage({
62802
- source: { kind: "user" },
62803
- content: [{
62804
- type: "text",
62805
- text: "Continue the discussion."
62806
- }, {
62807
- type: "tool-result",
62808
- toolCallId: ToolCallId("call-1"),
62878
+ messages: [
62879
+ createAssistantMessage2({
62880
+ source: {
62881
+ provider: "logfare",
62882
+ model: "gpt-5.6-sol"
62883
+ },
62884
+ content: [{
62885
+ type: "tool-call",
62886
+ id: ToolCallId("call-1"),
62887
+ name: "read",
62888
+ arguments: '{"path":"a"}'
62889
+ }]
62890
+ }),
62891
+ createToolResultMessage2({
62892
+ callId: ToolCallId("call-1"),
62809
62893
  content: [{
62810
62894
  type: "text",
62811
62895
  text: "ok"
62896
+ }],
62897
+ isError: false
62898
+ }),
62899
+ createUserMessage({
62900
+ source: { kind: "user" },
62901
+ content: [{
62902
+ type: "text",
62903
+ text: "Continue the discussion."
62812
62904
  }]
62813
- }]
62814
- })]
62905
+ })
62906
+ ]
62815
62907
  })).map((message2) => message2.role);
62816
62908
  return {
62817
- observed: roles.join(",") === "tool,user" ? 1 : 0,
62909
+ observed: roles.join(",") === "assistant,tool,user" ? 1 : 0,
62818
62910
  required: 1,
62819
62911
  detail: `roles=${roles.join(",")}`
62820
62912
  };
@@ -62835,16 +62927,13 @@ var WIRE_CASES = [
62835
62927
  const first = wireMessages(serializeRequest({
62836
62928
  provider: "logfare",
62837
62929
  model: "gpt-5.6-sol",
62838
- messages: [createUserMessage({
62839
- source: { kind: "user" },
62930
+ messages: [createToolResultMessage2({
62931
+ callId: ToolCallId("call-1"),
62840
62932
  content: [{
62841
- type: "tool-result",
62842
- toolCallId: ToolCallId("call-1"),
62843
- content: [{
62844
- type: "image",
62845
- attachment
62846
- }]
62847
- }]
62933
+ type: "image",
62934
+ attachment
62935
+ }],
62936
+ isError: false
62848
62937
  })]
62849
62938
  }))[0];
62850
62939
  return {
@@ -65897,23 +65986,42 @@ var ADAPTER_CASES = [
65897
65986
  }
65898
65987
  }
65899
65988
  ];
65989
+ function routingSettings(value, onWrite = () => void 0) {
65990
+ return {
65991
+ describe: () => [{
65992
+ ns: "subagent-model-selection",
65993
+ value,
65994
+ revision: 3
65995
+ }],
65996
+ update: async (_ns, patch) => {
65997
+ onWrite(patch);
65998
+ }
65999
+ };
66000
+ }
66001
+ var NO_ROUTING_SETTINGS = {
66002
+ describe: () => [],
66003
+ update: async () => void 0
66004
+ };
65900
66005
  var ROUTING_CASES = [
65901
66006
  {
65902
66007
  id: "routing.sync-needs-a-settings-namespace",
65903
66008
  suite: "routing",
65904
- claim: "A composition without the subagent settings namespace syncs nothing.",
66009
+ claim: "A composition that declares no Subagent model-selection entry syncs nothing and writes nothing.",
65905
66010
  measure: async () => {
66011
+ let writes = 0;
65906
66012
  const result = await synchronizeSubagentModelRoutes({
65907
- get: () => void 0,
65908
- update: async () => void 0
66013
+ ...NO_ROUTING_SETTINGS,
66014
+ update: async () => {
66015
+ writes += 1;
66016
+ }
65909
66017
  }, {
65910
66018
  listProviders: () => [{ id: "p" }],
65911
66019
  listModels: async () => [{ id: "m" }]
65912
66020
  });
65913
66021
  return {
65914
- observed: result.length === 0 ? 1 : 0,
66022
+ observed: result.length === 0 && writes === 0 ? 1 : 0,
65915
66023
  required: 1,
65916
- detail: `routes=${result.length}`
66024
+ detail: `routes=${result.length} writes=${writes}`
65917
66025
  };
65918
66026
  }
65919
66027
  },
@@ -65923,15 +66031,12 @@ var ROUTING_CASES = [
65923
66031
  claim: "Unavailable and non-text models are excluded, and duplicates are collapsed.",
65924
66032
  measure: async () => {
65925
66033
  const written = [];
65926
- const ids = (await synchronizeSubagentModelRoutes({
65927
- get: () => ({
65928
- allowedModels: [],
65929
- enabled: void 0
65930
- }),
65931
- update: async (_ns, value) => {
65932
- written.push(value);
65933
- }
65934
- }, {
66034
+ const ids = (await synchronizeSubagentModelRoutes(routingSettings({
66035
+ allowedModels: [],
66036
+ enabled: void 0
66037
+ }, (patch) => {
66038
+ written.push(patch);
66039
+ }), {
65935
66040
  listProviders: () => [{ id: "p" }],
65936
66041
  listModels: async () => [
65937
66042
  {
@@ -65972,27 +66077,23 @@ var ROUTING_CASES = [
65972
66077
  suite: "routing",
65973
66078
  claim: "A provider that answered with nothing loses its stale routes; one that failed keeps them.",
65974
66079
  measure: async () => {
65975
- const previous = [
65976
- {
65977
- provider: "answered",
65978
- model: "stale"
65979
- },
65980
- {
65981
- provider: "failed",
65982
- model: "keep"
65983
- },
65984
- {
65985
- provider: "removed",
65986
- model: "drop"
65987
- }
65988
- ];
65989
- const ids = (await synchronizeSubagentModelRoutes({
65990
- get: () => ({
65991
- allowedModels: previous,
65992
- enabled: true
65993
- }),
65994
- update: async () => void 0
65995
- }, {
66080
+ const ids = (await synchronizeSubagentModelRoutes(routingSettings({
66081
+ allowedModels: [
66082
+ {
66083
+ provider: "answered",
66084
+ model: "stale"
66085
+ },
66086
+ {
66087
+ provider: "failed",
66088
+ model: "keep"
66089
+ },
66090
+ {
66091
+ provider: "removed",
66092
+ model: "drop"
66093
+ }
66094
+ ],
66095
+ enabled: true
66096
+ }), {
65996
66097
  listProviders: () => [
65997
66098
  { id: "answered" },
65998
66099
  { id: "failed" },
@@ -66022,15 +66123,12 @@ var ROUTING_CASES = [
66022
66123
  measure: async () => {
66023
66124
  const writes = [];
66024
66125
  const run2 = async (enabled) => {
66025
- await synchronizeSubagentModelRoutes({
66026
- get: () => ({
66027
- allowedModels: [],
66028
- enabled
66029
- }),
66030
- update: async (_ns, value) => {
66031
- writes.push(value);
66032
- }
66033
- }, {
66126
+ await synchronizeSubagentModelRoutes(routingSettings({
66127
+ allowedModels: [],
66128
+ enabled
66129
+ }, (patch) => {
66130
+ writes.push(patch);
66131
+ }), {
66034
66132
  listProviders: () => [{ id: "p" }],
66035
66133
  listModels: async () => [{
66036
66134
  id: "m",
@@ -66056,18 +66154,15 @@ var ROUTING_CASES = [
66056
66154
  claim: "An unchanged catalog does not rewrite the settings document.",
66057
66155
  measure: async () => {
66058
66156
  let updates = 0;
66059
- await synchronizeSubagentModelRoutes({
66060
- get: () => ({
66061
- allowedModels: [{
66062
- provider: "p",
66063
- model: "m"
66064
- }],
66065
- enabled: true
66066
- }),
66067
- update: async () => {
66068
- updates += 1;
66069
- }
66070
- }, {
66157
+ await synchronizeSubagentModelRoutes(routingSettings({
66158
+ allowedModels: [{
66159
+ provider: "p",
66160
+ model: "m"
66161
+ }],
66162
+ enabled: true
66163
+ }, () => {
66164
+ updates += 1;
66165
+ }), {
66071
66166
  listProviders: () => [{ id: "p" }],
66072
66167
  listModels: async () => [{
66073
66168
  id: "m",
@@ -67745,10 +67840,6 @@ function reportCommandPolicyDiagnostics(ctx, policy) {
67745
67840
  commandPolicyDiagnosticsReported = true;
67746
67841
  ctx.logger?.warn?.(`freecodego: the built-in command policy compiled with ${policy.diagnostics.length} diagnostic(s); rules that failed their own examples were dropped and are no longer enforced \u2014 ${describePolicyDiagnostics(policy.diagnostics)}`);
67747
67842
  }
67748
- function bootstrapFreeCodeGoHarness(ctx) {
67749
- registerFreeCodeGoSessionEventTypes();
67750
- return installFreeCodeGoPluginConflictGuard(ctx);
67751
- }
67752
67843
  function memoryPipelineHome(cwd) {
67753
67844
  const identity = path.resolve(cwd).replaceAll("\\", "/").toLowerCase();
67754
67845
  const segment = createHash3("sha256").update(identity).digest("hex").slice(0, 24);
@@ -67837,6 +67928,8 @@ var FreeCodeGoHarnessPlugin = (() => {
67837
67928
  let _headroomStatus_decorators;
67838
67929
  let _deferredToolsStatus_decorators;
67839
67930
  let _deferredToolsSetEnabled_decorators;
67931
+ let _mediaGenerationStatus_decorators;
67932
+ let _mediaGenerationSetEnabled_decorators;
67840
67933
  let _lspMountStatus_decorators;
67841
67934
  let _sandboxModeStatus_decorators;
67842
67935
  let _sandboxModeSet_decorators;
@@ -67873,6 +67966,8 @@ var FreeCodeGoHarnessPlugin = (() => {
67873
67966
  let _engineeringCodeGraphCancel_decorators;
67874
67967
  let _engineeringCodeGraphClearProject_decorators;
67875
67968
  let _sessionDelete_decorators;
67969
+ let _webSearchBind_decorators;
67970
+ let _webSearchBindingStatus_decorators;
67876
67971
  let _capabilitiesSnapshot_decorators;
67877
67972
  let _capabilitiesSetEnabled_decorators;
67878
67973
  let _modelCategorySet_decorators;
@@ -68059,6 +68154,8 @@ var FreeCodeGoHarnessPlugin = (() => {
68059
68154
  _headroomStatus_decorators = [Remote("headroomStatus")];
68060
68155
  _deferredToolsStatus_decorators = [Remote("deferredToolsStatus")];
68061
68156
  _deferredToolsSetEnabled_decorators = [Remote("deferredToolsSetEnabled")];
68157
+ _mediaGenerationStatus_decorators = [Remote("mediaGenerationStatus")];
68158
+ _mediaGenerationSetEnabled_decorators = [Remote("mediaGenerationSetEnabled")];
68062
68159
  _lspMountStatus_decorators = [Remote("lspMountStatus")];
68063
68160
  _sandboxModeStatus_decorators = [Remote("sandboxModeStatus")];
68064
68161
  _sandboxModeSet_decorators = [Remote("sandboxModeSet")];
@@ -68095,6 +68192,8 @@ var FreeCodeGoHarnessPlugin = (() => {
68095
68192
  _engineeringCodeGraphCancel_decorators = [Remote("engineeringCodeGraphCancel")];
68096
68193
  _engineeringCodeGraphClearProject_decorators = [Remote("engineeringCodeGraphClearProject")];
68097
68194
  _sessionDelete_decorators = [Remote("sessionDelete")];
68195
+ _webSearchBind_decorators = [Remote("webSearchBind")];
68196
+ _webSearchBindingStatus_decorators = [Remote("webSearchBindingStatus")];
68098
68197
  _capabilitiesSnapshot_decorators = [Remote("capabilities")];
68099
68198
  _capabilitiesSetEnabled_decorators = [Remote("capabilitiesSetEnabled")];
68100
68199
  _modelCategorySet_decorators = [Remote("modelCategorySet")];
@@ -68788,6 +68887,28 @@ var FreeCodeGoHarnessPlugin = (() => {
68788
68887
  },
68789
68888
  metadata: _metadata
68790
68889
  }, null, _instanceExtraInitializers);
68890
+ __esDecorate(this, null, _mediaGenerationStatus_decorators, {
68891
+ kind: "method",
68892
+ name: "mediaGenerationStatus",
68893
+ static: false,
68894
+ private: false,
68895
+ access: {
68896
+ has: (obj) => "mediaGenerationStatus" in obj,
68897
+ get: (obj) => obj.mediaGenerationStatus
68898
+ },
68899
+ metadata: _metadata
68900
+ }, null, _instanceExtraInitializers);
68901
+ __esDecorate(this, null, _mediaGenerationSetEnabled_decorators, {
68902
+ kind: "method",
68903
+ name: "mediaGenerationSetEnabled",
68904
+ static: false,
68905
+ private: false,
68906
+ access: {
68907
+ has: (obj) => "mediaGenerationSetEnabled" in obj,
68908
+ get: (obj) => obj.mediaGenerationSetEnabled
68909
+ },
68910
+ metadata: _metadata
68911
+ }, null, _instanceExtraInitializers);
68791
68912
  __esDecorate(this, null, _lspMountStatus_decorators, {
68792
68913
  kind: "method",
68793
68914
  name: "lspMountStatus",
@@ -69184,6 +69305,28 @@ var FreeCodeGoHarnessPlugin = (() => {
69184
69305
  },
69185
69306
  metadata: _metadata
69186
69307
  }, null, _instanceExtraInitializers);
69308
+ __esDecorate(this, null, _webSearchBind_decorators, {
69309
+ kind: "method",
69310
+ name: "webSearchBind",
69311
+ static: false,
69312
+ private: false,
69313
+ access: {
69314
+ has: (obj) => "webSearchBind" in obj,
69315
+ get: (obj) => obj.webSearchBind
69316
+ },
69317
+ metadata: _metadata
69318
+ }, null, _instanceExtraInitializers);
69319
+ __esDecorate(this, null, _webSearchBindingStatus_decorators, {
69320
+ kind: "method",
69321
+ name: "webSearchBindingStatus",
69322
+ static: false,
69323
+ private: false,
69324
+ access: {
69325
+ has: (obj) => "webSearchBindingStatus" in obj,
69326
+ get: (obj) => obj.webSearchBindingStatus
69327
+ },
69328
+ metadata: _metadata
69329
+ }, null, _instanceExtraInitializers);
69187
69330
  __esDecorate(this, null, _capabilitiesSnapshot_decorators, {
69188
69331
  kind: "method",
69189
69332
  name: "capabilitiesSnapshot",
@@ -70654,40 +70797,336 @@ var FreeCodeGoHarnessPlugin = (() => {
70654
70797
  "sessionPersistence"
70655
70798
  ];
70656
70799
  /**
70657
- * Runtime schema for {@link Config}, declared inline so the generated config
70658
- * catalog can walk it: it is the only statically resolvable form a
70659
- * plugin-class schema may take. Schemastery has no first-class `.optional()`;
70660
- * the public `Config` type already types these fields as optional, so the
70661
- * schema carries the same contract at runtime (absent input passes through)
70662
- * without fighting the inferred builder types.
70800
+ * Runtime schema for {@link Config}.
70801
+ *
70802
+ * One flat `z.object({...})` literal, and that shape is load-bearing: the generated
70803
+ * config catalog walks this expression *statically* — it resolves a local `const` or
70804
+ * a workspace-package import and refuses anything else — and the readers gate in
70805
+ * `scripts/freecodego-config-readers.spec.ts` parses the same literal to check that
70806
+ * every field it declares is read somewhere. The document therefore cannot be
70807
+ * assembled from the group schemas' own modules, even though those modules own the
70808
+ * *types*: an `intersect` of relative imports is invisible to both walks, and a
70809
+ * schema nothing walks is a schema nothing checks.
70810
+ *
70811
+ * Which fields are which
70812
+ * ----------------------
70813
+ * The first block is deployment input: ordinary fields, no `volatile()`, and a change
70814
+ * to one of them is a composition change that remounts the plugin. Every field below
70815
+ * it is a setting — `volatile()` is what the loader reads as "commit this into the
70816
+ * running references instead of remounting", and it is also what puts the field in
70817
+ * the settings form. {@link FreeCodeGoPolicy} is the only reader of any of them; the
70818
+ * section comments name the module that owns each group's type.
70663
70819
  */
70664
70820
  static Config = z2.object({
70665
70821
  gateway: z2.object({ baseUrl: z2.string().default(FREECODEGO_CLOUD_ORIGIN) }),
70666
- defaultModel: z2.string(),
70667
- defaultEngine: z2.union([
70668
- z2.const("deepseek"),
70669
- z2.const("codex"),
70670
- z2.const("claude")
70671
- ]).default("deepseek"),
70672
70822
  codexRuntimeDirectory: z2.string().default(""),
70673
70823
  codexRuntimeSourceDirectory: z2.string().default(""),
70674
70824
  claudeRuntimeDirectory: z2.string().default(""),
70675
70825
  autoSubagentModelSelection: z2.boolean().default(true),
70676
- autoAdvisorEnabled: z2.boolean().default(true),
70677
70826
  updatePackageName: z2.string().default(""),
70678
70827
  updateReleaseRepository: z2.string().default(""),
70679
- updateReleaseTokenEnv: z2.string().default("")
70828
+ updateReleaseTokenEnv: z2.string().default(""),
70829
+ mcpEnabled: z2.boolean().default(true).volatile(),
70830
+ skillEnabled: z2.boolean().default(true).volatile(),
70831
+ voiceInputEnabled: z2.boolean().default(true).volatile(),
70832
+ sessionDeleteEnabled: z2.boolean().default(true).volatile(),
70833
+ modelCategories: z2.dict(z2.union([
70834
+ z2.const("text"),
70835
+ z2.const("image"),
70836
+ z2.const("video"),
70837
+ z2.const("audio")
70838
+ ])).default({}).volatile(),
70839
+ webSearchBindingProvider: z2.string().default("").volatile(),
70840
+ webSearchBindingModel: z2.string().default("").volatile(),
70841
+ mcpServers: z2.array(z2.object({
70842
+ id: z2.string().min(1).max(80),
70843
+ enabled: z2.boolean().default(true),
70844
+ transport: z2.union([z2.const("stdio"), z2.const("streamable-http")]),
70845
+ serverName: z2.string().pattern(SERVER_NAME$1),
70846
+ command: z2.string().default(""),
70847
+ args: z2.array(z2.string().max(4096)).default([]),
70848
+ env: z2.dict(z2.string()).default({}),
70849
+ cwd: z2.string().default(""),
70850
+ url: z2.string().default(""),
70851
+ headers: z2.dict(z2.string()).default({})
70852
+ })).default([]).volatile(),
70853
+ skillRoots: z2.array(z2.object({
70854
+ id: z2.string().min(1).max(80),
70855
+ enabled: z2.boolean().default(true),
70856
+ path: z2.string().min(1).max(4096)
70857
+ })).default([]).volatile(),
70858
+ skillInvocationOverrides: z2.dict(z2.boolean()).default({}).volatile(),
70859
+ preferredSkillPlacement: z2.union([z2.object({
70860
+ agent: z2.union([z2.const("harness"), z2.const("agents")]),
70861
+ scope: z2.union([z2.const("project"), z2.const("user")])
70862
+ }), z2.const(null)]).volatile(),
70863
+ envReadGuardEnabled: z2.boolean().default(true).volatile(),
70864
+ doomLoopGuardEnabled: z2.boolean().default(true).volatile(),
70865
+ lspEnabled: z2.boolean().default(true).volatile(),
70866
+ commandPolicyEnabled: z2.boolean().default(true).volatile(),
70867
+ planModeEnabled: z2.boolean().default(true).volatile(),
70868
+ contextBudgetEnabled: z2.boolean().default(true).volatile(),
70869
+ cacheColdClearEnabled: z2.boolean().default(true).volatile(),
70870
+ cacheBreakAttributionEnabled: z2.boolean().default(true).volatile(),
70871
+ assistantLoopGuardEnabled: z2.boolean().default(true).volatile(),
70872
+ spillRecallEnabled: z2.boolean().default(true).volatile(),
70873
+ compactionFidelityEnabled: z2.boolean().default(true).volatile(),
70874
+ promptCompositionEnabled: z2.boolean().default(true).volatile(),
70875
+ /**
70876
+ * Master switch for the whole gate.
70877
+ *
70878
+ * Off means every project-scoped surface is treated as trusted — the
70879
+ * pre-gate behaviour, kept reachable so a deployment that already controls
70880
+ * which repositories it opens is not forced to grant each one. It is
70881
+ * deliberately not the default: the safe state must not be the opt-in one.
70882
+ */
70883
+ folderTrustEnabled: z2.boolean().default(true).volatile(),
70884
+ pluginConflictProtectionEnabled: z2.boolean().default(true).volatile(),
70885
+ pluginConflictRecords: z2.array(z2.object({
70886
+ id: z2.string().min(1).max(256),
70887
+ detectedAt: z2.number(),
70888
+ resource: z2.union([
70889
+ z2.const("tool"),
70890
+ z2.const("command"),
70891
+ z2.const("settings"),
70892
+ z2.const("route"),
70893
+ z2.const("provider"),
70894
+ z2.const("slot")
70895
+ ]),
70896
+ resourceName: z2.string().min(1).max(256),
70897
+ disabledEntryId: z2.string().min(1).max(512),
70898
+ disabledModuleName: z2.string().min(1).max(512),
70899
+ keptEntryId: z2.string().min(1).max(512),
70900
+ keptModuleName: z2.string().min(1).max(512),
70901
+ yieldedToOfficial: z2.boolean()
70902
+ })).default([]).volatile(),
70903
+ pluginUpdateChecksEnabled: z2.boolean().default(true).volatile(),
70904
+ engineeringEnabled: z2.boolean().default(true).volatile(),
70905
+ /**
70906
+ * The rest of the bundled engineering library is opt-in: 23 audited Skills
70907
+ * (the remainder of the authored set plus the vendored `mattpocock/skills`
70908
+ * entries that are not in the default-on starter root) stay unmounted until
70909
+ * the user turns this on.
70910
+ */
70911
+ engineeringSkillsEnabled: z2.boolean().default(false).volatile(),
70912
+ /**
70913
+ * The starter set is on by default: ten Skills whose value does not depend on
70914
+ * adopting a whole methodology. Four are disciplines the model applies on its
70915
+ * own (evidence before completion claims, navigation before broad reading,
70916
+ * planning multi-file work, root-cause debugging); the rest are `/name`
70917
+ * entries a user reaches for on demand and that cost nothing until typed
70918
+ * (a prompt-technique reference, the grilling interview and its primitive, a
70919
+ * re-pitch escape hatch, a handoff document, and a questionnaire for
70920
+ * decisions the agent cannot settle). They live in their own asset root so
70921
+ * "all engineering Skills" stays the union of the roots — no skill file is
70922
+ * duplicated, and no audit can disagree with a mount.
70923
+ */
70924
+ engineeringStarterSkillsEnabled: z2.boolean().default(true).volatile(),
70925
+ /**
70926
+ * The vendored superpowers workflow pack is a separate opt-in: it is the
70927
+ * auto-triggering, plan-then-dispatch methodology, so it must be a deliberate
70928
+ * choice rather than a side effect of enabling the engineering disciplines.
70929
+ */
70930
+ engineeringSuperpowersSkillsEnabled: z2.boolean().default(false).volatile(),
70931
+ /**
70932
+ * Injects a bounded capability map of the mounted Skills at session start.
70933
+ * A default-off Skill library is invisible otherwise: the model never lists
70934
+ * it, and a user who turned a pack on has no way to see what arrived.
70935
+ */
70936
+ engineeringSkillMapEnabled: z2.boolean().default(true).volatile(),
70937
+ /** Enables bounded project-declared verification after an approved council plan. */
70938
+ engineeringQualityEnabled: z2.boolean().default(true).volatile(),
70939
+ engineeringMemoryEnabled: z2.boolean().default(true).volatile(),
70940
+ engineeringCouncilEnabled: z2.boolean().default(true).volatile(),
70941
+ engineeringCouncilDeepseekEnabled: z2.boolean().default(true).volatile(),
70942
+ engineeringCouncilCodexEnabled: z2.boolean().default(true).volatile(),
70943
+ engineeringCouncilClaudeEnabled: z2.boolean().default(true).volatile(),
70944
+ engineeringMemoryContextTokenBudget: z2.number().step(1).min(0).max(4e3).default(1200).volatile(),
70945
+ /**
70946
+ * Default off, deliberately: the lexical rerank is free, deterministic, and
70947
+ * already ranks by term coverage. Flipping this on spends one small model
70948
+ * request per memory search to catch the memories that share no wording with
70949
+ * the query — worth it for a user who searches by concept, wasted for one who
70950
+ * greps for identifiers.
70951
+ */
70952
+ engineeringMemorySelectorEnabled: z2.boolean().default(false).volatile(),
70953
+ /**
70954
+ * Default off, deliberately: this lets a model approve a pending action without
70955
+ * asking the user. Off means every approval keeps prompting. On means an action
70956
+ * whose arguments the reviewer can actually read may be cleared automatically —
70957
+ * anything it refuses, cannot read, or declines to judge still goes to the user.
70958
+ */
70959
+ engineeringActionReviewEnabled: z2.boolean().default(false).volatile(),
70960
+ engineeringCodeGraphEnabled: z2.boolean().default(true).volatile(),
70961
+ engineeringCodeGraphAutoUpdate: z2.boolean().default(true).volatile(),
70962
+ engineeringGraphEngine: z2.union([
70963
+ z2.const("auto"),
70964
+ z2.const("graphify"),
70965
+ z2.const("codegraph")
70966
+ ]).default("auto").volatile(),
70967
+ engineeringCouncilMaxRounds: z2.number().step(1).min(1).max(3).default(2).volatile(),
70968
+ engineeringCouncilTimeoutMs: z2.number().step(1).min(1e4).max(3e5).default(12e4).volatile(),
70969
+ engineeringCouncilQuorum: z2.number().step(1).min(1).max(3).default(2).volatile(),
70970
+ engineeringCouncilAutoRun: z2.boolean().default(false).volatile(),
70971
+ /**
70972
+ * The three phases of the autonomous engineering loop. Each is a separate
70973
+ * switch because they carry different risk: creating a goal only commits the
70974
+ * user to finishing, while enabling the driver lets work continue with no
70975
+ * human turn in between.
70976
+ *
70977
+ * - `engineeringLoopCapturePlan` — when the user approves a council decision,
70978
+ * persist the approved plan as a durable Harness goal.
70979
+ * - `engineeringLoopVerifyOnComplete` — when the active goal's driver reports
70980
+ * it finished, run the declared verification stages and record the result.
70981
+ * - `engineeringLoopAutoContinue` — let the Harness goal-round driver start
70982
+ * the next round without a user turn. Off by default: a goal that continues
70983
+ * unattended is the one behaviour a user must opt into explicitly.
70984
+ */
70985
+ engineeringLoopCapturePlan: z2.boolean().default(true).volatile(),
70986
+ engineeringLoopVerifyOnComplete: z2.boolean().default(true).volatile(),
70987
+ engineeringLoopAutoContinue: z2.boolean().default(false).volatile(),
70988
+ /** Round cap for a captured plan goal; bounds unattended continuation. */
70989
+ engineeringLoopMaxGoalRounds: z2.number().step(1).min(1).max(256).default(24).volatile(),
70990
+ engineeringCouncilMaxTokens: z2.number().step(100).min(1200).max(2e4).default(3600).volatile(),
70991
+ engineeringCouncilMaxConcurrent: z2.number().step(1).min(1).max(8).default(2).volatile(),
70992
+ engineeringCouncilDecisionTtlMs: z2.number().step(6e4).min(6e4).max(10080 * 6e4).default(30 * 6e4).volatile(),
70993
+ /** Master switch for declarative failure recovery. */
70994
+ hookChainsEnabled: z2.boolean().default(true).volatile(),
70995
+ hookChainsMaxDepth: z2.number().step(1).min(0).max(10).default(2).volatile(),
70996
+ hookChainsCooldownMs: z2.number().step(1e3).min(0).max(1440 * 60 * 1e3).default(3e4).volatile(),
70997
+ /** Master switch for the calendar planner. */
70998
+ scheduledTasksEnabled: z2.boolean().default(true).volatile(),
70999
+ defaultModel: z2.string().default("").volatile(),
71000
+ defaultEngine: z2.union([
71001
+ z2.const("deepseek"),
71002
+ z2.const("codex"),
71003
+ z2.const("claude")
71004
+ ]).default("deepseek").volatile(),
71005
+ mediaDefaults: z2.object({
71006
+ image: z2.string().default(""),
71007
+ video: z2.string().default(""),
71008
+ audio: z2.string().default("")
71009
+ }).default({
71010
+ image: "",
71011
+ video: "",
71012
+ audio: ""
71013
+ }).volatile(),
71014
+ /**
71015
+ * Master switch for the image and video generation tools.
71016
+ *
71017
+ * On by default: the tools spend nothing until a route is called, and they are the
71018
+ * only way the capability is reachable — a feature that has to be found before it
71019
+ * does anything is why the media surface looked absent. It governs the generic
71020
+ * `freecodego_generate_image` / `_video` pair and the legacy `agnes_*` aliases with
71021
+ * them; `freecodego_generate_audio` and `freecodego_transcribe_audio` are
71022
+ * deliberately outside it, because one writes into the active workspace and the
71023
+ * other reads out of it.
71024
+ */
71025
+ mediaGenerationEnabled: z2.boolean().default(true).volatile(),
71026
+ /**
71027
+ * Sandbox profile deny globs, enforced in the plugin policy layer.
71028
+ *
71029
+ * Empty by default: a deny list is containment the user asked for, and
71030
+ * inventing one would refuse paths nobody named.
71031
+ */
71032
+ sandboxDenyPatterns: z2.array(z2.string()).default([]).volatile(),
71033
+ rehydrationEnabled: z2.boolean().default(true).volatile(),
71034
+ /**
71035
+ * Conversation-arc rehydration (goals + decisions folded from the
71036
+ * session's own goal events, promoted memories, and announced
71037
+ * choices). Off by default: it changes what the post-compaction
71038
+ * message contains, and a user must opt into new prompt surface —
71039
+ * the mechanical fold is bounded, but its wording is still model-
71040
+ * visible behavior.
71041
+ */
71042
+ rehydrationArcEnabled: z2.boolean().default(false).volatile(),
71043
+ advisorEnabled: z2.boolean().default(true).volatile(),
71044
+ advisorMode: z2.union([
71045
+ z2.const("async"),
71046
+ z2.const("catchup"),
71047
+ z2.const("blocker-only")
71048
+ ]).default("async").volatile(),
71049
+ advisorProvider: z2.string().default("opencode").volatile(),
71050
+ advisorModel: z2.string().default("auto").volatile(),
71051
+ advisorAllowAgentControl: z2.boolean().default(true).volatile(),
71052
+ advisorInterruptCooldownTurns: z2.number().step(1).min(0).max(20).default(3).volatile(),
71053
+ advisorMemoryDraftsEnabled: z2.boolean().default(true).volatile(),
71054
+ reviewMode: z2.union([
71055
+ z2.const("off"),
71056
+ z2.const("record"),
71057
+ z2.const("gate")
71058
+ ]).default("off").volatile(),
71059
+ reviewThreshold: z2.union([
71060
+ z2.const("critical"),
71061
+ z2.const("high"),
71062
+ z2.const("medium"),
71063
+ z2.const("low")
71064
+ ]).default("high").volatile(),
71065
+ reviewCooldownTurns: z2.number().step(1).min(0).max(20).default(3).volatile(),
71066
+ reviewDeep: z2.boolean().default(false).volatile(),
71067
+ reviewEscalation: z2.boolean().default(false).volatile(),
71068
+ headroomEnabled: z2.boolean().default(true).volatile(),
71069
+ headroomThresholdChars: z2.number().step(1).min(256).max(1e6).default(1200).volatile(),
71070
+ headroomMinSavingsRatio: z2.number().min(0.05).max(0.95).default(0.85).volatile(),
71071
+ headroomDedupEnabled: z2.boolean().default(true).volatile(),
71072
+ /**
71073
+ * Tools to protect *in addition* to the built-in list.
71074
+ *
71075
+ * Additional, not instead of: the built-in set
71076
+ * (`DEFAULT_EXCLUDE_TOOLS`) is a safety guarantee — those outputs are
71077
+ * what the model byte-patches against — and this field defaults to an
71078
+ * empty array, so a reading where it replaced the built-ins would make
71079
+ * the default install protect nothing.
71080
+ */
71081
+ headroomExcludeTools: z2.array(z2.string()).default([]).volatile(),
71082
+ headroomFoldReads: z2.boolean().default(false).volatile(),
71083
+ /**
71084
+ * Skeletonize large source-file reads: retained lines stay byte-exact
71085
+ * while whole body runs collapse to a marker. On by default because
71086
+ * `read` is the largest single source of re-sent context; the original
71087
+ * is always retrievable through `headroom_retrieve`.
71088
+ */
71089
+ headroomCodeSkeletonEnabled: z2.boolean().default(true).volatile(),
71090
+ /**
71091
+ * Reversible-render competition policy. `reversible` (default) delivers a
71092
+ * render that clears `FOLD_DECISIVE_RATIO` on sight — a Stage 2 fold, or a
71093
+ * mixed-content splice; `max` demotes every one of them to a candidate so
71094
+ * the compressor written for the payload's shape is always asked first.
71095
+ * The two settings are two products, not a fine-tuning — see
71096
+ * `HeadroomRuntime.foldPolicy` for the measured difference on the shapes
71097
+ * where they disagree.
71098
+ */
71099
+ headroomFoldPolicy: z2.union([z2.const("reversible"), z2.const("max")]).default("reversible").volatile(),
71100
+ /**
71101
+ * Keep task-specific tool schemas out of the request until the model
71102
+ * asks for them with `tool_search`. Measured saving is ~3.2k tokens per
71103
+ * request on this plugin's own 37 tools alone.
71104
+ */
71105
+ deferredToolSchemasEnabled: z2.boolean().default(true).volatile(),
71106
+ /** Explicit deferral list; when empty the plugin-owned prefix rule applies. */
71107
+ deferredToolNames: z2.array(z2.string()).default([]).volatile(),
71108
+ headroomOutputShaper: z2.boolean().default(false).volatile(),
71109
+ headroomVerbosityLevel: z2.number().step(1).min(0).max(4).default(0).volatile(),
71110
+ /**
71111
+ * Memory consolidation rollout (`memory/rollout.ts`).
71112
+ *
71113
+ * A stage rather than a switch because consolidating runs a model over
71114
+ * the session's successful turns: `record_only` captures without
71115
+ * consolidating, `shadow` consolidates without committing, and only
71116
+ * `active` writes. Off by default, and off is where an unrecognised
71117
+ * value lands too — see `resolveMemoryRollout`, which fails closed
71118
+ * rather than falling back to the legacy search path.
71119
+ */
71120
+ memoryRollout: z2.union([
71121
+ z2.const("off"),
71122
+ z2.const("record_only"),
71123
+ z2.const("shadow"),
71124
+ z2.const("active")
71125
+ ]).default("off").volatile()
70680
71126
  });
70681
71127
  account;
70682
71128
  api;
70683
71129
  credentials;
70684
- /**
70685
- * The registered settings namespace. Nothing outside this class reads it.
70686
- *
70687
- * Every behaviour read goes through {@link policy}, so exactly one place
70688
- * answers "what is configured".
70689
- */
70690
- engineSettings;
70691
71130
  /** The one accessor behaviour reads go through (see `policy.ts`). */
70692
71131
  policy;
70693
71132
  capabilities;
@@ -70935,7 +71374,11 @@ var FreeCodeGoHarnessPlugin = (() => {
70935
71374
  this.config = config;
70936
71375
  reportCommandPolicyDiagnostics(ctx, this.commandPolicy);
70937
71376
  ensureDesktopDshShim();
70938
- this.pendingWrites.run(() => ensureFreeCodeGoAgentPreset());
71377
+ ctx.effect(() => {
71378
+ const presets = installFreeCodeGoAgentPresets(ctx);
71379
+ this.pendingWrites.run(() => presets.done);
71380
+ return presets.release;
71381
+ }, "freecodego: agent presets");
70939
71382
  ctx.effect(() => {
70940
71383
  const known = KNOWN_SESSION_EVENT_TYPES;
70941
71384
  const added = freeCodeGoSessionEventTypes.filter((type) => !known.has(type));
@@ -70992,138 +71435,15 @@ var FreeCodeGoHarnessPlugin = (() => {
70992
71435
  const claudeStatus = this.claudeRuntime.status();
70993
71436
  setEngineAvailability(this.agentEngines, "claude", claudeStatus.installed ? "available" : "unavailable");
70994
71437
  const settings = ctx.get("settings");
70995
- if (settings !== void 0) this.engineSettings = settings.register("freecodego-harness", z2.intersect([
70996
- z2.object({
70997
- defaultModel: z2.string().default(config.defaultModel ?? ""),
70998
- defaultEngine: z2.string().default(config.defaultEngine ?? "deepseek"),
70999
- mediaDefaults: z2.object({
71000
- image: z2.string().default(""),
71001
- video: z2.string().default(""),
71002
- audio: z2.string().default("")
71003
- }).default({
71004
- image: "",
71005
- video: "",
71006
- audio: ""
71007
- })
71008
- }),
71009
- FreeCodeGoCapabilitySettingsSchema,
71010
- FreeCodeGoPluginConflictSettingsSchema,
71011
- FreeCodeGoPluginUpdateSettingsSchema,
71012
- FreeCodeGoEngineeringSettingsSchema,
71013
- FreeCodeGoAutomationSettingsSchema,
71014
- FreeCodeGoGuardSettingsSchema,
71015
- FreeCodeGoTrustSettingsSchema,
71016
- z2.object({
71017
- /**
71018
- * Sandbox profile deny globs, enforced in the plugin policy layer.
71019
- *
71020
- * Empty by default: a deny list is containment the user asked for, and
71021
- * inventing one would refuse paths nobody named.
71022
- */
71023
- sandboxDenyPatterns: z2.array(z2.string()).default([])
71024
- }),
71025
- z2.object({
71026
- rehydrationEnabled: z2.boolean().default(true),
71027
- /**
71028
- * Conversation-arc rehydration (goals + decisions folded from the
71029
- * session's own goal events, promoted memories, and announced
71030
- * choices). Off by default: it changes what the post-compaction
71031
- * message contains, and a user must opt into new prompt surface —
71032
- * the mechanical fold is bounded, but its wording is still model-
71033
- * visible behavior.
71034
- */
71035
- rehydrationArcEnabled: z2.boolean().default(false)
71036
- }),
71037
- z2.object({
71038
- advisorEnabled: z2.boolean().default(config.autoAdvisorEnabled !== false),
71039
- advisorMode: z2.union([
71040
- z2.const("async"),
71041
- z2.const("catchup"),
71042
- z2.const("blocker-only")
71043
- ]).default("async"),
71044
- advisorProvider: z2.string().default("opencode"),
71045
- advisorModel: z2.string().default("auto"),
71046
- advisorAllowAgentControl: z2.boolean().default(true),
71047
- advisorInterruptCooldownTurns: z2.number().step(1).min(0).max(20).default(3),
71048
- advisorMemoryDraftsEnabled: z2.boolean().default(true),
71049
- reviewMode: z2.union([
71050
- z2.const("off"),
71051
- z2.const("record"),
71052
- z2.const("gate")
71053
- ]).default("off"),
71054
- reviewThreshold: z2.union([
71055
- z2.const("critical"),
71056
- z2.const("high"),
71057
- z2.const("medium"),
71058
- z2.const("low")
71059
- ]).default("high"),
71060
- reviewCooldownTurns: z2.number().step(1).min(0).max(20).default(3),
71061
- reviewDeep: z2.boolean().default(false),
71062
- reviewEscalation: z2.boolean().default(false),
71063
- headroomEnabled: z2.boolean().default(true),
71064
- headroomThresholdChars: z2.number().step(1).min(256).max(1e6).default(1200),
71065
- headroomMinSavingsRatio: z2.number().min(0.05).max(0.95).default(0.85),
71066
- headroomDedupEnabled: z2.boolean().default(true),
71067
- /**
71068
- * Tools to protect *in addition* to the built-in list.
71069
- *
71070
- * Additional, not instead of: the built-in set
71071
- * (`DEFAULT_EXCLUDE_TOOLS`) is a safety guarantee — those outputs are
71072
- * what the model byte-patches against — and this field defaults to an
71073
- * empty array, so a reading where it replaced the built-ins would make
71074
- * the default install protect nothing.
71075
- */
71076
- headroomExcludeTools: z2.array(z2.string()).default([]),
71077
- headroomFoldReads: z2.boolean().default(false),
71078
- /**
71079
- * Skeletonize large source-file reads: retained lines stay byte-exact
71080
- * while whole body runs collapse to a marker. On by default because
71081
- * `read` is the largest single source of re-sent context; the original
71082
- * is always retrievable through `headroom_retrieve`.
71083
- */
71084
- headroomCodeSkeletonEnabled: z2.boolean().default(true),
71085
- /**
71086
- * Reversible-render competition policy. `reversible` (default) delivers a
71087
- * render that clears `FOLD_DECISIVE_RATIO` on sight — a Stage 2 fold, or a
71088
- * mixed-content splice; `max` demotes every one of them to a candidate so
71089
- * the compressor written for the payload's shape is always asked first.
71090
- * The two settings are two products, not a fine-tuning — see
71091
- * `HeadroomRuntime.foldPolicy` for the measured difference on the shapes
71092
- * where they disagree.
71093
- */
71094
- headroomFoldPolicy: z2.union([z2.const("reversible"), z2.const("max")]).default("reversible"),
71095
- /**
71096
- * Keep task-specific tool schemas out of the request until the model
71097
- * asks for them with `tool_search`. Measured saving is ~3.2k tokens per
71098
- * request on this plugin's own 37 tools alone.
71099
- */
71100
- deferredToolSchemasEnabled: z2.boolean().default(true),
71101
- /** Explicit deferral list; when empty the plugin-owned prefix rule applies. */
71102
- deferredToolNames: z2.array(z2.string()).default([]),
71103
- headroomOutputShaper: z2.boolean().default(false),
71104
- headroomVerbosityLevel: z2.number().step(1).min(0).max(4).default(0)
71105
- }),
71106
- z2.object({
71107
- /**
71108
- * Memory consolidation rollout (`memory/rollout.ts`).
71109
- *
71110
- * A stage rather than a switch because consolidating runs a model over
71111
- * the session's successful turns: `record_only` captures without
71112
- * consolidating, `shadow` consolidates without committing, and only
71113
- * `active` writes. Off by default, and off is where an unrecognised
71114
- * value lands too — see `resolveMemoryRollout`, which fails closed
71115
- * rather than falling back to the legacy search path.
71116
- */
71117
- memoryRollout: z2.union([
71118
- z2.const("off"),
71119
- z2.const("record_only"),
71120
- z2.const("shadow"),
71121
- z2.const("active")
71122
- ]).default("off")
71123
- })
71124
- ]), { applies: "live" });
71125
- else this.engineSettings = void 0;
71126
- this.policy = new FreeCodeGoPolicy(this.engineSettings);
71438
+ const settingsWriter = settings === void 0 ? void 0 : { update: async (patch) => {
71439
+ const entry = this.ctx.fiber.entry;
71440
+ if (entry === void 0) return;
71441
+ await settings.update(entry.options.id, patch);
71442
+ } };
71443
+ this.ctx.inject(["settings"], (child) => {
71444
+ child.effect(() => child.settings.configure({ auto: false }, this.ctx.fiber));
71445
+ });
71446
+ this.policy = new FreeCodeGoPolicy(this.config, settingsWriter);
71127
71447
  this.capabilities = new FreeCodeGoCapabilityRegistry(ctx, this.policy, (directory) => this.projectScopeTrust(directory), async () => {
71128
71448
  const tier = await this.projectTierFor(process.cwd()).catch(() => void 0);
71129
71449
  return {
@@ -71435,10 +71755,7 @@ var FreeCodeGoHarnessPlugin = (() => {
71435
71755
  const failed = result.stages.filter((stage) => stage.state === "fail" || stage.state === "unavailable");
71436
71756
  if (failed.length === 0) return;
71437
71757
  agent.inject(createUserMessage({
71438
- source: {
71439
- kind: "plugin",
71440
- plugin: "freecodego-engineering-loop"
71441
- },
71758
+ source: { kind: "freecodego-engineering-loop" },
71442
71759
  content: [{
71443
71760
  type: "text",
71444
71761
  text: `Engineering goal "${goalId}" ended in phase "${phase}", but verification did not pass: ${failed.map((stage) => `${stage.id}=${stage.state}`).join(", ")}. Resolve these before treating the plan as done.`
@@ -71450,10 +71767,7 @@ var FreeCodeGoHarnessPlugin = (() => {
71450
71767
  if (loop.phase === "blocked") {
71451
71768
  const budget = loop.roundsStarted === void 0 || loop.maxGoalRounds === void 0 ? "" : ` after ${loop.roundsStarted}/${loop.maxGoalRounds} goal rounds`;
71452
71769
  agent.inject(createUserMessage({
71453
- source: {
71454
- kind: "plugin",
71455
- plugin: "freecodego-engineering-loop"
71456
- },
71770
+ source: { kind: "freecodego-engineering-loop" },
71457
71771
  content: [{
71458
71772
  type: "text",
71459
71773
  text: `Engineering goal "${goalId}" stopped unattended${budget}: ${loop.blockedMessage ?? loop.blockedCode ?? "the round driver blocked it"}. Nothing continues until that is resolved; use \u5DE5\u7A0B\u589E\u5F3A \u2192 \u65E0\u4EBA\u503C\u5B88\u5DE5\u7A0B\u56DE\u8DEF \u2192 \u7EE7\u7EED to resume it.`
@@ -71565,9 +71879,13 @@ var FreeCodeGoHarnessPlugin = (() => {
71565
71879
  this.catalogs.registerNvidiaAdapter();
71566
71880
  this.catalogs.registerKiloAdapter();
71567
71881
  this.catalogs.prewarmProviderCatalogs();
71882
+ this.repairWebSearchBinding();
71568
71883
  this.catalogs.refreshLogfareHealthInBackground();
71569
- this.registerAgnesTools();
71570
- this.registerMediaTools();
71884
+ this.syncMediaGenerationTools();
71885
+ ctx.effect(() => this.registerAudioTools(), "freecodego: media audio tools");
71886
+ ctx.effect(() => () => {
71887
+ this.disposeMediaGenerationTools();
71888
+ }, "freecodego: media generation tools");
71571
71889
  ctx.effect(() => async () => {
71572
71890
  await Promise.all([this.pendingWrites.drain(), this.catalogs.pendingWrites.drain()]);
71573
71891
  }, "freecodego: background preset and catalog writes");
@@ -71612,10 +71930,7 @@ var FreeCodeGoHarnessPlugin = (() => {
71612
71930
  const agent = agents.get(agentId);
71613
71931
  if (typeof agent?.inject !== "function") return;
71614
71932
  agent.inject(createUserMessage({
71615
- source: {
71616
- kind: "plugin",
71617
- plugin: "freecodego-verify-on-stop"
71618
- },
71933
+ source: { kind: "freecodego-verify-on-stop" },
71619
71934
  content: [{
71620
71935
  type: "text",
71621
71936
  text: `<verify-on-stop>
@@ -71701,10 +72016,7 @@ ${text2}
71701
72016
  const agent = agents.get(agentId);
71702
72017
  if (typeof agent?.inject !== "function") return false;
71703
72018
  agent.inject(createUserMessage({
71704
- source: {
71705
- kind: "plugin",
71706
- plugin: "freecodego-review"
71707
- },
72019
+ source: { kind: "freecodego-review" },
71708
72020
  content: [{
71709
72021
  type: "text",
71710
72022
  text: `<stop-time-review>
@@ -72225,6 +72537,32 @@ ${text2}
72225
72537
  await settings.update({ deferredToolSchemasEnabled: enabled });
72226
72538
  return this.deferredTools.status();
72227
72539
  }
72540
+ /**
72541
+ * Read the image/video generation switch and the tool names it governs.
72542
+ *
72543
+ * Two name lists, because "what this switch owns" and "what is mounted right now"
72544
+ * are different facts: the first comes from the registration modules' single
72545
+ * declarations, the second from the registrations themselves. Reported together so a
72546
+ * panel can say which tools a profile actually has without restating either list.
72547
+ * @returns the media Generation Status.
72548
+ */
72549
+ mediaGenerationStatus() {
72550
+ return {
72551
+ enabled: this.policy.get()?.mediaGenerationEnabled !== false,
72552
+ gated: [...Object.values(MEDIA_GENERATION_TOOL_NAMES), ...Object.values(AGNES_MEDIA_TOOL_NAMES)],
72553
+ registered: this.mediaGenerationTools?.names ?? []
72554
+ };
72555
+ }
72556
+ /**
72557
+ * Mount or unmount the image and video generation tools.
72558
+ * @param enabled - whether the capability is switched on.
72559
+ * @returns the media Generation Status.
72560
+ */
72561
+ async mediaGenerationSetEnabled(enabled) {
72562
+ await this.policy.update({ mediaGenerationEnabled: enabled });
72563
+ this.syncMediaGenerationTools();
72564
+ return this.mediaGenerationStatus();
72565
+ }
72228
72566
  /** Probe-and-mount status for the optional LSP stack (fail-soft).
72229
72567
  * @returns the probe-and-mount state of the optional LSP stack.
72230
72568
  */
@@ -73496,10 +73834,7 @@ ${detail.body}`
73496
73834
  const outcome = this.personaRuns.complete(childId, produced);
73497
73835
  if (outcome?.message === void 0) return;
73498
73836
  outcome.parent?.inject?.(createUserMessage({
73499
- source: {
73500
- kind: "plugin",
73501
- plugin: "freecodego-persona"
73502
- },
73837
+ source: { kind: "freecodego-persona" },
73503
73838
  content: [{
73504
73839
  type: "text",
73505
73840
  text: `[freecodego] ${outcome.message}`
@@ -73746,10 +74081,7 @@ ${detail.body}`
73746
74081
  })).agent;
73747
74082
  if (child?.followup === void 0) throw new Error("the child agent was created but cannot be sent its brief; it was not given one");
73748
74083
  child.followup(createUserMessage({
73749
- source: {
73750
- kind: "plugin",
73751
- plugin: "freecodego-persona"
73752
- },
74084
+ source: { kind: "freecodego-persona" },
73753
74085
  content: [{
73754
74086
  type: "text",
73755
74087
  text: input.session.brief
@@ -74333,10 +74665,7 @@ ${detail.body}`
74333
74665
  if (!plan.changed) return;
74334
74666
  if (typeof agent?.inject !== "function") return;
74335
74667
  agent.inject(createUserMessage({
74336
- source: {
74337
- kind: "plugin",
74338
- plugin: "freecodego-context-budget"
74339
- },
74668
+ source: { kind: "freecodego-context-budget" },
74340
74669
  content: [{
74341
74670
  type: "text",
74342
74671
  text: `<context-budget>
@@ -74373,10 +74702,7 @@ ${plan.text}
74373
74702
  if (fragments.length === 0) return;
74374
74703
  if (typeof agent.inject !== "function") return;
74375
74704
  agent.inject(createUserMessage({
74376
- source: {
74377
- kind: "plugin",
74378
- plugin: "freecodego-plan-mode"
74379
- },
74705
+ source: { kind: "freecodego-plan-mode" },
74380
74706
  content: [{
74381
74707
  type: "text",
74382
74708
  text: renderFragments(fragments)
@@ -74682,6 +75008,19 @@ ${plan.text}
74682
75008
  async sessionDelete(sessionId) {
74683
75009
  return sessionDelete(this.engineRemotesHost, sessionId);
74684
75010
  }
75011
+ /** Resolve one of this plugin's models as the DeepSeek web-search provider.
75012
+ * @param input - the provider and the model the user picked.
75013
+ * @returns the endpoint, model, and credential the search page must use.
75014
+ */
75015
+ async webSearchBind(input) {
75016
+ return webSearchBind(this.engineRemotesHost, input);
75017
+ }
75018
+ /** Report whether the saved web-search binding is this plugin's, and whether it still answers.
75019
+ * @returns the state the search page renders from.
75020
+ */
75021
+ async webSearchBindingStatus() {
75022
+ return webSearchBindingStatus(this.webSearchBindingReads());
75023
+ }
74685
75024
  /** Return the current cross-engine MCP and Skill capability inventory.
74686
75025
  * @returns the capability snapshot the Host reports.
74687
75026
  */
@@ -75905,10 +76244,6 @@ ${plan.text}
75905
76244
  });
75906
76245
  if (dispose !== void 0) this.ctx.effect(() => dispose, "freecodego: edit_and_run composite tool");
75907
76246
  }
75908
- /** Model-facing media tools keep Agnes API keys and requests in the Host. */
75909
- registerAgnesTools() {
75910
- registerAgnesTools(this.agentToolsDeps());
75911
- }
75912
76247
  agentToolsDeps() {
75913
76248
  return {
75914
76249
  ...this.coreDeps,
@@ -75921,9 +76256,105 @@ ${plan.text}
75921
76256
  probeCommandPolicy: (agent) => this.projectPolicyForAgent(agent)
75922
76257
  };
75923
76258
  }
75924
- /** Generic media tools resolve the user's live default at execution time. */
75925
- registerMediaTools() {
75926
- registerMediaTools(this.mediaHost);
76259
+ /** The mounted image/video registrations, absent while the switch is off. */
76260
+ mediaGenerationTools;
76261
+ /**
76262
+ * Register the audio generation and transcription tools, which no switch governs.
76263
+ * @returns the disposer that releases both registrations.
76264
+ */
76265
+ registerAudioTools() {
76266
+ return registerAudioTools(this.mediaHost).dispose;
76267
+ }
76268
+ /**
76269
+ * Mount or unmount the image and video tools so that they match the setting.
76270
+ *
76271
+ * Remounted from the switch's own Remote rather than read at call time, because a
76272
+ * tool the model can see *is* a registration: gating `execute` instead would leave the
76273
+ * schema in every request, which is the cost the switch exists to remove.
76274
+ */
76275
+ syncMediaGenerationTools() {
76276
+ const enabled = this.policy.get()?.mediaGenerationEnabled !== false;
76277
+ if (enabled === (this.mediaGenerationTools !== void 0)) return;
76278
+ if (!enabled) {
76279
+ this.disposeMediaGenerationTools();
76280
+ return;
76281
+ }
76282
+ const generation = registerGenerationTools(this.mediaHost);
76283
+ const legacy = registerAgnesMediaTools(this.agentToolsDeps());
76284
+ this.mediaGenerationTools = {
76285
+ names: [...generation.names, ...legacy.names],
76286
+ dispose: () => {
76287
+ generation.dispose();
76288
+ legacy.dispose();
76289
+ }
76290
+ };
76291
+ }
76292
+ /**
76293
+ * Rebuild the web-search binding this plugin wrote, when this process cannot serve it.
76294
+ *
76295
+ * Read once at start and never in a request path: a binding that died with the
76296
+ * previous process has to be whole again before the first search runs, and the
76297
+ * search itself must not be the thing that discovers it. Every read and write the
76298
+ * pass needs is injected, so `web-search-binding.ts` owns the rules and this method
76299
+ * owns only where they are satisfied — including the two services whose absence
76300
+ * leaves a binding unbuildable, which is reported rather than half-applied.
76301
+ */
76302
+ async repairWebSearchBinding() {
76303
+ let reads;
76304
+ let report;
76305
+ try {
76306
+ reads = this.webSearchBindingReads();
76307
+ report = await repairWebSearchBinding({
76308
+ ...reads,
76309
+ bind: (input) => webSearchBind(this.engineRemotesHost, input),
76310
+ writeCredential: async (ref, value) => {
76311
+ const credentials = this.credentials;
76312
+ if (credentials === void 0) throw new Error("the credentials service is not mounted, so the web-search key cannot be written");
76313
+ await credentials.set(credentialRef2(ref), value);
76314
+ },
76315
+ writeSection: async (patch) => {
76316
+ const settings = this.ctx.get("settings");
76317
+ if (settings === void 0) throw new Error("the settings service is not mounted, so the web-search section cannot be rewritten");
76318
+ await settings.update(WEB_SEARCH_SETTINGS_NAMESPACE, patch);
76319
+ }
76320
+ });
76321
+ } catch (error) {
76322
+ this.ctx.logger?.warn?.(`FreeCodeGo could not repair the web-search binding: ${error instanceof Error ? error.message : String(error)}`);
76323
+ return;
76324
+ }
76325
+ if (report.outcome === "rebuilt") this.ctx.logger?.info?.(`FreeCodeGo rebuilt the stale web-search binding for ${reads.remembered?.provider ?? ""}/${reads.remembered?.model ?? ""}`);
76326
+ if (report.outcome === "failed") this.ctx.logger?.warn?.(`FreeCodeGo could not rebuild the web-search binding: ${report.detail ?? "unknown failure"}`);
76327
+ }
76328
+ /**
76329
+ * The reads both web-search passes share, taken from this Host's services.
76330
+ *
76331
+ * One accessor rather than two because the answers have to agree: the boot pass
76332
+ * deciding a saved route is dead while the page reads it as alive would rewrite a
76333
+ * section the user is looking at. The remembered pair is read through the policy —
76334
+ * the one seam a settings read goes through — and a composition with no
76335
+ * configuration at all resolves to `undefined` rather than throwing here.
76336
+ */
76337
+ webSearchBindingReads() {
76338
+ const settings = this.ctx.get("settings");
76339
+ const configured = this.policy.get();
76340
+ const provider = asString(configured?.webSearchBindingProvider) ?? "";
76341
+ const model = asString(configured?.webSearchBindingModel) ?? "";
76342
+ return {
76343
+ remembered: provider === "" || model === "" ? void 0 : {
76344
+ provider,
76345
+ model
76346
+ },
76347
+ readSection: async () => {
76348
+ const descriptor = settings?.describe().find((row) => row.ns === WEB_SEARCH_SETTINGS_NAMESPACE);
76349
+ return descriptor === void 0 ? void 0 : maybeRecord(descriptor.value);
76350
+ },
76351
+ servesBridgeRoute: (routeId) => this.claudeBridge.hasRoute(routeId)
76352
+ };
76353
+ }
76354
+ /** Release the image and video registrations, when any are mounted. */
76355
+ disposeMediaGenerationTools() {
76356
+ this.mediaGenerationTools?.dispose();
76357
+ this.mediaGenerationTools = void 0;
75927
76358
  }
75928
76359
  get mediaHost() {
75929
76360
  return {
@@ -76744,13 +77175,12 @@ function hasInstalledClaudeRuntime() {
76744
77175
  // packages/freecodego/bundle-latest/src/index.ts
76745
77176
  var name = "freecodego";
76746
77177
  var inject = [];
76747
- async function apply2(ctx, config = {}) {
76748
- await ctx.plugin(FreeCodeGoHarnessPlugin, config);
77178
+ async function apply2(ctx, _config = {}) {
76749
77179
  await ctx.plugin(FreeCodeGoAgentEngineRouter, {});
76750
77180
  }
76751
77181
  export {
77182
+ FreeCodeGoHarnessPlugin,
76752
77183
  apply2 as apply,
76753
- bootstrapFreeCodeGoHarness,
76754
77184
  inject,
76755
77185
  name,
76756
77186
  registerFreeCodeGoSessionEventTypes