@xopcai/xopc 0.0.166 → 0.0.168

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 (151) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/telegram/xopc.extension.json +1 -1
  4. package/dist/gateway/static/root/assets/{activity-store-DkALQFJT.js → activity-store-DjEDu38T.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Tr2hgn4j.js → agent-avatar-dicebear-DVuLzaJi.js} +1 -1
  6. package/dist/gateway/static/root/assets/agents-tF_QTWxn.js +2 -0
  7. package/dist/gateway/static/root/assets/{api-DPIXEcIq.js → api-DwVCuPOb.js} +1 -1
  8. package/dist/gateway/static/root/assets/{app-management-settings-panel-D7Nrbhez.js → app-management-settings-panel-spzVBvAu.js} +1 -1
  9. package/dist/gateway/static/root/assets/{appearance-settings-DNNbABFj.js → appearance-settings-CetimI47.js} +1 -1
  10. package/dist/gateway/static/root/assets/{apps-page-0zXIeywq.js → apps-page-HDyN6Fjx.js} +1 -1
  11. package/dist/gateway/static/root/assets/{archive-plugin-CizqFy4G.js → archive-plugin-TOmQ2XKc.js} +1 -1
  12. package/dist/gateway/static/root/assets/{attachment-load-DU8KjTua.js → attachment-load-NFNEWVLJ.js} +1 -1
  13. package/dist/gateway/static/root/assets/{automation-suggestion-card-3pqyIveW.js → automation-suggestion-card-B0FnLpSl.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-page-DSz3xDsi.js → automations-page-DVevVD6A.js} +1 -1
  15. package/dist/gateway/static/root/assets/{binary-plugins-CipUnsOr.js → binary-plugins-Csc4xkR9.js} +1 -1
  16. package/dist/gateway/static/root/assets/{block-editor-XoEq6qiv.js → block-editor-C6Xgrbse.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-settings-page-COYvq3y5.js → browser-settings-page-CyDDEKxB.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-workflow-inputs-DRH995XI.js → browser-workflow-inputs-DaxyAai1.js} +1 -1
  19. package/dist/gateway/static/root/assets/{browser-workflows-page-65edjhpz.js → browser-workflows-page-CaX6iIJA.js} +1 -1
  20. package/dist/gateway/static/root/assets/capabilities-settings-panel-EXZv96j1.js +7 -0
  21. package/dist/gateway/static/root/assets/capability-presets-api-DdCHSzcA.js +1 -0
  22. package/dist/gateway/static/root/assets/capability-presets-settings-panel-o08SXqM4.js +2 -0
  23. package/dist/gateway/static/root/assets/{channel-recipient-api-B5rLegwk.js → channel-recipient-api-BOkLI89e.js} +1 -1
  24. package/dist/gateway/static/root/assets/{channels-settings-D8nH0mve.js → channels-settings-aCN05KdJ.js} +1 -1
  25. package/dist/gateway/static/root/assets/channels-status-swr-ZrqadU60.js +1 -0
  26. package/dist/gateway/static/root/assets/{connector-logo-n4Hg41iD.js → connector-logo-Mp40HWsb.js} +1 -1
  27. package/dist/gateway/static/root/assets/{connectors-page-jvRWZn3Y.js → connectors-page-i0cu5rg5.js} +1 -1
  28. package/dist/gateway/static/root/assets/{desktop-pet-D4OzBOwm.js → desktop-pet-BtzUJ0d7.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-settings-CDGezbaf.js → desktop-pet-settings-Dg0Duj8B.js} +1 -1
  30. package/dist/gateway/static/root/assets/{directory-picker-path-field-DmL4AUXQ.js → directory-picker-path-field-9f0k1wZq.js} +1 -1
  31. package/dist/gateway/static/root/assets/{dreaming-settings-ClUcBKnd.js → dreaming-settings-rdW7dsGP.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-debug-page-DsV_KUDM.js → extension-debug-page-C9zUnn-z.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-page-DJv_G1jY.js → extension-page-Cy5rfU0h.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-settings-page-DUAlJ4CU.js → extension-settings-page-BjIquAw6.js} +1 -1
  35. package/dist/gateway/static/root/assets/{gateway-config-api-SVYgzqv7.js → gateway-config-api-AQQmI6SW.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-settings-YL6f2C_F.js → gateway-settings-CYNmbqHj.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-startup-retry-ulwTJovP.js → gateway-startup-retry-qnEmj-vL.js} +1 -1
  38. package/dist/gateway/static/root/assets/{goal-create-dialog-CJJe-4Co.js → goal-create-dialog-CkXaDfN3.js} +1 -1
  39. package/dist/gateway/static/root/assets/{goal-detail-page-Da9VMWRd.js → goal-detail-page-FqDa5bfO.js} +1 -1
  40. package/dist/gateway/static/root/assets/{goals-config-api-st_Itxa9.js → goals-config-api-CaVGtM6n.js} +1 -1
  41. package/dist/gateway/static/root/assets/{goals-page-B6pC7PYZ.js → goals-page-B-uBRGN6.js} +1 -1
  42. package/dist/gateway/static/root/assets/{goals-settings-BEZePZ5m.js → goals-settings-C9J3MX0F.js} +1 -1
  43. package/dist/gateway/static/root/assets/{heartbeat-settings-DpeD9GQ9.js → heartbeat-settings-C0qFLyew.js} +1 -1
  44. package/dist/gateway/static/root/assets/{index-BhQMnKH1.js → index-DchbD-qM.js} +9 -9
  45. package/dist/gateway/static/root/assets/index-DfE2j0g8.css +1 -0
  46. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-BAZlRZMd.js +1 -0
  47. package/dist/gateway/static/root/assets/{local-app-workbench-page-Ctia6lSG.js → local-app-workbench-page-DkJUQRyC.js} +1 -1
  48. package/dist/gateway/static/root/assets/{local-apps-page-DWF9rpWQ.js → local-apps-page-PcQABi7F.js} +1 -1
  49. package/dist/gateway/static/root/assets/locale-store-tnRLWdx2.js +1 -0
  50. package/dist/gateway/static/root/assets/{logs-page-D3gL6vHX.js → logs-page-B-nXM29t.js} +1 -1
  51. package/dist/gateway/static/root/assets/{markdown-split-BuduJ0Ev.js → markdown-split-B6W1H0Fj.js} +1 -1
  52. package/dist/gateway/static/root/assets/{media-plugins-CLASZjhT.js → media-plugins-B-r9Tj74.js} +1 -1
  53. package/dist/gateway/static/root/assets/{messages-C96Zjb0V.js → messages-B1EG4gdu.js} +2 -4
  54. package/dist/gateway/static/root/assets/{notes-page-ve3XiBDF.js → notes-page-BNECY6Oy.js} +1 -1
  55. package/dist/gateway/static/root/assets/{notes-workbench-oHU532lp.js → notes-workbench-Be97S0Im.js} +2 -2
  56. package/dist/gateway/static/root/assets/{page-tabs-DU85jn98.js → page-tabs-BmUWVWO9.js} +1 -1
  57. package/dist/gateway/static/root/assets/{preview-open-alternatives-CtchTuRQ.js → preview-open-alternatives-a76A8p5R.js} +1 -1
  58. package/dist/gateway/static/root/assets/{product-open-page-DFT6Fx6y.js → product-open-page-BWhYrt-U.js} +1 -1
  59. package/dist/gateway/static/root/assets/{project-detail-page-Ceu0NoC1.js → project-detail-page-md0ANZ-f.js} +1 -1
  60. package/dist/gateway/static/root/assets/{projects-page-CdpqOmO8.js → projects-page-D1MNrLcy.js} +1 -1
  61. package/dist/gateway/static/root/assets/{remote-access-hub-D1NKLyiR.js → remote-access-hub-GTuiqlJO.js} +1 -1
  62. package/dist/gateway/static/root/assets/{save-bar-controls-CmcrHpMc.js → save-bar-controls-_ZMLDSUw.js} +1 -1
  63. package/dist/gateway/static/root/assets/{schedule-field-DmIkP6JV.js → schedule-field-D4ErObRc.js} +1 -1
  64. package/dist/gateway/static/root/assets/{schema-form-Dkk9blN0.js → schema-form-PqNSGlcH.js} +1 -1
  65. package/dist/gateway/static/root/assets/{sessions-page-CMj_JaOk.js → sessions-page-Ct0MVjNv.js} +1 -1
  66. package/dist/gateway/static/root/assets/settings-advanced-gate-QOUph2Oq.js +1 -0
  67. package/dist/gateway/static/root/assets/{settings-form-section-CuQzWvg5.js → settings-form-section-BCKaoLhJ.js} +1 -1
  68. package/dist/gateway/static/root/assets/{settings-loading-skeleton-C5FNbyej.js → settings-loading-skeleton-Dn95fked.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-page-a-A2yTGE.js → settings-page-C8MPUl2Q.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-layout-MuXpamWD.js → settings-page-layout-Bigc8-GR.js} +1 -1
  71. package/dist/gateway/static/root/assets/{setup-status-panel-BUdh8FKY.js → setup-status-panel-DFTcAHsd.js} +1 -1
  72. package/dist/gateway/static/root/assets/{share-preview-page-BfzzmTgg.js → share-preview-page-xAjqaV4y.js} +1 -1
  73. package/dist/gateway/static/root/assets/{shares-settings-8a9qnLF8.js → shares-settings-D2uvC_aM.js} +1 -1
  74. package/dist/gateway/static/root/assets/skill-reload-api-DYtfQXB-.js +1 -0
  75. package/dist/gateway/static/root/assets/{skills-page-CmHj-CKY.js → skills-page-6dr3RRVW.js} +1 -1
  76. package/dist/gateway/static/root/assets/{system-settings-panel-CyhRkWMi.js → system-settings-panel-BJJQulg9.js} +1 -1
  77. package/dist/gateway/static/root/assets/{theme-store-D37XLD_C.js → theme-store-H9T49Iar.js} +1 -1
  78. package/dist/gateway/static/root/assets/{time-picker-WEPp3HrB.js → time-picker-DsuPUbRE.js} +1 -1
  79. package/dist/gateway/static/root/assets/{typed-models-lib-Bi1uoxFR.js → typed-models-lib-Djk3F3EL.js} +1 -1
  80. package/dist/gateway/static/root/assets/{use-channel-catalog-DBh0dOC9.js → use-channel-catalog-UACJTyH0.js} +1 -1
  81. package/dist/gateway/static/root/assets/{user-context-page-BObEyiim.js → user-context-page-BC585kHK.js} +1 -1
  82. package/dist/gateway/static/root/assets/{voice-api-key-field-Gvgodhec.js → voice-api-key-field-CvocOFat.js} +1 -1
  83. package/dist/gateway/static/root/assets/{work-discovery-page-BHnjvGH1.js → work-discovery-page-DiMn6nrT.js} +1 -1
  84. package/dist/gateway/static/root/assets/{work-item-detail-page-CQ8ybkxx.js → work-item-detail-page-CFBlQbPy.js} +1 -1
  85. package/dist/gateway/static/root/assets/{work-page-D_U_En23.js → work-page-CLJLKqsf.js} +1 -1
  86. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-B-Sk6gFn.js → workflow-run-setup-panel-DbQwWPjv.js} +1 -1
  87. package/dist/gateway/static/root/assets/{workflows-page-DT0pzfkz.js → workflows-page-BJOhlABF.js} +1 -1
  88. package/dist/gateway/static/root/index.html +8 -8
  89. package/dist/package.js +1 -1
  90. package/dist/src/agent/agent-manager.js +12 -5
  91. package/dist/src/agent/image/generation/custom-openai-images-provider.d.ts +7 -0
  92. package/dist/src/agent/image/generation/custom-openai-images-provider.js +81 -0
  93. package/dist/src/agent/image/generation/{openai-compatible-image-provider.d.ts → openai-images-provider.d.ts} +10 -31
  94. package/dist/src/agent/image/generation/{openai-compatible-image-provider.js → openai-images-provider.js} +36 -23
  95. package/dist/src/agent/image/generation/provider-registry.d.ts +2 -0
  96. package/dist/src/agent/image/generation/provider-registry.js +50 -4
  97. package/dist/src/agent/image/generation/providers/openai.js +2 -2
  98. package/dist/src/agent/image/generation/runtime.js +5 -1
  99. package/dist/src/agent/image/generation/types.d.ts +11 -0
  100. package/dist/src/agent/models/manager.d.ts +5 -0
  101. package/dist/src/agent/models/manager.js +8 -0
  102. package/dist/src/agent/prompt/sections/messaging-runtime.js +1 -1
  103. package/dist/src/agent/prompt/sections/tooling.js +1 -1
  104. package/dist/src/agent/tools/communication.js +25 -7
  105. package/dist/src/channels/plugin-registry.js +6 -1
  106. package/dist/src/cli/commands/channels.js +1 -1
  107. package/dist/src/cli/commands/connectors.js +1 -1
  108. package/dist/src/cli/commands/doctor/checks/image-providers.d.ts +2 -0
  109. package/dist/src/cli/commands/doctor/checks/image-providers.js +68 -0
  110. package/dist/src/cli/commands/doctor/flow.js +2 -0
  111. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  112. package/dist/src/cli/commands/models.js +1 -1
  113. package/dist/src/cli/commands/onboard.js +1 -1
  114. package/dist/src/config/index.js +2 -2
  115. package/dist/src/config/models-json.d.ts +325 -0
  116. package/dist/src/config/models-json.js +139 -4
  117. package/dist/src/extensions/sdk/index.js +1 -1
  118. package/dist/src/gateway/custom-image-providers.d.ts +89 -0
  119. package/dist/src/gateway/custom-image-providers.js +59 -0
  120. package/dist/src/gateway/hono/routes/models.js +182 -1
  121. package/dist/src/gateway/hono/routes/workflows.js +1 -1
  122. package/dist/src/gateway/image-generation-setup.js +7 -1
  123. package/dist/src/gateway/service/config-coordinator.js +2 -0
  124. package/dist/src/gateway/service.js +2 -2
  125. package/dist/src/media-shared/http/index.d.ts +1 -1
  126. package/dist/src/media-shared/http/index.js +2 -2
  127. package/dist/src/media-shared/http/provider-http.d.ts +2 -0
  128. package/dist/src/media-shared/http/provider-http.js +43 -4
  129. package/dist/src/providers/model-reference-auditor.d.ts +1 -0
  130. package/dist/src/providers/model-reference-auditor.js +23 -13
  131. package/dist/src/session/patch-metadata.d.ts +2 -0
  132. package/dist/src/session/patch-metadata.js +2 -0
  133. package/dist/src/tui/backends/embedded-backend.d.ts +2 -0
  134. package/dist/src/tui/backends/embedded-backend.js +46 -9
  135. package/dist/src/tui/backends/gateway-sse-backend.js +10 -3
  136. package/dist/src/tui/tui-backend.d.ts +2 -0
  137. package/dist/src/tui/tui-empty-session-cleanup.d.ts +11 -0
  138. package/dist/src/tui/tui-empty-session-cleanup.js +37 -0
  139. package/dist/src/tui/tui.js +43 -16
  140. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  141. package/package.json +2 -2
  142. package/dist/gateway/static/root/assets/agents-DZ69tC4K.js +0 -2
  143. package/dist/gateway/static/root/assets/capabilities-settings-panel-LOQyfdsM.js +0 -2
  144. package/dist/gateway/static/root/assets/capability-presets-api-XL4h7dCK.js +0 -1
  145. package/dist/gateway/static/root/assets/capability-presets-settings-panel-KPJFkt1h.js +0 -2
  146. package/dist/gateway/static/root/assets/channels-status-swr-DZZq0upi.js +0 -1
  147. package/dist/gateway/static/root/assets/index-BXtJvaQl.css +0 -1
  148. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-fhssr2-J.js +0 -1
  149. package/dist/gateway/static/root/assets/locale-store-C_ax27uc.js +0 -1
  150. package/dist/gateway/static/root/assets/settings-advanced-gate-DR9Q2ZyF.js +0 -1
  151. package/dist/gateway/static/root/assets/skill-reload-api-da5gLCer.js +0 -1
