@jmanuelcorral/openteam 0.23.0 → 0.24.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.
Files changed (63) hide show
  1. package/README.es.md +28 -8
  2. package/README.md +24 -7
  3. package/dist/certificates/graph-release-certificate.json +2 -2
  4. package/dist/certificates/graph-shadow-certificate.json +2 -2
  5. package/dist/cli/setupAdapters.d.ts +2 -2
  6. package/dist/cli/setupAdapters.d.ts.map +1 -1
  7. package/dist/cli.js +955 -380
  8. package/dist/commands/agents.d.ts +6 -4
  9. package/dist/commands/agents.d.ts.map +1 -1
  10. package/dist/commands/dispatch.d.ts +1 -1
  11. package/dist/commands/dispatch.d.ts.map +1 -1
  12. package/dist/commands/setup.d.ts +22 -33
  13. package/dist/commands/setup.d.ts.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1497 -334
  16. package/dist/local/lemonade.d.ts.map +1 -1
  17. package/dist/local/lemonadeRequestGuard.d.ts +11 -0
  18. package/dist/local/lemonadeRequestGuard.d.ts.map +1 -0
  19. package/dist/local/lemonadeResidency.d.ts +22 -0
  20. package/dist/local/lemonadeResidency.d.ts.map +1 -0
  21. package/dist/local/modelResidency.d.ts +4 -0
  22. package/dist/local/modelResidency.d.ts.map +1 -0
  23. package/dist/local/slotPool.d.ts +13 -7
  24. package/dist/local/slotPool.d.ts.map +1 -1
  25. package/dist/messages/executionSetup.d.ts +19 -2
  26. package/dist/messages/executionSetup.d.ts.map +1 -1
  27. package/dist/messages/modelResidency.d.ts +19 -0
  28. package/dist/messages/modelResidency.d.ts.map +1 -0
  29. package/dist/messages/orchestration.d.ts +1 -0
  30. package/dist/messages/orchestration.d.ts.map +1 -1
  31. package/dist/messages/reload.d.ts +21 -0
  32. package/dist/messages/reload.d.ts.map +1 -0
  33. package/dist/opencodeArtifacts/createRosterReload.d.ts +2 -0
  34. package/dist/opencodeArtifacts/createRosterReload.d.ts.map +1 -0
  35. package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
  36. package/dist/opencodeArtifacts/slashCommand.d.ts.map +1 -1
  37. package/dist/opencodeArtifacts/teamPreparation.d.ts.map +1 -1
  38. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  39. package/dist/orchestrator/roster.d.ts +0 -7
  40. package/dist/orchestrator/roster.d.ts.map +1 -1
  41. package/dist/orchestrator/runtimeSelection.d.ts +4 -3
  42. package/dist/orchestrator/runtimeSelection.d.ts.map +1 -1
  43. package/dist/orchestrator/sessionStatus.d.ts +12 -0
  44. package/dist/orchestrator/sessionStatus.d.ts.map +1 -0
  45. package/dist/orchestrator/subsessions.d.ts +2 -10
  46. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  47. package/dist/plugin/availability.d.ts +4 -1
  48. package/dist/plugin/availability.d.ts.map +1 -1
  49. package/dist/plugin/hooks.d.ts +2 -0
  50. package/dist/plugin/hooks.d.ts.map +1 -1
  51. package/dist/plugin/memoryTool.d.ts +9 -3
  52. package/dist/plugin/memoryTool.d.ts.map +1 -1
  53. package/dist/plugin/modelResidency.d.ts +47 -0
  54. package/dist/plugin/modelResidency.d.ts.map +1 -0
  55. package/dist/plugin/reloadTool.d.ts +33 -0
  56. package/dist/plugin/reloadTool.d.ts.map +1 -0
  57. package/dist/router/types.d.ts +2 -0
  58. package/dist/router/types.d.ts.map +1 -1
  59. package/dist/telemetry/diagnostics.d.ts +1 -1
  60. package/dist/telemetry/diagnostics.d.ts.map +1 -1
  61. package/dist/telemetry/events.d.ts +24 -0
  62. package/dist/telemetry/events.d.ts.map +1 -1
  63. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4103,6 +4103,28 @@ import { z as z13 } from "zod";
4103
4103
  // src/telemetry/events.ts
4104
4104
  import { z as z11 } from "zod";
4105
4105
 
4106
+ // src/messages/reload.ts
4107
+ var reloadMessages = {
4108
+ description: "Queue a project-instance reload only after the user explicitly answers the fresh /create_roster reload question affirmatively. General tool approval, YOLO and earlier project-description answers are not consent. End this turn after queuing; do not dispatch work. Cancel withdraws a pending request.",
4109
+ action: "Request a confirmed reload or cancel this session's queued request.",
4110
+ confirmed: "True only for an unambiguous affirmative human answer to the current reload question, after successful preparation and the full Agent/Role/Model and file summaries. This field records consent; it does not independently prove a human answered.",
4111
+ invalid: "Reload not queued: provide only action (request or cancel) and explicit boolean confirmed. No target directory or other arguments are accepted.",
4112
+ declined: "Reload not queued: explicit affirmative consent to the current reload question is required. No files or policies were changed.",
4113
+ denied: "Reload is restricted to the openteam primary in this plugin's project.",
4114
+ closed: "This reload controller is no longer active. Check the current opencode instance before requesting another reload; agent discovery has not been verified.",
4115
+ other: "A reload request is already pending for another session. This call does not replace or extend its confirmation.",
4116
+ authorizing: "Host permission for this project's reload is still pending. This call queues nothing and does not extend the confirmation deadline.",
4117
+ none: "There is no queued reload for this session to cancel.",
4118
+ "reload-queued": "Reload queued, not completed. Waiting up to 30 seconds for this tool and confirming turn to finish and for all project sessions to be idle. End this turn now; no project work or agent readiness is implied.",
4119
+ "reload-waiting": "Reload is still waiting: project sessions are busy or retrying. They will not be aborted; the original 30-second deadline is unchanged.",
4120
+ "reload-accepted": "opencode accepted the project-instance reload request. Agent discovery and readiness have not been verified; existing discovery safeguards still apply.",
4121
+ "reload-cancelled": "The pending reload was cancelled before disposal was requested. Prepared files, history and policies are unchanged.",
4122
+ "reload-timeout": "Reload was not requested: the 30-second idle deadline expired. A new explicit confirmation is required; nothing will retry automatically.",
4123
+ "reload-status-failed": "Reload was not requested: project activity was missing, malformed, unavailable or did not respond within 5 seconds. No idle state was assumed and no automatic retry is armed.",
4124
+ "reload-dispose-failed": "opencode did not confirm acceptance of the reload request. No automatic retry is armed; check the current instance before trying again. Agent readiness is unverified.",
4125
+ "reload-unconfirmed": "Reload acceptance is unconfirmed because the request timed out, was cancelled or the instance was disposed while it was in flight. The host may still finish it; no automatic retry is armed. Check the current instance and agent discovery."
4126
+ };
4127
+
4106
4128
  // src/telemetry/diagnostics.ts
