akm-cli 0.9.25-alpha.1 → 0.9.25-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +159 -280
  2. package/dist/cli.js +1 -1
  3. package/dist/commands/improve/consolidate/pair-pass.js +1 -0
  4. package/dist/commands/improve/consolidate.js +7 -2
  5. package/dist/commands/improve/execution.js +2 -3
  6. package/dist/commands/improve/extract.js +1 -0
  7. package/dist/commands/improve/improve-cli.js +33 -1
  8. package/dist/commands/improve/loop-stages.js +3 -0
  9. package/dist/commands/improve/reflect-noise.js +125 -0
  10. package/dist/commands/improve/reflect.js +13 -16
  11. package/dist/commands/improve/retrieval-gate.js +7 -2
  12. package/dist/commands/improve/stage.js +31 -39
  13. package/dist/commands/proposal/drain.js +4 -7
  14. package/dist/commands/proposal/propose.js +2 -11
  15. package/dist/commands/proposal/validators/proposal-quality-validators.js +4 -2
  16. package/dist/commands/read/search-cli.js +0 -38
  17. package/dist/core/config/schema/engines.js +15 -33
  18. package/dist/core/config/schema/improve-processes.js +16 -0
  19. package/dist/core/redaction.js +4 -0
  20. package/dist/core/spawn-env.js +25 -0
  21. package/dist/core/structured.js +1 -1
  22. package/dist/execution/source.js +8 -12
  23. package/dist/integrations/agent/config.js +1 -3
  24. package/dist/integrations/agent/engine-resolution.js +0 -3
  25. package/dist/integrations/agent/execution.js +14 -13
  26. package/dist/integrations/agent/index.js +1 -1
  27. package/dist/integrations/agent/model-map.js +15 -16
  28. package/dist/integrations/agent/profiles.js +2 -2
  29. package/dist/integrations/agent/prompts.js +3 -39
  30. package/dist/integrations/agent/request-lowering.js +9 -7
  31. package/dist/integrations/agent/runner-dispatch.js +25 -31
  32. package/dist/integrations/harnesses/aider/agent-builder.js +1 -2
  33. package/dist/integrations/harnesses/amazonq/agent-builder.js +1 -2
  34. package/dist/integrations/harnesses/claude/agent-builder.js +4 -16
  35. package/dist/integrations/harnesses/codex/agent-builder.js +1 -2
  36. package/dist/integrations/harnesses/ids.js +10 -16
  37. package/dist/integrations/harnesses/opencode/agent-builder.js +14 -24
  38. package/dist/integrations/harnesses/opencode/model-config.js +15 -62
  39. package/dist/integrations/harnesses/opencode/model-work-agent.js +71 -36
  40. package/dist/integrations/harnesses/opencode-sdk/harness.js +2 -6
  41. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +32 -90
  42. package/dist/integrations/harnesses/openhands/agent-builder.js +1 -2
  43. package/dist/integrations/harnesses/pi/agent-builder.js +1 -2
  44. package/dist/llm/feature-gate.js +2 -5
  45. package/dist/llm/index-passes.js +2 -2
  46. package/dist/llm/structured-call.js +5 -5
  47. package/dist/output/shapes/passthrough.js +1 -0
  48. package/dist/scripts/akm-migrate-node.js +170 -194
  49. package/dist/scripts/akm-migrate.js +170 -194
  50. package/dist/workflows/exec/unit-dispatch.js +4 -13
  51. package/docs/reference/cli.md +12 -7
  52. package/docs/reference/configuration.md +78 -82
  53. package/docs/reference/data-and-telemetry.md +2 -3
  54. package/docs/reference/workflow-schema.md +6 -9
  55. package/package.json +1 -1
  56. package/schemas/akm-config.json +108 -36
@@ -29036,6 +29036,7 @@ var PASSTHROUGH_COMMANDS = [
29036
29036
  "extract",
29037
29037
  "health",
29038
29038
  "improve",
29039
+ "improve-judge",
29039
29040
  "improve-report",
29040
29041
  "import",
29041
29042
  "index",
@@ -35454,25 +35455,21 @@ var coerce = {
35454
35455
  };
35455
35456
  var NEVER = INVALID;
35456
35457
  // src/integrations/harnesses/ids.ts
35457
- var OPENCODE_INFERENCE = ["temperature", "maxTokens", "contextLength", "enableThinking", "reasoningEffort"];
35458
35458
  var HARNESS_ID_TABLE = [
35459
- { id: "opencode", agentDispatch: true, enforcesModelWorkTools: true, inference: OPENCODE_INFERENCE },
35460
- { id: "claude", agentDispatch: true, enforcesModelWorkTools: true, inference: ["reasoningEffort"] },
35461
- { id: "opencode-sdk", agentDispatch: true, enforcesModelWorkTools: true, inference: OPENCODE_INFERENCE },
35462
- { id: "codex", agentDispatch: true, enforcesModelWorkTools: false, inference: [] },
35463
- { id: "copilot", agentDispatch: true, enforcesModelWorkTools: false, inference: [] },
35464
- { id: "pi", agentDispatch: true, enforcesModelWorkTools: false, inference: [] },
35465
- { id: "gemini", agentDispatch: true, enforcesModelWorkTools: false, inference: [] },
35466
- { id: "aider", agentDispatch: true, enforcesModelWorkTools: false, inference: [] },
35467
- { id: "amazonq", agentDispatch: true, enforcesModelWorkTools: false, inference: [] },
35468
- { id: "openhands", agentDispatch: true, enforcesModelWorkTools: false, inference: [] }
35459
+ { id: "opencode", agentDispatch: true, enforcesModelWorkTools: true },
35460
+ { id: "claude", agentDispatch: true, enforcesModelWorkTools: true },
35461
+ { id: "opencode-sdk", agentDispatch: true, enforcesModelWorkTools: true },
35462
+ { id: "codex", agentDispatch: true, enforcesModelWorkTools: false },
35463
+ { id: "copilot", agentDispatch: true, enforcesModelWorkTools: false },
35464
+ { id: "pi", agentDispatch: true, enforcesModelWorkTools: false },
35465
+ { id: "gemini", agentDispatch: true, enforcesModelWorkTools: false },
35466
+ { id: "aider", agentDispatch: true, enforcesModelWorkTools: false },
35467
+ { id: "amazonq", agentDispatch: true, enforcesModelWorkTools: false },
35468
+ { id: "openhands", agentDispatch: true, enforcesModelWorkTools: false }
35469
35469
  ];
35470
35470
  var VALID_HARNESS_IDS = Object.freeze(HARNESS_ID_TABLE.map((h) => h.id));
35471
35471
  var HARNESS_AGENT_DISPATCH_IDS = new Set(HARNESS_ID_TABLE.filter((h) => h.agentDispatch).map((h) => h.id));
35472
35472
  var HARNESS_MODEL_WORK_IDS = new Set(HARNESS_ID_TABLE.filter((h) => h.enforcesModelWorkTools).map((h) => h.id));
35473
- function harnessInferenceKeys(platform) {
35474
- return HARNESS_ID_TABLE.find((h) => h.id === platform)?.inference ?? [];
35475
- }
35476
35473
 
35477
35474
  // src/core/extra-params.ts
35478
35475
  var EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS = [
@@ -35777,13 +35774,6 @@ var LlmEngineSchema = exports_external.object({
35777
35774
  });
35778
35775
  }
35779
35776
  });
