@xopcai/xopc 0.0.141 → 0.0.142
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/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BRTCpm3t.js → agent-avatar-dicebear-BawPxiu5.js} +1 -1
- package/dist/gateway/static/root/assets/agents-CyvCIHnB.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-DaMMZBAT.js → app-management-settings-panel-81xd1p9R.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BXHThPyM.js → appearance-settings-DYeQsYU8.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-B4omjLxa.js → apps-page-DK_Vx6nl.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-izB-4N4L.js → archive-plugin-D_oa7oti.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CJihXa5y.js → attachment-load-Cd-bwI2N.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-fgtj5Gek.js → automation-suggestion-card-B1LV3YYv.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-fzrovqhv.js +4 -0
- package/dist/gateway/static/root/assets/{binary-plugins-D3VUeG8s.js → binary-plugins-O8svLKWw.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-KnA0sLI3.js → block-editor-Dhj5f4Bx.js} +47 -47
- package/dist/gateway/static/root/assets/browser-settings-page-BDn-n-UB.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-D8rRNhc6.js → capability-presets-api-dJy1Eqgx.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-sCHojZr4.js → capability-presets-settings-panel-Co5ipo_Q.js} +1 -1
- package/dist/gateway/static/root/assets/{channel-recipient-api-BWgqwC5h.js → channel-recipient-api-Bfuhp2ty.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-BWlpaxg9.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-D_jkIZLV.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-BcekbW4q.js +2 -0
- package/dist/gateway/static/root/assets/{desktop-pet-CzdqX001.js → desktop-pet-DFMfIb5F.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-DqqUPC7G.js → desktop-pet-settings-BJCUuL8Y.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-PzlO5tSM.js → directory-picker-path-field-BrYt1gAr.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-BmL54kQq.js → dreaming-settings-BN1Z2mIo.js} +3 -3
- package/dist/gateway/static/root/assets/{extension-debug-page-DcW8Ie3r.js → extension-debug-page-Bh630pLD.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DT5cpace.js → extension-page-B-1KxvVy.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CbMk4GhQ.js → extension-settings-page-BF9NACI3.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-06q5eCMA.js → fetch-B1WtuprY.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-4VXUJtLz.js → gateway-config-api-De2N821l.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-CdghmKHJ.js → gateway-settings-DN9sNREz.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-2f7_vsv7.js → gateway-startup-retry-nEujFSDj.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-BpoQW8cD.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-CBRSPd0b.js +4 -0
- package/dist/gateway/static/root/assets/{goals-config-api-BpuQTufF.js → goals-config-api-DUy3eEse.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-BCcBi407.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-CW1NwxvV.js → goals-settings-pkTXzXkh.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-C86k5tff.js → heartbeat-settings-ezsufgLQ.js} +1 -1
- package/dist/gateway/static/root/assets/{index-ehajXmqL.js → index-DcUaXYeb.js} +65 -65
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-CVIwwM15.js +1 -0
- package/dist/gateway/static/root/assets/logs-page-DQtTRXtj.js +7 -0
- package/dist/gateway/static/root/assets/{markdown-split-zS4KLNAf.js → markdown-split-B_6us9j7.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-_BgNJ9_7.js → media-plugins-D_fryIyj.js} +1 -1
- package/dist/gateway/static/root/assets/messages-BXNELNBa.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-BP2M9n6Q.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-CspveYaO.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-BHwzJ0OC.js +2 -0
- package/dist/gateway/static/root/assets/{page-tabs-B7sGQkgU.js → page-tabs-D_8jGlqx.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-D1ri4s_O.js → preview-open-alternatives-BBLMK8Mp.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-Dy3rsobs.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-CWKpPE0L.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-CqNRyXEo.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-BClrOinh.js → save-bar-controls-DVS3FR5r.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-CL7YTwLV.js → schedule-field-D6vKd6-T.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-ww4PiIje.js → schema-form-BPy4lbIh.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-MEME7ivF.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-JVd0qaSF.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-D9gEEVm2.js → settings-collapsible-section-WzdikxPt.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DGeEoBkb.js → settings-form-section-CvfzhBV-.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-BXUeMR37.js → settings-loading-skeleton-BxR-Ulhi.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-C5G2klUB.js → settings-page-DYEbB1EV.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-CEepfbCa.js → settings-page-layout-CKefvbYi.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-oQ8OLnw3.js → setup-status-panel-dWN8BJ3z.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-CXtDHMSi.js → share-preview-page-D8foINzP.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-Tgbv1s7G.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-D_YK0b_Z.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-DGytfoZK.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-W6MBJXrr.js → system-settings-panel-BokgAgf7.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-BkODXN7T.js → typed-models-lib-BwHlNL3L.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-CTbwLZXc.js → use-channel-catalog-DR11ap3A.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-CeC308-E.js → user-profile-settings-panel-8YtygcJp.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-ZxrKHxyb.js → utils-BKUWB-TT.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-BVH71EFf.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-JeTE7XNM.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-CL_rnAUB.js +3 -0
- package/dist/gateway/static/root/assets/{workflow-start-dialog-CtLTtNUB.js → workflow-start-dialog-_u8Uc1ze.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-ixgk_y1l.js +18 -0
- package/dist/gateway/static/root/index.html +4 -4
- package/dist/package.js +1 -1
- package/dist/src/agent/embedded/session-runner.js +5 -5
- package/dist/src/agent/embedded/session-runner.js.map +1 -1
- package/dist/src/agent/embedded/xopc-auth-storage.d.ts +4 -3
- package/dist/src/agent/embedded/xopc-auth-storage.js +6 -6
- package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
- package/dist/src/agent/embedded/xopc-stream-bridge.js +1 -0
- package/dist/src/agent/embedded/xopc-stream-bridge.js.map +1 -1
- package/dist/src/agent/goals/checklist-judge.js +3 -3
- package/dist/src/agent/goals/checklist-judge.js.map +1 -1
- package/dist/src/agent/goals/judge.js +2 -2
- package/dist/src/agent/goals/judge.js.map +1 -1
- package/dist/src/agent/image/understanding/pi-ai-provider.js +2 -2
- package/dist/src/agent/image/understanding/pi-ai-provider.js.map +1 -1
- package/dist/src/agent/memory/compaction.js +2 -2
- package/dist/src/agent/memory/compaction.js.map +1 -1
- package/dist/src/agent/service/btw-query.d.ts +4 -0
- package/dist/src/agent/service/btw-query.js +94 -13
- package/dist/src/agent/service/btw-query.js.map +1 -1
- package/dist/src/agent/session/session-inspector.js +9 -1
- package/dist/src/agent/session/session-inspector.js.map +1 -1
- package/dist/src/agent/tools/session-search-tool.js +2 -2
- package/dist/src/agent/tools/session-search-tool.js.map +1 -1
- package/dist/src/agent/tools/web-extract.js +2 -2
- package/dist/src/agent/tools/web-extract.js.map +1 -1
- package/dist/src/ai-assist/text-assist.d.ts +2 -1
- package/dist/src/ai-assist/text-assist.js +10 -23
- package/dist/src/ai-assist/text-assist.js.map +1 -1
- package/dist/src/auth/credentials.js +5 -2
- package/dist/src/auth/credentials.js.map +1 -1
- package/dist/src/auth/oauth/github-copilot.d.ts +3 -4
- package/dist/src/auth/oauth/github-copilot.js +66 -2
- package/dist/src/auth/oauth/github-copilot.js.map +1 -0
- package/dist/src/auth/oauth/index.js +2 -2
- package/dist/src/automations/draft/automation-draft-service.js +3 -3
- package/dist/src/automations/draft/automation-draft-service.js.map +1 -1
- package/dist/src/chat-commands/builtins/review.js +22 -5
- package/dist/src/chat-commands/builtins/review.js.map +1 -1
- package/dist/src/chat-commands/types.d.ts +4 -0
- package/dist/src/gateway/hono/routes/models.js +1 -0
- package/dist/src/gateway/hono/routes/models.js.map +1 -1
- package/dist/src/gateway/hono/routes/voice.js +2 -2
- package/dist/src/gateway/hono/routes/voice.js.map +1 -1
- package/dist/src/goals/goal-contract-draft.js +2 -2
- package/dist/src/goals/goal-contract-draft.js.map +1 -1
- package/dist/src/goals/goal-evidence-review.js +2 -2
- package/dist/src/goals/goal-evidence-review.js.map +1 -1
- package/dist/src/notes/service.js +2 -2
- package/dist/src/notes/service.js.map +1 -1
- package/dist/src/providers/constants.d.ts +2 -0
- package/dist/src/providers/constants.js +11 -0
- package/dist/src/providers/constants.js.map +1 -0
- package/dist/src/providers/extension-stream-bridge.js +1 -0
- package/dist/src/providers/extension-stream-bridge.js.map +1 -1
- package/dist/src/providers/index.d.ts +3 -3
- package/dist/src/providers/index.js +5 -4
- package/dist/src/providers/index.js.map +1 -1
- package/dist/src/providers/model-call.d.ts +17 -0
- package/dist/src/providers/model-call.js +60 -0
- package/dist/src/providers/model-call.js.map +1 -0
- package/dist/src/session/session-title.js +2 -2
- package/dist/src/session/session-title.js.map +1 -1
- package/dist/src/voice/tts/summarize.js +2 -2
- package/dist/src/voice/tts/summarize.js.map +1 -1
- package/dist/src/workflows/draft/workflow-draft-service.js +2 -2
- package/dist/src/workflows/draft/workflow-draft-service.js.map +1 -1
- package/package.json +5 -5
- package/dist/gateway/static/root/assets/agents-Bd863Akz.js +0 -2
- package/dist/gateway/static/root/assets/automations-page-Dz2mgjIR.js +0 -4
- package/dist/gateway/static/root/assets/browser-settings-page-DZZz5v_i.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-UyVSnf9s.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-CB5FzcLf.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-BzhHiUO4.js +0 -2
- package/dist/gateway/static/root/assets/goal-create-dialog-CAlsM9MP.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-CLPa4F2y.js +0 -4
- package/dist/gateway/static/root/assets/goals-page-B2VpdzfH.js +0 -2
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-8xwKEhui.js +0 -1
- package/dist/gateway/static/root/assets/logs-page-C_BMONOO.js +0 -7
- package/dist/gateway/static/root/assets/messages-B-8CqAeX.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-Do_cwNjF.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-5IdlPHaX.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-0KsRZJIZ.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-Dk2zB1gy.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-CJQntm2G.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CHeZZg7O.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-9Hb_r0AG.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-PmzVFvAv.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-BlNoairj.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BYV5x1KO.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-ClBcKQZP.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-BE57S-nS.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-Di1ww5Aa.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-CzK84i-B.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-CZFZdua9.js +0 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-title.js","names":[],"sources":["../../../src/session/session-title.ts"],"sourcesContent":["/**\n * LLM-generated session titles (webchat and any path using SessionStore).\n */\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport { stripSessionStartupContextFromUserText } from '../agent/reply/startup-context.js';\nimport { stripEnvelopeTimestampPrefix } from '../channels/envelope-timestamp.js';\nimport { isCronSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { resolveModel } from '../providers/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport { readAgentMessageContent } from '../agent/memory/agent-message-access.js';\nimport type { SessionStore } from './store.js';\n\nconst log = createLogger('SessionAutoTitle');\n\nconst MAX_TITLE_LEN = 80;\n\n/** Collect visible text from any content block that exposes `text` (pi-ai / OpenAI / Anthropic shapes). */\nfunction extractTextFromMessage(m: AgentMessage): string {\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') return raw.trim();\n if (Array.isArray(raw)) {\n const parts: string[] = [];\n for (const c of raw) {\n if (c && typeof c === 'object') {\n const o = c as unknown as Record<string, unknown>;\n const type = typeof o.type === 'string' ? o.type : '';\n if (type === 'toolCall' || type === 'tool_use' || type === 'tool_result') continue;\n if (typeof o.text === 'string' && o.text.trim()) {\n parts.push(o.text.trim());\n }\n }\n }\n return parts.join(' ').trim();\n }\n return '';\n}\n\nfunction firstUserText(messages: AgentMessage[]): string {\n const u = messages.find((m) => m.role === 'user');\n if (!u) return '';\n const raw = extractTextFromMessage(u);\n return normalizeUserTextForTitle(raw);\n}\n\nfunction stripMediaClaimChecks(text: string): string {\n if (!text.includes('[media attached:') && !text.includes('xopc-media-uri:')) {\n return text;\n }\n return text\n .replace(\n /\\s*\\[media attached:[^\\]]+\\]\\s*\\r?\\nxopc-media-uri:[^\\r\\n]+\\r?\\n\\s*xopc-media-path:[^\\r\\n]+(?:\\r?\\n\\s*Use the read_media tool[^\\r\\n]*)?/g,\n '',\n )\n .replace(/\\s*\\[media attached:[^\\]]+\\]\\s*/g, ' ')\n .replace(/\\s*xopc-media-uri:[^\\r\\n]+/g, '')\n .replace(/\\s*xopc-media-path:[^\\r\\n]+/g, '')\n .replace(/\\s*Use the read_media tool[^\\r\\n]*/g, '')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nfunction stripExpandedSkillBlockForTitle(text: string): string {\n const trimmed = text.trimStart();\n if (!/^##\\s+Skill:\\s*\\S+/i.test(trimmed)) {\n return text;\n }\n\n const markerRe = /(?:^|\\r?\\n)\\*\\*Arguments\\*\\*:\\s*/gi;\n let argsStart = -1;\n while (markerRe.exec(trimmed) !== null) {\n argsStart = markerRe.lastIndex;\n }\n\n if (argsStart !== -1) {\n const args = trimmed.slice(argsStart).trim();\n if (args) return args;\n }\n\n const firstLine = trimmed.split(/\\r?\\n/)[0]?.trim() ?? '';\n return firstLine.replace(/^##\\s+Skill:\\s*/i, '').trim();\n}\n\nfunction stripSkillCommandForTitle(text: string): string {\n const trimmed = text.trimStart();\n const match = trimmed.match(/^\\/skill:([^\\s]+)(?:\\s+([\\s\\S]*))?$/i);\n if (!match) {\n return text;\n }\n const args = match[2]?.trim();\n return args || match[1]?.trim() || text;\n}\n\nfunction normalizeUserTextForTitle(raw: string): string {\n return stripSkillCommandForTitle(\n stripExpandedSkillBlockForTitle(\n stripMediaClaimChecks(\n stripEnvelopeTimestampPrefix(stripSessionStartupContextFromUserText(raw)),\n ),\n ),\n ).trim();\n}\n\n/** First assistant message that has visible text (skips tool-only assistant rows). */\nfunction firstAssistantText(messages: AgentMessage[]): string {\n for (const m of messages) {\n if (m.role === 'assistant') {\n const t = extractTextFromMessage(m);\n if (t.length > 0) return t;\n }\n }\n return '';\n}\n\nexport function isWebchatSessionKey(sessionKey: string): boolean {\n const p = parseSessionKey(sessionKey);\n if (p?.source === 'webchat') return true;\n return sessionKey.includes(':webchat:');\n}\n\n/** Whether to run LLM/fallback session naming for this key (excludes cron, heartbeat). */\nexport function shouldAutoTitleSessionKey(sessionKey: string): boolean {\n const raw = (sessionKey ?? '').trim();\n if (!raw) return false;\n if (isCronSessionKey(raw)) return false;\n if (raw.toLowerCase().startsWith('heartbeat:')) return false;\n return true;\n}\n\nexport function sanitizeSessionTitle(raw: string): string {\n let s = raw.trim();\n if ((s.startsWith('\"') && s.endsWith('\"')) || (s.startsWith(\"'\") && s.endsWith(\"'\"))) {\n s = s.slice(1, -1).trim();\n }\n const lineBreak = s.indexOf('\\n');\n if (lineBreak !== -1) s = s.slice(0, lineBreak).trim();\n if (s.length > MAX_TITLE_LEN) s = s.slice(0, MAX_TITLE_LEN - 1).trimEnd() + '…';\n return s;\n}\n\nexport type SessionTitleSource = 'provisional' | 'llm' | 'user';\n\nexport function getSessionTitleSource(\n meta: { customData?: Record<string, unknown> } | null | undefined,\n): SessionTitleSource | null {\n const raw = meta?.customData?.titleSource;\n if (raw === 'provisional' || raw === 'llm' || raw === 'user') return raw;\n return null;\n}\n\n/** Title from a single user message (first line), for immediate sidebar labels. */\nexport function provisionalTitleFromUserText(raw: string): string | null {\n const text = normalizeUserTextForTitle((raw ?? '').trim());\n if (!text) return null;\n const line = text.split(/\\n/)[0]?.trim();\n if (!line) return null;\n return sanitizeSessionTitle(line);\n}\n\n/** Non-LLM title: first line of first user text, else first assistant line. */\nexport function fallbackTitleFromMessages(messages: AgentMessage[]): string | null {\n const u = firstUserText(messages);\n if (u) {\n const line = u.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n const a = firstAssistantText(messages);\n if (a) {\n const line = a.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n return null;\n}\n\n/**\n * Returns a title string, or null if generation should be skipped or failed.\n */\nexport async function generateSessionTitleFromMessages(\n modelRef: string,\n messages: AgentMessage[],\n signal?: AbortSignal,\n): Promise<string | null> {\n const userText = firstUserText(messages);\n const assistantText = firstAssistantText(messages);\n if (!userText && !assistantText) return null;\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n log.warn({ err, modelRef }, 'Cannot resolve model for session title');\n return null;\n }\n\n const prompt =\n userText && assistantText\n ? `You label chat sessions. Given the first user message and the start of the assistant reply, output ONE short title (max 8 words). No quotes. No punctuation at the end. Use the same language as the user when possible.\n\nUser: ${userText.slice(0, 2000)}\n\nAssistant: ${assistantText.slice(0, 2000)}\n\nTitle:`\n : userText\n ? `The assistant reply only used tools (no visible text yet). Output ONE short title (max 8 words) based only on the user's first message. No quotes. No punctuation at the end. Use the same language as the user.\n\nUser: ${userText.slice(0, 2000)}\n\nTitle:`\n : `Output ONE short title (max 8 words) for this assistant reply. No quotes. No punctuation at the end.\n\nAssistant: ${assistantText!.slice(0, 2000)}\n\nTitle:`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n\n try {\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 64,\n temperature: 0.35,\n signal: signal as AbortSignal,\n },\n );\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\n const cleaned = sanitizeSessionTitle(text);\n return cleaned.length > 0 ? cleaned : null;\n } catch (err) {\n log.warn({ err }, 'Session title LLM call failed');\n return null;\n }\n}\n\nexport type SessionTitleUpdatedHook = (sessionKey: string, name: string) => void | Promise<void>;\n\n/** Whether post-turn LLM refine may run (provisional or still unnamed; not user-locked). */\nexport function shouldRefineSessionTitleWithLlm(\n meta: { name?: string; customData?: Record<string, unknown> } | null | undefined,\n): boolean {\n if (!meta) return false;\n const source = getSessionTitleSource(meta);\n if (source === 'user') return false;\n if (meta.name?.trim()) return source === 'provisional';\n return true;\n}\n\nfunction canAutoWriteTitle(meta: { name?: string; customData?: Record<string, unknown> } | null): boolean {\n if (!meta) return false;\n const source = getSessionTitleSource(meta);\n if (source === 'user') return false;\n return !meta.name?.trim();\n}\n\n/**\n * Set provisional title from first user text when session is still unnamed.\n * Skips cron/heartbeat keys and user-locked titles.\n */\nexport async function maybeSetProvisionalSessionTitle(\n sessionStore: SessionStore,\n sessionKey: string,\n userText?: string,\n onUpdated?: SessionTitleUpdatedHook,\n): Promise<void> {\n if (!shouldAutoTitleSessionKey(sessionKey)) return;\n\n let meta = await sessionStore.getMetadata(sessionKey);\n if (!meta) return;\n if (!canAutoWriteTitle(meta)) return;\n\n let title: string | null = null;\n if (userText?.trim()) {\n title = provisionalTitleFromUserText(userText);\n }\n if (!title) {\n const messages = await sessionStore.load(sessionKey);\n if (!messages.length) return;\n title = fallbackTitleFromMessages(messages);\n }\n if (!title) return;\n\n try {\n await sessionStore.updateMetadata(sessionKey, {\n name: title,\n customData: { ...(meta.customData ?? {}), titleSource: 'provisional' },\n });\n await onUpdated?.(sessionKey, title);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Session title: provisional updateMetadata failed');\n }\n}\n\n/**\n * LLM refine when title is empty or still provisional (not user-locked).\n */\nexport async function maybeRefineSessionTitleWithLlm(\n sessionStore: SessionStore,\n sessionKey: string,\n modelRef: string | undefined,\n onUpdated?: SessionTitleUpdatedHook,\n): Promise<void> {\n if (!shouldAutoTitleSessionKey(sessionKey)) return;\n\n let messages = await sessionStore.load(sessionKey);\n if (!messages.length) return;\n\n let meta = await sessionStore.getMetadata(sessionKey);\n if (!meta) {\n await sessionStore.saveMessages(sessionKey, messages);\n meta = await sessionStore.getMetadata(sessionKey);\n }\n if (!meta) {\n log.warn({ sessionKey }, 'Session title: metadata missing after save');\n return;\n }\n if (!shouldRefineSessionTitleWithLlm(meta)) return;\n\n const source = getSessionTitleSource(meta);\n let title: string | null = null;\n const ref = modelRef?.trim();\n if (ref) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 25_000);\n try {\n title = await generateSessionTitleFromMessages(ref, messages, controller.signal);\n } finally {\n clearTimeout(timeout);\n }\n }\n if (!title) {\n title = fallbackTitleFromMessages(messages);\n }\n if (!title) return;\n\n const existing = meta.name?.trim();\n if (existing === title) {\n if (source !== 'llm') {\n try {\n await sessionStore.updateMetadata(sessionKey, {\n customData: { ...(meta.customData ?? {}), titleSource: 'llm' },\n });\n } catch {\n /* ignore */\n }\n }\n return;\n }\n\n try {\n await sessionStore.updateMetadata(sessionKey, {\n name: title,\n customData: { ...(meta.customData ?? {}), titleSource: 'llm' },\n });\n await onUpdated?.(sessionKey, title);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Session title: refine updateMetadata failed');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-title.js","names":[],"sources":["../../../src/session/session-title.ts"],"sourcesContent":["/**\n * LLM-generated session titles (webchat and any path using SessionStore).\n */\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport { stripSessionStartupContextFromUserText } from '../agent/reply/startup-context.js';\nimport { stripEnvelopeTimestampPrefix } from '../channels/envelope-timestamp.js';\nimport { isCronSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { resolveModel } from '../providers/index.js';\nimport { completeWithResolvedCredentials } from '../providers/model-call.js';\nimport { createLogger } from '../utils/logger.js';\nimport { readAgentMessageContent } from '../agent/memory/agent-message-access.js';\nimport type { SessionStore } from './store.js';\n\nconst log = createLogger('SessionAutoTitle');\n\nconst MAX_TITLE_LEN = 80;\n\n/** Collect visible text from any content block that exposes `text` (pi-ai / OpenAI / Anthropic shapes). */\nfunction extractTextFromMessage(m: AgentMessage): string {\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') return raw.trim();\n if (Array.isArray(raw)) {\n const parts: string[] = [];\n for (const c of raw) {\n if (c && typeof c === 'object') {\n const o = c as unknown as Record<string, unknown>;\n const type = typeof o.type === 'string' ? o.type : '';\n if (type === 'toolCall' || type === 'tool_use' || type === 'tool_result') continue;\n if (typeof o.text === 'string' && o.text.trim()) {\n parts.push(o.text.trim());\n }\n }\n }\n return parts.join(' ').trim();\n }\n return '';\n}\n\nfunction firstUserText(messages: AgentMessage[]): string {\n const u = messages.find((m) => m.role === 'user');\n if (!u) return '';\n const raw = extractTextFromMessage(u);\n return normalizeUserTextForTitle(raw);\n}\n\nfunction stripMediaClaimChecks(text: string): string {\n if (!text.includes('[media attached:') && !text.includes('xopc-media-uri:')) {\n return text;\n }\n return text\n .replace(\n /\\s*\\[media attached:[^\\]]+\\]\\s*\\r?\\nxopc-media-uri:[^\\r\\n]+\\r?\\n\\s*xopc-media-path:[^\\r\\n]+(?:\\r?\\n\\s*Use the read_media tool[^\\r\\n]*)?/g,\n '',\n )\n .replace(/\\s*\\[media attached:[^\\]]+\\]\\s*/g, ' ')\n .replace(/\\s*xopc-media-uri:[^\\r\\n]+/g, '')\n .replace(/\\s*xopc-media-path:[^\\r\\n]+/g, '')\n .replace(/\\s*Use the read_media tool[^\\r\\n]*/g, '')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nfunction stripExpandedSkillBlockForTitle(text: string): string {\n const trimmed = text.trimStart();\n if (!/^##\\s+Skill:\\s*\\S+/i.test(trimmed)) {\n return text;\n }\n\n const markerRe = /(?:^|\\r?\\n)\\*\\*Arguments\\*\\*:\\s*/gi;\n let argsStart = -1;\n while (markerRe.exec(trimmed) !== null) {\n argsStart = markerRe.lastIndex;\n }\n\n if (argsStart !== -1) {\n const args = trimmed.slice(argsStart).trim();\n if (args) return args;\n }\n\n const firstLine = trimmed.split(/\\r?\\n/)[0]?.trim() ?? '';\n return firstLine.replace(/^##\\s+Skill:\\s*/i, '').trim();\n}\n\nfunction stripSkillCommandForTitle(text: string): string {\n const trimmed = text.trimStart();\n const match = trimmed.match(/^\\/skill:([^\\s]+)(?:\\s+([\\s\\S]*))?$/i);\n if (!match) {\n return text;\n }\n const args = match[2]?.trim();\n return args || match[1]?.trim() || text;\n}\n\nfunction normalizeUserTextForTitle(raw: string): string {\n return stripSkillCommandForTitle(\n stripExpandedSkillBlockForTitle(\n stripMediaClaimChecks(\n stripEnvelopeTimestampPrefix(stripSessionStartupContextFromUserText(raw)),\n ),\n ),\n ).trim();\n}\n\n/** First assistant message that has visible text (skips tool-only assistant rows). */\nfunction firstAssistantText(messages: AgentMessage[]): string {\n for (const m of messages) {\n if (m.role === 'assistant') {\n const t = extractTextFromMessage(m);\n if (t.length > 0) return t;\n }\n }\n return '';\n}\n\nexport function isWebchatSessionKey(sessionKey: string): boolean {\n const p = parseSessionKey(sessionKey);\n if (p?.source === 'webchat') return true;\n return sessionKey.includes(':webchat:');\n}\n\n/** Whether to run LLM/fallback session naming for this key (excludes cron, heartbeat). */\nexport function shouldAutoTitleSessionKey(sessionKey: string): boolean {\n const raw = (sessionKey ?? '').trim();\n if (!raw) return false;\n if (isCronSessionKey(raw)) return false;\n if (raw.toLowerCase().startsWith('heartbeat:')) return false;\n return true;\n}\n\nexport function sanitizeSessionTitle(raw: string): string {\n let s = raw.trim();\n if ((s.startsWith('\"') && s.endsWith('\"')) || (s.startsWith(\"'\") && s.endsWith(\"'\"))) {\n s = s.slice(1, -1).trim();\n }\n const lineBreak = s.indexOf('\\n');\n if (lineBreak !== -1) s = s.slice(0, lineBreak).trim();\n if (s.length > MAX_TITLE_LEN) s = s.slice(0, MAX_TITLE_LEN - 1).trimEnd() + '…';\n return s;\n}\n\nexport type SessionTitleSource = 'provisional' | 'llm' | 'user';\n\nexport function getSessionTitleSource(\n meta: { customData?: Record<string, unknown> } | null | undefined,\n): SessionTitleSource | null {\n const raw = meta?.customData?.titleSource;\n if (raw === 'provisional' || raw === 'llm' || raw === 'user') return raw;\n return null;\n}\n\n/** Title from a single user message (first line), for immediate sidebar labels. */\nexport function provisionalTitleFromUserText(raw: string): string | null {\n const text = normalizeUserTextForTitle((raw ?? '').trim());\n if (!text) return null;\n const line = text.split(/\\n/)[0]?.trim();\n if (!line) return null;\n return sanitizeSessionTitle(line);\n}\n\n/** Non-LLM title: first line of first user text, else first assistant line. */\nexport function fallbackTitleFromMessages(messages: AgentMessage[]): string | null {\n const u = firstUserText(messages);\n if (u) {\n const line = u.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n const a = firstAssistantText(messages);\n if (a) {\n const line = a.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n return null;\n}\n\n/**\n * Returns a title string, or null if generation should be skipped or failed.\n */\nexport async function generateSessionTitleFromMessages(\n modelRef: string,\n messages: AgentMessage[],\n signal?: AbortSignal,\n): Promise<string | null> {\n const userText = firstUserText(messages);\n const assistantText = firstAssistantText(messages);\n if (!userText && !assistantText) return null;\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n log.warn({ err, modelRef }, 'Cannot resolve model for session title');\n return null;\n }\n\n const prompt =\n userText && assistantText\n ? `You label chat sessions. Given the first user message and the start of the assistant reply, output ONE short title (max 8 words). No quotes. No punctuation at the end. Use the same language as the user when possible.\n\nUser: ${userText.slice(0, 2000)}\n\nAssistant: ${assistantText.slice(0, 2000)}\n\nTitle:`\n : userText\n ? `The assistant reply only used tools (no visible text yet). Output ONE short title (max 8 words) based only on the user's first message. No quotes. No punctuation at the end. Use the same language as the user.\n\nUser: ${userText.slice(0, 2000)}\n\nTitle:`\n : `Output ONE short title (max 8 words) for this assistant reply. No quotes. No punctuation at the end.\n\nAssistant: ${assistantText!.slice(0, 2000)}\n\nTitle:`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n\n try {\n const result = await completeWithResolvedCredentials(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 64,\n temperature: 0.35,\n signal: signal as AbortSignal,\n },\n );\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\n const cleaned = sanitizeSessionTitle(text);\n return cleaned.length > 0 ? cleaned : null;\n } catch (err) {\n log.warn({ err }, 'Session title LLM call failed');\n return null;\n }\n}\n\nexport type SessionTitleUpdatedHook = (sessionKey: string, name: string) => void | Promise<void>;\n\n/** Whether post-turn LLM refine may run (provisional or still unnamed; not user-locked). */\nexport function shouldRefineSessionTitleWithLlm(\n meta: { name?: string; customData?: Record<string, unknown> } | null | undefined,\n): boolean {\n if (!meta) return false;\n const source = getSessionTitleSource(meta);\n if (source === 'user') return false;\n if (meta.name?.trim()) return source === 'provisional';\n return true;\n}\n\nfunction canAutoWriteTitle(meta: { name?: string; customData?: Record<string, unknown> } | null): boolean {\n if (!meta) return false;\n const source = getSessionTitleSource(meta);\n if (source === 'user') return false;\n return !meta.name?.trim();\n}\n\n/**\n * Set provisional title from first user text when session is still unnamed.\n * Skips cron/heartbeat keys and user-locked titles.\n */\nexport async function maybeSetProvisionalSessionTitle(\n sessionStore: SessionStore,\n sessionKey: string,\n userText?: string,\n onUpdated?: SessionTitleUpdatedHook,\n): Promise<void> {\n if (!shouldAutoTitleSessionKey(sessionKey)) return;\n\n let meta = await sessionStore.getMetadata(sessionKey);\n if (!meta) return;\n if (!canAutoWriteTitle(meta)) return;\n\n let title: string | null = null;\n if (userText?.trim()) {\n title = provisionalTitleFromUserText(userText);\n }\n if (!title) {\n const messages = await sessionStore.load(sessionKey);\n if (!messages.length) return;\n title = fallbackTitleFromMessages(messages);\n }\n if (!title) return;\n\n try {\n await sessionStore.updateMetadata(sessionKey, {\n name: title,\n customData: { ...(meta.customData ?? {}), titleSource: 'provisional' },\n });\n await onUpdated?.(sessionKey, title);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Session title: provisional updateMetadata failed');\n }\n}\n\n/**\n * LLM refine when title is empty or still provisional (not user-locked).\n */\nexport async function maybeRefineSessionTitleWithLlm(\n sessionStore: SessionStore,\n sessionKey: string,\n modelRef: string | undefined,\n onUpdated?: SessionTitleUpdatedHook,\n): Promise<void> {\n if (!shouldAutoTitleSessionKey(sessionKey)) return;\n\n let messages = await sessionStore.load(sessionKey);\n if (!messages.length) return;\n\n let meta = await sessionStore.getMetadata(sessionKey);\n if (!meta) {\n await sessionStore.saveMessages(sessionKey, messages);\n meta = await sessionStore.getMetadata(sessionKey);\n }\n if (!meta) {\n log.warn({ sessionKey }, 'Session title: metadata missing after save');\n return;\n }\n if (!shouldRefineSessionTitleWithLlm(meta)) return;\n\n const source = getSessionTitleSource(meta);\n let title: string | null = null;\n const ref = modelRef?.trim();\n if (ref) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 25_000);\n try {\n title = await generateSessionTitleFromMessages(ref, messages, controller.signal);\n } finally {\n clearTimeout(timeout);\n }\n }\n if (!title) {\n title = fallbackTitleFromMessages(messages);\n }\n if (!title) return;\n\n const existing = meta.name?.trim();\n if (existing === title) {\n if (source !== 'llm') {\n try {\n await sessionStore.updateMetadata(sessionKey, {\n customData: { ...(meta.customData ?? {}), titleSource: 'llm' },\n });\n } catch {\n /* ignore */\n }\n }\n return;\n }\n\n try {\n await sessionStore.updateMetadata(sessionKey, {\n name: title,\n customData: { ...(meta.customData ?? {}), titleSource: 'llm' },\n });\n await onUpdated?.(sessionKey, title);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Session title: refine updateMetadata failed');\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { getDefaultModelSync, init_providers, resolveModel } from "../../providers/index.js";
|
|
4
|
+
import { completeWithResolvedCredentials } from "../../providers/model-call.js";
|
|
4
5
|
import { truncateAtSentenceBoundary } from "./sentence-boundary.js";
|
|
5
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
6
6
|
//#region src/voice/tts/summarize.ts
|
|
7
7
|
init_providers();
|
|
8
8
|
init_logger();
|
|
@@ -67,7 +67,7 @@ async function summarizeForTts(options) {
|
|
|
67
67
|
};
|
|
68
68
|
const timeoutSignal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(SUMMARIZE_TIMEOUT_MS) : void 0;
|
|
69
69
|
const mergedSignal = signal && timeoutSignal && typeof AbortSignal.any === "function" ? AbortSignal.any([signal, timeoutSignal]) : signal ?? timeoutSignal;
|
|
70
|
-
const result = await
|
|
70
|
+
const result = await completeWithResolvedCredentials(model, { messages: [userMsg] }, {
|
|
71
71
|
maxTokens: Math.min(Math.ceil(targetLength / 2), 4096),
|
|
72
72
|
temperature: .3,
|
|
73
73
|
signal: mergedSignal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarize.js","names":[],"sources":["../../../../src/voice/tts/summarize.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"summarize.js","names":[],"sources":["../../../../src/voice/tts/summarize.ts"],"sourcesContent":["import { type UserMessage } from '@earendil-works/pi-ai/compat';\nimport type { Api, Model } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport { completeWithResolvedCredentials } from '../../providers/model-call.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { truncateAtSentenceBoundary } from './sentence-boundary.js';\n\nconst log = createLogger('TTS:Summarize');\n\nconst SUMMARIZE_TIMEOUT_MS = 45_000;\n\nconst SUMMARIZE_SYSTEM_PROMPT = `You are a text summarizer for a text-to-speech system.\nYour task is to condense the given text while preserving the key information and meaning.\n\nRules:\n1. Output ONLY the summarized text, no explanations or meta-commentary\n2. Keep the summary in the same language as the input\n3. Preserve key facts, numbers, names, and conclusions\n4. Use natural, spoken language suitable for audio playback\n5. Remove code blocks, URLs, markdown formatting, and technical syntax where possible\n6. Keep the summary under the specified character limit\n7. End with a complete sentence, never mid-sentence`;\n\nexport interface SummarizeForTtsOptions {\n text: string;\n targetLength: number;\n config?: Config;\n modelRef?: string;\n signal?: AbortSignal;\n}\n\nexport interface SummarizeResult {\n summary: string;\n originalLength: number;\n summaryLength: number;\n wasSummarized: boolean;\n}\n\nfunction resolveSummarizeModel(config: Config | undefined, modelRef?: string): Model<Api> {\n const envRef = process.env.XOPC_TTS_SUMMARIZE_MODEL?.trim();\n const ref = modelRef?.trim() || envRef;\n if (ref) {\n try {\n return resolveModel(ref);\n } catch {\n /* fall through */\n }\n }\n for (const candidate of ['openai/gpt-5.6-luna', 'google/gemini-3.5-flash']) {\n try {\n return resolveModel(candidate);\n } catch {\n /* next */\n }\n }\n return resolveModel(getDefaultModelSync(config));\n}\n\nfunction fallbackTruncate(text: string, maxLength: number): SummarizeResult {\n const truncated = truncateAtSentenceBoundary(text, maxLength);\n return {\n summary: truncated,\n originalLength: text.length,\n summaryLength: truncated.length,\n wasSummarized: false,\n };\n}\n\n/**\n * Condense text for TTS using a small LLM pass; falls back to sentence-boundary truncation.\n */\nexport async function summarizeForTts(options: SummarizeForTtsOptions): Promise<SummarizeResult> {\n const { text, targetLength, config, modelRef, signal } = options;\n\n if (text.length <= targetLength) {\n return {\n summary: text,\n originalLength: text.length,\n summaryLength: text.length,\n wasSummarized: false,\n };\n }\n\n const startTime = Date.now();\n\n try {\n const model = resolveSummarizeModel(config, modelRef);\n const userPrompt = [\n `Summarize the following text to fit within ${targetLength} characters.`,\n `The summary will be read aloud by a text-to-speech system, so use natural spoken language.`,\n '',\n '---',\n '',\n text,\n ].join('\\n');\n\n const userMsg: UserMessage = {\n role: 'user',\n content: `${SUMMARIZE_SYSTEM_PROMPT}\\n\\n---\\n\\n${userPrompt}`,\n timestamp: Date.now(),\n };\n\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(SUMMARIZE_TIMEOUT_MS)\n : undefined;\n const mergedSignal =\n signal && timeoutSignal && typeof AbortSignal.any === 'function'\n ? AbortSignal.any([signal, timeoutSignal])\n : signal ?? timeoutSignal;\n\n const result = await completeWithResolvedCredentials(\n model,\n { messages: [userMsg] },\n {\n maxTokens: Math.min(Math.ceil(targetLength / 2), 4096),\n temperature: 0.3,\n signal: mergedSignal as AbortSignal,\n },\n );\n\n let out = '';\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 out += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const summary = out.trim();\n if (!summary) {\n log.warn('Empty summary returned, falling back to truncation');\n return fallbackTruncate(text, targetLength);\n }\n\n const finalSummary =\n summary.length > targetLength ? truncateAtSentenceBoundary(summary, targetLength) : summary;\n\n log.info(\n {\n originalLength: text.length,\n summaryLength: finalSummary.length,\n durationMs: Date.now() - startTime,\n },\n 'Text summarized for TTS',\n );\n\n return {\n summary: finalSummary,\n originalLength: text.length,\n summaryLength: finalSummary.length,\n wasSummarized: true,\n };\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.warn({ errorMessage: em }, 'Summarization failed, falling back to truncation');\n return fallbackTruncate(text, targetLength);\n }\n}\n"],"mappings":";;;;;;gBAI6E;aAExB;AAGrD,MAAM,MAAM,aAAa,gBAAgB;AAEzC,MAAM,uBAAuB;AAE7B,MAAM,0BAA0B;;;;;;;;;;;AA2BhC,SAAS,sBAAsB,QAA4B,UAA+B;CACxF,MAAM,SAAS,QAAQ,IAAI,0BAA0B,MAAM;CAC3D,MAAM,MAAM,UAAU,MAAM,IAAI;AAChC,KAAI,IACF,KAAI;AACF,SAAO,aAAa,IAAI;SAClB;AAIV,MAAK,MAAM,aAAa,CAAC,uBAAuB,0BAA0B,CACxE,KAAI;AACF,SAAO,aAAa,UAAU;SACxB;AAIV,QAAO,aAAa,oBAAoB,OAAO,CAAC;;AAGlD,SAAS,iBAAiB,MAAc,WAAoC;CAC1E,MAAM,YAAY,2BAA2B,MAAM,UAAU;AAC7D,QAAO;EACL,SAAS;EACT,gBAAgB,KAAK;EACrB,eAAe,UAAU;EACzB,eAAe;EAChB;;;;;AAMH,eAAsB,gBAAgB,SAA2D;CAC/F,MAAM,EAAE,MAAM,cAAc,QAAQ,UAAU,WAAW;AAEzD,KAAI,KAAK,UAAU,aACjB,QAAO;EACL,SAAS;EACT,gBAAgB,KAAK;EACrB,eAAe,KAAK;EACpB,eAAe;EAChB;CAGH,MAAM,YAAY,KAAK,KAAK;AAE5B,KAAI;EACF,MAAM,QAAQ,sBAAsB,QAAQ,SAAS;EAUrD,MAAM,UAAuB;GAC3B,MAAM;GACN,SAAS,GAAG,wBAAwB,aAXnB;IACjB,8CAA8C,aAAa;IAC3D;IACA;IACA;IACA;IACA;IACD,CAAC,KAAK,KAIsD;GAC3D,WAAW,KAAK,KAAK;GACtB;EAED,MAAM,gBACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aACjE,YAAY,QAAQ,qBAAqB,GACzC,KAAA;EACN,MAAM,eACJ,UAAU,iBAAiB,OAAO,YAAY,QAAQ,aAClD,YAAY,IAAI,CAAC,QAAQ,cAAc,CAAC,GACxC,UAAU;EAEhB,MAAM,SAAS,MAAM,gCACnB,OACA,EAAE,UAAU,CAAC,QAAQ,EAAE,EACvB;GACE,WAAW,KAAK,IAAI,KAAK,KAAK,eAAe,EAAE,EAAE,KAAK;GACtD,aAAa;GACb,QAAQ;GACT,CACF;EAED,IAAI,MAAM;AACV,MAAI,MAAM,QAAQ,OAAO,QAAQ;QAC1B,MAAM,KAAK,OAAO,QACrB,KAAI,KAAK,OAAO,MAAM,YAAa,EAAwB,SAAS,OAClE,QAAO,OAAQ,EAAwB,QAAQ,GAAG;;EAKxD,MAAM,UAAU,IAAI,MAAM;AAC1B,MAAI,CAAC,SAAS;AACZ,OAAI,KAAK,qDAAqD;AAC9D,UAAO,iBAAiB,MAAM,aAAa;;EAG7C,MAAM,eACJ,QAAQ,SAAS,eAAe,2BAA2B,SAAS,aAAa,GAAG;AAEtF,MAAI,KACF;GACE,gBAAgB,KAAK;GACrB,eAAe,aAAa;GAC5B,YAAY,KAAK,KAAK,GAAG;GAC1B,EACD,0BACD;AAED,SAAO;GACL,SAAS;GACT,gBAAgB,KAAK;GACrB,eAAe,aAAa;GAC5B,eAAe;GAChB;UACM,OAAO;EACd,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,MAAI,KAAK,EAAE,cAAc,IAAI,EAAE,mDAAmD;AAClF,SAAO,iBAAiB,MAAM,aAAa"}
|
|
@@ -2,10 +2,10 @@ import { createLogger } from "../../utils/logger/index.js";
|
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
|
|
4
4
|
import { getDefaultModelSync, init_providers, resolveModel } from "../../providers/index.js";
|
|
5
|
+
import { completeWithResolvedCredentials } from "../../providers/model-call.js";
|
|
5
6
|
import { resolveModelRef } from "../../config/agent-typed-models.js";
|
|
6
7
|
import { buildWorkflowDraftPrompt, buildWorkflowDraftRepairPrompt } from "./workflow-draft-prompt.js";
|
|
7
8
|
import { buildWorkflowDraftResponse, parseGeneratedWorkflowDraft } from "./workflow-draft-validator.js";
|
|
8
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
9
9
|
//#region src/workflows/draft/workflow-draft-service.ts
|
|
10
10
|
init_schema();
|
|
11
11
|
init_providers();
|
|
@@ -32,7 +32,7 @@ var WorkflowDraftService = class {
|
|
|
32
32
|
let lastIssues = [];
|
|
33
33
|
let lastText = "";
|
|
34
34
|
for (let attempt = 0; attempt <= maxRepairAttempts; attempt += 1) {
|
|
35
|
-
lastText = extractText(await
|
|
35
|
+
lastText = extractText(await completeWithResolvedCredentials(model, { messages: [{
|
|
36
36
|
role: "user",
|
|
37
37
|
content: messageContent,
|
|
38
38
|
timestamp: Date.now()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-draft-service.js","names":[],"sources":["../../../../src/workflows/draft/workflow-draft-service.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"workflow-draft-service.js","names":[],"sources":["../../../../src/workflows/draft/workflow-draft-service.ts"],"sourcesContent":["import { type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport { resolveModelRef } from '../../config/agent-typed-models.js';\nimport { getAgentDefaultModelRef } from '../../config/schema.js';\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport { completeWithResolvedCredentials } from '../../providers/model-call.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport {\n buildWorkflowDraftPrompt,\n buildWorkflowDraftRepairPrompt,\n type WorkflowDraftRepairIssue,\n} from './workflow-draft-prompt.js';\nimport type { CreateWorkflowDraftRequest, WorkflowDraftResponse } from './workflow-draft.types.js';\nimport { buildWorkflowDraftResponse, parseGeneratedWorkflowDraft } from './workflow-draft-validator.js';\n\nconst log = createLogger('WorkflowDraft');\nconst DEFAULT_MAX_REPAIR_ATTEMPTS = 2;\n\nexport interface WorkflowDraftServiceOptions {\n config: Config;\n maxRepairAttempts?: number;\n}\n\nexport class WorkflowDraftService {\n constructor(private readonly options: WorkflowDraftServiceOptions) {}\n\n async createDraft(request: CreateWorkflowDraftRequest, signal?: AbortSignal): Promise<WorkflowDraftResponse> {\n const prompt = request.prompt.trim();\n if (!prompt) throw new Error('prompt is required');\n const modelRef = resolveDraftModelRef(this.options.config, request.agentId);\n const model = resolveModel(modelRef);\n const maxRepairAttempts = this.options.maxRepairAttempts ?? DEFAULT_MAX_REPAIR_ATTEMPTS;\n let messageContent = buildWorkflowDraftPrompt({\n prompt,\n language: request.language,\n mode: request.mode ?? (request.existingScript ? 'improve' : 'create'),\n existingScript: request.existingScript,\n constraints: request.constraints,\n });\n let lastIssues: WorkflowDraftRepairIssue[] = [];\n let lastText = '';\n\n for (let attempt = 0; attempt <= maxRepairAttempts; attempt += 1) {\n const userMsg: UserMessage = {\n role: 'user',\n content: messageContent,\n timestamp: Date.now(),\n };\n const result = await completeWithResolvedCredentials(\n model,\n { messages: [userMsg] },\n { maxTokens: 4096, temperature: attempt === 0 ? 0.25 : 0.1, signal: signal as AbortSignal },\n );\n lastText = extractText(result);\n try {\n const draft = parseGeneratedWorkflowDraft(lastText);\n const response = buildWorkflowDraftResponse(draft, request.constraints);\n const blockingIssues = getBlockingDraftIssues(response);\n if (blockingIssues.length === 0) {\n return { ...response, repairAttempts: attempt };\n }\n lastIssues = blockingIssues;\n } catch (err) {\n lastIssues = [{\n source: 'parse',\n severity: 'error',\n code: 'invalid_model_output',\n message: err instanceof Error ? err.message : String(err),\n }];\n }\n\n if (attempt >= maxRepairAttempts) break;\n log.warn(\n { modelRef, attempt: attempt + 1, issueCount: lastIssues.length, preview: lastText.slice(0, 500) },\n 'Workflow draft validation failed; asking model to repair',\n );\n messageContent = buildWorkflowDraftRepairPrompt({\n prompt,\n language: request.language,\n constraints: request.constraints,\n previousOutput: lastText,\n issues: lastIssues,\n });\n }\n\n const message = `Unable to generate a valid workflow draft after ${maxRepairAttempts + 1} attempts: ${formatRepairIssues(lastIssues)}`;\n log.warn({ modelRef, issues: lastIssues, preview: lastText.slice(0, 500) }, message);\n throw new Error(message);\n }\n}\n\nexport function resolveDraftModelRef(config: Config, agentId: string): string {\n try {\n return resolveModelRef(config, agentId, 'large');\n } catch {\n return getAgentDefaultModelRef(config) ?? getDefaultModelSync(config);\n }\n}\n\nfunction extractText(result: unknown): string {\n const content = (result as { content?: unknown }).content;\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content.map((item) => {\n if (item && typeof item === 'object' && (item as { type?: string }).type === 'text') {\n return String((item as { text?: string }).text ?? '');\n }\n return '';\n }).join('');\n }\n return '';\n}\n\nfunction getBlockingDraftIssues(response: WorkflowDraftResponse): WorkflowDraftRepairIssue[] {\n const validationIssues: WorkflowDraftRepairIssue[] = [\n ...response.validation.errors.map((issue) => ({\n source: 'validation' as const,\n severity: 'error' as const,\n code: issue.code,\n message: issue.message,\n line: issue.line,\n column: issue.column,\n })),\n ...response.validation.warnings.map((issue) => ({\n source: 'validation' as const,\n severity: 'warning' as const,\n code: issue.code,\n message: issue.message,\n line: issue.line,\n column: issue.column,\n })),\n ];\n const lintIssues = response.lint\n .filter((issue) => issue.severity === 'error')\n .map((issue) => ({\n source: 'lint' as const,\n severity: issue.severity,\n code: issue.code,\n message: issue.message,\n }));\n return [...validationIssues, ...lintIssues];\n}\n\nfunction formatRepairIssues(issues: WorkflowDraftRepairIssue[]): string {\n if (issues.length === 0) return 'unknown validation failure';\n return issues.slice(0, 5).map((issue) => `${issue.source}:${issue.code}: ${issue.message}`).join('; ');\n}\n"],"mappings":";;;;;;;;;aAGiE;gBAEY;aAExB;AAUrD,MAAM,MAAM,aAAa,gBAAgB;AACzC,MAAM,8BAA8B;AAOpC,IAAa,uBAAb,MAAkC;CAChC,YAAY,SAAuD;AAAtC,OAAA,UAAA;;CAE7B,MAAM,YAAY,SAAqC,QAAsD;EAC3G,MAAM,SAAS,QAAQ,OAAO,MAAM;AACpC,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,qBAAqB;EAClD,MAAM,WAAW,qBAAqB,KAAK,QAAQ,QAAQ,QAAQ,QAAQ;EAC3E,MAAM,QAAQ,aAAa,SAAS;EACpC,MAAM,oBAAoB,KAAK,QAAQ,qBAAqB;EAC5D,IAAI,iBAAiB,yBAAyB;GAC5C;GACA,UAAU,QAAQ;GAClB,MAAM,QAAQ,SAAS,QAAQ,iBAAiB,YAAY;GAC5D,gBAAgB,QAAQ;GACxB,aAAa,QAAQ;GACtB,CAAC;EACF,IAAI,aAAyC,EAAE;EAC/C,IAAI,WAAW;AAEf,OAAK,IAAI,UAAU,GAAG,WAAW,mBAAmB,WAAW,GAAG;AAWhE,cAAW,YAAY,MALF,gCACnB,OACA,EAAE,UAAU,CAAC;IANb,MAAM;IACN,SAAS;IACT,WAAW,KAAK,KAAK;IAID,CAAC,EAAE,EACvB;IAAE,WAAW;IAAM,aAAa,YAAY,IAAI,MAAO;IAAa;IAAuB,CAC5F,CAC6B;AAC9B,OAAI;IAEF,MAAM,WAAW,2BADH,4BAA4B,SACO,EAAE,QAAQ,YAAY;IACvE,MAAM,iBAAiB,uBAAuB,SAAS;AACvD,QAAI,eAAe,WAAW,EAC5B,QAAO;KAAE,GAAG;KAAU,gBAAgB;KAAS;AAEjD,iBAAa;YACN,KAAK;AACZ,iBAAa,CAAC;KACZ,QAAQ;KACR,UAAU;KACV,MAAM;KACN,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KAC1D,CAAC;;AAGJ,OAAI,WAAW,kBAAmB;AAClC,OAAI,KACF;IAAE;IAAU,SAAS,UAAU;IAAG,YAAY,WAAW;IAAQ,SAAS,SAAS,MAAM,GAAG,IAAI;IAAE,EAClG,2DACD;AACD,oBAAiB,+BAA+B;IAC9C;IACA,UAAU,QAAQ;IAClB,aAAa,QAAQ;IACrB,gBAAgB;IAChB,QAAQ;IACT,CAAC;;EAGJ,MAAM,UAAU,mDAAmD,oBAAoB,EAAE,aAAa,mBAAmB,WAAW;AACpI,MAAI,KAAK;GAAE;GAAU,QAAQ;GAAY,SAAS,SAAS,MAAM,GAAG,IAAI;GAAE,EAAE,QAAQ;AACpF,QAAM,IAAI,MAAM,QAAQ;;;AAI5B,SAAgB,qBAAqB,QAAgB,SAAyB;AAC5E,KAAI;AACF,SAAO,gBAAgB,QAAQ,SAAS,QAAQ;SAC1C;AACN,SAAO,wBAAwB,OAAO,IAAI,oBAAoB,OAAO;;;AAIzE,SAAS,YAAY,QAAyB;CAC5C,MAAM,UAAW,OAAiC;AAClD,KAAI,OAAO,YAAY,SAAU,QAAO;AACxC,KAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QAAQ,KAAK,SAAS;AAC3B,MAAI,QAAQ,OAAO,SAAS,YAAa,KAA2B,SAAS,OAC3E,QAAO,OAAQ,KAA2B,QAAQ,GAAG;AAEvD,SAAO;GACP,CAAC,KAAK,GAAG;AAEb,QAAO;;AAGT,SAAS,uBAAuB,UAA6D;CAC3F,MAAM,mBAA+C,CACnD,GAAG,SAAS,WAAW,OAAO,KAAK,WAAW;EAC5C,QAAQ;EACR,UAAU;EACV,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,MAAM,MAAM;EACZ,QAAQ,MAAM;EACf,EAAE,EACH,GAAG,SAAS,WAAW,SAAS,KAAK,WAAW;EAC9C,QAAQ;EACR,UAAU;EACV,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,MAAM,MAAM;EACZ,QAAQ,MAAM;EACf,EAAE,CACJ;CACD,MAAM,aAAa,SAAS,KACzB,QAAQ,UAAU,MAAM,aAAa,QAAQ,CAC7C,KAAK,WAAW;EACf,QAAQ;EACR,UAAU,MAAM;EAChB,MAAM,MAAM;EACZ,SAAS,MAAM;EAChB,EAAE;AACL,QAAO,CAAC,GAAG,kBAAkB,GAAG,WAAW;;AAG7C,SAAS,mBAAmB,QAA4C;AACtE,KAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAO,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,UAAU,GAAG,MAAM,OAAO,GAAG,MAAM,KAAK,IAAI,MAAM,UAAU,CAAC,KAAK,KAAK"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xopcai/xopc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.142",
|
|
4
4
|
"description": "Local-first AI system that remembers context, coordinates AI, and sustains long-term progress.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"node": ">=22.19.0"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@earendil-works/pi-agent-core": "
|
|
69
|
-
"@earendil-works/pi-ai": "
|
|
70
|
-
"@earendil-works/pi-coding-agent": "
|
|
71
|
-
"@earendil-works/pi-tui": "
|
|
68
|
+
"@earendil-works/pi-agent-core": "0.80.10",
|
|
69
|
+
"@earendil-works/pi-ai": "0.80.10",
|
|
70
|
+
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
71
|
+
"@earendil-works/pi-tui": "0.80.10",
|
|
72
72
|
"@grammyjs/runner": "^2.0.3",
|
|
73
73
|
"@hono/node-server": "^2.0.4",
|
|
74
74
|
"@inquirer/prompts": "^8.5.0",
|