@@ -49,7 +49,8 @@ async function postJsonRequest(url, options) {
49
49
  init: {
50
50
  method: "POST",
51
51
  headers,
52
- body: JSON.stringify(options.body)
52
+ body: JSON.stringify(options.body),
53
+ redirect: "error"
53
54
  }
54
55
  });
55
56
  await assertOkOrThrowProviderError(response, options.label);
@@ -62,7 +63,8 @@ async function postMultipartRequest(url, options) {
62
63
  init: {
63
64
  method: "POST",
64
65
  headers,
65
- body: options.body
66
+ body: options.body,
67
+ redirect: "error"
66
68
  }
67
69
  });
68
70
  await assertOkOrThrowProviderError(response, options.label);
@@ -75,12 +77,49 @@ async function getJsonRequest(url, options) {
75
77
  ...options,
76
78
  init: {
77
79
  method: "GET",
78
- headers
80
+ headers,
81
+ redirect: "error"
79
82
  }
80
83
  });
81
84
  await assertOkOrThrowProviderError(response, options.label);
82
85
  return response;
83
86
  }
87
+ /** Read and parse a JSON response without allowing an unbounded body allocation. */
88
+ async function readJsonResponseLimited(response, maxBytes) {
89
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("JSON response byte limit must be a positive safe integer.");
90
+ const declaredLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
91
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) throw new Error(`Provider JSON response exceeds ${maxBytes} bytes.`);
92
+ const reader = response.body?.getReader();
93
+ if (!reader) throw new Error("Provider returned an empty JSON response.");
94
+ const chunks = [];
95
+ let total = 0;
96
+ try {
97
+ while (true) {
98
+ const { value, done } = await reader.read();
99
+ if (done) break;
100
+ if (!value?.byteLength) continue;
101
+ total += value.byteLength;
102
+ if (total > maxBytes) {
103
+ await reader.cancel().catch(() => {});
104
+ throw new Error(`Provider JSON response exceeds ${maxBytes} bytes.`);
105
+ }
106
+ chunks.push(value);
107
+ }
108
+ } finally {
109
+ reader.releaseLock();
110
+ }
111
+ const body = new Uint8Array(total);
112
+ let offset = 0;
113
+ for (const chunk of chunks) {
114
+ body.set(chunk, offset);
115
+ offset += chunk.byteLength;
116
+ }
117
+ try {
118
+ return JSON.parse(new TextDecoder().decode(body));
119
+ } catch {
120
+ throw new Error("Provider returned invalid JSON.");
121
+ }
122
+ }
84
123
  /**
85
124
  * Normalize a base URL: trim trailing slash, force http(s) scheme.
86
125
  */