35780
- var AGENT_INFERENCE_KEYS = [
35781
- "temperature",
35782
- "maxTokens",
35783
- "contextLength",
35784
- "enableThinking",
35785
- "reasoningEffort"
35786
- ];
35787
35777
  var AgentEngineSchema = exports_external.object({
35788
35778
  kind: exports_external.literal("agent"),
35789
35779
  platform: AgentPlatformSchema.refine((platform) => HARNESS_AGENT_DISPATCH_IDS.has(platform), {
@@ -35794,27 +35784,25 @@ var AgentEngineSchema = exports_external.object({
35794
35784
  workspace: nonEmptyString.optional(),
35795
35785
  model: nonEmptyString.optional(),
35796
35786
  timeoutMs: timeoutMsField,
35797
- llmEngine: engineName.optional(),
35798
- temperature: exports_external.number().finite().optional(),
35799
- maxTokens: positiveInt.optional(),
35800
- contextLength: positiveInt.optional(),
35801
- enableThinking: exports_external.boolean().optional(),
35802
- reasoningEffort: nonEmptyString.optional()
35787
+ llmEngine: engineName.optional()
35803
35788
  }).passthrough().superRefine((value, ctx) => {
35804
- for (const key of ["provider", "endpoint", "apiKey", "apiKeyFile", "concurrency", "extraParams", "modelAliases"]) {
35789
+ for (const key of [
35790
+ "provider",
35791
+ "endpoint",
35792
+ "apiKey",
35793
+ "apiKeyFile",
35794
+ "temperature",
35795
+ "maxTokens",
35796
+ "concurrency",
35797
+ "extraParams",
35798
+ "contextLength",
35799
+ "enableThinking",
35800
+ "reasoningEffort",
35801
+ "modelAliases"
35802
+ ]) {
35805
35803
  if (key in value)
35806
35804
  ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: [key], message: `${key} is not valid on an agent engine` });
35807
35805
  }
35808
- const translated = harnessInferenceKeys(value.platform);
35809
- for (const key of AGENT_INFERENCE_KEYS) {
35810
- if (key in value && !translated.includes(key)) {
35811
- ctx.addIssue({
35812
- code: exports_external.ZodIssueCode.custom,
35813
- path: [key],
35814
- message: `${key} is not valid on a ${value.platform} engine: ${translated.length > 0 ? `the platform translates only ${translated.join(", ")}` : "the platform translates no inference fields"}`
35815
- });
35816
- }
35817
- }
35818
35806
  if (value.platform !== "opencode-sdk" && value.llmEngine !== undefined) {
35819
35807
  ctx.addIssue({
35820
35808
  code: exports_external.ZodIssueCode.custom,
@@ -35878,6 +35866,11 @@ var clsField = exports_external.object({
35878
35866
  }).passthrough().optional();
35879
35867
  var fidelityCheckField = exports_external.object({ enabled: exports_external.boolean().optional() }).passthrough().optional();
35880
35868
  var lowValueFilterField = exports_external.object({ enabled: exports_external.boolean().optional() }).passthrough().optional();
35869
+ var defectFilterField = exports_external.object({
35870
+ placeholders: exports_external.array(nonEmptyString).optional(),
35871
+ metaCommentary: exports_external.array(nonEmptyString).optional(),
35872
+ frontmatterKeys: exports_external.array(nonEmptyString).optional()
35873
+ }).passthrough().optional();
35881
35874
  var extractTriageGateField = exports_external.object({
35882
35875
  enabled: exports_external.boolean().optional(),
35883
35876
  minScore: exports_external.number().min(0).optional()
@@ -35900,7 +35893,8 @@ var REFLECT_PROCESS_FIELDS = {
35900
35893
  excludeRefPrefixes: excludeRefPrefixesField,
35901
35894
  limit: processLimitField,
35902
35895
  qualityGate: qualityGateField,
35903
- lowValueFilter: lowValueFilterField
35896
+ lowValueFilter: lowValueFilterField,
35897
+ defectFilter: defectFilterField
35904
35898
  };
35905
35899
  var DISTILL_PROCESS_FIELDS = {
35906
35900
  allowedTypes: allowedTypesField,
@@ -37622,6 +37616,10 @@ var ENV_PASSTHROUGH_REDACTION_POLICY = {
37622
37616
  OPENCODE_CONFIG: "path",
37623
37617
  CLAUDE_CONFIG: "path",
37624
37618
  CODEX_CONFIG: "path",
37619
+ XDG_CONFIG_HOME: "path",
37620
+ XDG_DATA_HOME: "path",
37621
+ XDG_CACHE_HOME: "path",
37622
+ XDG_STATE_HOME: "path",
37625
37623
  AWS_PROFILE: "identifier",
37626
37624
  AWS_REGION: "identifier",
37627
37625
  LLM_MODEL: "identifier",
@@ -39674,10 +39672,7 @@ import { createHash as createHash3 } from "node:crypto";
39674
39672
 
39675
39673
  // src/execution/source.ts
39676
39674
  var EXECUTION_SOURCE_SCHEMA_VERSION = 1;
39677
- var MODEL_WORK_TOOLS = Object.freeze(["read", "edit", "akm search", "akm show"]);
39678
- function isModelWorkTools(tools) {
39679
- return Array.isArray(tools) && tools.length === MODEL_WORK_TOOLS.length && tools.every((tool, index) => tool === MODEL_WORK_TOOLS[index]);
39680
- }
39675
+ var MODEL_WORK_POLICY_ID = "model-work";
39681
39676
  function requireRecord(value, path18) {
39682
39677
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
39683
39678
  throw new TypeError(`${path18} must be an object`);
@@ -51045,8 +51040,9 @@ function extensionFields(request) {
51045
51040
  ];
51046
51041
  }
51047
51042
  function createAgentRequestLowerer(options) {
51048
- return (profile, request) => {
51049
- const supportedInference = new Set(typeof options.inference === "function" ? options.inference(profile, request) : options.inference ?? []);
51043
+ const supportedInference = new Set(options.inference ?? []);
51044
+ return (_profile, request) => {
51045
+ const modelWork = request.authorization.policy?.id === MODEL_WORK_POLICY_ID;
51050
51046
  const notices = [];
51051
51047
  const skip = (field) => {
51052
51048
  notices.push(untranslated(options.adapter, field));
@@ -51087,18 +51083,18 @@ function createAgentRequestLowerer(options) {
51087
51083
  if (Object.hasOwn(request, "inference")) {
51088
51084
  dispatch.inference = request.inference ?? null;
51089
51085
  for (const key of Object.keys(request.inference ?? {}).sort()) {
51090
- if (!supportedInference.has(key))
51086
+ if (!(modelWork && supportedInference.has(key)))
51091
51087
  skip(`inference.${key}`);
51092
51088
  }
51093
51089
  }
51094
- if (isModelWorkTools(request.tools)) {
51095
- if (!options.modelWorkTools) {
51090
+ if (modelWork) {
51091
+ if (!HARNESS_MODEL_WORK_IDS.has(options.adapter)) {
51096
51092
  throw new ConfigError(`The ${options.adapter} transport cannot enforce the model-work tool policy.`, "INVALID_CONFIG_FILE");
51097
51093
  }
51098
51094
  if (dispatch.agent) {
51099
51095
  throw new ConfigError(`The ${options.adapter} transport cannot run native agent ${JSON.stringify(dispatch.agent)} under the model-work tool policy.`, "INVALID_CONFIG_FILE");
51100
51096
  }
51101
- dispatch.tools = request.tools;
51097
+ dispatch.modelWork = true;
51102
51098
  } else if (request.tools !== undefined) {
51103
51099
  if (hasToolSelection(request.tools) && !translatesTools(options.tools, request.tools)) {
51104
51100
  throw new ConfigError(`The ${options.adapter} transport cannot enforce the resolved tool policy.`, "INVALID_CONFIG_FILE");
@@ -51294,12 +51290,10 @@ var claudeBuilder = {
51294
51290
  adapter: "claude",
51295
51291
  personaChannel: "native",
51296
51292
  nativeAgentSelector: true,
51297
- tools: "all",
51298
- modelWorkTools: true,
51299
- inference: ["reasoningEffort"]
51293
+ tools: "all"
51300
51294
  }),
51301
51295
  build(profile, req) {
51302
- const modelWork = isModelWorkTools(req.tools);
51296
+ const modelWork = req.modelWork === true;
51303
51297
  const args = modelWork ? [...MODEL_WORK_CLAUDE_FLAGS] : [...profile.args];
51304
51298
  if (req.agent) {
51305
51299
  args.push("--agent", req.agent);
@@ -51315,10 +51309,7 @@ var claudeBuilder = {
51315
51309
  if (model)
51316
51310
  args.push("--model", model);
51317
51311
  }
51318
- const effort = req.inference?.reasoningEffort;
51319
- if (typeof effort === "string" && effort.length > 0)
51320
- args.push("--effort", effort);
51321
- if (req.tools && !modelWork) {
51312
+ if (req.tools) {
51322
51313
  args.push("--allowedTools", normalizeTools(req.tools));
51323
51314
  }
51324
51315
  if (req.schema) {
@@ -52080,101 +52071,87 @@ class GeminiHarness extends BaseHarness {
52080
52071
  }
52081
52072
 
52082
52073
  // src/integrations/harnesses/opencode/model-config.ts
52074
+ var MODEL_WORK_AGENT_INFERENCE = ["temperature", "reasoningEffort", "enableThinking"];
52083
52075
  var isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
52084
- function carriedInference(inference) {
52085
- const carried = {};
52076
+ function opencodeInferenceConfig(inference, modelWork) {
52086
52077
  const { temperature, reasoningEffort, enableThinking, maxTokens, contextLength } = inference ?? {};
52078
+ const options = {};
52087
52079
  if (typeof temperature === "number" && Number.isFinite(temperature))
52088
- carried.temperature = temperature;
52080
+ options.temperature = temperature;
52089
52081
  if (typeof reasoningEffort === "string" && reasoningEffort.length > 0)
52090
- carried.reasoningEffort = reasoningEffort;
52091
- if (typeof enableThinking === "boolean")
52092
- carried.enableThinking = enableThinking;
52093
- const limit = isPositiveInteger(maxTokens) && isPositiveInteger(contextLength);
52094
- if (limit)
52095
- carried.limit = { context: contextLength, output: maxTokens };
52096
- const keys = [
52097
- ["temperature", carried.temperature !== undefined, temperature],
52098
- ["reasoningEffort", carried.reasoningEffort !== undefined, reasoningEffort],
52099
- ["enableThinking", carried.enableThinking !== undefined, enableThinking],
52100
- ["maxTokens", limit, maxTokens],
52101
- ["contextLength", limit, contextLength]
52102
- ].filter(([, isCarried, value]) => isCarried || value === null).map(([key]) => key);
52103
- return { carried, keys };
52104
- }
52105
- function splitOpencodeModel(model) {
52106
- const slash = model.indexOf("/");
52107
- if (slash <= 0 || slash === model.length - 1)
52108
- return;
52109
- return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
52110
- }
52111
- function opencodeCarriedKeys(attachable, inference, modelWork) {
52112
- const { keys } = carriedInference(inference);
52113
- if (attachable)
52114
- return keys;
52115
- return modelWork ? keys.filter((key) => key !== "maxTokens" && key !== "contextLength") : [];
52116
- }
52117
- function opencodeInferenceConfig(inference, modelWork) {
52118
- const { carried } = carriedInference(inference);
52119
- const options = {};
52120
- if (carried.temperature !== undefined)
52121
- options.temperature = carried.temperature;
52122
- if (carried.reasoningEffort !== undefined)
52123
- options.reasoningEffort = carried.reasoningEffort;
52124
- if (carried.enableThinking !== undefined) {
52125
- options.chat_template_kwargs = { enable_thinking: carried.enableThinking };
52126
- options.enable_thinking = carried.enableThinking;
52082
+ options.reasoningEffort = reasoningEffort;
52083
+ if (typeof enableThinking === "boolean") {
52084
+ options.chat_template_kwargs = { enable_thinking: enableThinking };
52085
+ options.enable_thinking = enableThinking;
52127
52086
  }
52128
52087
  const hasOptions = Object.keys(options).length > 0;
52129
52088
  return {
52130
52089
  entry: {
52131
52090
  ...hasOptions && !modelWork ? { options } : {},
52132
- ...carried.limit ? { limit: { ...carried.limit } } : {}
52091
+ ...isPositiveInteger(maxTokens) && isPositiveInteger(contextLength) ? { limit: { context: contextLength, output: maxTokens } } : {}
52133
52092
  },
52134
52093
  ...hasOptions && modelWork ? { agentOptions: options } : {}
52135
52094
  };
52136
52095
  }
52137
- function opencodeModelConfig(model, entry) {
52138
- const target = model === undefined ? undefined : splitOpencodeModel(model);
52139
- if (!target || Object.keys(entry).length === 0)
52140
- return;
52141
- return { provider: { [target.providerID]: { models: { [target.modelID]: entry } } } };
52142
- }
52143
52096
 
52144
52097
  // src/integrations/harnesses/opencode/model-work-agent.ts
52098
+ import path43 from "node:path";
52145
52099
  var MODEL_WORK_OPENCODE_AGENT = "akm-model-work";
52146
- var MODEL_WORK_STEPS = 8;
52147
52100
  var MODEL_WORK_PROMPT = "You do one bounded task for akm. Use tools only to check what the task needs, never repeat a tool call, and reply with exactly what the task asks for.";
52148
- var MODEL_WORK_PERMISSION = {
52149
- "*": "deny",
52150
- read: "allow",
52151
- edit: "allow",
52152
- external_directory: "deny",
52153
- bash: "deny",
52154
- doom_loop: "deny",
52155
- glob: "deny",
52156
- grep: "deny",
52157
- list: "deny",
52158
- lsp: "deny",
52159
- question: "deny",
52160
- skill: "deny",
52161
- task: "deny",
52162
- todowrite: "deny",
52163
- webfetch: "deny",
52164
- websearch: "deny"
52165
- };
52101
+ function modelWorkPermission(stash) {
52102
+ return {
52103
+ "*": "deny",
52104
+ read: "allow",
52105
+ grep: "allow",
52106
+ glob: "allow",
52107
+ edit: { "*": "allow", ...stash ? { [`${stash.slice(1)}/*`]: "deny" } : {} },
52108
+ external_directory: {
52109
+ ...stash ? { [`${stash}/*`]: "allow" } : {},
52110
+ "~/.local/share/opencode/tool-output/*": "deny"
52111
+ },
52112
+ akm_search: "allow",
52113
+ akm_show: "allow",
52114
+ akm_feedback: "deny",
52115
+ akm_remember: "deny",
52116
+ akm_curate: "deny",
52117
+ bash: "deny",
52118
+ doom_loop: "deny",
52119
+ list: "deny",
52120
+ lsp: "deny",
52121
+ question: "deny",
52122
+ skill: "deny",
52123
+ task: "deny",
52124
+ todowrite: "deny",
52125
+ webfetch: "deny",
52126
+ websearch: "deny"
52127
+ };
52128
+ }
52129
+ function modelWorkPluginEnv() {
52130
+ return {
52131
+ AKM_AUTO_CURATE: "0",
52132
+ AKM_AUTO_LEARNING: "0",
52133
+ AKM_AUTO_SKILL_PROPOSALS: "0",
52134
+ AKM_WRITE_GATE: "off",
52135
+ XDG_STATE_HOME: path43.join(getStateDir(), "opencode-model-work"),
52136
+ ...process.platform === "win32" ? {} : { AKM_OPENCODE_CLI: "/bin/true" }
52137
+ };
52138
+ }
52166
52139
  function modelWorkOpencodeConfig(options) {
52140
+ let stash;
52141
+ try {
52142
+ stash = resolveStashDir();
52143
+ } catch {}
52144
+ const permission = modelWorkPermission(stash);
52167
52145
  return {
52168
- permission: { ...MODEL_WORK_PERMISSION },
52146
+ permission: { ...permission },
52169
52147
  compaction: { auto: false },
52170
52148
  agent: {
52171
52149
  [MODEL_WORK_OPENCODE_AGENT]: {
52172
52150
  mode: "primary",
52173
52151
  description: "akm unattended model work: read and edit inside its working directory only.",
52174
52152
  prompt: MODEL_WORK_PROMPT,
52175
- steps: MODEL_WORK_STEPS,
52176
52153
  ...options ? { options } : {},
52177
- permission: { ...MODEL_WORK_PERMISSION }
52154
+ permission: { ...permission }
52178
52155
  }
52179
52156
  }
52180
52157
  };
@@ -52189,18 +52166,12 @@ var opencodeBuilder = {
52189
52166
  personaChannel: "prompt",
52190
52167
  nativeAgentSelector: true,
52191
52168
  tools: "none",
52192
- modelWorkTools: true,
52193
- inference: (profile, request) => {
52194
- const model = request.model?.resolved ?? modelFromArgs(profile.args);
52195
- return opencodeCarriedKeys(model !== undefined && splitOpencodeModel(model) !== undefined, request.inference, isModelWorkTools(request.tools));
52196
- }
52169
+ inference: MODEL_WORK_AGENT_INFERENCE
52197
52170
  }),
52198
52171
  build(profile, req) {
52199
- const model = req.model ?? modelFromArgs(profile.args);
52200
- const modelWork = isModelWorkTools(req.tools);
52201
- const { entry, agentOptions } = opencodeInferenceConfig(req.inference, modelWork);
52202
- const modelConfig = opencodeModelConfig(model, entry);
52203
- if (modelWork) {
52172
+ if (req.modelWork) {
52173
+ const model = req.model ?? modelFromArgs(profile.args);
52174
+ const { agentOptions } = opencodeInferenceConfig(req.inference, true);
52204
52175
  return {
52205
52176
  argv: [
52206
52177
  profile.bin,
@@ -52211,7 +52182,10 @@ var opencodeBuilder = {
52211
52182
  "--",
52212
52183
  req.prompt
52213
52184
  ],
52214
- env: { OPENCODE_CONFIG_CONTENT: JSON.stringify({ ...modelWorkOpencodeConfig(agentOptions), ...modelConfig }) }
52185
+ env: {
52186
+ ...modelWorkPluginEnv(),
52187
+ OPENCODE_CONFIG_CONTENT: JSON.stringify(modelWorkOpencodeConfig(agentOptions))
52188
+ }
52215
52189
  };
52216
52190
  }
52217
52191
  const args = req.model ? [] : [...profile.args];
@@ -52236,23 +52210,20 @@ var opencodeBuilder = {
52236
52210
  }
52237
52211
  args.push("--");
52238
52212
  args.push(req.prompt);
52239
- return {
52240
- argv: [profile.bin, ...args],
52241
- ...modelConfig ? { env: { OPENCODE_CONFIG_CONTENT: JSON.stringify(modelConfig) } } : {}
52242
- };
52213
+ return { argv: [profile.bin, ...args] };
52243
52214
  }
52244
52215
  };
52245
52216
 
52246
52217
  // src/integrations/harnesses/opencode/session-log.ts
52247
52218
  import fs37 from "node:fs";
52248
52219
  import os6 from "node:os";
52249
- import path44 from "node:path";
52220
+ import path45 from "node:path";
52250
52221
 
52251
52222
  // src/storage/sqlite-read-snapshot.ts
52252
52223
  import { spawnSync as spawnSync5 } from "node:child_process";
52253
52224
  import fs36 from "node:fs";
52254
52225
  import os5 from "node:os";
52255
- import path43 from "node:path";
52226
+ import path44 from "node:path";
52256
52227
  var SNAPSHOT_MAX_ATTEMPTS = 8;
52257
52228
  var SNAPSHOT_BACKOFF_MS = 25;
52258
52229
 
@@ -52317,8 +52288,8 @@ function copyFileOutsideThisProcess(source, destination) {
52317
52288
  function openSqliteReadSnapshot(dbPath) {
52318
52289
  if (!pathExists(dbPath))
52319
52290
  return;
52320
- const snapshotDir = fs36.mkdtempSync(path43.join(os5.tmpdir(), "akm-sqlite-read-"));
52321
- const snapshotPath = path43.join(snapshotDir, "snapshot.db");
52291
+ const snapshotDir = fs36.mkdtempSync(path44.join(os5.tmpdir(), "akm-sqlite-read-"));
52292
+ const snapshotPath = path44.join(snapshotDir, "snapshot.db");
52322
52293
  let db;
52323
52294
  try {
52324
52295
  let copied = false;
@@ -52388,9 +52359,9 @@ function openSqliteReadSnapshot(dbPath) {
52388
52359
  // src/integrations/harnesses/opencode/session-log.ts
52389
52360
  function getOpenCodeBaseDir() {
52390
52361
  if (process.platform === "darwin") {
52391
- return path44.join(os6.homedir(), "Library", "Application Support", "opencode");
52362
+ return path45.join(os6.homedir(), "Library", "Application Support", "opencode");
52392
52363
  }
52393
- return path44.join(os6.homedir(), ".local", "share", "opencode");
52364
+ return path45.join(os6.homedir(), ".local", "share", "opencode");
52394
52365
  }
52395
52366
  var OPENCODE_DB_FILENAME = "opencode.db";
52396
52367
 
@@ -52401,7 +52372,7 @@ class OpenCodeProvider extends AbstractSessionLogProvider {
52401
52372
  return this.#dbPath(this.#baseDir);
52402
52373
  }
52403
52374
  #dbPath(base) {
52404
- return path44.join(base, OPENCODE_DB_FILENAME);
52375
+ return path45.join(base, OPENCODE_DB_FILENAME);
52405
52376
  }
52406
52377
  listSessions(input = {}) {
52407
52378
  const base = input.location ?? this.#baseDir;
@@ -52539,8 +52510,7 @@ class OpencodeSdkHarness extends BaseHarness {
52539
52510
  personaChannel: "native",
52540
52511
  nativeAgentSelector: true,
52541
52512
  tools: "sdk",
52542
- modelWorkTools: true,
52543
- inference: (_profile, request) => opencodeCarriedKeys(Boolean(request.model?.resolved), request.inference, isModelWorkTools(request.tools))
52513
+ inference: MODEL_WORK_AGENT_INFERENCE
52544
52514
  })
52545
52515
  };
52546
52516
  capabilities = caps({
@@ -52817,11 +52787,6 @@ var VALID_HARNESS_IDS2 = Object.freeze(HARNESS_REGISTRY.map((h) => h.id));
52817
52787
  var SESSION_LOG_HARNESSES = HARNESS_REGISTRY.filter(isSessionLogHarness);
52818
52788
  var AGENT_DISPATCH_HARNESSES = HARNESS_REGISTRY.filter((h) => h.capabilities.agentDispatch);
52819
52789
 
52820
- // src/integrations/agent/model-map.ts
52821
- var ENGINE_KEY_PATTERN = new RegExp(ENGINE_NAME_PATTERN_SOURCE);
52822
- var RESERVED_MAP_KEYS = new Set(["__proto__", "constructor", "prototype", "tostring"]);
52823
- var PROFILE_FIELD_KEYS = new Set(["model", "inference", "engine"]);
52824
-
52825
52790
  // src/core/spawn-env.ts
52826
52791
  var COMMON_SPAWN_ENV_PASSTHROUGH = [
52827
52792
  "HOME",
@@ -52833,6 +52798,12 @@ var COMMON_SPAWN_ENV_PASSTHROUGH = [
52833
52798
  "TMPDIR",
52834
52799
  "AKM_EVENT_SOURCE"
52835
52800
  ];
52801
+ var XDG_BASE_DIR_ENV_PASSTHROUGH = [
52802
+ "XDG_CONFIG_HOME",
52803
+ "XDG_DATA_HOME",
52804
+ "XDG_CACHE_HOME",
52805
+ "XDG_STATE_HOME"
52806
+ ];
52836
52807
  var supplementedPaths = new Map;
52837
52808
 
52838
52809
  // src/integrations/agent/profiles.ts
@@ -52843,7 +52814,7 @@ var BUILTINS = {
52843
52814
  bin: "opencode",
52844
52815
  args: ["run"],
52845
52816
  stdio: "interactive",
52846
- envPassthrough: [...COMMON_PASSTHROUGH, "OPENCODE_API_KEY", "OPENCODE_CONFIG"],
52817
+ envPassthrough: [...COMMON_PASSTHROUGH, "OPENCODE_API_KEY", "OPENCODE_CONFIG", ...XDG_BASE_DIR_ENV_PASSTHROUGH],
52847
52818
  parseOutput: "text"
52848
52819
  },
52849
52820
  claude: {
@@ -52923,6 +52894,11 @@ var FALLBACK_ANNOUNCEMENT = `No engine is configured; falling back to \`${FALLBA
52923
52894
  var NO_ENGINE_MESSAGE_SUFFIX = `has no selected engine, and no usable \`${OPENCODE_SDK_SERVER_BIN}\` binary was found to fall back to.`;
52924
52895
  var NO_ENGINE_REMEDY = "Run `akm setup` to detect an installed agent, or set one explicitly: " + '`akm config set engines.claude \'{"kind":"agent","platform":"claude"}\'` ' + "then `akm config set defaults.engine claude`. " + `Installing the \`${OPENCODE_SDK_SERVER_BIN}\` binary also works ` + "(`npm i -g opencode-ai`) — akm falls back to it automatically.";
52925
52896
 
52897
+ // src/integrations/agent/model-map.ts
52898
+ var ENGINE_KEY_PATTERN = new RegExp(ENGINE_NAME_PATTERN_SOURCE);
52899
+ var RESERVED_MAP_KEYS = new Set(["__proto__", "constructor", "prototype", "tostring"]);
52900
+ var PROFILE_FIELD_KEYS = new Set(["model", "inference", "engine"]);
52901
+
52926
52902
  // src/storage/repositories/improve-ledger-repository.ts
52927
52903
  var LEDGER_REJECTION_WINDOW_DAYS = Object.freeze({
52928
52904
  reflect: 14,
@@ -53312,7 +53288,7 @@ Commands:
53312
53288
 
53313
53289
  // scripts/akm-migrate/migrate/dead-residue.ts
53314
53290
  import fs38 from "node:fs";
53315
- import path45 from "node:path";
53291
+ import path46 from "node:path";
53316
53292
  var DEAD_RESIDUE_PATHS = [
53317
53293
  { name: "proposals", reason: "superseded by the `proposals` table in $DATA/state.db (0.9.0)" },
53318
53294
  { prefix: "runs.archived-", reason: "orphaned archive of a directory that no longer exists" },
@@ -53334,7 +53310,7 @@ function dirSizeBytes(target) {
53334
53310
  return 0;
53335
53311
  }
53336
53312
  for (const entry of entries) {
53337
- const entryPath = path45.join(target, entry.name);
53313
+ const entryPath = path46.join(target, entry.name);
53338
53314
  if (entry.isDirectory()) {
53339
53315
  total += dirSizeBytes(entryPath);
53340
53316
  } else if (entry.isFile()) {
@@ -53352,15 +53328,15 @@ function sizeOf(target) {
53352
53328
  function hostResidue() {
53353
53329
  const data = getDataDir();
53354
53330
  const state = getStateDir();
53355
- const configDir = path45.dirname(getConfigPath());
53331
+ const configDir = path46.dirname(getConfigPath());
53356
53332
  const named = [
53357
- { absolutePath: path45.join(data, "txn"), reason: "filesystem transaction journals (removed in 0.9.17)" },
53358
- { absolutePath: path45.join(data, "txn-quarantine"), reason: "quarantined transaction journals (removed in 0.9.17)" },
53359
- { absolutePath: path45.join(data, "maintenance.barrier.lock"), reason: "maintenance barrier (removed in 0.9.17)" },
53360
- { absolutePath: path45.join(data, "maintenance-activities"), reason: "per-process activity registry (removed in 0.9.17)" },
53361
- { absolutePath: path45.join(state, "maintenance-activities"), reason: "per-process activity registry (removed in 0.9.17)" },
53362
- { absolutePath: path45.join(state, "version-reconcile.json"), reason: "startup reconcile stamp (removed in 0.9.17)" },
53363
- { absolutePath: path45.join(state, "locks", "version-reconcile.lock"), reason: "startup reconcile lock (removed in 0.9.17)" }
53333
+ { absolutePath: path46.join(data, "txn"), reason: "filesystem transaction journals (removed in 0.9.17)" },
53334
+ { absolutePath: path46.join(data, "txn-quarantine"), reason: "quarantined transaction journals (removed in 0.9.17)" },
53335
+ { absolutePath: path46.join(data, "maintenance.barrier.lock"), reason: "maintenance barrier (removed in 0.9.17)" },
53336
+ { absolutePath: path46.join(data, "maintenance-activities"), reason: "per-process activity registry (removed in 0.9.17)" },
53337
+ { absolutePath: path46.join(state, "maintenance-activities"), reason: "per-process activity registry (removed in 0.9.17)" },
53338
+ { absolutePath: path46.join(state, "version-reconcile.json"), reason: "startup reconcile stamp (removed in 0.9.17)" },
53339
+ { absolutePath: path46.join(state, "locks", "version-reconcile.lock"), reason: "startup reconcile lock (removed in 0.9.17)" }
53364
53340
  ];
53365
53341
  const found = [];
53366
53342
  const claimed = new Set;
@@ -53395,7 +53371,7 @@ function findSidecars(root, depth, skip) {
53395
53371
  }
53396
53372
  const found = [];
53397
53373
  for (const entry of entries) {
53398
- const entryPath = path45.join(root, entry.name);
53374
+ const entryPath = path46.join(root, entry.name);
53399
53375
  if (entry.isFile() && entry.name.endsWith(".operations.sensitive"))
53400
53376
  found.push(entryPath);
53401
53377
  else if (entry.isDirectory() && depth > 0 && !skip.has(entryPath)) {
@@ -53408,7 +53384,7 @@ function findDeadResidueEntries(stashDir) {
53408
53384
  const host = hostResidue();
53409
53385
  if (stashDir === undefined)
53410
53386
  return host;
53411
- const akmDir = path45.join(stashDir, ".akm");
53387
+ const akmDir = path46.join(stashDir, ".akm");
53412
53388
  let names;
53413
53389
  try {
53414
53390
  names = fs38.readdirSync(akmDir);
@@ -53421,14 +53397,14 @@ function findDeadResidueEntries(stashDir) {
53421
53397
  for (const name of matches) {
53422
53398
  if (!names.includes(name))
53423
53399
  continue;
53424
- const absolutePath = path45.join(akmDir, name);
53400
+ const absolutePath = path46.join(akmDir, name);
53425
53401
  let sizeBytes;
53426
53402
  try {
53427
53403
  sizeBytes = sizeOf(absolutePath);
53428
53404
  } catch {
53429
53405
  continue;
53430
53406
  }
53431
- found.push({ relativePath: path45.join(".akm", name), absolutePath, sizeBytes, reason: spec.reason });
53407
+ found.push({ relativePath: path46.join(".akm", name), absolutePath, sizeBytes, reason: spec.reason });
53432
53408
  }
53433
53409
  }
53434
53410
  return found;
@@ -53454,16 +53430,16 @@ function removeDeadResidue(stashDir) {
53454
53430
  import { randomUUID as randomUUID3 } from "node:crypto";
53455
53431
  import fs40 from "node:fs";
53456
53432
  import os7 from "node:os";
53457
- import path48 from "node:path";
53433
+ import path49 from "node:path";
53458
53434
 
53459
53435
  // scripts/akm-migrate/migrate/task-files.ts
53460
53436
  import crypto4 from "node:crypto";
53461
53437
  import fs39 from "node:fs";
53462
- import path47 from "node:path";
53438
+ import path48 from "node:path";
53463
53439
 
53464
53440
  // src/tasks/source/task-to-v4.ts
53465
53441
  import crypto3 from "node:crypto";
53466
- import path46 from "node:path";
53442
+ import path47 from "node:path";
53467
53443
 
53468
53444
  // src/tasks/task-id.ts
53469
53445
  var VALID_TASK_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
@@ -54272,7 +54248,7 @@ function addSharedNonPromptOverrides(data, akm) {
54272
54248
  }
54273
54249
  function promptSourceKind(raw) {
54274
54250
  const trimmed = raw.trim();
54275
- if (trimmed.startsWith("./") || trimmed.startsWith("../") || path46.isAbsolute(trimmed) || /^[A-Za-z]:[\\/]/.test(trimmed)) {
54251
+ if (trimmed.startsWith("./") || trimmed.startsWith("../") || path47.isAbsolute(trimmed) || /^[A-Za-z]:[\\/]/.test(trimmed)) {
54276
54252
  return "file";
54277
54253
  }
54278
54254
  try {
@@ -54376,7 +54352,7 @@ function planV2DataToV4(input, data) {
54376
54352
  return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
54377
54353
  }
54378
54354
  try {
54379
- validateTaskId(path46.basename(input.filePath, ".yml"));
54355
+ validateTaskId(path47.basename(input.filePath, ".yml"));
54380
54356
  } catch (cause) {
54381
54357
  return blocked(input, "invalid-v2-task", causeMessage(cause));
54382
54358
  }
@@ -54521,7 +54497,7 @@ function taskToV4PlanFromOutcomes(outcomes) {
54521
54497
  for (let index = 1;index < files.length; index += 1) {
54522
54498
  const previous = files[index - 1];
54523
54499
  const current = files[index];
54524
- if (previous && current && path46.resolve(previous.filePath) === path46.resolve(current.filePath)) {
54500
+ if (previous && current && path47.resolve(previous.filePath) === path47.resolve(current.filePath)) {
54525
54501
  throw new Error(`duplicate task migration file path: ${current.filePath}`);
54526
54502
  }
54527
54503
  }
@@ -54533,8 +54509,8 @@ function migrationError(detail) {
54533
54509
  return new ConfigError(`Task migration failed: ${detail}`, "INVALID_CONFIG_FILE");
54534
54510
  }
54535
54511
  function contained2(root, candidate) {
54536
- const relative = path47.relative(root, candidate);
54537
- return relative === "" || !relative.startsWith("..") && !path47.isAbsolute(relative);
54512
+ const relative = path48.relative(root, candidate);
54513
+ return relative === "" || !relative.startsWith("..") && !path48.isAbsolute(relative);
54538
54514
  }
54539
54515
  function realDirectory(filePath) {
54540
54516
  const stat = fs39.lstatSync(filePath);
@@ -54562,7 +54538,7 @@ function walkTasks(root, tasksDir, out) {
54562
54538
  throw migrationError(`${directory} resolves outside bundle ${root.bundleId}.`);
54563
54539
  }
54564
54540
  for (const entry of fs39.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
54565
- const candidate = path47.join(directory, entry.name);
54541
+ const candidate = path48.join(directory, entry.name);
54566
54542
  if (entry.isSymbolicLink())
54567
54543
  throw migrationError(`task migration does not follow symbolic link ${candidate}.`);
54568
54544
  if (entry.isDirectory()) {
@@ -54588,7 +54564,7 @@ function walkTasks(root, tasksDir, out) {
54588
54564
  function inspectTaskFiles(roots) {
54589
54565
  const files = [];
54590
54566
  for (const root of [...roots].sort((a, b) => a.bundleId.localeCompare(b.bundleId))) {
54591
- const tasksDir = root.layout === "akm-task" ? root.root : path47.join(root.root, "tasks");
54567
+ const tasksDir = root.layout === "akm-task" ? root.root : path48.join(root.root, "tasks");
54592
54568
  try {
54593
54569
  const stat = fs39.lstatSync(tasksDir);
54594
54570
  if (stat.isSymbolicLink())
@@ -54608,8 +54584,8 @@ function planTaskFilesMigration(inputs) {
54608
54584
  return taskToV4PlanFromOutcomes(inputs.map(planTaskToV4File));
54609
54585
  }
54610
54586
  function taskFileBackupPath(backupRoot, filePath) {
54611
- const digest = crypto4.createHash("sha256").update(path47.resolve(filePath)).digest("hex").slice(0, 16);
54612
- return path47.join(backupRoot, "files", `${digest}-${path47.basename(filePath)}`);
54587
+ const digest = crypto4.createHash("sha256").update(path48.resolve(filePath)).digest("hex").slice(0, 16);
54588
+ return path48.join(backupRoot, "files", `${digest}-${path48.basename(filePath)}`);
54613
54589
  }
54614
54590
  function fsyncDirectory(directory) {
54615
54591
  if (process.platform === "win32")
@@ -54628,7 +54604,7 @@ function fsyncDirectory(directory) {
54628
54604
  }
54629
54605
  }
54630
54606
  function writeDurable(filePath, bytes, mode, exclusive = false) {
54631
- fs39.mkdirSync(path47.dirname(filePath), { recursive: true });
54607
+ fs39.mkdirSync(path48.dirname(filePath), { recursive: true });
54632
54608
  const fd = fs39.openSync(filePath, exclusive ? "wx" : "w", mode);
54633
54609
  try {
54634
54610
  fs39.writeFileSync(fd, bytes);
@@ -54637,14 +54613,14 @@ function writeDurable(filePath, bytes, mode, exclusive = false) {
54637
54613
  fs39.closeSync(fd);
54638
54614
  }
54639
54615
  fs39.chmodSync(filePath, mode);
54640
- fsyncDirectory(path47.dirname(filePath));
54616
+ fsyncDirectory(path48.dirname(filePath));
54641
54617
  }
54642
54618
  function replaceAtomically(filePath, bytes, mode) {
54643
- const temporary = path47.join(path47.dirname(filePath), `.${path47.basename(filePath)}.migrate-${crypto4.randomUUID()}`);
54619
+ const temporary = path48.join(path48.dirname(filePath), `.${path48.basename(filePath)}.migrate-${crypto4.randomUUID()}`);
54644
54620
  try {
54645
54621
  writeDurable(temporary, bytes, mode, true);
54646
54622
  fs39.renameSync(temporary, filePath);
54647
- fsyncDirectory(path47.dirname(filePath));
54623
+ fsyncDirectory(path48.dirname(filePath));
54648
54624
  } finally {
54649
54625
  fs39.rmSync(temporary, { force: true });
54650
54626
  }
@@ -54681,7 +54657,7 @@ function expandTilde(value) {
54681
54657
  if (value === "~")
54682
54658
  return os7.homedir();
54683
54659
  if (value.startsWith("~/") || value.startsWith("~\\"))
54684
- return path48.join(os7.homedir(), value.slice(2));
54660
+ return path49.join(os7.homedir(), value.slice(2));
54685
54661
  return value;
54686
54662
  }
54687
54663
  function existingDirectory(target) {
@@ -54707,14 +54683,14 @@ function taskRoots(config, resolutionBase = process.cwd()) {
54707
54683
  const source = sources.get(bundleId);
54708
54684
  if (!source)
54709
54685
  continue;
54710
- const configuredRoot = source.type === "filesystem" && source.path ? path48.resolve(resolutionBase, expandTilde(source.path)) : lockContentRootFor(bundleId, source.type);
54686
+ const configuredRoot = source.type === "filesystem" && source.path ? path49.resolve(resolutionBase, expandTilde(source.path)) : lockContentRootFor(bundleId, source.type);
54711
54687
  if (!configuredRoot || !existingDirectory(configuredRoot))
54712
54688
  continue;
54713
- const bundleRoot = path48.resolve(configuredRoot);
54689
+ const bundleRoot = path49.resolve(configuredRoot);
54714
54690
  const component = bundleComponentConfig(bundle);
54715
- const componentRoot = path48.resolve(bundleRoot, component?.root ?? ".");
54716
- const relative = path48.relative(bundleRoot, componentRoot);
54717
- if (relative === ".." || relative.startsWith(`..${path48.sep}`) || path48.isAbsolute(relative)) {
54691
+ const componentRoot = path49.resolve(bundleRoot, component?.root ?? ".");
54692
+ const relative = path49.relative(bundleRoot, componentRoot);
54693
+ if (relative === ".." || relative.startsWith(`..${path49.sep}`) || path49.isAbsolute(relative)) {
54718
54694
  throw new ConfigError(`Task migration component root ${componentRoot} escapes bundle ${bundleId} at ${bundleRoot}.`, "INVALID_CONFIG_FILE");
54719
54695
  }
54720
54696
  if (!existingDirectory(componentRoot))
@@ -54731,7 +54707,7 @@ function taskRoots(config, resolutionBase = process.cwd()) {
54731
54707
  }
54732
54708
  const envStash = process.env.AKM_BUNDLE_DIR?.trim();
54733
54709
  if (envStash && existingDirectory(envStash)) {
54734
- const stashRoot = path48.resolve(envStash);
54710
+ const stashRoot = path49.resolve(envStash);
54735
54711
  if (!rootsByPath.has(stashRoot)) {
54736
54712
  const bundleId = deriveBundleId(undefined, stashRoot, new Set(Object.keys(config.bundles ?? {})));
54737
54713
  const candidate = taskRoot(bundleId, stashRoot, stashRoot, detectAdapterId(stashRoot), () => true);
@@ -54800,7 +54776,7 @@ function applyTaskFilesMigration() {
54800
54776
  const before = inspectCurrent();
54801
54777
  if (before.result.taskFiles.changed === 0)
54802
54778
  return before.result;
54803
- const backupPath = path48.join(getDataDir(), "backups", "tasks", `${Date.now()}-${randomUUID3()}`);
54779
+ const backupPath = path49.join(getDataDir(), "backups", "tasks", `${Date.now()}-${randomUUID3()}`);
54804
54780
  const applied = applyTaskFilesPlan(before.plan, { backupRoot: backupPath });
54805
54781
  return { ...inspectCurrent().result, backupPath, applied: applied.changed.length };
54806
54782
  });