@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.
- package/dist/extensions/telegram/src/command-handler.js +1 -1
- package/dist/extensions/telegram/src/command-handler.js.map +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js → agents-BdISC5UA.js} +2 -2
- package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js.map → agents-BdISC5UA.js.map} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js → apps-page-CXU_Jg95.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js.map → apps-page-CXU_Jg95.js.map} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-CjUmKQrC.js → channels-settings-Doe1ciOW.js} +2 -2
- package/dist/gateway/static/root/assets/{channels-settings-CjUmKQrC.js.map → channels-settings-Doe1ciOW.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js → cron-dreaming-jobs-C-V4_3vz.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js.map → cron-dreaming-jobs-C-V4_3vz.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js → cron-page-CIIy81K6.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js.map → cron-page-CIIy81K6.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-wDej8fSi.js → dist-VW7dXc5X.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-wDej8fSi.js.map → dist-VW7dXc5X.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js → extension-debug-page-Cslwx62-.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js.map → extension-debug-page-Cslwx62-.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js → extension-page-Dzyebr6T.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js.map → extension-page-Dzyebr6T.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js → extension-settings-page-B07uetL_.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js.map → extension-settings-page-B07uetL_.js.map} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js → heartbeat-config-api-kmvGNriW.js} +2 -2
- package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js.map → heartbeat-config-api-kmvGNriW.js.map} +1 -1
- package/dist/gateway/static/root/assets/{index-DeELk--t.js → index-DW6JvymK.js} +11 -11
- package/dist/gateway/static/root/assets/{index-DeELk--t.js.map → index-DW6JvymK.js.map} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js → logs-page-BiRnV2Ex.js} +2 -2
- package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js.map → logs-page-BiRnV2Ex.js.map} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js → sessions-page-DQq0QaQh.js} +2 -2
- package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js.map → sessions-page-DQq0QaQh.js.map} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js → settings-page-BV_l8nJ3.js} +2 -2
- package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js.map → settings-page-BV_l8nJ3.js.map} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js → skills-page-Bv0pphDM.js} +2 -2
- package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js.map → skills-page-Bv0pphDM.js.map} +1 -1
- package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js → use-image-provider-credentials-BPcW1K0N.js} +2 -2
- package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js.map → use-image-provider-credentials-BPcW1K0N.js.map} +1 -1
- package/dist/gateway/static/root/index.html +1 -1
- package/dist/package.js +1 -1
- package/dist/src/agent/goals/checklist-judge.js +21 -17
- package/dist/src/agent/goals/checklist-judge.js.map +1 -1
- package/dist/src/agent/goals/evaluate-turn.js +6 -11
- package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
- package/dist/src/agent/goals/judge.d.ts +16 -0
- package/dist/src/agent/goals/judge.js +61 -13
- package/dist/src/agent/goals/judge.js.map +1 -1
- package/dist/src/agent/goals/state.d.ts +7 -0
- package/dist/src/agent/goals/state.js +24 -1
- package/dist/src/agent/goals/state.js.map +1 -1
- package/dist/src/chat-commands/builtins/model.d.ts +2 -2
- package/dist/src/chat-commands/builtins/model.js +10 -8
- package/dist/src/chat-commands/builtins/model.js.map +1 -1
- package/dist/src/chat-commands/builtins/system.js +1 -1
- package/dist/src/chat-commands/builtins/system.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +1 -1
- package/dist/src/tui/backends/embedded-backend.js +24 -3
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.js +36 -10
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/tui-commands.js +1 -1
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui.js +12 -1
- package/dist/src/tui/tui.js.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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":"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"}
|
|
@@ -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
|
|
6
|
-
* - /switch - Switch
|
|
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
|
|
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
|
-
|
|
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(`
|
|
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
|
|
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: "❌
|
|
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
|
|
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: `❌
|
|
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
|
|
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* -
|
|
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* -
|
|
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(
|
|
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(
|
|
132
|
-
|
|
133
|
-
|
|
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":";;;;;;;;;;;;;;gBAMgF;aAC3B;AAWrD,MAAM,MAAM,aAAa,eAAe;;;;;;;AAQxC,IAAa,kBAAb,MAAmD;CACjD;CACA,QAAqC;CACrC,UAAkB;CAClB,YAA4C;CAE5C;CACA;CACA;CAEA,cAAc;AACZ,OAAK,MAAM,IAAI,YAAY;;CAG7B,IAAI,kBAA0B;AAC5B,SAAO;;CAGT,QAAc;AACZ,MAAI,KAAK,QAAS;AAClB,OAAK,UAAU;EAEf,MAAM,SAAS,YAAY;EAC3B,MAAM,YAAY,iBAAiB,OAAO;EAC1C,MAAM,cAAc,OAAO,QAAQ,UAAU;EAC7C,MAAM,UAAU,OAAO,gBAAgB,WAAW,cAAc,aAAa;AAE7E,OAAK,QAAQ,IAAI,aAAa,KAAK,KAAK;GACtC,WAAW,aAAa,QAAQ,KAAK;GACrC,OAAO;GACP;GACD,CAAC;AAEF,OAAK,MAAM,OAAO,CAAC,OAAO,QAAQ;GAChC,MAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AACrE,OAAI,MAAM;IAAE;IAAK;IAAc,EAAE,0BAA0B,eAAe;AAC1E,QAAK,iBAAiB,aAAa;IACnC;AAGF,OAAK,iBAAiB;AAGtB,uBAAqB,KAAK,eAAe,CAAC;;CAG5C,OAAa;AACX,OAAK,UAAU;AACf,OAAK,IAAI,UAAU;AACd,OAAK,OAAO,MAAM;AACvB,OAAK,QAAQ;;CAGf,MAAM,SAAS,MAAmD;AAChE,MAAI,CAAC,KAAK,MAAO,OAAM,IAAI,MAAM,oBAAoB;EAErD,MAAM,QAAQ,OAAO,YAAY;AACjC,OAAK,WAAW,OAAO;AACvB,OAAK,YAAY,IAAI,iBAAiB;EACtC,MAAM,SAAS,KAAK,UAAU;AAE9B,OAAK,UAAU;GAAE,OAAO;GAAU,MAAM;IAAE,QAAQ;IAAW;IAAO;GAAE,CAAC;AAGvE,GAAM,YAAY;AAChB,OAAI;IAIF,MAAM,kBAAkB,KAAK,QAAQ,WAAW,CAAC,WAAW,IAAI,GAC5D,KAAK,UACL,yBAAyB,KAAK,QAAQ;IAE1C,MAAM,SAAS,KAAK,MAAO,uBACzB,iBACA,KAAK,YACL,KAAA,GACA,KAAK,UACL,EAAE,QAAQ,CACX;AAED,eAAW,MAAM,SAAS,QAAQ;AAChC,SAAI,OAAO,QAAS;AACpB,UAAK,UAAU;MAAE,OAAO,MAAM;MAAM,MAAM;MAAO,CAAC;;AAGpD,QAAI,CAAC,OAAO,QACV,MAAK,UAAU;KACb,OAAO;KACP,MAAM,EAAE,IAAI,MAAM;KACnB,CAAC;YAEG,OAAO;AACd,QAAI,OAAO,QAAS;IACpB,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,SAAK,UAAU;KAAE,OAAO;KAAS,MAAM,EAAE,SAAS,cAAc;KAAE,CAAC;;MAEnE;AAEJ,SAAO,EAAE,OAAO;;CAGlB,MAAM,UAAU,OAAwE;AACtF,MAAI,KAAK,WAAW;AAClB,QAAK,UAAU,OAAO;AACtB,QAAK,YAAY;AACjB,UAAO,EAAE,IAAI,MAAM;;AAErB,SAAO,EAAE,IAAI,OAAO;;CAGtB,MAAM,YAAY,MAG0B;AAC1C,MAAI,CAAC,KAAK,MACR,QAAO,EAAE,UAAU,EAAE,EAAE;AAEzB,MAAI;GACF,MAAM,SAAS,MAAM,KAAK,MAAM,kBAAkB,KAAK,WAAW;AAClE,OAAI,CAAC,OACH,QAAO,EAAE,UAAU,EAAE,EAAE;AAEzB,UAAO,EACL,UAAU,wBAAwB,OAAO,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC,EAC1E;WACM,OAAO;GACd,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,OAAI,KAAK;IAAE,KAAK;IAAO;IAAc,EAAE,gCAAgC,eAAe;AACtF,UAAO,EAAE,UAAU,EAAE,EAAE;;;CAI3B,MAAM,eAA0C;AAC9C,SAAO,EAAE;;CAGX,MAAM,eAAe,YAA0C;AAC7D,MAAI,CAAC,KAAK,OAAO;GAEf,MAAM,cADS,YACW,CAAC,QAAQ,UAAU;AAE7C,UAAO,EAAE,QADK,OAAO,gBAAgB,WAAW,cAAc,aAAa,YAClD,KAAA,GAAW;;AAEtC,MAAI;GACF,MAAM,MAAM,MAAM,KAAK,MAAM,sBAAsB,WAAW;GAC9D,MAAM,SAAS,cAAc,IAAI,MAAM;AACvC,UAAO;IACL,OAAO,QAAQ,SAAS,IAAI;IAC5B,eAAe,QAAQ;IACvB,eAAe,IAAI;IACpB;WACM,KAAK;GACZ,MAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AACrE,OAAI,KAAK;IAAE;IAAK;IAAY;IAAc,EAAE,0BAA0B,eAAe;GAErF,MAAM,cADS,YACW,CAAC,QAAQ,UAAU;AAE7C,UAAO,EAAE,QADK,OAAO,gBAAgB,WAAW,cAAc,aAAa,YAClD,KAAA,GAAW;;;CAIxC,MAAM,aAAwC;EAC5C,MAAM,UAA4B,EAAE;AACpC,OAAK,MAAM,YAAY,iBAAiB,CACtC,MAAK,MAAM,SAAS,oBAAoB,SAAS,CAC/C,SAAQ,KAAK;GACX,IAAI,MAAM;GACV,MAAM,MAAM,QAAQ,MAAM;GAC1B;GACD,CAAC;AAGN,SAAO;;CAGT,MAAM,aAAa,aAAoC;AAErD,OAAK,MAAM;AACX,OAAK,MAAM,IAAI,YAAY;AAC3B,OAAK,OAAO;;CAGd,MAAM,aACJ,aACA,QACe;CAIjB,kBAAgC;AAC9B,GAAM,YAAY;AAChB,UAAO,KAAK,QACV,KAAI;IACF,MAAM,MAAM,MAAM,KAAK,IAAI,iBAAiB;AAC5C,QAAI,MAAM;KAAE,SAAS,IAAI;KAAS,QAAQ,IAAI;KAAS,EAAE,mBAAmB;YACrE,OAAO;AACd,QAAI,iBAAiB,wBAAyB;IAC9C,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,QAAI,KAAK;KAAE,KAAK;KAAO;KAAc,EAAE,8BAA8B,eAAe;AACpF,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,IAAK,CAAC;;MAG3D"}
|
|
@@ -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
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
}
|