@xopcai/xopc 0.0.47 → 0.0.48

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 (62) hide show
  1. package/dist/extensions/telegram/src/command-handler.js +1 -1
  2. package/dist/extensions/telegram/src/command-handler.js.map +1 -1
  3. package/dist/extensions/telegram/xopc.extension.json +1 -1
  4. package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js → agents-BdISC5UA.js} +2 -2
  5. package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js.map → agents-BdISC5UA.js.map} +1 -1
  6. package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js → apps-page-CXU_Jg95.js} +2 -2
  7. package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js.map → apps-page-CXU_Jg95.js.map} +1 -1
  8. package/dist/gateway/static/root/assets/{channels-settings-CjUmKQrC.js → channels-settings-Doe1ciOW.js} +2 -2
  9. package/dist/gateway/static/root/assets/{channels-settings-CjUmKQrC.js.map → channels-settings-Doe1ciOW.js.map} +1 -1
  10. package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js → cron-dreaming-jobs-C-V4_3vz.js} +2 -2
  11. package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js.map → cron-dreaming-jobs-C-V4_3vz.js.map} +1 -1
  12. package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js → cron-page-CIIy81K6.js} +2 -2
  13. package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js.map → cron-page-CIIy81K6.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{dist-wDej8fSi.js → dist-VW7dXc5X.js} +2 -2
  15. package/dist/gateway/static/root/assets/{dist-wDej8fSi.js.map → dist-VW7dXc5X.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js → extension-debug-page-Cslwx62-.js} +2 -2
  17. package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js.map → extension-debug-page-Cslwx62-.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js → extension-page-Dzyebr6T.js} +2 -2
  19. package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js.map → extension-page-Dzyebr6T.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js → extension-settings-page-B07uetL_.js} +2 -2
  21. package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js.map → extension-settings-page-B07uetL_.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js → heartbeat-config-api-kmvGNriW.js} +2 -2
  23. package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js.map → heartbeat-config-api-kmvGNriW.js.map} +1 -1
  24. package/dist/gateway/static/root/assets/{index-DeELk--t.js → index-DW6JvymK.js} +11 -11
  25. package/dist/gateway/static/root/assets/{index-DeELk--t.js.map → index-DW6JvymK.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js → logs-page-BiRnV2Ex.js} +2 -2
  27. package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js.map → logs-page-BiRnV2Ex.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js → sessions-page-DQq0QaQh.js} +2 -2
  29. package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js.map → sessions-page-DQq0QaQh.js.map} +1 -1
  30. package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js → settings-page-BV_l8nJ3.js} +2 -2
  31. package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js.map → settings-page-BV_l8nJ3.js.map} +1 -1
  32. package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js → skills-page-Bv0pphDM.js} +2 -2
  33. package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js.map → skills-page-Bv0pphDM.js.map} +1 -1
  34. package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js → use-image-provider-credentials-BPcW1K0N.js} +2 -2
  35. package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js.map → use-image-provider-credentials-BPcW1K0N.js.map} +1 -1
  36. package/dist/gateway/static/root/index.html +1 -1
  37. package/dist/package.js +1 -1
  38. package/dist/src/agent/goals/checklist-judge.js +21 -17
  39. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  40. package/dist/src/agent/goals/evaluate-turn.js +6 -11
  41. package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
  42. package/dist/src/agent/goals/judge.d.ts +16 -0
  43. package/dist/src/agent/goals/judge.js +61 -13
  44. package/dist/src/agent/goals/judge.js.map +1 -1
  45. package/dist/src/agent/goals/state.d.ts +7 -0
  46. package/dist/src/agent/goals/state.js +24 -1
  47. package/dist/src/agent/goals/state.js.map +1 -1
  48. package/dist/src/chat-commands/builtins/model.d.ts +2 -2
  49. package/dist/src/chat-commands/builtins/model.js +10 -8
  50. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  51. package/dist/src/chat-commands/builtins/system.js +1 -1
  52. package/dist/src/chat-commands/builtins/system.js.map +1 -1
  53. package/dist/src/tui/backends/embedded-backend.d.ts +1 -1
  54. package/dist/src/tui/backends/embedded-backend.js +24 -3
  55. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  56. package/dist/src/tui/backends/gateway-sse-backend.js +36 -10
  57. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  58. package/dist/src/tui/tui-commands.js +1 -1
  59. package/dist/src/tui/tui-commands.js.map +1 -1
  60. package/dist/src/tui/tui.js +12 -1
  61. package/dist/src/tui/tui.js.map +1 -1
  62. package/package.json +1 -1
@@ -1,10 +1,45 @@
1
- import { init_providers, resolveModel } from "../../providers/index.js";
1
+ import { getApiKey, init_providers, resolveModel } from "../../providers/index.js";
2
2
  import { JUDGE_REASON_EN } from "../../i18n/goals-bundle.js";
3
3
  import { goalUiLocaleOrFallback, judgeFreeformBuiltinMessages, judgeResponseLanguageNote, localizeJudgeReasonText } from "./goal-locale.js";
4
4
  import { complete } from "@earendil-works/pi-ai";
5
5
  //#region src/agent/goals/judge.ts
6
6
  init_providers();
7
7
  const JUDGE_RESPONSE_SNIPPET_CHARS = 4e3;
8
+ /**
9
+ * Extract visible text from a pi-ai `AssistantMessage.content` array.
10
+ * Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`
11
+ * (`type: 'thinking'`) when the text blocks are empty — reasoning models
12
+ * (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside
13
+ * thinking blocks, leaving the text portion blank.
14
+ */
15
+ function extractAssistantText(content) {
16
+ if (!Array.isArray(content)) return "";
17
+ let textParts = "";
18
+ let thinkingParts = "";
19
+ for (const block of content) {
20
+ if (!block || typeof block !== "object") continue;
21
+ const typed = block;
22
+ const blockType = typed.type;
23
+ if (blockType === "text" && typeof typed.text === "string") textParts += typed.text;
24
+ else if (blockType === "thinking") {
25
+ const thinking = typeof typed.thinking === "string" ? typed.thinking : typeof typed.text === "string" ? typed.text : "";
26
+ thinkingParts += thinking;
27
+ }
28
+ }
29
+ return textParts.trim() ? textParts : thinkingParts;
30
+ }
31
+ /**
32
+ * Strip markdown code fences (opening AND closing) from raw model output.
33
+ * Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.
34
+ */
35
+ function stripCodeFences(raw) {
36
+ let text = raw.trim();
37
+ const openMatch = text.match(/^`{3,}[^\n]*\n?/);
38
+ if (openMatch) text = text.slice(openMatch[0].length);
39
+ const closeMatch = text.match(/\n?`{3,}\s*$/);
40
+ if (closeMatch) text = text.slice(0, -closeMatch[0].length);
41
+ return text.trim();
42
+ }
8
43
  /** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */
9
44
  const JUDGE_SYSTEM_PROMPT = "You are a strict judge evaluating whether an autonomous agent has achieved a user's stated goal. You receive the goal text and the agent's most recent response. Your only job is to decide whether the goal is fully satisfied based on that response.\n\nA goal is DONE only when:\n- The response explicitly confirms the goal was completed, OR\n- The response clearly shows the final deliverable was produced, OR\n- The response explains the goal is unachievable / blocked / needs user input (treat this as DONE with reason describing the block).\n\nOtherwise the goal is NOT done — CONTINUE.\n\nReply ONLY with a single JSON object on one line:\n{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}";
10
45
  const JUDGE_USER_PROMPT_TEMPLATE = "Goal:\n{goal}\n\nAgent's most recent response:\n{response}\n\nIs the goal satisfied?";
@@ -14,6 +49,21 @@ function truncateGoalText(text, limit) {
14
49
  if (text.length <= limit) return text;
15
50
  return text.slice(0, limit) + "… [truncated]";
16
51
  }
52
+ async function resolveGoalJudgeApiKey(model) {
53
+ try {
54
+ return await getApiKey(model.provider);
55
+ } catch {
56
+ return;
57
+ }
58
+ }
59
+ function getAssistantMessageErrorReason(message) {
60
+ if (!message || typeof message !== "object") return null;
61
+ const record = message;
62
+ const stopReason = record.stopReason;
63
+ const errorMessage = typeof record.errorMessage === "string" ? record.errorMessage.trim() : "";
64
+ if (stopReason === "error") return errorMessage || "Judge model call failed.";
65
+ return null;
66
+ }
17
67
  /** Parse judge JSON — fail-open to **continue** (Hermes semantics). */