@@ -107,4 +146,4 @@ async function waitProviderOperationPollInterval(intervalMs, signal) {
107
146
  await setTimeout$1(intervalMs, void 0, { signal });
108
147
  }
109
148
  //#endregion
110
- export { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, waitProviderOperationPollInterval };
149
+ export { createProviderOperationDeadline, fetchWithTimeoutGuarded, getJsonRequest, normalizeBaseUrl, postJsonRequest, postMultipartRequest, readJsonResponseLimited, waitProviderOperationPollInterval };
@@ -11,4 +11,5 @@ export interface ModelReferenceHealth {
11
11
  export declare function auditModelReferences(config: Config, sessionConfigs?: ReadonlyMap<string, SessionAgentConfig>, deps?: {
12
12
  registry?: ModelRegistry;
13
13
  catalog?: ModelCatalogSnapshot;
14
+ resolveImageGenerationModel?: (ref: string) => boolean;
14
15
  }): ModelReferenceHealth[];
@@ -1,22 +1,25 @@
1
1
  import { getModelCatalogStore, init_model_catalog_store } from "./model-catalog-store.js";
2
2
  import { getModelRegistry, init_model_registry } from "./model-registry.js";
3
+ import { parseImageGenerationModelRef } from "../agent/image/generation/model-ref.js";
4
+ import { getImageGenerationProvider } from "../agent/image/generation/provider-registry.js";
3
5
  //#region src/providers/model-reference-auditor.ts
4
6
  init_model_catalog_store();
5
7
  init_model_registry();
6
8
  function asRecord(value) {
7
9
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
8
10
  }
9
- function addRef(out, value, location) {
11
+ function addRef(out, value, location, kind = "text") {
10
12
  if (typeof value === "string" && value.trim()) out.push({
11
13
  ref: value.trim(),
12
- location
14
+ location,
15
+ kind
13
16
  });
14
17
  }
15
- function collectToolModel(out, value, location) {
18
+ function collectToolModel(out, value, location, kind = "text") {
16
19
  const model = asRecord(value);
17
20
  if (!model) return;
18
- addRef(out, model.primary, `${location}.primary`);
19
- if (Array.isArray(model.fallbacks)) model.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.fallbacks[${index}]`));
21
+ addRef(out, model.primary, `${location}.primary`, kind);
22
+ if (Array.isArray(model.fallbacks)) model.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.fallbacks[${index}]`, kind));
20
23
  }
21
24
  function collectModelPolicy(out, value, location) {
22
25
  const policy = asRecord(value);
@@ -29,7 +32,7 @@ function collectModelPolicy(out, value, location) {
29
32
  if (Array.isArray(role.fallbacks)) role.fallbacks.forEach((ref, index) => addRef(out, ref, `${location}.roles.${roleId}.fallbacks[${index}]`));
30
33
  }
31
34
  collectToolModel(out, policy.imageModel, `${location}.imageModel`);
32
- collectToolModel(out, policy.imageGenerationModel, `${location}.imageGenerationModel`);
35
+ collectToolModel(out, policy.imageGenerationModel, `${location}.imageGenerationModel`, "image-generation");
33
36
  }
34
37
  function collectReferences(config, sessionConfigs) {
35
38
  const out = [];
@@ -47,22 +50,29 @@ function suggestedRef(ref, catalog) {
47
50
  const first = source.models.find((model) => model.availability === "available");
48
51
  return first ? `${providerId}/${first.id}` : void 0;
49
52
  }
53
+ function hasImageGenerationModel(ref) {
54
+ const parsed = parseImageGenerationModelRef(ref);
55
+ if (!parsed) return false;
56
+ return getImageGenerationProvider(parsed.provider)?.models.includes(parsed.model) ?? false;
57
+ }
50
58
  function auditModelReferences(config, sessionConfigs = /* @__PURE__ */ new Map(), deps = {}) {
51
59
  const registry = deps.registry ?? getModelRegistry();
52
60
  const catalog = deps.catalog ?? getModelCatalogStore().load();
61
+ const resolveImageGenerationModel = deps.resolveImageGenerationModel ?? hasImageGenerationModel;
53
62
  const grouped = /* @__PURE__ */ new Map();
54
63
  for (const reference of collectReferences(config, sessionConfigs)) {
55
- const locations = grouped.get(reference.ref) ?? [];
56
- locations.push(reference.location);
57
- grouped.set(reference.ref, locations);
64
+ const references = grouped.get(reference.ref) ?? [];
65
+ references.push(reference);
66
+ grouped.set(reference.ref, references);
58
67
  }
59
- return [...grouped.entries()].map(([ref, locations]) => {
60
- const availability = registry.resolve(ref) ? "available" : "unavailable";
61
- const suggestion = availability === "unavailable" ? suggestedRef(ref, catalog) : void 0;
68
+ return [...grouped.entries()].map(([ref, references]) => {
69
+ const availability = references.every((reference) => reference.kind === "image-generation" ? resolveImageGenerationModel(ref) : Boolean(registry.resolve(ref))) ? "available" : "unavailable";
70
+ const textOnly = references.every((reference) => reference.kind === "text");
71
+ const suggestion = availability === "unavailable" && textOnly ? suggestedRef(ref, catalog) : void 0;
62
72
  return {
63
73
  ref,
64
74
  availability,
65
- locations,
75
+ locations: references.map((reference) => reference.location),
66
76
  ...suggestion ? { suggestedRef: suggestion } : {}
67
77
  };
68
78
  }).sort((a, b) => a.ref.localeCompare(b.ref));
@@ -8,5 +8,7 @@ export type SessionPatchBody = {
8
8
  /** When true, `tags` replaces the tag list; otherwise tags are union-merged. */
9
9
  replaceTags?: boolean;
10
10
  customData?: Record<string, unknown>;
11
+ hiddenFromSessionList?: boolean;
12
+ projectId?: string;
11
13
  };
12
14
  export declare function applySessionPatchToMetadata(existing: SessionMetadata, patch: SessionPatchBody): Partial<SessionMetadata>;
@@ -15,6 +15,8 @@ function applySessionPatchToMetadata(existing, patch) {
15
15
  ...existing.customData ?? {},
16
16
  ...patch.customData
17
17
  };
18
+ if (typeof patch.hiddenFromSessionList === "boolean") out.hiddenFromSessionList = patch.hiddenFromSessionList;
19
+ if (typeof patch.projectId === "string") out.projectId = patch.projectId.trim() || void 0;
18
20
  return out;
19
21
  }
20
22
  //#endregion
@@ -36,6 +36,7 @@ export declare class EmbeddedBackend implements TuiBackend {
36
36
  start(): void;
37
37
  private ensureAgent;
38
38
  private createAgent;
39
+ private refreshXopcCloudModels;
39
40
  stop(): void;
40
41
  getActiveSignal(): AbortSignal | undefined;
41
42
  private getWorkflowRunService;
@@ -100,6 +101,7 @@ export declare class EmbeddedBackend implements TuiBackend {
100
101
  }>;
101
102
  getSessionInfo(sessionKey: string): Promise<SessionInfo>;
102
103
  listModels(): Promise<TuiModelChoice[]>;
104
+ refreshModels(): Promise<void>;
103
105
  resetSession(sessionKey: string): Promise<void>;
104
106
  patchSession(sessionKey: string, patch: Record<string, unknown>): Promise<void>;
105
107
  compactSession(sessionKey: string, options?: {
@@ -122,6 +122,7 @@ var EmbeddedBackend = class {
122
122
  }
123
123
  }
124
124
  async createAgent(config, sessionIndex) {
125
+ await this.refreshXopcCloudModels();
125
126
  const { AgentService } = await import("../../agent/service.js");
126
127
  const workspace = this.workspace || getWorkspacePath(config);
127
128
  const modelId = getAgentDefaultModelRef(config);
@@ -143,6 +144,20 @@ var EmbeddedBackend = class {
143
144
  await agent.start();
144
145
  return agent;
145
146
  }
147
+ async refreshXopcCloudModels() {
148
+ try {
149
+ const { XopcCloudModelSource } = await import("../../providers/xopc-cloud-model-source.js");
150
+ const result = await new XopcCloudModelSource().refresh();
151
+ if (result.status === "updated") log.info({ modelCount: result.modelCount }, `XOPC Cloud model catalog refreshed: ${result.modelCount} models`);
152
+ } catch (err) {
153
+ const errorMessage = err instanceof Error ? err.message : String(err);
154
+ log.warn({
155
+ err,
156
+ errorMessage,
157
+ phase: "model_catalog_refresh"
158
+ }, `XOPC Cloud model catalog refresh failed: ${errorMessage}`);
159
+ }
160
+ }
146
161
  stop() {
147
162
  this.running = false;
148
163
  this.chatAbort?.abort();
@@ -419,9 +434,10 @@ var EmbeddedBackend = class {
419
434
  }
420
435
  }
421
436
  async getSessionStats(sessionKey) {
422
- if (!this.agent) return computeTuiSessionStats([]);
437
+ const store = this.agent?.sessionStore ?? this.sessionIndex?.getStore();
438
+ if (!store) return computeTuiSessionStats([]);
423
439
  try {
424
- return computeTuiSessionStats(await this.agent.sessionStore.loadTranscriptRows(sessionKey));
440
+ return computeTuiSessionStats(await store.loadTranscriptRows(sessionKey));
425
441
  } catch (error) {
426
442
  const errorMessage = error instanceof Error ? error.message : String(error);
427
443
  log.warn({
@@ -485,9 +501,10 @@ var EmbeddedBackend = class {
485
501
  }
486
502
  }
487
503
  async deleteSession(sessionKey) {
488
- if (!this.agent) return { ok: false };
504
+ const store = this.agent?.sessionStore ?? this.sessionIndex?.getStore();
505
+ if (!store) return { ok: false };
489
506
  try {
490
- return { ok: await this.agent.sessionStore.deleteSession(sessionKey) };
507
+ return { ok: await store.deleteSession(sessionKey) };
491
508
  } catch (error) {
492
509
  const errorMessage = error instanceof Error ? error.message : String(error);
493
510
  log.warn({
@@ -535,6 +552,9 @@ var EmbeddedBackend = class {
535
552
  contextWindow: model.contextWindow
536
553
  }));
537
554
  }
555
+ async refreshModels() {
556
+ await this.refreshXopcCloudModels();
557
+ }
538
558
  async resetSession(sessionKey) {
539
559
  if (!this.agent) return;
540
560
  await this.agent.resetSession(sessionKey);
@@ -542,11 +562,6 @@ var EmbeddedBackend = class {
542
562
  async patchSession(sessionKey, patch) {
543
563
  const agent = await this.ensureAgent();
544
564
  const projectId = typeof patch.projectId === "string" ? patch.projectId.trim() : "";
545
- if (projectId) {
546
- await this.sessionIndexReady;
547
- await this.sessionIndex?.getStore().resolveTranscriptPath(sessionKey);
548
- new ProjectService().attachSession(sessionKey, projectId);
549
- }
550
565
  const result = await agent.sessionConfig.patch(sessionKey, {
551
566
  model: typeof patch.model === "string" ? patch.model : void 0,
552
567
  thinkingLevel: typeof patch.thinkingLevel === "string" ? patch.thinkingLevel : void 0,
@@ -555,6 +570,28 @@ var EmbeddedBackend = class {
555
570
  workingDirectory: typeof patch.workingDirectory === "string" ? patch.workingDirectory : void 0
556
571
  });
557
572
  if (!result.ok) throw new Error(result.error);
573
+ if (projectId) {
574
+ await this.sessionIndexReady;
575
+ await this.sessionIndex?.getStore().resolveTranscriptPath(sessionKey);
576
+ new ProjectService().attachSession(sessionKey, projectId);
577
+ }
578
+ const hiddenFromSessionList = typeof patch.hiddenFromSessionList === "boolean" ? patch.hiddenFromSessionList : void 0;
579
+ const customData = patch.customData && typeof patch.customData === "object" && !Array.isArray(patch.customData) ? patch.customData : void 0;
580
+ if (hiddenFromSessionList !== void 0 || customData !== void 0) {
581
+ await this.sessionIndexReady;
582
+ const store = this.sessionIndex?.getStore();
583
+ if (store) {
584
+ await store.resolveTranscriptPath(sessionKey);
585
+ const existing = getSessionMetadata(sessionKey);
586
+ if (existing && existing.messageCount === 0) await store.updateMetadata(sessionKey, {
587
+ ...hiddenFromSessionList !== void 0 ? { hiddenFromSessionList } : {},
588
+ ...customData ? { customData: {
589
+ ...existing.customData ?? {},
590
+ ...customData
591
+ } } : {}
592
+ });
593
+ }
594
+ }
558
595
  }
559
596
  async compactSession(sessionKey, options) {
560
597
  if (!this.agent) return {
@@ -793,13 +793,20 @@ var GatewaySseBackend = class {
793
793
  const json = await res.json().catch(() => ({}));
794
794
  if (!res.ok || json.ok === false) throw new Error(json.error ?? `Session config patch failed (${res.status})`);
795
795
  const projectId = typeof patch.projectId === "string" ? patch.projectId.trim() : "";
796
- if (projectId) {
796
+ const hiddenFromSessionList = typeof patch.hiddenFromSessionList === "boolean" ? patch.hiddenFromSessionList : void 0;
797
+ const customData = patch.customData && typeof patch.customData === "object" && !Array.isArray(patch.customData) ? patch.customData : void 0;
798
+ if (projectId || hiddenFromSessionList !== void 0 || customData !== void 0) {
799
+ const metadataPatch = {
800
+ ...projectId ? { projectId } : {},
801
+ ...hiddenFromSessionList !== void 0 ? { hiddenFromSessionList } : {},
802
+ ...customData ? { customData } : {}
803
+ };
797
804
  const metaRes = await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}`, this.credential, {
798
805
  method: "PATCH",
799
- body: JSON.stringify({ projectId })
806
+ body: JSON.stringify(metadataPatch)
800
807
  });
801
808
  const metaJson = await metaRes.json().catch(() => ({}));
802
- if (!metaRes.ok || metaJson.ok === false) throw new Error(metaJson.error ?? `Session project patch failed (${metaRes.status})`);
809
+ if (!metaRes.ok || metaJson.ok === false) throw new Error(metaJson.error ?? `Session metadata patch failed (${metaRes.status})`);
803
810
  }
804
811
  }
805
812
  async renameSession(sessionKey, name) {
@@ -255,6 +255,8 @@ export interface TuiBackend {
255
255
  getSessionInfo(sessionKey: string): Promise<SessionInfo>;
256
256
  /** List available models. */
257
257
  listModels(): Promise<TuiModelChoice[]>;
258
+ /** Refresh dynamic model catalogs after credentials change. */
259
+ refreshModels?(): Promise<void>;
258
260
  /** Reset / create new session. */
259
261
  resetSession(sessionKey: string): Promise<void>;
260
262
  /** Rename session display name. */
@@ -0,0 +1,11 @@
1
+ import type { TuiBackend } from './tui-backend.js';
2
+ export declare const GENERATED_TUI_SESSION_SHELL_PATCH: {
3
+ readonly hiddenFromSessionList: true;
4
+ readonly customData: {
5
+ readonly genericNewChatShell: true;
6
+ };
7
+ };
8
+ export declare function isGeneratedTuiSessionKey(sessionKey: string): boolean;
9
+ export declare function deleteGeneratedTuiSessionIfEmpty(client: Pick<TuiBackend, 'getSessionStats' | 'deleteSession'>, sessionKey: string): Promise<boolean>;
10
+ export declare function cleanupAbandonedTuiSessions(client: Pick<TuiBackend, 'listSessions' | 'getSessionStats' | 'deleteSession'>, currentSessionKey: string, nowMs?: number): Promise<string[]>;
11
+ export declare function hideLegacyEmptyTuiSessions(client: Pick<TuiBackend, 'listSessions' | 'patchSession'>): Promise<string[]>;
@@ -0,0 +1,37 @@
1
+ import { parseAgentSessionKey } from "../routing/session-key-utils.js";
2
+ import { init_agent_session_key } from "../routing/agent-session-key.js";
3
+ //#region src/tui/tui-empty-session-cleanup.ts
4
+ init_agent_session_key();
5
+ const GENERATED_TUI_SUFFIX_RE = /^tui-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
+ const ABANDONED_SESSION_AGE_MS = 60 * 6e4;
7
+ const GENERATED_TUI_SESSION_SHELL_PATCH = {
8
+ hiddenFromSessionList: true,
9
+ customData: { genericNewChatShell: true }
10
+ };
11
+ function isGeneratedTuiSessionKey(sessionKey) {
12
+ const parsed = parseAgentSessionKey(sessionKey);
13
+ return parsed ? GENERATED_TUI_SUFFIX_RE.test(parsed.rest) : false;
14
+ }
15
+ async function deleteGeneratedTuiSessionIfEmpty(client, sessionKey) {
16
+ if (!isGeneratedTuiSessionKey(sessionKey)) return false;
17
+ if ((await client.getSessionStats(sessionKey)).totalMessages > 0) return false;
18
+ return (await client.deleteSession(sessionKey)).ok;
19
+ }
20
+ async function cleanupAbandonedTuiSessions(client, currentSessionKey, nowMs = Date.now()) {
21
+ const candidates = (await client.listSessions()).filter((session) => session.key !== currentSessionKey && session.messageCount === 0 && typeof session.updatedAt === "number" && session.updatedAt <= nowMs - ABANDONED_SESSION_AGE_MS && isGeneratedTuiSessionKey(session.key));
22
+ const deleted = [];
23
+ for (const session of candidates) if (await deleteGeneratedTuiSessionIfEmpty(client, session.key)) deleted.push(session.key);
24
+ return deleted;
25
+ }
26
+ async function hideLegacyEmptyTuiSessions(client) {
27
+ const sessions = await client.listSessions();
28
+ const hidden = [];
29
+ for (const session of sessions) {
30
+ if (session.messageCount !== 0 || !isGeneratedTuiSessionKey(session.key)) continue;
31
+ await client.patchSession(session.key, GENERATED_TUI_SESSION_SHELL_PATCH);
32
+ hidden.push(session.key);
33
+ }
34
+ return hidden;
35
+ }
36
+ //#endregion
37
+ export { GENERATED_TUI_SESSION_SHELL_PATCH, cleanupAbandonedTuiSessions, deleteGeneratedTuiSessionIfEmpty, hideLegacyEmptyTuiSessions, isGeneratedTuiSessionKey };
@@ -27,6 +27,7 @@ import { transcriptTreeEntryIdToRowNumber } from "./tui-transcript-tree.js";
27
27
  import { formatTuiCompactionResult, formatTuiShareResult } from "./tui-command-formatters.js";
28
28
  import { formatTuiStartupText } from "./tui-startup-text.js";
29
29
  import { createTuiCommandHandler, getSlashCommands } from "./tui-commands.js";
30
+ import { GENERATED_TUI_SESSION_SHELL_PATCH, cleanupAbandonedTuiSessions, deleteGeneratedTuiSessionIfEmpty, hideLegacyEmptyTuiSessions, isGeneratedTuiSessionKey } from "./tui-empty-session-cleanup.js";
30
31
  import { extensionCustomMessageContentToText, extensionCustomMessageToTurnText, extensionUserMessageContentToText } from "./tui-extension-user-message.js";
31
32
  import { resolveFdPath } from "./tui-fd-path.js";
32
33
  import { drainFollowUpQueue, restoreQueuedMessages } from "./tui-follow-up-queue.js";
@@ -197,6 +198,9 @@ async function runTui(opts) {
197
198
  let tuiSettings = loadTuiSettings();
198
199
  initTuiTheme({ themeId: opts.theme ?? tuiSettings.theme });
199
200
  const state = createInitialState(startup.sessionKey);
201
+ const generatedStartupSessionKey = !opts.session?.trim() && isGeneratedTuiSessionKey(startup.sessionKey) ? startup.sessionKey : null;
202
+ const generatedStartupSessionKeys = new Set(generatedStartupSessionKey ? [generatedStartupSessionKey] : []);
203
+ let startupSessionHadUserTurn = false;
200
204
  const startupWorkingDirectory = resolveStartupWorkingDirectory(opts, isLocalMode);
201
205
  const implicitTrustedWorkspace = startupWorkingDirectory ?? (isLocalMode ? process.cwd() : void 0);
202
206
  const projectTrustStore = new ProjectTrustStore();
@@ -947,14 +951,21 @@ async function runTui(opts) {
947
951
  clearInterval(streamWatchdogId);
948
952
  streamWatchdogId = null;
949
953
  }
950
- client.stop();
951
954
  tui.terminal.setProgress(false);
952
- drainAndStopTuiSafely(tui).then(() => {
955
+ (async () => {
956
+ const removedEmptyStartupSessions = /* @__PURE__ */ new Set();
957
+ if (!state.activeRunId) for (const sessionKey of generatedStartupSessionKeys) {
958
+ if (sessionKey === state.currentSessionKey && startupSessionHadUserTurn) continue;
959
+ if (await deleteGeneratedTuiSessionIfEmpty(client, sessionKey).catch(() => false)) removedEmptyStartupSessions.add(sessionKey);
960
+ }
961
+ client.stop();
962
+ await drainAndStopTuiSafely(tui);
953
963
  restoreStdio();
954
- if (options?.showResumeHint !== false) process.stdout.write(`\nTo resume this session: ${formatTuiResumeCommand(opts, state.currentSessionKey)}\n`);
964
+ const currentSessionWasRemoved = removedEmptyStartupSessions.has(state.currentSessionKey);
965
+ if (options?.showResumeHint !== false && !currentSessionWasRemoved) process.stdout.write(`\nTo resume this session: ${formatTuiResumeCommand(opts, state.currentSessionKey)}\n`);
955
966
  finishTui?.();
956
967
  process.exit(0);
957
- });
968
+ })();
958
969
  };
959
970
  const { refreshSessionInfo, loadHistory: loadSessionHistory, loadHistoryWindow, setSession, abortActive, resetCurrentSession, clearChatForSessionSwitch } = createSessionActions({
960
971
  client,
@@ -1001,7 +1012,10 @@ async function runTui(opts) {
1001
1012
  const applyStartupWorkingDirectory = async () => {
1002
1013
  if (startupWorkingDirectoryApplied || !startupWorkingDirectory) return;
1003
1014
  startupWorkingDirectoryApplied = true;
1004
- await client.patchSession(state.currentSessionKey, { workingDirectory: startupWorkingDirectory });
1015
+ await client.patchSession(state.currentSessionKey, {
1016
+ workingDirectory: startupWorkingDirectory,
1017
+ ...isGeneratedTuiSessionKey(state.currentSessionKey) ? GENERATED_TUI_SESSION_SHELL_PATCH : {}
1018
+ });
1005
1019
  state.sessionInfo.effectiveWorkspacePath = startupWorkingDirectory;
1006
1020
  state.sessionInfo.workingDirectoryLocked = true;
1007
1021
  };
@@ -1018,15 +1032,22 @@ async function runTui(opts) {
1018
1032
  const projectAgentId = result.project.defaultAgentId?.trim() ? normalizeAgentId(result.project.defaultAgentId) : void 0;
1019
1033
  const currentParsed = parseAgentSessionKey(state.currentSessionKey);
1020
1034
  if (!opts.agentId?.trim() && currentParsed?.rest && projectAgentId && projectAgentId !== currentAgentId) {
1021
- await setSession(`agent:${projectAgentId}:${currentParsed.rest}`);
1035
+ const targetSessionKey = `agent:${projectAgentId}:${currentParsed.rest}`;
1036
+ await setSession(targetSessionKey);
1037
+ if (isGeneratedTuiSessionKey(targetSessionKey)) generatedStartupSessionKeys.add(targetSessionKey);
1022
1038
  currentAgentId = projectAgentId;
1039
+ if (isGeneratedTuiSessionKey(state.currentSessionKey)) await client.patchSession(state.currentSessionKey, GENERATED_TUI_SESSION_SHELL_PATCH);
1023
1040
  await client.patchSession(state.currentSessionKey, {
1024
1041
  projectId: result.project.id,
1025
- workingDirectory: startupWorkingDirectory
1042
+ workingDirectory: startupWorkingDirectory,
1043
+ ...isGeneratedTuiSessionKey(state.currentSessionKey) ? GENERATED_TUI_SESSION_SHELL_PATCH : {}
1026
1044
  });
1027
1045
  state.sessionInfo.effectiveWorkspacePath = startupWorkingDirectory;
1028
1046
  state.sessionInfo.workingDirectoryLocked = true;
1029
- } else await client.patchSession(state.currentSessionKey, { projectId: result.project.id });
1047
+ } else await client.patchSession(state.currentSessionKey, {
1048
+ projectId: result.project.id,
1049
+ ...isGeneratedTuiSessionKey(state.currentSessionKey) ? GENERATED_TUI_SESSION_SHELL_PATCH : {}
1050
+ });
1030
1051
  const verb = result.created ? "Created project" : "Using project";
1031
1052
  chatLog.addStatus(theme.dim(`${verb}: ${result.project.name}`));
1032
1053
  };
@@ -1458,6 +1479,7 @@ async function runTui(opts) {
1458
1479
  return;
1459
1480
  }
1460
1481
  chatLog.addUser(userDisplayContentForAttachments(text, attachments));
1482
+ startupSessionHadUserTurn = true;
1461
1483
  sessionSnapshot.appendMessage("user", messageText);
1462
1484
  lastRetryMessageText = messageText;
1463
1485
  markRunSending(state);
@@ -1682,14 +1704,18 @@ async function runTui(opts) {
1682
1704
  getEntry: () => projectTrustStore.getEntry(process.cwd()),
1683
1705
  getSessionDecision: () => projectTrustSessionDecision
1684
1706
  },
1685
- runLogin: (provider) => runTuiOAuthLogin(provider, {
1686
- chatLog,
1687
- tui,
1688
- editor,
1689
- openOverlay: openCommandOverlay,
1690
- closeOverlay: closeCommandOverlay,
1691
- keybindings
1692
- })
1707
+ runLogin: async (provider) => {
1708
+ await runTuiOAuthLogin(provider, {
1709
+ chatLog,
1710
+ tui,
1711
+ editor,
1712
+ openOverlay: openCommandOverlay,
1713
+ closeOverlay: closeCommandOverlay,
1714
+ keybindings
1715
+ });
1716
+ await client.refreshModels?.();
1717
+ await refreshModelChoices();
1718
+ }
1693
1719
  });
1694
1720
  const { runLocalShellLine } = createLocalShellRunner({
1695
1721
  chatLog,
@@ -2064,6 +2090,7 @@ async function runTui(opts) {
2064
2090
  await refreshSessionInfoWithBorder();
2065
2091
  await refreshModelChoices();
2066
2092
  await loadSessionHistory({ merge: true });
2093
+ cleanupAbandonedTuiSessions(client, state.currentSessionKey).then(() => hideLegacyEmptyTuiSessions(client)).catch(() => {});
2067
2094
  showStartupCardOnce();
2068
2095
  refreshStartupResources().then(() => {
2069
2096
  updateFooter();
@@ -4,8 +4,8 @@ import { publishAutomationProductEvent } from "../../automations/product-events.
4
4
  import { GoalService } from "../../goals/goal-service.js";
5
5
  import "../../goals/index.js";
6
6
  import { DelegateSubagentRunner } from "../../agent/workflow/subagent-runner.js";
7
- import { resolveModelRef } from "../../config/agent-typed-models.js";
8
7
  import { preflightWorkflowConnectors } from "../../connectors/workflow-preflight.js";
8
+ import { resolveModelRef } from "../../config/agent-typed-models.js";
9
9
  import { CatalogWorkflowDefinitionRegistry } from "../registry/catalog-workflow-definition-registry.js";
10
10
  import { validateWorkflowJsonSchema } from "../domain/schema-validation.js";
11
11
  import { isTerminalWorkflowRunStatus } from "../domain/run.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.166",
3
+ "version": "0.0.168",
4
4
  "description": "Local-first AI system that remembers context, coordinates AI, and sustains long-term progress.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -100,7 +100,7 @@
100
100
  "semver": "^7.8.1",
101
101
  "silk-wasm": "^3.7.1",
102
102
  "thread-stream": "^4.2.0",
103
- "undici": "^8.5.0",
103
+ "undici": "^8.9.0",
104
104
  "uuid": "^14.0.0",
105
105
  "ws": "^8.21.0",
106
106
  "zod": "^4.4.3"