4107
4129
  var DIAGNOSTIC_CODES = [
4108
4130
  "availability-refresh-failed",
@@ -4115,6 +4137,14 @@ var DIAGNOSTIC_CODES = [
4115
4137
  "opencode-server-url",
4116
4138
  "opencode-version-read-failed",
4117
4139
  "privacy-inert",
4140
+ "reload-queued",
4141
+ "reload-waiting",
4142
+ "reload-accepted",
4143
+ "reload-cancelled",
4144
+ "reload-timeout",
4145
+ "reload-status-failed",
4146
+ "reload-dispose-failed",
4147
+ "reload-unconfirmed",
4118
4148
  "soak-evidence-rejected",
4119
4149
  "telemetry-warning",
4120
4150
  "telemetry-write-failed",
@@ -4142,6 +4172,15 @@ function diagnosticDescription(code) {
4142
4172
  return "openteam could not read the live opencode version; graph gates fail closed when this happens.";
4143
4173
  case "privacy-inert":
4144
4174
  return routingMessages.privacyModeDeprecated;
4175
+ case "reload-queued":
4176
+ case "reload-waiting":
4177
+ case "reload-accepted":
4178
+ case "reload-cancelled":
4179
+ case "reload-timeout":
4180
+ case "reload-status-failed":
4181
+ case "reload-dispose-failed":
4182
+ case "reload-unconfirmed":
4183
+ return reloadMessages[code];
4145
4184
  case "soak-evidence-rejected":
4146
4185
  return "Graph soak evidence contained rejected observation lines; cutover gate evidence may be incomplete.";
4147
4186
  case "telemetry-warning":
@@ -4894,6 +4933,48 @@ function withDeterministicGitLocale(env) {
4894
4933
  };
4895
4934
  }
4896
4935
 
4936
+ // src/opencodeArtifacts/createRosterReload.ts
4937
+ var CREATE_ROSTER_RELOAD_INSTRUCTIONS = [
4938
+ "## Reload confirmation — /create_roster only",
4939
+ "",
4940
+ "Only after successful persisted registration and verified agent files may you offer reload:",
4941
+ "read back the parseable persisted roster, including guaranteed roles; actually create each missing",
4942
+ "subagent file and verify every required existing or created file.",
4943
+ "First show the complete Agent/Role/Model table returned by `openteam-register-cast`,",
4944
+ "including primary, guaranteed and preserved agents with their unchanged tool-returned policy values.",
4945
+ "Then show the created-versus-preserved file summary and any warnings. Both must be visible",
4946
+ "BEFORE asking a fresh explicit reload question in this opencode conversation.",
4947
+ "If registration, file creation or verification failed or is partial, do not ask the reload question:",
4948
+ "report the exact blocker and do not claim readiness. If registration succeeded, still show its",
4949
+ "complete table even when file preparation later fails.",
4950
+ "",
4951
+ "Ask whether to reload this project's opencode agent definitions now, explaining the request is",
4952
+ "queued/waiting until the requesting turn completes and all project sessions are idle.",
4953
+ "Use the host question tool when available; otherwise ask a normal conversational question",
4954
+ "in opencode and WAIT for the user's answer. Never use a terminal wizard, tool permission",
4955
+ "approval or YOLO permission as reload consent. A greenfield project-description reply is NOT reload consent.",
4956
+ "Only an explicit affirmative answer to this specific reload question authorizes",
4957
+ '`openteam-reload` with `{ action: "request", confirmed: true }`.',
4958
+ "False, no, cancel, an ambiguous answer or no reply means no reload request.",
4959
+ "A decline before anything is queued needs no tool call; do not turn ambiguity into confirmation.",
4960
+ "To cancel an already queued request at the user's direction, use",
4961
+ '`openteam-reload` with `{ action: "cancel", confirmed: false }` and report its actual result.',
4962
+ "",
4963
+ "This internal tool is restricted to the product primary `openteam` and its bound project.",
4964
+ "Do not supply a directory, project, session or other model-selected target.",
4965
+ "Report queued/waiting honestly and end the confirming turn immediately.",
4966
+ "Do not await your own idle state, poll or hold the turn open. Do not start work or call",
4967
+ "`task` or `openteam-orchestrate`; do not rewrite/recast files or change models, permissions",
4968
+ "or execution policies. The idle wait has a finite timeout; surface timeout and failure results",
4969
+ "when the runtime reports them, including rejected requests, rather than promising success or silently retrying.",
4970
+ "SDK acceptance is not verified discovery or readiness. Preserve all existing discovery guards:",
4971
+ "if new agents remain undiscovered, surface the reload/discovery blocker;",
4972
+ "do not dispatch through a generic substitute or claim readiness.",
4973
+ "This workflow and all its replies remain composition only. After completion, decline or cancellation,",
4974
+ "wait for a separate work request; neither a reload answer nor queued acceptance starts project work."
4975
+ ].join(`
4976
+ `);
4977
+
4897
4978
  // src/opencodeArtifacts/rosterSummary.ts
4898
4979
  var ROSTER_SUMMARY_INSTRUCTIONS = [
4899
4980
  "After every successful cast creation, repair or extension, show the complete Agent/Role/Model table",
@@ -4926,6 +5007,20 @@ var TEAM_PREPARATION_INSTRUCTIONS = [
4926
5007
  "machine bindings stay unchanged; do not require a terminal or manually edit policy to finish hiring.",
4927
5008
  "Never infer execution domains from prose, relax permissions, or invent a concrete auto model.",
4928
5009
  "Registration/review is idempotent: a capable configured team needs no extra members or file rewrites.",
5010
+ "`agentName`, not `roleID`, determines each worker filename.",
5011
+ "Create `.opencode/agent/<agentName>.md` only for registered roster entries.",
5012
+ "A built-in role profile is not an extra agent.",
5013
+ "Do not create a parallel `<roleID>.md` when the roster maps that role to another alias.",
5014
+ "An existing roster alias equal to its roleID is still valid; do not rename it.",
5015
+ "Do not run `openteam setup` to hire workers or repair their files.",
5016
+ "Preserve unrelated and legacy generic files; do not delete, overwrite or silently register them.",
5017
+ "Registration writes roster/profile state, not worker Markdown.",
5018
+ "Report only verified file changes; do not call pre-existing files newly created.",
5019
+ "When creating a scribe worker, put the following memory instructions in its charter:",
5020
+ "Use `openteam-memory` for durable, reusable facts, entities and typed relations.",
5021
+ "Send only redacted knowledge messages, never raw conversations, prompts or credentials.",
5022
+ "Honor operator permissions and memory settings; do not broaden them.",
5023
+ "A disabled-memory or nothing-recorded result is expected; do not retry it.",
4929
5024
  "Create only missing agent definitions; preserve customized files. Show the registration table",
4930
5025
  "and verify preparation before dispatch. If opencode cannot discover a new agent, surface",
4931
5026
  "the reload/discovery blocker; do not dispatch through a generic substitute or claim readiness."
@@ -4967,33 +5062,43 @@ function buildOrchestratorAgent(model, options = {}) {
4967
5062
  "primary agent: there is no pre-created team. You **build the team of",
4968
5063
  "subagents on demand** according to the goal of the user and the repository.",
4969
5064
  "",
4970
- "## Golden rule: check the team on EVERY request",
4971
- "",
4972
- "Before answering or delegating **any** request, your **first action",
4973
- "always** is to check whether a team already exists, even if the user does not",
4974
- "explicitly ask you to create it. Never assume there is a team or that there is not:",
4975
- `verify it by looking at \`${OPENTEAM_ROSTER_PATH}\` and the \`.opencode/agent/*.md\``,
4976
- "files with `mode: subagent` (use `read`/`glob`/`list`).",
4977
- "",
4978
- ROSTER_SUMMARY_INSTRUCTIONS,
4979
- "",
4980
- TEAM_PREPARATION_INSTRUCTIONS,
4981
- "",
4982
5065
  "## Roster composition takes precedence",
4983
5066
  "",
4984
5067
  "For `/create_roster`, including replies to its pending questions, follow",
4985
5068
  "the command's roster-only workflow: do not distribute tasks or start implementation.",
5069
+ "These rules override the ordinary golden rule and capability checks below.",
5070
+ "A bare yes to a pending reload question continues only reload confirmation:",
5071
+ "do not restart roster reads, capability preparation or registration.",
4986
5072
  "Reuse or conservatively extend the existing cast and preserve customized agent files.",
4987
5073
  "Use a supplied description; otherwise inspect real project source read-only.",
4988
5074
  "If there is no meaningful source and no description, ask what the user wants to",
4989
5075
  "build in this conversation and wait before any writes or cast registration.",
4990
5076
  "Register through `openteam-register-cast`, create missing subagent files, verify",
4991
- "the persisted roster and files, then stop. Do not call `openteam-orchestrate` or",
5077
+ "the persisted roster and files, then follow the reload confirmation below.",
5078
+ "Do not call `openteam-orchestrate` or",
4992
5079
  "`task` for this workflow. A later answer to your question continues composition",
4993
5080
  "only until it completes or the user cancels; it is not an implementation request.",
4994
5081
  "",
5082
+ CREATE_ROSTER_RELOAD_INSTRUCTIONS,
5083
+ "",
5084
+ "## Golden rule: check the team on EVERY request",
5085
+ "",
5086
+ "The roster composition and pending reload-reply rules above take precedence.",
5087
+ "Before answering or delegating **any ordinary work** request, your **first action",
5088
+ "always** is to check whether a team already exists, even if the user does not",
5089
+ "explicitly ask you to create it. Never assume there is a team or that there is not:",
5090
+ `verify it by looking at \`${OPENTEAM_ROSTER_PATH}\` and the \`.opencode/agent/*.md\``,
5091
+ "files with `mode: subagent` (use `read`/`glob`/`list`).",
5092
+ "",
5093
+ ROSTER_SUMMARY_INSTRUCTIONS,
5094
+ "",
5095
+ TEAM_PREPARATION_INSTRUCTIONS,
5096
+ "",
4995
5097
  "## For work requests, including analysis, review and documentation",
4996
5098
  "",
5099
+ "Ordinary automatic hiring must NOT ask for reload or automatically reset the project.",
5100
+ "If discovery is blocked, surface the blocker and preserve the discovery guards instead.",
5101
+ "",
4997
5102
  "- **If the team ALREADY exists** → review capability coverage, reuse suitable",
4998
5103
  " members and extend only for uncovered needs. Complete any existing member's",
4999
5104
  " profile through `openteam-register-cast`; do not hire for a configuration gap.",
@@ -5368,8 +5473,10 @@ function buildCreateRosterCommand() {
5368
5473
  "required agent file exists. If anything failed, report the incomplete state and exact",
5369
5474
  "blocker; do not delete preserved state or claim the team is ready.",
5370
5475
  "Summarize the cast, role rationale/evidence, preserved versus newly created files, and",
5371
- "any warnings. New subagents may require an opencode reload before discovery.",
5372
- "Stop after composition; wait for a separate request before starting project work.",
5476
+ "any warnings. Continue only to the reload confirmation below, including its pending replies;",
5477
+ "do not restart preparation when the user answers that reload question.",
5478
+ "",
5479
+ CREATE_ROSTER_RELOAD_INSTRUCTIONS,
5373
5480
  "",
5374
5481
  "## Project description (user input)",
5375
5482
  "",
@@ -5636,7 +5743,7 @@ import { z as z15 } from "zod";
5636
5743
  // package.json
5637
5744
  var package_default = {
5638
5745
  name: "@jmanuelcorral/openteam",
5639
- version: "0.23.0",
5746
+ version: "0.24.1",
5640
5747
  packageManager: "bun@1.3.14",
5641
5748
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
5642
5749
  license: "MIT",
@@ -5993,6 +6100,25 @@ var executionSetupMessages = {
5993
6100
  `),
5994
6101
  localOnlyTitle: "Local execution",
5995
6102
  localRequiredError: "local execution requires an enabled local runtime",
6103
+ lemonadeDetectedHint: (loaded, advertised) => `detected - ${loaded} confirmed loaded of ${advertised} advertised text model(s)`,
6104
+ lemonadeNoLoadedModels: "Lemonade has no text models verified as loaded and ready. Keep the intended model active in Lemonade and rerun setup; inactive or unknown models cannot be selected or entered manually.",
6105
+ lemonadeModelNotLoaded: (model) => `Lemonade model "${model}" is not positively verified as loaded and ready. Keep that exact text model active and rerun setup; no substitute was selected and no implicit loading is allowed.`,
6106
+ lemonadeProbeRequired: "Lemonade setup requires a fresh read-only residency probe before writing configuration. Use the CLI with its runtime probe enabled; manual model IDs cannot bypass this requirement.",
6107
+ lemonadeProbeFailed: "Lemonade residency could not be verified at the configured endpoint. Check connectivity and the read-only /models, /health, and /internal/aliases responses, then rerun setup with the intended text models already active.",
6108
+ lemonadeEndpointMismatch: "Lemonade endpoint/provider mapping differs from the selected runtime. Reconcile the configured base URL and provider before rerunning setup; no model was substituted.",
6109
+ lemonadeModelIdentityUnknown: (model) => `Lemonade model "${model}" has no usable registered wire identity in the effective opencode configuration. Fix its model declaration before rerunning setup; setup will not replace or guess an alias.`,
6110
+ lemonadeEffectiveConfigLabel: "effective setup configuration",
6111
+ lemonadeEffectiveConfigAmbiguous: "Cannot establish one effective openteam configuration from the configured plugin loaders. Reconcile their options before rerunning setup; no provider identity was guessed.",
6112
+ lemonadeRetainedProviderUnprotected: (providerID) => `Cannot safely keep retained Lemonade provider "${providerID}": the effective configuration must retain one matching Lemonade runtime, disabled when deselected. Reconcile the plugin options or machine overlay, or explicitly remove the provider registration before rerunning setup; no configuration was written.`,
6113
+ lemonadeV2InvalidProvider: (providerID) => `Lemonade provider ${JSON.stringify(providerID)} cannot be represented as an exact native V2 policy resource. Choose a non-empty provider ID without wildcards, slashes, backslashes, or whitespace; setup will not broaden a deny rule.`,
6114
+ lemonadeV2InvalidPolicies: "Cannot safely preserve experimental.policies: experimental must be an object and policies must be an array of provider.use statements with a string resource and allow/deny effect. Fix the project configuration before rerunning setup.",
6115
+ lemonadeV2Title: "Lemonade transport compatibility",
6116
+ lemonadeV2Restriction: (providers) => `Project policies disable native V2 Lemonade access for: ${providers.join(", ")}. Ordinary guarded V1 configuration remains supported; V2 is not an openteam-hook execution path. Higher-precedence or global policies can override this project restriction; it is not a server-atomic no-autoload guarantee.`,
6117
+ lemonadeInventory: (instanceID, providerID, models) => {
6118
+ const inventory = models.map((model) => `${model.modelID} (${model.loaded === true ? "loaded" : model.loaded === false ? "inactive" : "residency unknown"})`).join(", ");
6119
+ return `Lemonade inventory ${instanceID} (provider ${providerID}): ${inventory || "no advertised text models"}. Only confirmed loaded text models count as ready.`;
6120
+ },
6121
+ lemonadeAgentResidency: (agent, model, state) => `Lemonade readiness: ${agent} (${model}): ${state === "not-loaded" ? "inactive (not ready)" : "residency unknown"}; advertised inventory is not loaded readiness. Keep this exact model active and verify residency before use; requests without verified readiness are blocked.`,
5996
6122
  mixedPrimaryQuestion: "Primary coordinator model",
5997
6123
  mixedPrimaryLocal: (model) => `Use local thinking model ${model}`,
5998
6124
  mixedPrimaryFrontier: "Choose a frontier model",
@@ -6015,12 +6141,14 @@ var executionSetupMessages = {
6015
6141
  `Local runtimes: ${params.localRuntimes}`,
6016
6142
  `YOLO mode: ${params.yolo ? "enabled (auto-approves permissions)" : "disabled"}`,
6017
6143
  "Web Console: launched separately with 'openteam console' (multi-session, loopback)",
6018
- `Wrote: ${params.opencodePath}, ${params.configPath}, ${params.agentPath}, ${params.agentDir}/*.md (${params.roleAgentCount} standard subagents), ${params.commandDir}/*.md (${params.commandCount} commands); ensured ${params.gitignorePath}`,
6144
+ `Wrote: ${params.opencodePath}, ${params.configPath}, ${params.agentPath}, ${params.commandDir}/*.md (${params.commandCount} commands); ensured ${params.gitignorePath}`,
6145
+ "Worker files are created through conversational roster preparation, not setup; existing worker files were left untouched.",
6019
6146
  "",
6020
6147
  "Next steps:",
6021
6148
  params.executionMode === "local" ? " 1. No frontier authentication is required while local execution is active" : " 1. Authenticate the frontier provider: opencode auth login",
6022
6149
  params.executionMode === "local" ? executionSetupMessages.localRuntimeNextStep : params.executionMode === "mixed" ? executionSetupMessages.mixedNextStep : executionSetupMessages.frontierNextStep,
6023
- " 3. Press Tab and pick the 'openteam' agent, or type / and pick an /openteam… command"
6150
+ " 3. Press Tab and pick the 'openteam' agent, or type / and pick an /openteam… command",
6151
+ " 4. Use /create_roster to compose the team without starting project work, or describe a task to prepare the team and begin work"
6024
6152
  ],
6025
6153
  configuredTitle: "openteam configured",
6026
6154
  complete: (paths) => `openteam setup complete: ${paths}`,
@@ -9822,6 +9950,243 @@ function ensureFoundryV1BaseURL(endpoint) {
9822
9950
  }
9823
9951
  }
9824
9952
 
9953
+ // src/local/lemonadeResidency.ts
9954
+ import { z as z19 } from "zod";
9955
+
9956
+ // src/messages/modelResidency.ts
9957
+ var reasons = {
9958
+ unknown: "fresh residency metadata is unavailable, malformed, or ambiguous",
9959
+ inactive: "the requested model is not verified as resident and serving",
9960
+ identity: "the requested model identity can be rewritten or is ambiguous",
9961
+ destination: "the request destination does not match its configured runtime",
9962
+ transport: "this provider execution path cannot enforce the residency guard",
9963
+ request: "this is not a supported, explicit model inference request",
9964
+ cancelled: "the request was cancelled before residency could be verified",
9965
+ timeout: "the residency metadata deadline expired",
9966
+ configuration: "the residency guard configuration is invalid"
9967
+ };
9968
+ var modelResidencyMessages = {
9969
+ blocked: (reason) => `[openteam] Lemonade model request blocked: ${reasons[reason]}.`,
9970
+ providerDisabled: (providerID) => `[openteam] Lemonade provider "${providerID}" is disabled because its configured transport or runtime mapping cannot enforce loaded-only requests.`,
9971
+ extractionRuntimeUnavailable: "[openteam] Memory extraction requires one enabled local runtime matching the configured extraction provider."
9972
+ };
9973
+
9974
+ // src/local/lemonadeResidency.ts
9975
+ class ModelResidencyError extends Error {
9976
+ code;
9977
+ statusCode = 400;
9978
+ isRetryable = false;
9979
+ constructor(code) {
9980
+ super(modelResidencyMessages.blocked(code));
9981
+ this.code = code;
9982
+ this.name = "ModelResidencyError";
9983
+ }
9984
+ }
9985
+ var modelName = z19.string().min(1).refine((name) => name === name.trim());
9986
+ var LoadedModelSchema = z19.object({
9987
+ model_name: modelName,
9988
+ loaded: z19.boolean().optional(),
9989
+ status: z19.string().optional(),
9990
+ backend_alive: z19.boolean().optional(),
9991
+ backend_health: z19.string().optional(),
9992
+ watchdog_reset: z19.boolean().optional(),
9993
+ watchdog_reset_reason: z19.string().optional(),
9994
+ residency_class: z19.string().optional(),
9995
+ slot_pool: z19.string().optional(),
9996
+ checkpoint: z19.string().optional(),
9997
+ type: z19.string().optional(),
9998
+ device: z19.string().optional(),
9999
+ backend_url: z19.string().optional(),
10000
+ pid: z19.number().int().optional(),
10001
+ launch_command: z19.unknown().optional(),
10002
+ pinned: z19.boolean().optional(),
10003
+ recipe: z19.string().optional(),
10004
+ recipe_options: z19.unknown().optional(),
10005
+ is_busy: z19.boolean().optional(),
10006
+ is_streaming: z19.boolean().optional(),
10007
+ max_context_window: z19.number().optional(),
10008
+ cost_input_per_million: z19.number().optional(),
10009
+ cost_output_per_million: z19.number().optional(),
10010
+ last_use: z19.number().optional()
10011
+ }).strict();
10012
+ var TelemetrySchema = z19.object({
10013
+ enabled: z19.boolean(),
10014
+ captures: z19.array(z19.string()).optional()
10015
+ }).strict();
10016
+ var HealthSchema = z19.object({
10017
+ status: z19.literal("ok"),
10018
+ all_models_loaded: z19.array(LoadedModelSchema),
10019
+ version: z19.string().optional(),
10020
+ model_loaded: z19.string().nullable().optional(),
10021
+ telemetry: TelemetrySchema.optional(),
10022
+ max_models: z19.record(z19.string(), z19.number().int()).optional(),
10023
+ pinned_models: z19.record(z19.string(), z19.number().int()).optional(),
10024
+ pinned_helper_models: z19.record(z19.string(), z19.number().int()).optional(),
10025
+ websocket_port: z19.number().int().optional(),
10026
+ update_check_done: z19.boolean().optional()
10027
+ }).strict();
10028
+ var AliasSchema = z19.object({
10029
+ alias: modelName,
10030
+ target: modelName,
10031
+ downloaded: z19.boolean().optional(),
10032
+ recipe: z19.string().optional()
10033
+ }).strict();
10034
+ var AliasesSchema = z19.object({
10035
+ aliases: z19.array(AliasSchema)
10036
+ }).strict();
10037
+ var MetadataRecordSchema = z19.record(z19.string(), z19.unknown());
10038
+ var inactiveStates = new Set([
10039
+ "loading",
10040
+ "downsizing",
10041
+ "downsized",
10042
+ "evicting",
10043
+ "unloaded"
10044
+ ]);
10045
+ var inactiveHealth = new Set(["stopped", "exited", "watchdog_reset"]);
10046
+ var standardPools = new Set([
10047
+ "standard/llm",
10048
+ "standard/embedding",
10049
+ "standard/reranking",
10050
+ "standard/transcription",
10051
+ "standard/image",
10052
+ "standard/tts",
10053
+ "standard/audio-generation",
10054
+ "standard/classification",
10055
+ "standard/mesh"
10056
+ ]);
10057
+ function usableState(entry) {
10058
+ if (entry.loaded === false || entry.backend_alive === false || entry.watchdog_reset === true || typeof entry.status === "string" && inactiveStates.has(entry.status) || typeof entry.backend_health === "string" && inactiveHealth.has(entry.backend_health) || entry.residency_class === "routing_helper" || typeof entry.slot_pool === "string" && entry.slot_pool.startsWith("routing_helper/")) {
10059
+ return false;
10060
+ }
10061
+ if (entry.loaded !== true || entry.backend_alive !== true || entry.watchdog_reset !== false || entry.status !== "ready" && entry.status !== "in_use" || entry.backend_health !== "ready" && entry.backend_health !== "busy" || entry.watchdog_reset_reason !== undefined && entry.watchdog_reset_reason !== "" || entry.residency_class !== undefined && entry.residency_class !== "standard" || entry.slot_pool !== undefined && (typeof entry.slot_pool !== "string" || !standardPools.has(entry.slot_pool))) {
10062
+ return;
10063
+ }
10064
+ return true;
10065
+ }
10066
+ function projectMetadataObject(payload, keys) {
10067
+ const record = MetadataRecordSchema.safeParse(payload);
10068
+ if (!record.success)
10069
+ throw new ModelResidencyError("unknown");
10070
+ return Object.fromEntries(keys.filter((key) => Object.hasOwn(record.data, key)).map((key) => [key, record.data[key]]));
10071
+ }
10072
+ function parseResidency(healthPayload, aliasPayload) {
10073
+ const healthFields = projectMetadataObject(healthPayload, Object.keys(HealthSchema.shape));
10074
+ if (Array.isArray(healthFields.all_models_loaded)) {
10075
+ healthFields.all_models_loaded = healthFields.all_models_loaded.map((entry) => projectMetadataObject(entry, Object.keys(LoadedModelSchema.shape)));
10076
+ }
10077
+ if (healthFields.telemetry !== undefined) {
10078
+ healthFields.telemetry = projectMetadataObject(healthFields.telemetry, Object.keys(TelemetrySchema.shape));
10079
+ }
10080
+ const aliasFields = projectMetadataObject(aliasPayload, Object.keys(AliasesSchema.shape));
10081
+ if (Array.isArray(aliasFields.aliases)) {
10082
+ aliasFields.aliases = aliasFields.aliases.map((entry) => projectMetadataObject(entry, Object.keys(AliasSchema.shape)));
10083
+ }
10084
+ const health = HealthSchema.safeParse(healthFields);
10085
+ const aliases = AliasesSchema.safeParse(aliasFields);
10086
+ if (!health.success || !aliases.success)
10087
+ throw new ModelResidencyError("unknown");
10088
+ const result = new Map;
10089
+ for (const entry of health.data.all_models_loaded) {
10090
+ if (result.has(entry.model_name))
10091
+ throw new ModelResidencyError("unknown");
10092
+ result.set(entry.model_name, {
10093
+ loaded: entry.model_name.endsWith(":latest") ? undefined : usableState(entry),
10094
+ metadata: entry
10095
+ });
10096
+ }
10097
+ const seenAliases = new Set;
10098
+ for (const entry of aliases.data.aliases) {
10099
+ if (seenAliases.has(entry.alias))
10100
+ throw new ModelResidencyError("unknown");
10101
+ seenAliases.add(entry.alias);
10102
+ result.set(entry.alias, {
10103
+ loaded: undefined,
10104
+ metadata: result.get(entry.alias)?.metadata ?? {}
10105
+ });
10106
+ }
10107
+ return result;
10108
+ }
10109
+ function lemonadeBaseURL(value) {
10110
+ let url;
10111
+ try {
10112
+ url = new URL(value);
10113
+ } catch {
10114
+ throw new ModelResidencyError("destination");
10115
+ }
10116
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !/^\/(?:api\/)?v[01]\/?$/.test(url.pathname)) {
10117
+ throw new ModelResidencyError("destination");
10118
+ }
10119
+ return `${url.origin}${url.pathname.replace(/\/$/, "")}`;
10120
+ }
10121
+ async function withResidencyDeadline(operation, options) {
10122
+ const timeoutMs = options.timeoutMs ?? 5000;
10123
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
10124
+ throw new ModelResidencyError("configuration");
10125
+ }
10126
+ const controller = new AbortController;
10127
+ const cancel = () => controller.abort(new ModelResidencyError("cancelled"));
10128
+ if (options.signal?.aborted)
10129
+ throw new ModelResidencyError("cancelled");
10130
+ options.signal?.addEventListener("abort", cancel, { once: true });
10131
+ const schedule = options.scheduleTimeout ?? ((callback, ms) => {
10132
+ const timer = setTimeout(callback, ms);
10133
+ return () => clearTimeout(timer);
10134
+ });
10135
+ let onAbort = () => {};
10136
+ const aborted = new Promise((_, reject) => {
10137
+ onAbort = () => reject(controller.signal.reason);
10138
+ controller.signal.addEventListener("abort", onAbort, { once: true });
10139
+ });
10140
+ const clearTimer = schedule(() => controller.abort(new ModelResidencyError("timeout")), timeoutMs);
10141
+ try {
10142
+ controller.signal.throwIfAborted();
10143
+ return await Promise.race([operation(controller.signal), aborted]);
10144
+ } finally {
10145
+ clearTimer();
10146
+ options.signal?.removeEventListener("abort", cancel);
10147
+ controller.signal.removeEventListener("abort", onAbort);
10148
+ controller.abort();
10149
+ }
10150
+ }
10151
+ async function metadata(url, fetch, signal, headers) {
10152
+ signal.throwIfAborted();
10153
+ let payload;
10154
+ try {
10155
+ const response = await fetch(url, {
10156
+ method: "GET",
10157
+ headers,
10158
+ signal,
10159
+ redirect: "error",
10160
+ cache: "no-store"
10161
+ });
10162
+ signal.throwIfAborted();
10163
+ if (!response.ok || response.redirected || response.url !== "" && response.url !== url) {
10164
+ throw new ModelResidencyError("unknown");
10165
+ }
10166
+ payload = await response.json();
10167
+ } catch {
10168
+ signal.throwIfAborted();
10169
+ throw new ModelResidencyError("unknown");
10170
+ }
10171
+ signal.throwIfAborted();
10172
+ return payload;
10173
+ }
10174
+ async function readLemonadeResidency(baseURL, fetch, options = {}) {
10175
+ const base = lemonadeBaseURL(baseURL);
10176
+ const headers = new Headers(options.headers);
10177
+ headers.delete("content-type");
10178
+ headers.delete("content-length");
10179
+ headers.set("accept", "application/json");
10180
+ headers.set("cache-control", "no-cache, no-store");
10181
+ headers.delete("if-none-match");
10182
+ headers.delete("if-modified-since");
10183
+ return withResidencyDeadline(async (signal) => {
10184
+ const aliases = await metadata(`${new URL(base).origin}/internal/aliases`, fetch, signal, headers);
10185
+ const health = await metadata(`${base}/health`, fetch, signal, headers);
10186
+ return parseResidency(health, aliases);
10187
+ }, options);
10188
+ }
10189
+
9825
10190
  // src/local/lemonade.ts
9826
10191
  var LEMONADE_DEFAULT_BASE_URL = "http://localhost:13305/api/v1";
9827
10192
  var NON_TEXT_LABELS = new Set([
@@ -9908,36 +10273,19 @@ function isNonTextModel(entry) {
9908
10273
  return entry.labels.some((label) => typeof label === "string" && NON_TEXT_LABELS.has(label));
9909
10274
  }
9910
10275
  async function readLoadedModels(baseURL, fetch) {
9911
- let payload;
9912
10276
  try {
9913
- const response = await fetch(`${baseURL}/health`, {
9914
- method: "GET",
9915
- headers: { accept: "application/json" }
9916
- });
9917
- if (!response.ok) {
9918
- return;
9919
- }
9920
- payload = await response.json();
10277
+ const residency = await readLemonadeResidency(baseURL, fetch);
10278
+ return new Map([...residency].map(([name, entry]) => [
10279
+ name,
10280
+ {
10281
+ loaded: entry.loaded,
10282
+ slots: entry.loaded === true ? readSlotCount(entry.metadata) : undefined,
10283
+ totalContextWindow: entry.loaded === true ? readLoadedContextWindow(entry.metadata) : undefined
10284
+ }
10285
+ ]));
9921
10286
  } catch {
9922
10287
  return;
9923
10288
  }
9924
- if (!isObject2(payload) || !Array.isArray(payload.all_models_loaded)) {
9925
- return;
9926
- }
9927
- const loaded = new Map;
9928
- for (const entry of payload.all_models_loaded) {
9929
- if (!isObject2(entry) || typeof entry.model_name !== "string") {
9930
- continue;
9931
- }
9932
- if (entry.loaded === false) {
9933
- continue;
9934
- }
9935
- loaded.set(entry.model_name, {
9936
- slots: readSlotCount(entry),
9937
- totalContextWindow: readLoadedContextWindow(entry)
9938
- });
9939
- }
9940
- return loaded;
9941
10289
  }
9942
10290
  function readSlotCount(entry) {
9943
10291
  return slotsFromLlamacppArgs(entry) ?? slotsFromLaunchCommand(entry);
@@ -10084,6 +10432,9 @@ function enrichWithLoadState(models, loaded) {
10084
10432
  if (hit === undefined) {
10085
10433
  return { ...base, loaded: false };
10086
10434
  }
10435
+ if (hit.loaded !== true) {
10436
+ return hit.loaded === false ? { ...base, loaded: false } : base;
10437
+ }
10087
10438
  const contextWindow = derivedConservativeContextWindow(model.contextWindow, hit);
10088
10439
  return {
10089
10440
  ...base,
@@ -10227,6 +10578,14 @@ function reportedModelSlotsByRuntime(snapshots) {
10227
10578
  }
10228
10579
 
10229
10580
  // src/local/slotPool.ts
10581
+ function localModelAliases(providerID, models) {
10582
+ const aliases = new Map;
10583
+ for (const model of models ?? []) {
10584
+ if (model.kind === "local" && model.providerID === providerID && model.runtimeModelID !== undefined && model.runtimeModelID !== model.modelID)
10585
+ aliases.set(model.modelID, model.runtimeModelID);
10586
+ }
10587
+ return aliases;
10588
+ }
10230
10589
  var POOL_SEPARATOR = "::";
10231
10590
  function slotPoolKey(runtime, modelID) {
10232
10591
  if (modelID === undefined) {
@@ -10256,13 +10615,20 @@ function slotPoolReservedInteractive(runtime, modelID) {
10256
10615
  function unknownPoolCapacity() {
10257
10616
  return DEFAULT_LOCAL_MAX_CONCURRENCY;
10258
10617
  }
10259
- function withReportedModelSlots(runtime, reported) {
10260
- if (reported === undefined || Object.keys(reported).length === 0) {
10618
+ function withReportedModelSlots(runtime, reported, aliases) {
10619
+ const configured = Object.entries(runtime.modelConcurrency ?? {});
10620
+ const hasAliasLimits = configured.some(([modelID]) => aliases?.has(modelID) === true);
10621
+ if (!hasAliasLimits && (reported === undefined || Object.keys(reported).length === 0)) {
10261
10622
  return runtime;
10262
10623
  }
10624
+ const explicit = new Map;
10625
+ for (const [modelID, limit] of configured) {
10626
+ const physicalID = aliases?.get(modelID) ?? modelID;
10627
+ explicit.set(physicalID, Math.min(explicit.get(physicalID) ?? limit, limit));
10628
+ }
10263
10629
  return {
10264
10630
  ...runtime,
10265
- modelConcurrency: { ...reported, ...runtime.modelConcurrency }
10631
+ modelConcurrency: { ...reported, ...Object.fromEntries(explicit) }
10266
10632
  };
10267
10633
  }
10268
10634
 
@@ -10294,8 +10660,11 @@ var UNAUTHORIZED_MODEL_SWITCH_REASON = "routing-model-switch-not-authorized";
10294
10660
  function invalidRoleIdentifier(roleID) {
10295
10661
  return `invalid role identifier "${roleID}" — use letters, numbers, underscores, or hyphens, starting with a letter or number`;
10296
10662
  }
10663
+ function agentPreparationRemedy(agentName) {
10664
+ return "Use `/create_roster` in opencode to complete the missing roster agent " + "definitions without changing existing aliases. " + `If \`.opencode/agent/${agentName}.md\` already exists, reload opencode ` + "to discover it before retrying; do not substitute an unrelated agent.";
10665
+ }
10297
10666
  function missingAgentDefinition(agentName, roleID) {
10298
- return `agent definition "${agentName}" is not installed for role "${roleID}". ` + `Create .opencode/agent/${agentName}.md or choose an installed builtin agent before retrying.`;
10667
+ return `agent definition "${agentName}" is not installed for role "${roleID}". ` + agentPreparationRemedy(agentName);
10299
10668
  }
10300
10669
  function dependencyValidationError(code, detail) {
10301
10670
  return `dependency validation error: ${code} (${detail})`;
@@ -10356,14 +10725,14 @@ var readReportArtifact = async (reportPath) => {
10356
10725
  };
10357
10726
 
10358
10727
  // src/orchestrator/roster.ts
10359
- import { z as z19 } from "zod";
10360
- var RosterEntrySchema = z19.object({
10361
- roleID: z19.string().min(1),
10362
- agentName: z19.string().min(1)
10728
+ import { z as z20 } from "zod";
10729
+ var RosterEntrySchema = z20.object({
10730
+ roleID: z20.string().min(1),
10731
+ agentName: z20.string().min(1)
10363
10732
  }).strict();
10364
- var RosterSchema = z19.object({
10365
- universe: z19.string().min(1),
10366
- entries: z19.array(RosterEntrySchema)
10733
+ var RosterSchema = z20.object({
10734
+ universe: z20.string().min(1),
10735
+ entries: z20.array(RosterEntrySchema)
10367
10736
  }).strict();
10368
10737
  var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
10369
10738
 
@@ -10548,6 +10917,50 @@ function lifecycleRecordingAdvisory(status) {
10548
10917
  return `Lifecycle history recording is ${status.state}${failure}; model execution continued without claiming complete history.`;
10549
10918
  }
10550
10919
 
10920
+ // src/orchestrator/sessionStatus.ts
10921
+ function dataProperties(value) {
10922
+ if (typeof value !== "object" || value === null || Array.isArray(value))
10923
+ return;
10924
+ try {
10925
+ const prototype = Object.getPrototypeOf(value);
10926
+ if (prototype !== Object.prototype && prototype !== null)
10927
+ return;
10928
+ if (Reflect.ownKeys(value).some((key) => typeof key !== "string"))
10929
+ return;
10930
+ const properties = Object.getOwnPropertyDescriptors(value);
10931
+ return Object.values(properties).every((property) => ("value" in property) && property.enumerable === true) ? properties : undefined;
10932
+ } catch {
10933
+ return;
10934
+ }
10935
+ }
10936
+ function parseSessionStatus(value) {
10937
+ const properties = dataProperties(value);
10938
+ if (properties === undefined)
10939
+ return;
10940
+ const type = properties.type?.value;
10941
+ if (type === "idle" || type === "busy")
10942
+ return { type };
10943
+ const attempt = properties.attempt?.value;
10944
+ const message = properties.message?.value;
10945
+ const next = properties.next?.value;
10946
+ if (type === "retry" && typeof attempt === "number" && Number.isFinite(attempt) && typeof message === "string" && typeof next === "number" && Number.isFinite(next))
10947
+ return { type, attempt, message, next };
10948
+ return;
10949
+ }
10950
+ function parseSessionStatusMap(value) {
10951
+ const properties = dataProperties(value);
10952
+ if (properties === undefined)
10953
+ return;
10954
+ const entries = [];
10955
+ for (const [sessionID, property] of Object.entries(properties)) {
10956
+ const status = parseSessionStatus(property.value);
10957
+ if (status === undefined)
10958
+ return;
10959
+ entries.push([sessionID, status]);
10960
+ }
10961
+ return Object.fromEntries(entries);
10962
+ }
10963
+
10551
10964
  // src/orchestrator/subsessions.ts
10552
10965
  var DEFAULT_ABORT_GRACE_MS = 2000;
10553
10966
  var DEFAULT_STATUS_POLL_MS = 100;
@@ -10773,28 +11186,6 @@ function statusArgs(directory, signal) {
10773
11186
  function abortArgs(sessionID, directory, signal) {
10774
11187
  return directory === undefined ? { path: { id: sessionID }, signal } : { path: { id: sessionID }, query: { directory }, signal };
10775
11188
  }
10776
- function isSessionStatus(value) {
10777
- if (!isObject3(value))
10778
- return false;
10779
- const type = safeGet(value, "type");
10780
- if (type === "idle" || type === "busy")
10781
- return true;
10782
- if (type !== "retry")
10783
- return false;
10784
- return typeof safeGet(value, "attempt") === "number" && typeof safeGet(value, "message") === "string" && typeof safeGet(value, "next") === "number";
10785
- }
10786
- function isSessionStatusMap(value) {
10787
- if (!isObject3(value) || Array.isArray(value))
10788
- return false;
10789
- try {
10790
- const prototype = Object.getPrototypeOf(value);
10791
- if (prototype !== Object.prototype && prototype !== null)
10792
- return false;
10793
- return Object.values(value).every(isSessionStatus);
10794
- } catch {
10795
- return false;
10796
- }
10797
- }
10798
11189
  async function confirmSessionStopped(client, sessionID, directory, deps, deadline) {
10799
11190
  const abortSession = client.session.abort;
10800
11191
  if (abortSession === undefined) {
@@ -10843,8 +11234,8 @@ async function confirmSessionStopped(client, sessionID, directory, deps, deadlin
10843
11234
  if (safeGet(statuses, "error") !== undefined) {
10844
11235
  return { confirmed: false, cause: "status-error" };
10845
11236
  }
10846
- const snapshot = safeGet(statuses, "data");
10847
- if (!isSessionStatusMap(snapshot)) {
11237
+ const snapshot = parseSessionStatusMap(safeGet(statuses, "data"));
11238
+ if (snapshot === undefined) {
10848
11239
  return { confirmed: false, cause: "status-missing" };
10849
11240
  }
10850
11241
  if (!Object.hasOwn(snapshot, sessionID)) {
@@ -10986,10 +11377,21 @@ async function runSubsession(client, req, deps) {
10986
11377
  }
10987
11378
  }
10988
11379
 
11380
+ // src/local/modelResidency.ts
11381
+ function hasRequiredModelResidency(kind, model) {
11382
+ return kind !== "lemonade" || model?.loaded === true;
11383
+ }
11384
+
10989
11385
  // src/plugin/availability.ts
10990
11386
  function modelKey2(model) {
10991
11387
  return `${model.providerID}/${model.modelID}`;
10992
11388
  }
11389
+ function availabilityMetadata({
11390
+ apiModelID: _apiModelID,
11391
+ ...metadata2
11392
+ }) {
11393
+ return metadata2;
11394
+ }
10993
11395
  function compareAvailableModels(left, right) {
10994
11396
  const kindOrder = Number(left.kind === "frontier") - Number(right.kind === "frontier");
10995
11397
  if (kindOrder !== 0) {
@@ -11007,6 +11409,9 @@ function uniqueSelections(models) {
11007
11409
  function isConfiguredLocalModel(config, model) {
11008
11410
  return config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === model.providerID);
11009
11411
  }
11412
+ function requiresModelResidency(config, model) {
11413
+ return config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === model.providerID && !hasRequiredModelResidency(localRuntimeKind(runtime), undefined));
11414
+ }
11010
11415
  function configuredFrontierProviderIDs(config) {
11011
11416
  const providerIDs = new Set([config.baseline.hardDefault.providerID]);
11012
11417
  if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
@@ -11101,7 +11506,7 @@ function frontierAvailableModels(config, configuredModels) {
11101
11506
  for (const model of configuredModels ?? frontierBaselines(config)) {
11102
11507
  if (!isConfiguredLocalModel(config, model)) {
11103
11508
  frontierByKey.set(modelKey2(model), {
11104
- ...model,
11509
+ ...availabilityMetadata(model),
11105
11510
  kind: "frontier",
11106
11511
  available: true
11107
11512
  });
@@ -11117,7 +11522,7 @@ function defaultAvailableModels(config, configuredModels) {
11117
11522
  ...defaultLocalModels(config).map((model) => ({
11118
11523
  ...model,
11119
11524
  kind: "local",
11120
- available: true
11525
+ available: !requiresModelResidency(config, model)
11121
11526
  })),
11122
11527
  ...frontierAvailableModels(config, configuredModels)
11123
11528
  ].sort(compareAvailableModels);
@@ -11131,7 +11536,8 @@ function toAvailableModels(snapshots, config, configuredModels) {
11131
11536
  continue;
11132
11537
  }
11133
11538
  localByKey.set(modelKey2(model), {
11134
- ...model,
11539
+ ...availabilityMetadata(model),
11540
+ ...model.apiModelID !== undefined && model.apiModelID !== model.modelID ? { runtimeModelID: model.apiModelID } : {},
11135
11541
  kind: "local",
11136
11542
  available: false
11137
11543
  });
@@ -11142,27 +11548,25 @@ function toAvailableModels(snapshots, config, configuredModels) {
11142
11548
  continue;
11143
11549
  }
11144
11550
  for (const model of snapshot.models) {
11145
- const ref = {
11146
- providerID: runtime.defaultModel.providerID,
11147
- modelID: model.modelID
11148
- };
11149
- if (registeredKeys !== undefined && !registeredKeys.has(modelKey2(ref))) {
11150
- continue;
11151
- }
11152
- const existing = localByKey.get(modelKey2(ref));
11153
- const availableModel = {
11154
- ...existing,
11155
- ...ref,
11156
- kind: "local",
11157
- available: snapshot.reachable || existing?.available === true
11158
- };
11159
- if (model.supportsTools !== "unknown") {
11160
- availableModel.supportsTools = model.supportsTools;
11161
- }
11162
- if (model.contextWindow !== undefined) {
11163
- availableModel.contextWindow = model.contextWindow;
11551
+ const providerID = runtime.defaultModel.providerID;
11552
+ const refs = configuredModels === undefined ? [{ providerID, modelID: model.modelID }] : configuredModels.filter((registered) => registered.providerID === providerID && (registered.apiModelID ?? registered.modelID) === model.modelID);
11553
+ for (const ref of refs) {
11554
+ const existing = localByKey.get(modelKey2(ref));
11555
+ const availableModel = {
11556
+ ...existing,
11557
+ providerID: ref.providerID,
11558
+ modelID: ref.modelID,
11559
+ kind: "local",
11560
+ available: snapshot.reachable && hasRequiredModelResidency(localRuntimeKind(runtime), model) || existing?.available === true
11561
+ };
11562
+ if (model.supportsTools !== "unknown") {
11563
+ availableModel.supportsTools = model.supportsTools;
11564
+ }
11565
+ if (model.contextWindow !== undefined) {
11566
+ availableModel.contextWindow = model.contextWindow;
11567
+ }
11568
+ localByKey.set(modelKey2(ref), availableModel);
11164
11569
  }
11165
- localByKey.set(modelKey2(ref), availableModel);
11166
11570
  }
11167
11571
  }
11168
11572
  return [
@@ -11171,7 +11575,7 @@ function toAvailableModels(snapshots, config, configuredModels) {
11171
11575
  ].sort(compareAvailableModels);
11172
11576
  }
11173
11577
  function createAvailabilityCache(options) {
11174
- let configuredModels = options.initialConfiguredModels === undefined ? undefined : [...options.initialConfiguredModels];
11578
+ let configuredModels = options.initialConfiguredModels === undefined ? undefined : options.initialConfiguredModels.map((model) => ({ ...model }));
11175
11579
  let configuredProfiles = (options.initialConfiguredProfiles ?? []).map((profile) => profileForConfiguredDomain(options.config, profile));
11176
11580
  let cached = defaultAvailableModels(options.config, configuredModels);
11177
11581
  let inFlight;
@@ -11181,7 +11585,9 @@ function createAvailabilityCache(options) {
11181
11585
  if (options.listConfiguredInventory !== undefined) {
11182
11586
  const inventory = await options.listConfiguredInventory().then((value) => ({ kind: "models", value }), (error) => ({ kind: "error", error }));
11183
11587
  if (inventory.kind === "models") {
11184
- configuredModels = inventory.value.models;
11588
+ configuredModels = inventory.value.models.map((model) => ({
11589
+ ...model
11590
+ }));
11185
11591
  configuredProfiles = inventory.value.profiles.map((profile) => profileForConfiguredDomain(options.config, profile));
11186
11592
  } else {
11187
11593
  options.onRefreshError?.(inventory.error);
@@ -11194,15 +11600,22 @@ function createAvailabilityCache(options) {
11194
11600
  }
11195
11601
  return {
11196
11602
  get: () => cloneAvailableModels(cached),
11603
+ resolveApiModelID: (ref) => {
11604
+ const registered = configuredModels?.find((model) => model.providerID === ref.providerID && model.modelID === ref.modelID);
11605
+ return registered?.apiModelID ?? registered?.modelID;
11606
+ },
11197
11607
  getProfiles: () => configuredProfiles.map((profile) => ({
11198
11608
  ...profile,
11199
11609
  ref: { ...profile.ref },
11200
11610
  costPer1M: { ...profile.costPer1M }
11201
11611
  })),
11202
11612
  refresh: () => {
11203
- inFlight ??= refreshNow().finally(() => {
11204
- inFlight = undefined;
11205
- });
11613
+ if (inFlight === undefined) {
11614
+ cached = cached.map((model) => requiresModelResidency(options.config, model) ? { ...model, available: false } : model);
11615
+ inFlight = refreshNow().finally(() => {
11616
+ inFlight = undefined;
11617
+ });
11618
+ }
11206
11619
  return inFlight;
11207
11620
  }
11208
11621
  };
@@ -11370,7 +11783,7 @@ function planWorktreeReconciliation(input) {
11370
11783
  }
11371
11784
 
11372
11785
  // src/telemetry/otelConfig.ts
11373
- import { z as z20 } from "zod";
11786
+ import { z as z21 } from "zod";
11374
11787
 
11375
11788
  // src/telemetry/fanout.ts
11376
11789
  function createFanOutSink(deps) {
@@ -11448,10 +11861,10 @@ function createOtelSink(deps) {
11448
11861
  }
11449
11862
 
11450
11863
  // src/telemetry/otelConfig.ts
11451
- var OtelBackendConfigSchema = z20.object({
11452
- backend: z20.literal("opentelemetry"),
11453
- connectionEnv: z20.string().min(1),
11454
- serviceName: z20.string().min(1).optional()
11864
+ var OtelBackendConfigSchema = z21.object({
11865
+ backend: z21.literal("opentelemetry"),
11866
+ connectionEnv: z21.string().min(1),
11867
+ serviceName: z21.string().min(1).optional()
11455
11868
  }).strict();
11456
11869
  function parseConnectionString(raw) {
11457
11870
  const pairs = new Map;
@@ -11876,7 +12289,7 @@ function parseOpencodeProviders(opencodeConfig) {
11876
12289
  function localRuntimeStates(config) {
11877
12290
  const map = new Map;
11878
12291
  for (const runtime of config.local.runtimes) {
11879
- map.set(runtime.id, { enabled: runtime.enabled });
12292
+ map.set(runtime.defaultModel.providerID, { enabled: runtime.enabled });
11880
12293
  }
11881
12294
  return map;
11882
12295
  }
@@ -11884,7 +12297,7 @@ function disabledLocalProviderIDs(config) {
11884
12297
  const ids = new Set;
11885
12298
  for (const runtime of config.local.runtimes) {
11886
12299
  if (!runtime.enabled) {
11887
- ids.add(runtime.id);
12300
+ ids.add(runtime.defaultModel.providerID);
11888
12301
  }
11889
12302
  }
11890
12303
  return ids;
@@ -11900,7 +12313,11 @@ function computeLive(model, isLocalProvider, liveModels) {
11900
12313
  if (!snapshot.reachable) {
11901
12314
  return "unreachable";
11902
12315
  }
11903
- return snapshot.modelIDs.has(model.modelID) ? "confirmed" : "not-advertised";
12316
+ if (snapshot.modelIDs.has(model.modelID))
12317
+ return "confirmed";
12318
+ if (snapshot.residency?.has(model.modelID))
12319
+ return snapshot.residency.get(model.modelID) === false ? "not-loaded" : "residency-unknown";
12320
+ return "not-advertised";
11904
12321
  }
11905
12322
  function resolveAgentModel(model, context) {
11906
12323
  const isLocalProvider = KNOWN_LOCAL_PROVIDER_IDS.has(model.providerID) || context.localRuntimes.has(model.providerID);
@@ -13174,7 +13591,6 @@ var GENERATED_OPENTEAM_STATE_GROUPS = [
13174
13591
  { kind: "file", paths: [SOAK_RECORDER_ID_PATH] }
13175
13592
  ];
13176
13593
  var OPENCODE_DIR = ".opencode";
13177
- var AGENT_DIR = `${OPENCODE_DIR}/agent`;
13178
13594
  function splitSetupPath(path3) {
13179
13595
  return path3.split("/").filter((segment) => segment.length > 0);
13180
13596
  }
@@ -14232,12 +14648,19 @@ function defaultModelFromOpencode(opencodeConfig) {
14232
14648
  }
14233
14649
  return;
14234
14650
  }
14235
- function liveModelsFromSnapshots(snapshots) {
14651
+ function liveModelsFromSnapshots(snapshots, config) {
14236
14652
  const map = new Map;
14237
14653
  for (const snapshot of snapshots) {
14238
- map.set(snapshot.id, {
14654
+ const runtime = config.local.runtimes.find((runtime2) => runtime2.id === snapshot.id);
14655
+ if (runtime === undefined || !runtime.enabled)
14656
+ continue;
14657
+ const kind = localRuntimeKind(runtime);
14658
+ map.set(runtime.defaultModel.providerID, {
14239
14659
  reachable: snapshot.reachable,
14240
- modelIDs: new Set(snapshot.models.map((model) => model.modelID))
14660
+ modelIDs: new Set(snapshot.models.filter((model) => hasRequiredModelResidency(kind, model)).map((model) => model.modelID)),
14661
+ ...kind === "lemonade" ? {
14662
+ residency: new Map(snapshot.models.map((model) => [model.modelID, model.loaded]))
14663
+ } : {}
14241
14664
  });
14242
14665
  }
14243
14666
  return map;
@@ -14523,7 +14946,7 @@ async function runCli(argv, deps) {
14523
14946
  const agentModels = diagnoseAgentModels(agentFiles, {
14524
14947
  opencodeProviders: parseOpencodeProviders(opencodeConfig),
14525
14948
  localRuntimes: localRuntimeStates(config),
14526
- liveModels: liveModelsFromSnapshots(snapshots),
14949
+ liveModels: liveModelsFromSnapshots(snapshots, config),
14527
14950
  sourceDir: deps.agentDir,
14528
14951
  ...defaultModel !== undefined ? { defaultModel } : {}
14529
14952
  });
@@ -14562,29 +14985,53 @@ async function runCli(argv, deps) {
14562
14985
  const rosterMissing = deps.loadRosterForDoctor !== undefined && roster === undefined && rosterLoadError === undefined;
14563
14986
  const orchestratorAgentName = ORCHESTRATOR_AGENT_PATH.slice(ORCHESTRATOR_AGENT_PATH.lastIndexOf("/") + 1).replace(/\.md$/, "");
14564
14987
  const orchestratorAgentMissing = !agentFiles.some((file) => file.name === orchestratorAgentName);
14988
+ const inventoryNotes = [];
14989
+ const readySnapshots = snapshots.map((snapshot) => {
14990
+ const runtime = config.local.runtimes.find((runtime2) => runtime2.id === snapshot.id);
14991
+ if (runtime === undefined || localRuntimeKind(runtime) !== "lemonade")
14992
+ return snapshot;
14993
+ inventoryNotes.push(executionSetupMessages.lemonadeInventory(runtime.id, runtime.defaultModel.providerID, snapshot.models));
14994
+ return {
14995
+ ...snapshot,
14996
+ models: snapshot.models.filter((model) => hasRequiredModelResidency(localRuntimeKind(runtime), model))
14997
+ };
14998
+ });
14999
+ const residencyNotes = agentModels.flatMap((agent) => {
15000
+ const live = agent.resolution?.live;
15001
+ if (agent.model === undefined || live !== "not-loaded" && live !== "residency-unknown")
15002
+ return [];
15003
+ return [
15004
+ executionSetupMessages.lemonadeAgentResidency(agent.name, `${agent.model.providerID}/${agent.model.modelID}`, live)
15005
+ ];
15006
+ });
14565
15007
  return {
14566
15008
  exitCode: 0,
14567
- stdout: renderDoctor({
14568
- config,
14569
- snapshots,
14570
- telemetryPath,
14571
- telemetryRecords: records.length,
14572
- agentModels,
14573
- opencodeConfigPaths,
14574
- ...opencodeConfig !== undefined ? { opencodeConfig } : {},
14575
- ...queueWait !== undefined ? { queueWait } : {},
14576
- ...rosterAudit !== undefined ? { rosterAudit } : {},
14577
- ...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
14578
- ...rosterLoadError !== undefined ? { rosterLoadError } : {},
14579
- ...rosterMissing ? { rosterMissing } : {},
14580
- ...orchestratorAgentMissing ? { orchestratorAgentMissing } : {},
14581
- ...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
14582
- ...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
14583
- ...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
14584
- ...legacyLayout !== undefined && legacyLayout.length > 0 ? { legacyLayout } : {},
14585
- ...otelBackend !== undefined ? { otelBackend } : {},
14586
- ...gitignoreCoverage !== undefined ? { gitignore: gitignoreCoverage } : {}
14587
- })
15009
+ stdout: [
15010
+ renderDoctor({
15011
+ config,
15012
+ snapshots: readySnapshots,
15013
+ telemetryPath,
15014
+ telemetryRecords: records.length,
15015
+ agentModels,
15016
+ opencodeConfigPaths,
15017
+ ...opencodeConfig !== undefined ? { opencodeConfig } : {},
15018
+ ...queueWait !== undefined ? { queueWait } : {},
15019
+ ...rosterAudit !== undefined ? { rosterAudit } : {},
15020
+ ...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
15021
+ ...rosterLoadError !== undefined ? { rosterLoadError } : {},
15022
+ ...rosterMissing ? { rosterMissing } : {},
15023
+ ...orchestratorAgentMissing ? { orchestratorAgentMissing } : {},
15024
+ ...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
15025
+ ...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
15026
+ ...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
15027
+ ...legacyLayout !== undefined && legacyLayout.length > 0 ? { legacyLayout } : {},
15028
+ ...otelBackend !== undefined ? { otelBackend } : {},
15029
+ ...gitignoreCoverage !== undefined ? { gitignore: gitignoreCoverage } : {}
15030
+ }),
15031
+ ...inventoryNotes,
15032
+ ...residencyNotes
15033
+ ].join(`
15034
+ `)
14588
15035
  };
14589
15036
  }
14590
15037
  if (command === "migrate") {
@@ -14768,29 +15215,29 @@ import {
14768
15215
  writeFile as writeFile2
14769
15216
  } from "node:fs/promises";
14770
15217
  import { dirname as dirname5, join as join12, resolve as resolve7 } from "node:path";
14771
- import { z as z21 } from "zod";
15218
+ import { z as z22 } from "zod";
14772
15219
  var CONSOLE_RUNTIME_SCHEMA_VERSION = 1;
14773
15220
  var CONSOLE_RUNTIME_DIRECTORY = ".opencode/openteam-local/console";
14774
15221
  var CONSOLE_RUNTIME_FILE = "runtime.json";
14775
15222
  var CONSOLE_LAUNCH_LOCK_FILE = "launch.lock";
14776
15223
  var CONSOLE_LOG_FILE = "console.log";
14777
- var ConsoleRuntimeStateSchema = z21.object({
14778
- schemaVersion: z21.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
14779
- pid: z21.number().int().positive(),
14780
- workspaceRoot: z21.string().min(1),
14781
- host: z21.string().min(1),
14782
- port: z21.number().int().min(1).max(65535),
14783
- url: z21.url(),
14784
- startedAt: z21.iso.datetime({ offset: true }),
14785
- launchId: z21.uuid(),
14786
- logPath: z21.string().min(1)
15224
+ var ConsoleRuntimeStateSchema = z22.object({
15225
+ schemaVersion: z22.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
15226
+ pid: z22.number().int().positive(),
15227
+ workspaceRoot: z22.string().min(1),
15228
+ host: z22.string().min(1),
15229
+ port: z22.number().int().min(1).max(65535),
15230
+ url: z22.url(),
15231
+ startedAt: z22.iso.datetime({ offset: true }),
15232
+ launchId: z22.uuid(),
15233
+ logPath: z22.string().min(1)
14787
15234
  }).strict();
14788
- var ConsoleLaunchLockSchema = z21.object({
14789
- schemaVersion: z21.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
14790
- workspaceRoot: z21.string().min(1),
14791
- launchId: z21.uuid(),
14792
- ownerPid: z21.number().int().positive(),
14793
- createdAt: z21.iso.datetime({ offset: true })
15235
+ var ConsoleLaunchLockSchema = z22.object({
15236
+ schemaVersion: z22.literal(CONSOLE_RUNTIME_SCHEMA_VERSION),
15237
+ workspaceRoot: z22.string().min(1),
15238
+ launchId: z22.uuid(),
15239
+ ownerPid: z22.number().int().positive(),
15240
+ createdAt: z22.iso.datetime({ offset: true })
14794
15241
  }).strict();
14795
15242
 
14796
15243
  class ConsoleRuntimeStateFileError extends Error {
@@ -15489,36 +15936,36 @@ function formatResponse(response) {
15489
15936
  }
15490
15937
 
15491
15938
  // src/plugin/reportRunSchema.ts
15492
- import { z as z23 } from "zod";
15939
+ import { z as z24 } from "zod";
15493
15940
 
15494
15941
  // src/plugin/graphShadowIngress.ts
15495
- import { z as z22 } from "zod";
15942
+ import { z as z23 } from "zod";
15496
15943
  var LEGACY_EXECUTION_TRACE_VERSION = 1;
15497
- var LegacyExecutionStatusSchema = z22.enum([
15944
+ var LegacyExecutionStatusSchema = z23.enum([
15498
15945
  "completed",
15499
15946
  "failed",
15500
15947
  "cancelled"
15501
15948
  ]);
15502
- var LegacyReviewOutcomeSchema = z22.enum([
15949
+ var LegacyReviewOutcomeSchema = z23.enum([
15503
15950
  "approved",
15504
15951
  "rejected",
15505
15952
  "inconclusive"
15506
15953
  ]);
15507
- var LegacyTraceNodeV1Schema = z22.object({
15954
+ var LegacyTraceNodeV1Schema = z23.object({
15508
15955
  id: NodeIDSchema,
15509
15956
  role: NodeRoleSchema,
15510
- model: z22.string().min(1),
15511
- sessionRef: z22.string().min(1).optional(),
15957
+ model: z23.string().min(1),
15958
+ sessionRef: z23.string().min(1).optional(),
15512
15959
  errorClass: ErrorClassSchema.optional()
15513
15960
  }).strict();
15514
- var LegacyExecutionTraceV1Schema = z22.object({
15515
- version: z22.literal(LEGACY_EXECUTION_TRACE_VERSION),
15961
+ var LegacyExecutionTraceV1Schema = z23.object({
15962
+ version: z23.literal(LEGACY_EXECUTION_TRACE_VERSION),
15516
15963
  runID: NodeIDSchema,
15517
15964
  status: LegacyExecutionStatusSchema,
15518
15965
  outcome: LegacyReviewOutcomeSchema,
15519
- fixes: z22.number().int().nonnegative(),
15520
- nodes: z22.array(LegacyTraceNodeV1Schema).min(1),
15521
- parentSessionRef: z22.string().min(1).optional()
15966
+ fixes: z23.number().int().nonnegative(),
15967
+ nodes: z23.array(LegacyTraceNodeV1Schema).min(1),
15968
+ parentSessionRef: z23.string().min(1).optional()
15522
15969
  }).strict();
15523
15970
  function gateReason2(gate) {
15524
15971
  if (gate === undefined || gate.mode === "off") {
@@ -15589,20 +16036,20 @@ function parseLegacyExecutionTrace(input) {
15589
16036
  }
15590
16037
 
15591
16038
  // src/plugin/reportRunSchema.ts
15592
- var ReportRunNodeSchema = z23.object({
16039
+ var ReportRunNodeSchema = z24.object({
15593
16040
  id: NodeIDSchema,
15594
16041
  role: NodeRoleSchema,
15595
- model: z23.string().min(1),
15596
- ok: z23.boolean(),
15597
- sessionRef: z23.string().min(1).optional(),
16042
+ model: z24.string().min(1),
16043
+ ok: z24.boolean(),
16044
+ sessionRef: z24.string().min(1).optional(),
15598
16045
  errorClass: ErrorClassSchema.optional()
15599
16046
  }).strict();
15600
- var ReportRunPayloadSchema = z23.object({
16047
+ var ReportRunPayloadSchema = z24.object({
15601
16048
  runID: NodeIDSchema,
15602
16049
  status: LegacyExecutionStatusSchema,
15603
- fixes: z23.number().int().nonnegative(),
15604
- nodes: z23.array(ReportRunNodeSchema).min(1),
15605
- parentSessionRef: z23.string().min(1).optional()
16050
+ fixes: z24.number().int().nonnegative(),
16051
+ nodes: z24.array(ReportRunNodeSchema).min(1),
16052
+ parentSessionRef: z24.string().min(1).optional()
15606
16053
  }).strict();
15607
16054
 
15608
16055
  // src/orchestrator/graphShadow.ts
@@ -16706,6 +17153,309 @@ function hashPrompt(prompt) {
16706
17153
  return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
16707
17154
  }
16708
17155
 
17156
+ // src/plugin/modelResidency.ts
17157
+ import { z as z25 } from "zod";
17158
+
17159
+ // src/local/lemonadeRequestGuard.ts
17160
+ var inferencePaths = new Set([
17161
+ "/chat/completions",
17162
+ "/completions",
17163
+ "/embeddings",
17164
+ "/responses"
17165
+ ]);
17166
+ function modelID(payload) {
17167
+ if (typeof payload !== "object" || payload === null || !("model" in payload) || typeof payload.model !== "string" || payload.model.length === 0 || payload.model !== payload.model.trim()) {
17168
+ throw new ModelResidencyError("request");
17169
+ }
17170
+ if (payload.model.endsWith(":latest"))
17171
+ throw new ModelResidencyError("identity");
17172
+ return payload.model;
17173
+ }
17174
+ function createLemonadeGuardedFetch(options) {
17175
+ const base = lemonadeBaseURL(options.baseURL);
17176
+ const upstream = options.fetch;
17177
+ const healthFetch = options.healthFetch ?? upstream;
17178
+ return Object.assign(async (input, init) => {
17179
+ const request = new Request(input, init);
17180
+ const url = new URL(request.url);
17181
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !request.url.startsWith(`${base}/`)) {
17182
+ throw new ModelResidencyError("destination");
17183
+ }
17184
+ const path4 = request.url.slice(base.length);
17185
+ const metadataOnly = request.method === "GET" && (path4 === "/health" || /^\/models(?:\/[^/]+)?$/.test(path4));
17186
+ if (!metadataOnly && (request.method !== "POST" || !inferencePaths.has(path4))) {
17187
+ throw new ModelResidencyError("request");
17188
+ }
17189
+ if (!metadataOnly) {
17190
+ await withResidencyDeadline(async (signal) => {
17191
+ signal.throwIfAborted();
17192
+ let payload;
17193
+ try {
17194
+ payload = await request.clone().json();
17195
+ } catch {
17196
+ signal.throwIfAborted();
17197
+ throw new ModelResidencyError("request");
17198
+ }
17199
+ signal.throwIfAborted();
17200
+ const model = modelID(payload);
17201
+ const modelIDs = typeof options.modelIDs === "function" ? await options.modelIDs() : options.modelIDs;
17202
+ signal.throwIfAborted();
17203
+ if (modelIDs !== undefined && !modelIDs.has(model)) {
17204
+ throw new ModelResidencyError("identity");
17205
+ }
17206
+ const residency = await readLemonadeResidency(base, healthFetch, {
17207
+ ...options,
17208
+ signal,
17209
+ headers: request.headers
17210
+ });
17211
+ signal.throwIfAborted();
17212
+ const entry = residency.get(model);
17213
+ if (entry?.loaded !== true) {
17214
+ throw new ModelResidencyError(entry?.loaded === undefined && entry !== undefined ? "unknown" : "inactive");
17215
+ }
17216
+ }, { ...options, signal: request.signal });
17217
+ }
17218
+ if (request.signal.aborted)
17219
+ throw new ModelResidencyError("cancelled");
17220
+ const {
17221
+ body: _body,
17222
+ headers: _headers,
17223
+ method: _method,
17224
+ signal: _signal,
17225
+ redirect: _redirect,
17226
+ ...transportOptions
17227
+ } = init ?? {};
17228
+ return upstream(request, { ...transportOptions, redirect: "error" });
17229
+ }, {
17230
+ preconnect: (...args) => upstream.preconnect?.(...args)
17231
+ });
17232
+ }
17233
+
17234
+ // src/plugin/modelResidency.ts
17235
+ function isFetch(value) {
17236
+ return typeof value === "function";
17237
+ }
17238
+ function origin(value) {
17239
+ return typeof value === "string" && URL.canParse(value) ? new URL(value).origin : undefined;
17240
+ }
17241
+ var TEMPLATE_REFERENCE = /\$\{[^}]+\}/;
17242
+ function resolveTemplateReferences(value, readEnv) {
17243
+ let unresolved = false;
17244
+ const resolved = value.replace(/\$\{([^}]+)\}/g, (match, key) => {
17245
+ const resolvedValue = readEnv(key);
17246
+ if (resolvedValue === undefined) {
17247
+ unresolved = true;
17248
+ return match;
17249
+ }
17250
+ return resolvedValue;
17251
+ });
17252
+ return unresolved ? undefined : resolved;
17253
+ }
17254
+ function endpointBinding(value, readEnv) {
17255
+ if (!TEMPLATE_REFERENCE.test(value))
17256
+ return { ambiguous: false, origin: origin(value) };
17257
+ const resolved = resolveTemplateReferences(value, readEnv);
17258
+ return resolved === undefined ? { ambiguous: true } : { ambiguous: false, origin: origin(resolved) };
17259
+ }
17260
+ function optionBaseURL(value) {
17261
+ return typeof value === "string" && value !== "" ? value : undefined;
17262
+ }
17263
+ function providerBase(provider, model) {
17264
+ return optionBaseURL(provider.options?.baseURL) ?? model?.provider?.api ?? provider.api;
17265
+ }
17266
+ function providerRoutes(provider) {
17267
+ const models = Object.values(provider.models ?? {});
17268
+ return (models.length === 0 ? [undefined] : models).map((model) => ({
17269
+ baseURL: providerBase(provider, model),
17270
+ npm: model?.provider?.npm ?? provider.npm
17271
+ }));
17272
+ }
17273
+ function wireID(key, model) {
17274
+ const id = model?.id ?? key;
17275
+ if (id.length === 0 || id !== id.trim())
17276
+ throw new ModelResidencyError("identity");
17277
+ return id;
17278
+ }
17279
+ function nativeEligibleID(id) {
17280
+ return id === "openai" || id === "anthropic" || id.startsWith("opencode");
17281
+ }
17282
+ function configuredRuntime(config, providerID) {
17283
+ const runtimes = config.local.runtimes.filter((runtime2) => runtime2.defaultModel.providerID === providerID);
17284
+ const runtime = runtimes[0];
17285
+ if (runtimes.length !== 1 || runtime === undefined || !runtime.enabled) {
17286
+ throw new ModelResidencyError("destination");
17287
+ }
17288
+ return runtime;
17289
+ }
17290
+ function createModelResidencyGuard(config, options) {
17291
+ const guarded = new Map;
17292
+ const blocked = new Set;
17293
+ let hostConfig;
17294
+ const readEnv = options.readEnv ?? ((name) => process.env[name]);
17295
+ const lemonadeRuntimes = () => config.local.runtimes.filter((runtime) => localRuntimeKind(runtime) === "lemonade");
17296
+ const protects = (providerID, baseURL) => lemonadeRuntimes().some((runtime) => {
17297
+ if (runtime.defaultModel.providerID === providerID)
17298
+ return true;
17299
+ if (baseURL === undefined)
17300
+ return false;
17301
+ const binding = endpointBinding(baseURL, readEnv);
17302
+ if (binding.ambiguous)
17303
+ return true;
17304
+ return binding.origin !== undefined && binding.origin === origin(runtime.baseURL ?? LEMONADE_DEFAULT_BASE_URL);
17305
+ });
17306
+ const resolvedWireID = (model, declaredID) => {
17307
+ const id = options.resolveApiModelID === undefined ? declaredID : options.resolveApiModelID(model);
17308
+ if (id === undefined)
17309
+ throw new ModelResidencyError("identity");
17310
+ return wireID(id, undefined);
17311
+ };
17312
+ const configHook = async (host) => {
17313
+ hostConfig = host;
17314
+ const previousGuards = new Map(guarded);
17315
+ guarded.clear();
17316
+ blocked.clear();
17317
+ for (const [providerID, provider] of Object.entries(host.provider ?? {})) {
17318
+ const routes = providerRoutes(provider);
17319
+ if (!routes.some((route) => protects(providerID, route.baseURL)))
17320
+ continue;
17321
+ const previous = previousGuards.get(providerID);
17322
+ const configuredFetch = provider.options?.fetch;
17323
+ const originalFetch = previous !== undefined && configuredFetch === previous.fetch ? previous.originalFetch : configuredFetch ?? options.fetch;
17324
+ blocked.add(providerID);
17325
+ const wasDisabled = host.disabled_providers?.includes(providerID) ?? false;
17326
+ host.disabled_providers = [
17327
+ ...new Set([...host.disabled_providers ?? [], providerID])
17328
+ ];
17329
+ const reject = Object.assign(async () => {
17330
+ throw new ModelResidencyError("transport");
17331
+ }, { preconnect: () => {} });
17332
+ provider.options = { ...provider.options, fetch: reject };
17333
+ try {
17334
+ const runtime = configuredRuntime(config, providerID);
17335
+ const baseURL = lemonadeBaseURL(runtime.baseURL ?? LEMONADE_DEFAULT_BASE_URL);
17336
+ if (localRuntimeKind(runtime) !== "lemonade" || routes.some((route) => route.baseURL === undefined || lemonadeBaseURL(route.baseURL) !== baseURL))
17337
+ throw new ModelResidencyError("destination");
17338
+ if (routes.some((route) => route.npm !== "@ai-sdk/openai-compatible") || nativeEligibleID(providerID) || !isFetch(originalFetch))
17339
+ throw new ModelResidencyError("transport");
17340
+ const modelKeys = new Map;
17341
+ for (const [key, model] of Object.entries(provider.models ?? {})) {
17342
+ if (model.options?.fetch !== undefined || model.options?.baseURL !== undefined && (typeof model.options.baseURL !== "string" || lemonadeBaseURL(model.options.baseURL) !== baseURL))
17343
+ throw new ModelResidencyError("transport");
17344
+ modelKeys.set(key, wireID(key, model));
17345
+ }
17346
+ if (!modelKeys.has(runtime.defaultModel.modelID))
17347
+ modelKeys.set(runtime.defaultModel.modelID, undefined);
17348
+ const modelIDs = () => {
17349
+ const ids = new Set;
17350
+ for (const [modelID2, declaredID] of modelKeys) {
17351
+ const id = options.resolveApiModelID === undefined ? declaredID : options.resolveApiModelID({ providerID, modelID: modelID2 });
17352
+ if (id !== undefined)
17353
+ ids.add(wireID(id, undefined));
17354
+ }
17355
+ return ids;
17356
+ };
17357
+ const fetch = createLemonadeGuardedFetch({
17358
+ ...options,
17359
+ baseURL,
17360
+ modelIDs: async () => {
17361
+ await options.modelInventoryReady?.();
17362
+ return modelIDs();
17363
+ },
17364
+ fetch: originalFetch,
17365
+ healthFetch: options.fetch
17366
+ });
17367
+ provider.options.fetch = fetch;
17368
+ guarded.set(providerID, {
17369
+ baseURL,
17370
+ fetch,
17371
+ originalFetch,
17372
+ modelIDs,
17373
+ modelKeys
17374
+ });
17375
+ blocked.delete(providerID);
17376
+ if (!wasDisabled) {
17377
+ host.disabled_providers = host.disabled_providers.filter((id) => id !== providerID);
17378
+ }
17379
+ } catch (error) {
17380
+ if (!(error instanceof ModelResidencyError))
17381
+ throw error;
17382
+ (options.warn ?? console.warn)(modelResidencyMessages.providerDisabled(providerID));
17383
+ }
17384
+ }
17385
+ };
17386
+ const beforeInference = async (input) => {
17387
+ const base = optionBaseURL(input.provider.options.baseURL) ?? input.model.api.url;
17388
+ if (!protects(input.model.providerID, base))
17389
+ return;
17390
+ const provider = guarded.get(input.model.providerID);
17391
+ if (provider === undefined || blocked.has(input.model.providerID) || input.provider.options.fetch !== provider.fetch || input.model.api.npm !== "@ai-sdk/openai-compatible" || nativeEligibleID(input.model.providerID))
17392
+ throw new ModelResidencyError("transport");
17393
+ if (lemonadeBaseURL(base) !== provider.baseURL) {
17394
+ throw new ModelResidencyError("destination");
17395
+ }
17396
+ const id = resolvedWireID({ providerID: input.model.providerID, modelID: input.model.id }, provider.modelKeys.get(input.model.id));
17397
+ if (id !== input.model.api.id)
17398
+ throw new ModelResidencyError("identity");
17399
+ if (!provider.modelKeys.has(input.model.id))
17400
+ provider.modelKeys.set(input.model.id, undefined);
17401
+ };
17402
+ const localTarget = (model) => {
17403
+ const runtime = configuredRuntime(config, model.providerID);
17404
+ if (hostConfig?.disabled_providers?.includes(model.providerID)) {
17405
+ throw new ModelResidencyError("transport");
17406
+ }
17407
+ if (runtime.baseURL === undefined)
17408
+ throw new ModelResidencyError("destination");
17409
+ const provider = hostConfig?.provider?.[model.providerID];
17410
+ const entry = Object.hasOwn(provider?.models ?? {}, model.modelID) ? provider?.models?.[model.modelID] : undefined;
17411
+ const configuredURL = provider === undefined ? undefined : providerBase(provider, entry);
17412
+ if (configuredURL !== undefined && new URL(configuredURL).href.replace(/\/$/, "") !== new URL(runtime.baseURL).href.replace(/\/$/, "")) {
17413
+ throw new ModelResidencyError("destination");
17414
+ }
17415
+ const modelID2 = resolvedWireID(model, entry === undefined && localRuntimeKind(runtime) === "lemonade" ? undefined : wireID(model.modelID, entry));
17416
+ let fetch = options.fetch;
17417
+ if (localRuntimeKind(runtime) === "lemonade") {
17418
+ const installed = guarded.get(model.providerID);
17419
+ if (installed === undefined || blocked.has(model.providerID)) {
17420
+ throw new ModelResidencyError("transport");
17421
+ }
17422
+ if (installed.baseURL !== lemonadeBaseURL(runtime.baseURL)) {
17423
+ throw new ModelResidencyError("destination");
17424
+ }
17425
+ if (!installed.modelKeys.has(model.modelID))
17426
+ installed.modelKeys.set(model.modelID, undefined);
17427
+ if (!installed.modelIDs().has(modelID2))
17428
+ throw new ModelResidencyError("identity");
17429
+ fetch = installed.fetch;
17430
+ }
17431
+ const configuredHeaders = z25.record(z25.string(), z25.string()).safeParse(provider?.options?.headers ?? {});
17432
+ if (!configuredHeaders.success)
17433
+ throw new ModelResidencyError("configuration");
17434
+ const headers = new Headers(configuredHeaders.data);
17435
+ if (typeof provider?.options?.apiKey === "string" && !headers.has("authorization")) {
17436
+ headers.set("authorization", `Bearer ${provider.options.apiKey}`);
17437
+ }
17438
+ for (const [name, value] of Object.entries(entry?.headers ?? {}))
17439
+ headers.set(name, value);
17440
+ const upstream = fetch;
17441
+ return {
17442
+ baseURL: runtime.baseURL,
17443
+ modelID: modelID2,
17444
+ fetch: [...headers].length === 0 ? fetch : Object.assign(async (input, init) => {
17445
+ const request = new Request(input, init);
17446
+ for (const [name, value] of headers) {
17447
+ if (!request.headers.has(name))
17448
+ request.headers.set(name, value);
17449
+ }
17450
+ return upstream(request);
17451
+ }, {
17452
+ preconnect: (...args) => upstream.preconnect?.(...args)
17453
+ })
17454
+ };
17455
+ };
17456
+ return { config: configHook, beforeInference, localTarget };
17457
+ }
17458
+
16709
17459
  // src/plugin/hooks.ts
16710
17460
  var codeSignals = [
16711
17461
  "```",
@@ -16849,7 +17599,10 @@ function createChatMessageHook(config, getAvailable, options = {}) {
16849
17599
  };
16850
17600
  }
16851
17601
  function createHooks(config, getAvailable, options = {}) {
17602
+ const residency = options.modelResidency ?? createModelResidencyGuard(config, { fetch: globalThis.fetch });
16852
17603
  return {
17604
+ config: residency.config,
17605
+ "chat.params": residency.beforeInference,
16853
17606
  "chat.message": createChatMessageHook(config, getAvailable, options)
16854
17607
  };
16855
17608
  }
@@ -16903,62 +17656,62 @@ ${lines.join(`
16903
17656
  }
16904
17657
 
16905
17658
  // src/memory/types.ts
16906
- import { z as z24 } from "zod";
17659
+ import { z as z26 } from "zod";
16907
17660
  var SHARED_OWNER_KEY = "*";
16908
- var OwnerKeySchema = z24.string().min(1);
16909
- var MemoryKindSchema = z24.enum(["fact", "preference", "entity"]);
16910
- var MemoryBaseSchema = z24.object({
16911
- id: z24.string().min(1),
17661
+ var OwnerKeySchema = z26.string().min(1);
17662
+ var MemoryKindSchema = z26.enum(["fact", "preference", "entity"]);
17663
+ var MemoryBaseSchema = z26.object({
17664
+ id: z26.string().min(1),
16912
17665
  ownerKey: OwnerKeySchema,
16913
- confidence: z24.number().min(0).max(1),
16914
- validFrom: z24.number().finite(),
16915
- validUntil: z24.number().finite().nullable().default(null),
16916
- createdAt: z24.number().finite(),
16917
- invalidatedAt: z24.number().finite().nullable().default(null),
16918
- sourceHash: z24.string().min(1),
16919
- supersededBy: z24.string().min(1).nullable().default(null)
17666
+ confidence: z26.number().min(0).max(1),
17667
+ validFrom: z26.number().finite(),
17668
+ validUntil: z26.number().finite().nullable().default(null),
17669
+ createdAt: z26.number().finite(),
17670
+ invalidatedAt: z26.number().finite().nullable().default(null),
17671
+ sourceHash: z26.string().min(1),
17672
+ supersededBy: z26.string().min(1).nullable().default(null)
16920
17673
  }).strict();
16921
17674
  var FactSchema = MemoryBaseSchema.extend({
16922
- kind: z24.literal("fact"),
16923
- subject: z24.string().min(1),
16924
- predicate: z24.string().min(1),
16925
- object: z24.string().min(1),
16926
- category: z24.string().min(1).nullable().default(null)
17675
+ kind: z26.literal("fact"),
17676
+ subject: z26.string().min(1),
17677
+ predicate: z26.string().min(1),
17678
+ object: z26.string().min(1),
17679
+ category: z26.string().min(1).nullable().default(null)
16927
17680
  });
16928
17681
  var PreferenceSchema = MemoryBaseSchema.extend({
16929
- kind: z24.literal("preference"),
16930
- category: z24.string().min(1),
16931
- preference: z24.string().min(1),
16932
- context: z24.string().min(1).nullable().default(null),
16933
- lastAccessedAt: z24.number().finite().nullable().default(null),
16934
- accessCount: z24.number().int().min(0).default(0)
17682
+ kind: z26.literal("preference"),
17683
+ category: z26.string().min(1),
17684
+ preference: z26.string().min(1),
17685
+ context: z26.string().min(1).nullable().default(null),
17686
+ lastAccessedAt: z26.number().finite().nullable().default(null),
17687
+ accessCount: z26.number().int().min(0).default(0)
16935
17688
  });
16936
17689
  var EntitySchema = MemoryBaseSchema.extend({
16937
- kind: z24.literal("entity"),
16938
- canonicalName: z24.string().min(1),
16939
- type: z24.string().min(1),
16940
- aliases: z24.array(z24.string().min(1)).default([])
17690
+ kind: z26.literal("entity"),
17691
+ canonicalName: z26.string().min(1),
17692
+ type: z26.string().min(1),
17693
+ aliases: z26.array(z26.string().min(1)).default([])
16941
17694
  });
16942
17695
  var RelationSchema = MemoryBaseSchema.extend({
16943
- kind: z24.literal("relation"),
16944
- from: z24.string().min(1),
16945
- to: z24.string().min(1),
16946
- predicate: z24.string().min(1),
16947
- annotation: z24.string().min(1).nullable().default(null)
17696
+ kind: z26.literal("relation"),
17697
+ from: z26.string().min(1),
17698
+ to: z26.string().min(1),
17699
+ predicate: z26.string().min(1),
17700
+ annotation: z26.string().min(1).nullable().default(null)
16948
17701
  });
16949
- var MemoryRecordSchema = z24.discriminatedUnion("kind", [
17702
+ var MemoryRecordSchema = z26.discriminatedUnion("kind", [
16950
17703
  FactSchema,
16951
17704
  PreferenceSchema,
16952
17705
  EntitySchema,
16953
17706
  RelationSchema
16954
17707
  ]);
16955
- var RecallQuerySchema = z24.object({
17708
+ var RecallQuerySchema = z26.object({
16956
17709
  ownerKey: OwnerKeySchema,
16957
- kinds: z24.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
16958
- asOf: z24.number().finite().nullable().default(null),
16959
- limit: z24.number().int().positive().default(8),
16960
- minSimilarity: z24.number().min(0).max(1).default(0.2),
16961
- includeShared: z24.boolean().default(true)
17710
+ kinds: z26.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
17711
+ asOf: z26.number().finite().nullable().default(null),
17712
+ limit: z26.number().int().positive().default(8),
17713
+ minSimilarity: z26.number().min(0).max(1).default(0.2),
17714
+ includeShared: z26.boolean().default(true)
16962
17715
  }).strict();
16963
17716
 
16964
17717
  // src/memory/rank.ts
@@ -17529,42 +18282,42 @@ function buildMemoryInjector(deps, policy) {
17529
18282
 
17530
18283
  // src/plugin/memoryTool.ts
17531
18284
  import { tool as tool4 } from "@opencode-ai/plugin";
17532
- import { z as z26 } from "zod";
18285
+ import { z as z28 } from "zod";
17533
18286
 
17534
18287
  // src/memory/extract.ts
17535
- import { z as z25 } from "zod";
18288
+ import { z as z27 } from "zod";
17536
18289
  var REDACTED = "[redacted]";
17537
- var RawFactSchema = z25.object({
17538
- subject: z25.string().min(1),
17539
- predicate: z25.string().min(1),
17540
- object: z25.string().min(1),
17541
- category: z25.string().min(1).nullable().optional(),
17542
- confidence: z25.number().min(0).max(1).optional()
18290
+ var RawFactSchema = z27.object({
18291
+ subject: z27.string().min(1),
18292
+ predicate: z27.string().min(1),
18293
+ object: z27.string().min(1),
18294
+ category: z27.string().min(1).nullable().optional(),
18295
+ confidence: z27.number().min(0).max(1).optional()
17543
18296
  });
17544
- var RawPreferenceSchema = z25.object({
17545
- category: z25.string().min(1),
17546
- preference: z25.string().min(1),
17547
- context: z25.string().min(1).nullable().optional(),
17548
- confidence: z25.number().min(0).max(1).optional()
18297
+ var RawPreferenceSchema = z27.object({
18298
+ category: z27.string().min(1),
18299
+ preference: z27.string().min(1),
18300
+ context: z27.string().min(1).nullable().optional(),
18301
+ confidence: z27.number().min(0).max(1).optional()
17549
18302
  });
17550
- var RawEntitySchema = z25.object({
17551
- canonicalName: z25.string().min(1),
17552
- type: z25.string().min(1),
17553
- aliases: z25.array(z25.string().min(1)).optional(),
17554
- confidence: z25.number().min(0).max(1).optional()
18303
+ var RawEntitySchema = z27.object({
18304
+ canonicalName: z27.string().min(1),
18305
+ type: z27.string().min(1),
18306
+ aliases: z27.array(z27.string().min(1)).optional(),
18307
+ confidence: z27.number().min(0).max(1).optional()
17555
18308
  });
17556
- var RawRelationSchema = z25.object({
17557
- from: z25.string().min(1),
17558
- to: z25.string().min(1),
17559
- predicate: z25.string().min(1),
17560
- annotation: z25.string().min(1).nullable().optional(),
17561
- confidence: z25.number().min(0).max(1).optional()
18309
+ var RawRelationSchema = z27.object({
18310
+ from: z27.string().min(1),
18311
+ to: z27.string().min(1),
18312
+ predicate: z27.string().min(1),
18313
+ annotation: z27.string().min(1).nullable().optional(),
18314
+ confidence: z27.number().min(0).max(1).optional()
17562
18315
  }).strict();
17563
- var RawExtractionSchema = z25.object({
17564
- facts: z25.array(RawFactSchema).default([]),
17565
- preferences: z25.array(RawPreferenceSchema).default([]),
17566
- entities: z25.array(RawEntitySchema).default([]),
17567
- relations: z25.array(RawRelationSchema).default([])
18316
+ var RawExtractionSchema = z27.object({
18317
+ facts: z27.array(RawFactSchema).default([]),
18318
+ preferences: z27.array(RawPreferenceSchema).default([]),
18319
+ entities: z27.array(RawEntitySchema).default([]),
18320
+ relations: z27.array(RawRelationSchema).default([])
17568
18321
  });
17569
18322
  var DEFAULT_CONFIDENCE = 0.6;
17570
18323
  var SYSTEM_PROMPT = [
@@ -17818,13 +18571,13 @@ var memoryToolMessages = {
17818
18571
 
17819
18572
  // src/plugin/memoryTool.ts
17820
18573
  var SCRIBE_ROLE_ID = "scribe";
17821
- var MemoryMessageSchema = z26.object({
17822
- role: z26.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
17823
- content: z26.string().min(1).describe(memoryToolMessages.messageContentDescription)
18574
+ var MemoryMessageSchema = z28.object({
18575
+ role: z28.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
18576
+ content: z28.string().min(1).describe(memoryToolMessages.messageContentDescription)
17824
18577
  }).strict();
17825
- var MemoryWritePayloadSchema = z26.object({
17826
- messages: z26.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
17827
- roleID: z26.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
18578
+ var MemoryWritePayloadSchema = z28.object({
18579
+ messages: z28.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
18580
+ roleID: z28.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
17828
18581
  }).strict();
17829
18582
  function createMemoryTool(deps) {
17830
18583
  return tool4({
@@ -17836,7 +18589,7 @@ function createMemoryTool(deps) {
17836
18589
  })).describe(memoryToolMessages.messagesDescription),
17837
18590
  roleID: tool4.schema.string().optional().describe(memoryToolMessages.roleIDDescription)
17838
18591
  },
17839
- async execute(args) {
18592
+ async execute(args, toolContext) {
17840
18593
  const parsed = MemoryWritePayloadSchema.safeParse(args);
17841
18594
  if (!parsed.success) {
17842
18595
  return memoryToolMessages.validationError(parsed.error.message);
@@ -17857,33 +18610,83 @@ function createMemoryTool(deps) {
17857
18610
  if (executionMode2 === "frontier") {
17858
18611
  return memoryToolMessages.frontierDomainRefusal;
17859
18612
  }
17860
- if (!await deps.localRuntimeReachable()) {
17861
- return memoryToolMessages.localRuntimeUnavailable;
17862
- }
17863
- const runtime = deps.config.local.runtimes.find((rt) => rt.enabled && typeof rt.baseURL === "string" && rt.baseURL.length > 0);
17864
- if (runtime?.baseURL === undefined) {
17865
- return memoryToolMessages.localRuntimeBaseURLMissing;
17866
- }
17867
- const modelID = semantic.extraction.model?.modelID ?? runtime.defaultModel.modelID;
17868
- const transcript = messages.map((m) => `${m.role}: ${m.content}`).join(`
17869
- `);
17870
- const sourceHash = sha256Hex(transcript);
17871
- const now = deps.now();
17872
- const context = {
17873
- ownerKey: semantic.scope === "user" ? "user" : "project",
17874
- now,
17875
- sourceHash,
17876
- storeContent: semantic.storeContent,
17877
- idPrefix: `${sourceHash.slice(0, 12)}:${now}`
17878
- };
17879
18613
  let records;
17880
18614
  try {
18615
+ const signal = toolContext.abort;
18616
+ const throwIfAborted = () => {
18617
+ if (signal?.aborted)
18618
+ throw new ModelResidencyError("cancelled");
18619
+ };
18620
+ throwIfAborted();
18621
+ const configuredModel = semantic.extraction.model;
18622
+ let runtime;
18623
+ if (configuredModel != null) {
18624
+ const matches = deps.config.local.runtimes.filter((candidate) => candidate.defaultModel.providerID === configuredModel.providerID);
18625
+ runtime = matches[0];
18626
+ if (matches.length !== 1 || runtime === undefined || !runtime.enabled) {
18627
+ return modelResidencyMessages.extractionRuntimeUnavailable;
18628
+ }
18629
+ } else {
18630
+ runtime = deps.config.local.runtimes.find((candidate) => candidate.enabled && typeof candidate.baseURL === "string" && candidate.baseURL.length > 0);
18631
+ }
18632
+ if (runtime?.baseURL === undefined || runtime.baseURL.length === 0) {
18633
+ return memoryToolMessages.localRuntimeBaseURLMissing;
18634
+ }
18635
+ const baseURL = runtime.baseURL;
18636
+ const model = configuredModel ?? runtime.defaultModel;
18637
+ const selectedRuntime = runtime;
18638
+ const deadline = {
18639
+ ...deps.residencyOptions,
18640
+ ...signal === undefined ? {} : { signal }
18641
+ };
18642
+ const reachable = localRuntimeKind(runtime) === "lemonade" ? await withResidencyDeadline((signal2) => deps.localRuntimeReachable(selectedRuntime, signal2), deadline) : await deps.localRuntimeReachable(runtime);
18643
+ throwIfAborted();
18644
+ if (!reachable)
18645
+ return memoryToolMessages.localRuntimeUnavailable;
18646
+ const resolveTarget = deps.resolveExtractionTarget;
18647
+ const target = resolveTarget ? await withResidencyDeadline(async () => resolveTarget(model), deadline) : {
18648
+ baseURL,
18649
+ modelID: model.modelID,
18650
+ fetch: localRuntimeKind(runtime) === "lemonade" ? createLemonadeGuardedFetch({
18651
+ baseURL,
18652
+ fetch: deps.fetch,
18653
+ modelIDs: new Set([model.modelID]),
18654
+ ...deps.residencyOptions
18655
+ }) : deps.fetch
18656
+ };
18657
+ throwIfAborted();
18658
+ if (target === undefined || target.baseURL !== baseURL) {
18659
+ throw new ModelResidencyError("destination");
18660
+ }
18661
+ const fetch = signal === undefined ? target.fetch : Object.assign(async (input, init) => {
18662
+ throwIfAborted();
18663
+ const response = await target.fetch(input, {
18664
+ ...init,
18665
+ signal
18666
+ });
18667
+ throwIfAborted();
18668
+ return response;
18669
+ }, {
18670
+ preconnect: (...args2) => target.fetch.preconnect?.(...args2)
18671
+ });
18672
+ const transcript = messages.map((m) => `${m.role}: ${m.content}`).join(`
18673
+ `);
18674
+ const sourceHash = sha256Hex(transcript);
18675
+ const now = deps.now();
18676
+ const context = {
18677
+ ownerKey: semantic.scope === "user" ? "user" : "project",
18678
+ now,
18679
+ sourceHash,
18680
+ storeContent: semantic.storeContent,
18681
+ idPrefix: `${sourceHash.slice(0, 12)}:${now}`
18682
+ };
17881
18683
  records = await extractMemory({
17882
- baseURL: runtime.baseURL,
17883
- modelID,
18684
+ baseURL: target.baseURL,
18685
+ modelID: target.modelID,
17884
18686
  messages,
17885
18687
  context
17886
- }, { fetch: deps.fetch, ...deps.warn ? { warn: deps.warn } : {} });
18688
+ }, { fetch, ...deps.warn ? { warn: deps.warn } : {} });
18689
+ throwIfAborted();
17887
18690
  } catch (error) {
17888
18691
  const reason = error instanceof Error ? error.message : String(error);
17889
18692
  return memoryToolMessages.extractionFailure(reason);
@@ -17949,10 +18752,10 @@ function createOrchestratedModelOwnership() {
17949
18752
 
17950
18753
  // src/plugin/orchestrateTool.ts
17951
18754
  import { tool as tool5 } from "@opencode-ai/plugin";
17952
- import { z as z28 } from "zod";
18755
+ import { z as z30 } from "zod";
17953
18756
 
17954
18757
  // src/orchestrator/coordinator.ts
17955
- import { z as z27 } from "zod";
18758
+ import { z as z29 } from "zod";
17956
18759
 
17957
18760
  // src/orchestrator/nodeIteration.ts
17958
18761
  var DEFAULT_MAX_RETRIES = 2;
@@ -18162,12 +18965,15 @@ async function applyReportDurabilityGate(results, probe) {
18162
18965
  }
18163
18966
 
18164
18967
  // src/orchestrator/runtimeSelection.ts
18165
- function configuredRuntime(runtimeId, config) {
18968
+ function configuredRuntime2(runtimeId, config) {
18166
18969
  return config.local.runtimes.find((runtime) => runtime.id === runtimeId);
18167
18970
  }
18168
18971
  function runtimeIdOfModel(model, config) {
18169
18972
  return config.local.runtimes.find((runtime) => sameModel(runtime.defaultModel, model))?.id;
18170
18973
  }
18974
+ function enabledRuntimeForProvider(providerID, config) {
18975
+ return config.local.runtimes.find((runtime) => runtime.enabled && runtime.defaultModel.providerID === providerID)?.id;
18976
+ }
18171
18977
  function roleLocalModel(role, runtimeId, fallback, runtime) {
18172
18978
  const pinned = role.localModels?.[runtimeId];
18173
18979
  if (pinned !== undefined) {
@@ -18182,7 +18988,7 @@ function runtimeClassModel(modelClass, runtime) {
18182
18988
  }
18183
18989
  return modelClass === "thinking" ? runtime.thinkingModel : runtime.coderModel;
18184
18990
  }
18185
- function selectDispatchRuntime(role, decision2, reachable, config) {
18991
+ function selectDispatchRuntime(role, decision2, reachable, config, availableModels2) {
18186
18992
  if (decision2.routeKind !== "local") {
18187
18993
  return { runtimeId: undefined, decision: decision2 };
18188
18994
  }
@@ -18193,7 +18999,7 @@ function selectDispatchRuntime(role, decision2, reachable, config) {
18193
18999
  if (!reachable.has(runtimeId)) {
18194
19000
  continue;
18195
19001
  }
18196
- const runtime = configuredRuntime(runtimeId, config);
19002
+ const runtime = configuredRuntime2(runtimeId, config);
18197
19003
  if (runtime?.defaultModel.providerID === decision2.selected.providerID) {
18198
19004
  return { runtimeId, decision: decision2 };
18199
19005
  }
@@ -18203,15 +19009,17 @@ function selectDispatchRuntime(role, decision2, reachable, config) {
18203
19009
  if (exactRuntimeId !== undefined) {
18204
19010
  return { runtimeId: exactRuntimeId, decision: decision2 };
18205
19011
  }
18206
- const providerRuntime = config.local.runtimes.find((runtime) => runtime.enabled && runtime.defaultModel.providerID === decision2.selected.providerID);
18207
- return { runtimeId: providerRuntime?.id, decision: decision2 };
19012
+ return {
19013
+ runtimeId: enabledRuntimeForProvider(decision2.selected.providerID, config),
19014
+ decision: decision2
19015
+ };
18208
19016
  }
18209
19017
  if (listed !== undefined && listed.length > 0 && reachable !== undefined) {
18210
19018
  for (const runtimeId of listed) {
18211
19019
  if (!reachable.has(runtimeId)) {
18212
19020
  continue;
18213
19021
  }
18214
- const runtime = configuredRuntime(runtimeId, config);
19022
+ const runtime = configuredRuntime2(runtimeId, config);
18215
19023
  if (runtime === undefined) {
18216
19024
  continue;
18217
19025
  }
@@ -18224,7 +19032,7 @@ function selectDispatchRuntime(role, decision2, reachable, config) {
18224
19032
  }
18225
19033
  const resolvedRuntimeId = runtimeIdOfModel(decision2.selected, config);
18226
19034
  if (resolvedRuntimeId !== undefined) {
18227
- const pinned = roleLocalModel(role, resolvedRuntimeId, decision2.selected, configuredRuntime(resolvedRuntimeId, config));
19035
+ const pinned = roleLocalModel(role, resolvedRuntimeId, decision2.selected, configuredRuntime2(resolvedRuntimeId, config));
18228
19036
  if (pinned !== decision2.selected) {
18229
19037
  return {
18230
19038
  runtimeId: resolvedRuntimeId,
@@ -18232,8 +19040,9 @@ function selectDispatchRuntime(role, decision2, reachable, config) {
18232
19040
  };
18233
19041
  }
18234
19042
  }
19043
+ const knownAlias = localModelAliases(decision2.selected.providerID, availableModels2).has(decision2.selected.modelID);
18235
19044
  return {
18236
- runtimeId: resolvedRuntimeId,
19045
+ runtimeId: resolvedRuntimeId ?? (knownAlias ? enabledRuntimeForProvider(decision2.selected.providerID, config) : undefined),
18237
19046
  decision: decision2
18238
19047
  };
18239
19048
  }
@@ -18420,7 +19229,7 @@ function buildRecord(input, decision2, subsession, deps, decisionID, batchID) {
18420
19229
  }
18421
19230
  return record;
18422
19231
  }
18423
- var TelemetryFailureClassSchema = z27.enum([
19232
+ var TelemetryFailureClassSchema = z29.enum([
18424
19233
  "create-rejected",
18425
19234
  "create-no-id",
18426
19235
  "prompt-rejected",
@@ -18679,7 +19488,7 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
18679
19488
  if (routed.routeKind === "blocked") {
18680
19489
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
18681
19490
  }
18682
- const selection = selectDispatchRuntime(role, routed, batch.set, input.config);
19491
+ const selection = selectDispatchRuntime(role, routed, batch.set, input.config, input.availableModels);
18683
19492
  const decision2 = selection.decision;
18684
19493
  const composed = composeSubsessionPrompt({
18685
19494
  prompt: input.prompt,
@@ -18761,8 +19570,9 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
18761
19570
  if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
18762
19571
  const limiter = deps.runtimeLimiter;
18763
19572
  const priority = priorityForRole(role);
18764
- const dispatchRuntime = configuredRuntime(runtimeId, input.config) ?? { id: runtimeId };
18765
- const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId)), decision2.selected.modelID);
19573
+ const dispatchRuntime = configuredRuntime2(runtimeId, input.config) ?? { id: runtimeId };
19574
+ const aliases = localModelAliases(decision2.selected.providerID, input.availableModels);
19575
+ const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId), aliases), aliases.get(decision2.selected.modelID) ?? decision2.selected.modelID);
18766
19576
  release = limiter.tryAcquire(poolKey, priority);
18767
19577
  let waitedMs = 0;
18768
19578
  if (release === undefined) {
@@ -19236,7 +20046,7 @@ async function runRoleTasks(inputs, deps, options = {}) {
19236
20046
  var _pluginWarnedRoles = new Set;
19237
20047
  var EMPTY_ROLE_ID_SET = new Set;
19238
20048
  var SAFE_ROLE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
19239
- var RoleIDSchema = z28.string().min(1).superRefine((roleID, ctx) => {
20049
+ var RoleIDSchema = z30.string().min(1).superRefine((roleID, ctx) => {
19240
20050
  if (!SAFE_ROLE_ID.test(roleID)) {
19241
20051
  ctx.addIssue({
19242
20052
  code: "custom",
@@ -19244,16 +20054,16 @@ var RoleIDSchema = z28.string().min(1).superRefine((roleID, ctx) => {
19244
20054
  });
19245
20055
  }
19246
20056
  });
19247
- var RoleAssignmentSchema = z28.object({
20057
+ var RoleAssignmentSchema = z30.object({
19248
20058
  roleID: RoleIDSchema.describe("Role identifier from the roster"),
19249
- prompt: z28.string().min(1).describe("Work prompt for this role"),
19250
- title: z28.string().min(1).optional().describe("Human-readable title"),
19251
- dependsOn: z28.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
20059
+ prompt: z30.string().min(1).describe("Work prompt for this role"),
20060
+ title: z30.string().min(1).optional().describe("Human-readable title"),
20061
+ dependsOn: z30.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
19252
20062
  }).strict();
19253
- var OrchestratePayloadSchema = z28.object({
19254
- assignments: z28.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
19255
- parentSessionID: z28.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
19256
- directory: z28.string().min(1).optional().describe("Working directory override")
20063
+ var OrchestratePayloadSchema = z30.object({
20064
+ assignments: z30.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
20065
+ parentSessionID: z30.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
20066
+ directory: z30.string().min(1).optional().describe("Working directory override")
19257
20067
  }).strict();
19258
20068
  function validateAssignmentDependencies(assignments) {
19259
20069
  const ids = new Set;
@@ -19989,7 +20799,7 @@ ${advisory}`;
19989
20799
 
19990
20800
  // src/plugin/registerCastTool.ts
19991
20801
  import { tool as tool6 } from "@opencode-ai/plugin";
19992
- import { z as z29 } from "zod";
20802
+ import { z as z31 } from "zod";
19993
20803
 
19994
20804
  // src/messages/rosterSummary.ts
19995
20805
  var ROSTER_SUMMARY_HEADING = "Registered team model policies";
@@ -20218,13 +21028,13 @@ async function persistPreparedTeam(storage, files) {
20218
21028
  }
20219
21029
 
20220
21030
  // src/plugin/registerCastTool.ts
20221
- var CastEntrySchema = z29.object({
20222
- roleID: z29.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
20223
- agentName: z29.string().min(1).describe("Themed character name assigned to the role")
21031
+ var CastEntrySchema = z31.object({
21032
+ roleID: z31.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
21033
+ agentName: z31.string().min(1).describe("Themed character name assigned to the role")
20224
21034
  }).strict();
20225
- var RegisterCastPayloadSchema = z29.object({
20226
- universe: z29.string().min(1).describe("Casting universe for this roster"),
20227
- entries: z29.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
21035
+ var RegisterCastPayloadSchema = z31.object({
21036
+ universe: z31.string().min(1).describe("Casting universe for this roster"),
21037
+ entries: z31.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
20228
21038
  }).strict();
20229
21039
  function offendingFields(error) {
20230
21040
  const fields = error.issues.flatMap((issue) => {
@@ -20343,6 +21153,307 @@ function createRegisterCastTool(deps) {
20343
21153
  });
20344
21154
  }
20345
21155
 
21156
+ // src/plugin/reloadTool.ts
21157
+ import { resolve as resolve9 } from "node:path";
21158
+ import { tool as tool7 } from "@opencode-ai/plugin/tool";
21159
+ var WAIT_MS = 30000;
21160
+ var SDK_TIMEOUT_MS = 5000;
21161
+ var POLL_MS2 = 1000;
21162
+ var schema = tool7.schema.object({
21163
+ action: tool7.schema.enum(["request", "cancel"]).describe(reloadMessages.action),
21164
+ confirmed: tool7.schema.boolean().describe(reloadMessages.confirmed)
21165
+ }).strict();
21166
+ function sdkData(value) {
21167
+ if (typeof value !== "object" || value === null)
21168
+ return;
21169
+ try {
21170
+ const prototype = Object.getPrototypeOf(value);
21171
+ if (prototype !== Object.prototype && prototype !== null)
21172
+ return;
21173
+ const error = Object.getOwnPropertyDescriptor(value, "error");
21174
+ const data = Object.getOwnPropertyDescriptor(value, "data");
21175
+ if (error !== undefined && (!("value" in error) || error.value !== undefined))
21176
+ return;
21177
+ return data !== undefined && "value" in data ? data.value : undefined;
21178
+ } catch {
21179
+ return;
21180
+ }
21181
+ }
21182
+ function createReloadController(deps) {
21183
+ const scheduler = deps.scheduler ?? {
21184
+ now: () => Date.now(),
21185
+ schedule: (run, ms) => {
21186
+ const timer = setTimeout(run, ms);
21187
+ timer.unref();
21188
+ return () => clearTimeout(timer);
21189
+ }
21190
+ };
21191
+ const directory = resolve9(deps.directory);
21192
+ let pending;
21193
+ let authorizing;
21194
+ let closed = false;
21195
+ function current(request) {
21196
+ return pending === request && !request.abort.signal.aborted;
21197
+ }
21198
+ function finish(request, code) {
21199
+ if (!current(request))
21200
+ return;
21201
+ pending = undefined;
21202
+ request.stopDeadline();
21203
+ request.stopPoll?.();
21204
+ request.unlinkCaller();
21205
+ request.abort.abort();
21206
+ deps.notify(code);
21207
+ }
21208
+ function revokeAuthorization() {
21209
+ authorizing?.abort.abort();
21210
+ authorizing = undefined;
21211
+ }
21212
+ function cancel() {
21213
+ revokeAuthorization();
21214
+ if (pending !== undefined)
21215
+ finish(pending, pending.disposing ? "reload-unconfirmed" : "reload-cancelled");
21216
+ }
21217
+ async function requestPermission(context, authorization) {
21218
+ let finishInterruption;
21219
+ const interrupted = new Promise((done) => {
21220
+ finishInterruption = done;
21221
+ });
21222
+ const onAbort = () => finishInterruption("cancelled");
21223
+ const stopTimer = scheduler.schedule(() => finishInterruption("timeout"), Math.max(0, authorization.deadline - scheduler.now()));
21224
+ context.abort.addEventListener("abort", onAbort, { once: true });
21225
+ authorization.abort.signal.addEventListener("abort", onAbort, {
21226
+ once: true
21227
+ });
21228
+ try {
21229
+ return await Promise.race([
21230
+ context.ask({
21231
+ permission: "openteam-reload",
21232
+ patterns: [directory],
21233
+ always: [directory],
21234
+ metadata: { directory }
21235
+ }).then(() => "approved"),
21236
+ interrupted
21237
+ ]);
21238
+ } finally {
21239
+ stopTimer();
21240
+ context.abort.removeEventListener("abort", onAbort);
21241
+ authorization.abort.signal.removeEventListener("abort", onAbort);
21242
+ }
21243
+ }
21244
+ function bounded(request, run) {
21245
+ return new Promise((done) => {
21246
+ const operation = new AbortController;
21247
+ let settled = false;
21248
+ const settle = (outcome) => {
21249
+ if (settled)
21250
+ return;
21251
+ settled = true;
21252
+ stopTimer();
21253
+ request.abort.signal.removeEventListener("abort", onAbort);
21254
+ if (outcome.kind !== "settled")
21255
+ operation.abort();
21256
+ done(outcome);
21257
+ };
21258
+ const onAbort = () => settle({ kind: "cancelled" });
21259
+ const stopTimer = scheduler.schedule(() => settle({ kind: "timeout" }), Math.max(0, Math.min(SDK_TIMEOUT_MS, request.deadline - scheduler.now())));
21260
+ request.abort.signal.addEventListener("abort", onAbort, { once: true });
21261
+ try {
21262
+ run(operation.signal).then((value) => settle({ kind: "settled", value }), () => settle({ kind: "failed" }));
21263
+ } catch {
21264
+ settle({ kind: "failed" });
21265
+ }
21266
+ });
21267
+ }
21268
+ function poll(request) {
21269
+ if (!current(request) || request.stopPoll !== undefined)
21270
+ return;
21271
+ request.stopPoll = scheduler.schedule(() => {
21272
+ request.stopPoll = undefined;
21273
+ probe(request);
21274
+ }, POLL_MS2);
21275
+ }
21276
+ async function probe(request) {
21277
+ if (!current(request) || !request.callerIdle || request.probing || request.disposing)
21278
+ return;
21279
+ if (scheduler.now() >= request.deadline) {
21280
+ finish(request, "reload-timeout");
21281
+ return;
21282
+ }
21283
+ request.stopPoll?.();
21284
+ request.stopPoll = undefined;
21285
+ request.probing = true;
21286
+ const revision = request.revision;
21287
+ try {
21288
+ for (let check = 0;check < 2; check++) {
21289
+ const outcome2 = await bounded(request, (signal) => deps.client.session.status({ query: { directory }, signal }));
21290
+ if (!current(request))
21291
+ return;
21292
+ if (outcome2.kind !== "settled") {
21293
+ finish(request, "reload-status-failed");
21294
+ return;
21295
+ }
21296
+ const snapshot = parseSessionStatusMap(sdkData(outcome2.value));
21297
+ if (snapshot === undefined) {
21298
+ finish(request, "reload-status-failed");
21299
+ return;
21300
+ }
21301
+ if (revision !== request.revision || !request.callerIdle) {
21302
+ poll(request);
21303
+ return;
21304
+ }
21305
+ if (Object.values(snapshot).some((status) => status.type !== "idle")) {
21306
+ if (Object.hasOwn(snapshot, request.sessionID) && snapshot[request.sessionID]?.type !== "idle")
21307
+ request.callerIdle = false;
21308
+ if (!request.waitingReported) {
21309
+ request.waitingReported = true;
21310
+ deps.notify("reload-waiting");
21311
+ }
21312
+ poll(request);
21313
+ return;
21314
+ }
21315
+ }
21316
+ if (scheduler.now() >= request.deadline) {
21317
+ finish(request, "reload-timeout");
21318
+ return;
21319
+ }
21320
+ request.disposing = true;
21321
+ closed = true;
21322
+ const outcome = await bounded(request, (signal) => deps.client.instance.dispose({ query: { directory }, signal }));
21323
+ if (!current(request))
21324
+ return;
21325
+ finish(request, outcome.kind === "timeout" ? "reload-unconfirmed" : outcome.kind === "settled" && sdkData(outcome.value) === true ? "reload-accepted" : "reload-dispose-failed");
21326
+ } finally {
21327
+ request.probing = false;
21328
+ if (current(request) && request.callerIdle && !request.disposing && revision !== request.revision)
21329
+ probe(request);
21330
+ }
21331
+ }
21332
+ const definition = {
21333
+ description: reloadMessages.description,
21334
+ args: schema.shape,
21335
+ async execute(args, context) {
21336
+ const parsed = schema.safeParse(args);
21337
+ if (!parsed.success)
21338
+ return reloadMessages.invalid;
21339
+ if (context.agent !== "openteam" || resolve9(context.directory) !== directory || context.sessionID.trim().length === 0)
21340
+ return reloadMessages.denied;
21341
+ if (parsed.data.action === "cancel") {
21342
+ if ((pending?.sessionID ?? authorizing?.sessionID) !== context.sessionID)
21343
+ return reloadMessages.none;
21344
+ const message = pending?.disposing ? reloadMessages["reload-unconfirmed"] : reloadMessages["reload-cancelled"];
21345
+ cancel();
21346
+ return message;
21347
+ }
21348
+ if (!parsed.data.confirmed) {
21349
+ const inFlight = pending?.sessionID === context.sessionID && pending.disposing;
21350
+ if ((pending?.sessionID ?? authorizing?.sessionID) === context.sessionID)
21351
+ cancel();
21352
+ return inFlight ? reloadMessages["reload-unconfirmed"] : reloadMessages.declined;
21353
+ }
21354
+ if (context.abort.aborted)
21355
+ return reloadMessages["reload-cancelled"];
21356
+ if (closed)
21357
+ return reloadMessages.closed;
21358
+ if (authorizing !== undefined)
21359
+ return authorizing.sessionID === context.sessionID ? reloadMessages.authorizing : reloadMessages.other;
21360
+ if (pending !== undefined) {
21361
+ if (pending.sessionID !== context.sessionID)
21362
+ return reloadMessages.other;
21363
+ pending.toolDone = false;
21364
+ pending.callerIdle = false;
21365
+ pending.revision++;
21366
+ return reloadMessages["reload-queued"];
21367
+ }
21368
+ const authorization = {
21369
+ sessionID: context.sessionID,
21370
+ deadline: scheduler.now() + WAIT_MS,
21371
+ abort: new AbortController
21372
+ };
21373
+ authorizing = authorization;
21374
+ try {
21375
+ const permission = await requestPermission(context, authorization);
21376
+ if (closed)
21377
+ return reloadMessages.closed;
21378
+ if (authorizing !== authorization || context.abort.aborted || permission === "cancelled")
21379
+ return reloadMessages["reload-cancelled"];
21380
+ if (permission === "timeout" || scheduler.now() >= authorization.deadline) {
21381
+ deps.notify("reload-timeout");
21382
+ return reloadMessages["reload-timeout"];
21383
+ }
21384
+ const onAbort = () => {
21385
+ if (pending === request)
21386
+ cancel();
21387
+ };
21388
+ const request = {
21389
+ sessionID: context.sessionID,
21390
+ deadline: authorization.deadline,
21391
+ abort: new AbortController,
21392
+ unlinkCaller: () => context.abort.removeEventListener("abort", onAbort),
21393
+ stopDeadline: () => {},
21394
+ stopPoll: undefined,
21395
+ toolDone: false,
21396
+ callerIdle: false,
21397
+ probing: false,
21398
+ disposing: false,
21399
+ waitingReported: false,
21400
+ revision: 0
21401
+ };
21402
+ pending = request;
21403
+ context.abort.addEventListener("abort", onAbort, { once: true });
21404
+ request.stopDeadline = scheduler.schedule(() => finish(request, request.disposing ? "reload-unconfirmed" : "reload-timeout"), request.deadline - scheduler.now());
21405
+ deps.notify("reload-queued");
21406
+ return reloadMessages["reload-queued"];
21407
+ } finally {
21408
+ if (authorizing === authorization)
21409
+ authorizing = undefined;
21410
+ }
21411
+ }
21412
+ };
21413
+ return {
21414
+ tool: definition,
21415
+ toolCompleted(sessionID) {
21416
+ if (pending?.sessionID !== sessionID)
21417
+ return;
21418
+ pending.unlinkCaller();
21419
+ pending.toolDone = true;
21420
+ pending.callerIdle = false;
21421
+ pending.revision++;
21422
+ },
21423
+ userTurn(_sessionID) {
21424
+ cancel();
21425
+ },
21426
+ event(event) {
21427
+ const sessionID = pending?.sessionID ?? authorizing?.sessionID;
21428
+ if (sessionID === undefined)
21429
+ return;
21430
+ if (event.type === "session.error" && (event.properties.sessionID === undefined || event.properties.sessionID === sessionID) || event.type === "session.deleted" && event.properties.info.id === sessionID) {
21431
+ cancel();
21432
+ return;
21433
+ }
21434
+ if (event.type === "session.created" || event.type === "session.status" && event.properties.status.type !== "idle")
21435
+ revokeAuthorization();
21436
+ const request = pending;
21437
+ if (request === undefined)
21438
+ return;
21439
+ if (event.type === "session.created" || event.type === "session.status") {
21440
+ request.revision++;
21441
+ if (event.type === "session.status" && event.properties.sessionID === request.sessionID && event.properties.status.type !== "idle")
21442
+ request.callerIdle = false;
21443
+ }
21444
+ if (event.type === "session.idle" || event.type === "session.status" && event.properties.status.type === "idle") {
21445
+ if (event.properties.sessionID === request.sessionID && request.toolDone)
21446
+ request.callerIdle = true;
21447
+ probe(request);
21448
+ }
21449
+ },
21450
+ dispose() {
21451
+ closed = true;
21452
+ cancel();
21453
+ }
21454
+ };
21455
+ }
21456
+
20346
21457
  // src/plugin/sessionTracker.ts
20347
21458
  function createSessionTracker() {
20348
21459
  const knownSessions = new Set;
@@ -20542,7 +21653,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
20542
21653
  if (runtime?.baseURL === undefined) {
20543
21654
  return;
20544
21655
  }
20545
- const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
21656
+ const modelID2 = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
20546
21657
  const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
20547
21658
  const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
20548
21659
  const fetchImpl = deps.fetch ?? globalThis.fetch;
@@ -20555,8 +21666,8 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
20555
21666
  if (rebuildResult.rejectedLines > 0) {
20556
21667
  warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
20557
21668
  }
20558
- const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
20559
- await ensureMemoryEmbeddings({ index, embedder, model: modelID, warn });
21669
+ const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID: modelID2 }, { fetch: fetchImpl });
21670
+ await ensureMemoryEmbeddings({ index, embedder, model: modelID2, warn });
20560
21671
  return {
20561
21672
  index,
20562
21673
  embedder,
@@ -20580,22 +21691,22 @@ import {
20580
21691
  statSync,
20581
21692
  unlinkSync as unlinkSync2
20582
21693
  } from "node:fs";
20583
- import { join as join15, resolve as resolve10 } from "node:path";
21694
+ import { join as join15, resolve as resolve11 } from "node:path";
20584
21695
  import { ZodError } from "zod";
20585
21696
 
20586
21697
  // src/storage/lifecycle/codec.ts
20587
- import { z as z30 } from "zod";
21698
+ import { z as z32 } from "zod";
20588
21699
  var LIFECYCLE_CODEC_VERSION = 1;
20589
21700
  var LIFECYCLE_GENESIS_DIGEST = sha256Hex("openteam/lifecycle-journal/genesis/v1");
20590
21701
  var NUL5 = "\x00";
20591
21702
  var NEWLINE2 = `
20592
21703
  `;
20593
- var frameSchema = z30.object({
20594
- v: z30.literal(LIFECYCLE_CODEC_VERSION),
20595
- seq: z30.number().int().nonnegative(),
20596
- prev: z30.string().min(1),
20597
- sum: z30.string().min(1),
20598
- event: z30.unknown()
21704
+ var frameSchema = z32.object({
21705
+ v: z32.literal(LIFECYCLE_CODEC_VERSION),
21706
+ seq: z32.number().int().nonnegative(),
21707
+ prev: z32.string().min(1),
21708
+ sum: z32.string().min(1),
21709
+ event: z32.unknown()
20599
21710
  }).strict();
20600
21711
  function canonical2(value) {
20601
21712
  if (Array.isArray(value)) {
@@ -20699,7 +21810,7 @@ import {
20699
21810
  unlinkSync,
20700
21811
  writeSync as writeSync2
20701
21812
  } from "node:fs";
20702
- import { basename as basename3, dirname as dirname7, join as join14, relative as relative3, resolve as resolve9 } from "node:path";
21813
+ import { basename as basename3, dirname as dirname7, join as join14, relative as relative3, resolve as resolve10 } from "node:path";
20703
21814
  var SAFE_RUN_ID = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$/u;
20704
21815
 
20705
21816
  class LifecycleFsError extends Error {
@@ -20723,7 +21834,7 @@ function validateRunID(runID) {
20723
21834
  }
20724
21835
  function resolvedLifecycleRoot(root) {
20725
21836
  try {
20726
- const absolute = resolve9(root);
21837
+ const absolute = resolve10(root);
20727
21838
  mkdirSync2(absolute, { recursive: true });
20728
21839
  const rootStat = lstatSync2(absolute);
20729
21840
  if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
@@ -20766,7 +21877,7 @@ function assertExistingSafeRun(root, runID) {
20766
21877
  throw new LifecycleFsError("unavailable");
20767
21878
  }
20768
21879
  const child = relative3(root, real);
20769
- if (child === "" || child.startsWith("..") || resolve9(root, child) !== real || dirname7(real) !== root) {
21880
+ if (child === "" || child.startsWith("..") || resolve10(root, child) !== real || dirname7(real) !== root) {
20770
21881
  throw new LifecycleFsError("corrupt");
20771
21882
  }
20772
21883
  return real;
@@ -20935,8 +22046,8 @@ function readRun(runDir, runID) {
20935
22046
  if (metadataText === undefined || eventsText === undefined) {
20936
22047
  throw new LifecycleFsError("corrupt");
20937
22048
  }
20938
- const metadata = parseJson(metadataText, parseLifecycleRunMetadata);
20939
- if (metadata.runID !== runID) {
22049
+ const metadata2 = parseJson(metadataText, parseLifecycleRunMetadata);
22050
+ if (metadata2.runID !== runID) {
20940
22051
  throw new LifecycleFsError("corrupt");
20941
22052
  }
20942
22053
  let decoded;
@@ -20951,10 +22062,10 @@ function readRun(runDir, runID) {
20951
22062
  }
20952
22063
  }
20953
22064
  const first = decoded.events[0];
20954
- if (first === undefined || first.type !== "run.started" || first.source !== metadata.source || first.executionAuthority !== metadata.executionAuthority || first.root.operationID !== metadata.root.operationID || first.root.taskID !== metadata.root.taskID) {
22065
+ if (first === undefined || first.type !== "run.started" || first.source !== metadata2.source || first.executionAuthority !== metadata2.executionAuthority || first.root.operationID !== metadata2.root.operationID || first.root.taskID !== metadata2.root.taskID) {
20955
22066
  throw new LifecycleFsError("corrupt");
20956
22067
  }
20957
- return { metadata, decoded };
22068
+ return { metadata: metadata2, decoded };
20958
22069
  }
20959
22070
  function terminalOutcome(events) {
20960
22071
  const last = events.at(-1);
@@ -21006,7 +22117,7 @@ function canDeleteTerminalRun(runDir, runID, now) {
21006
22117
  return now >= lease.expiresAt;
21007
22118
  }
21008
22119
  var createFsLifecycleJournal = (options) => {
21009
- const configuredRoot = resolve10(options.root);
22120
+ const configuredRoot = resolve11(options.root);
21010
22121
  const now = options.now ?? Date.now;
21011
22122
  const mutateRun = options.withMutationLock ?? withRunMutationLock;
21012
22123
  return {
@@ -21032,7 +22143,7 @@ var createFsLifecycleJournal = (options) => {
21032
22143
  mkdirSync3(stagingPath);
21033
22144
  const writerToken = issueWriterToken();
21034
22145
  const eventAt = now();
21035
- const metadata = parseLifecycleRunMetadata({
22146
+ const metadata2 = parseLifecycleRunMetadata({
21036
22147
  ...parsedInput,
21037
22148
  version: 1,
21038
22149
  createdAt: eventAt
@@ -21048,7 +22159,7 @@ var createFsLifecycleJournal = (options) => {
21048
22159
  executionAuthority: parsedInput.executionAuthority,
21049
22160
  root: parsedInput.root
21050
22161
  });
21051
- writeDurable2(join15(stagingPath, METADATA_FILE), `${JSON.stringify(metadata)}
22162
+ writeDurable2(join15(stagingPath, METADATA_FILE), `${JSON.stringify(metadata2)}
21052
22163
  `);
21053
22164
  writeDurable2(join15(stagingPath, EVENTS_FILE), encodeLifecycleJournal([event]));
21054
22165
  writeDurable2(join15(stagingPath, OWNER_FILE2), `${JSON.stringify(lease)}
@@ -21064,7 +22175,7 @@ var createFsLifecycleJournal = (options) => {
21064
22175
  stagingPath = undefined;
21065
22176
  return ok({
21066
22177
  handle: { runID, writerToken },
21067
- metadata,
22178
+ metadata: metadata2,
21068
22179
  event,
21069
22180
  lease,
21070
22181
  head: 1
@@ -21638,8 +22749,8 @@ async function readOpencodeVersion(serverUrl, options = {}) {
21638
22749
  }
21639
22750
  var DEFAULT_OPENCODE_VERSION_BACKOFF_MS = [50, 250];
21640
22751
  function defaultSleep(ms) {
21641
- return new Promise((resolve11) => {
21642
- setTimeout(resolve11, ms);
22752
+ return new Promise((resolve12) => {
22753
+ setTimeout(resolve12, ms);
21643
22754
  });
21644
22755
  }
21645
22756
  function createLazyOpencodeVersionReader(serverUrl, options = {}) {
@@ -21925,6 +23036,9 @@ async function listConfiguredOpencodeInventory(ctx) {
21925
23036
  providerID: model.providerID,
21926
23037
  modelID: model.id
21927
23038
  };
23039
+ if (typeof model.api?.id === "string" && model.api.id.length > 0 && model.api.id === model.api.id.trim() && model.api.id !== model.id) {
23040
+ configuredModel.apiModelID = model.api.id;
23041
+ }
21928
23042
  if (typeof model.capabilities?.toolcall === "boolean") {
21929
23043
  configuredModel.supportsTools = model.capabilities.toolcall;
21930
23044
  }
@@ -21994,12 +23108,20 @@ var server = async (ctx, rawOptions) => {
21994
23108
  const injectMemory = await buildMemoryInjectorFromConfig(config, storage, diagnostics.warn("memory-runtime-warning", { toast: false }));
21995
23109
  const sessionTracker = createSessionTracker();
21996
23110
  const orchestratedModelOwnership = createOrchestratedModelOwnership();
23111
+ const modelResidency = createModelResidencyGuard(config, {
23112
+ fetch: globalThis.fetch,
23113
+ ...ctx.client.config?.providers === undefined ? {} : {
23114
+ resolveApiModelID: cache.resolveApiModelID,
23115
+ modelInventoryReady: ensureAvailability
23116
+ }
23117
+ });
21997
23118
  const resolveAgentRole = async (agentName) => {
21998
23119
  const entry = (await readRoutingRosterEntries(storage)).find((candidate) => candidate.agentName === agentName);
21999
23120
  return entry?.roleID;
22000
23121
  };
22001
23122
  const hooks = createHooks(config, cache.get, injectMemory === undefined ? {
22002
23123
  sink,
23124
+ modelResidency,
22003
23125
  beforeRoute: ensureAvailability,
22004
23126
  capabilityProfiles,
22005
23127
  orchestratedModelOwnership,
@@ -22007,6 +23129,7 @@ var server = async (ctx, rawOptions) => {
22007
23129
  onRoute: (sid, model) => sessionTracker.trackRoutedModel(sid, model)
22008
23130
  } : {
22009
23131
  sink,
23132
+ modelResidency,
22010
23133
  beforeRoute: ensureAvailability,
22011
23134
  capabilityProfiles,
22012
23135
  injectMemory,
@@ -22127,7 +23250,7 @@ var server = async (ctx, rawOptions) => {
22127
23250
  if (runtime === undefined) {
22128
23251
  return;
22129
23252
  }
22130
- return withReportedModelSlots(runtime, reportedSlotsByRuntime.get(runtimeId));
23253
+ return withReportedModelSlots(runtime, reportedSlotsByRuntime.get(runtimeId), localModelAliases(runtime.defaultModel.providerID, cache.get()));
22131
23254
  };
22132
23255
  const runtimeLimiter = createRuntimeConcurrencyLimiter({
22133
23256
  capacityFor: (key2) => {
@@ -22163,8 +23286,8 @@ var server = async (ctx, rawOptions) => {
22163
23286
  lifecycleRecorder,
22164
23287
  abortGraceMs: DEFAULT_ABORT_GRACE_MS,
22165
23288
  statusPollMs: DEFAULT_STATUS_POLL_MS,
22166
- delay: (ms) => new Promise((resolve11) => {
22167
- setTimeout(resolve11, ms);
23289
+ delay: (ms) => new Promise((resolve12) => {
23290
+ setTimeout(resolve12, ms);
22168
23291
  }),
22169
23292
  warn: diagnostics.warn("unknown-role")
22170
23293
  });
@@ -22172,7 +23295,27 @@ var server = async (ctx, rawOptions) => {
22172
23295
  config,
22173
23296
  storage,
22174
23297
  now,
22175
- localRuntimeReachable,
23298
+ localRuntimeReachable: async (runtime, signal) => {
23299
+ if (localRuntimeKind(runtime) === "lemonade") {
23300
+ if (ctx.client.config?.providers !== undefined)
23301
+ await ensureAvailability();
23302
+ signal?.throwIfAborted();
23303
+ const target = modelResidency.localTarget(resolveMemorySemantic(config).extraction.model ?? runtime.defaultModel);
23304
+ const response = await target.fetch(`${target.baseURL}/health`, {
23305
+ method: "GET",
23306
+ ...signal === undefined ? {} : { signal }
23307
+ });
23308
+ return response.ok;
23309
+ }
23310
+ const snapshots = await registry.probe([runtime]);
23311
+ rememberReportedSlots(snapshots);
23312
+ return anyRuntimeReachable(snapshots);
23313
+ },
23314
+ resolveExtractionTarget: async (model) => {
23315
+ if (ctx.client.config?.providers !== undefined)
23316
+ await ensureAvailability();
23317
+ return modelResidency.localTarget(model);
23318
+ },
22176
23319
  fetch: globalThis.fetch,
22177
23320
  warn: diagnostics.warn("memory-extraction-warning", { toast: false })
22178
23321
  });
@@ -22184,23 +23327,43 @@ var server = async (ctx, rawOptions) => {
22184
23327
  options: rawOptions
22185
23328
  })
22186
23329
  });
23330
+ const reload = createReloadController({
23331
+ directory: ctx.directory,
23332
+ client: ctx.client,
23333
+ notify: (code) => diagnostics.emit({
23334
+ code,
23335
+ level: code === "reload-timeout" || code === "reload-status-failed" || code === "reload-dispose-failed" || code === "reload-unconfirmed" ? "warn" : "info"
23336
+ }, { toast: code !== "reload-queued" })
23337
+ });
22187
23338
  return {
22188
23339
  ...hooks,
23340
+ "chat.message": async (input, output) => {
23341
+ reload.userTurn(input.sessionID);
23342
+ await hooks["chat.message"]?.(input, output);
23343
+ },
23344
+ dispose: async () => {
23345
+ reload.dispose();
23346
+ await hooks.dispose?.();
23347
+ },
22189
23348
  tool: {
22190
23349
  openteam: createCommandTool(cliDeps),
22191
23350
  "openteam-console": createConsoleTool(),
22192
23351
  "openteam-graph": graphSurface.definition,
22193
23352
  "openteam-orchestrate": orchestrateTool,
22194
23353
  "openteam-memory": memoryTool,
22195
- "openteam-register-cast": registerCastTool
23354
+ "openteam-register-cast": registerCastTool,
23355
+ "openteam-reload": reload.tool
22196
23356
  },
22197
23357
  "tool.execute.before": async (input) => {
22198
23358
  toolcalls.before(input);
22199
23359
  },
22200
23360
  "tool.execute.after": async (input) => {
22201
23361
  await toolcalls.after(input);
23362
+ if (input.tool === "openteam-reload")
23363
+ reload.toolCompleted(input.sessionID);
22202
23364
  },
22203
23365
  event: async ({ event }) => {
23366
+ reload.event(event);
22204
23367
  if (event.type === "session.created" || event.type === "session.idle") {
22205
23368
  cache.refresh();
22206
23369
  }