@xopcai/xopc 0.0.167 → 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 (98) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DQULz7qY.js → agent-avatar-dicebear-DVuLzaJi.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agents-CnzkBgKC.js → agents-tF_QTWxn.js} +1 -1
  5. package/dist/gateway/static/root/assets/{app-management-settings-panel-DOjWALVF.js → app-management-settings-panel-spzVBvAu.js} +1 -1
  6. package/dist/gateway/static/root/assets/{appearance-settings-C31COUIC.js → appearance-settings-CetimI47.js} +1 -1
  7. package/dist/gateway/static/root/assets/{apps-page-lSC486eu.js → apps-page-HDyN6Fjx.js} +1 -1
  8. package/dist/gateway/static/root/assets/{archive-plugin-DQPvLqfx.js → archive-plugin-TOmQ2XKc.js} +1 -1
  9. package/dist/gateway/static/root/assets/{attachment-load-DwT1x1rc.js → attachment-load-NFNEWVLJ.js} +1 -1
  10. package/dist/gateway/static/root/assets/{automation-suggestion-card-ByYBiiMR.js → automation-suggestion-card-B0FnLpSl.js} +1 -1
  11. package/dist/gateway/static/root/assets/{automations-page-iZTnc1YZ.js → automations-page-DVevVD6A.js} +1 -1
  12. package/dist/gateway/static/root/assets/{binary-plugins-CRXiHoZS.js → binary-plugins-Csc4xkR9.js} +1 -1
  13. package/dist/gateway/static/root/assets/{block-editor-CHO6ILiz.js → block-editor-C6Xgrbse.js} +1 -1
  14. package/dist/gateway/static/root/assets/{browser-settings-page-C-OOPZcJ.js → browser-settings-page-CyDDEKxB.js} +1 -1
  15. package/dist/gateway/static/root/assets/{browser-workflow-inputs-3UaarfXS.js → browser-workflow-inputs-DaxyAai1.js} +1 -1
  16. package/dist/gateway/static/root/assets/{browser-workflows-page-BFaa-N_D.js → browser-workflows-page-CaX6iIJA.js} +1 -1
  17. package/dist/gateway/static/root/assets/{capabilities-settings-panel-BayH0Uom.js → capabilities-settings-panel-EXZv96j1.js} +1 -1
  18. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-JlhjAhT7.js → capability-presets-settings-panel-o08SXqM4.js} +1 -1
  19. package/dist/gateway/static/root/assets/{channels-settings-C-Spmwr5.js → channels-settings-aCN05KdJ.js} +1 -1
  20. package/dist/gateway/static/root/assets/{connector-logo-CbcC0T6i.js → connector-logo-Mp40HWsb.js} +1 -1
  21. package/dist/gateway/static/root/assets/{connectors-page-DpDmJ3Nx.js → connectors-page-i0cu5rg5.js} +1 -1
  22. package/dist/gateway/static/root/assets/{desktop-pet-CR7RGQff.js → desktop-pet-BtzUJ0d7.js} +1 -1
  23. package/dist/gateway/static/root/assets/{desktop-pet-settings-BudmCcPE.js → desktop-pet-settings-Dg0Duj8B.js} +1 -1
  24. package/dist/gateway/static/root/assets/{directory-picker-path-field-BLG2Oe6z.js → directory-picker-path-field-9f0k1wZq.js} +1 -1
  25. package/dist/gateway/static/root/assets/{dreaming-settings-BwBGC0SG.js → dreaming-settings-rdW7dsGP.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-debug-page-Pr65_hLC.js → extension-debug-page-C9zUnn-z.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-page-BuJDcoKn.js → extension-page-Cy5rfU0h.js} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-settings-page-B3802tn5.js → extension-settings-page-BjIquAw6.js} +1 -1
  29. package/dist/gateway/static/root/assets/{gateway-config-api-D-NVkfts.js → gateway-config-api-AQQmI6SW.js} +1 -1
  30. package/dist/gateway/static/root/assets/{gateway-settings-DIdsKVXv.js → gateway-settings-CYNmbqHj.js} +1 -1
  31. package/dist/gateway/static/root/assets/{goal-create-dialog-51j_DxgV.js → goal-create-dialog-CkXaDfN3.js} +1 -1
  32. package/dist/gateway/static/root/assets/{goal-detail-page-CeqMr5GF.js → goal-detail-page-FqDa5bfO.js} +1 -1
  33. package/dist/gateway/static/root/assets/{goals-config-api-BkVBh7ct.js → goals-config-api-CaVGtM6n.js} +1 -1
  34. package/dist/gateway/static/root/assets/{goals-page-lf3oM4Xa.js → goals-page-B-uBRGN6.js} +1 -1
  35. package/dist/gateway/static/root/assets/{goals-settings-Cg7MJLvx.js → goals-settings-C9J3MX0F.js} +1 -1
  36. package/dist/gateway/static/root/assets/{heartbeat-settings-BV3kJOcX.js → heartbeat-settings-C0qFLyew.js} +1 -1
  37. package/dist/gateway/static/root/assets/{index-B24B26og.js → index-DchbD-qM.js} +6 -6
  38. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B9K5rTZZ.js → keyboard-shortcuts-settings-BAZlRZMd.js} +1 -1
  39. package/dist/gateway/static/root/assets/{local-app-workbench-page-ehzr9CBG.js → local-app-workbench-page-DkJUQRyC.js} +1 -1
  40. package/dist/gateway/static/root/assets/{local-apps-page-BBx6mfAD.js → local-apps-page-PcQABi7F.js} +1 -1
  41. package/dist/gateway/static/root/assets/{logs-page-xuwEkusZ.js → logs-page-B-nXM29t.js} +1 -1
  42. package/dist/gateway/static/root/assets/{markdown-split-DRDddIt8.js → markdown-split-B6W1H0Fj.js} +1 -1
  43. package/dist/gateway/static/root/assets/{media-plugins-Dn3hxhOV.js → media-plugins-B-r9Tj74.js} +1 -1
  44. package/dist/gateway/static/root/assets/{notes-page-BtKHSb4z.js → notes-page-BNECY6Oy.js} +1 -1
  45. package/dist/gateway/static/root/assets/{notes-workbench-DXGXn5XF.js → notes-workbench-Be97S0Im.js} +2 -2
  46. package/dist/gateway/static/root/assets/{page-tabs-P2BEd1D8.js → page-tabs-BmUWVWO9.js} +1 -1
  47. package/dist/gateway/static/root/assets/{preview-open-alternatives-DrkjmW0F.js → preview-open-alternatives-a76A8p5R.js} +1 -1
  48. package/dist/gateway/static/root/assets/{product-open-page-CGzMEowb.js → product-open-page-BWhYrt-U.js} +1 -1
  49. package/dist/gateway/static/root/assets/{project-detail-page-IiE2ddWb.js → project-detail-page-md0ANZ-f.js} +1 -1
  50. package/dist/gateway/static/root/assets/{projects-page-DGOVhaYI.js → projects-page-D1MNrLcy.js} +1 -1
  51. package/dist/gateway/static/root/assets/{remote-access-hub-l_y_Shgl.js → remote-access-hub-GTuiqlJO.js} +1 -1
  52. package/dist/gateway/static/root/assets/{save-bar-controls-BkGrufxl.js → save-bar-controls-_ZMLDSUw.js} +1 -1
  53. package/dist/gateway/static/root/assets/{schedule-field-A8tDpL-9.js → schedule-field-D4ErObRc.js} +1 -1
  54. package/dist/gateway/static/root/assets/{schema-form-N6_hDWgx.js → schema-form-PqNSGlcH.js} +1 -1
  55. package/dist/gateway/static/root/assets/{sessions-page-CAX4ipPv.js → sessions-page-Ct0MVjNv.js} +1 -1
  56. package/dist/gateway/static/root/assets/settings-advanced-gate-QOUph2Oq.js +1 -0
  57. package/dist/gateway/static/root/assets/{settings-form-section-Bb6yYt5f.js → settings-form-section-BCKaoLhJ.js} +1 -1
  58. package/dist/gateway/static/root/assets/{settings-loading-skeleton-Q_Vh6HEl.js → settings-loading-skeleton-Dn95fked.js} +1 -1
  59. package/dist/gateway/static/root/assets/{settings-page-CjOtQpbR.js → settings-page-C8MPUl2Q.js} +1 -1
  60. package/dist/gateway/static/root/assets/{settings-page-layout-CtESNNdx.js → settings-page-layout-Bigc8-GR.js} +1 -1
  61. package/dist/gateway/static/root/assets/{setup-status-panel-CGxHXgJB.js → setup-status-panel-DFTcAHsd.js} +1 -1
  62. package/dist/gateway/static/root/assets/{share-preview-page-C65IsFmd.js → share-preview-page-xAjqaV4y.js} +1 -1
  63. package/dist/gateway/static/root/assets/{shares-settings-Ct3fcbHr.js → shares-settings-D2uvC_aM.js} +1 -1
  64. package/dist/gateway/static/root/assets/{skill-reload-api-ElA5kabh.js → skill-reload-api-DYtfQXB-.js} +1 -1
  65. package/dist/gateway/static/root/assets/{skills-page-FeieOEn3.js → skills-page-6dr3RRVW.js} +1 -1
  66. package/dist/gateway/static/root/assets/{system-settings-panel-CdUl2I0D.js → system-settings-panel-BJJQulg9.js} +1 -1
  67. package/dist/gateway/static/root/assets/{time-picker-EkhqT3fS.js → time-picker-DsuPUbRE.js} +1 -1
  68. package/dist/gateway/static/root/assets/{typed-models-lib-5xRRUE9Y.js → typed-models-lib-Djk3F3EL.js} +1 -1
  69. package/dist/gateway/static/root/assets/{user-context-page-DHiRvd4i.js → user-context-page-BC585kHK.js} +1 -1
  70. package/dist/gateway/static/root/assets/{voice-api-key-field-Bql0SPCp.js → voice-api-key-field-CvocOFat.js} +1 -1
  71. package/dist/gateway/static/root/assets/{work-discovery-page-BcmZux7y.js → work-discovery-page-DiMn6nrT.js} +1 -1
  72. package/dist/gateway/static/root/assets/{work-item-detail-page-D94Q5zVD.js → work-item-detail-page-CFBlQbPy.js} +1 -1
  73. package/dist/gateway/static/root/assets/{work-page-DdFBMDcB.js → work-page-CLJLKqsf.js} +1 -1
  74. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Dow7ZztR.js → workflow-run-setup-panel-DbQwWPjv.js} +1 -1
  75. package/dist/gateway/static/root/assets/{workflows-page-wOVSoduy.js → workflows-page-BJOhlABF.js} +1 -1
  76. package/dist/gateway/static/root/index.html +1 -1
  77. package/dist/package.js +1 -1
  78. package/dist/src/agent/agent-manager.js +12 -5
  79. package/dist/src/agent/models/manager.d.ts +5 -0
  80. package/dist/src/agent/models/manager.js +8 -0
  81. package/dist/src/agent/prompt/sections/messaging-runtime.js +1 -1
  82. package/dist/src/agent/prompt/sections/tooling.js +1 -1
  83. package/dist/src/agent/tools/communication.js +25 -7
  84. package/dist/src/channels/plugin-registry.js +6 -1
  85. package/dist/src/gateway/heartbeat/service.js +1 -1
  86. package/dist/src/heartbeat/index.js +1 -1
  87. package/dist/src/session/patch-metadata.d.ts +2 -0
  88. package/dist/src/session/patch-metadata.js +2 -0
  89. package/dist/src/tui/backends/embedded-backend.d.ts +2 -0
  90. package/dist/src/tui/backends/embedded-backend.js +46 -9
  91. package/dist/src/tui/backends/gateway-sse-backend.js +10 -3
  92. package/dist/src/tui/tui-backend.d.ts +2 -0
  93. package/dist/src/tui/tui-empty-session-cleanup.d.ts +11 -0
  94. package/dist/src/tui/tui-empty-session-cleanup.js +37 -0
  95. package/dist/src/tui/tui.js +43 -16
  96. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  97. package/package.json +1 -1
  98. package/dist/gateway/static/root/assets/settings-advanced-gate-eVlH9brU.js +0 -1
