@jmanuelcorral/openteam 0.24.1 → 0.26.0

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 (59) hide show
  1. package/README.es.md +42 -10
  2. package/README.md +37 -8
  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.map +1 -1
  6. package/dist/cli.js +1200 -384
  7. package/dist/commands/agents.d.ts.map +1 -1
  8. package/dist/commands/dispatch.d.ts.map +1 -1
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/setup.d.ts +20 -15
  11. package/dist/commands/setup.d.ts.map +1 -1
  12. package/dist/config/schema.d.ts +101 -7
  13. package/dist/config/schema.d.ts.map +1 -1
  14. package/dist/console/assets.d.ts.map +1 -1
  15. package/dist/console/conversation.d.ts +5 -1
  16. package/dist/console/conversation.d.ts.map +1 -1
  17. package/dist/console/credentials.d.ts +4 -0
  18. package/dist/console/credentials.d.ts.map +1 -0
  19. package/dist/console/inspector.d.ts +1 -1
  20. package/dist/console/inspector.d.ts.map +1 -1
  21. package/dist/console/refresh.d.ts +1 -1
  22. package/dist/console/refresh.d.ts.map +1 -1
  23. package/dist/console/render.d.ts.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +657 -128
  26. package/dist/local/lemonadeResidency.d.ts +2 -14
  27. package/dist/local/lemonadeResidency.d.ts.map +1 -1
  28. package/dist/local/llamaSwap.d.ts +5 -0
  29. package/dist/local/llamaSwap.d.ts.map +1 -0
  30. package/dist/local/llamaSwapRequestGuard.d.ts +11 -0
  31. package/dist/local/llamaSwapRequestGuard.d.ts.map +1 -0
  32. package/dist/local/llamaSwapResidency.d.ts +9 -0
  33. package/dist/local/llamaSwapResidency.d.ts.map +1 -0
  34. package/dist/local/modelResidency.d.ts +1 -0
  35. package/dist/local/modelResidency.d.ts.map +1 -1
  36. package/dist/local/registry.d.ts.map +1 -1
  37. package/dist/local/residency.d.ts +16 -0
  38. package/dist/local/residency.d.ts.map +1 -0
  39. package/dist/local/types.d.ts +10 -2
  40. package/dist/local/types.d.ts.map +1 -1
  41. package/dist/messages/consoleDetail.d.ts +8 -5
  42. package/dist/messages/consoleDetail.d.ts.map +1 -1
  43. package/dist/messages/executionSetup.d.ts +22 -3
  44. package/dist/messages/executionSetup.d.ts.map +1 -1
  45. package/dist/messages/memoryRuntime.d.ts +6 -0
  46. package/dist/messages/memoryRuntime.d.ts.map +1 -0
  47. package/dist/messages/memoryTool.d.ts +1 -1
  48. package/dist/messages/memoryTool.d.ts.map +1 -1
  49. package/dist/messages/modelResidency.d.ts +20 -2
  50. package/dist/messages/modelResidency.d.ts.map +1 -1
  51. package/dist/plugin/availability.d.ts.map +1 -1
  52. package/dist/plugin/memoryTool.d.ts.map +1 -1
  53. package/dist/plugin/modelResidency.d.ts.map +1 -1
  54. package/dist/storage/index/memoryRuntime.d.ts.map +1 -1
  55. package/dist/web/conversationRoute.d.ts +2 -2
  56. package/dist/web/conversationRoute.d.ts.map +1 -1
  57. package/dist/web/server.d.ts +1 -1
  58. package/dist/web/server.d.ts.map +1 -1
  59. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -424,6 +424,9 @@ function migrateConfig(raw) {
424
424
  var consoleDetailMessages = {
425
425
  follow: "Follow new activity",
426
426
  metadataOnly: "Lifecycle and telemetry stay metadata-only.",
427
+ logsDescription: "Current recorded conversations load automatically. Lifecycle and telemetry remain metadata-only; credentials are masked.",
428
+ credentialMasked: "[credential masked]",
429
+ offscreen: "Messages load automatically when this conversation is visible.",
427
430
  filter: "Filter sessions",
428
431
  incoming: "New activity available — show updates",
429
432
  refreshError: "Live updates unavailable; retrying.",
@@ -444,7 +447,7 @@ var consoleDetailMessages = {
444
447
  partialMetadata: "Some current session titles are unavailable or outside the bounded batch; fallback labels remain.",
445
448
  rootCurrent: "Current whole orchestrator session; may contain multiple batches. Not a historical or batch-only transcript.",
446
449
  recovered: "Read-only association verified from current opencode history; not an immutable lifecycle event.",
447
- rootUnlinked: "Orchestrator session not recorded. Reveal to try a bounded, read-only relationship search.",
450
+ rootUnlinked: "Orchestrator session not recorded. A bounded, read-only relationship search is attempted when visible.",
448
451
  rootUnavailable: "Orchestrator session unavailable: no unique, verifiable relationship was found in local history.",
449
452
  rootAmbiguous: "Orchestrator session unavailable: conflicting or copied relationships are ambiguous.",
450
453
  searchIncomplete: "Orchestrator association search incomplete or exceeded its bounded budget. No session was guessed.",
@@ -457,9 +460,9 @@ var consoleDetailMessages = {
457
460
  arguments: "Arguments",
458
461
  result: "Result",
459
462
  loading: "Loading local conversation…",
460
- current: "Present-day session, not a timeline reconstruction. May include later messages and tools. Refresh explicitly for updates.",
461
- historical: "Historical cursor: conversation content is hidden to avoid showing future mutable text. Return to live before revealing this operation.",
462
- disabled: "Content is disabled. Set console.sessionHistory.contentEnabled to true in .opencode/openteam.json and restart the Console. Then reveal content here.",
463
+ current: "Present-day whole session, not a timeline reconstruction or per-attempt transcript. Visible messages update automatically while live.",
464
+ historical: "Historical cursor: conversation content is hidden to avoid showing future mutable text. Return to live to read current messages.",
465
+ disabled: "Content is disabled by console.sessionHistory.contentEnabled: false. Lifecycle and telemetry remain metadata-only.",
463
466
  unavailable: "Conversation unavailable. Start an operator-owned local opencode serve against the same workspace and user history, configure console.sessionHistory.endpoint, then retry. No inference was requested.",
464
467
  empty: "No readable user messages, assistant output or tool calls remain in this session.",
465
468
  invalid: "This attempt has no recorded session in the selected run, or its history is incomplete.",
@@ -469,7 +472,7 @@ var consoleDetailMessages = {
469
472
  invalidEndpoint: "Expected a loopback HTTP origin without credentials, path, query or fragment.",
470
473
  readFailed: "Local session history could not be read.",
471
474
  oversized: "Conversation exceeds the local size limit (4 MiB). No content was loaded.",
472
- truncated: "Conversation display is truncated by local safety limits; omitted content is not empty history.",
475
+ truncated: "Conversation display is truncated by local safety limits. The newest bounded message window is shown; older messages or oversized fields may be omitted, not empty history.",
473
476
  truncatedMarker: `
474
477
  [truncated]`,
475
478
  terminalGap: "Live collection was interrupted; events during the gap are unavailable. This is not complete session history.",
@@ -546,7 +549,7 @@ var ConsoleConfigSchema = z2.object({
546
549
  openBrowser: z2.boolean().default(false),
547
550
  remoteStorage: z2.boolean().default(false),
548
551
  sessionHistory: z2.object({
549
- contentEnabled: z2.boolean().default(false),
552
+ contentEnabled: z2.boolean().default(true),
550
553
  endpoint: z2.string().refine(isLocalHistoryEndpoint, consoleDetailMessages.invalidEndpoint).optional()
551
554
  }).strict().optional(),
552
555
  terminal: z2.object({
@@ -684,7 +687,8 @@ var LocalRuntimeKindSchema = z2.enum([
684
687
  "ollama",
685
688
  "lmstudio",
686
689
  "foundry-local",
687
- "lemonade"
690
+ "lemonade",
691
+ "llama-swap"
688
692
  ]);
689
693
  var LocalRuntimeSchema = z2.object({
690
694
  id: z2.string().min(1),
@@ -704,13 +708,26 @@ var LocalRuntimeSchema = z2.object({
704
708
  ctx.addIssue({
705
709
  code: z2.ZodIssueCode.custom,
706
710
  path: ["kind"],
707
- message: `local runtime "${runtime.id}" has no resolvable kind: add "kind" ` + "(one of ollama, lmstudio, foundry-local, lemonade), since the id is " + "not itself a runtime kind."
711
+ message: `local runtime "${runtime.id}" has no resolvable kind: add "kind" ` + "(one of ollama, lmstudio, foundry-local, lemonade, llama-swap), " + "since the id is not itself a runtime kind."
708
712
  });
709
713
  }
714
+ }).transform((runtime) => {
715
+ const resolvedKind = runtime.kind ?? runtime.id;
716
+ if (resolvedKind !== "llama-swap" || runtime.maxConcurrency !== undefined) {
717
+ return runtime;
718
+ }
719
+ return { ...runtime, maxConcurrency: 1 };
710
720
  });
711
721
  function localRuntimeKind(runtime) {
712
722
  return runtime.kind ?? runtime.id;
713
723
  }
724
+ function effectiveMaxConcurrency(runtime) {
725
+ if (runtime.maxConcurrency !== undefined) {
726
+ return runtime.maxConcurrency;
727
+ }
728
+ const kind = runtime.kind ?? (runtime.id !== undefined && LocalRuntimeKindSchema.safeParse(runtime.id).success ? runtime.id : undefined);
729
+ return kind === "llama-swap" ? 1 : DEFAULT_LOCAL_MAX_CONCURRENCY;
730
+ }
714
731
  var MemoryScopeSchema = z2.enum(["project", "user"]);
715
732
  var MemoryExtractionModeSchema = z2.enum([
716
733
  "local",
@@ -891,6 +908,27 @@ import { isAbsolute, normalize, parse, resolve, sep } from "node:path";
891
908
  import { createOpencodeClient } from "@opencode-ai/sdk";
892
909
  import { z as z4 } from "zod";
893
910
 
911
+ // src/console/credentials.ts
912
+ var URL_WITH_AUTHORITY = /https?:\/\/[^\s/?#]+/giu;
913
+ function maskUrlCredentials(value) {
914
+ return value.replace(URL_WITH_AUTHORITY, (candidate) => {
915
+ const schemeEnd = candidate.indexOf("://") + 3;
916
+ const at = candidate.indexOf("@", schemeEnd);
917
+ if (at === -1)
918
+ return candidate;
919
+ const colon = candidate.indexOf(":", schemeEnd);
920
+ if (colon === -1 || colon <= schemeEnd || colon + 1 >= at || colon > at)
921
+ return candidate;
922
+ return `${candidate.slice(0, schemeEnd)}${consoleDetailMessages.credentialMasked}${candidate.slice(at)}`;
923
+ });
924
+ }
925
+ function maskConversationCredentials(value) {
926
+ return maskUrlCredentials(value.replace(/-----BEGIN (?:[A-Z]+ )?PRIVATE KEY-----[\s\S]*?(?:-----END (?:[A-Z]+ )?PRIVATE KEY-----|$)/gu, consoleDetailMessages.credentialMasked).replace(/\b(?:Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/giu, consoleDetailMessages.credentialMasked).replace(/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{16,}|AKIA[A-Z0-9]{16}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)\b/gu, consoleDetailMessages.credentialMasked).replace(/(\b(?:password|passwd|secret|api[_-]?key|access[_-]?token|refresh[_-]?token|token|authorization|cookie|set-cookie)\b["']?\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,;}\]]+)/giu, (_match, prefix) => prefix + consoleDetailMessages.credentialMasked));
927
+ }
928
+ function isCredentialKey(key) {
929
+ return /^(?:password|passwd|secret|api[_-]?key|access[_-]?token|refresh[_-]?token|token|authorization|cookie|set-cookie|private[_-]?key)$/iu.test(key);
930
+ }
931
+
894
932
  // src/console/historyBoundary.ts
895
933
  function only(keys) {
896
934
  return (value) => {
@@ -1148,7 +1186,7 @@ class ConversationTooLargeError extends Error {
1148
1186
  }
1149
1187
  }
1150
1188
  var Info = z4.preprocess(only(["id", "sessionID", "role", "time"]), z4.object({
1151
- id: z4.string(),
1189
+ id: z4.string().min(1).max(256),
1152
1190
  sessionID: z4.string(),
1153
1191
  role: z4.enum(["user", "assistant"]),
1154
1192
  time: z4.preprocess(only(["created", "completed"]), z4.object({
@@ -1168,7 +1206,7 @@ var Text = PartIdentity.extend({
1168
1206
  });
1169
1207
  var Tool = PartIdentity.extend({
1170
1208
  type: z4.literal("tool"),
1171
- callID: z4.string(),
1209
+ callID: z4.string().min(1).max(256),
1172
1210
  tool: z4.string(),
1173
1211
  state: z4.preprocess(only(["status", "input", "output", "error", "time"]), z4.object({
1174
1212
  status: z4.enum(["pending", "running", "completed", "error"]),
@@ -1191,6 +1229,7 @@ function projectConversation(raw, sessionID) {
1191
1229
  let remaining = limits.totalChars;
1192
1230
  let toolsRemaining = limits.tools;
1193
1231
  function text(value) {
1232
+ value = maskConversationCredentials(value);
1194
1233
  const length = Math.min(value.length, limits.fieldChars, remaining);
1195
1234
  remaining -= length;
1196
1235
  if (length === value.length)
@@ -1222,7 +1261,7 @@ function projectConversation(raw, sessionID) {
1222
1261
  truncated = true;
1223
1262
  break;
1224
1263
  }
1225
- const child = bounded(value[key], depth + 1);
1264
+ const child = isCredentialKey(key) ? consoleDetailMessages.credentialMasked : bounded(value[key], depth + 1);
1226
1265
  if (Array.isArray(value))
1227
1266
  array.push(child);
1228
1267
  else
@@ -1233,7 +1272,7 @@ function projectConversation(raw, sessionID) {
1233
1272
  return text(JSON.stringify(bounded(input, 0), null, 2));
1234
1273
  }
1235
1274
  const messages = [];
1236
- for (const rawRow of raw.slice(0, limits.messages)) {
1275
+ for (const rawRow of raw.slice(-limits.messages).reverse()) {
1237
1276
  if (remaining <= 0) {
1238
1277
  truncated = true;
1239
1278
  break;
@@ -1242,12 +1281,13 @@ function projectConversation(raw, sessionID) {
1242
1281
  if (row.info.sessionID !== sessionID)
1243
1282
  throw new Error(consoleDetailMessages.readFailed);
1244
1283
  const message = {
1245
- id: text(row.info.id),
1284
+ id: row.info.id,
1246
1285
  role: row.info.role,
1247
1286
  createdAt: row.info.time.created,
1248
1287
  ...row.info.time.completed !== undefined ? { completedAt: row.info.time.completed } : {},
1249
1288
  text: [],
1250
- tools: []
1289
+ tools: [],
1290
+ order: []
1251
1291
  };
1252
1292
  if (row.parts.length > limits.parts)
1253
1293
  truncated = true;
@@ -1271,16 +1311,19 @@ function projectConversation(raw, sessionID) {
1271
1311
  if (part.sessionID !== sessionID || part.messageID !== row.info.id)
1272
1312
  throw new Error(consoleDetailMessages.readFailed);
1273
1313
  if (part.type === "text") {
1274
- if (!part.ignored)
1314
+ if (!part.ignored) {
1315
+ message.order?.push({ type: "text", index: message.text.length });
1275
1316
  message.text.push(text(part.text));
1317
+ }
1276
1318
  } else {
1277
1319
  if (toolsRemaining-- <= 0) {
1278
1320
  truncated = true;
1279
1321
  continue;
1280
1322
  }
1281
1323
  const state = part.state;
1324
+ message.order?.push({ type: "tool", index: message.tools.length });
1282
1325
  message.tools.push({
1283
- id: text(part.callID),
1326
+ id: part.callID,
1284
1327
  name: text(part.tool),
1285
1328
  status: state.status,
1286
1329
  arguments: argumentsText(state.input),
@@ -1385,7 +1428,7 @@ function createConversationReader(deps) {
1385
1428
  if (workspaceKey(session.directory) !== workspaceKey(directory))
1386
1429
  throw new Error(consoleDetailMessages.readFailed);
1387
1430
  const time = z4.preprocess(only(["created"]), z4.object({ created: z4.number().finite().min(0).max(8640000000000000) }).strict()).safeParse(session.time);
1388
- const title = boundedTitle(session.title);
1431
+ const title = boundedTitle(typeof session.title === "string" ? maskConversationCredentials(session.title) : session.title);
1389
1432
  return {
1390
1433
  id: session.id,
1391
1434
  directory: session.directory,
@@ -1435,10 +1478,13 @@ function createConversationReader(deps) {
1435
1478
  labels.delete(oldest);
1436
1479
  return session;
1437
1480
  },
1438
- messages: async (id) => {
1481
+ messages: async (id, limit) => {
1439
1482
  if (!verified.has(id))
1440
1483
  throw new Error(consoleDetailMessages.readFailed);
1441
- const result = await client.session.messages({ path: { id } });
1484
+ const result = await client.session.messages({
1485
+ path: { id },
1486
+ ...limit === undefined ? {} : { query: { limit } }
1487
+ });
1442
1488
  if (!result.response.ok || result.error !== undefined)
1443
1489
  throw new Error(consoleDetailMessages.readFailed);
1444
1490
  return result.data;
@@ -1475,14 +1521,37 @@ function createConversationReader(deps) {
1475
1521
  const read = async (endpoint, sessionID, signal) => withHistory(endpoint, signal, async (port) => {
1476
1522
  const session = await port.get(sessionID);
1477
1523
  return {
1478
- ...projectConversation(await port.messages(sessionID), sessionID),
1524
+ ...projectConversation(await port.messages(sessionID, CONVERSATION_LIMITS.messages + 1), sessionID),
1479
1525
  session: session.label
1480
1526
  };
1481
1527
  });
1528
+ const roots = new Map;
1482
1529
  read.root = (endpoint, projection, signal) => withHistory(endpoint, signal, async (port) => {
1483
- const recovered = await recoverRootSession(port, projection);
1530
+ const key = JSON.stringify([
1531
+ endpoint,
1532
+ projection.runID,
1533
+ projection.rootOperationID,
1534
+ projection.operations.flatMap((operation) => operation.attempts.map((attempt) => [
1535
+ operation.operationID,
1536
+ attempt.attemptID,
1537
+ attempt.sessionID
1538
+ ]))
1539
+ ]);
1540
+ const cached = roots.get(key);
1541
+ const recovered = cached ? {
1542
+ session: await port.get(cached),
1543
+ association: "sdk-parent"
1544
+ } : await recoverRootSession(port, projection);
1545
+ roots.delete(key);
1546
+ if (recovered.association === "sdk-parent")
1547
+ roots.set(key, recovered.session.id);
1548
+ if (roots.size > 32) {
1549
+ const oldest = roots.keys().next().value;
1550
+ if (oldest !== undefined)
1551
+ roots.delete(oldest);
1552
+ }
1484
1553
  return {
1485
- ...projectConversation(await port.messages(recovered.session.id), recovered.session.id),
1554
+ ...projectConversation(await port.messages(recovered.session.id, CONVERSATION_LIMITS.messages + 1), recovered.session.id),
1486
1555
  session: recovered.session.label,
1487
1556
  association: recovered.association
1488
1557
  };
@@ -3356,6 +3425,23 @@ var executionSetupMessages = {
3356
3425
  `),
3357
3426
  localOnlyTitle: "Local execution",
3358
3427
  localRequiredError: "local execution requires an enabled local runtime",
3428
+ llamaSwap: {
3429
+ label: "llama-swap",
3430
+ noLoadedModels: "llama-swap has no canonical models verified as ready with no active routing profile. Keep the intended model ready through the operator's existing runtime controls, then rerun setup; manual IDs cannot bypass admission.",
3431
+ modelNotLoaded: (model) => `llama-swap model "${model}" is not positively verified as canonical and ready without an active routing profile. No substitute was selected.`,
3432
+ probeRequired: "llama-swap setup requires a fresh read-only residency probe before writing configuration. Manual model IDs cannot bypass this requirement.",
3433
+ probeFailed: "llama-swap readiness could not be verified. Check the configured /v1/models catalog and origin /running and /api/profiles endpoints; authenticated runtimes are not supported.",
3434
+ endpointMismatch: "llama-swap endpoint/provider mapping differs from the selected runtime. Reconcile the configured origin or /v1 URL and provider; no configuration was written.",
3435
+ modelIdentityUnknown: (model) => `llama-swap model "${model}" has no usable registered physical identity. Fix its model declaration; setup will not guess an alias.`,
3436
+ declaredLimits: "llama-swap context and output limits are operator-declared metadata, not measured per-slot capacities. Missing output metadata uses openteam's client policy default, not a guessed model cap. Unknown context remains 0 and disables automatic compaction. Tool support remains unknown without evidence.",
3437
+ declaredLimitsTitle: "Declared llama-swap limits",
3438
+ outputReduction: (model, previous, ceiling) => `${model}: reducing limit.output from ${previous} to ${ceiling} to respect the declared runtime ceiling. Smaller operator limits are preserved.`,
3439
+ outputReductionTitle: "Declared output ceiling",
3440
+ outputChanged: (model, ceiling) => `${model}: fresh runtime metadata declares an output ceiling of ${ceiling} below the prepared configuration. Rerun setup to review the updated limit; no configuration was written.`
3441
+ },
3442
+ residencyV2Title: "Resident runtime transport compatibility",
3443
+ residencyV2Restriction: (providers) => `Project policies disable opencode's separate native/core V2 transport for resident-only providers: ${providers.join(", ")}. Guarded V1 provider transport remains supported; this does not ban the public SDK v2 client library. Higher-precedence policies can override project restrictions. Readiness preflight is not an atomic no-autoload guarantee.`,
3444
+ retainedResidencyProviderUnprotected: (providerID) => `Cannot safely retain resident-only provider "${providerID}": the effective configuration must retain its matching runtime, disabled when deselected. Reconcile the runtime/provider configuration; no configuration was written.`,
3359
3445
  lemonadeDetectedHint: (loaded, advertised) => `detected - ${loaded} confirmed loaded of ${advertised} advertised text model(s)`,
3360
3446
  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.",
3361
3447
  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.`,
@@ -3370,11 +3456,11 @@ var executionSetupMessages = {
3370
3456
  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.",
3371
3457
  lemonadeV2Title: "Lemonade transport compatibility",
3372
3458
  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.`,
3373
- lemonadeInventory: (instanceID, providerID, models) => {
3459
+ lemonadeInventory: (instanceID, providerID, models, runtime = "Lemonade") => {
3374
3460
  const inventory = models.map((model) => `${model.modelID} (${model.loaded === true ? "loaded" : model.loaded === false ? "inactive" : "residency unknown"})`).join(", ");
3375
- return `Lemonade inventory ${instanceID} (provider ${providerID}): ${inventory || "no advertised text models"}. Only confirmed loaded text models count as ready.`;
3461
+ return `${runtime} inventory ${instanceID} (provider ${providerID}): ${inventory || "no advertised text models"}. Only confirmed loaded text models count as ready.`;
3376
3462
  },
3377
- 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.`,
3463
+ lemonadeAgentResidency: (agent, model, state, runtime = "Lemonade") => `${runtime} 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.`,
3378
3464
  mixedPrimaryQuestion: "Primary coordinator model",
3379
3465
  mixedPrimaryLocal: (model) => `Use local thinking model ${model}`,
3380
3466
  mixedPrimaryFrontier: "Choose a frontier model",
@@ -3478,6 +3564,8 @@ var executionPolicyMessages = {
3478
3564
  primaryLocalMismatchRemedy: (sourceFile) => ` remedy: re-run \`openteam setup\` to regenerate ${sourceFile} and opencode.json from the same primary policy, then re-run \`openteam doctor\`.`
3479
3565
  };
3480
3566
  var doctorMessages = {
3567
+ defaultRuntimeSlots: (slots) => ` · ${slots} slot(s) (default)`,
3568
+ llamaSwapConcurrency: " llama-swap does not report per-model slots; openteam uses the configured runtime-wide concurrency limit. No per-model capacity was inferred.",
3481
3569
  localModelLimits: {
3482
3570
  section: " local model limits:",
3483
3571
  healthy: " ✓ enabled local provider models declare valid output and context limits in opencode.json.",
@@ -3494,7 +3582,7 @@ var doctorMessages = {
3494
3582
  invalidInput: "invalid limit.input (expected a positive integer token count)",
3495
3583
  outputExceedsContext: "limit.output is greater than or equal to limit.context, so the configured context leaves no usable input window",
3496
3584
  outputExceedsInput: "limit.input is less than or equal to the reserved output budget, so compaction would have no usable input threshold",
3497
- outputRemedy: " remedy: re-run `openteam setup` to write openteam's 8 192-token local output default, then re-run `openteam doctor`.",
3585
+ outputRemedy: " remedy: re-run `openteam setup` to write openteam's local output policy (at most 8 192 tokens, narrowed by declared runtime ceilings and smaller context/input budgets), then re-run `openteam doctor`.",
3498
3586
  unconfiguredLimitRemedy: " remedy: the optional limit block may remain absent; to configure explicit limits, manually add a complete limit object in opencode.json with positive output and non-negative context (0 means unknown), because setup fills an absent limit block only for models the runtime currently discovers. Then re-run `openteam doctor`.",
3499
3587
  detectedContextRemedy: " remedy: re-run `openteam setup` while the runtime is reachable to copy the detected usable context budget, then re-run `openteam doctor`.",
3500
3588
  unknownContextRemedy: " remedy: re-run `openteam setup` to write the required context:0 sentinel; automatic compaction remains disabled until a positive context is configured.",
@@ -4482,234 +4570,286 @@ async function handleLifecycleHistoryRoute(request, response, url, deps) {
4482
4570
  // src/console/inspector.ts
4483
4571
  var CONVERSATION_CLIENT_JS = `
4484
4572
  function wireConversationInspector(){
4573
+ if(document.__conversationsWired){return}document.__conversationsWired=true;
4574
+ var msg=${JSON.stringify(consoleDetailMessages)},limits=${JSON.stringify(CONVERSATION_LIMITS)};
4575
+ var records=new Map(),recoveries=new Map(),stopped=false,timer=null,scheduled=null,active=0,serial=0,retained=null;
4485
4576
  var mount=document.getElementById('lifecycle-timeline-mount');
4486
- if(!mount){return}
4487
- var msg=${JSON.stringify(consoleDetailMessages)};
4488
- var limits=${JSON.stringify(CONVERSATION_LIMITS)};
4489
- var panel=document.createElement('section');panel.className='lc-conversation';panel.hidden=true;panel.id='lc-attempt-conversation';
4490
- panel.setAttribute('data-conversation-inspector','');
4491
- panel.setAttribute('aria-labelledby','lc-conversation-heading');
4492
- mount.appendChild(panel);
4493
- var selection=null,request=null,epoch=0,retained=null;
4494
- function element(tag,text){var e=document.createElement(tag);if(text!==undefined){e.textContent=text}return e}
4495
- function selectedButton(){
4496
- if(!selection||mount.getAttribute('data-conversation-transition')==='1'||
4497
- mount.getAttribute('data-lifecycle-run')!==selection.run||
4498
- (mount.getAttribute('data-conversation-cursor')||null)!==selection.cursor){return null}
4499
- return Array.from(mount.querySelectorAll('[data-inspect-attempt],[data-inspect-root]')).find(function(button){
4500
- var attempt=button.closest('[data-lifecycle-attempt-id]');
4501
- var operation=button.closest('[data-lifecycle-operation-id]');
4502
- var session=button.closest(selection.target==='root'?'[data-root-session-id]':'[data-lifecycle-session-id]');
4503
- return !button.disabled&&operation&&session&&
4504
- (selection.target==='root'?button.hasAttribute('data-inspect-root'):attempt&&attempt.getAttribute('data-lifecycle-attempt-id')===selection.attempt&&
4505
- button.getAttribute('data-inspect-attempt')===selection.attempt)&&
4506
- operation.getAttribute('data-lifecycle-operation-id')===selection.operation&&
4507
- button.getAttribute('data-inspect-operation')===selection.operation&&
4508
- session.getAttribute(selection.target==='root'?'data-root-session-id':'data-lifecycle-session-id')===selection.session;
4509
- })||null;
4510
- }
4511
- function expanded(button,open){
4512
- if(button.getAttribute('aria-expanded')!==String(open)){button.setAttribute('aria-expanded',String(open))}
4513
- var label=open?msg.close:msg.inspectConversation;
4514
- if(button.textContent!==label){button.textContent=label}
4515
- }
4516
- function cancel(){
4517
- epoch++;if(request){request.abort();request=null}
4518
- }
4519
- function close(focus){
4520
- var button=selectedButton();
4521
- cancel();selection=null;retained=null;
4522
- mount.querySelectorAll('[data-inspect-attempt],[data-inspect-root]').forEach(function(control){expanded(control,false)});
4523
- panel.textContent='';panel.hidden=true;
4524
- ['data-conversation-kind','data-conversation-run','data-conversation-operation','data-conversation-attempt','data-conversation-session','data-conversation-target','aria-busy'].forEach(function(name){panel.removeAttribute(name)});
4525
- mount.appendChild(panel);
4526
- if(focus===true&&button){button.focus({preventScroll:true})}
4527
- }
4528
- // The renderer replaces metadata, not retained content. Restore only after
4529
- // the real run/cursor commits and the same attempt/session is still visible.
4530
- function retain(){
4531
- if(!selection||panel.hidden||!selectedButton()){return}
4532
- var scrolls=[],ancestor=panel.parentElement;
4533
- while(ancestor){scrolls.push([ancestor,ancestor.scrollTop,ancestor.scrollLeft]);ancestor=ancestor.parentElement}
4534
- panel.querySelectorAll('pre,[data-conversation-tool-list]').forEach(function(node){scrolls.push([node,node.scrollTop,node.scrollLeft])});
4535
- retained={scrolls:scrolls,top:panel.getBoundingClientRect().top,focus:panel.contains(document.activeElement)?document.activeElement:null};
4536
- }
4537
- function reanchor(){
4538
- if(!selection){return}
4539
- var button=selectedButton();
4540
- if(!button){close();return}
4541
- var attempt=selection.target==='root'?button.closest('.lct-operation-card'):button.closest('[data-lifecycle-attempt-id]');
4542
- if(panel.parentElement!==attempt){attempt.appendChild(panel)}
4543
- expanded(button,true);
4544
- if(retained){
4545
- retained.scrolls.forEach(function(item){if(item[0].isConnected){item[0].scrollTop=item[1];item[0].scrollLeft=item[2]}});
4546
- var tree=mount.querySelector('[data-lifecycle-tree-mount]');
4547
- if(tree){tree.scrollTop+=panel.getBoundingClientRect().top-retained.top}
4548
- if(retained.focus){retained.focus.focus({preventScroll:true})}
4549
- retained=null;
4577
+ function el(tag,text){var node=document.createElement(tag);if(text!==undefined){node.textContent=text}return node}
4578
+ function setText(node,text){if(node.textContent!==text){node.textContent=text}}
4579
+ function visible(node){
4580
+ if(document.hidden||!node.isConnected){return false}
4581
+ var parent=node;
4582
+ while(parent){if(parent.hidden){return false}parent=parent.parentElement}
4583
+ var rect=node.getBoundingClientRect();
4584
+ return rect.bottom>=0&&rect.top<(window.innerHeight||1000);
4585
+ }
4586
+ function cancel(record){
4587
+ if(record.request){record.request.abort();record.request=null}
4588
+ if(record.finish){record.finish();record.finish=null}
4589
+ record.version++;
4590
+ }
4591
+ function discard(record){
4592
+ cancel(record);record.panel.remove();record.body.textContent='';record.source.textContent='';record.rows.clear();records.delete(record.key);
4593
+ }
4594
+ function clear(scope){
4595
+ Array.from(records.values()).forEach(function(record){
4596
+ if(scope==='native'&&record.selection.target==='session'){return}
4597
+ discard(record);
4598
+ });
4599
+ if(scope!=='native'){retained=null}
4600
+ }
4601
+ function recovery(record){
4602
+ var saved=recoveries.get(record.key);
4603
+ if(saved&&saved.until<=Date.now()){recoveries.delete(record.key);return null}
4604
+ return saved||null;
4605
+ }
4606
+ function rootRecovery(record,data){
4607
+ return record.selection.target==='root'&&!record.selection.session&&data.kind==='unavailable'&&
4608
+ [msg.rootUnavailable,msg.rootAmbiguous,msg.searchIncomplete].includes(data.message)?
4609
+ {message:data.message,until:Date.now()+30000}:null;
4610
+ }
4611
+ function rememberRecovery(record,saved){
4612
+ if(!saved){recoveries.delete(record.key);return}
4613
+ recoveries.delete(record.key);recoveries.set(record.key,saved);
4614
+ if(recoveries.size>128){recoveries.delete(recoveries.keys().next().value)}
4615
+ }
4616
+ function desired(){
4617
+ var result=[];
4618
+ if(stopped||document.hidden){return result}
4619
+ var explicitHistorical=!!((mount&&mount.getAttribute('data-conversation-cursor'))||new URLSearchParams(window.location.search).has('cursor'));
4620
+ var transitioning=mount&&mount.getAttribute('data-conversation-transition')==='1';
4621
+ if(mount){
4622
+ var notice=mount.querySelector('[data-conversation-historical]');
4623
+ if(explicitHistorical){
4624
+ if(!notice){notice=el('p',msg.historical);notice.setAttribute('data-conversation-historical','');mount.appendChild(notice)}
4625
+ }else if(notice){notice.remove()}
4626
+ }
4627
+ if(mount&&!explicitHistorical&&!transitioning){
4628
+ mount.querySelectorAll('[data-inspect-root],[data-inspect-attempt]').forEach(function(button){
4629
+ if(button.disabled){return}
4630
+ var root=button.hasAttribute('data-inspect-root');
4631
+ var session=button.closest(root?'[data-root-session-id]':'[data-lifecycle-session-id]');
4632
+ var host=root?button.closest('.lct-operation-card'):button.closest('[data-lifecycle-attempt-id]');
4633
+ if(!session||!host){return}
4634
+ var selection={target:root?'root':'attempt',run:mount.getAttribute('data-lifecycle-run'),
4635
+ operation:button.getAttribute('data-inspect-operation'),attempt:root?null:button.getAttribute('data-inspect-attempt'),
4636
+ session:session.getAttribute(root?'data-root-session-id':'data-lifecycle-session-id')};
4637
+ if(!selection.run||!selection.operation){return}
4638
+ result.push({key:JSON.stringify(selection),selection:selection,host:host,button:button});
4639
+ });
4550
4640
  }
4641
+ document.querySelectorAll('[data-session-conversation]').forEach(function(host){
4642
+ var selection={target:'session',session:host.getAttribute('data-session-conversation')};
4643
+ if(explicitHistorical){
4644
+ if(visible(host)){setText(host,msg.historical)}
4645
+ return;
4646
+ }
4647
+ result.push({key:JSON.stringify(selection),selection:selection,host:host});
4648
+ });
4649
+ return result;
4551
4650
  }
4552
- function reset(){
4553
- panel.textContent='';
4554
- var header=element('header');header.className='lc-conversation-head';
4555
- var heading=element('h3',msg.title);heading.id='lc-conversation-heading';heading.tabIndex=-1;header.appendChild(heading);
4556
- var closeBtn=element('button',msg.close);closeBtn.type='button';
4557
- closeBtn.setAttribute('data-conversation-close','');
4558
- closeBtn.addEventListener('click',function(){close(true)});header.appendChild(closeBtn);panel.appendChild(header);
4559
- }
4560
- function showHeading(){
4561
- var heading=panel.querySelector('h3');
4562
- heading.focus({preventScroll:true});
4563
- heading.scrollIntoView({block:'start',inline:'nearest'});
4564
- }
4565
- function load(reveal){
4566
- if(!selectedButton()){return}
4567
- cancel();var current=epoch,selected=selection;
4568
- reset();
4569
- panel.setAttribute('data-conversation-kind','loading');
4570
- panel.setAttribute('aria-busy','true');
4571
- var status=element('p',msg.loading);status.setAttribute('role','status');panel.appendChild(status);
4572
- var opening=null;
4573
- if(reveal===true){
4574
- showHeading();
4575
- opening={focus:document.activeElement,scrolls:[mount.querySelector('[data-lifecycle-tree-mount]'),mount.closest('.scroll')].filter(Boolean).map(function(node){return [node,node.scrollTop,node.scrollLeft]})};
4576
- }
4577
- var controller=new AbortController();request=controller;
4578
- var query=new URLSearchParams({run:selected.run,operation:selected.operation,diagnostic:'1'});
4579
- if(selected.target==='root'){query.set('target','root')}else{query.set('attempt',selected.attempt)}
4580
- query.set('token',document.documentElement.getAttribute('data-token')||'');
4581
- if(selected.cursor!==null){query.set('cursor',selected.cursor)}
4582
- var timeout=setTimeout(function(){controller.abort()},15000);
4583
- fetch('/api/graph/conversation?'+query.toString(),{signal:controller.signal,cache:'no-store'})
4584
- .then(function(r){return r.json()}).then(function(data){
4585
- if(epoch!==current||selection!==selected||!selectedButton()){return}
4586
- var bringIntoView=opening&&document.activeElement===opening.focus&&opening.scrolls.every(function(item){return item[0].scrollTop===item[1]&&item[0].scrollLeft===item[2]});
4587
- if((data.kind==='conversation'||data.kind==='empty')&&(data.runID!==selected.run||data.operationID!==selected.operation||
4588
- (selected.target==='root'?(data.target!=='root'||data.attemptID!==undefined||!data.sessionID||(!selected.session&&!['sdk-parent','tool-result'].includes(data.association))):(data.target!=='attempt'||data.attemptID!==selected.attempt))||
4589
- (selected.session&&data.sessionID!==selected.session))){
4590
- data={kind:'unavailable',message:msg.unavailable};
4591
- }
4592
- status.textContent=data.message||msg.unavailable;
4593
- panel.setAttribute('data-conversation-kind',data.kind||'unavailable');
4594
- if(data.kind==='conversation'||data.kind==='empty'){
4595
- panel.setAttribute('data-conversation-session',data.sessionID);
4596
- var source=element('p');source.appendChild(element('strong',data.session&&data.session.title|| (selected.target==='root'?msg.orchestrator:msg.session)));
4597
- var created=consoleLabels.utcTime(data.session&&data.session.createdAt);
4598
- source.appendChild(element('small',' · '+(created===msg.timeUnknown?msg.timeUnknown:msg.sessionCreated+': '+created)));
4599
- source.appendChild(element('code',' · '+data.sessionID));panel.appendChild(source);
4600
- if(data.session&&data.session.id===data.sessionID){document.dispatchEvent(new CustomEvent('console-session-metadata',{detail:{session:data.session,runID:data.runID,target:selected.target}}))}
4601
- }
4602
- if(data.kind==='conversation'){
4603
- renderMessages(data.messages||[]);
4604
- }else if(data.kind==='empty'){
4605
- renderMessages([]);
4651
+ function create(item){
4652
+ var panel=el('section');panel.className='lc-conversation';panel.id='lc-conversation-'+(++serial);
4653
+ panel.setAttribute('data-conversation-inspector','');
4654
+ panel.setAttribute('data-conversation-target',item.selection.target);
4655
+ panel.setAttribute('data-conversation-session',item.selection.session||'');
4656
+ if(item.selection.run){panel.setAttribute('data-conversation-run',item.selection.run)}
4657
+ if(item.selection.attempt){panel.setAttribute('data-conversation-attempt',item.selection.attempt)}
4658
+ var heading=el('h3',msg.title);panel.appendChild(heading);
4659
+ var status=el('p',msg.loading);status.setAttribute('role','status');panel.appendChild(status);
4660
+ var source=el('p');panel.appendChild(source);
4661
+ var body=el('div');body.setAttribute('data-conversation-messages','');panel.appendChild(body);
4662
+ var refresh=el('button',msg.refresh);refresh.type='button';refresh.setAttribute('data-conversation-refresh','');panel.appendChild(refresh);
4663
+ var record={key:item.key,selection:item.selection,host:item.host,panel:panel,status:status,source:source,body:body,rows:new Map(),
4664
+ request:null,finish:null,version:0,due:0,button:item.button};
4665
+ refresh.addEventListener('click',function(){record.due=0;recoveries.delete(record.key);sync()});
4666
+ item.host.appendChild(panel);records.set(item.key,record);
4667
+ return record;
4668
+ }
4669
+ function matches(record,data){
4670
+ var s=record.selection;
4671
+ return data.sessionID&&(s.session?data.sessionID===s.session:['sdk-parent','tool-result'].includes(data.association))&&
4672
+ (s.target==='session'?data.target==='session'&&!data.runID:
4673
+ data.runID===s.run&&data.operationID===s.operation&&data.target===s.target&&
4674
+ (s.target==='root'?data.attemptID===undefined:data.attemptID===s.attempt));
4675
+ }
4676
+ function render(record,messages){
4677
+ var remaining=limits.totalChars,tools=0,truncated=false,used=new Set();
4678
+ function text(value){
4679
+ value=String(value===undefined?'':value);
4680
+ var length=Math.min(value.length,limits.fieldChars+msg.truncatedMarker.length,remaining);
4681
+ remaining-=length;if(length<value.length){truncated=true}return value.slice(0,length);
4682
+ }
4683
+ if(messages.length>limits.messages){truncated=true}
4684
+ messages.slice(0,limits.messages).forEach(function(message){
4685
+ if(!message||!['user','assistant'].includes(message.role)||typeof message.id!=='string'||used.has(message.id)){return}
4686
+ used.add(message.id);
4687
+ var row=record.rows.get(message.id);
4688
+ if(!row){
4689
+ var article=el('article');article.setAttribute('data-conversation-role',message.role);
4690
+ article.appendChild(el('h4',message.role==='user'?msg.prompt:msg.assistant));
4691
+ var time=el('time');article.appendChild(time);
4692
+ row={node:article,time:time,texts:[],tools:new Map()};record.rows.set(message.id,row);record.body.appendChild(article);
4693
+ }
4694
+ var selection=window.getSelection&&window.getSelection();
4695
+ if(selection&&!selection.isCollapsed&&row.node.contains(selection.anchorNode)){return}
4696
+ setText(row.time,consoleLabels.utcTime(message.createdAt));
4697
+ var parts=(message.text||[]).slice(0,limits.parts);
4698
+ parts.forEach(function(value,index){
4699
+ if(!row.texts[index]){row.texts[index]=el('pre');row.node.insertBefore(row.texts[index],row.node.querySelector('details'))}
4700
+ setText(row.texts[index],text(value));
4701
+ });
4702
+ while(row.texts.length>parts.length){row.texts.pop().remove()}
4703
+ var seenTools=new Set();
4704
+ (message.tools||[]).slice(0,limits.parts).forEach(function(tool){
4705
+ if(tools++>=limits.tools||remaining<=0){truncated=true;return}
4706
+ seenTools.add(tool.id);
4707
+ var call=row.tools.get(tool.id);
4708
+ if(!call){
4709
+ var detail=el('details');detail.setAttribute('data-tool-call',tool.id);detail.open=true;
4710
+ var summary=el('summary');detail.appendChild(summary);
4711
+ detail.appendChild(el('h5',msg.arguments));var args=el('pre');detail.appendChild(args);
4712
+ detail.appendChild(el('h5',msg.result));var result=el('pre');detail.appendChild(result);
4713
+ call={node:detail,summary:summary,args:args,result:result};row.tools.set(tool.id,call);row.node.appendChild(detail);
4606
4714
  }
4607
- // The short loading card may not have enough scroll range. Complete
4608
- // the explicit reveal after layout grows, unless the reader moved on.
4609
- if(bringIntoView){panel.querySelector('h3').scrollIntoView({block:'start',inline:'nearest'})}
4610
- }).catch(function(){
4611
- if(epoch===current){status.textContent=msg.unavailable;panel.setAttribute('data-conversation-kind','unavailable')}
4612
- }).finally(function(){
4613
- clearTimeout(timeout);
4614
- if(epoch!==current){return}
4615
- request=null;
4616
- panel.setAttribute('aria-busy','false');
4617
- var refresh=element('button',msg.refresh);refresh.type='button';refresh.setAttribute('data-conversation-refresh','');
4618
- refresh.addEventListener('click',load);panel.appendChild(refresh);
4715
+ setText(call.summary,text(tool.name)+' · '+text(tool.status));
4716
+ setText(call.args,text(tool.arguments));setText(call.result,text(tool.result===undefined?msg.pending:tool.result));
4619
4717
  });
4620
- }
4621
- function renderMessages(messages){
4622
- var truncated=messages.length>limits.messages,remaining=limits.totalChars,toolCount=0;
4623
- messages=messages.slice(0,limits.messages);
4624
- var tools=element('details');tools.setAttribute('data-conversation-tools','');tools.open=true;
4625
- var count=messages.reduce(function(total,m){return total+(m.tools||[]).length},0);
4626
- tools.appendChild(element('summary',msg.tools.replace('{count}',String(count))));
4627
- var calls=element('div');calls.setAttribute('data-conversation-tool-list','');tools.appendChild(calls);
4628
- panel.appendChild(tools);
4629
- function text(value){
4630
- value=String(value);
4631
- var size=Math.min(value.length,limits.fieldChars+msg.truncatedMarker.length,remaining);
4632
- remaining-=size;
4633
- if(size<value.length){truncated=true}
4634
- return value.slice(0,size);
4635
- }
4636
- ['user','assistant'].forEach(function(role){
4637
- var section=element('section');section.setAttribute('data-conversation-role',role);
4638
- section.appendChild(element('h4',role==='user'?msg.prompt:msg.assistant));
4639
- var hasText=false;
4640
- messages.filter(function(m){return m.role===role}).forEach(function(m){
4641
- var item=element('article');item.appendChild(element('time',consoleLabels.utcTime(m.createdAt)));
4642
- if((m.text||[]).length>limits.parts){truncated=true}
4643
- (m.text||[]).slice(0,limits.parts).forEach(function(value){
4644
- if(!value){return}
4645
- hasText=true;
4646
- if(remaining>0){item.appendChild(element('pre',text(value)))}else{truncated=true}
4718
+ row.tools.forEach(function(call,id){if(!seenTools.has(id)){call.node.remove();row.tools.delete(id)}});
4719
+ if(Array.isArray(message.order)){
4720
+ var previous=row.time;
4721
+ message.order.slice(0,limits.parts).forEach(function(part){
4722
+ var node=part.type==='text'?row.texts[part.index]:row.tools.get((message.tools[part.index]||{}).id);
4723
+ node=node&&node.node||node;
4724
+ if(node){if(previous.nextSibling!==node){row.node.insertBefore(node,previous.nextSibling||null)}previous=node}
4647
4725
  });
4648
- if(item.querySelector('pre')){section.appendChild(item)}
4649
- });panel.appendChild(section);
4650
- if(!hasText){section.appendChild(element('p',role==='user'?msg.noPrompts:msg.noOutput))}
4726
+ }
4727
+ });
4728
+ record.rows.forEach(function(row,id){if(!used.has(id)){row.node.remove();record.rows.delete(id)}});
4729
+ if(truncated){setText(record.status,msg.current+'\\n'+msg.truncated)}
4730
+ }
4731
+ function load(record){
4732
+ var selected=record.selection,controller=new AbortController(),version=++record.version;
4733
+ record.request=controller;active++;record.panel.setAttribute('aria-busy','true');
4734
+ var finished=false;
4735
+ record.finish=function(){if(!finished){finished=true;active--;clearTimeout(timeout)}};
4736
+ var query=new URLSearchParams({token:document.documentElement.getAttribute('data-token')||''});
4737
+ var path='/api/graph/conversation';
4738
+ if(selected.target==='session'){path='/api/console/conversation';query.set('session',selected.session)}
4739
+ else{query.set('run',selected.run);query.set('operation',selected.operation);
4740
+ if(selected.target==='root'){query.set('target','root')}else{query.set('attempt',selected.attempt)}}
4741
+ var timeout=setTimeout(function(){
4742
+ if(record.version!==version){return}
4743
+ cancel(record);record.due=Date.now()+30000;setText(record.status,msg.unavailable);
4744
+ record.panel.setAttribute('aria-busy','false');schedule();
4745
+ },15000);
4746
+ fetch(path+'?'+query,{signal:controller.signal,cache:'no-store'}).then(function(response){return response.json()}).then(function(data){
4747
+ if(record.version!==version||!records.has(record.key)||!visible(record.host)||controller.signal.aborted){return}
4748
+ if((data.kind==='conversation'||data.kind==='empty')&&!matches(record,data)){data={kind:'unavailable',message:msg.unavailable}}
4749
+ var negative=rootRecovery(record,data);
4750
+ record.panel.setAttribute('data-conversation-kind',data.kind||'unavailable');
4751
+ setText(record.status,data.message||((data.kind==='conversation'||data.kind==='empty')?msg.current:msg.unavailable));
4752
+ if(data.kind==='conversation'||data.kind==='empty'){
4753
+ recoveries.delete(record.key);
4754
+ record.panel.setAttribute('data-conversation-session',data.sessionID);
4755
+ setText(record.source,(data.session&&data.session.title||msg.session)+' · '+data.sessionID);
4756
+ var focus=record.panel.contains(document.activeElement)?document.activeElement:null;
4757
+ render(record,data.messages||[]);
4758
+ if(focus&&focus.isConnected&&focus!==document.activeElement&&
4759
+ (!document.activeElement||document.activeElement===document.body)){focus.focus({preventScroll:true})}
4760
+ if(data.session&&data.session.id===data.sessionID){
4761
+ document.dispatchEvent(new CustomEvent('console-session-metadata',{detail:{session:data.session,runID:data.runID,target:selected.target}}));
4762
+ }
4763
+ }else{rememberRecovery(record,negative);record.body.textContent='';record.source.textContent='';record.rows.clear()}
4764
+ record.due=['disabled','unauthorized','historical','invalid','oversized'].includes(data.kind)?Infinity:Date.now()+(data.kind==='unavailable'?30000:2000);
4765
+ }).catch(function(){
4766
+ if(record.version===version&&!controller.signal.aborted){
4767
+ recoveries.delete(record.key);
4768
+ setText(record.status,msg.unavailable);record.panel.setAttribute('data-conversation-kind','unavailable');
4769
+ record.body.textContent='';record.source.textContent='';record.rows.clear();record.due=Date.now()+30000;
4770
+ }
4771
+ }).finally(function(){
4772
+ if(!finished){finished=true;clearTimeout(timeout);active--}
4773
+ if(record.version===version){record.request=null;record.panel.setAttribute('aria-busy','false');if(controller.signal.aborted){record.due=Date.now()+30000;setText(record.status,msg.unavailable)}}
4774
+ schedule();
4775
+ });
4776
+ }
4777
+ function sync(){
4778
+ var items=desired(),byKey=new Map(items.map(function(item){return [item.key,item]}));
4779
+ Array.from(records.values()).forEach(function(record){
4780
+ var item=byKey.get(record.key);
4781
+ if(!item||!visible(item.host)){
4782
+ if(item&&item.host.isConnected){item.host.style.minHeight=item.host.getBoundingClientRect().height+'px'}
4783
+ discard(record);return;
4784
+ }
4785
+ record.host=item.host;
4786
+ if(record.panel.parentElement!==item.host){item.host.appendChild(record.panel)}
4787
+ });
4788
+ items.forEach(function(item){
4789
+ if(!visible(item.host)){return}
4790
+ var record=records.get(item.key);
4791
+ if(!record&&records.size<8){
4792
+ if(item.selection.target==='session'){item.host.textContent=''}
4793
+ record=create(item);
4794
+ }
4795
+ if(!record){return}
4796
+ if(item.button){item.button.hidden=true;item.button.setAttribute('aria-controls',record.panel.id)}
4797
+ var saved=recovery(record);
4798
+ if(saved&&!record.rows.size&&!record.request){record.panel.setAttribute('data-conversation-kind','unavailable');setText(record.status,saved.message)}
4799
+ if(!record.request&&active<2&&record.due<=Date.now()&&!saved){load(record)}
4651
4800
  });
4652
- messages.forEach(function(m){
4653
- if((m.tools||[]).length>limits.parts){truncated=true}
4654
- (m.tools||[]).slice(0,limits.parts).forEach(function(tool){
4655
- if(toolCount++>=limits.tools||remaining<=0){truncated=true;return}
4656
- var call=element('details');call.setAttribute('data-tool-call',tool.id);
4657
- call.appendChild(element('summary',text(tool.name)+' · '+text(tool.status)));
4658
- if(tool.startedAt!==undefined){call.appendChild(element('time',consoleLabels.utcTime(tool.startedAt)))}
4659
- call.appendChild(element('h5',msg.arguments));call.appendChild(element('pre',text(tool.arguments)));
4660
- call.appendChild(element('h5',msg.result));call.appendChild(element('pre',text(tool.result===undefined?msg.pending:tool.result)));
4661
- calls.appendChild(call);
4801
+ }
4802
+ function schedule(){
4803
+ if(stopped||scheduled!==null){return}
4804
+ scheduled=setTimeout(function(){scheduled=null;sync()},50);
4805
+ }
4806
+ document.addEventListener('conversation-selection',function(event){
4807
+ clear(event&&event.detail&&event.detail.scope==='native'?'native':'all');
4808
+ sync();
4809
+ });
4810
+ document.addEventListener('conversation-tree-replacing',function(){
4811
+ var scroll=document.querySelector('.scroll'),tree=mount&&mount.querySelector('[data-lifecycle-tree-mount]');
4812
+ retained={focus:document.activeElement,scroll:scroll,scrollTop:scroll&&scroll.scrollTop,treeTop:tree&&tree.scrollTop,positions:[]};
4813
+ records.forEach(function(record){
4814
+ [record.body].concat(Array.from(record.panel.querySelectorAll('pre'))).forEach(function(node){
4815
+ retained.positions.push([node,node.scrollTop,node.scrollLeft]);
4662
4816
  });
4663
4817
  });
4664
- if(count===0){calls.appendChild(element('p',msg.noTools))}
4665
- if(truncated){panel.appendChild(element('p',msg.truncated))}
4666
- }
4667
- mount.addEventListener('click',function(event){
4668
- var button=event.target.closest('[data-inspect-attempt],[data-inspect-root]');
4669
- if(!button||button.disabled||mount.getAttribute('data-conversation-transition')==='1'){return}
4670
- if(button===selectedButton()){close(true);return}
4671
- close();
4672
- var root=button.hasAttribute('data-inspect-root');
4673
- var session=button.closest(root?'[data-root-session-id]':'[data-lifecycle-session-id]');
4674
- if(!session){return}
4675
- selection={
4676
- run:mount.getAttribute('data-lifecycle-run'),
4677
- operation:button.getAttribute('data-inspect-operation'),
4678
- attempt:button.getAttribute('data-inspect-attempt'),
4679
- target:root?'root':'attempt',
4680
- session:session.getAttribute(root?'data-root-session-id':'data-lifecycle-session-id'),
4681
- cursor:mount.getAttribute('data-conversation-cursor')||null
4682
- };
4683
- if(!selectedButton()){close();return}
4684
- panel.setAttribute('data-conversation-run',selection.run);
4685
- panel.setAttribute('data-conversation-operation',selection.operation);
4686
- if(selection.attempt){panel.setAttribute('data-conversation-attempt',selection.attempt)}
4687
- panel.setAttribute('data-conversation-target',selection.target);
4688
- panel.setAttribute('data-conversation-session',selection.session);
4689
- reanchor();
4690
- panel.hidden=false;
4691
- if(selection.cursor!==null){reset();panel.setAttribute('data-conversation-kind','historical');panel.appendChild(element('p',msg.historical));showHeading()}
4692
- else{load(true)}
4818
+ records.forEach(function(record){if(record.selection.target!=='session'){record.panel.remove()}});
4819
+ });
4820
+ document.addEventListener('conversation-tree-committed',function(){
4821
+ sync();
4822
+ if(retained){
4823
+ retained.positions.forEach(function(item){if(item[0].isConnected){item[0].scrollTop=item[1];item[0].scrollLeft=item[2]}});
4824
+ var tree=mount&&mount.querySelector('[data-lifecycle-tree-mount]');
4825
+ if(tree&&retained.treeTop!==null){tree.scrollTop=retained.treeTop}
4826
+ if(retained.scroll){retained.scroll.scrollTop=retained.scrollTop}
4827
+ if(retained.focus&&retained.focus.isConnected&&retained.focus!==document.activeElement&&
4828
+ (!document.activeElement||document.activeElement===document.body||document.activeElement===document.documentElement)){
4829
+ retained.focus.focus({preventScroll:true});
4830
+ }
4831
+ retained=null;
4832
+ }
4693
4833
  });
4694
- document.addEventListener('conversation-selection',close);
4695
- document.addEventListener('conversation-tree-replacing',retain);
4696
- document.addEventListener('conversation-tree-committed',reanchor);
4697
- window.addEventListener('pagehide',close);
4834
+ document.addEventListener('console-conversation-selection',sync);
4835
+ document.addEventListener('visibilitychange',function(){if(document.hidden){clear()}else{sync()}});
4836
+ document.addEventListener('scroll',schedule,true);
4837
+ window.addEventListener('resize',schedule);
4838
+ window.addEventListener('pagehide',function(){stopped=true;clear();clearInterval(timer);clearTimeout(scheduled);scheduled=null});
4839
+ window.addEventListener('pageshow',function(event){if(event.persisted){stopped=false;timer=setInterval(sync,2000);sync()}});
4840
+ timer=setInterval(sync,2000);sync();
4698
4841
  }
4699
4842
  `;
4700
4843
  var CONVERSATION_STYLE = `
4701
- .lc-conversation{margin-top:12px;padding:12px;min-width:0;border:1px solid var(--cp-border);border-radius:16px;background:var(--cp-surface);color:var(--cp-text);overflow-anchor:none}
4844
+ .lc-conversation{margin-top:12px;padding:12px;min-width:0;border:1px solid var(--cp-border,var(--border));border-radius:12px;background:var(--cp-surface,var(--surface));color:var(--cp-text,var(--fg));overflow-anchor:none}
4702
4845
  .lc-conversation[hidden]{display:none}
4703
- .lc-conversation-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px}
4704
4846
  .lc-conversation h3,.lc-conversation h4,.lc-conversation p{margin:8px 0}
4705
- .lc-conversation h3{scroll-margin-top:12px}
4706
- .lc-conversation pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:400px;overflow:auto;background:var(--cp-surface-soft);padding:12px;font-family:Consolas,"Courier New",Courier,monospace}
4707
- .lc-conversation [data-conversation-role="user"] pre{max-height:180px}
4708
- .lc-conversation [data-conversation-tool-list]{max-height:160px;overflow:auto}
4709
- .lc-conversation details{margin-top:12px;padding:8px;border:1px solid var(--cp-border);border-radius:.625rem}
4710
- .lc-conversation summary{cursor:pointer;color:var(--cp-accent)}
4711
- .lc-conversation time{font-size:11px;color:var(--cp-text-muted)}
4712
- .lc-conversation button,[data-inspect-attempt],[data-inspect-root]{padding:6px 10px;margin:6px;border:1px solid var(--cp-border);border-radius:.625rem;background:var(--cp-surface);color:var(--cp-accent)}
4847
+ .lc-conversation [data-conversation-messages]{max-height:520px;overflow:auto;overflow-anchor:none}
4848
+ .lc-conversation pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:400px;overflow:auto;background:var(--cp-surface-soft,var(--bg));padding:12px;font-family:var(--font-mono)}
4849
+ .lc-conversation details{margin:12px 0;padding:8px;border:1px solid var(--cp-border,var(--border));border-radius:8px}
4850
+ .lc-conversation summary{cursor:pointer;color:var(--cp-accent,var(--accent))}
4851
+ .lc-conversation time{font-size:11px;color:var(--cp-text-muted,var(--muted))}
4852
+ .lc-conversation button,[data-inspect-attempt],[data-inspect-root]{padding:6px 10px;margin:6px;border:1px solid var(--cp-border,var(--border));border-radius:8px;background:var(--cp-surface,var(--surface));color:var(--cp-accent,var(--accent))}
4713
4853
  .lct-operation-name{display:block;font-size:16px}
4714
4854
  .lct-operation-head details,.lct-session details{font-size:11px;color:var(--cp-text-muted)}
4715
4855
  .lct-session small,.session-btn small{display:block;color:var(--cp-text-muted)}
@@ -4743,7 +4883,7 @@ var CONSOLE_REFRESH_JS = `
4743
4883
  if(old.getAttribute('data-label-fallback')!==next.textContent){old.setAttribute('data-label-fallback',next.textContent)}
4744
4884
  return;
4745
4885
  }
4746
- if(old.matches('input,textarea,select,.term,.pty,.graph-drill,[data-config-mount]')){return}
4886
+ if(old.matches('input,textarea,select,.term,.pty,.graph-drill,[data-config-mount],[data-session-conversation]')){return}
4747
4887
  Array.from(old.attributes).forEach(function(a){
4748
4888
  if(a.name!=='open'&&a.name!=='hidden'&&a.name!=='aria-selected'&&!next.hasAttribute(a.name)){old.removeAttribute(a.name)}
4749
4889
  });
@@ -4957,6 +5097,8 @@ svg.graph{display:block}
4957
5097
  .graph-drill .drilllog .tl.error{color:#f85149}
4958
5098
  `;
4959
5099
  var CONSOLE_CLIENT_JS = `
5100
+ ${LABELS_CLIENT_JS}
5101
+ ${CONVERSATION_CLIENT_JS}
4960
5102
  (function(){
4961
5103
  var detailMsg=${JSON.stringify(consoleDetailMessages)};
4962
5104
  var terminalCollectors=[];
@@ -5011,6 +5153,7 @@ var CONSOLE_CLIENT_JS = `
5011
5153
  if(show){matched=true;if(!screen.hidden){connectTerminal(cards[j],id)}}
5012
5154
  }
5013
5155
  updateTerminalLeases();
5156
+ document.dispatchEvent(new CustomEvent('console-conversation-selection'));
5014
5157
  return matched;
5015
5158
  }
5016
5159
  if(!screen.__logsWired){
@@ -5071,6 +5214,7 @@ var CONSOLE_CLIENT_JS = `
5071
5214
  if(name==='config'&&activeScreen){loadConfig(activeScreen)}
5072
5215
  if(name==='logs'&&activeScreen){wireLogsScreen(activeScreen)}
5073
5216
  updateTerminalLeases();
5217
+ document.dispatchEvent(new CustomEvent('console-conversation-selection'));
5074
5218
  try{localStorage.setItem('openteam.screen',name)}catch(e){}
5075
5219
  document.body.classList.remove('nav-open');
5076
5220
  return true;
@@ -5472,8 +5616,6 @@ var LIFECYCLE_THEME_BOOT = `
5472
5616
  })();
5473
5617
  `;
5474
5618
  var LIFECYCLE_CLIENT_JS = `
5475
- ${LABELS_CLIENT_JS}
5476
- ${CONVERSATION_CLIENT_JS}
5477
5619
  function wireLifecycleTimeline(){
5478
5620
  var mount=document.getElementById('lifecycle-timeline-mount');
5479
5621
  if(!mount){return}
@@ -5520,7 +5662,7 @@ function wireLifecycleTimeline(){
5520
5662
  treeMountEl.setAttribute('inert','');
5521
5663
  treeMountEl.querySelectorAll('[data-inspect-attempt],[data-inspect-root]').forEach(function(button){button.disabled=true});
5522
5664
  }
5523
- document.dispatchEvent(new CustomEvent('conversation-selection'));
5665
+ document.dispatchEvent(new CustomEvent('conversation-selection',{detail:{scope:'native'}}));
5524
5666
  }
5525
5667
  function commitConversation(run,cursor){
5526
5668
  cursor=cursor===null?null:String(cursor);
@@ -5982,6 +6124,7 @@ function wireLifecycleTimeline(){
5982
6124
  inspect.setAttribute('aria-expanded','false');
5983
6125
  inspect.setAttribute('aria-controls','lc-attempt-conversation');
5984
6126
  inspect.setAttribute('data-inspect-attempt',attempt.attemptID);
6127
+ inspect.hidden=true;
5985
6128
  inspect.setAttribute('data-inspect-operation',attempt.operationID);
5986
6129
  session.appendChild(inspect);
5987
6130
  if(attempt.parentSessionID){
@@ -6134,7 +6277,7 @@ function wireLifecycleTimeline(){
6134
6277
  var time=makeElement('small','',${JSON.stringify(consoleDetailMessages.timeUnknown)});time.setAttribute('data-session-time',rootID);rootSession.appendChild(time);
6135
6278
  var id=makeElement('code','',rootID);rootSession.appendChild(id);
6136
6279
  }else{rootSession.appendChild(makeElement('span','',${JSON.stringify(consoleDetailMessages.rootUnlinked)}))}
6137
- var inspect=makeElement('button','',message('inspect'));inspect.type='button';inspect.setAttribute('data-inspect-root','');inspect.setAttribute('data-inspect-operation',op.operationID);inspect.setAttribute('aria-expanded','false');inspect.setAttribute('aria-controls','lc-attempt-conversation');rootSession.appendChild(inspect);
6280
+ var inspect=makeElement('button','',message('inspect'));inspect.type='button';inspect.hidden=true;inspect.setAttribute('data-inspect-root','');inspect.setAttribute('data-inspect-operation',op.operationID);rootSession.appendChild(inspect);
6138
6281
  card.insertBefore(rootSession,card.querySelector('.lct-attempts'));
6139
6282
  }
6140
6283
  var children=childrenByParent.get(op.operationID)||[];
@@ -6746,6 +6889,7 @@ function sessionDetailCard(tab, terminalEnabled, ptyEnabled, visible) {
6746
6889
  const idSuffix = domIdPart(tab.sessionID);
6747
6890
  const body = [
6748
6891
  sessionHeaderPanel(tab),
6892
+ `<div data-session-conversation="${escapeHtml(tab.sessionID)}"></div>`,
6749
6893
  terminalEnabled ? terminalPanel(tab) : "",
6750
6894
  terminalEnabled && ptyEnabled ? ptyPanel(tab) : "",
6751
6895
  routesPanel(tab.recentRoutes, idSuffix),
@@ -7131,7 +7275,7 @@ function lcSessionHtml(attempt, role) {
7131
7275
  sessionNameHtml(attempt.sessionID, role),
7132
7276
  `<small data-session-time="${escapeHtml(attempt.sessionID)}">${escapeHtml(consoleDetailMessages.timeUnknown)}</small>`,
7133
7277
  `<details><summary>${escapeHtml(consoleDetailMessages.technicalID)}</summary><code>${escapeHtml(attempt.sessionID)}</code></details>`,
7134
- `<button type="button" aria-expanded="false" aria-controls="lc-attempt-conversation" data-inspect-operation="${escapeHtml(attempt.operationID)}" data-inspect-attempt="${escapeHtml(attempt.attemptID)}">${escapeHtml(consoleDetailMessages.inspectConversation)}</button>`,
7278
+ `<button type="button" hidden data-inspect-operation="${escapeHtml(attempt.operationID)}" data-inspect-attempt="${escapeHtml(attempt.attemptID)}">${escapeHtml(consoleDetailMessages.inspectConversation)}</button>`,
7135
7279
  attempt.parentSessionID === undefined ? "" : `<span class="lct-parent-session">${escapeHtml(lifecycleMessages.parentSessionLabel)} <code>${escapeHtml(attempt.parentSessionID)}</code></span>`,
7136
7280
  "</div>"
7137
7281
  ].join("");
@@ -7218,7 +7362,7 @@ function lcOperationHtml(operation, operationIDs, childrenHtml, depth, rootSessi
7218
7362
  isRoot && operation.kind === "coordinator" ? [
7219
7363
  `<div class="lct-session" data-root-session-id="${escapeHtml(rootSessionID ?? "")}">`,
7220
7364
  rootSessionID ? `${sessionNameHtml(rootSessionID, consoleDetailMessages.orchestrator)} <small data-session-time="${escapeHtml(rootSessionID)}">${escapeHtml(consoleDetailMessages.timeUnknown)}</small><details><summary>${escapeHtml(consoleDetailMessages.technicalID)}</summary><code>${escapeHtml(rootSessionID)}</code></details>` : `<span>${escapeHtml(consoleDetailMessages.rootUnlinked)}</span>`,
7221
- `<button type="button" data-inspect-root data-inspect-operation="${escapeHtml(operation.operationID)}" aria-expanded="false" aria-controls="lc-attempt-conversation">${escapeHtml(consoleDetailMessages.inspectConversation)}</button>`,
7365
+ `<button type="button" hidden data-inspect-root data-inspect-operation="${escapeHtml(operation.operationID)}">${escapeHtml(consoleDetailMessages.inspectConversation)}</button>`,
7222
7366
  "</div>"
7223
7367
  ].join("") : "",
7224
7368
  `<section class="lct-attempts" aria-label="${escapeHtml(lifecycleMessages.attemptsLabel)}">${attempts}</section>`,
@@ -7597,11 +7741,11 @@ function renderConsoleHtml(state, options = {}) {
7597
7741
  const body = [
7598
7742
  screenSection("tree", treeCopy.label, treeCopy.heading, treeCopy.description, treeBody, true),
7599
7743
  screenSection("routing", "Routing and telemetry", "Routing + telemetry", "Aggregate routing and cost telemetry from <code>GET /api/state</code> across all sessions.", routingScreenBody(state)),
7600
- screenSection("logs", "Logs and session activity", "Logs / activity", "Per-session activity. Prompts are referenced by hash only and never rendered in clear text.", `<div data-log-controls><label>${escapeHtml(consoleDetailMessages.filter)} <input type="search" data-log-filter></label> <label><input type="checkbox" data-log-follow> ${escapeHtml(consoleDetailMessages.follow)}</label> <button type="button" data-log-incoming hidden>${escapeHtml(consoleDetailMessages.incoming)}</button></div><div data-log-content>${logsScreenBody(state, terminalEnabled, ptyEnabled)}</div>`),
7744
+ screenSection("logs", "Logs and session activity", "Logs / activity", consoleDetailMessages.logsDescription, `<div data-log-controls><label>${escapeHtml(consoleDetailMessages.filter)} <input type="search" data-log-filter></label> <label><input type="checkbox" data-log-follow> ${escapeHtml(consoleDetailMessages.follow)}</label> <button type="button" data-log-incoming hidden>${escapeHtml(consoleDetailMessages.incoming)}</button></div><div data-log-content>${logsScreenBody(state, terminalEnabled, ptyEnabled)}</div>`),
7601
7745
  screenSection("config", "Editable configuration", 'Configuration <span class="pill warn">editable</span>', "Edit the whole openteam JSON configuration. Roles are editable inside the JSON when present; unsupported surfaces are not shown.", configScreenBody(options.configEditor?.hasToken === true))
7602
7746
  ].join("");
7603
7747
  const session = state.session.id === undefined ? "" : ` · session ${escapeHtml(state.session.id)}`;
7604
- const tokenAttr = (terminalEnabled || graphDrilldown || configEnabled || lifecycleEnabled) && options.consoleToken !== undefined ? ` data-token="${escapeHtml(options.consoleToken)}"${terminalEnabled ? ' data-terminal="1"' : ""}${ptyEnabled ? ' data-pty="1"' : ""}${graphDrilldown ? ' data-drilldown="1"' : ""}${configEnabled ? ' data-config="1"' : ""}` : "";
7748
+ const tokenAttr = options.consoleToken !== undefined ? ` data-token="${escapeHtml(options.consoleToken)}"${terminalEnabled ? ' data-terminal="1"' : ""}${terminalEnabled && ptyEnabled ? ' data-pty="1"' : ""}${graphDrilldown ? ' data-drilldown="1"' : ""}${configEnabled ? ' data-config="1"' : ""}` : "";
7605
7749
  return [
7606
7750
  "<!doctype html>",
7607
7751
  `<html lang="en" data-generated="${escapeHtml(state.generatedAt)}" data-refresh="${refreshMs}"${tokenAttr}>`,
@@ -7610,7 +7754,7 @@ function renderConsoleHtml(state, options = {}) {
7610
7754
  '<meta name="viewport" content="width=device-width,initial-scale=1">',
7611
7755
  "<title>openteam console</title>",
7612
7756
  lifecycleEnabled ? `<script>${LIFECYCLE_THEME_BOOT}</script>` : "",
7613
- `<style>${CONSOLE_STYLE}${graphOptionProvided ? GRAPH_STYLE : ""}${lifecycleEnabled ? LIFECYCLE_STYLE + CONVERSATION_STYLE : ""}</style>`,
7757
+ `<style>${CONSOLE_STYLE}${CONVERSATION_STYLE}${graphOptionProvided ? GRAPH_STYLE : ""}${lifecycleEnabled ? LIFECYCLE_STYLE : ""}</style>`,
7614
7758
  "</head>",
7615
7759
  "<body>",
7616
7760
  '<div class="app">',
@@ -8387,7 +8531,8 @@ async function untilAborted(read, signal) {
8387
8531
  }
8388
8532
  async function handleConversationRoute(request, response, url, deps) {
8389
8533
  const labelsOnly = url.pathname === "/api/graph/session-labels";
8390
- if (url.pathname !== "/api/graph/conversation" && !labelsOnly)
8534
+ const direct = url.pathname === "/api/console/conversation";
8535
+ if (url.pathname !== "/api/graph/conversation" && !labelsOnly && !direct)
8391
8536
  return false;
8392
8537
  const send = (status, kind, message, extra = {}) => {
8393
8538
  response.writeHead(status, {
@@ -8407,11 +8552,60 @@ async function handleConversationRoute(request, response, url, deps) {
8407
8552
  send(405, "unavailable", consoleDetailMessages.unauthorized);
8408
8553
  return true;
8409
8554
  }
8410
- if (!deps.enabled || !deps.contentEnabled || url.searchParams.get("diagnostic") !== "1") {
8555
+ if (!direct && !deps.enabled || !deps.contentEnabled) {
8411
8556
  send(403, "disabled", consoleDetailMessages.disabled);
8412
8557
  return true;
8413
8558
  }
8414
8559
  const params = url.searchParams;
8560
+ if (direct) {
8561
+ const sessionID = params.get("session");
8562
+ if (!sessionID || sessionID.length > 256 || params.getAll("session").length !== 1 || ["run", "operation", "attempt", "target"].some((key) => params.has(key))) {
8563
+ send(400, "invalid", consoleDetailMessages.invalid);
8564
+ return true;
8565
+ }
8566
+ if (params.has("cursor")) {
8567
+ send(409, "historical", consoleDetailMessages.historical);
8568
+ return true;
8569
+ }
8570
+ const controller2 = new AbortController;
8571
+ const cancel2 = () => controller2.abort();
8572
+ response.on("close", cancel2);
8573
+ request.on("aborted", cancel2);
8574
+ const timeout2 = setTimeout(cancel2, SESSION_HISTORY_LIMITS.timeoutMs);
8575
+ try {
8576
+ const observed = await untilAborted(deps.observedSessionIDs?.() ?? Promise.resolve([]), controller2.signal);
8577
+ if (!observed.includes(sessionID)) {
8578
+ send(404, "invalid", consoleDetailMessages.invalid);
8579
+ return true;
8580
+ }
8581
+ const endpoint = await untilAborted(deps.resolveEndpoint(sessionID), controller2.signal);
8582
+ if (!endpoint) {
8583
+ send(503, "unavailable", consoleDetailMessages.unavailable);
8584
+ return true;
8585
+ }
8586
+ const conversation = await untilAborted(deps.readConversation(endpoint, sessionID, controller2.signal), controller2.signal);
8587
+ send(200, conversation.messages.length || conversation.truncated ? "conversation" : "empty", `${consoleDetailMessages.current}${conversation.truncated ? `
8588
+ ${consoleDetailMessages.truncated}` : conversation.messages.length ? "" : `
8589
+ ${consoleDetailMessages.empty}`}`, {
8590
+ scope: "present-day-session",
8591
+ target: "session",
8592
+ sessionID,
8593
+ ...conversation
8594
+ });
8595
+ } catch (error) {
8596
+ if (!response.destroyed)
8597
+ send(error instanceof ConversationTooLargeError ? 413 : 503, error instanceof ConversationTooLargeError ? "oversized" : "unavailable", error instanceof ConversationTooLargeError ? consoleDetailMessages.oversized : consoleDetailMessages.unavailable);
8598
+ } finally {
8599
+ clearTimeout(timeout2);
8600
+ response.off("close", cancel2);
8601
+ request.off("aborted", cancel2);
8602
+ }
8603
+ return true;
8604
+ }
8605
+ if (!deps.reader) {
8606
+ send(503, "unavailable", consoleDetailMessages.unavailable);
8607
+ return true;
8608
+ }
8415
8609
  const rootTarget = params.get("target") === "root";
8416
8610
  if ((labelsOnly ? params.has("run") ? ["run"] : [] : rootTarget ? ["run", "operation"] : ["run", "operation", "attempt"]).some((key) => params.getAll(key).length !== 1 || !params.get(key)) || params.has("session") || params.getAll("target").length > 1 || params.has("target") && !rootTarget && params.get("target") !== "attempt" || rootTarget && params.has("attempt") || labelsOnly && (params.has("operation") || params.has("attempt") || params.has("target"))) {
8417
8611
  send(400, "invalid", consoleDetailMessages.invalid);
@@ -8435,7 +8629,7 @@ async function handleConversationRoute(request, response, url, deps) {
8435
8629
  if (request.aborted || request.socket.destroyed)
8436
8630
  cancel2();
8437
8631
  const deadline = Date.now() + SESSION_HISTORY_LIMITS.timeoutMs;
8438
- const timeout = setTimeout(cancel2, SESSION_HISTORY_LIMITS.timeoutMs);
8632
+ const timeout2 = setTimeout(cancel2, SESSION_HISTORY_LIMITS.timeoutMs);
8439
8633
  try {
8440
8634
  const history2 = selected?.kind === "view" ? selected : undefined;
8441
8635
  const selectedRoot = history2?.runs.find((run) => run.runID === history2.selection.runID)?.rootSessionID;
@@ -8501,7 +8695,7 @@ ${consoleDetailMessages.partialMetadata}` : consoleDetailMessages.currentMetadat
8501
8695
  if (!response.destroyed && !request.socket.destroyed)
8502
8696
  send(503, "unavailable", consoleDetailMessages.unavailable);
8503
8697
  } finally {
8504
- clearTimeout(timeout);
8698
+ clearTimeout(timeout2);
8505
8699
  response.off("close", cancel2);
8506
8700
  request.off("aborted", cancel2);
8507
8701
  request.socket.off("close", cancel2);
@@ -8526,21 +8720,23 @@ ${consoleDetailMessages.partialMetadata}` : consoleDetailMessages.currentMetadat
8526
8720
  const controller = new AbortController;
8527
8721
  const cancel = () => controller.abort();
8528
8722
  response.on("close", cancel);
8723
+ request.on("aborted", cancel);
8724
+ const timeout = setTimeout(cancel, SESSION_HISTORY_LIMITS.timeoutMs);
8529
8725
  try {
8530
8726
  const run = history.runs.find((item) => item.runID === history.selection.runID);
8531
8727
  const recordedRoot = run?.rootSessionID;
8532
8728
  const sessionID = rootTarget ? recordedRoot : attempt?.sessionID;
8533
8729
  const endpointID = sessionID ?? history.projection.operations.flatMap((op) => op.attempts).filter((item) => item.sessionID).at(-1)?.sessionID ?? "";
8534
- const endpoint = await deps.resolveEndpoint(endpointID);
8730
+ const endpoint = await untilAborted(deps.resolveEndpoint(endpointID), controller.signal);
8535
8731
  if (!endpoint) {
8536
8732
  send(503, "unavailable", consoleDetailMessages.unavailable);
8537
8733
  return true;
8538
8734
  }
8539
- const recovered = !sessionID && rootTarget && deps.readConversation.root ? await deps.readConversation.root(endpoint, history.projection, controller.signal) : undefined;
8735
+ const recovered = !sessionID && rootTarget && deps.readConversation.root ? await untilAborted(deps.readConversation.root(endpoint, history.projection, controller.signal), controller.signal) : undefined;
8540
8736
  const resolvedID = sessionID ?? recovered?.session.id;
8541
8737
  if (!resolvedID)
8542
8738
  throw new AssociationError("unavailable");
8543
- const conversation = recovered ?? await deps.readConversation(endpoint, resolvedID, controller.signal);
8739
+ const conversation = recovered ?? await untilAborted(deps.readConversation(endpoint, resolvedID, controller.signal), controller.signal);
8544
8740
  const current = rootTarget ? `${consoleDetailMessages.rootCurrent}
8545
8741
  ${consoleDetailMessages.current}${recovered ? `
8546
8742
  ${consoleDetailMessages.recovered}` : ""}` : consoleDetailMessages.current;
@@ -8556,10 +8752,12 @@ ${consoleDetailMessages.empty}`, {
8556
8752
  ...conversation
8557
8753
  });
8558
8754
  } catch (error) {
8559
- if (!controller.signal.aborted)
8755
+ if (!response.destroyed)
8560
8756
  send(error instanceof ConversationTooLargeError ? 413 : 503, error instanceof ConversationTooLargeError ? "oversized" : "unavailable", error instanceof AssociationError ? error.message : error instanceof ConversationTooLargeError ? consoleDetailMessages.oversized : consoleDetailMessages.unavailable);
8561
8757
  } finally {
8758
+ clearTimeout(timeout);
8562
8759
  response.off("close", cancel);
8760
+ request.off("aborted", cancel);
8563
8761
  }
8564
8762
  return true;
8565
8763
  }
@@ -8839,15 +9037,19 @@ async function createConsoleServer(deps) {
8839
9037
  };
8840
9038
  const handle = async (request, response) => {
8841
9039
  const url2 = new URL(request.url ?? "/", "http://localhost");
8842
- if (["/api/graph/conversation", "/api/graph/session-labels"].includes(url2.pathname) && deps.storageApi && !readAuthorized2(request, url2, deps.storageApi.token)) {
9040
+ if ([
9041
+ "/api/graph/conversation",
9042
+ "/api/graph/session-labels",
9043
+ "/api/console/conversation"
9044
+ ].includes(url2.pathname) && deps.storageApi && !readAuthorized2(request, url2, deps.storageApi.token)) {
8843
9045
  response.writeHead(401, { ...JSON_HEADERS5, "cache-control": "no-store" }).end(JSON.stringify({ version: 1, kind: "unauthorized" }));
8844
9046
  return;
8845
9047
  }
8846
- if (deps.console?.readConversation && deps.lifecycleHistoryApi && await handleConversationRoute(request, response, url2, {
9048
+ if (deps.console?.readConversation && await handleConversationRoute(request, response, url2, {
8847
9049
  token: deps.console.token,
8848
9050
  enabled: deps.config.graphView?.enabled === true,
8849
- contentEnabled: deps.console.conversationContentEnabled === true,
8850
- reader: deps.lifecycleHistoryApi.reader,
9051
+ contentEnabled: deps.console.conversationContentEnabled ?? deps.config.sessionHistory?.contentEnabled ?? true,
9052
+ ...deps.lifecycleHistoryApi ? { reader: deps.lifecycleHistoryApi.reader } : {},
8851
9053
  resolveEndpoint: async (id) => deps.console?.historyEndpoint ?? await resolveEndpoint(id),
8852
9054
  observedSessionIDs: async () => buildConsoleState(await deps.readSnapshot()).sessions.map((session) => session.sessionID),
8853
9055
  readConversation: deps.console.readConversation
@@ -9014,6 +9216,7 @@ var PROVIDER_LABELS = {
9014
9216
  ollama: "Ollama",
9015
9217
  lmstudio: "LM Studio",
9016
9218
  lemonade: "Lemonade Server",
9219
+ "llama-swap": executionSetupMessages.llamaSwap.label,
9017
9220
  "foundry-local": "Foundry Local"
9018
9221
  };
9019
9222
  function providerLabel(providerID) {
@@ -9235,7 +9438,8 @@ var KNOWN_LOCAL_PROVIDER_IDS = new Set([
9235
9438
  "ollama",
9236
9439
  "lmstudio",
9237
9440
  "foundry-local",
9238
- "lemonade"
9441
+ "lemonade",
9442
+ "llama-swap"
9239
9443
  ]);
9240
9444
  var KNOWN_FRONTIER_PROVIDER_IDS = new Set([
9241
9445
  "github-copilot",
@@ -11150,7 +11354,7 @@ async function runConsoleServe(deps, options) {
11150
11354
  ptyEnabled,
11151
11355
  createClient: makeClient,
11152
11356
  drilldownEnabled: graphViewEnabled,
11153
- conversationContentEnabled: config.console.sessionHistory?.contentEnabled === true,
11357
+ conversationContentEnabled: config.console.sessionHistory?.contentEnabled !== false,
11154
11358
  ...config.console.sessionHistory?.endpoint !== undefined ? { historyEndpoint: config.console.sessionHistory.endpoint } : {},
11155
11359
  readConversation: createConversationReader({
11156
11360
  directory: deps.directory ?? process.cwd(),
@@ -16043,6 +16247,7 @@ var reasons = {
16043
16247
  unknown: "fresh residency metadata is unavailable, malformed, or ambiguous",
16044
16248
  inactive: "the requested model is not verified as resident and serving",
16045
16249
  identity: "the requested model identity can be rewritten or is ambiguous",
16250
+ "authentication-unsupported": "authentication is required but unsupported by this integration",
16046
16251
  destination: "the request destination does not match its configured runtime",
16047
16252
  transport: "this provider execution path cannot enforce the residency guard",
16048
16253
  request: "this is not a supported, explicit model inference request",
@@ -16051,22 +16256,72 @@ var reasons = {
16051
16256
  configuration: "the residency guard configuration is invalid"
16052
16257
  };
16053
16258
  var modelResidencyMessages = {
16054
- blocked: (reason) => `[openteam] Lemonade model request blocked: ${reasons[reason]}.`,
16055
- providerDisabled: (providerID) => `[openteam] Lemonade provider "${providerID}" is disabled because its configured transport or runtime mapping cannot enforce loaded-only requests.`,
16056
- extractionRuntimeUnavailable: "[openteam] Memory extraction requires one enabled local runtime matching the configured extraction provider."
16259
+ blocked: (reason, runtimeLabel = "Lemonade", detail) => `[openteam] ${runtimeLabel} model request blocked: ${reasons[reason]}${detail === undefined ? "" : ` (${detail})`}.`,
16260
+ providerDisabled: (providerID, runtimeLabel = "Lemonade") => `[openteam] ${runtimeLabel} provider "${providerID}" is disabled because its configured transport or runtime mapping cannot enforce loaded-only requests.`,
16261
+ extractionRuntimeUnavailable: "[openteam] Memory extraction requires one enabled local runtime matching the configured extraction provider.",
16262
+ extractionRuntimeUnavailableFor: (runtimeLabel) => `[openteam] Memory extraction requires one enabled ${runtimeLabel} local runtime matching the configured extraction provider.`
16263
+ };
16264
+ var llamaSwapResidencyMessages = {
16265
+ catalogModelDetail: (id, detail) => `catalog model "${id}" ${detail}`,
16266
+ invalidCatalogField: (field) => `declares an invalid ${field}`,
16267
+ catalogEntryMustExposeStringId: "catalog entries must expose a string id",
16268
+ catalogModelIdsMustBeNonEmptyTrimmedStrings: "catalog model ids must be non-empty, trimmed strings",
16269
+ expectedDataArray: (path) => `GET ${path} must return an object with a data array`,
16270
+ duplicateCanonicalModelId: (modelID, path) => `duplicate canonical model id "${modelID}" in ${path}`,
16271
+ httpFailure: (path, status) => `GET ${path} failed with HTTP ${status}`,
16272
+ unverifiableResponse: (path) => `GET ${path} failed before a verifiable response`,
16273
+ expectedProfilesArray: (path) => `GET ${path} must return an object with a profiles array`,
16274
+ activeProfileRewrite: (profile) => `active profile "${profile}" can rewrite canonical model routing`,
16275
+ invalidActiveProfileValue: (path) => `GET ${path} reported an invalid active profile value`,
16276
+ expectedRunningArray: (path) => `GET ${path} must return an object with a running array`,
16277
+ malformedRunningEvidence: (path) => `GET ${path} returned malformed model/state evidence`,
16278
+ duplicateRunningEvidence: (path, modelID) => `GET ${path} returned duplicate evidence for "${modelID}"`
16057
16279
  };
16058
16280
 
16059
- // src/local/lemonadeResidency.ts
16281
+ // src/local/residency.ts
16060
16282
  class ModelResidencyError extends Error {
16061
16283
  code;
16062
16284
  statusCode = 400;
16063
16285
  isRetryable = false;
16064
- constructor(code) {
16065
- super(modelResidencyMessages.blocked(code));
16286
+ constructor(code, runtimeLabel = "Lemonade", detail) {
16287
+ super(modelResidencyMessages.blocked(code, runtimeLabel, detail));
16066
16288
  this.code = code;
16067
16289
  this.name = "ModelResidencyError";
16068
16290
  }
16069
16291
  }
16292
+ async function withResidencyDeadline(operation, options) {
16293
+ const timeoutMs = options.timeoutMs ?? 5000;
16294
+ const runtimeLabel = options.runtimeLabel;
16295
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
16296
+ throw new ModelResidencyError("configuration", runtimeLabel);
16297
+ }
16298
+ const controller = new AbortController;
16299
+ const cancel = () => controller.abort(new ModelResidencyError("cancelled", runtimeLabel));
16300
+ if (options.signal?.aborted)
16301
+ throw new ModelResidencyError("cancelled", runtimeLabel);
16302
+ options.signal?.addEventListener("abort", cancel, { once: true });
16303
+ const schedule = options.scheduleTimeout ?? ((callback, ms2) => {
16304
+ const timer = setTimeout(callback, ms2);
16305
+ return () => clearTimeout(timer);
16306
+ });
16307
+ let onAbort = () => {};
16308
+ const aborted = new Promise((_, reject) => {
16309
+ onAbort = () => reject(controller.signal.reason);
16310
+ controller.signal.addEventListener("abort", onAbort, { once: true });
16311
+ });
16312
+ const clearTimer = schedule(() => controller.abort(new ModelResidencyError("timeout", runtimeLabel)), timeoutMs);
16313
+ try {
16314
+ controller.signal.throwIfAborted();
16315
+ return await Promise.race([operation(controller.signal), aborted]);
16316
+ } finally {
16317
+ clearTimer();
16318
+ options.signal?.removeEventListener("abort", cancel);
16319
+ controller.signal.removeEventListener("abort", onAbort);
16320
+ controller.abort();
16321
+ }
16322
+ }
16323
+
16324
+ // src/local/lemonadeResidency.ts
16070
16325
  var modelName = z22.string().min(1).refine((name) => name === name.trim());
16071
16326
  var LoadedModelSchema = z22.object({
16072
16327
  model_name: modelName,
@@ -16203,36 +16458,6 @@ function lemonadeBaseURL(value) {
16203
16458
  }
16204
16459
  return `${url.origin}${url.pathname.replace(/\/$/, "")}`;
16205
16460
  }
16206
- async function withResidencyDeadline(operation, options) {
16207
- const timeoutMs = options.timeoutMs ?? 5000;
16208
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
16209
- throw new ModelResidencyError("configuration");
16210
- }
16211
- const controller = new AbortController;
16212
- const cancel = () => controller.abort(new ModelResidencyError("cancelled"));
16213
- if (options.signal?.aborted)
16214
- throw new ModelResidencyError("cancelled");
16215
- options.signal?.addEventListener("abort", cancel, { once: true });
16216
- const schedule = options.scheduleTimeout ?? ((callback, ms2) => {
16217
- const timer = setTimeout(callback, ms2);
16218
- return () => clearTimeout(timer);
16219
- });
16220
- let onAbort = () => {};
16221
- const aborted = new Promise((_, reject) => {
16222
- onAbort = () => reject(controller.signal.reason);
16223
- controller.signal.addEventListener("abort", onAbort, { once: true });
16224
- });
16225
- const clearTimer = schedule(() => controller.abort(new ModelResidencyError("timeout")), timeoutMs);
16226
- try {
16227
- controller.signal.throwIfAborted();
16228
- return await Promise.race([operation(controller.signal), aborted]);
16229
- } finally {
16230
- clearTimer();
16231
- options.signal?.removeEventListener("abort", cancel);
16232
- controller.signal.removeEventListener("abort", onAbort);
16233
- controller.abort();
16234
- }
16235
- }
16236
16461
  async function metadata(url, fetch2, signal, headers) {
16237
16462
  signal.throwIfAborted();
16238
16463
  let payload;
@@ -16536,9 +16761,314 @@ function isObject2(value) {
16536
16761
  return typeof value === "object" && value !== null && !Array.isArray(value);
16537
16762
  }
16538
16763
 
16764
+ // src/local/llamaSwapResidency.ts
16765
+ function isObject3(value) {
16766
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16767
+ }
16768
+ function readPositiveSafeInteger(value) {
16769
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
16770
+ }
16771
+ function readSupportsTools2(model) {
16772
+ const direct = readBoolean2(model, [
16773
+ "tool_call",
16774
+ "tool_calls",
16775
+ "toolCalling",
16776
+ "supportsToolCalling",
16777
+ "supports_tools",
16778
+ "supportsTools"
16779
+ ]);
16780
+ if (direct !== undefined) {
16781
+ return direct;
16782
+ }
16783
+ const capabilities = readCapabilityBoolean2(model.capabilities);
16784
+ if (capabilities !== undefined) {
16785
+ return capabilities;
16786
+ }
16787
+ const features = readCapabilityBoolean2(model.features);
16788
+ if (features !== undefined) {
16789
+ return features;
16790
+ }
16791
+ const labels = readCapabilityBoolean2(model.labels);
16792
+ if (labels !== undefined) {
16793
+ return labels;
16794
+ }
16795
+ return "unknown";
16796
+ }
16797
+ function readCapabilityBoolean2(value) {
16798
+ if (Array.isArray(value)) {
16799
+ if (value.some((entry) => typeof entry === "string" && ["tools", "tool_call", "tool-calling", "function_calling"].includes(entry))) {
16800
+ return true;
16801
+ }
16802
+ return;
16803
+ }
16804
+ if (!isObject3(value)) {
16805
+ return;
16806
+ }
16807
+ return readBoolean2(value, [
16808
+ "tools",
16809
+ "tool_call",
16810
+ "tool_calls",
16811
+ "toolCalling",
16812
+ "function_calling",
16813
+ "supportsToolCalling"
16814
+ ]);
16815
+ }
16816
+ function readBoolean2(value, keys) {
16817
+ for (const key of keys) {
16818
+ if (typeof value[key] === "boolean") {
16819
+ return value[key];
16820
+ }
16821
+ }
16822
+ return;
16823
+ }
16824
+ function modelDetail(id, detail) {
16825
+ return llamaSwapResidencyMessages.catalogModelDetail(id, detail);
16826
+ }
16827
+ function llamaSwapBaseURL(value) {
16828
+ let url;
16829
+ try {
16830
+ url = new URL(value);
16831
+ } catch {
16832
+ throw new ModelResidencyError("destination", "llama-swap");
16833
+ }
16834
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") {
16835
+ throw new ModelResidencyError("destination", "llama-swap");
16836
+ }
16837
+ if (url.pathname === "/" || url.pathname === "") {
16838
+ return `${url.origin}/v1`;
16839
+ }
16840
+ if (url.pathname === "/v1" || url.pathname === "/v1/") {
16841
+ return `${url.origin}/v1`;
16842
+ }
16843
+ throw new ModelResidencyError("destination", "llama-swap");
16844
+ }
16845
+ function parseCanonicalType(entry, id) {
16846
+ if (!isObject3(entry.meta) || !isObject3(entry.meta.llamaswap)) {
16847
+ return;
16848
+ }
16849
+ const type = entry.meta.llamaswap.type;
16850
+ if (type === undefined) {
16851
+ return;
16852
+ }
16853
+ if (typeof type !== "string" || type.length === 0 || type !== type.trim()) {
16854
+ throw new ModelResidencyError("unknown", "llama-swap", modelDetail(id, llamaSwapResidencyMessages.invalidCatalogField("meta.llamaswap.type")));
16855
+ }
16856
+ return type === "model" ? "model" : undefined;
16857
+ }
16858
+ function readContextCandidates(entry) {
16859
+ const values = [
16860
+ readPositiveSafeInteger(entry.context_length),
16861
+ readPositiveSafeInteger(entry.context_window),
16862
+ isObject3(entry.meta) ? readPositiveSafeInteger(entry.meta.n_ctx) : undefined
16863
+ ];
16864
+ return values.filter((value) => value !== undefined);
16865
+ }
16866
+ function readDeclaredMaxOutput(entry, id) {
16867
+ if (!isObject3(entry.meta) || !isObject3(entry.meta.llamaswap)) {
16868
+ return;
16869
+ }
16870
+ if (!Object.hasOwn(entry.meta.llamaswap, "max_output_tokens")) {
16871
+ return;
16872
+ }
16873
+ const maxOutputTokens = readPositiveSafeInteger(entry.meta.llamaswap.max_output_tokens);
16874
+ if (maxOutputTokens === undefined) {
16875
+ throw new ModelResidencyError("unknown", "llama-swap", modelDetail(id, llamaSwapResidencyMessages.invalidCatalogField("meta.llamaswap.max_output_tokens")));
16876
+ }
16877
+ return maxOutputTokens;
16878
+ }
16879
+ function parseCatalogModel(value) {
16880
+ if (!isObject3(value) || typeof value.id !== "string") {
16881
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.catalogEntryMustExposeStringId);
16882
+ }
16883
+ const id = value.id;
16884
+ if (id.length === 0 || id !== id.trim()) {
16885
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.catalogModelIdsMustBeNonEmptyTrimmedStrings);
16886
+ }
16887
+ if (parseCanonicalType(value, id) !== "model") {
16888
+ return;
16889
+ }
16890
+ const model = {
16891
+ modelID: id,
16892
+ supportsTools: readSupportsTools2(value)
16893
+ };
16894
+ const contexts = readContextCandidates(value);
16895
+ if (contexts.length > 0) {
16896
+ model.contextWindow = Math.min(...contexts);
16897
+ model.contextWindowProvenance = "declared";
16898
+ }
16899
+ const maxOutputTokens = readDeclaredMaxOutput(value, id);
16900
+ if (maxOutputTokens !== undefined) {
16901
+ model.maxOutputTokens = maxOutputTokens;
16902
+ model.maxOutputTokensProvenance = "declared";
16903
+ }
16904
+ return model;
16905
+ }
16906
+ function parseCatalog(payload) {
16907
+ if (!isObject3(payload) || !Array.isArray(payload.data)) {
16908
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.expectedDataArray("/v1/models"));
16909
+ }
16910
+ const seen = new Set;
16911
+ const models = [];
16912
+ for (const entry of payload.data) {
16913
+ const model = parseCatalogModel(entry);
16914
+ if (model === undefined) {
16915
+ continue;
16916
+ }
16917
+ if (seen.has(model.modelID)) {
16918
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.duplicateCanonicalModelId(model.modelID, "/v1/models"));
16919
+ }
16920
+ seen.add(model.modelID);
16921
+ models.push(model);
16922
+ }
16923
+ return models;
16924
+ }
16925
+ async function metadata2(url, fetch2, signal, headers) {
16926
+ signal.throwIfAborted();
16927
+ try {
16928
+ const response = await fetch2(url, {
16929
+ method: "GET",
16930
+ headers,
16931
+ signal,
16932
+ redirect: "error",
16933
+ cache: "no-store"
16934
+ });
16935
+ signal.throwIfAborted();
16936
+ if (response.status === 401 || response.status === 403) {
16937
+ throw new ModelResidencyError("authentication-unsupported", "llama-swap");
16938
+ }
16939
+ if (!response.ok || response.redirected || response.url !== "" && response.url !== url) {
16940
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.httpFailure(new URL(url).pathname, response.status));
16941
+ }
16942
+ return await response.json();
16943
+ } catch (error) {
16944
+ signal.throwIfAborted();
16945
+ if (error instanceof ModelResidencyError) {
16946
+ throw error;
16947
+ }
16948
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.unverifiableResponse(new URL(url).pathname));
16949
+ }
16950
+ }
16951
+ function readProfiles(payload) {
16952
+ if (!isObject3(payload) || !Array.isArray(payload.profiles)) {
16953
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.expectedProfilesArray("/api/profiles"));
16954
+ }
16955
+ if (payload.active === null) {
16956
+ return;
16957
+ }
16958
+ if (typeof payload.active === "string" && payload.active.length > 0 && payload.active === payload.active.trim()) {
16959
+ throw new ModelResidencyError("identity", "llama-swap", llamaSwapResidencyMessages.activeProfileRewrite(payload.active));
16960
+ }
16961
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.invalidActiveProfileValue("/api/profiles"));
16962
+ }
16963
+ function readRunning(payload, modelIDs) {
16964
+ if (!isObject3(payload) || !Array.isArray(payload.running)) {
16965
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.expectedRunningArray("/running"));
16966
+ }
16967
+ const result = new Map;
16968
+ for (const modelID of modelIDs) {
16969
+ result.set(modelID, { loaded: false });
16970
+ }
16971
+ const seen = new Set;
16972
+ for (const entry of payload.running) {
16973
+ if (!isObject3(entry) || typeof entry.model !== "string" || entry.model.length === 0 || entry.model !== entry.model.trim() || typeof entry.state !== "string" || entry.state.length === 0 || entry.state !== entry.state.trim()) {
16974
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.malformedRunningEvidence("/running"));
16975
+ }
16976
+ if (seen.has(entry.model)) {
16977
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.duplicateRunningEvidence("/running", entry.model));
16978
+ }
16979
+ seen.add(entry.model);
16980
+ if (!modelIDs.has(entry.model)) {
16981
+ continue;
16982
+ }
16983
+ result.set(entry.model, { loaded: entry.state === "ready" });
16984
+ }
16985
+ return result;
16986
+ }
16987
+ function requestHeaders(headers) {
16988
+ const result = new Headers(headers);
16989
+ result.delete("content-type");
16990
+ result.delete("content-length");
16991
+ result.set("accept", "application/json");
16992
+ result.set("cache-control", "no-cache, no-store");
16993
+ result.delete("if-none-match");
16994
+ result.delete("if-modified-since");
16995
+ return result;
16996
+ }
16997
+ function llamaSwapResidencyOptions(options) {
16998
+ return { ...options, runtimeLabel: "llama-swap" };
16999
+ }
17000
+ async function readLlamaSwapCatalog(baseURL, fetch2, options = {}) {
17001
+ const base = llamaSwapBaseURL(baseURL);
17002
+ const headers = requestHeaders(options.headers);
17003
+ return withResidencyDeadline(async (signal) => parseCatalog(await metadata2(`${base}/models`, fetch2, signal, headers)), llamaSwapResidencyOptions(options));
17004
+ }
17005
+ async function readLlamaSwapResidency(baseURL, fetch2, options = {}) {
17006
+ const base = llamaSwapBaseURL(baseURL);
17007
+ const origin = new URL(base).origin;
17008
+ const headers = requestHeaders(options.headers);
17009
+ return withResidencyDeadline(async (signal) => {
17010
+ const models = await readLlamaSwapCatalog(base, fetch2, {
17011
+ ...llamaSwapResidencyOptions(options),
17012
+ signal
17013
+ });
17014
+ readProfiles(await metadata2(`${origin}/api/profiles`, fetch2, signal, headers));
17015
+ return readRunning(await metadata2(`${origin}/running`, fetch2, signal, headers), new Set(models.map((model) => model.modelID)));
17016
+ }, llamaSwapResidencyOptions(options));
17017
+ }
17018
+
17019
+ // src/local/llamaSwap.ts
17020
+ var LLAMA_SWAP_DEFAULT_BASE_URL = "http://localhost:8080/v1";
17021
+ function withLoadedState(models, residency) {
17022
+ return models.map((model) => {
17023
+ const loaded = residency.get(model.modelID)?.loaded;
17024
+ return loaded === undefined ? model : { ...model, loaded };
17025
+ });
17026
+ }
17027
+ function createLlamaSwapAdapter() {
17028
+ const listModels = async (options) => {
17029
+ const baseURL = llamaSwapBaseURL(options.baseURL ?? LLAMA_SWAP_DEFAULT_BASE_URL);
17030
+ const models = await readLlamaSwapCatalog(baseURL, options.fetch);
17031
+ try {
17032
+ return withLoadedState(models, await readLlamaSwapResidency(baseURL, options.fetch));
17033
+ } catch (error) {
17034
+ if (error instanceof ModelResidencyError && (error.code === "identity" || error.code === "unknown")) {
17035
+ return models;
17036
+ }
17037
+ throw error;
17038
+ }
17039
+ };
17040
+ return {
17041
+ id: "llama-swap",
17042
+ defaultBaseURL: LLAMA_SWAP_DEFAULT_BASE_URL,
17043
+ listModels,
17044
+ async probe(options) {
17045
+ const baseURL = llamaSwapBaseURL(options.baseURL ?? LLAMA_SWAP_DEFAULT_BASE_URL);
17046
+ try {
17047
+ return {
17048
+ id: "llama-swap",
17049
+ baseURL,
17050
+ reachable: true,
17051
+ models: await listModels({ ...options, baseURL }),
17052
+ probedAt: options.probedAt
17053
+ };
17054
+ } catch (error) {
17055
+ return unavailableSnapshot({
17056
+ id: "llama-swap",
17057
+ baseURL,
17058
+ probedAt: options.probedAt,
17059
+ error
17060
+ });
17061
+ }
17062
+ }
17063
+ };
17064
+ }
17065
+
16539
17066
  // src/local/modelResidency.ts
17067
+ function runtimeRequiresModelResidency(kind) {
17068
+ return kind === "lemonade" || kind === "llama-swap";
17069
+ }
16540
17070
  function hasRequiredModelResidency(kind, model) {
16541
- return kind !== "lemonade" || model?.loaded === true;
17071
+ return !runtimeRequiresModelResidency(kind) || model?.loaded === true;
16542
17072
  }
16543
17073
 
16544
17074
  // src/local/lmstudio.ts
@@ -16593,6 +17123,7 @@ class RuntimeRegistry {
16593
17123
  lmstudio: createLMStudioAdapter(),
16594
17124
  "foundry-local": createFoundryLocalAdapter(),
16595
17125
  lemonade: createLemonadeAdapter(),
17126
+ "llama-swap": createLlamaSwapAdapter(),
16596
17127
  ...options.adapters
16597
17128
  };
16598
17129
  this.clock = options.clock;
@@ -16730,6 +17261,12 @@ var DETECTION_RUNTIMES = [
16730
17261
  baseURL: LEMONADE_DEFAULT_BASE_URL,
16731
17262
  defaultModel: { providerID: "lemonade", modelID: "probe" }
16732
17263
  },
17264
+ {
17265
+ id: "llama-swap",
17266
+ enabled: true,
17267
+ baseURL: LLAMA_SWAP_DEFAULT_BASE_URL,
17268
+ defaultModel: { providerID: "llama-swap", modelID: "probe" }
17269
+ },
16733
17270
  {
16734
17271
  id: "foundry-local",
16735
17272
  enabled: true,
@@ -17228,7 +17765,7 @@ function runtimeLine(snapshot, runtime) {
17228
17765
  const mark = snapshot.reachable ? "✓" : "✗";
17229
17766
  const detail = snapshot.reachable ? `${snapshot.models.length} model(s)` : snapshot.error ?? "unreachable";
17230
17767
  const declared = runtime?.maxConcurrency;
17231
- const slots = declared === undefined ? ` · ${DEFAULT_LOCAL_MAX_CONCURRENCY} slot(s) (default)` : ` · ${declared} slot(s) (declared)`;
17768
+ const slots = declared === undefined ? doctorMessages.defaultRuntimeSlots(effectiveMaxConcurrency(runtime ?? {})) : ` · ${declared} slot(s) (declared)`;
17232
17769
  return ` ${mark} ${snapshot.id.padEnd(14)} ${snapshot.baseURL || "(no baseURL)"} — ${detail}${slots}`;
17233
17770
  }
17234
17771
  function runtimeModelSlotsLine(runtime, snapshot) {
@@ -17240,6 +17777,8 @@ function runtimeModelSlotsLine(runtime, snapshot) {
17240
17777
  if (runtime === undefined || snapshot === undefined || !snapshot.reachable || snapshot.models.length === 0) {
17241
17778
  return [];
17242
17779
  }
17780
+ if (localRuntimeKind(runtime) === "llama-swap")
17781
+ return [doctorMessages.llamaSwapConcurrency];
17243
17782
  const reported = snapshot.models.flatMap((model) => model.slots === undefined ? [] : [`${model.modelID} ${model.slots} slot(s)`]);
17244
17783
  if (reported.length > 0) {
17245
17784
  return [
@@ -17643,7 +18182,7 @@ function renderDoctor(input) {
17643
18182
  continue;
17644
18183
  }
17645
18184
  const runtime = enabledRuntimes.find((r) => r.id === snapshot.id);
17646
- const cap = runtime?.maxConcurrency ?? DEFAULT_LOCAL_MAX_CONCURRENCY;
18185
+ const cap = effectiveMaxConcurrency(runtime ?? {});
17647
18186
  lines.push(` ⚠ ${snapshot.id} is reached over the network: its ${cap}-slot cap is enforced per openteam process, so`, " two processes at once (an opencode session plus 'openteam console') can together exceed it.");
17648
18187
  }
17649
18188
  lines.push(` telemetry: ${input.telemetryPath} — ${input.telemetryRecords} record(s)`);
@@ -18046,7 +18585,7 @@ import { posix as pathPosix, win32 as pathWin32 } from "node:path";
18046
18585
  // package.json
18047
18586
  var package_default = {
18048
18587
  name: "@jmanuelcorral/openteam",
18049
- version: "0.24.1",
18588
+ version: "0.26.0",
18050
18589
  packageManager: "bun@1.3.14",
18051
18590
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
18052
18591
  license: "MIT",
@@ -18365,14 +18904,25 @@ function frontierModelsForProvider(profiles, providerID) {
18365
18904
  var OPENCODE_GITIGNORE_PATH = ".opencode/.gitignore";
18366
18905
  var DEFAULT_LOCAL_OUTPUT_TOKENS = 8192;
18367
18906
  var MIN_USABLE_INPUT_TOKENS = 1024;
18368
- function computeModelLimit(contextWindow, inputBudget) {
18907
+ function computeModelLimit(contextWindow, inputBudget, maxOutputTokens) {
18369
18908
  const context = contextWindow ?? 0;
18370
18909
  const contextBudget = context > 0 ? context : undefined;
18371
18910
  const validInputBudget = inputBudget !== undefined && inputBudget > 0 ? inputBudget : undefined;
18372
18911
  const effectiveBudget = contextBudget === undefined ? validInputBudget : validInputBudget === undefined ? contextBudget : Math.min(contextBudget, validInputBudget);
18373
- const output = effectiveBudget !== undefined && effectiveBudget - DEFAULT_LOCAL_OUTPUT_TOKENS < MIN_USABLE_INPUT_TOKENS ? Math.max(1, Math.floor(effectiveBudget / 2)) : DEFAULT_LOCAL_OUTPUT_TOKENS;
18912
+ const desired = Math.min(DEFAULT_LOCAL_OUTPUT_TOKENS, positiveInteger3(maxOutputTokens) ?? DEFAULT_LOCAL_OUTPUT_TOKENS);
18913
+ const output = Math.min(desired, effectiveBudget !== undefined && effectiveBudget - desired < MIN_USABLE_INPUT_TOKENS ? Math.max(1, Math.floor(effectiveBudget / 2)) : desired);
18374
18914
  return { context, output };
18375
18915
  }
18916
+ function collectMaxOutputTokens(modelInfo, usedModelIDs) {
18917
+ const used = new Set(usedModelIDs);
18918
+ const ceilings = {};
18919
+ for (const model of modelInfo ?? []) {
18920
+ const ceiling = positiveInteger3(model.maxOutputTokens);
18921
+ if (ceiling !== undefined && used.has(model.modelID))
18922
+ ceilings[model.modelID] = ceiling;
18923
+ }
18924
+ return Object.keys(ceilings).length > 0 ? ceilings : undefined;
18925
+ }
18376
18926
  function collectContextWindows(modelInfo, usedModelIDs) {
18377
18927
  if (modelInfo === undefined) {
18378
18928
  return;
@@ -18489,6 +19039,12 @@ var KNOWN_RUNTIMES = [
18489
19039
  defaultBaseURL: LEMONADE_DEFAULT_BASE_URL,
18490
19040
  fallbackModelID: "Qwen3-Coder-30B-A3B-Instruct-GGUF"
18491
19041
  },
19042
+ {
19043
+ id: "llama-swap",
19044
+ label: executionSetupMessages.llamaSwap.label,
19045
+ defaultBaseURL: LLAMA_SWAP_DEFAULT_BASE_URL,
19046
+ fallbackModelID: ""
19047
+ },
18492
19048
  {
18493
19049
  id: "foundry-local",
18494
19050
  label: "Foundry Local",
@@ -18496,7 +19052,7 @@ var KNOWN_RUNTIMES = [
18496
19052
  dynamicPort: true
18497
19053
  }
18498
19054
  ];
18499
- var LEMONADE_PROJECT_CONFIG_CANDIDATES = [
19055
+ var RESIDENT_PROJECT_CONFIG_CANDIDATES = [
18500
19056
  ".opencode/opencode.jsonc",
18501
19057
  ".opencode/opencode.json",
18502
19058
  "opencode.jsonc",
@@ -18518,6 +19074,19 @@ function choiceKind(choice) {
18518
19074
  function choiceProviderID(choice) {
18519
19075
  return choice.providerID ?? choice.id;
18520
19076
  }
19077
+ function residencySetupMessages(kind) {
19078
+ return kind === "llama-swap" ? executionSetupMessages.llamaSwap : {
19079
+ noLoadedModels: executionSetupMessages.lemonadeNoLoadedModels,
19080
+ modelNotLoaded: executionSetupMessages.lemonadeModelNotLoaded,
19081
+ probeRequired: executionSetupMessages.lemonadeProbeRequired,
19082
+ probeFailed: executionSetupMessages.lemonadeProbeFailed,
19083
+ endpointMismatch: executionSetupMessages.lemonadeEndpointMismatch,
19084
+ modelIdentityUnknown: executionSetupMessages.lemonadeModelIdentityUnknown
19085
+ };
19086
+ }
19087
+ function residencyBaseURL(kind, value) {
19088
+ return kind === "llama-swap" ? llamaSwapBaseURL(value ?? LLAMA_SWAP_DEFAULT_BASE_URL) : lemonadeBaseURL(value ?? LEMONADE_DEFAULT_BASE_URL);
19089
+ }
18521
19090
  function selectableModelIDs(models, modelInfo, kind) {
18522
19091
  const byID = new Map(modelInfo?.map((model) => [model.modelID, model]));
18523
19092
  return models.filter((id) => hasRequiredModelResidency(kind, byID.get(id)));
@@ -18535,7 +19104,7 @@ function assertRuntimeResidency(choice, primary) {
18535
19104
  continue;
18536
19105
  const info = choice.models.includes(id) ? choice.modelInfo?.find((model) => model.modelID === id) : undefined;
18537
19106
  if (!hasRequiredModelResidency(kind, info))
18538
- throw new Error(executionSetupMessages.lemonadeModelNotLoaded(`${choiceProviderID(choice)}/${id}`));
19107
+ throw new Error(residencySetupMessages(kind).modelNotLoaded(`${choiceProviderID(choice)}/${id}`));
18539
19108
  }
18540
19109
  }
18541
19110
  function assertSetupResidency(answers) {
@@ -18543,11 +19112,11 @@ function assertSetupResidency(answers) {
18543
19112
  if (choice.enabled) {
18544
19113
  assertRuntimeResidency(choice, answers.primaryModel);
18545
19114
  } else if (choiceProviderID(choice) === answers.primaryModel.providerID && !hasRequiredModelResidency(localRuntimeKind(choice), undefined)) {
18546
- throw new Error(executionSetupMessages.lemonadeModelNotLoaded(`${answers.primaryModel.providerID}/${answers.primaryModel.modelID}`));
19115
+ throw new Error(residencySetupMessages(localRuntimeKind(choice)).modelNotLoaded(`${answers.primaryModel.providerID}/${answers.primaryModel.modelID}`));
18547
19116
  }
18548
19117
  }
18549
19118
  }
18550
- function lemonadeV2Policies(providers) {
19119
+ function residentV2Policies(providers) {
18551
19120
  return [...new Set(providers)].sort().map((providerID) => {
18552
19121
  if (providerID.length === 0 || /[*?\\/\s]/.test(providerID))
18553
19122
  throw new Error(executionSetupMessages.lemonadeV2InvalidProvider(providerID));
@@ -18564,7 +19133,7 @@ function buildOpenTeamConfig(answers) {
18564
19133
  providerID: choiceProviderID(choice),
18565
19134
  modelID: choice.defaultModelID
18566
19135
  },
18567
- maxConcurrency: choice.maxConcurrency ?? DEFAULT_LOCAL_MAX_CONCURRENCY
19136
+ maxConcurrency: effectiveMaxConcurrency(choice)
18568
19137
  };
18569
19138
  if (choice.modelConcurrency !== undefined && Object.keys(choice.modelConcurrency).length > 0) {
18570
19139
  runtime.modelConcurrency = { ...choice.modelConcurrency };
@@ -18579,7 +19148,7 @@ function buildOpenTeamConfig(answers) {
18579
19148
  runtime.kind = choice.kind;
18580
19149
  }
18581
19150
  if (choice.baseURL !== undefined) {
18582
- runtime.baseURL = choice.baseURL;
19151
+ runtime.baseURL = localRuntimeKind(choice) === "llama-swap" ? llamaSwapBaseURL(choice.baseURL) : choice.baseURL;
18583
19152
  } else if (knownRuntime(choiceKind(choice)).dynamicPort === true) {
18584
19153
  runtime.discovery = "cli";
18585
19154
  }
@@ -18625,7 +19194,7 @@ function orchestratorAgentModel(answers) {
18625
19194
  }
18626
19195
  function buildOpencodeConfig(answers) {
18627
19196
  assertSetupResidency(answers);
18628
- const policies = lemonadeV2Policies(answers.runtimes.filter((choice) => choice.enabled && choiceKind(choice) === "lemonade").map(choiceProviderID));
19197
+ const policies = residentV2Policies(answers.runtimes.filter((choice) => choice.enabled && runtimeRequiresModelResidency(localRuntimeKind(choice))).map(choiceProviderID));
18629
19198
  const provider = {};
18630
19199
  const providerOwner = new Map;
18631
19200
  for (const choice of answers.runtimes) {
@@ -18633,7 +19202,8 @@ function buildOpencodeConfig(answers) {
18633
19202
  continue;
18634
19203
  }
18635
19204
  const meta = knownRuntime(choiceKind(choice));
18636
- const baseURL = choice.baseURL ?? meta.defaultBaseURL;
19205
+ const configuredBaseURL = choice.baseURL ?? meta.defaultBaseURL;
19206
+ const baseURL = meta.id === "llama-swap" ? llamaSwapBaseURL(configuredBaseURL ?? LLAMA_SWAP_DEFAULT_BASE_URL) : configuredBaseURL;
18637
19207
  if (baseURL === undefined) {
18638
19208
  continue;
18639
19209
  }
@@ -18652,7 +19222,7 @@ function buildOpencodeConfig(answers) {
18652
19222
  const models = {};
18653
19223
  for (const modelID of modelIDs) {
18654
19224
  const contextWindow = choice.modelContextWindows?.[modelID];
18655
- const limit = computeModelLimit(contextWindow);
19225
+ const limit = computeModelLimit(contextWindow, undefined, choice.modelMaxOutputTokens?.[modelID]);
18656
19226
  models[modelID] = { name: modelID, limit };
18657
19227
  }
18658
19228
  const owner = providerOwner.get(providerID);
@@ -18833,8 +19403,8 @@ function preferredModelID(models, modelInfo, kind) {
18833
19403
  async function chooseRuntimeModel(prompt, models, meta, modelInfo) {
18834
19404
  const offered = selectableModelIDs(models, modelInfo, meta.id);
18835
19405
  if (offered.length === 0) {
18836
- if (meta.id === "lemonade")
18837
- throw new Error(executionSetupMessages.lemonadeNoLoadedModels);
19406
+ if (runtimeRequiresModelResidency(meta.id))
19407
+ throw new Error(residencySetupMessages(meta.id).noLoadedModels);
18838
19408
  return prompt.text({
18839
19409
  message: `Default model for ${meta.label} (no models detected)`,
18840
19410
  placeholder: meta.fallbackModelID,
@@ -18850,8 +19420,8 @@ async function chooseRuntimeModel(prompt, models, meta, modelInfo) {
18850
19420
  opts.initial = first;
18851
19421
  }
18852
19422
  const selected = await prompt.select(opts);
18853
- if (meta.id === "lemonade" && !offered.includes(selected))
18854
- throw new Error(executionSetupMessages.lemonadeModelNotLoaded(selected));
19423
+ if (runtimeRequiresModelResidency(meta.id) && !offered.includes(selected))
19424
+ throw new Error(residencySetupMessages(meta.id).modelNotLoaded(selected));
18855
19425
  return selected;
18856
19426
  }
18857
19427
  function remoteBaseURLExample(meta) {
@@ -18907,28 +19477,31 @@ async function resolveRuntimeConnection(deps, meta, info) {
18907
19477
  initial: RUNTIME_LOCATION_LOCAL
18908
19478
  });
18909
19479
  if (location === RUNTIME_LOCATION_LOCAL) {
18910
- if (meta.id === "lemonade" && info?.reachable !== true)
18911
- throw new Error(executionSetupMessages.lemonadeProbeFailed);
19480
+ if (runtimeRequiresModelResidency(meta.id) && info?.reachable !== true)
19481
+ throw new Error(info?.error ?? residencySetupMessages(meta.id).probeFailed);
18912
19482
  const baseURL2 = info?.baseURL !== undefined && info.baseURL.length > 0 ? info.baseURL : meta.defaultBaseURL;
18913
- return { baseURL: baseURL2, ...connectionOf(info) };
19483
+ return {
19484
+ baseURL: meta.id === "llama-swap" ? llamaSwapBaseURL(baseURL2) : baseURL2,
19485
+ ...connectionOf(info)
19486
+ };
18914
19487
  }
18915
19488
  const entered = await deps.prompt.text({
18916
19489
  message: `${meta.label} URL on the network (OpenAI-compatible endpoint)`,
18917
19490
  placeholder: remoteBaseURLExample(meta)
18918
19491
  });
18919
- const baseURL = normalizeRemoteBaseURL(entered, meta.defaultBaseURL);
19492
+ const baseURL = meta.id === "llama-swap" ? llamaSwapBaseURL(entered) : normalizeRemoteBaseURL(entered, meta.defaultBaseURL);
18920
19493
  let probedRuntime;
18921
- if (meta.id === "lemonade" && deps.probeRuntime === undefined)
18922
- throw new Error(executionSetupMessages.lemonadeProbeRequired);
19494
+ if (runtimeRequiresModelResidency(meta.id) && deps.probeRuntime === undefined)
19495
+ throw new Error(residencySetupMessages(meta.id).probeRequired);
18923
19496
  if (deps.probeRuntime !== undefined) {
18924
19497
  try {
18925
19498
  probedRuntime = await deps.probeRuntime(meta.id, baseURL);
18926
19499
  } catch (cause) {
18927
- if (meta.id === "lemonade")
18928
- throw new Error(executionSetupMessages.lemonadeProbeFailed, { cause });
19500
+ if (runtimeRequiresModelResidency(meta.id))
19501
+ throw new Error(residencySetupMessages(meta.id).probeFailed, { cause });
18929
19502
  }
18930
- if (meta.id === "lemonade" && probedRuntime?.reachable !== true)
18931
- throw new Error(executionSetupMessages.lemonadeProbeFailed);
19503
+ if (runtimeRequiresModelResidency(meta.id) && probedRuntime?.reachable !== true)
19504
+ throw new Error(probedRuntime?.error ?? residencySetupMessages(meta.id).probeFailed);
18932
19505
  }
18933
19506
  return { baseURL, ...connectionOf(probedRuntime) };
18934
19507
  }
@@ -18936,8 +19509,8 @@ async function chooseClassModel(prompt, models, meta, defaultModelID, modelClass
18936
19509
  const label = modelClass === "thinking" ? `Thinking model for ${meta.label} (design, analysis, review reasoning)` : `Coder model for ${meta.label} (writing and editing code)`;
18937
19510
  const offered = selectableModelIDs(models, modelInfo, meta.id);
18938
19511
  if (offered.length === 0) {
18939
- if (meta.id === "lemonade")
18940
- throw new Error(executionSetupMessages.lemonadeNoLoadedModels);
19512
+ if (runtimeRequiresModelResidency(meta.id))
19513
+ throw new Error(residencySetupMessages(meta.id).noLoadedModels);
18941
19514
  const raw = await prompt.text({
18942
19515
  message: `${label} — enter to reuse the default`,
18943
19516
  placeholder: defaultModelID,
@@ -18953,8 +19526,8 @@ async function chooseClassModel(prompt, models, meta, defaultModelID, modelClass
18953
19526
  })),
18954
19527
  initial: defaultModelID
18955
19528
  });
18956
- if (meta.id === "lemonade" && !offered.includes(selected))
18957
- throw new Error(executionSetupMessages.lemonadeModelNotLoaded(selected));
19529
+ if (runtimeRequiresModelResidency(meta.id) && !offered.includes(selected))
19530
+ throw new Error(residencySetupMessages(meta.id).modelNotLoaded(selected));
18958
19531
  return selected;
18959
19532
  }
18960
19533
  async function resolveEnabledRuntime(deps, meta, info) {
@@ -18991,6 +19564,16 @@ async function resolveEnabledRuntime(deps, meta, info) {
18991
19564
  if (modelContextWindows !== undefined) {
18992
19565
  choice.modelContextWindows = modelContextWindows;
18993
19566
  }
19567
+ const modelMaxOutputTokens = collectMaxOutputTokens(connection.modelInfo, [
19568
+ defaultModelID,
19569
+ thinkingModelID,
19570
+ coderModelID,
19571
+ ...connection.models
19572
+ ]);
19573
+ if (modelMaxOutputTokens !== undefined)
19574
+ choice.modelMaxOutputTokens = modelMaxOutputTokens;
19575
+ if (meta.id === "llama-swap")
19576
+ deps.prompt.note(executionSetupMessages.llamaSwap.declaredLimits, executionSetupMessages.llamaSwap.declaredLimitsTitle);
18994
19577
  return choice;
18995
19578
  }
18996
19579
  function reportedModelSlots(modelInfo, usedModelIDs) {
@@ -19017,11 +19600,11 @@ async function promptSlotCount(prompt, meta, reported) {
19017
19600
  }
19018
19601
  const raw = await prompt.text({
19019
19602
  message: `How many concurrent generation slots does ${meta.label} have? (match its real capacity; leaving one free keeps interactive work responsive)`,
19020
- placeholder: String(DEFAULT_LOCAL_MAX_CONCURRENCY),
19021
- initial: String(DEFAULT_LOCAL_MAX_CONCURRENCY)
19603
+ placeholder: String(effectiveMaxConcurrency({ id: meta.id })),
19604
+ initial: String(effectiveMaxConcurrency({ id: meta.id }))
19022
19605
  });
19023
19606
  const parsed = Number.parseInt(raw.trim(), 10);
19024
- return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_LOCAL_MAX_CONCURRENCY;
19607
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : effectiveMaxConcurrency({ id: meta.id });
19025
19608
  }
19026
19609
  function reportedSlotSummary(reported) {
19027
19610
  const entries = Object.entries(reported ?? {});
@@ -19080,6 +19663,29 @@ async function resolveRuntimeInstances(deps, meta, info, usedIds) {
19080
19663
  function isRecord6(value) {
19081
19664
  return typeof value === "object" && value !== null && !Array.isArray(value);
19082
19665
  }
19666
+ function setupOutputCeilings(answers, existing) {
19667
+ const result = {};
19668
+ const providers = recordAt2(existing, "provider");
19669
+ for (const choice of answers.runtimes.filter((runtime) => runtime.enabled)) {
19670
+ const physical = choice.modelMaxOutputTokens;
19671
+ if (physical === undefined)
19672
+ continue;
19673
+ const providerID = choiceProviderID(choice);
19674
+ const ceilings = { ...physical };
19675
+ const models = recordAt2(recordAt2(providers, providerID), "models");
19676
+ for (const [alias, value] of Object.entries(models ?? {})) {
19677
+ if (!isRecord6(value))
19678
+ continue;
19679
+ const id = typeof value.id === "string" ? value.id : alias;
19680
+ delete ceilings[alias];
19681
+ const ceiling = physical[id];
19682
+ if (ceiling !== undefined)
19683
+ ceilings[alias] = ceiling;
19684
+ }
19685
+ result[providerID] = ceilings;
19686
+ }
19687
+ return result;
19688
+ }
19083
19689
  var LIMIT_FIELDS = [
19084
19690
  "context",
19085
19691
  "input",
@@ -19569,7 +20175,7 @@ function readModelLimitValues(limit) {
19569
20175
  function mergeModelLimitForRerun(input) {
19570
20176
  const target = readModelLimitValues(input.targetLimit);
19571
20177
  const effective = readModelLimitValues(input.effectiveLimit);
19572
- const computed = computeModelLimit(effective.context ?? input.generatedLimit?.context, effective.input);
20178
+ const computed = computeModelLimit(effective.context ?? input.generatedLimit?.context, effective.input, input.maxOutputTokens);
19573
20179
  const merged = { ...input.targetLimit ?? {} };
19574
20180
  const targetOutputIsGeneratedDefault = target.output === DEFAULT_LOCAL_OUTPUT_TOKENS;
19575
20181
  if (targetOutputIsGeneratedDefault && effective.output !== undefined && effective.output !== DEFAULT_LOCAL_OUTPUT_TOKENS) {
@@ -19583,6 +20189,8 @@ function mergeModelLimitForRerun(input) {
19583
20189
  } else {
19584
20190
  merged.output = computed.output;
19585
20191
  }
20192
+ if (input.maxOutputTokens !== undefined && typeof merged.output === "number")
20193
+ merged.output = Math.min(merged.output, input.maxOutputTokens);
19586
20194
  if (target.context !== undefined) {
19587
20195
  merged.context = target.context;
19588
20196
  } else if (effective.context !== undefined) {
@@ -19602,7 +20210,8 @@ function completePhysicalModelLimit(input) {
19602
20210
  const target = readModelLimitValues(input.targetLimit);
19603
20211
  const effective = readModelLimitValues(input.effectiveLimit);
19604
20212
  const context = target.context ?? effective.context ?? input.generatedLimit?.context ?? 0;
19605
- const output = target.output ?? effective.output ?? computeModelLimit(context, effective.input).output;
20213
+ const requestedOutput = target.output ?? effective.output ?? computeModelLimit(context, effective.input, input.maxOutputTokens).output;
20214
+ const output = input.maxOutputTokens === undefined ? requestedOutput : Math.min(requestedOutput, input.maxOutputTokens);
19606
20215
  return { ...input.targetLimit, context, output };
19607
20216
  }
19608
20217
  function repairManagedModelLimits(input) {
@@ -19623,16 +20232,18 @@ function repairManagedModelLimits(input) {
19623
20232
  let providerChanged = false;
19624
20233
  const repairedModels = { ...targetModels };
19625
20234
  for (const [modelID, modelValue] of Object.entries(targetModels)) {
19626
- if (!isRecord6(modelValue) || !hasOwn2(modelValue, "limit")) {
20235
+ const ceiling = input.modelOutputCeilings?.[providerID]?.[modelID];
20236
+ if (!isRecord6(modelValue) || !hasOwn2(modelValue, "limit") && ceiling === undefined) {
19627
20237
  continue;
19628
20238
  }
19629
- const targetLimit = recordAt2(modelValue, "limit");
20239
+ const targetLimit = recordAt2(modelValue, "limit") ?? (ceiling === undefined ? undefined : {});
19630
20240
  if (targetLimit === undefined) {
19631
20241
  continue;
19632
20242
  }
19633
20243
  const effectiveLimit = recordAt2(recordAt2(effectiveModels, modelID), "limit");
19634
20244
  const generatedLimit = generatedModelLimit(input.generated, providerID, modelID);
19635
20245
  const repairedLimit = completePhysicalModelLimit({
20246
+ maxOutputTokens: ceiling,
19636
20247
  ...generatedLimit !== undefined ? { generatedLimit } : {},
19637
20248
  targetLimit,
19638
20249
  ...effectiveLimit !== undefined ? { effectiveLimit } : {}
@@ -19691,7 +20302,7 @@ function highestExistingPinVersion(files) {
19691
20302
  }
19692
20303
  return highest;
19693
20304
  }
19694
- function reconcileLemonadeV2Policies(raw, denials) {
20305
+ function reconcileResidentV2Policies(raw, denials) {
19695
20306
  if (denials.length === 0)
19696
20307
  return raw;
19697
20308
  const experimental = raw.experimental;
@@ -19750,6 +20361,7 @@ function mergeOpencodeConfigForRerun(generated, targetRaw, options) {
19750
20361
  const targetLimit = recordAt2(targetModel, "limit");
19751
20362
  const effectiveLimit = recordAt2(effectiveModel, "limit");
19752
20363
  const mergedLimit = mergeModelLimitForRerun({
20364
+ maxOutputTokens: options.modelOutputCeilings?.[providerID]?.[modelID],
19753
20365
  ...modelConfig.limit !== undefined ? { generatedLimit: modelConfig.limit } : {},
19754
20366
  ...targetLimit !== undefined ? { targetLimit } : {},
19755
20367
  ...effectiveLimit !== undefined ? { effectiveLimit } : {}
@@ -19792,7 +20404,7 @@ function mergeOpencodeConfigForRerun(generated, targetRaw, options) {
19792
20404
  if (managesV2Policies && hasOwn2(targetRaw, "experimental")) {
19793
20405
  result.experimental = targetRaw.experimental;
19794
20406
  }
19795
- return reconcileLemonadeV2Policies(result, generated.experimental?.policies ?? []);
20407
+ return reconcileResidentV2Policies(result, generated.experimental?.policies ?? []);
19796
20408
  }
19797
20409
  function mergeOpenTeamConfigForRerun(generated, existing) {
19798
20410
  const existingOrch = existing.orchestrator;
@@ -19842,9 +20454,9 @@ function mergeOpenTeamConfigForRerun(generated, existing) {
19842
20454
  assertEffectiveConfigModelDomains(merged);
19843
20455
  return merged;
19844
20456
  }
19845
- function preserveRetainedLemonadeRuntimes(generated, previous, opencode) {
20457
+ function preserveRetainedResidentRuntimes(generated, previous, opencode) {
19846
20458
  const providers = recordAt2(opencode, "provider") ?? {};
19847
- const retained = (previous?.local.runtimes ?? []).filter((runtime) => localRuntimeKind(runtime) === "lemonade" && hasOwn2(providers, runtime.defaultModel.providerID));
20459
+ const retained = (previous?.local.runtimes ?? []).filter((runtime) => runtimeRequiresModelResidency(localRuntimeKind(runtime)) && hasOwn2(providers, runtime.defaultModel.providerID));
19848
20460
  const deselected = retained.filter((runtime) => !generated.local.runtimes.some((selected) => selected.defaultModel.providerID === runtime.defaultModel.providerID)).map((runtime) => ({ ...runtime, enabled: false }));
19849
20461
  return {
19850
20462
  config: deselected.length === 0 ? generated : OpenTeamConfigSchema.parse({
@@ -19884,31 +20496,33 @@ async function confirmJsonNormalization(prompt, path2, format) {
19884
20496
  initial: false
19885
20497
  });
19886
20498
  }
19887
- function assertPreviousLemonadePrimary(answers, previousConfig) {
19888
- const priorLemonadePrimary = previousConfig?.local.runtimes.some((runtime) => runtime.defaultModel.providerID === answers.primaryModel.providerID && localRuntimeKind(runtime) === "lemonade");
19889
- if (priorLemonadePrimary && !answers.runtimes.some((choice) => choice.enabled && choiceProviderID(choice) === answers.primaryModel.providerID)) {
19890
- throw new Error(executionSetupMessages.lemonadeModelNotLoaded(`${answers.primaryModel.providerID}/${answers.primaryModel.modelID}`));
20499
+ function assertPreviousResidentPrimary(answers, previousConfig) {
20500
+ const priorResidentPrimary = previousConfig?.local.runtimes.find((runtime) => runtime.defaultModel.providerID === answers.primaryModel.providerID && runtimeRequiresModelResidency(localRuntimeKind(runtime)));
20501
+ if (priorResidentPrimary && !answers.runtimes.some((choice) => choice.enabled && choiceProviderID(choice) === answers.primaryModel.providerID)) {
20502
+ throw new Error(residencySetupMessages(localRuntimeKind(priorResidentPrimary)).modelNotLoaded(`${answers.primaryModel.providerID}/${answers.primaryModel.modelID}`));
19891
20503
  }
19892
20504
  }
19893
- async function verifyLemonadeBeforeWrite(deps, answers, config, opencode) {
20505
+ async function verifyResidencyBeforeWrite(deps, answers, config, opencode) {
19894
20506
  for (const choice of answers.runtimes) {
19895
- if (choice.enabled && localRuntimeKind(choice) === "lemonade" && !config.local.runtimes.some((runtime) => runtime.enabled && runtime.defaultModel.providerID === choiceProviderID(choice) && localRuntimeKind(runtime) === "lemonade"))
19896
- throw new Error(executionSetupMessages.lemonadeEndpointMismatch);
20507
+ if (choice.enabled && runtimeRequiresModelResidency(localRuntimeKind(choice)) && !config.local.runtimes.some((runtime) => runtime.enabled && runtime.defaultModel.providerID === choiceProviderID(choice) && localRuntimeKind(runtime) === localRuntimeKind(choice)))
20508
+ throw new Error(residencySetupMessages(localRuntimeKind(choice)).endpointMismatch);
19897
20509
  }
19898
20510
  for (const runtime of config.local.runtimes) {
19899
- if (localRuntimeKind(runtime) !== "lemonade")
20511
+ const kind = localRuntimeKind(runtime);
20512
+ if (!runtimeRequiresModelResidency(kind))
19900
20513
  continue;
20514
+ const messages = residencySetupMessages(kind);
19901
20515
  const providerID = runtime.defaultModel.providerID;
19902
20516
  const provider = isRecord6(opencode.provider) ? opencode.provider[providerID] : undefined;
19903
20517
  if (!runtime.enabled && provider === undefined)
19904
20518
  continue;
19905
20519
  const endpoint = isRecord6(provider) ? (isRecord6(provider.options) ? provider.options.baseURL : undefined) ?? provider.api : undefined;
19906
- if (typeof endpoint !== "string" || lemonadeBaseURL(endpoint) !== lemonadeBaseURL(runtime.baseURL ?? LEMONADE_DEFAULT_BASE_URL) || config.local.runtimes.filter((candidate) => candidate.defaultModel.providerID === providerID).length !== 1)
19907
- throw new Error(executionSetupMessages.lemonadeEndpointMismatch);
20520
+ if (typeof endpoint !== "string" || residencyBaseURL(kind, endpoint) !== residencyBaseURL(kind, runtime.baseURL) || config.local.runtimes.filter((candidate) => candidate.defaultModel.providerID === providerID).length !== 1)
20521
+ throw new Error(messages.endpointMismatch);
19908
20522
  if (!runtime.enabled)
19909
20523
  continue;
19910
20524
  if (deps.probeRuntime === undefined)
19911
- throw new Error(executionSetupMessages.lemonadeProbeRequired);
20525
+ throw new Error(messages.probeRequired);
19912
20526
  const references2 = new Set([
19913
20527
  runtime.defaultModel.modelID,
19914
20528
  runtime.thinkingModel,
@@ -19946,21 +20560,30 @@ async function verifyLemonadeBeforeWrite(deps, answers, config, opencode) {
19946
20560
  const declaration = recordAt2(models, alias);
19947
20561
  const wireID = declaration !== undefined && hasOwn2(declaration, "id") ? declaration.id : alias;
19948
20562
  if (declaration === undefined || typeof wireID !== "string" || wireID.length === 0 || wireID !== wireID.trim())
19949
- throw new Error(executionSetupMessages.lemonadeModelIdentityUnknown(`${providerID}/${alias}`));
20563
+ throw new Error(messages.modelIdentityUnknown(`${providerID}/${alias}`));
19950
20564
  wireIDs.add(wireID);
19951
20565
  }
19952
20566
  let fresh;
19953
20567
  try {
19954
20568
  fresh = await deps.probeRuntime(localRuntimeKind(runtime), endpoint);
19955
20569
  } catch (cause) {
19956
- throw new Error(executionSetupMessages.lemonadeProbeFailed, { cause });
20570
+ throw new Error(messages.probeFailed, { cause });
19957
20571
  }
19958
- if (!fresh.reachable || fresh.id !== localRuntimeKind(runtime) || fresh.baseURL.replace(/\/+$/, "") !== endpoint.replace(/\/+$/, ""))
19959
- throw new Error(executionSetupMessages.lemonadeProbeFailed);
20572
+ if (!fresh.reachable || fresh.id !== localRuntimeKind(runtime) || residencyBaseURL(kind, fresh.baseURL) !== residencyBaseURL(kind, endpoint))
20573
+ throw new Error(fresh.error ?? messages.probeFailed);
19960
20574
  for (const wireID of wireIDs) {
19961
20575
  const info = fresh.models.includes(wireID) ? fresh.modelInfo?.find((model) => model.modelID === wireID) : undefined;
19962
- if (!hasRequiredModelResidency("lemonade", info))
19963
- throw new Error(executionSetupMessages.lemonadeModelNotLoaded(`${providerID}/${wireID}`));
20576
+ if (!hasRequiredModelResidency(kind, info))
20577
+ throw new Error(messages.modelNotLoaded(`${providerID}/${wireID}`));
20578
+ }
20579
+ for (const [alias, declaration] of Object.entries(models ?? {})) {
20580
+ if (!isRecord6(declaration))
20581
+ continue;
20582
+ const wireID = typeof declaration.id === "string" ? declaration.id : alias;
20583
+ const ceiling = fresh.modelInfo?.find((model) => model.modelID === wireID)?.maxOutputTokens;
20584
+ const output = positiveInteger3(recordAt2(declaration, "limit")?.output);
20585
+ if (ceiling !== undefined && output !== undefined && output > ceiling)
20586
+ throw new Error(executionSetupMessages.llamaSwap.outputChanged(`${providerID}/${alias}`, ceiling));
19964
20587
  }
19965
20588
  }
19966
20589
  }
@@ -19975,7 +20598,7 @@ async function runSetup(deps) {
19975
20598
  return {
19976
20599
  value: meta.id,
19977
20600
  label: meta.label,
19978
- hint: info?.reachable ? meta.id === "lemonade" ? executionSetupMessages.lemonadeDetectedHint(selectableModelIDs(info.models, info.modelInfo, meta.id).length, info.modelInfo?.length ?? info.models.length) : `detected · ${info.models.length} model(s)` : "not detected (you can point it to another machine)",
20601
+ hint: info?.reachable ? runtimeRequiresModelResidency(meta.id) ? executionSetupMessages.lemonadeDetectedHint(selectableModelIDs(info.models, info.modelInfo, meta.id).length, info.modelInfo?.length ?? info.models.length) : `detected · ${info.models.length} model(s)` : "not detected (you can point it to another machine)",
19979
20602
  selected: info?.reachable ?? false
19980
20603
  };
19981
20604
  });
@@ -20092,7 +20715,7 @@ async function runSetup(deps) {
20092
20715
  };
20093
20716
  const openTeamConfig = buildOpenTeamConfig(answers);
20094
20717
  const opencodeConfig = buildOpencodeConfig(answers);
20095
- const opencodeConfigPaths = LEMONADE_PROJECT_CONFIG_CANDIDATES;
20718
+ const opencodeConfigPaths = RESIDENT_PROJECT_CONFIG_CANDIDATES;
20096
20719
  const orchestratorAgent = buildOrchestratorAgent(orchestratorAgentModel(answers), { yolo });
20097
20720
  const slashCommands = buildOpenteamCommands();
20098
20721
  const openTeamPath = DEFAULT_CONFIG_PATH;
@@ -20113,29 +20736,44 @@ async function runSetup(deps) {
20113
20736
  }
20114
20737
  throw error;
20115
20738
  }
20116
- assertPreviousLemonadePrimary(answers, existingOpenTeamConfig);
20739
+ assertPreviousResidentPrimary(answers, existingOpenTeamConfig);
20117
20740
  const effectiveExistingOpencode = mergeExistingOpencodeConfigFiles(existingOpencodeFiles, opencodeConfigPaths);
20741
+ const modelOutputCeilings = setupOutputCeilings(answers, effectiveExistingOpencode);
20742
+ const reductions = new Set;
20743
+ for (const file of existingOpencodeFiles) {
20744
+ for (const [providerID, ceilings] of Object.entries(modelOutputCeilings)) {
20745
+ const models = recordAt2(recordAt2(recordAt2(file.raw, "provider"), providerID), "models");
20746
+ for (const [modelID, ceiling] of Object.entries(ceilings)) {
20747
+ const output = positiveInteger3(recordAt2(recordAt2(models, modelID), "limit")?.output);
20748
+ if (output !== undefined && output > ceiling)
20749
+ reductions.add(executionSetupMessages.llamaSwap.outputReduction(`${providerID}/${modelID}`, output, ceiling));
20750
+ }
20751
+ }
20752
+ }
20753
+ if (reductions.size > 0)
20754
+ prompt.note([...reductions].join(`
20755
+ `), executionSetupMessages.llamaSwap.outputReductionTitle);
20118
20756
  const {
20119
20757
  config: finalOpenTeamConfig,
20120
- providerIDs: retainedLemonadeProviderIDs
20121
- } = preserveRetainedLemonadeRuntimes(existingOpenTeamConfig !== undefined ? mergeOpenTeamConfigForRerun(openTeamConfig, existingOpenTeamConfig) : openTeamConfig, existingOpenTeamConfig, effectiveExistingOpencode);
20758
+ providerIDs: retainedResidentProviderIDs
20759
+ } = preserveRetainedResidentRuntimes(existingOpenTeamConfig !== undefined ? mergeOpenTeamConfigForRerun(openTeamConfig, existingOpenTeamConfig) : openTeamConfig, existingOpenTeamConfig, effectiveExistingOpencode);
20122
20760
  assertEffectiveConfigModelDomains(finalOpenTeamConfig);
20123
20761
  const effectiveOpenTeamConfig = await effectiveSetupConfig(finalOpenTeamConfig, existingOpencodeFiles, deps);
20124
20762
  assertEffectiveConfigModelDomains(effectiveOpenTeamConfig);
20125
- for (const providerID of retainedLemonadeProviderIDs) {
20763
+ for (const providerID of retainedResidentProviderIDs) {
20126
20764
  const matches = effectiveOpenTeamConfig.local.runtimes.filter((runtime) => runtime.defaultModel.providerID === providerID);
20127
- if (matches.length !== 1 || matches[0] === undefined || localRuntimeKind(matches[0]) !== "lemonade")
20128
- throw new Error(executionSetupMessages.lemonadeRetainedProviderUnprotected(providerID));
20765
+ if (matches.length !== 1 || matches[0] === undefined || !runtimeRequiresModelResidency(localRuntimeKind(matches[0])))
20766
+ throw new Error(finalOpenTeamConfig.local.runtimes.some((runtime) => runtime.defaultModel.providerID === providerID && localRuntimeKind(runtime) === "lemonade") ? executionSetupMessages.lemonadeRetainedProviderUnprotected(providerID) : executionSetupMessages.retainedResidencyProviderUnprotected(providerID));
20129
20767
  }
20130
- const v2Denials = lemonadeV2Policies([
20768
+ const v2Denials = residentV2Policies([
20131
20769
  ...opencodeConfig.experimental?.policies.map((policy) => policy.resource) ?? [],
20132
- ...effectiveOpenTeamConfig.local.runtimes.filter((runtime) => localRuntimeKind(runtime) === "lemonade").map((runtime) => runtime.defaultModel.providerID)
20770
+ ...effectiveOpenTeamConfig.local.runtimes.filter((runtime) => runtimeRequiresModelResidency(localRuntimeKind(runtime))).map((runtime) => runtime.defaultModel.providerID)
20133
20771
  ]);
20134
20772
  if (v2Denials.length > 0)
20135
20773
  opencodeConfig.experimental = { policies: v2Denials };
20136
- const v2Notice = v2Denials.length > 0 ? executionSetupMessages.lemonadeV2Restriction(v2Denials.map((policy) => policy.resource)) : undefined;
20774
+ const v2Notice = v2Denials.length > 0 ? executionSetupMessages.residencyV2Restriction(v2Denials.map((policy) => policy.resource)) : undefined;
20137
20775
  if (v2Notice !== undefined)
20138
- prompt.note(v2Notice, executionSetupMessages.lemonadeV2Title);
20776
+ prompt.note(v2Notice, executionSetupMessages.residencyV2Title);
20139
20777
  const relevantProviderIDs = relevantLocalProviderIDs(opencodeConfig, existingOpenTeamConfig);
20140
20778
  for (const file of existingOpencodeFiles) {
20141
20779
  assertRelevantLimitNumbers(file, relevantProviderIDs);
@@ -20166,12 +20804,14 @@ async function runSetup(deps) {
20166
20804
  const includePluginInDataFile = pluginOwnerPath === opencodeDataPath;
20167
20805
  const dataTargetRaw = opencodeFileAt(existingOpencodeFiles, opencodeDataPath)?.raw ?? {};
20168
20806
  const mergedOpencodeRaw = mergeOpencodeConfigForRerun(opencodeConfig, dataTargetRaw, {
20807
+ modelOutputCeilings,
20169
20808
  ...effectiveExistingOpencode !== undefined ? { effectiveRaw: effectiveExistingOpencode } : {},
20170
20809
  includePlugin: includePluginInDataFile,
20171
20810
  ...includePluginInDataFile ? { pluginEntries: pluginPlan.plugins } : {},
20172
20811
  yolo
20173
20812
  });
20174
20813
  const repairedDataTarget = repairManagedModelLimits({
20814
+ modelOutputCeilings,
20175
20815
  raw: mergedOpencodeRaw,
20176
20816
  effectiveRaw: effectiveExistingOpencode,
20177
20817
  generated: opencodeConfig,
@@ -20185,12 +20825,13 @@ async function runSetup(deps) {
20185
20825
  continue;
20186
20826
  }
20187
20827
  const repaired = repairManagedModelLimits({
20828
+ modelOutputCeilings,
20188
20829
  raw: file.raw,
20189
20830
  effectiveRaw: effectiveExistingOpencode,
20190
20831
  generated: opencodeConfig,
20191
20832
  providerIDs: relevantProviderIDs
20192
20833
  });
20193
- let plannedRaw = reconcileLemonadeV2Policies(repaired.raw, v2Denials);
20834
+ let plannedRaw = reconcileResidentV2Policies(repaired.raw, v2Denials);
20194
20835
  let changed = repaired.changed || plannedRaw !== repaired.raw;
20195
20836
  if (file.path === pluginOwnerPath && !includePluginInDataFile && pluginPlan.changed) {
20196
20837
  plannedRaw = { ...plannedRaw, plugin: [...pluginPlan.plugins] };
@@ -20264,7 +20905,7 @@ async function runSetup(deps) {
20264
20905
  const effectivePlannedOpencode = mergeExistingOpencodeConfigFiles(existingOpencodeFiles, opencodeConfigPaths, opencodeWritePlan);
20265
20906
  if (effectivePlannedOpencode === undefined)
20266
20907
  throw new Error(executionSetupMessages.lemonadeEndpointMismatch);
20267
- await verifyLemonadeBeforeWrite(deps, answers, effectiveOpenTeamConfig, effectivePlannedOpencode);
20908
+ await verifyResidencyBeforeWrite(deps, answers, effectiveOpenTeamConfig, effectivePlannedOpencode);
20268
20909
  await deps.writeFile(openTeamPath, serializeOpenTeamConfig(finalOpenTeamConfig));
20269
20910
  for (const [path2, contents] of opencodeWritePlan) {
20270
20911
  await deps.writeFile(path2, serializeOpencodeConfig(contents));
@@ -22647,7 +23288,7 @@ function liveModelsFromSnapshots(snapshots, config) {
22647
23288
  map.set(runtime.defaultModel.providerID, {
22648
23289
  reachable: snapshot.reachable,
22649
23290
  modelIDs: new Set(snapshot.models.filter((model) => hasRequiredModelResidency(kind, model)).map((model) => model.modelID)),
22650
- ...kind === "lemonade" ? {
23291
+ ...runtimeRequiresModelResidency(kind) ? {
22651
23292
  residency: new Map(snapshot.models.map((model) => [model.modelID, model.loaded]))
22652
23293
  } : {}
22653
23294
  });
@@ -22977,9 +23618,9 @@ async function runCli(argv, deps) {
22977
23618
  const inventoryNotes = [];
22978
23619
  const readySnapshots = snapshots.map((snapshot) => {
22979
23620
  const runtime = config.local.runtimes.find((runtime2) => runtime2.id === snapshot.id);
22980
- if (runtime === undefined || localRuntimeKind(runtime) !== "lemonade")
23621
+ if (runtime === undefined || !runtimeRequiresModelResidency(localRuntimeKind(runtime)))
22981
23622
  return snapshot;
22982
- inventoryNotes.push(executionSetupMessages.lemonadeInventory(runtime.id, runtime.defaultModel.providerID, snapshot.models));
23623
+ inventoryNotes.push(executionSetupMessages.lemonadeInventory(runtime.id, runtime.defaultModel.providerID, snapshot.models, localRuntimeKind(runtime) === "llama-swap" ? "llama-swap" : "Lemonade"));
22983
23624
  return {
22984
23625
  ...snapshot,
22985
23626
  models: snapshot.models.filter((model) => hasRequiredModelResidency(localRuntimeKind(runtime), model))
@@ -22990,7 +23631,7 @@ async function runCli(argv, deps) {
22990
23631
  if (agent.model === undefined || live !== "not-loaded" && live !== "residency-unknown")
22991
23632
  return [];
22992
23633
  return [
22993
- executionSetupMessages.lemonadeAgentResidency(agent.name, `${agent.model.providerID}/${agent.model.modelID}`, live)
23634
+ executionSetupMessages.lemonadeAgentResidency(agent.name, `${agent.model.providerID}/${agent.model.modelID}`, live, config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === agent.model?.providerID && localRuntimeKind(runtime) === "llama-swap") ? "llama-swap" : "Lemonade")
22994
23635
  ];
22995
23636
  });
22996
23637
  return {
@@ -24812,6 +25453,159 @@ var realCacheAdapter = {
24812
25453
  isReparsePath
24813
25454
  };
24814
25455
 
25456
+ // src/local/lemonadeRequestGuard.ts
25457
+ var inferencePaths = new Set([
25458
+ "/chat/completions",
25459
+ "/completions",
25460
+ "/embeddings",
25461
+ "/responses"
25462
+ ]);
25463
+ function modelID(payload) {
25464
+ if (typeof payload !== "object" || payload === null || !("model" in payload) || typeof payload.model !== "string" || payload.model.length === 0 || payload.model !== payload.model.trim()) {
25465
+ throw new ModelResidencyError("request");
25466
+ }
25467
+ if (payload.model.endsWith(":latest"))
25468
+ throw new ModelResidencyError("identity");
25469
+ return payload.model;
25470
+ }
25471
+ function createLemonadeGuardedFetch(options) {
25472
+ const base = lemonadeBaseURL(options.baseURL);
25473
+ const upstream = options.fetch;
25474
+ const healthFetch = options.healthFetch ?? upstream;
25475
+ return Object.assign(async (input, init) => {
25476
+ const request = new Request(input, init);
25477
+ const url = new URL(request.url);
25478
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !request.url.startsWith(`${base}/`)) {
25479
+ throw new ModelResidencyError("destination");
25480
+ }
25481
+ const path4 = request.url.slice(base.length);
25482
+ const metadataOnly = request.method === "GET" && (path4 === "/health" || /^\/models(?:\/[^/]+)?$/.test(path4));
25483
+ if (!metadataOnly && (request.method !== "POST" || !inferencePaths.has(path4))) {
25484
+ throw new ModelResidencyError("request");
25485
+ }
25486
+ if (!metadataOnly) {
25487
+ await withResidencyDeadline(async (signal) => {
25488
+ signal.throwIfAborted();
25489
+ let payload;
25490
+ try {
25491
+ payload = await request.clone().json();
25492
+ } catch {
25493
+ signal.throwIfAborted();
25494
+ throw new ModelResidencyError("request");
25495
+ }
25496
+ signal.throwIfAborted();
25497
+ const model = modelID(payload);
25498
+ const modelIDs = typeof options.modelIDs === "function" ? await options.modelIDs() : options.modelIDs;
25499
+ signal.throwIfAborted();
25500
+ if (modelIDs !== undefined && !modelIDs.has(model)) {
25501
+ throw new ModelResidencyError("identity");
25502
+ }
25503
+ const residency = await readLemonadeResidency(base, healthFetch, {
25504
+ ...options,
25505
+ signal,
25506
+ headers: request.headers
25507
+ });
25508
+ signal.throwIfAborted();
25509
+ const entry = residency.get(model);
25510
+ if (entry?.loaded !== true) {
25511
+ throw new ModelResidencyError(entry?.loaded === undefined && entry !== undefined ? "unknown" : "inactive");
25512
+ }
25513
+ }, { ...options, signal: request.signal });
25514
+ }
25515
+ if (request.signal.aborted)
25516
+ throw new ModelResidencyError("cancelled");
25517
+ const {
25518
+ body: _body,
25519
+ headers: _headers,
25520
+ method: _method,
25521
+ signal: _signal,
25522
+ redirect: _redirect,
25523
+ ...transportOptions
25524
+ } = init ?? {};
25525
+ return upstream(request, { ...transportOptions, redirect: "error" });
25526
+ }, {
25527
+ preconnect: (...args) => upstream.preconnect?.(...args)
25528
+ });
25529
+ }
25530
+
25531
+ // src/local/llamaSwapRequestGuard.ts
25532
+ var inferencePaths2 = new Set([
25533
+ "/chat/completions",
25534
+ "/completions",
25535
+ "/embeddings",
25536
+ "/responses"
25537
+ ]);
25538
+ async function validatedModelID(payload, modelIDs) {
25539
+ if (typeof payload !== "object" || payload === null || !("model" in payload) || typeof payload.model !== "string" || payload.model.length === 0 || payload.model !== payload.model.trim()) {
25540
+ throw new ModelResidencyError("request", "llama-swap");
25541
+ }
25542
+ if (modelIDs === undefined) {
25543
+ return payload.model;
25544
+ }
25545
+ const ids = typeof modelIDs === "function" ? await modelIDs() : modelIDs;
25546
+ if (ids.has(payload.model)) {
25547
+ return payload.model;
25548
+ }
25549
+ throw new ModelResidencyError("identity", "llama-swap");
25550
+ }
25551
+ function createLlamaSwapGuardedFetch(options) {
25552
+ const base = llamaSwapBaseURL(options.baseURL);
25553
+ const upstream = options.fetch;
25554
+ const healthFetch = options.healthFetch ?? upstream;
25555
+ return Object.assign(async (input, init) => {
25556
+ const request = new Request(input, init);
25557
+ const url = new URL(request.url);
25558
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !request.url.startsWith(`${base}/`)) {
25559
+ throw new ModelResidencyError("destination", "llama-swap");
25560
+ }
25561
+ const path4 = request.url.slice(base.length);
25562
+ const metadataOnly = request.method === "GET" && /^\/models(?:\/[^/]+)?$/.test(path4);
25563
+ if (!metadataOnly && (request.method !== "POST" || !inferencePaths2.has(path4))) {
25564
+ throw new ModelResidencyError("request", "llama-swap");
25565
+ }
25566
+ if (!metadataOnly) {
25567
+ await withResidencyDeadline(async (signal) => {
25568
+ signal.throwIfAborted();
25569
+ let payload;
25570
+ try {
25571
+ payload = await request.clone().json();
25572
+ } catch {
25573
+ signal.throwIfAborted();
25574
+ throw new ModelResidencyError("request", "llama-swap");
25575
+ }
25576
+ signal.throwIfAborted();
25577
+ const model = await validatedModelID(payload, options.modelIDs);
25578
+ const residency = await readLlamaSwapResidency(base, healthFetch, {
25579
+ ...options,
25580
+ signal,
25581
+ headers: request.headers
25582
+ });
25583
+ signal.throwIfAborted();
25584
+ if (!residency.has(model)) {
25585
+ throw new ModelResidencyError("identity", "llama-swap");
25586
+ }
25587
+ if (residency.get(model)?.loaded !== true) {
25588
+ throw new ModelResidencyError("inactive", "llama-swap");
25589
+ }
25590
+ }, { ...options, signal: request.signal, runtimeLabel: "llama-swap" });
25591
+ }
25592
+ if (request.signal.aborted) {
25593
+ throw new ModelResidencyError("cancelled", "llama-swap");
25594
+ }
25595
+ const {
25596
+ body: _body,
25597
+ headers: _headers,
25598
+ method: _method,
25599
+ signal: _signal,
25600
+ redirect: _redirect,
25601
+ ...transportOptions
25602
+ } = init ?? {};
25603
+ return upstream(request, { ...transportOptions, redirect: "error" });
25604
+ }, {
25605
+ preconnect: (...args) => upstream.preconnect?.(...args)
25606
+ });
25607
+ }
25608
+
24815
25609
  // src/memory/types.ts
24816
25610
  import { z as z28 } from "zod";
24817
25611
  var SHARED_OWNER_KEY = "*";
@@ -25476,6 +26270,13 @@ function createLocalEmbedder(request, deps) {
25476
26270
  return (texts) => embedTexts({ ...request, texts }, deps);
25477
26271
  }
25478
26272
 
26273
+ // src/messages/memoryRuntime.ts
26274
+ var memoryRuntimeMessages = {
26275
+ providerUnavailable: "[openteam] Semantic memory requires exactly one enabled local runtime matching the configured embeddings provider. No embedding request was sent.",
26276
+ unavailable: "[openteam] Semantic memory initialization failed; recall and memory injection are unavailable.",
26277
+ rejectedLines: (count) => `[openteam] memory log rejected ${count} line(s); semantic memory may be incomplete.`
26278
+ };
26279
+
25479
26280
  // src/memory/embedText.ts
25480
26281
  function normalize3(parts) {
25481
26282
  return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
@@ -25553,25 +26354,39 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
25553
26354
  if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
25554
26355
  return;
25555
26356
  }
25556
- const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
26357
+ const warn = deps.warn ?? console.warn;
26358
+ const configuredModel = semantic.embeddings.model;
26359
+ const candidates = configuredModel == null ? config.local.runtimes.filter((rt) => rt.enabled && rt.baseURL !== undefined) : config.local.runtimes.filter((rt) => rt.defaultModel.providerID === configuredModel.providerID);
26360
+ const runtime = candidates[0];
26361
+ if (configuredModel != null && (candidates.length !== 1 || runtime?.enabled !== true)) {
26362
+ warn(memoryRuntimeMessages.providerUnavailable);
26363
+ return;
26364
+ }
25557
26365
  if (runtime?.baseURL === undefined) {
25558
26366
  return;
25559
26367
  }
25560
- const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
26368
+ const modelID2 = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
25561
26369
  const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
25562
26370
  const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
25563
26371
  const fetchImpl = deps.fetch ?? globalThis.fetch;
25564
26372
  const now = deps.now ?? (() => Date.now());
25565
- const warn = deps.warn ?? console.warn;
26373
+ const kind = localRuntimeKind(runtime);
25566
26374
  try {
26375
+ const baseURL = kind === "llama-swap" ? llamaSwapBaseURL(runtime.baseURL) : runtime.baseURL;
26376
+ const guardedFetch = runtimeRequiresModelResidency(kind) ? (kind === "llama-swap" ? createLlamaSwapGuardedFetch : createLemonadeGuardedFetch)({
26377
+ baseURL,
26378
+ fetch: fetchImpl,
26379
+ healthFetch: fetchImpl,
26380
+ modelIDs: new Set([modelID2])
26381
+ }) : fetchImpl;
25567
26382
  const db = await openDatabase(semantic.indexPath);
25568
26383
  const index = createMemoryIndex(db, warn);
25569
26384
  const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
25570
26385
  if (rebuildResult.rejectedLines > 0) {
25571
- warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
26386
+ warn(memoryRuntimeMessages.rejectedLines(rebuildResult.rejectedLines));
25572
26387
  }
25573
- const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
25574
- await ensureMemoryEmbeddings({ index, embedder, model: modelID, warn });
26388
+ const embedder = makeEmbedder({ baseURL, modelID: modelID2 }, { fetch: guardedFetch });
26389
+ await ensureMemoryEmbeddings({ index, embedder, model: modelID2, warn });
25575
26390
  return {
25576
26391
  index,
25577
26392
  embedder,
@@ -25580,6 +26395,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
25580
26395
  semantic
25581
26396
  };
25582
26397
  } catch {
26398
+ warn(memoryRuntimeMessages.unavailable);
25583
26399
  return;
25584
26400
  }
25585
26401
  }