18
68
  function parseJudgeResponseFailOpen(raw) {
19
69
  if (!raw?.trim()) return {
@@ -21,12 +71,7 @@ function parseJudgeResponseFailOpen(raw) {
21
71
  reason: JUDGE_REASON_EN.judge_returned_empty,
22
72
  parseFailed: true
23
73
  };
24
- let text = raw.trim();
25
- if (text.startsWith("```")) {
26
- text = text.replace(/^`+/, "");
27
- const nl = text.indexOf("\n");
28
- if (nl !== -1) text = text.slice(nl + 1);
29
- }
74
+ const text = stripCodeFences(raw);
30
75
  let data = null;
31
76
  try {
32
77
  data = JSON.parse(text);
@@ -98,16 +143,19 @@ async function judgeGoalHermesStyle(goal, lastResponse, judgeModelRef, signal, o
98
143
  content: `${JUDGE_SYSTEM_PROMPT}\n\n${userContent}`,
99
144
  timestamp: Date.now()
100
145
  };
146
+ const apiKey = await resolveGoalJudgeApiKey(model);
101
147
  const result = await complete(model, { messages: [combinedUser] }, {
148
+ apiKey,
102
149
  maxTokens: 200,
103
150
  temperature: 0,
104
151
  signal: merged
105
152
  });
106
- let text = "";
107
- if (Array.isArray(result.content)) {
108
- for (const c of result.content) if (c && typeof c === "object" && c.type === "text") text += String(c.text || "");
109
- }
110
- const { done, reason, parseFailed } = parseJudgeResponseFailOpen(text);
153
+ if (getAssistantMessageErrorReason(result)) return {
154
+ verdict: "continue",
155
+ reason: b.callFailed,
156
+ parseFailed: false
157
+ };
158
+ const { done, reason, parseFailed } = parseJudgeResponseFailOpen(extractAssistantText(result.content));
111
159
  const reasonOut = localizeJudgeReasonText(reason, locale);
112
160
  return {
113
161
  verdict: done ? "done" : "continue",
@@ -125,6 +173,6 @@ async function judgeGoalHermesStyle(goal, lastResponse, judgeModelRef, signal, o
125
173
  }
126
174
  }
127
175
  //#endregion
128
- export { DEFAULT_JUDGE_TIMEOUT_MS, JUDGE_SYSTEM_PROMPT, JUDGE_USER_PROMPT_TEMPLATE, judgeGoalHermesStyle, parseJudgeResponseFailOpen, truncateGoalText };
176
+ export { DEFAULT_JUDGE_TIMEOUT_MS, JUDGE_SYSTEM_PROMPT, JUDGE_USER_PROMPT_TEMPLATE, extractAssistantText, getAssistantMessageErrorReason, judgeGoalHermesStyle, parseJudgeResponseFailOpen, resolveGoalJudgeApiKey, stripCodeFences, truncateGoalText };
129
177
 
130
178
  //# sourceMappingURL=judge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"judge.js","names":[],"sources":["../../../../src/agent/goals/judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai';\n\nimport { resolveModel } from '../../providers/index.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeFreeformBuiltinMessages,\n JUDGE_REASON_EN,\n judgeResponseLanguageNote,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst JUDGE_RESPONSE_SNIPPET_CHARS = 4000;\n\n/** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */\nexport const JUDGE_SYSTEM_PROMPT =\n 'You are a strict judge evaluating whether an autonomous agent has ' +\n \"achieved a user's stated goal. You receive the goal text and the \" +\n \"agent's most recent response. Your only job is to decide whether \" +\n 'the goal is fully satisfied based on that response.\\n\\n' +\n 'A goal is DONE only when:\\n' +\n '- The response explicitly confirms the goal was completed, OR\\n' +\n '- The response clearly shows the final deliverable was produced, OR\\n' +\n '- The response explains the goal is unachievable / blocked / needs ' +\n 'user input (treat this as DONE with reason describing the block).\\n\\n' +\n 'Otherwise the goal is NOT done — CONTINUE.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}';\n\nexport const JUDGE_USER_PROMPT_TEMPLATE =\n 'Goal:\\n{goal}\\n\\n' + \"Agent's most recent response:\\n{response}\\n\\n\" + 'Is the goal satisfied?';\n\nexport const DEFAULT_JUDGE_TIMEOUT_MS = 60_000;\n\nexport function truncateGoalText(text: string, limit: number): string {\n if (!text) return '';\n if (text.length <= limit) return text;\n return text.slice(0, limit) + '… [truncated]';\n}\n\n/** Parse judge JSON — fail-open to **continue** (Hermes semantics). */\nexport function parseJudgeResponseFailOpen(raw: string): {\n done: boolean;\n reason: string;\n parseFailed: boolean;\n} {\n if (!raw?.trim()) {\n return { done: false, reason: JUDGE_REASON_EN.judge_returned_empty, parseFailed: true };\n }\n\n let text = raw.trim();\n if (text.startsWith('```')) {\n text = text.replace(/^`+/, '');\n const nl = text.indexOf('\\n');\n if (nl !== -1) text = text.slice(nl + 1);\n }\n\n let data: Record<string, unknown> | null = null;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(text.slice(start, end + 1)) as Record<string, unknown>;\n } catch {\n data = null;\n }\n }\n }\n\n if (!data || typeof data !== 'object') {\n return { done: false, reason: JUDGE_REASON_EN.judge_reply_not_json, parseFailed: true };\n }\n\n const doneVal = data.done;\n let done: boolean;\n if (typeof doneVal === 'string') {\n done = ['true', 'yes', '1', 'done'].includes(doneVal.trim().toLowerCase());\n } else {\n done = Boolean(doneVal);\n }\n const reason = typeof data.reason === 'string' ? data.reason.trim() : '';\n return { done, reason: reason || JUDGE_REASON_EN.no_reason_provided, parseFailed: false };\n}\n\nexport type GoalJudgeVerdict = 'done' | 'continue' | 'skipped';\n\n/**\n * Ask the configured model whether the goal is satisfied.\n * Fail-open: any error → `continue` (Hermes — turn budget is the backstop).\n */\nexport async function judgeGoalHermesStyle(\n goal: string,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { judgeTimeoutMs?: number; uiLocale?: GoalUiLocale },\n): Promise<{ verdict: GoalJudgeVerdict; reason: string; parseFailed: boolean }> {\n const locale = goalUiLocaleOrFallback(opts?.uiLocale);\n const b = judgeFreeformBuiltinMessages(locale);\n\n if (!goal.trim()) {\n return { verdict: 'skipped', reason: b.emptyGoal, parseFailed: false };\n }\n if (!lastResponse.trim()) {\n return { verdict: 'continue', reason: b.emptyResponse, parseFailed: false };\n }\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(judgeModelRef);\n } catch {\n return { verdict: 'continue', reason: b.noModel, parseFailed: false };\n }\n\n const userContent =\n JUDGE_USER_PROMPT_TEMPLATE.replace('{goal}', truncateGoalText(goal, 2000)).replace(\n '{response}',\n truncateGoalText(lastResponse, JUDGE_RESPONSE_SNIPPET_CHARS),\n ) + judgeResponseLanguageNote(locale);\n\n const timeoutMs =\n typeof opts?.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;\n\n try {\n const combinedUser: UserMessage = {\n role: 'user',\n content: `${JUDGE_SYSTEM_PROMPT}\\n\\n${userContent}`,\n timestamp: Date.now(),\n };\n\n const result = await complete(\n model,\n {\n messages: [combinedUser],\n },\n { maxTokens: 200, temperature: 0, signal: merged },\n );\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n const { done, reason, parseFailed } = parseJudgeResponseFailOpen(text);\n const reasonOut = localizeJudgeReasonText(reason, locale);\n return { verdict: done ? 'done' : 'continue', reason: reasonOut, parseFailed };\n } catch {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"judge.js","names":[],"sources":["../../../../src/agent/goals/judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai';\n\nimport { getApiKey, resolveModel } from '../../providers/index.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeFreeformBuiltinMessages,\n JUDGE_REASON_EN,\n judgeResponseLanguageNote,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst JUDGE_RESPONSE_SNIPPET_CHARS = 4000;\n\n/**\n * Extract visible text from a pi-ai `AssistantMessage.content` array.\n * Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`\n * (`type: 'thinking'`) when the text blocks are empty — reasoning models\n * (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside\n * thinking blocks, leaving the text portion blank.\n */\nexport function extractAssistantText(content: unknown): string {\n if (!Array.isArray(content)) return '';\n\n let textParts = '';\n let thinkingParts = '';\n\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n const typed = block as Record<string, unknown>;\n const blockType = typed.type;\n if (blockType === 'text' && typeof typed.text === 'string') {\n textParts += typed.text;\n } else if (blockType === 'thinking') {\n const thinking =\n typeof typed.thinking === 'string'\n ? typed.thinking\n : typeof typed.text === 'string'\n ? typed.text\n : '';\n thinkingParts += thinking;\n }\n }\n\n // Prefer text blocks; fall back to thinking blocks when text is empty.\n return textParts.trim() ? textParts : thinkingParts;\n}\n\n/**\n * Strip markdown code fences (opening AND closing) from raw model output.\n * Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.\n */\nexport function stripCodeFences(raw: string): string {\n let text = raw.trim();\n\n // Remove opening code fence: ```<optional-lang>\\n\n const openMatch = text.match(/^`{3,}[^\\n]*\\n?/);\n if (openMatch) {\n text = text.slice(openMatch[0].length);\n }\n\n // Remove closing code fence: \\n```<optional-whitespace>\n const closeMatch = text.match(/\\n?`{3,}\\s*$/);\n if (closeMatch) {\n text = text.slice(0, -closeMatch[0].length);\n }\n\n return text.trim();\n}\n\n/** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */\nexport const JUDGE_SYSTEM_PROMPT =\n 'You are a strict judge evaluating whether an autonomous agent has ' +\n \"achieved a user's stated goal. You receive the goal text and the \" +\n \"agent's most recent response. Your only job is to decide whether \" +\n 'the goal is fully satisfied based on that response.\\n\\n' +\n 'A goal is DONE only when:\\n' +\n '- The response explicitly confirms the goal was completed, OR\\n' +\n '- The response clearly shows the final deliverable was produced, OR\\n' +\n '- The response explains the goal is unachievable / blocked / needs ' +\n 'user input (treat this as DONE with reason describing the block).\\n\\n' +\n 'Otherwise the goal is NOT done — CONTINUE.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}';\n\nexport const JUDGE_USER_PROMPT_TEMPLATE =\n 'Goal:\\n{goal}\\n\\n' + \"Agent's most recent response:\\n{response}\\n\\n\" + 'Is the goal satisfied?';\n\nexport const DEFAULT_JUDGE_TIMEOUT_MS = 60_000;\n\nexport function truncateGoalText(text: string, limit: number): string {\n if (!text) return '';\n if (text.length <= limit) return text;\n return text.slice(0, limit) + '… [truncated]';\n}\n\nexport async function resolveGoalJudgeApiKey(\n model: ReturnType<typeof resolveModel>,\n): Promise<string | undefined> {\n try {\n return await getApiKey(model.provider);\n } catch {\n return undefined;\n }\n}\n\nexport function getAssistantMessageErrorReason(message: unknown): string | null {\n if (!message || typeof message !== 'object') return null;\n\n const record = message as Record<string, unknown>;\n const stopReason = record.stopReason;\n const errorMessage = typeof record.errorMessage === 'string' ? record.errorMessage.trim() : '';\n\n if (stopReason === 'error') {\n return errorMessage || 'Judge model call failed.';\n }\n\n return null;\n}\n\n/** Parse judge JSON — fail-open to **continue** (Hermes semantics). */\nexport function parseJudgeResponseFailOpen(raw: string): {\n done: boolean;\n reason: string;\n parseFailed: boolean;\n} {\n if (!raw?.trim()) {\n return { done: false, reason: JUDGE_REASON_EN.judge_returned_empty, parseFailed: true };\n }\n\n const text = stripCodeFences(raw);\n\n let data: Record<string, unknown> | null = null;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(text.slice(start, end + 1)) as Record<string, unknown>;\n } catch {\n data = null;\n }\n }\n }\n\n if (!data || typeof data !== 'object') {\n return { done: false, reason: JUDGE_REASON_EN.judge_reply_not_json, parseFailed: true };\n }\n\n const doneVal = data.done;\n let done: boolean;\n if (typeof doneVal === 'string') {\n done = ['true', 'yes', '1', 'done'].includes(doneVal.trim().toLowerCase());\n } else {\n done = Boolean(doneVal);\n }\n const reason = typeof data.reason === 'string' ? data.reason.trim() : '';\n return { done, reason: reason || JUDGE_REASON_EN.no_reason_provided, parseFailed: false };\n}\n\nexport type GoalJudgeVerdict = 'done' | 'continue' | 'skipped';\n\n/**\n * Ask the configured model whether the goal is satisfied.\n * Fail-open: any error → `continue` (Hermes — turn budget is the backstop).\n */\nexport async function judgeGoalHermesStyle(\n goal: string,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { judgeTimeoutMs?: number; uiLocale?: GoalUiLocale },\n): Promise<{ verdict: GoalJudgeVerdict; reason: string; parseFailed: boolean }> {\n const locale = goalUiLocaleOrFallback(opts?.uiLocale);\n const b = judgeFreeformBuiltinMessages(locale);\n\n if (!goal.trim()) {\n return { verdict: 'skipped', reason: b.emptyGoal, parseFailed: false };\n }\n if (!lastResponse.trim()) {\n return { verdict: 'continue', reason: b.emptyResponse, parseFailed: false };\n }\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(judgeModelRef);\n } catch {\n return { verdict: 'continue', reason: b.noModel, parseFailed: false };\n }\n\n const userContent =\n JUDGE_USER_PROMPT_TEMPLATE.replace('{goal}', truncateGoalText(goal, 2000)).replace(\n '{response}',\n truncateGoalText(lastResponse, JUDGE_RESPONSE_SNIPPET_CHARS),\n ) + judgeResponseLanguageNote(locale);\n\n const timeoutMs =\n typeof opts?.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;\n\n try {\n const combinedUser: UserMessage = {\n role: 'user',\n content: `${JUDGE_SYSTEM_PROMPT}\\n\\n${userContent}`,\n timestamp: Date.now(),\n };\n\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n {\n messages: [combinedUser],\n },\n { apiKey, maxTokens: 200, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n }\n\n const text = extractAssistantText(result.content);\n const { done, reason, parseFailed } = parseJudgeResponseFailOpen(text);\n const reasonOut = localizeJudgeReasonText(reason, locale);\n return { verdict: done ? 'done' : 'continue', reason: reasonOut, parseFailed };\n } catch {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":";;;;;gBAGmE;AAWnE,MAAM,+BAA+B;;;;;;;;AASrC,SAAgB,qBAAqB,SAA0B;AAC7D,KAAI,CAAC,MAAM,QAAQ,QAAQ,CAAE,QAAO;CAEpC,IAAI,YAAY;CAChB,IAAI,gBAAgB;AAEpB,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU;EACzC,MAAM,QAAQ;EACd,MAAM,YAAY,MAAM;AACxB,MAAI,cAAc,UAAU,OAAO,MAAM,SAAS,SAChD,cAAa,MAAM;WACV,cAAc,YAAY;GACnC,MAAM,WACJ,OAAO,MAAM,aAAa,WACtB,MAAM,WACN,OAAO,MAAM,SAAS,WACpB,MAAM,OACN;AACR,oBAAiB;;;AAKrB,QAAO,UAAU,MAAM,GAAG,YAAY;;;;;;AAOxC,SAAgB,gBAAgB,KAAqB;CACnD,IAAI,OAAO,IAAI,MAAM;CAGrB,MAAM,YAAY,KAAK,MAAM,kBAAkB;AAC/C,KAAI,UACF,QAAO,KAAK,MAAM,UAAU,GAAG,OAAO;CAIxC,MAAM,aAAa,KAAK,MAAM,eAAe;AAC7C,KAAI,WACF,QAAO,KAAK,MAAM,GAAG,CAAC,WAAW,GAAG,OAAO;AAG7C,QAAO,KAAK,MAAM;;;AAIpB,MAAa,sBACX;AAaF,MAAa,6BACX;AAEF,MAAa,2BAA2B;AAExC,SAAgB,iBAAiB,MAAc,OAAuB;AACpE,KAAI,CAAC,KAAM,QAAO;AAClB,KAAI,KAAK,UAAU,MAAO,QAAO;AACjC,QAAO,KAAK,MAAM,GAAG,MAAM,GAAG;;AAGhC,eAAsB,uBACpB,OAC6B;AAC7B,KAAI;AACF,SAAO,MAAM,UAAU,MAAM,SAAS;SAChC;AACN;;;AAIJ,SAAgB,+BAA+B,SAAiC;AAC9E,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;CAEpD,MAAM,SAAS;CACf,MAAM,aAAa,OAAO;CAC1B,MAAM,eAAe,OAAO,OAAO,iBAAiB,WAAW,OAAO,aAAa,MAAM,GAAG;AAE5F,KAAI,eAAe,QACjB,QAAO,gBAAgB;AAGzB,QAAO;;;AAIT,SAAgB,2BAA2B,KAIzC;AACA,KAAI,CAAC,KAAK,MAAM,CACd,QAAO;EAAE,MAAM;EAAO,QAAQ,gBAAgB;EAAsB,aAAa;EAAM;CAGzF,MAAM,OAAO,gBAAgB,IAAI;CAEjC,IAAI,OAAuC;AAC3C,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;EACN,MAAM,QAAQ,KAAK,QAAQ,IAAI;EAC/B,MAAM,MAAM,KAAK,YAAY,IAAI;AACjC,MAAI,UAAU,MAAM,MAAM,MACxB,KAAI;AACF,UAAO,KAAK,MAAM,KAAK,MAAM,OAAO,MAAM,EAAE,CAAC;UACvC;AACN,UAAO;;;AAKb,KAAI,CAAC,QAAQ,OAAO,SAAS,SAC3B,QAAO;EAAE,MAAM;EAAO,QAAQ,gBAAgB;EAAsB,aAAa;EAAM;CAGzF,MAAM,UAAU,KAAK;CACrB,IAAI;AACJ,KAAI,OAAO,YAAY,SACrB,QAAO;EAAC;EAAQ;EAAO;EAAK;EAAO,CAAC,SAAS,QAAQ,MAAM,CAAC,aAAa,CAAC;KAE1E,QAAO,QAAQ,QAAQ;CAEzB,MAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,OAAO,MAAM,GAAG;AACtE,QAAO;EAAE;EAAM,QAAQ,UAAU,gBAAgB;EAAoB,aAAa;EAAO;;;;;;AAS3F,eAAsB,qBACpB,MACA,cACA,eACA,QACA,MAC8E;CAC9E,MAAM,SAAS,uBAAuB,MAAM,SAAS;CACrD,MAAM,IAAI,6BAA6B,OAAO;AAE9C,KAAI,CAAC,KAAK,MAAM,CACd,QAAO;EAAE,SAAS;EAAW,QAAQ,EAAE;EAAW,aAAa;EAAO;AAExE,KAAI,CAAC,aAAa,MAAM,CACtB,QAAO;EAAE,SAAS;EAAY,QAAQ,EAAE;EAAe,aAAa;EAAO;CAG7E,IAAI;AACJ,KAAI;AACF,UAAQ,aAAa,cAAc;SAC7B;AACN,SAAO;GAAE,SAAS;GAAY,QAAQ,EAAE;GAAS,aAAa;GAAO;;CAGvE,MAAM,cACJ,2BAA2B,QAAQ,UAAU,iBAAiB,MAAM,IAAK,CAAC,CAAC,QACzE,cACA,iBAAiB,cAAc,6BAA6B,CAC7D,GAAG,0BAA0B,OAAO;CAEvC,MAAM,YACJ,OAAO,MAAM,mBAAmB,YAAY,OAAO,SAAS,KAAK,eAAe,GAC5E,KAAK,IAAI,KAAO,KAAK,IAAI,MAAS,KAAK,MAAM,KAAK,eAAe,CAAC,CAAC,GACnE;CACN,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,QAAQ,iBAAiB,WAAW,OAAO,EAAE,UAAU;CAC7D,MAAM,SAAS,SAAS,YAAY,IAAI,CAAC,QAAQ,WAAW,OAAO,CAAC,GAAG,WAAW;AAElF,KAAI;EACF,MAAM,eAA4B;GAChC,MAAM;GACN,SAAS,GAAG,oBAAoB,MAAM;GACtC,WAAW,KAAK,KAAK;GACtB;EAED,MAAM,SAAS,MAAM,uBAAuB,MAAM;EAClD,MAAM,SAAS,MAAM,SACnB,OACA,EACE,UAAU,CAAC,aAAa,EACzB,EACD;GAAE;GAAQ,WAAW;GAAK,aAAa;GAAG,QAAQ;GAAQ,CAC3D;AAED,MADoB,+BAA+B,OACpC,CACb,QAAO;GAAE,SAAS;GAAY,QAAQ,EAAE;GAAY,aAAa;GAAO;EAI1E,MAAM,EAAE,MAAM,QAAQ,gBAAgB,2BADzB,qBAAqB,OAAO,QAC4B,CAAC;EACtE,MAAM,YAAY,wBAAwB,QAAQ,OAAO;AACzD,SAAO;GAAE,SAAS,OAAO,SAAS;GAAY,QAAQ;GAAW;GAAa;SACxE;AACN,SAAO;GAAE,SAAS;GAAY,QAAQ,EAAE;GAAY,aAAa;GAAO;WAChE;AACR,eAAa,MAAM"}
@@ -31,6 +31,13 @@ export declare function serializePersistentGoal(s: PersistentGoalState): Record<
31
31
  export declare function renderChecklistPlain(items: GoalChecklistItem[]): string;
32
32
  /** Numbered checklist for judge user prompts (1-based indices). */
33
33
  export declare function renderChecklistNumbered(items: GoalChecklistItem[]): string;
34
+ /**
35
+ * After LLM decomposition, keep existing checklist rows (e.g. user-added acceptance criteria)
36
+ * and append judge-generated items, skipping duplicate text (case-insensitive trim).
37
+ */
38
+ export declare function mergeDecomposedChecklistItems(existing: GoalChecklistItem[], decomposedTexts: {
39
+ text: string;
40
+ }[]): GoalChecklistItem[];
34
41
  export declare function applyJudgeChecklistUpdates(items: GoalChecklistItem[], parsed: {
35
42
  updates: {
36
43
  index: number;
@@ -117,6 +117,29 @@ function renderChecklistNumbered(items) {
117
117
  }
118
118
  return lines.join("\n");
119
119
  }
120
+ /**
121
+ * After LLM decomposition, keep existing checklist rows (e.g. user-added acceptance criteria)
122
+ * and append judge-generated items, skipping duplicate text (case-insensitive trim).
123
+ */
124
+ function mergeDecomposedChecklistItems(existing, decomposedTexts) {
125
+ const now = Date.now();
126
+ const next = existing.map((it) => ({ ...it }));
127
+ const seen = new Set(next.map((it) => it.text.trim().toLowerCase()));
128
+ for (const row of decomposedTexts) {
129
+ const t = row.text.trim();
130
+ if (!t) continue;
131
+ const key = t.toLowerCase();
132
+ if (seen.has(key)) continue;
133
+ seen.add(key);
134
+ next.push({
135
+ text: t,
136
+ status: CHECKLIST_ITEM_PENDING,
137
+ addedBy: "judge",
138
+ addedAt: now
139
+ });
140
+ }
141
+ return next;
142
+ }
120
143
  function applyJudgeChecklistUpdates(items, parsed) {
121
144
  const next = items.map((it) => ({ ...it }));
122
145
  const now = Date.now();
@@ -149,6 +172,6 @@ function mergeCustomDataPatch(existingCustom, patch) {
149
172
  };
150
173
  }
151
174
  //#endregion
152
- export { PERSISTENT_GOAL_CUSTOM_KEY, applyJudgeChecklistUpdates, defaultMaxTurns, mergeCustomDataPatch, readPersistentGoal, renderChecklistNumbered, renderChecklistPlain, serializePersistentGoal };
175
+ export { PERSISTENT_GOAL_CUSTOM_KEY, applyJudgeChecklistUpdates, defaultMaxTurns, mergeCustomDataPatch, mergeDecomposedChecklistItems, readPersistentGoal, renderChecklistNumbered, renderChecklistPlain, serializePersistentGoal };
153
176
 
154
177
  //# sourceMappingURL=state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","names":[],"sources":["../../../../src/agent/goals/state.ts"],"sourcesContent":["import {\n CHECKLIST_ITEM_PENDING,\n TERMINAL_CHECKLIST_STATUSES,\n type ChecklistItemAddedBy,\n type ChecklistItemStatus,\n type GoalChecklistItem,\n} from './checklist-types.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\n\n/** Persisted under `SessionMetadata.customData.persistentGoal`. */\nexport const PERSISTENT_GOAL_CUSTOM_KEY = 'persistentGoal';\n\nexport type PersistentGoalStatus = 'active' | 'paused' | 'done' | 'cleared';\n\nexport interface PersistentGoalState {\n goal: string;\n status: PersistentGoalStatus;\n turnsUsed: number;\n maxTurns: number;\n createdAt: number;\n lastTurnAt: number;\n lastVerdict?: 'done' | 'continue' | 'skipped' | 'decompose';\n lastReason?: string;\n pausedReason?: string;\n judgeModelRef?: string;\n /** Hermes-style: judge JSON parse failures in a row (API errors do not increment). */\n consecutiveParseFailures?: number;\n /** After first successful decomposition, checklist drives Phase-B judging. */\n decomposed?: boolean;\n checklist?: GoalChecklistItem[];\n /** Gateway console language: drives judge `reason` language and system messages. */\n uiLocale?: GoalUiLocale;\n}\n\nexport function defaultMaxTurns(cfg: { maxTurns?: number } | undefined): number {\n const n = cfg?.maxTurns;\n if (typeof n === 'number' && Number.isFinite(n)) {\n return Math.max(1, Math.min(500, Math.floor(n)));\n }\n return 20;\n}\n\nfunction coerceStatus(s: unknown): PersistentGoalStatus | undefined {\n if (s === 'active' || s === 'paused' || s === 'done' || s === 'cleared') return s;\n return undefined;\n}\n\nfunction coerceChecklistItem(raw: unknown): GoalChecklistItem | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;\n const r = raw as Record<string, unknown>;\n const text = typeof r.text === 'string' ? r.text.trim() : '';\n if (!text) return null;\n const st = typeof r.status === 'string' ? r.status.trim().toLowerCase() : '';\n const status: ChecklistItemStatus =\n st === 'completed' || st === 'impossible' || st === 'pending' ? st : CHECKLIST_ITEM_PENDING;\n const ab = typeof r.addedBy === 'string' ? r.addedBy.trim().toLowerCase() : '';\n const addedBy: ChecklistItemAddedBy = ab === 'user' ? 'user' : 'judge';\n const addedAt =\n typeof r.addedAt === 'number' && Number.isFinite(r.addedAt) ? Math.floor(r.addedAt) : Date.now();\n const completedAt =\n typeof r.completedAt === 'number' && Number.isFinite(r.completedAt) ? Math.floor(r.completedAt) : undefined;\n const evidence = typeof r.evidence === 'string' ? r.evidence : undefined;\n return { text, status, addedBy, addedAt, completedAt, evidence };\n}\n\nexport function readPersistentGoal(customData: Record<string, unknown> | undefined): PersistentGoalState | null {\n if (!customData || typeof customData !== 'object') return null;\n\n const raw = customData[PERSISTENT_GOAL_CUSTOM_KEY];\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n const o = raw as Record<string, unknown>;\n const goal = typeof o.goal === 'string' ? o.goal.trim() : '';\n if (!goal) return null;\n const status = coerceStatus(o.status) ?? 'active';\n const maxTurns =\n typeof o.maxTurns === 'number' && Number.isFinite(o.maxTurns)\n ? Math.max(1, Math.min(500, Math.floor(o.maxTurns)))\n : 20;\n const turnsUsed =\n typeof o.turnsUsed === 'number' && Number.isFinite(o.turnsUsed)\n ? Math.max(0, Math.floor(o.turnsUsed))\n : 0;\n const createdAt =\n typeof o.createdAt === 'number' && Number.isFinite(o.createdAt) ? o.createdAt : Date.now();\n const lastTurnAt =\n typeof o.lastTurnAt === 'number' && Number.isFinite(o.lastTurnAt) ? o.lastTurnAt : 0;\n const lastVerdict =\n o.lastVerdict === 'done' ||\n o.lastVerdict === 'continue' ||\n o.lastVerdict === 'skipped' ||\n o.lastVerdict === 'decompose'\n ? o.lastVerdict\n : undefined;\n const lastReason = typeof o.lastReason === 'string' ? o.lastReason : undefined;\n const pausedReason = typeof o.pausedReason === 'string' ? o.pausedReason : undefined;\n const judgeModelRef = typeof o.judgeModelRef === 'string' ? o.judgeModelRef.trim() : undefined;\n const consecutiveParseFailures =\n typeof o.consecutiveParseFailures === 'number' && Number.isFinite(o.consecutiveParseFailures)\n ? Math.max(0, Math.floor(o.consecutiveParseFailures))\n : 0;\n const decomposed = Boolean(o.decomposed);\n const uiLocale = o.uiLocale === 'zh' || o.uiLocale === 'en' ? o.uiLocale : undefined;\n const checklistRaw = o.checklist;\n const checklist: GoalChecklistItem[] = [];\n if (Array.isArray(checklistRaw)) {\n for (const row of checklistRaw) {\n const it = coerceChecklistItem(row);\n if (it) checklist.push(it);\n }\n }\n return {\n goal,\n status,\n turnsUsed,\n maxTurns,\n createdAt,\n lastTurnAt,\n lastVerdict,\n lastReason,\n pausedReason,\n judgeModelRef: judgeModelRef || undefined,\n consecutiveParseFailures,\n decomposed: decomposed || undefined,\n checklist: checklist.length ? checklist : undefined,\n uiLocale,\n };\n }\n\n return null;\n}\n\nexport function serializePersistentGoal(s: PersistentGoalState): Record<string, unknown> {\n return {\n goal: s.goal,\n status: s.status,\n turnsUsed: s.turnsUsed,\n maxTurns: s.maxTurns,\n createdAt: s.createdAt,\n lastTurnAt: s.lastTurnAt,\n ...(s.lastVerdict ? { lastVerdict: s.lastVerdict } : {}),\n ...(s.lastReason ? { lastReason: s.lastReason } : {}),\n ...(s.pausedReason ? { pausedReason: s.pausedReason } : {}),\n ...(s.judgeModelRef ? { judgeModelRef: s.judgeModelRef } : {}),\n ...(s.consecutiveParseFailures ? { consecutiveParseFailures: s.consecutiveParseFailures } : {}),\n ...(s.decomposed ? { decomposed: true } : {}),\n ...(s.uiLocale ? { uiLocale: s.uiLocale } : {}),\n ...(s.checklist?.length\n ? {\n checklist: s.checklist.map((it) => ({\n text: it.text,\n status: it.status,\n addedBy: it.addedBy,\n addedAt: it.addedAt,\n ...(it.completedAt !== undefined ? { completedAt: it.completedAt } : {}),\n ...(it.evidence ? { evidence: it.evidence } : {}),\n })),\n }\n : {}),\n };\n}\n\n/** Render checklist for continuation prompt (Hermes-style, no numbers in body). */\nexport function renderChecklistPlain(items: GoalChecklistItem[]): string {\n if (!items.length) return '(empty)';\n const lines: string[] = [];\n for (const it of items) {\n const marker =\n it.status === 'completed' ? '[x]' : it.status === 'impossible' ? '[!]' : '[ ]';\n let line = `${marker} ${it.text}`;\n if (it.status === 'impossible' && it.evidence) line += ` (impossible: ${it.evidence})`;\n lines.push(line);\n }\n return lines.join('\\n');\n}\n\n/** Numbered checklist for judge user prompts (1-based indices). */\nexport function renderChecklistNumbered(items: GoalChecklistItem[]): string {\n if (!items.length) return '(empty)';\n const lines: string[] = [];\n for (let i = 0; i < items.length; i++) {\n const it = items[i]!;\n const n = i + 1;\n const marker =\n it.status === 'completed' ? '[x]' : it.status === 'impossible' ? '[!]' : '[ ]';\n let line = `${n}. ${marker} ${it.text}`;\n if (it.status === 'impossible' && it.evidence) line += ` (impossible: ${it.evidence})`;\n lines.push(line);\n }\n return lines.join('\\n');\n}\n\nexport function applyJudgeChecklistUpdates(\n items: GoalChecklistItem[],\n parsed: {\n updates: { index: number; status: ChecklistItemStatus; evidence?: string | null }[];\n newItems: { text: string }[];\n },\n): GoalChecklistItem[] {\n const next = items.map((it) => ({ ...it }));\n const now = Date.now();\n for (const upd of parsed.updates) {\n const idx = upd.index;\n if (idx < 0 || idx >= next.length) continue;\n const item = next[idx]!;\n if (TERMINAL_CHECKLIST_STATUSES.has(item.status)) continue;\n if (!TERMINAL_CHECKLIST_STATUSES.has(upd.status)) continue;\n item.status = upd.status;\n item.completedAt = now;\n if (upd.evidence?.trim()) item.evidence = upd.evidence.trim();\n }\n for (const ni of parsed.newItems) {\n const t = ni.text.trim();\n if (!t) continue;\n next.push({\n text: t,\n status: CHECKLIST_ITEM_PENDING,\n addedBy: 'judge',\n addedAt: now,\n });\n }\n return next;\n}\n\n\nexport function mergeCustomDataPatch(\n existingCustom: Record<string, unknown> | undefined,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n return { ...(existingCustom ?? {}), ...patch };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"state.js","names":[],"sources":["../../../../src/agent/goals/state.ts"],"sourcesContent":["import {\n CHECKLIST_ITEM_PENDING,\n TERMINAL_CHECKLIST_STATUSES,\n type ChecklistItemAddedBy,\n type ChecklistItemStatus,\n type GoalChecklistItem,\n} from './checklist-types.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\n\n/** Persisted under `SessionMetadata.customData.persistentGoal`. */\nexport const PERSISTENT_GOAL_CUSTOM_KEY = 'persistentGoal';\n\nexport type PersistentGoalStatus = 'active' | 'paused' | 'done' | 'cleared';\n\nexport interface PersistentGoalState {\n goal: string;\n status: PersistentGoalStatus;\n turnsUsed: number;\n maxTurns: number;\n createdAt: number;\n lastTurnAt: number;\n lastVerdict?: 'done' | 'continue' | 'skipped' | 'decompose';\n lastReason?: string;\n pausedReason?: string;\n judgeModelRef?: string;\n /** Hermes-style: judge JSON parse failures in a row (API errors do not increment). */\n consecutiveParseFailures?: number;\n /** After first successful decomposition, checklist drives Phase-B judging. */\n decomposed?: boolean;\n checklist?: GoalChecklistItem[];\n /** Gateway console language: drives judge `reason` language and system messages. */\n uiLocale?: GoalUiLocale;\n}\n\nexport function defaultMaxTurns(cfg: { maxTurns?: number } | undefined): number {\n const n = cfg?.maxTurns;\n if (typeof n === 'number' && Number.isFinite(n)) {\n return Math.max(1, Math.min(500, Math.floor(n)));\n }\n return 20;\n}\n\nfunction coerceStatus(s: unknown): PersistentGoalStatus | undefined {\n if (s === 'active' || s === 'paused' || s === 'done' || s === 'cleared') return s;\n return undefined;\n}\n\nfunction coerceChecklistItem(raw: unknown): GoalChecklistItem | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;\n const r = raw as Record<string, unknown>;\n const text = typeof r.text === 'string' ? r.text.trim() : '';\n if (!text) return null;\n const st = typeof r.status === 'string' ? r.status.trim().toLowerCase() : '';\n const status: ChecklistItemStatus =\n st === 'completed' || st === 'impossible' || st === 'pending' ? st : CHECKLIST_ITEM_PENDING;\n const ab = typeof r.addedBy === 'string' ? r.addedBy.trim().toLowerCase() : '';\n const addedBy: ChecklistItemAddedBy = ab === 'user' ? 'user' : 'judge';\n const addedAt =\n typeof r.addedAt === 'number' && Number.isFinite(r.addedAt) ? Math.floor(r.addedAt) : Date.now();\n const completedAt =\n typeof r.completedAt === 'number' && Number.isFinite(r.completedAt) ? Math.floor(r.completedAt) : undefined;\n const evidence = typeof r.evidence === 'string' ? r.evidence : undefined;\n return { text, status, addedBy, addedAt, completedAt, evidence };\n}\n\nexport function readPersistentGoal(customData: Record<string, unknown> | undefined): PersistentGoalState | null {\n if (!customData || typeof customData !== 'object') return null;\n\n const raw = customData[PERSISTENT_GOAL_CUSTOM_KEY];\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n const o = raw as Record<string, unknown>;\n const goal = typeof o.goal === 'string' ? o.goal.trim() : '';\n if (!goal) return null;\n const status = coerceStatus(o.status) ?? 'active';\n const maxTurns =\n typeof o.maxTurns === 'number' && Number.isFinite(o.maxTurns)\n ? Math.max(1, Math.min(500, Math.floor(o.maxTurns)))\n : 20;\n const turnsUsed =\n typeof o.turnsUsed === 'number' && Number.isFinite(o.turnsUsed)\n ? Math.max(0, Math.floor(o.turnsUsed))\n : 0;\n const createdAt =\n typeof o.createdAt === 'number' && Number.isFinite(o.createdAt) ? o.createdAt : Date.now();\n const lastTurnAt =\n typeof o.lastTurnAt === 'number' && Number.isFinite(o.lastTurnAt) ? o.lastTurnAt : 0;\n const lastVerdict =\n o.lastVerdict === 'done' ||\n o.lastVerdict === 'continue' ||\n o.lastVerdict === 'skipped' ||\n o.lastVerdict === 'decompose'\n ? o.lastVerdict\n : undefined;\n const lastReason = typeof o.lastReason === 'string' ? o.lastReason : undefined;\n const pausedReason = typeof o.pausedReason === 'string' ? o.pausedReason : undefined;\n const judgeModelRef = typeof o.judgeModelRef === 'string' ? o.judgeModelRef.trim() : undefined;\n const consecutiveParseFailures =\n typeof o.consecutiveParseFailures === 'number' && Number.isFinite(o.consecutiveParseFailures)\n ? Math.max(0, Math.floor(o.consecutiveParseFailures))\n : 0;\n const decomposed = Boolean(o.decomposed);\n const uiLocale = o.uiLocale === 'zh' || o.uiLocale === 'en' ? o.uiLocale : undefined;\n const checklistRaw = o.checklist;\n const checklist: GoalChecklistItem[] = [];\n if (Array.isArray(checklistRaw)) {\n for (const row of checklistRaw) {\n const it = coerceChecklistItem(row);\n if (it) checklist.push(it);\n }\n }\n return {\n goal,\n status,\n turnsUsed,\n maxTurns,\n createdAt,\n lastTurnAt,\n lastVerdict,\n lastReason,\n pausedReason,\n judgeModelRef: judgeModelRef || undefined,\n consecutiveParseFailures,\n decomposed: decomposed || undefined,\n checklist: checklist.length ? checklist : undefined,\n uiLocale,\n };\n }\n\n return null;\n}\n\nexport function serializePersistentGoal(s: PersistentGoalState): Record<string, unknown> {\n return {\n goal: s.goal,\n status: s.status,\n turnsUsed: s.turnsUsed,\n maxTurns: s.maxTurns,\n createdAt: s.createdAt,\n lastTurnAt: s.lastTurnAt,\n ...(s.lastVerdict ? { lastVerdict: s.lastVerdict } : {}),\n ...(s.lastReason ? { lastReason: s.lastReason } : {}),\n ...(s.pausedReason ? { pausedReason: s.pausedReason } : {}),\n ...(s.judgeModelRef ? { judgeModelRef: s.judgeModelRef } : {}),\n ...(s.consecutiveParseFailures ? { consecutiveParseFailures: s.consecutiveParseFailures } : {}),\n ...(s.decomposed ? { decomposed: true } : {}),\n ...(s.uiLocale ? { uiLocale: s.uiLocale } : {}),\n ...(s.checklist?.length\n ? {\n checklist: s.checklist.map((it) => ({\n text: it.text,\n status: it.status,\n addedBy: it.addedBy,\n addedAt: it.addedAt,\n ...(it.completedAt !== undefined ? { completedAt: it.completedAt } : {}),\n ...(it.evidence ? { evidence: it.evidence } : {}),\n })),\n }\n : {}),\n };\n}\n\n/** Render checklist for continuation prompt (Hermes-style, no numbers in body). */\nexport function renderChecklistPlain(items: GoalChecklistItem[]): string {\n if (!items.length) return '(empty)';\n const lines: string[] = [];\n for (const it of items) {\n const marker =\n it.status === 'completed' ? '[x]' : it.status === 'impossible' ? '[!]' : '[ ]';\n let line = `${marker} ${it.text}`;\n if (it.status === 'impossible' && it.evidence) line += ` (impossible: ${it.evidence})`;\n lines.push(line);\n }\n return lines.join('\\n');\n}\n\n/** Numbered checklist for judge user prompts (1-based indices). */\nexport function renderChecklistNumbered(items: GoalChecklistItem[]): string {\n if (!items.length) return '(empty)';\n const lines: string[] = [];\n for (let i = 0; i < items.length; i++) {\n const it = items[i]!;\n const n = i + 1;\n const marker =\n it.status === 'completed' ? '[x]' : it.status === 'impossible' ? '[!]' : '[ ]';\n let line = `${n}. ${marker} ${it.text}`;\n if (it.status === 'impossible' && it.evidence) line += ` (impossible: ${it.evidence})`;\n lines.push(line);\n }\n return lines.join('\\n');\n}\n\n/**\n * After LLM decomposition, keep existing checklist rows (e.g. user-added acceptance criteria)\n * and append judge-generated items, skipping duplicate text (case-insensitive trim).\n */\nexport function mergeDecomposedChecklistItems(\n existing: GoalChecklistItem[],\n decomposedTexts: { text: string }[],\n): GoalChecklistItem[] {\n const now = Date.now();\n const next = existing.map((it) => ({ ...it }));\n const seen = new Set(next.map((it) => it.text.trim().toLowerCase()));\n for (const row of decomposedTexts) {\n const t = row.text.trim();\n if (!t) continue;\n const key = t.toLowerCase();\n if (seen.has(key)) continue;\n seen.add(key);\n next.push({\n text: t,\n status: CHECKLIST_ITEM_PENDING,\n addedBy: 'judge',\n addedAt: now,\n });\n }\n return next;\n}\n\nexport function applyJudgeChecklistUpdates(\n items: GoalChecklistItem[],\n parsed: {\n updates: { index: number; status: ChecklistItemStatus; evidence?: string | null }[];\n newItems: { text: string }[];\n },\n): GoalChecklistItem[] {\n const next = items.map((it) => ({ ...it }));\n const now = Date.now();\n for (const upd of parsed.updates) {\n const idx = upd.index;\n if (idx < 0 || idx >= next.length) continue;\n const item = next[idx]!;\n if (TERMINAL_CHECKLIST_STATUSES.has(item.status)) continue;\n if (!TERMINAL_CHECKLIST_STATUSES.has(upd.status)) continue;\n item.status = upd.status;\n item.completedAt = now;\n if (upd.evidence?.trim()) item.evidence = upd.evidence.trim();\n }\n for (const ni of parsed.newItems) {\n const t = ni.text.trim();\n if (!t) continue;\n next.push({\n text: t,\n status: CHECKLIST_ITEM_PENDING,\n addedBy: 'judge',\n addedAt: now,\n });\n }\n return next;\n}\n\n\nexport function mergeCustomDataPatch(\n existingCustom: Record<string, unknown> | undefined,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n return { ...(existingCustom ?? {}), ...patch };\n}\n"],"mappings":"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"}
@@ -2,8 +2,8 @@
2
2
  * Model Commands
3
3
  *
4
4
  * Built-in commands for model management:
5
- * - /models - List available models
6
- * - /switch - Switch to a different model
5
+ * - /models - List models with display names and `provider/model` refs for /switch
6
+ * - /switch - Switch model using the ref from /models
7
7
  * - /usage - Show token usage statistics
8
8
  */
9
9
  export declare function registerModelCommands(): void;
@@ -4,7 +4,7 @@ const modelsCommand = {
4
4
  id: "model.list",
5
5
  name: "models",
6
6
  aliases: ["model"],
7
- description: "List available AI models",
7
+ description: "List models with display names and `provider/model` refs for /switch",
8
8
  category: "model",
9
9
  scope: [
10
10
  "global",
@@ -24,16 +24,18 @@ const modelsCommand = {
24
24
  if (!byProvider.has(m.provider)) byProvider.set(m.provider, []);
25
25
  byProvider.get(m.provider).push(m);
26
26
  }
27
- const lines = ["🤖 Available Models:\n"];
27
+ /** `m.id` from listModels is always `serviceId/modelId` (canonical `/switch` ref). */
28
+ const lines = ["🤖 Available models (use the `provider/model` ref with `/switch`):\n"];
28
29
  for (const [provider, providerModels] of byProvider) {
29
30
  lines.push(`**${provider}**`);
30
31
  for (const m of providerModels.slice(0, 5)) {
31
32
  const indicator = m.id === currentModel ? "▶️" : " ";
32
- lines.push(`${indicator} ${m.name}`);
33
+ lines.push(`${indicator} ${m.name} — \`${m.id}\``);
33
34
  }
34
- if (providerModels.length > 5) lines.push(` ... and ${providerModels.length - 5} more`);
35
+ if (providerModels.length > 5) lines.push(` and ${providerModels.length - 5} more in this provider`);
35
36
  lines.push("");
36
37
  }
38
+ lines.push("_Copy the ref in backticks after each name, then: `/switch provider/model-id`._");
37
39
  const content = lines.join("\n");
38
40
  if (ctx.supports("buttons")) return {
39
41
  content,
@@ -61,7 +63,7 @@ const modelsCommand = {
61
63
  const switchCommand = {
62
64
  id: "model.switch",
63
65
  name: "switch",
64
- description: "Switch to a different model (usage: /switch <model-id>)",
66
+ description: "Switch model pass the `provider/model` ref shown by /models",
65
67
  category: "model",
66
68
  scope: [
67
69
  "global",
@@ -72,17 +74,17 @@ const switchCommand = {
72
74
  examples: ["/switch openai/gpt-4o", "/switch minimax/minimax-m2.1"],
73
75
  handler: async (ctx, args) => {
74
76
  if (!args.trim()) return {
75
- content: "❌ Please specify a model ID.\nUsage: /switch <model-id>\nExample: /switch openai/gpt-4o",
77
+ content: "❌ Missing model ref.\n\n**Usage:** `/switch provider/model-id`\n\nRun `/models` — each line shows a display name and a `provider/model` ref in backticks. Copy that ref.\n\n**Example:** `/switch openai/gpt-4o`",
76
78
  success: false
77
79
  };
78
80
  await ctx.setTyping(true);
79
81
  const modelId = args.trim();
80
82
  if (await ctx.switchModel(modelId)) return {
81
- content: `✅ Switched to model: *${modelId.split("/").pop() || modelId}*\n\nThis model will be used for your next message.`,
83
+ content: `✅ Switched to *\`${modelId}\`* (${modelId.split("/").pop() || modelId}).\n\nThis model will be used for your next message.`,
82
84
  success: true
83
85
  };
84
86
  else return {
85
- content: `❌ Failed to switch to model: ${modelId}\nPlease check the model ID and try again.`,
87
+ content: `❌ Could not switch to \`${modelId}\`.\n\nUse the exact \`provider/model\` ref from \`/models\` (not only the display name). **Example:** \`/switch anthropic/claude-sonnet-4-20250514\``,
86
88
  success: false
87
89
  };
88
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","names":[],"sources":["../../../../src/chat-commands/builtins/model.ts"],"sourcesContent":["/**\n * Model Commands\n * \n * Built-in commands for model management:\n * - /models - List available models\n * - /switch - Switch to a different model\n * - /usage - Show token usage statistics\n */\n\nimport type { CommandDefinition, CommandContext, UIComponent } from '../types.js';\nimport { commandRegistry } from '../registry.js';\n\nconst modelsCommand: CommandDefinition = {\n id: 'model.list',\n name: 'models',\n aliases: ['model'],\n description: 'List available AI models',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n \n const models = await ctx.listModels();\n const currentModel = ctx.getCurrentModel();\n \n if (models.length === 0) {\n return {\n content: '🤖 No models available. Please check your configuration.',\n success: true,\n };\n }\n \n // Group by provider\n const byProvider = new Map<string, typeof models>();\n for (const m of models) {\n if (!byProvider.has(m.provider)) {\n byProvider.set(m.provider, []);\n }\n byProvider.get(m.provider)!.push(m);\n }\n \n // Build text response\n const lines: string[] = ['🤖 Available Models:\\n'];\n \n for (const [provider, providerModels] of byProvider) {\n lines.push(`**${provider}**`);\n for (const m of providerModels.slice(0, 5)) {\n const indicator = m.id === currentModel ? '▶️' : ' ';\n lines.push(`${indicator} ${m.name}`);\n }\n if (providerModels.length > 5) {\n lines.push(` ... and ${providerModels.length - 5} more`);\n }\n lines.push('');\n }\n \n const content = lines.join('\\n');\n \n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'model-picker',\n providers: Array.from(byProvider.entries()).map(([id, models]) => ({\n id,\n name: id,\n models: models.map(m => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n })),\n })),\n currentModel,\n };\n \n return {\n content,\n success: true,\n components: [component],\n };\n }\n \n return {\n content,\n success: true,\n };\n },\n};\n\nconst switchCommand: CommandDefinition = {\n id: 'model.switch',\n name: 'switch',\n description: 'Switch to a different model (usage: /switch <model-id>)',\n category: 'model',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/switch openai/gpt-4o', '/switch minimax/minimax-m2.1'],\n handler: async (ctx: CommandContext, args: string) => {\n if (!args.trim()) {\n return {\n content: '❌ Please specify a model ID.\\nUsage: /switch <model-id>\\nExample: /switch openai/gpt-4o',\n success: false,\n };\n }\n \n await ctx.setTyping(true);\n \n const modelId = args.trim();\n const success = await ctx.switchModel(modelId);\n \n if (success) {\n const modelName = modelId.split('/').pop() || modelId;\n return {\n content: `✅ Switched to model: *${modelName}*\\n\\nThis model will be used for your next message.`,\n success: true,\n };\n } else {\n return {\n content: `❌ Failed to switch to model: ${modelId}\\nPlease check the model ID and try again.`,\n success: false,\n };\n }\n },\n};\n\nconst usageCommand: CommandDefinition = {\n id: 'model.usage',\n name: 'usage',\n description: 'Show token usage statistics for current session',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n \n const stats = await ctx.getUsage();\n const modelName = ctx.getCurrentModel().split('/').pop() || 'Unknown';\n \n const content = \n '📊 *Session Token Usage*\\n\\n' +\n `🤖 Model: ${modelName}\\n` +\n `📥 Prompt: ${stats.promptTokens.toLocaleString()} tokens\\n` +\n `📤 Completion: ${stats.completionTokens.toLocaleString()} tokens\\n` +\n `📊 Total: ${stats.totalTokens.toLocaleString()} tokens\\n` +\n `💬 Messages: ${stats.messageCount}`;\n \n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'usage-display',\n stats,\n modelName,\n };\n \n return {\n content,\n success: true,\n components: [component],\n };\n }\n \n return {\n content,\n success: true,\n };\n },\n};\n\n// Register all model commands\nexport function registerModelCommands(): void {\n commandRegistry.register(modelsCommand);\n commandRegistry.register(switchCommand);\n commandRegistry.register(usageCommand);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../../../src/chat-commands/builtins/model.ts"],"sourcesContent":["/**\n * Model Commands\n * \n * Built-in commands for model management:\n * - /models - List models with display names and `provider/model` refs for /switch\n * - /switch - Switch model using the ref from /models\n * - /usage - Show token usage statistics\n */\n\nimport type { CommandDefinition, CommandContext, UIComponent } from '../types.js';\nimport { commandRegistry } from '../registry.js';\n\nconst modelsCommand: CommandDefinition = {\n id: 'model.list',\n name: 'models',\n aliases: ['model'],\n description: 'List models with display names and `provider/model` refs for /switch',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n \n const models = await ctx.listModels();\n const currentModel = ctx.getCurrentModel();\n \n if (models.length === 0) {\n return {\n content: '🤖 No models available. Please check your configuration.',\n success: true,\n };\n }\n \n // Group by provider\n const byProvider = new Map<string, typeof models>();\n for (const m of models) {\n if (!byProvider.has(m.provider)) {\n byProvider.set(m.provider, []);\n }\n byProvider.get(m.provider)!.push(m);\n }\n \n /** `m.id` from listModels is always `serviceId/modelId` (canonical `/switch` ref). */\n const lines: string[] = [\n '🤖 Available models (use the `provider/model` ref with `/switch`):\\n',\n ];\n\n for (const [provider, providerModels] of byProvider) {\n lines.push(`**${provider}**`);\n for (const m of providerModels.slice(0, 5)) {\n const indicator = m.id === currentModel ? '▶️' : ' ';\n lines.push(`${indicator} ${m.name} — \\`${m.id}\\``);\n }\n if (providerModels.length > 5) {\n lines.push(` and ${providerModels.length - 5} more in this provider`);\n }\n lines.push('');\n }\n\n lines.push('_Copy the ref in backticks after each name, then: `/switch provider/model-id`._');\n const content = lines.join('\\n');\n \n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'model-picker',\n providers: Array.from(byProvider.entries()).map(([id, models]) => ({\n id,\n name: id,\n models: models.map(m => ({\n id: m.id,\n name: m.name,\n provider: m.provider,\n })),\n })),\n currentModel,\n };\n \n return {\n content,\n success: true,\n components: [component],\n };\n }\n \n return {\n content,\n success: true,\n };\n },\n};\n\nconst switchCommand: CommandDefinition = {\n id: 'model.switch',\n name: 'switch',\n description: 'Switch model pass the `provider/model` ref shown by /models',\n category: 'model',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/switch openai/gpt-4o', '/switch minimax/minimax-m2.1'],\n handler: async (ctx: CommandContext, args: string) => {\n if (!args.trim()) {\n return {\n content:\n '❌ Missing model ref.\\n\\n' +\n '**Usage:** `/switch provider/model-id`\\n\\n' +\n 'Run `/models` — each line shows a display name and a `provider/model` ref in backticks. Copy that ref.\\n\\n' +\n '**Example:** `/switch openai/gpt-4o`',\n success: false,\n };\n }\n \n await ctx.setTyping(true);\n \n const modelId = args.trim();\n const success = await ctx.switchModel(modelId);\n \n if (success) {\n const modelName = modelId.split('/').pop() || modelId;\n return {\n content:\n `✅ Switched to *\\`${modelId}\\`* (${modelName}).\\n\\n` +\n 'This model will be used for your next message.',\n success: true,\n };\n } else {\n return {\n content:\n `❌ Could not switch to \\`${modelId}\\`.\\n\\n` +\n 'Use the exact `provider/model` ref from `/models` (not only the display name). ' +\n '**Example:** `/switch anthropic/claude-sonnet-4-20250514`',\n success: false,\n };\n }\n },\n};\n\nconst usageCommand: CommandDefinition = {\n id: 'model.usage',\n name: 'usage',\n description: 'Show token usage statistics for current session',\n category: 'model',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n await ctx.setTyping(true);\n \n const stats = await ctx.getUsage();\n const modelName = ctx.getCurrentModel().split('/').pop() || 'Unknown';\n \n const content = \n '📊 *Session Token Usage*\\n\\n' +\n `🤖 Model: ${modelName}\\n` +\n `📥 Prompt: ${stats.promptTokens.toLocaleString()} tokens\\n` +\n `📤 Completion: ${stats.completionTokens.toLocaleString()} tokens\\n` +\n `📊 Total: ${stats.totalTokens.toLocaleString()} tokens\\n` +\n `💬 Messages: ${stats.messageCount}`;\n \n // Create UI component if supported\n if (ctx.supports('buttons')) {\n const component: UIComponent = {\n type: 'usage-display',\n stats,\n modelName,\n };\n \n return {\n content,\n success: true,\n components: [component],\n };\n }\n \n return {\n content,\n success: true,\n };\n },\n};\n\n// Register all model commands\nexport function registerModelCommands(): void {\n commandRegistry.register(modelsCommand);\n commandRegistry.register(switchCommand);\n commandRegistry.register(usageCommand);\n}\n"],"mappings":"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"}
@@ -45,7 +45,7 @@ const startCommand = {
45
45
  ],
46
46
  handler: async (_ctx) => {
47
47
  return {
48
- content: "👋 *Welcome to xopc!*\n\nI am your AI assistant. Here's what I can do:\n\n🤖 *AI Chat* - Just send a message to start chatting\n📊 *Session Management* - Use /new, /list, /usage\n🔧 *Model Selection* - Use /models, /switch\n\nType /help to see all available commands.",
48
+ content: "👋 *Welcome to xopc!*\n\nI am your AI assistant. Here's what I can do:\n\n🤖 *AI Chat* - Just send a message to start chatting\n📊 *Session Management* - Use /new, /list, /usage\n🔧 *Model Selection* - `/models` shows names and `provider/model` refs; `/switch` uses that ref\n\nType /help to see all available commands.",
49
49
  success: true
50
50
  };
51
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"system.js","names":[],"sources":["../../../../src/chat-commands/builtins/system.ts"],"sourcesContent":["/**\n * System Commands\n *\n * Built-in system commands:\n * - /help - Show help message\n * - /start - Welcome message\n * - /settings - Show settings\n */\n\nimport type { CommandDefinition, CommandContext } from '../types.js';\nimport { commandRegistry } from '../registry.js';\n\nconst helpCommand: CommandDefinition = {\n id: 'system.help',\n name: 'help',\n aliases: ['h', 'commands'],\n description: 'Show available commands',\n category: 'system',\n scope: ['global', 'private', 'group'],\n handler: async (_ctx: CommandContext) => {\n const allCommands = commandRegistry.list();\n\n // Group by category\n const byCategory = new Map<string, typeof allCommands>();\n for (const cmd of allCommands) {\n if (!byCategory.has(cmd.category)) {\n byCategory.set(cmd.category, []);\n }\n byCategory.get(cmd.category)!.push(cmd);\n }\n\n const lines: string[] = ['📖 *Available Commands*\\n'];\n\n for (const [category, commands] of byCategory) {\n lines.push(`*${category.toUpperCase()}*`);\n for (const cmd of commands) {\n const aliases = cmd.aliases?.length ? ` (${cmd.aliases.join(', ')})` : '';\n lines.push(`/${cmd.name}${aliases} - ${cmd.description}`);\n }\n lines.push('');\n }\n\n return {\n content: lines.join('\\n'),\n success: true,\n };\n },\n};\n\nconst startCommand: CommandDefinition = {\n id: 'system.start',\n name: 'start',\n description: 'Show welcome message',\n category: 'system',\n scope: ['global', 'private', 'group'],\n handler: async (_ctx: CommandContext) => {\n const content =\n '👋 *Welcome to xopc!*\\n\\n' +\n 'I am your AI assistant. Here\\'s what I can do:\\n\\n' +\n '🤖 *AI Chat* - Just send a message to start chatting\\n' +\n '📊 *Session Management* - Use /new, /list, /usage\\n' +\n '🔧 *Model Selection* - Use /models, /switch\\n\\n' +\n 'Type /help to see all available commands.';\n\n return {\n content,\n success: true,\n };\n },\n};\n\nconst settingsCommand: CommandDefinition = {\n id: 'system.settings',\n name: 'settings',\n description: 'Show current settings',\n category: 'system',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n const model = ctx.getCurrentModel();\n const sessionKey = ctx.sessionKey;\n\n const content =\n '⚙️ *Current Settings*\\n\\n' +\n `🤖 Model: ${model}\\n` +\n `💬 Session: ${sessionKey}\\n` +\n `📱 Platform: ${ctx.source}\\n` +\n `👥 Group: ${ctx.isGroup ? 'Yes' : 'No'}`;\n\n return {\n content,\n success: true,\n };\n },\n};\n\nconst skillsCommand: CommandDefinition = {\n id: 'system.skills',\n name: 'skills',\n description: 'Manage skills (e.g., /skills reload)',\n category: 'system',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/skills reload'],\n handler: async (ctx: CommandContext, args: string) => {\n if (args === 'reload') {\n // Publish system event to reload skills\n // This will be handled by AgentService skill reload logic\n return {\n content: '✅ Skills reloaded successfully',\n success: true,\n };\n }\n \n return {\n content: \n '🛠️ *Skills Management*\\n\\n' +\n 'Available commands:\\n' +\n '/skills reload - Reload all skills from disk',\n success: true,\n };\n },\n};\n\n// Register all system commands\nexport function registerSystemCommands(): void {\n commandRegistry.register(helpCommand);\n commandRegistry.register(startCommand);\n commandRegistry.register(settingsCommand);\n commandRegistry.register(skillsCommand);\n}\n"],"mappings":";;AAYA,MAAM,cAAiC;CACrC,IAAI;CACJ,MAAM;CACN,SAAS,CAAC,KAAK,WAAW;CAC1B,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,SAAyB;EACvC,MAAM,cAAc,gBAAgB,MAAM;EAG1C,MAAM,6BAAa,IAAI,KAAiC;AACxD,OAAK,MAAM,OAAO,aAAa;AAC7B,OAAI,CAAC,WAAW,IAAI,IAAI,SAAS,CAC/B,YAAW,IAAI,IAAI,UAAU,EAAE,CAAC;AAElC,cAAW,IAAI,IAAI,SAAS,CAAE,KAAK,IAAI;;EAGzC,MAAM,QAAkB,CAAC,4BAA4B;AAErD,OAAK,MAAM,CAAC,UAAU,aAAa,YAAY;AAC7C,SAAM,KAAK,IAAI,SAAS,aAAa,CAAC,GAAG;AACzC,QAAK,MAAM,OAAO,UAAU;IAC1B,MAAM,UAAU,IAAI,SAAS,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,CAAC,KAAK;AACvE,UAAM,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,IAAI,cAAc;;AAE3D,SAAM,KAAK,GAAG;;AAGhB,SAAO;GACL,SAAS,MAAM,KAAK,KAAK;GACzB,SAAS;GACV;;CAEJ;AAED,MAAM,eAAkC;CACtC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,SAAyB;AASvC,SAAO;GACL,SAAA;GACA,SAAS;GACV;;CAEJ;AAED,MAAM,kBAAqC;CACzC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,QAAwB;AAWtC,SAAO;GACL,SAAA;;YAXY,IAAI,iBAKE,CAAC,gBAJF,IAAI,WAKK,iBACV,IAAI,OAAO,cACd,IAAI,UAAU,QAAQ;GAInC,SAAS;GACV;;CAEJ;AAED,MAAM,gBAAmC;CACvC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,aAAa;CACb,UAAU,CAAC,iBAAiB;CAC5B,SAAS,OAAO,KAAqB,SAAiB;AACpD,MAAI,SAAS,SAGX,QAAO;GACL,SAAS;GACT,SAAS;GACV;AAGH,SAAO;GACL,SACE;GAGF,SAAS;GACV;;CAEJ;AAGD,SAAgB,yBAA+B;AAC7C,iBAAgB,SAAS,YAAY;AACrC,iBAAgB,SAAS,aAAa;AACtC,iBAAgB,SAAS,gBAAgB;AACzC,iBAAgB,SAAS,cAAc"}
1
+ {"version":3,"file":"system.js","names":[],"sources":["../../../../src/chat-commands/builtins/system.ts"],"sourcesContent":["/**\n * System Commands\n *\n * Built-in system commands:\n * - /help - Show help message\n * - /start - Welcome message\n * - /settings - Show settings\n */\n\nimport type { CommandDefinition, CommandContext } from '../types.js';\nimport { commandRegistry } from '../registry.js';\n\nconst helpCommand: CommandDefinition = {\n id: 'system.help',\n name: 'help',\n aliases: ['h', 'commands'],\n description: 'Show available commands',\n category: 'system',\n scope: ['global', 'private', 'group'],\n handler: async (_ctx: CommandContext) => {\n const allCommands = commandRegistry.list();\n\n // Group by category\n const byCategory = new Map<string, typeof allCommands>();\n for (const cmd of allCommands) {\n if (!byCategory.has(cmd.category)) {\n byCategory.set(cmd.category, []);\n }\n byCategory.get(cmd.category)!.push(cmd);\n }\n\n const lines: string[] = ['📖 *Available Commands*\\n'];\n\n for (const [category, commands] of byCategory) {\n lines.push(`*${category.toUpperCase()}*`);\n for (const cmd of commands) {\n const aliases = cmd.aliases?.length ? ` (${cmd.aliases.join(', ')})` : '';\n lines.push(`/${cmd.name}${aliases} - ${cmd.description}`);\n }\n lines.push('');\n }\n\n return {\n content: lines.join('\\n'),\n success: true,\n };\n },\n};\n\nconst startCommand: CommandDefinition = {\n id: 'system.start',\n name: 'start',\n description: 'Show welcome message',\n category: 'system',\n scope: ['global', 'private', 'group'],\n handler: async (_ctx: CommandContext) => {\n const content =\n '👋 *Welcome to xopc!*\\n\\n' +\n 'I am your AI assistant. Here\\'s what I can do:\\n\\n' +\n '🤖 *AI Chat* - Just send a message to start chatting\\n' +\n '📊 *Session Management* - Use /new, /list, /usage\\n' +\n '🔧 *Model Selection* - `/models` shows names and `provider/model` refs; `/switch` uses that ref\\n\\n' +\n 'Type /help to see all available commands.';\n\n return {\n content,\n success: true,\n };\n },\n};\n\nconst settingsCommand: CommandDefinition = {\n id: 'system.settings',\n name: 'settings',\n description: 'Show current settings',\n category: 'system',\n scope: ['global', 'private', 'group'],\n handler: async (ctx: CommandContext) => {\n const model = ctx.getCurrentModel();\n const sessionKey = ctx.sessionKey;\n\n const content =\n '⚙️ *Current Settings*\\n\\n' +\n `🤖 Model: ${model}\\n` +\n `💬 Session: ${sessionKey}\\n` +\n `📱 Platform: ${ctx.source}\\n` +\n `👥 Group: ${ctx.isGroup ? 'Yes' : 'No'}`;\n\n return {\n content,\n success: true,\n };\n },\n};\n\nconst skillsCommand: CommandDefinition = {\n id: 'system.skills',\n name: 'skills',\n description: 'Manage skills (e.g., /skills reload)',\n category: 'system',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/skills reload'],\n handler: async (ctx: CommandContext, args: string) => {\n if (args === 'reload') {\n // Publish system event to reload skills\n // This will be handled by AgentService skill reload logic\n return {\n content: '✅ Skills reloaded successfully',\n success: true,\n };\n }\n \n return {\n content: \n '🛠️ *Skills Management*\\n\\n' +\n 'Available commands:\\n' +\n '/skills reload - Reload all skills from disk',\n success: true,\n };\n },\n};\n\n// Register all system commands\nexport function registerSystemCommands(): void {\n commandRegistry.register(helpCommand);\n commandRegistry.register(startCommand);\n commandRegistry.register(settingsCommand);\n commandRegistry.register(skillsCommand);\n}\n"],"mappings":";;AAYA,MAAM,cAAiC;CACrC,IAAI;CACJ,MAAM;CACN,SAAS,CAAC,KAAK,WAAW;CAC1B,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,SAAyB;EACvC,MAAM,cAAc,gBAAgB,MAAM;EAG1C,MAAM,6BAAa,IAAI,KAAiC;AACxD,OAAK,MAAM,OAAO,aAAa;AAC7B,OAAI,CAAC,WAAW,IAAI,IAAI,SAAS,CAC/B,YAAW,IAAI,IAAI,UAAU,EAAE,CAAC;AAElC,cAAW,IAAI,IAAI,SAAS,CAAE,KAAK,IAAI;;EAGzC,MAAM,QAAkB,CAAC,4BAA4B;AAErD,OAAK,MAAM,CAAC,UAAU,aAAa,YAAY;AAC7C,SAAM,KAAK,IAAI,SAAS,aAAa,CAAC,GAAG;AACzC,QAAK,MAAM,OAAO,UAAU;IAC1B,MAAM,UAAU,IAAI,SAAS,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,CAAC,KAAK;AACvE,UAAM,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,IAAI,cAAc;;AAE3D,SAAM,KAAK,GAAG;;AAGhB,SAAO;GACL,SAAS,MAAM,KAAK,KAAK;GACzB,SAAS;GACV;;CAEJ;AAED,MAAM,eAAkC;CACtC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,SAAyB;AASvC,SAAO;GACL,SAAA;GACA,SAAS;GACV;;CAEJ;AAED,MAAM,kBAAqC;CACzC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,SAAS,OAAO,QAAwB;AAWtC,SAAO;GACL,SAAA;;YAXY,IAAI,iBAKE,CAAC,gBAJF,IAAI,WAKK,iBACV,IAAI,OAAO,cACd,IAAI,UAAU,QAAQ;GAInC,SAAS;GACV;;CAEJ;AAED,MAAM,gBAAmC;CACvC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,aAAa;CACb,UAAU,CAAC,iBAAiB;CAC5B,SAAS,OAAO,KAAqB,SAAiB;AACpD,MAAI,SAAS,SAGX,QAAO;GACL,SAAS;GACT,SAAS;GACV;AAGH,SAAO;GACL,SACE;GAGF,SAAS;GACV;;CAEJ;AAGD,SAAgB,yBAA+B;AAC7C,iBAAgB,SAAS,YAAY;AACrC,iBAAgB,SAAS,aAAa;AACtC,iBAAgB,SAAS,gBAAgB;AACzC,iBAAgB,SAAS,cAAc"}
@@ -34,7 +34,7 @@ export declare class EmbeddedBackend implements TuiBackend {
34
34
  messages: HistoryMessage[];
35
35
  }>;
36
36
  listSessions(): Promise<TuiSessionItem[]>;
37
- getSessionInfo(_sessionKey: string): Promise<SessionInfo>;
37
+ getSessionInfo(sessionKey: string): Promise<SessionInfo>;
38
38
  listModels(): Promise<TuiModelChoice[]>;
39
39
  resetSession(_sessionKey: string): Promise<void>;
40
40
  patchSession(_sessionKey: string, _patch: Record<string, unknown>): Promise<void>;
@@ -7,6 +7,7 @@ import { MessageBus, MessageBusShutdownError } from "../../infra/bus/queue.js";
7
7
  import "../../infra/bus/index.js";
8
8
  import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
9
9
  import { messagesToClientHistory } from "../../session/client-history.js";
10
+ import { parseModelRef } from "../../agent/models/selection.js";
10
11
  import { AgentService } from "../../agent/service.js";
11
12
  import "../../agent/index.js";
12
13
  import "../../config/index.js";
@@ -128,9 +129,29 @@ var EmbeddedBackend = class {
128
129
  async listSessions() {
129
130
  return [];
130
131
  }
131
- async getSessionInfo(_sessionKey) {
132
- const modelConfig = loadConfig().agents?.defaults?.model;
133
- return { model: (typeof modelConfig === "string" ? modelConfig : modelConfig?.primary) ?? void 0 };
132
+ async getSessionInfo(sessionKey) {
133
+ if (!this.agent) {
134
+ const modelConfig = loadConfig().agents?.defaults?.model;
135
+ return { model: (typeof modelConfig === "string" ? modelConfig : modelConfig?.primary) ?? void 0 };
136
+ }
137
+ try {
138
+ const cfg = await this.agent.getSessionAgentConfig(sessionKey);
139
+ const parsed = parseModelRef(cfg.model);
140
+ return {
141
+ model: parsed?.model ?? cfg.model,
142
+ modelProvider: parsed?.provider,
143
+ thinkingLevel: cfg.thinkingLevel
144
+ };
145
+ } catch (err) {
146
+ const errorMessage = err instanceof Error ? err.message : String(err);
147
+ log.warn({
148
+ err,
149
+ sessionKey,
150
+ errorMessage
151
+ }, `getSessionInfo failed: ${errorMessage}`);
152
+ const modelConfig = loadConfig().agents?.defaults?.model;
153
+ return { model: (typeof modelConfig === "string" ? modelConfig : modelConfig?.primary) ?? void 0 };
154
+ }
134
155
  }
135
156
  async listModels() {
136
157
  const choices = [];
@@ -1 +1 @@
1
- {"version":3,"file":"embedded-backend.js","names":[],"sources":["../../../../src/tui/backends/embedded-backend.ts"],"sourcesContent":["import { AgentService } from '../../agent/index.js';\nimport { messagesToClientHistory } from '../../session/client-history.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { loadConfig, getWorkspacePath } from '../../config/index.js';\nimport { MessageBus, MessageBusShutdownError } from '../../infra/bus/index.js';\nimport { getAllProviders, getModelsByProvider } from '../../providers/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type {\n ChatSendOptions,\n HistoryMessage,\n TuiBackend,\n TuiEvent,\n TuiModelChoice,\n TuiSessionItem,\n} from '../tui-backend.js';\nimport type { SessionInfo } from '../tui-types.js';\n\nconst log = createLogger('TUI:Embedded');\n\n/**\n * TUI backend that runs the agent in-process (no gateway required).\n *\n * Wraps `AgentService` directly and emits TuiEvents by observing the\n * `MessageBus` output stream.\n */\nexport class EmbeddedBackend implements TuiBackend {\n private bus: MessageBus;\n private agent: AgentService | null = null;\n private running = false;\n private chatAbort: AbortController | null = null;\n\n onEvent?: (evt: TuiEvent) => void;\n onConnected?: () => void;\n onDisconnected?: (reason: string) => void;\n\n constructor() {\n this.bus = new MessageBus();\n }\n\n get connectionLabel(): string {\n return 'local embedded';\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n\n const config = loadConfig();\n const workspace = getWorkspacePath(config);\n const modelConfig = config.agents?.defaults?.model;\n const modelId = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n\n this.agent = new AgentService(this.bus, {\n workspace: workspace ?? process.cwd(),\n model: modelId,\n config,\n });\n\n this.agent.start().catch((err) => {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage }, `Embedded agent failed: ${errorMessage}`);\n this.onDisconnected?.(errorMessage);\n });\n\n // Process outbound messages in background\n this.processOutbound();\n\n // Signal ready\n queueMicrotask(() => this.onConnected?.());\n }\n\n stop(): void {\n this.running = false;\n this.bus.shutdown();\n void this.agent?.stop();\n this.agent = null;\n }\n\n async sendChat(opts: ChatSendOptions): Promise<{ runId: string }> {\n if (!this.agent) throw new Error('Agent not started');\n\n const runId = crypto.randomUUID();\n this.chatAbort?.abort();\n this.chatAbort = new AbortController();\n const signal = this.chatAbort.signal;\n\n this.onEvent?.({ event: 'status', data: { status: 'started', runId } });\n\n // Run the stream in background so the TUI event loop stays responsive.\n void (async () => {\n try {\n // Prepend envelope timestamp so the model knows the current date/time,\n // matching the behavior of channel pipelines (Telegram, Weixin, etc.).\n // Skip for slash commands — parseSlashCommand requires lines starting with '/'.\n const messageForAgent = opts.message.trimStart().startsWith('/')\n ? opts.message\n : prependEnvelopeTimestamp(opts.message);\n\n const stream = this.agent!.processDirectStreaming(\n messageForAgent,\n opts.sessionKey,\n undefined,\n opts.thinking,\n { signal },\n );\n\n for await (const event of stream) {\n if (signal.aborted) break;\n this.onEvent?.({ event: event.type, data: event });\n }\n\n if (!signal.aborted) {\n this.onEvent?.({\n event: 'result',\n data: { ok: true },\n });\n }\n } catch (error) {\n if (signal.aborted) return;\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.onEvent?.({ event: 'error', data: { content: errorMessage } });\n }\n })();\n\n return { runId };\n }\n\n async abortChat(_opts: { sessionKey: string; runId: string }): Promise<{ ok: boolean }> {\n if (this.chatAbort) {\n this.chatAbort.abort();\n this.chatAbort = null;\n return { ok: true };\n }\n return { ok: false };\n }\n\n async loadHistory(opts: {\n sessionKey: string;\n limit?: number;\n }): Promise<{ messages: HistoryMessage[] }> {\n if (!this.agent) {\n return { messages: [] };\n }\n try {\n const detail = await this.agent.loadSessionDetail(opts.sessionKey);\n if (!detail) {\n return { messages: [] };\n }\n return {\n messages: messagesToClientHistory(detail.messages, { limit: opts.limit }),\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Embedded loadHistory failed: ${errorMessage}`);\n return { messages: [] };\n }\n }\n\n async listSessions(): Promise<TuiSessionItem[]> {\n return [];\n }\n\n async getSessionInfo(_sessionKey: string): Promise<SessionInfo> {\n const config = loadConfig();\n const modelConfig = config.agents?.defaults?.model;\n const model = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n return { model: model ?? undefined };\n }\n\n async listModels(): Promise<TuiModelChoice[]> {\n const choices: TuiModelChoice[] = [];\n for (const provider of getAllProviders()) {\n for (const model of getModelsByProvider(provider)) {\n choices.push({\n id: model.id,\n name: model.name ?? model.id,\n provider,\n });\n }\n }\n return choices;\n }\n\n async resetSession(_sessionKey: string): Promise<void> {\n // Restart agent for a clean session\n this.stop();\n this.bus = new MessageBus();\n this.start();\n }\n\n async patchSession(\n _sessionKey: string,\n _patch: Record<string, unknown>,\n ): Promise<void> {\n // Not supported in embedded mode\n }\n\n private processOutbound(): void {\n void (async () => {\n while (this.running) {\n try {\n const msg = await this.bus.consumeOutbound();\n log.debug({ channel: msg.channel, chatId: msg.chat_id }, 'Outbound message');\n } catch (error) {\n if (error instanceof MessageBusShutdownError) break;\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Outbound processor failed: ${errorMessage}`);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n })();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"embedded-backend.js","names":[],"sources":["../../../../src/tui/backends/embedded-backend.ts"],"sourcesContent":["import { AgentService } from '../../agent/index.js';\nimport { parseModelRef } from '../../agent/models/selection.js';\nimport { messagesToClientHistory } from '../../session/client-history.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { loadConfig, getWorkspacePath } from '../../config/index.js';\nimport { MessageBus, MessageBusShutdownError } from '../../infra/bus/index.js';\nimport { getAllProviders, getModelsByProvider } from '../../providers/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type {\n ChatSendOptions,\n HistoryMessage,\n TuiBackend,\n TuiEvent,\n TuiModelChoice,\n TuiSessionItem,\n} from '../tui-backend.js';\nimport type { SessionInfo } from '../tui-types.js';\n\nconst log = createLogger('TUI:Embedded');\n\n/**\n * TUI backend that runs the agent in-process (no gateway required).\n *\n * Wraps `AgentService` directly and emits TuiEvents by observing the\n * `MessageBus` output stream.\n */\nexport class EmbeddedBackend implements TuiBackend {\n private bus: MessageBus;\n private agent: AgentService | null = null;\n private running = false;\n private chatAbort: AbortController | null = null;\n\n onEvent?: (evt: TuiEvent) => void;\n onConnected?: () => void;\n onDisconnected?: (reason: string) => void;\n\n constructor() {\n this.bus = new MessageBus();\n }\n\n get connectionLabel(): string {\n return 'local embedded';\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n\n const config = loadConfig();\n const workspace = getWorkspacePath(config);\n const modelConfig = config.agents?.defaults?.model;\n const modelId = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n\n this.agent = new AgentService(this.bus, {\n workspace: workspace ?? process.cwd(),\n model: modelId,\n config,\n });\n\n this.agent.start().catch((err) => {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage }, `Embedded agent failed: ${errorMessage}`);\n this.onDisconnected?.(errorMessage);\n });\n\n // Process outbound messages in background\n this.processOutbound();\n\n // Signal ready\n queueMicrotask(() => this.onConnected?.());\n }\n\n stop(): void {\n this.running = false;\n this.bus.shutdown();\n void this.agent?.stop();\n this.agent = null;\n }\n\n async sendChat(opts: ChatSendOptions): Promise<{ runId: string }> {\n if (!this.agent) throw new Error('Agent not started');\n\n const runId = crypto.randomUUID();\n this.chatAbort?.abort();\n this.chatAbort = new AbortController();\n const signal = this.chatAbort.signal;\n\n this.onEvent?.({ event: 'status', data: { status: 'started', runId } });\n\n // Run the stream in background so the TUI event loop stays responsive.\n void (async () => {\n try {\n // Prepend envelope timestamp so the model knows the current date/time,\n // matching the behavior of channel pipelines (Telegram, Weixin, etc.).\n // Skip for slash commands — parseSlashCommand requires lines starting with '/'.\n const messageForAgent = opts.message.trimStart().startsWith('/')\n ? opts.message\n : prependEnvelopeTimestamp(opts.message);\n\n const stream = this.agent!.processDirectStreaming(\n messageForAgent,\n opts.sessionKey,\n undefined,\n opts.thinking,\n { signal },\n );\n\n for await (const event of stream) {\n if (signal.aborted) break;\n this.onEvent?.({ event: event.type, data: event });\n }\n\n if (!signal.aborted) {\n this.onEvent?.({\n event: 'result',\n data: { ok: true },\n });\n }\n } catch (error) {\n if (signal.aborted) return;\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.onEvent?.({ event: 'error', data: { content: errorMessage } });\n }\n })();\n\n return { runId };\n }\n\n async abortChat(_opts: { sessionKey: string; runId: string }): Promise<{ ok: boolean }> {\n if (this.chatAbort) {\n this.chatAbort.abort();\n this.chatAbort = null;\n return { ok: true };\n }\n return { ok: false };\n }\n\n async loadHistory(opts: {\n sessionKey: string;\n limit?: number;\n }): Promise<{ messages: HistoryMessage[] }> {\n if (!this.agent) {\n return { messages: [] };\n }\n try {\n const detail = await this.agent.loadSessionDetail(opts.sessionKey);\n if (!detail) {\n return { messages: [] };\n }\n return {\n messages: messagesToClientHistory(detail.messages, { limit: opts.limit }),\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Embedded loadHistory failed: ${errorMessage}`);\n return { messages: [] };\n }\n }\n\n async listSessions(): Promise<TuiSessionItem[]> {\n return [];\n }\n\n async getSessionInfo(sessionKey: string): Promise<SessionInfo> {\n if (!this.agent) {\n const config = loadConfig();\n const modelConfig = config.agents?.defaults?.model;\n const model = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n return { model: model ?? undefined };\n }\n try {\n const cfg = await this.agent.getSessionAgentConfig(sessionKey);\n const parsed = parseModelRef(cfg.model);\n return {\n model: parsed?.model ?? cfg.model,\n modelProvider: parsed?.provider,\n thinkingLevel: cfg.thinkingLevel,\n };\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.warn({ err, sessionKey, errorMessage }, `getSessionInfo failed: ${errorMessage}`);\n const config = loadConfig();\n const modelConfig = config.agents?.defaults?.model;\n const model = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n return { model: model ?? undefined };\n }\n }\n\n async listModels(): Promise<TuiModelChoice[]> {\n const choices: TuiModelChoice[] = [];\n for (const provider of getAllProviders()) {\n for (const model of getModelsByProvider(provider)) {\n choices.push({\n id: model.id,\n name: model.name ?? model.id,\n provider,\n });\n }\n }\n return choices;\n }\n\n async resetSession(_sessionKey: string): Promise<void> {\n // Restart agent for a clean session\n this.stop();\n this.bus = new MessageBus();\n this.start();\n }\n\n async patchSession(\n _sessionKey: string,\n _patch: Record<string, unknown>,\n ): Promise<void> {\n // Not supported in embedded mode\n }\n\n private processOutbound(): void {\n void (async () => {\n while (this.running) {\n try {\n const msg = await this.bus.consumeOutbound();\n log.debug({ channel: msg.channel, chatId: msg.chat_id }, 'Outbound message');\n } catch (error) {\n if (error instanceof MessageBusShutdownError) break;\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Outbound processor failed: ${errorMessage}`);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n })();\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
4
+ import { parseModelRef } from "../../agent/models/selection.js";
4
5
  import { consumeSSEStream, parseSSEData } from "../sse-consumer.js";
5
6
  //#region src/tui/backends/gateway-sse-backend.ts
6
7
  init_logger();
@@ -160,17 +161,42 @@ var GatewaySseBackend = class {
160
161
  }
161
162
  }
162
163
  async getSessionInfo(sessionKey) {
164
+ const out = {};
163
165
  try {
164
- const res = await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}`, this.token);
165
- if (!res.ok) return {};
166
- const s = (await res.json()).session;
167
- if (!s) return {};
168
- return {
169
- displayName: s.name,
170
- totalTokens: s.estimatedTokens ?? void 0,
171
- model: typeof s.customData?.model === "string" ? s.customData.model : typeof s.customData?.modelRef === "string" ? s.customData.modelRef : void 0,
172
- modelProvider: typeof s.customData?.modelProvider === "string" ? s.customData.modelProvider : void 0
173
- };
166
+ const sessionPath = `/api/sessions/${encodeURIComponent(sessionKey)}`;
167
+ const [sessionRes, agentCfgRes] = await Promise.all([gatewayFetch(this.baseUrl, sessionPath, this.token), gatewayFetch(this.baseUrl, `${sessionPath}/agent-config`, this.token)]);
168
+ let session;
169
+ if (sessionRes.ok) {
170
+ session = (await sessionRes.json()).session;
171
+ if (session) {
172
+ if (session.name) out.displayName = session.name;
173
+ if (session.estimatedTokens != null) out.totalTokens = session.estimatedTokens;
174
+ }
175
+ }
176
+ if (agentCfgRes.ok) {
177
+ const p = (await agentCfgRes.json()).payload;
178
+ if (p?.model && typeof p.model === "string") {
179
+ const parsed = parseModelRef(p.model);
180
+ if (parsed) {
181
+ out.model = parsed.model;
182
+ out.modelProvider = parsed.provider;
183
+ } else out.model = p.model;
184
+ }
185
+ if (p?.thinkingLevel && typeof p.thinkingLevel === "string") out.thinkingLevel = p.thinkingLevel;
186
+ }
187
+ if (!out.model && session?.customData) {
188
+ const cd = session.customData;
189
+ const ref = typeof cd.model === "string" ? cd.model : typeof cd.modelRef === "string" ? cd.modelRef : void 0;
190
+ if (ref) {
191
+ const parsed = parseModelRef(ref);
192
+ if (parsed) {
193
+ out.model = parsed.model;
194
+ out.modelProvider = parsed.provider;
195
+ } else out.model = ref;
196
+ }
197
+ if (!out.modelProvider && typeof cd.modelProvider === "string") out.modelProvider = cd.modelProvider;
198
+ }
199
+ return out;
174
200
  } catch {
175
201
  return {};
176
202
  }