@@ -11,7 +11,7 @@
11
11
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=20260713-1" />
12
12
  <link rel="manifest" href="/site.webmanifest?v=20260713-1" />
13
13
  <title>XOPC — Turn goals into loops. Keep what matters moving.</title>
14
- <script type="module" crossorigin src="/assets/index-B24B26og.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-DchbD-qM.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-aKtaBQYM.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-zJ_50EbN.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BOUWij0V.js">
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.0.167";
2
+ var version = "0.0.168";
3
3
  //#endregion
4
4
  export { version };
@@ -649,7 +649,17 @@ var AgentManager = class {
649
649
  if (isMemorySubsystemEnabled(cfg) && shouldRegisterCuratedMemoryTool(cfg)) rt.builtinMemoryStore.loadFromDiskSync();
650
650
  const activeProjectContext = buildActiveProjectContextForPrompt(sessionKey);
651
651
  const activeSelfVerifyContext = this.getSelfVerifyPromptContext(sessionKey, profile.agentId);
652
- const { agent, registeredToolNames } = this.createAgentForProfile(sessionKey, profile, resolvedPath, rt, activeProjectContext, activeSelfVerifyContext);
652
+ const profileModelRef = profile.primaryModelRef?.trim() || this.defaultModel;
653
+ const modelManager = this.config.getModelManager?.();
654
+ const initialModelRef = modelManager?.resolveInitialModelForSession(sessionKey, profileModelRef, profile.fallbacks) ?? profileModelRef;
655
+ let created;
656
+ try {
657
+ created = this.createAgentForProfile(sessionKey, profile, initialModelRef, resolvedPath, rt, activeProjectContext, activeSelfVerifyContext);
658
+ } catch (err) {
659
+ modelManager?.clearSessionProfileDefault(sessionKey);
660
+ throw err;
661
+ }
662
+ const { agent, registeredToolNames } = created;
653
663
  this.agents.set(sessionKey, {
654
664
  agent,
655
665
  sessionKey,
@@ -663,8 +673,6 @@ var AgentManager = class {
663
673
  activeSelfVerifyContext,
664
674
  skillEnvPassthroughKeys: /* @__PURE__ */ new Set()
665
675
  });
666
- const modelRef = profile.primaryModelRef?.trim() || this.defaultModel;
667
- this.config.getModelManager?.().setSessionProfileDefault(sessionKey, modelRef, profile.fallbacks);
668
676
  log.debug({
669
677
  sessionKey,
670
678
  totalAgents: this.agents.size,
@@ -822,8 +830,7 @@ var AgentManager = class {
822
830
  const inst = sessionKey?.trim() ? this.agents.get(sessionKey.trim()) : void 0;
823
831
  return this.resolveCapabilityCatalogForInstance(inst);
824
832
  }
825
- createAgentForProfile(sessionKey, profile, resolvedWorkspacePath, rt, activeProjectContext, activeSelfVerifyContext) {
826
- const modelRef = profile.primaryModelRef?.trim() || this.defaultModel;
833
+ createAgentForProfile(sessionKey, profile, modelRef, resolvedWorkspacePath, rt, activeProjectContext, activeSelfVerifyContext) {
827
834
  const model = this.resolveModelStringToModel(modelRef);
828
835
  let agent;
829
836
  const contextFiles = this.resolveContextFilesForSession(sessionKey, profile);
@@ -31,6 +31,11 @@ export declare class ModelManager {
31
31
  * Cleared by {@link updateFromConfig} or {@link clearSessionProfileDefault}.
32
32
  */
33
33
  setSessionProfileDefault(sessionKey: string, modelRef: string, fallbacks?: string[]): void;
34
+ /**
35
+ * Register the profile model and return the model that should initialize the
36
+ * session agent. An existing per-session override always wins.
37
+ */
38
+ resolveInitialModelForSession(sessionKey: string, profileModelRef: string, fallbacks?: string[]): string;
34
39
  clearSessionProfileDefault(sessionKey: string): void;
35
40
  /**
36
41
  * Get current model name
@@ -44,6 +44,14 @@ var ModelManager = class {
44
44
  if (cleanFallbacks.length > 0) this.sessionProfileFallbacks.set(sessionKey, cleanFallbacks);
45
45
  else this.sessionProfileFallbacks.delete(sessionKey);
46
46
  }
47
+ /**
48
+ * Register the profile model and return the model that should initialize the
49
+ * session agent. An existing per-session override always wins.
50
+ */
51
+ resolveInitialModelForSession(sessionKey, profileModelRef, fallbacks = []) {
52
+ this.setSessionProfileDefault(sessionKey, profileModelRef, fallbacks);
53
+ return this.getModelForSession(sessionKey);
54
+ }
47
55
  clearSessionProfileDefault(sessionKey) {
48
56
  this.sessionProfileDefaults.delete(sessionKey);
49
57
  this.sessionProfileFallbacks.delete(sessionKey);
@@ -3,7 +3,7 @@ import { NO_REPLY } from "../../../heartbeat/tokens.js";
3
3
  function buildMessagingSection(params) {
4
4
  if (params.isMinimal || params.channels.length === 0) return "";
5
5
  const channelList = params.channels.join(", ");
6
- const messageToolLine = params.hasSendMessage ? `- Use \`send_message\` for proactive sends + channel actions` : "- Outbound messaging tools are not available in this session";
6
+ const messageToolLine = params.hasSendMessage ? `- Use \`send_message\` for proactive sends + channel actions; provide \`channel\` and \`chat_id\` together for a cross-channel destination` : "- Outbound messaging tools are not available in this session";
7
7
  return [
8
8
  "## Messaging",
9
9
  `- Reply in current session → automatically routes to the source channel (${channelList})`,
@@ -57,7 +57,7 @@ const CORE_TOOL_SUMMARIES = {
57
57
  web_fetch: "Fetch and extract readable content from a URL",
58
58
  web_extract: "Extract structured content from a URL",
59
59
  browser_use: "Control the configured browser",
60
- send_message: "Send messages to the current channel",
60
+ send_message: "Send messages to the current conversation or an explicit configured channel",
61
61
  send_media: "Send media attachments to the current channel",
62
62
  memory_search: "Semantic search over indexed memory sources",
63
63
  memory_get: "Read specific lines from memory sources returned by search",
@@ -6,6 +6,9 @@ init_logger();
6
6
  const log = createLogger("MessageTool");
7
7
  const MessageSendSchema = Type.Object({
8
8
  content: Type.String({ description: "The message content to send" }),
9
+ channel: Type.Optional(Type.String({ description: "Configured destination channel (for example weixin or telegram). Defaults to the current channel." })),
10
+ chat_id: Type.Optional(Type.String({ description: "Destination chat/peer id. Required when channel is provided." })),
11
+ accountId: Type.Optional(Type.String({ description: "Optional account id for a multi-account destination channel." })),
9
12
  mediaUrl: Type.Optional(Type.String({ description: "URL of the media to send" })),
10
13
  mediaType: Type.Optional(Type.Enum({
11
14
  photo: "photo",
@@ -36,7 +39,9 @@ const MessageSendSchema = Type.Object({
36
39
  function createMessageTool(bus, getContext) {
37
40
  return {
38
41
  name: "send_message",
39
- description: `Send a message to the user.
42
+ description: `Send a message to the current conversation or an explicit configured channel.
43
+
44
+ For a proactive cross-channel send, provide both \`channel\` and \`chat_id\` (for example \`channel: "weixin"\`). Use \`accountId\` when the destination has multiple configured accounts. Omit destination fields to reply to the current conversation.
40
45
 
41
46
  TTS (Text-to-Speech) is handled automatically by the system based on configuration:
42
47
  - trigger=off: Never use voice
@@ -50,18 +55,31 @@ When TTS is enabled, you may also use the \`text_to_speech\` tool to send a stan
50
55
  async execute(_toolCallId, params, _signal) {
51
56
  const p = params;
52
57
  const ctx = getContext();
53
- if (!ctx) return {
58
+ const requestedChannel = p.channel?.trim();
59
+ const requestedChatId = p.chat_id?.trim();
60
+ if (Boolean(requestedChannel) !== Boolean(requestedChatId)) return {
61
+ content: [{
62
+ type: "text",
63
+ text: "Error: channel and chat_id must be provided together"
64
+ }],
65
+ details: {}
66
+ };
67
+ if (!ctx && (!requestedChannel || !requestedChatId)) return {
54
68
  content: [{
55
69
  type: "text",
56
70
  text: "Error: No active conversation context"
57
71
  }],
58
72
  details: {}
59
73
  };
74
+ const channel = requestedChannel || ctx.channel;
75
+ const chatId = requestedChatId || ctx.chatId;
76
+ const accountId = p.accountId?.trim();
60
77
  try {
61
78
  const msg = {
62
- channel: ctx.channel,
63
- chat_id: ctx.chatId,
79
+ channel,
80
+ chat_id: chatId,
64
81
  content: p.content,
82
+ ...accountId ? { metadata: { accountId } } : {},
65
83
  mediaUrl: p.mediaUrl,
66
84
  mediaType: p.mediaType,
67
85
  replyToMessageId: p.replyTo,
@@ -83,10 +101,10 @@ When TTS is enabled, you may also use the \`text_to_speech\` tool to send a stan
83
101
  log.error({
84
102
  err: error,
85
103
  errorMessage: em,
86
- channel: ctx.channel,
87
- chatId: ctx.chatId,
104
+ channel,
105
+ chatId,
88
106
  contentPreview: String(p.content ?? "").slice(0, 100)
89
- }, `send_message: outbound publish failed (${ctx.channel}/${ctx.chatId}): ${em}`);
107
+ }, `send_message: outbound publish failed (${channel}/${chatId}): ${em}`);
90
108
  return {
91
109
  content: [{
92
110
  type: "text",
@@ -7,7 +7,12 @@ const log = createLogger("ChannelPluginRegistry");
7
7
  var ChannelPluginRegistry = class {
8
8
  plugins = /* @__PURE__ */ new Map();
9
9
  register(plugin) {
10
- if (this.plugins.has(plugin.id)) log.warn({ channel: plugin.id }, "Channel plugin already registered, overwriting");
10
+ const current = this.plugins.get(plugin.id);
11
+ if (current === plugin) {
12
+ log.debug({ channel: plugin.id }, "Channel plugin already registered");
13
+ return;
14
+ }
15
+ if (current) log.warn({ channel: plugin.id }, "Channel plugin already registered, overwriting");
11
16
  this.plugins.set(plugin.id, plugin);
12
17
  syncChannelPluginsFromManager(this.all());
13
18
  log.debug({ channel: plugin.id }, "Registered channel plugin");
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { shouldSilence, stripHeartbeatToken } from "../../heartbeat/tokens.js";
4
- import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
5
4
  import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
6
5
  import { isHeartbeatContentEmpty } from "../../heartbeat/content-check.js";
6
+ import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
7
7
  import { createHeartbeatWake } from "../../heartbeat/wake.js";
8
8
  import { resolveHeartbeatMdPath } from "../workspace-heartbeat-path.js";
9
9
  import { readFile } from "fs/promises";
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
2
- import { appendCronEventLines } from "./event-prompt.js";
3
2
  import { isWithinActiveHours } from "./active-hours.js";
4
3
  import { isHeartbeatContentEmpty } from "./content-check.js";
4
+ import { appendCronEventLines } from "./event-prompt.js";
5
5
  import { createHeartbeatWake } from "./wake.js";
6
6
  export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
@@ -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.167",
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",
@@ -1 +0,0 @@
1
- import{ti as e}from"./index-B24B26og.js";function t(t){let{children:n,fallback:r}=t,i=r===void 0?null:r;return e()?n:i}export{t};