chainlesschain 0.162.119 → 0.162.121
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DHRc0Ie_.js → AIOps-Bv7Ucinc.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CgUXfXke.js → ActionButton-B0guX-Pk.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DIKHjclG.js → Analytics-pvYAYaTD.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-vX1r-6CY.js → AppLayout-_GgOKQ2t.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-DzJx-WeF.js → Audit-CFS769nz.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DZ4na4uj.js → Backup-DzC81Yn0.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-qUEPpz0X.js → BaseInput-BsJKfb1P.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DTZl8nH5.js → Chat-WsUPq3Wj.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DD2tLmRr.js → ChatBubbleRenderer-DvtzQM9S.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BQFF8-dn.js → Checkbox-C3yuwxvq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DIQaC76u.js → Codegen-QIFxk_hy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-uy6MSNXM.js → Col-DH6v5BCo.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DJSclfR8.js → Community-DPm78h9b.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DS2_Cpq1.js → Compact-t_O2D6_A.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-T6LqAmah.js → Compliance-CLKqB8ca.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BrqyP337.js → Cowork-BAfAK8X3.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DYFCc-f1.js → Cron-BeG0RFLN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CnWNsqde.js → Crosschain-CaWGF4pH.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cno6plcv.js → DID-DnknPYMQ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CsgzRLsJ.js → Dashboard-CCChnAaV.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BUKI1xuG.js → Dropdown-DrnkbWTA.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DuEOjlci.js → EmailListRenderer-yvHFL0rf.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BfnUdEaU.js → FamilyGuardDashboard-CkGkMoL4.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CfeUTibi.js → Federation-1njD_iZv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-D14wwVsq.js → FormItemContext-CHniFKJL.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CGx7VfKf.js → GenericCardRenderer-DUWt7emA.js} +1 -1
- package/src/assets/web-panel/assets/{Git-chaiL5nL.js → Git-BCkiDXHW.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Ca5rbA-N.js → Governance-CvYe_1r7.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DA0DK7dc.js → Inference-DLMzzPHV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BwaAK34_.js → KnowledgeGraph-CcaaJRB6.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-FA4XPXuO.js → Logs-CgrnKQgi.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-eOsO5rY3.js → Marketplace-ANMynfeO.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D88TlCnE.js → McpTools-_C4-YpAK.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DsIN2Sv6.js → Memory-XGQtG2x2.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge--q60qDOY.js → MobileBridge-D4tGRMBu.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-84NgjQck.js → MobileProjects-CSMoOsbj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BYfKH7WL.js → Mtc-BkOT98e8.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-D35u_eyo.js → MtcAudit-R3Bc7QS3.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BksPgmCa.js → Multisig-DtPhN_Vp.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D5cbMuDm.js → NLProgramming-BedLm2U-.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cu_pbWTB.js → Notes-DQAs8WXS.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C81tVX8S.js → NotificationSettings-CU5inrwZ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Ccx2Lxcc.js → OrderTableRenderer-CuOAigzP.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BIJzR15j.js → Organization-BBYSlNS4.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BKCASkRj.js → Overflow-D4LFJ0F8.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Q4mb4TFL.js → P2P-Cb9wWcwI.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CPWIjtON.js → PdhVaultBrowser-CbrWEB55.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DCo0YoEd.js → Permissions-B6IgqOCw.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CT-Efwnw.js → PersonalDataHub-Bjr4sBQJ.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-B5iEJ2T_.js → Pipeline-Dh5Jusv2.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BjOXW26f.js → Privacy-Dyrhjpr4.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DE6GZFCM.js → ProjectInit-D_Ooa-_1.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-cHsBvsYo.js → ProjectSettings-BcxJj9D5.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Bi4Wv8m6.js → Projects-D2iHs0Go.js} +1 -1
- package/src/assets/web-panel/assets/Providers-D0-FNfIu.js +1 -0
- package/src/assets/web-panel/assets/{QuickAsk-C6BQQUUw.js → QuickAsk-BwmHGadq.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Dz18iBwP.js → Recommend-Cn6UbQuv.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-LagVnM_Q.js → Reputation-jSYCTpEv.js} +1 -1
- package/src/assets/web-panel/assets/{Row--IaheXkB.js → Row-P2QvD32s.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CDNYaijn.js → RssFeed-Cbhe_ZuG.js} +2 -2
- package/src/assets/web-panel/assets/{Search-D6DPr_Km.js → Search-7aQqsu6b.js} +1 -1
- package/src/assets/web-panel/assets/{Security-a4-jIBqX.js → Security-BHcX3oNC.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Bmj4jr2Q.js → Services-MyS-8Lbb.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BhKpCNqE.js → Skeleton-QZIW_jXl.js} +1 -1
- package/src/assets/web-panel/assets/Skills-BiGNAz9O.js +1 -0
- package/src/assets/web-panel/assets/{Sla-D68DDW4T.js → Sla-C62HvLJq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CxCF4E_o.js → SpeechSettings-Bo1H5Wn6.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-pcetCirO.js → SyncSettings-Bsz0-gn1.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-bwGsNuzp.js +1 -0
- package/src/assets/web-panel/assets/{Templates-6QfV55r3.js → Templates-BougCISz.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BOI2_4NB.js → Tenant-CuZVHTEg.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DSIv5Z7S.js → Terminal-Cuf5GwW1.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C4tC343-.js → TimelineRenderer-DIvfcwcy.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CnNoHnJv.js → Tokens-BiS_SqlJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BaRSXO70.js → Trigger-Di7RbOTg.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CWnKxwgN.js → Trust-DJfddmdL.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-De8aN7WZ.js → UkeySign-D6K4VEkr.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Cu7XhMNZ.js → VideoEditing-DncGaI3n.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-xfhJUu0f.js → Wallet-C6dwNmEv.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Bt6sYYDk.js → WebAuthn-DECzmHdt.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-C4LZ0x-0.js → WorkflowEditor-7Oui1r4s.js} +1 -1
- package/src/assets/web-panel/assets/{chat-33Oq8wz3.js → chat-D7cr3pGX.js} +1 -1
- package/src/assets/web-panel/assets/{colors-COL29MyD.js → colors-DVBzBfcl.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D8hinshK.js → compact-item-BbB5VBbN.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DVD4d6-0.js → createContext-DZIhmdMW.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-roRmJB_V.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CvLpycQb.js → hasIn-o6cAbkdF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaXHhqHE.js → index-B-ZE4Sb_.js} +1 -1
- package/src/assets/web-panel/assets/{index-3GhR2NdM.js → index-B46pBbZO.js} +1 -1
- package/src/assets/web-panel/assets/{index-1HmyagUk.js → index-B7wUz-Ms.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnxPhaUe.js → index-BCmv_7S_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcSZkELE.js → index-BE27hqn2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmieBFpM.js → index-BHUQ725h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DD9PcRt3.js → index-BjFOIfCN.js} +1 -1
- package/src/assets/web-panel/assets/{index-czxh_xOe.js → index-BpBK2OXI.js} +1 -1
- package/src/assets/web-panel/assets/index-C24iCbrD.js +1 -0
- package/src/assets/web-panel/assets/{index-CSzLUjjZ.js → index-C3zE2Zb3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm47rSqn.js → index-C7E1YSkg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ctz762df.js → index-CBU-k7oT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXFtAKWy.js → index-CMBJfBO0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcqrzCOI.js → index-CRwhnqeq.js} +1 -1
- package/src/assets/web-panel/assets/{index-65FhG6v3.js → index-Cqjc-kry.js} +1 -1
- package/src/assets/web-panel/assets/{index-D72pZlFa.js → index-CsMviqrA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCJdqeDG.js → index-Cv57ZM9B.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUkg9C1d.js → index-Cw4L9i5q.js} +1 -1
- package/src/assets/web-panel/assets/{index-JR5iJ3Nm.js → index-CzqSSSPT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhHsdI-s.js → index-D8x9OXYZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bc1y36ct.js → index-D9ImwlR4.js} +3 -3
- package/src/assets/web-panel/assets/{index-Dk6Mp5o3.js → index-D9kIcXZC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzJsjPb9.js → index-DAH13RZu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dz_IGu03.js → index-DClnoV1K.js} +1 -1
- package/src/assets/web-panel/assets/{index-DigxhXCh.js → index-DDlaS-eI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAZl_jZJ.js → index-DKepNJYO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNTOYZQJ.js → index-DTk9u72E.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_KT8dhv.js → index-DZ3tksOn.js} +1 -1
- package/src/assets/web-panel/assets/index-DjgW9EJK.js +1 -0
- package/src/assets/web-panel/assets/{index-DpAeo8Y1.js → index-KRt3wlmg.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3Ja9sDO.js → index-M33z1syq.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4E97124.js → index-PrARM-sN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDzdST6b.js → index-Uu0xnJjT.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2nqCkxx.js → index-WVuFehZH.js} +1 -1
- package/src/assets/web-panel/assets/{index-cgs0nxzF.js → index-bVBYhd7K.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdlzRBsd.js → index-l0044mtE.js} +1 -1
- package/src/assets/web-panel/assets/{index-D50I92to.js → index-wTvVuzRE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGDKCtU2.js → index-yuTbNtNs.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4L1oFlv.js → index-zKIib2ju.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DqSLMT1R.js → initDefaultProps-BserruiV.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DkdJz9l-.js → motion-C_f04bi5.js} +1 -1
- package/src/assets/web-panel/assets/{move-oonN7lOc.js → move-BexrY4Hu.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BeFyiGwR.js → omit-CIZJousS.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-OePKGTKF.js → pickAttrs-BkLOyxvO.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CjQXhAph.js → placementArrow-C3ddcrAy.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Dmn9lUes.js → responsiveObserve-D7hnwzkh.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Dfi_sykC.js → slide-CF7E-K6h.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Dl9mPVld.js → statusUtils-B6OJYlGm.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D3ay9a4W.js → styleChecker-bFHBq4VZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D07_5fY3.js → useFlexGapSupport-C4oscNMu.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D3axlil7.js → useFs-C4qeTm22.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CR53u77i.js → usePersonalDataHub-BREWau9k.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNOIJ4an.js → vnode-D_FePgCk.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-MIAkyImQ.js → zoom-CrTvlO1G.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/update.js +20 -0
- package/src/lib/runnable-provider.js +64 -0
- package/src/repl/agent-repl.js +35 -0
- package/src/runtime/__tests__/message-roles.test.js +217 -0
- package/src/runtime/agent-core.js +5 -3
- package/src/runtime/headless-runner.js +8 -2
- package/src/runtime/headless-stream.js +40 -0
- package/src/runtime/message-roles.js +121 -0
- package/src/assets/web-panel/assets/Providers-C0yVlpp5.js +0 -1
- package/src/assets/web-panel/assets/Skills-BqmheUDl.js +0 -1
- package/src/assets/web-panel/assets/Tasks-D3er3tkS.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CKV56AdO.js +0 -1
- package/src/assets/web-panel/assets/index-DBC8yuEG.js +0 -1
- package/src/assets/web-panel/assets/index-z9Rtlw7F.js +0 -1
package/src/repl/agent-repl.js
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
import { HookEvents } from "../lib/hook-manager.js";
|
|
70
70
|
import { IterationBudget } from "../lib/iteration-budget.js";
|
|
71
71
|
import { resolveAgentMcp } from "../runtime/mcp-config.js";
|
|
72
|
+
import { collapseConsecutiveMessagesInPlace } from "../runtime/message-roles.js";
|
|
72
73
|
import {
|
|
73
74
|
AGENT_TOOLS,
|
|
74
75
|
buildSystemPrompt,
|
|
@@ -185,6 +186,18 @@ async function executeTool(name, args) {
|
|
|
185
186
|
* Agentic loop — wraps agent-core's async generator with REPL display output.
|
|
186
187
|
*/
|
|
187
188
|
async function agentLoop(messages, options) {
|
|
189
|
+
// Resume-degenerate role merge (Claude Code 2.1.187 parity), gated by the
|
|
190
|
+
// one-shot `mergeRoles` flag so it fires only on the first model call after
|
|
191
|
+
// resuming a session whose prior run produced no assistant response. Collapse
|
|
192
|
+
// IN PLACE: `coreAgentLoop` mutates this same array (appending assistant/tool
|
|
193
|
+
// turns), and the REPL reuses it across turns, so folding a copy would leave
|
|
194
|
+
// the degenerate `[user, user]` pair in the persistent history and re-break
|
|
195
|
+
// the next turn. Safe in place because the gated turn is still tool-free (a
|
|
196
|
+
// resumed transcript holds only user/assistant/system) and the helper never
|
|
197
|
+
// folds `tool` turns regardless.
|
|
198
|
+
if (options.mergeRoles) {
|
|
199
|
+
collapseConsecutiveMessagesInPlace(messages);
|
|
200
|
+
}
|
|
188
201
|
const usageEvents = [];
|
|
189
202
|
// Visible cross-vendor fallback notice: a silent switch from the configured
|
|
190
203
|
// provider onto another vendor (or a baseUrl relabel) is surfaced as a yellow
|
|
@@ -603,6 +616,14 @@ export async function startAgentRepl(options = {}) {
|
|
|
603
616
|
);
|
|
604
617
|
let _activeOutputStyle = null; // { name, body }
|
|
605
618
|
const messages = [{ role: "system", content: _replBaseSystem }];
|
|
619
|
+
// Resume-degenerate role sanitation (Claude Code 2.1.187 parity): a one-shot
|
|
620
|
+
// flag armed when a resumed transcript ends with a bare `user` turn (the prior
|
|
621
|
+
// run produced no assistant response). Consumed by the first model call so the
|
|
622
|
+
// first live prompt — which would otherwise stack a second `user` and trip
|
|
623
|
+
// "roles must alternate" on Anthropic/Bedrock — is folded exactly once. Gated
|
|
624
|
+
// (not every turn) so the live loop's intentional consecutive-`user` states
|
|
625
|
+
// are never folded.
|
|
626
|
+
let _sanitizeRolesNextTurn = false;
|
|
606
627
|
// Checkpoint marks ({ atMessageCount, id, tool }) recorded as the agent loop
|
|
607
628
|
// emits `checkpoint` events, so `/rewind <n>` can also restore files to the
|
|
608
629
|
// snapshot taken before that turn (Claude-Code rewind = code + conversation).
|
|
@@ -824,6 +845,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
824
845
|
const rebuilt = rebuildMessages(sessionId);
|
|
825
846
|
if (rebuilt.length > 0) {
|
|
826
847
|
messages.push(...rebuilt.filter((m) => m.role !== "system"));
|
|
848
|
+
// Arm the resume role-merge if the prior run left a dangling user turn.
|
|
849
|
+
_sanitizeRolesNextTurn =
|
|
850
|
+
messages[messages.length - 1]?.role === "user";
|
|
827
851
|
logger.info(
|
|
828
852
|
`Resumed JSONL session ${sessionId} (${rebuilt.length} messages)`,
|
|
829
853
|
);
|
|
@@ -836,6 +860,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
836
860
|
? JSON.parse(existing.messages)
|
|
837
861
|
: existing.messages;
|
|
838
862
|
messages.push(...parsed.filter((m) => m.role !== "system"));
|
|
863
|
+
_sanitizeRolesNextTurn =
|
|
864
|
+
messages[messages.length - 1]?.role === "user";
|
|
839
865
|
logger.info(
|
|
840
866
|
`Resumed session ${sessionId} (${parsed.length} messages)`,
|
|
841
867
|
);
|
|
@@ -2047,6 +2073,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
2047
2073
|
const rebuilt = rebuildMessages(resumeId);
|
|
2048
2074
|
messages.length = 1; // keep system prompt
|
|
2049
2075
|
messages.push(...rebuilt.filter((m) => m.role !== "system"));
|
|
2076
|
+
_sanitizeRolesNextTurn =
|
|
2077
|
+
messages[messages.length - 1]?.role === "user";
|
|
2050
2078
|
sessionId = resumeId;
|
|
2051
2079
|
// The prior session's checkpoint marks (turn-index → checkpoint id)
|
|
2052
2080
|
// no longer map to this swapped-in history; keeping them would make
|
|
@@ -2065,6 +2093,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
2065
2093
|
: existing.messages;
|
|
2066
2094
|
messages.length = 1; // keep system prompt
|
|
2067
2095
|
messages.push(...parsed.filter((m) => m.role !== "system"));
|
|
2096
|
+
_sanitizeRolesNextTurn =
|
|
2097
|
+
messages[messages.length - 1]?.role === "user";
|
|
2068
2098
|
sessionId = existing.id;
|
|
2069
2099
|
// Drop the prior session's checkpoint marks (see JSONL branch).
|
|
2070
2100
|
_checkpointMarks.length = 0;
|
|
@@ -3342,12 +3372,17 @@ export async function startAgentRepl(options = {}) {
|
|
|
3342
3372
|
}
|
|
3343
3373
|
: {};
|
|
3344
3374
|
if (_streamLive) process.stdout.write("\n");
|
|
3375
|
+
// Consume the one-shot resume-degeneracy flag for THIS turn so the role
|
|
3376
|
+
// merge fires exactly once (2.1.187 parity; see _sanitizeRolesNextTurn).
|
|
3377
|
+
const _mergeRolesThisTurn = _sanitizeRolesNextTurn;
|
|
3378
|
+
_sanitizeRolesNextTurn = false;
|
|
3345
3379
|
const {
|
|
3346
3380
|
content: response,
|
|
3347
3381
|
usageEvents,
|
|
3348
3382
|
thinking: reasoning,
|
|
3349
3383
|
} = await agentLoop(messages, {
|
|
3350
3384
|
...liveOpts,
|
|
3385
|
+
mergeRoles: _mergeRolesThisTurn,
|
|
3351
3386
|
// Visible auto-retry feedback (Claude-Code 2.1.181): when the model's
|
|
3352
3387
|
// streaming call hits a transient connection drop and retries, tell the
|
|
3353
3388
|
// user instead of leaving them staring at a silent pause. To stderr so
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
mergeConsecutiveMessages,
|
|
4
|
+
collapseConsecutiveMessagesInPlace,
|
|
5
|
+
_internal,
|
|
6
|
+
} from "../message-roles.js";
|
|
7
|
+
|
|
8
|
+
describe("mergeConsecutiveMessages", () => {
|
|
9
|
+
it("is a no-op on a healthy alternating transcript", () => {
|
|
10
|
+
const msgs = [
|
|
11
|
+
{ role: "system", content: "sys" },
|
|
12
|
+
{ role: "user", content: "a" },
|
|
13
|
+
{ role: "assistant", content: "b" },
|
|
14
|
+
{ role: "user", content: "c" },
|
|
15
|
+
];
|
|
16
|
+
expect(mergeConsecutiveMessages(msgs)).toEqual(msgs);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("merges two consecutive user turns (the --resume no-response bug)", () => {
|
|
20
|
+
// history ended with a bare `user` (prior run produced no assistant
|
|
21
|
+
// response); a new user prompt was spliced after it.
|
|
22
|
+
const out = mergeConsecutiveMessages([
|
|
23
|
+
{ role: "system", content: "sys" },
|
|
24
|
+
{ role: "user", content: "original task" },
|
|
25
|
+
{ role: "user", content: "continue please" },
|
|
26
|
+
]);
|
|
27
|
+
expect(out).toEqual([
|
|
28
|
+
{ role: "system", content: "sys" },
|
|
29
|
+
{ role: "user", content: "original task\n\ncontinue please" },
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("merges three-plus consecutive same-role turns", () => {
|
|
34
|
+
const out = mergeConsecutiveMessages([
|
|
35
|
+
{ role: "user", content: "one" },
|
|
36
|
+
{ role: "user", content: "two" },
|
|
37
|
+
{ role: "user", content: "three" },
|
|
38
|
+
]);
|
|
39
|
+
expect(out).toEqual([{ role: "user", content: "one\n\ntwo\n\nthree" }]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("merges consecutive assistant turns defensively", () => {
|
|
43
|
+
const out = mergeConsecutiveMessages([
|
|
44
|
+
{ role: "assistant", content: "x" },
|
|
45
|
+
{ role: "assistant", content: "y" },
|
|
46
|
+
]);
|
|
47
|
+
expect(out).toEqual([{ role: "assistant", content: "x\n\ny" }]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("never merges consecutive tool messages (preserves each tool_call_id)", () => {
|
|
51
|
+
// A multi-tool turn produces back-to-back `tool` results — folding them
|
|
52
|
+
// would drop tool_call_ids and corrupt the tool-call pairing.
|
|
53
|
+
const msgs = [
|
|
54
|
+
{ role: "assistant", content: "", tool_calls: [{ id: "a" }, { id: "b" }] },
|
|
55
|
+
{ role: "tool", content: "r1", tool_call_id: "a" },
|
|
56
|
+
{ role: "tool", content: "r2", tool_call_id: "b" },
|
|
57
|
+
];
|
|
58
|
+
expect(mergeConsecutiveMessages(msgs)).toEqual(msgs);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("never merges system messages (leading base + context pair kept distinct)", () => {
|
|
62
|
+
const msgs = [
|
|
63
|
+
{ role: "system", content: "base" },
|
|
64
|
+
{ role: "system", content: "session-context" },
|
|
65
|
+
{ role: "user", content: "hi" },
|
|
66
|
+
];
|
|
67
|
+
expect(mergeConsecutiveMessages(msgs)).toEqual(msgs);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("preserves the user turn after a healthy assistant tail", () => {
|
|
71
|
+
const out = mergeConsecutiveMessages([
|
|
72
|
+
{ role: "assistant", content: "done" },
|
|
73
|
+
{ role: "user", content: "next" },
|
|
74
|
+
]);
|
|
75
|
+
expect(out).toEqual([
|
|
76
|
+
{ role: "assistant", content: "done" },
|
|
77
|
+
{ role: "user", content: "next" },
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("combines multimodal content arrays when merging user turns", () => {
|
|
82
|
+
const out = mergeConsecutiveMessages([
|
|
83
|
+
{
|
|
84
|
+
role: "user",
|
|
85
|
+
content: [
|
|
86
|
+
{ type: "text", text: "look" },
|
|
87
|
+
{ type: "image", source: "x" },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{ role: "user", content: "and describe" },
|
|
91
|
+
]);
|
|
92
|
+
expect(out).toHaveLength(1);
|
|
93
|
+
expect(out[0].role).toBe("user");
|
|
94
|
+
expect(out[0].content).toEqual([
|
|
95
|
+
{ type: "text", text: "look" },
|
|
96
|
+
{ type: "image", source: "x" },
|
|
97
|
+
{ type: "text", text: "and describe" },
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("treats an empty-string content as nothing when merging", () => {
|
|
102
|
+
const out = mergeConsecutiveMessages([
|
|
103
|
+
{ role: "user", content: "" },
|
|
104
|
+
{ role: "user", content: "real" },
|
|
105
|
+
]);
|
|
106
|
+
expect(out).toEqual([{ role: "user", content: "real" }]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("does not mutate the input array or its messages", () => {
|
|
110
|
+
const input = [
|
|
111
|
+
{ role: "user", content: "a" },
|
|
112
|
+
{ role: "user", content: "b" },
|
|
113
|
+
];
|
|
114
|
+
const snapshot = JSON.parse(JSON.stringify(input));
|
|
115
|
+
mergeConsecutiveMessages(input);
|
|
116
|
+
expect(input).toEqual(snapshot);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("passes through falsy / role-less entries verbatim without crashing", () => {
|
|
120
|
+
const out = mergeConsecutiveMessages([
|
|
121
|
+
null,
|
|
122
|
+
{ content: "no role" },
|
|
123
|
+
{ role: "user", content: "a" },
|
|
124
|
+
{ role: "user", content: "b" },
|
|
125
|
+
]);
|
|
126
|
+
expect(out).toEqual([
|
|
127
|
+
null,
|
|
128
|
+
{ content: "no role" },
|
|
129
|
+
{ role: "user", content: "a\n\nb" },
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns the argument unchanged when not an array", () => {
|
|
134
|
+
expect(mergeConsecutiveMessages(null)).toBe(null);
|
|
135
|
+
expect(mergeConsecutiveMessages(undefined)).toBe(undefined);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("_internal.joinContent", () => {
|
|
139
|
+
const { joinContent } = _internal;
|
|
140
|
+
it("joins two strings with a blank line", () => {
|
|
141
|
+
expect(joinContent("a", "b")).toBe("a\n\nb");
|
|
142
|
+
});
|
|
143
|
+
it("returns the non-empty side when one string is empty", () => {
|
|
144
|
+
expect(joinContent("", "b")).toBe("b");
|
|
145
|
+
expect(joinContent("a", "")).toBe("a");
|
|
146
|
+
});
|
|
147
|
+
it("promotes a string to a text block when joining with an array", () => {
|
|
148
|
+
expect(joinContent("hi", [{ type: "image", source: "x" }])).toEqual([
|
|
149
|
+
{ type: "text", text: "hi" },
|
|
150
|
+
{ type: "image", source: "x" },
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("collapseConsecutiveMessagesInPlace", () => {
|
|
157
|
+
it("collapses a degenerate user pair in place and returns true", () => {
|
|
158
|
+
const messages = [
|
|
159
|
+
{ role: "system", content: "sys" },
|
|
160
|
+
{ role: "user", content: "original task" },
|
|
161
|
+
{ role: "user", content: "continue please" },
|
|
162
|
+
];
|
|
163
|
+
const changed = collapseConsecutiveMessagesInPlace(messages);
|
|
164
|
+
expect(changed).toBe(true);
|
|
165
|
+
expect(messages).toEqual([
|
|
166
|
+
{ role: "system", content: "sys" },
|
|
167
|
+
{ role: "user", content: "original task\n\ncontinue please" },
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("is a no-op (returns false) on a healthy alternating transcript", () => {
|
|
172
|
+
const messages = [
|
|
173
|
+
{ role: "system", content: "sys" },
|
|
174
|
+
{ role: "user", content: "a" },
|
|
175
|
+
{ role: "assistant", content: "b" },
|
|
176
|
+
{ role: "user", content: "c" },
|
|
177
|
+
];
|
|
178
|
+
const before = JSON.parse(JSON.stringify(messages));
|
|
179
|
+
expect(collapseConsecutiveMessagesInPlace(messages)).toBe(false);
|
|
180
|
+
expect(messages).toEqual(before);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("keeps the SAME array reference (closures over it stay valid)", () => {
|
|
184
|
+
const messages = [
|
|
185
|
+
{ role: "user", content: "a" },
|
|
186
|
+
{ role: "user", content: "b" },
|
|
187
|
+
];
|
|
188
|
+
const ref = messages;
|
|
189
|
+
collapseConsecutiveMessagesInPlace(messages);
|
|
190
|
+
expect(messages).toBe(ref);
|
|
191
|
+
expect(messages).toHaveLength(1);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("returns false for a non-array argument", () => {
|
|
195
|
+
expect(collapseConsecutiveMessagesInPlace(null)).toBe(false);
|
|
196
|
+
expect(collapseConsecutiveMessagesInPlace(undefined)).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("does not corrupt tool results adjacent in a resumed transcript", () => {
|
|
200
|
+
// Defensive: a DB-resumed transcript could carry tool turns; they must
|
|
201
|
+
// survive an in-place collapse untouched while a user pair still folds.
|
|
202
|
+
const messages = [
|
|
203
|
+
{ role: "user", content: "u1" },
|
|
204
|
+
{ role: "user", content: "u2" },
|
|
205
|
+
{ role: "assistant", content: "", tool_calls: [{ id: "a" }, { id: "b" }] },
|
|
206
|
+
{ role: "tool", content: "r1", tool_call_id: "a" },
|
|
207
|
+
{ role: "tool", content: "r2", tool_call_id: "b" },
|
|
208
|
+
];
|
|
209
|
+
expect(collapseConsecutiveMessagesInPlace(messages)).toBe(true);
|
|
210
|
+
expect(messages).toEqual([
|
|
211
|
+
{ role: "user", content: "u1\n\nu2" },
|
|
212
|
+
{ role: "assistant", content: "", tool_calls: [{ id: "a" }, { id: "b" }] },
|
|
213
|
+
{ role: "tool", content: "r1", tool_call_id: "a" },
|
|
214
|
+
{ role: "tool", content: "r2", tool_call_id: "b" },
|
|
215
|
+
]);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -4293,7 +4293,7 @@ export async function* agentLoop(messages, options) {
|
|
|
4293
4293
|
const { makeRunnableProviderFallback } =
|
|
4294
4294
|
await import("../lib/runnable-provider.js");
|
|
4295
4295
|
llmCall = makeRunnableProviderFallback(llmCall, {
|
|
4296
|
-
onFallback: ({ from, to, reason }) => {
|
|
4296
|
+
onFallback: ({ from, to, reason, fromModel, toModel }) => {
|
|
4297
4297
|
// Switching VENDORS (or relabelling via baseUrl) must NEVER be silent —
|
|
4298
4298
|
// a user who configured volcengine deserves to know it ran on another
|
|
4299
4299
|
// provider/model. Build a human message and hand it to the driver's
|
|
@@ -4302,8 +4302,10 @@ export async function* agentLoop(messages, options) {
|
|
|
4302
4302
|
const message =
|
|
4303
4303
|
reason === "env-key"
|
|
4304
4304
|
? `"${from}" 鉴权失败,已临时切换到不同厂商 "${to}"(请检查 ${from} 的 API key:cc config set llm.apiKey …)。`
|
|
4305
|
-
:
|
|
4306
|
-
|
|
4305
|
+
: reason === "model-mismatch"
|
|
4306
|
+
? `模型 "${fromModel}" 不属于 ${from},已改用其默认模型 "${toModel}"(用 cc config set llm.model 设置正确的 ${from} 模型)。`
|
|
4307
|
+
: `provider 配置与 baseUrl 不一致,已按 baseUrl 切换到 "${to}"。`;
|
|
4308
|
+
const info = { from, to, reason, fromModel, toModel, message };
|
|
4307
4309
|
if (typeof options.onProviderFallback === "function") {
|
|
4308
4310
|
try {
|
|
4309
4311
|
options.onProviderFallback(info);
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
import { expandFileRefsAsync } from "./file-ref-expander.js";
|
|
47
47
|
import { composeSystemPrompt } from "./system-prompt.js";
|
|
48
48
|
import { buildUserContent } from "../lib/image-input.js";
|
|
49
|
+
import { mergeConsecutiveMessages } from "./message-roles.js";
|
|
49
50
|
import { isHeadlessConfigCommand } from "../lib/headless-config-command.js";
|
|
50
51
|
import { withQuietStdout } from "./quiet-stdout.js";
|
|
51
52
|
import { CostBudget } from "../lib/cost-budget.js";
|
|
@@ -610,14 +611,19 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
610
611
|
// content array (agent-core converts it per-provider for ollama/anthropic).
|
|
611
612
|
const userMessageContent = buildUserContent(userContent, options.images);
|
|
612
613
|
|
|
613
|
-
|
|
614
|
+
// Merge consecutive same-role turns so a resumed session whose previous run
|
|
615
|
+
// produced NO assistant response (history ends with a bare `user` turn) does
|
|
616
|
+
// not yield two adjacent `user` messages — which Anthropic/Bedrock reject as
|
|
617
|
+
// "roles must alternate", failing the resume (Claude Code 2.1.187 parity).
|
|
618
|
+
// No-op on a healthy alternating transcript.
|
|
619
|
+
const messages = mergeConsecutiveMessages([
|
|
614
620
|
{ role: "system", content: systemContent },
|
|
615
621
|
...(sessionStartContext
|
|
616
622
|
? [{ role: "system", content: sessionStartContext }]
|
|
617
623
|
: []),
|
|
618
624
|
...history,
|
|
619
625
|
{ role: "user", content: userMessageContent },
|
|
620
|
-
];
|
|
626
|
+
]);
|
|
621
627
|
|
|
622
628
|
// Persist the user turn up front (best-effort) so a session is recoverable
|
|
623
629
|
// even if the run crashes mid-loop. startSession is append-safe: only seed
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { bootstrap } from "./bootstrap.js";
|
|
20
20
|
import { buildSystemPrompt, agentLoop as coreAgentLoop } from "./agent-core.js";
|
|
21
21
|
import { composeSystemPrompt } from "./system-prompt.js";
|
|
22
|
+
import { collapseConsecutiveMessagesInPlace } from "./message-roles.js";
|
|
22
23
|
import { expandFileRefsAsync } from "./file-ref-expander.js";
|
|
23
24
|
import { detectImagePaths } from "../lib/image-input.js";
|
|
24
25
|
import { detectVersionSkew, versionSkewMessage } from "../lib/version-skew.js";
|
|
@@ -259,6 +260,28 @@ async function runTurn(messages, loopOptions, { runLoop, emit, costBudget }) {
|
|
|
259
260
|
let endReason = "complete";
|
|
260
261
|
let stopForCost = false;
|
|
261
262
|
|
|
263
|
+
// Collapse consecutive same-role turns before the model call ONLY when the
|
|
264
|
+
// caller flags a resume-degenerate transcript (`mergeRoles`): a resumed
|
|
265
|
+
// session whose previous run produced NO assistant response leaves a trailing
|
|
266
|
+
// bare `user` turn, so splicing the first live prompt after it sends two
|
|
267
|
+
// adjacent `user` messages — which Anthropic/Bedrock reject as "roles must
|
|
268
|
+
// alternate" (Claude Code 2.1.187 parity). It is GATED rather than applied
|
|
269
|
+
// every turn because the live turn loop legitimately produces consecutive
|
|
270
|
+
// `user` messages — an interrupted turn's dangling prompt, a PDH feedback
|
|
271
|
+
// note (§3.5.13) — that must reach the model distinctly and must not be
|
|
272
|
+
// folded (folding the interrupt-dangling turn even resurrects the abandoned
|
|
273
|
+
// request).
|
|
274
|
+
//
|
|
275
|
+
// Collapse IN PLACE (not a folded copy): the persistent `messages` array is
|
|
276
|
+
// reused for every later turn, so leaving the `[user, user]` pair in it would
|
|
277
|
+
// re-break the SECOND live turn (which re-sends the lingering pair). The gated
|
|
278
|
+
// turn is the first after resume, still tool-free (resumed history is
|
|
279
|
+
// user/assistant/system only), so an in-place fold is safe and the helper
|
|
280
|
+
// never folds `tool` turns regardless. Un-gated later turns are untouched, so
|
|
281
|
+
// the intentional interrupt/feedback consecutive-`user` states are preserved.
|
|
282
|
+
if (loopOptions.mergeRoles) {
|
|
283
|
+
collapseConsecutiveMessagesInPlace(messages);
|
|
284
|
+
}
|
|
262
285
|
for await (const event of runLoop(messages, loopOptions)) {
|
|
263
286
|
switch (event.type) {
|
|
264
287
|
case "tool-executing":
|
|
@@ -642,6 +665,15 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
642
665
|
}
|
|
643
666
|
}
|
|
644
667
|
|
|
668
|
+
// Resume-degenerate role sanitation (Claude Code 2.1.187 parity): when the
|
|
669
|
+
// replayed history ends with a bare `user` turn (the prior run produced no
|
|
670
|
+
// assistant response), the FIRST live prompt would make two adjacent `user`
|
|
671
|
+
// messages. Arm a one-shot flag consumed by the first model call so the merge
|
|
672
|
+
// fires exactly once — the live turn loop's intentional consecutive-`user`
|
|
673
|
+
// states (interrupt-dangling turn, PDH feedback note) are never folded.
|
|
674
|
+
let sanitizeRolesNextTurn =
|
|
675
|
+
resumedMessages > 0 && messages[messages.length - 1]?.role === "user";
|
|
676
|
+
|
|
645
677
|
emit({
|
|
646
678
|
type: "system",
|
|
647
679
|
subtype: "init",
|
|
@@ -1294,6 +1326,12 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
1294
1326
|
? AbortSignal.any([options.signal, currentAbort.signal])
|
|
1295
1327
|
: currentAbort.signal;
|
|
1296
1328
|
|
|
1329
|
+
// Consume the one-shot resume-degeneracy flag for THIS turn (see
|
|
1330
|
+
// sanitizeRolesNextTurn) so the role merge fires exactly once, whether the
|
|
1331
|
+
// model call completes or is interrupted.
|
|
1332
|
+
const mergeRolesThisTurn = sanitizeRolesNextTurn;
|
|
1333
|
+
sanitizeRolesNextTurn = false;
|
|
1334
|
+
|
|
1297
1335
|
let outcome;
|
|
1298
1336
|
try {
|
|
1299
1337
|
outcome = await runTurn(
|
|
@@ -1339,6 +1377,8 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
1339
1377
|
: {}),
|
|
1340
1378
|
iterationBudget: budget,
|
|
1341
1379
|
signal: turnSignal,
|
|
1380
|
+
// Resume-degenerate role merge for the first live model call only.
|
|
1381
|
+
mergeRoles: mergeRolesThisTurn,
|
|
1342
1382
|
},
|
|
1343
1383
|
{ runLoop, emit, costBudget },
|
|
1344
1384
|
);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation role sanitation shared by the headless runner and the
|
|
3
|
+
* stream-json runtime.
|
|
4
|
+
*
|
|
5
|
+
* Providers that strictly enforce alternating roles — Anthropic, Bedrock, and
|
|
6
|
+
* some OpenAI-compatible gateways — reject a payload with two consecutive
|
|
7
|
+
* messages of the same non-system role ("messages: roles must alternate").
|
|
8
|
+
*
|
|
9
|
+
* A degenerate transcript reaches that state on `--resume` when the ORIGINAL
|
|
10
|
+
* run produced NO assistant response (the model errored, the turn was
|
|
11
|
+
* interrupted, or the provider was down): the persisted JSONL ends with a bare
|
|
12
|
+
* `user` turn. Splicing the new resume prompt after it yields
|
|
13
|
+
* `[…, {role:"user"}, {role:"user"}]` → the provider 400s and resume fails.
|
|
14
|
+
*
|
|
15
|
+
* `mergeConsecutiveMessages` folds adjacent same-role (non-system) turns into a
|
|
16
|
+
* single message so the transcript always alternates, regardless of how it
|
|
17
|
+
* degenerated. This mirrors Claude Code 2.1.187 ("Fixed --resume failing when
|
|
18
|
+
* original run produced no model responses"). It is a no-op on a well-formed
|
|
19
|
+
* alternating transcript, so healthy sessions are byte-for-byte unchanged.
|
|
20
|
+
*
|
|
21
|
+
* @module runtime/message-roles
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Normalize a message `content` field to an array of content blocks. A plain
|
|
26
|
+
* string becomes a single `text` block; an empty string contributes nothing;
|
|
27
|
+
* an existing array is returned as-is; any other object is wrapped as a single
|
|
28
|
+
* opaque block so unknown shapes survive a merge.
|
|
29
|
+
* @param {*} content
|
|
30
|
+
* @returns {Array}
|
|
31
|
+
*/
|
|
32
|
+
function toBlocks(content) {
|
|
33
|
+
if (Array.isArray(content)) return content;
|
|
34
|
+
if (content == null) return [];
|
|
35
|
+
if (typeof content === "string") {
|
|
36
|
+
return content === "" ? [] : [{ type: "text", text: content }];
|
|
37
|
+
}
|
|
38
|
+
return [content];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Join two message `content` values. When both are plain strings the result
|
|
43
|
+
* stays a string (joined by a blank line) — the overwhelmingly common case, so
|
|
44
|
+
* a text-only resume keeps a string payload. When either side is a multimodal
|
|
45
|
+
* content array, both are normalized to block arrays and concatenated.
|
|
46
|
+
* @param {*} a
|
|
47
|
+
* @param {*} b
|
|
48
|
+
* @returns {string|Array}
|
|
49
|
+
*/
|
|
50
|
+
function joinContent(a, b) {
|
|
51
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
52
|
+
if (a === "") return b;
|
|
53
|
+
if (b === "") return a;
|
|
54
|
+
return `${a}\n\n${b}`;
|
|
55
|
+
}
|
|
56
|
+
return [...toBlocks(a), ...toBlocks(b)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Roles that may be folded together. ONLY `user` and `assistant` — a `tool`
|
|
61
|
+
* message carries a `tool_call_id` that must stay paired with its originating
|
|
62
|
+
* `assistant` tool-call, so consecutive tool results (a multi-tool turn) must
|
|
63
|
+
* NEVER merge; `system` turns (base prompt + injected context) stay distinct.
|
|
64
|
+
*/
|
|
65
|
+
const MERGEABLE_ROLES = new Set(["user", "assistant"]);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Merge consecutive same-role messages so the conversation alternates roles for
|
|
69
|
+
* the provider. Only `user`/`assistant` turns fold; `tool` and `system` turns
|
|
70
|
+
* are passed through untouched and never participate in a merge. Falsy /
|
|
71
|
+
* role-less entries are passed through verbatim rather than dropped.
|
|
72
|
+
*
|
|
73
|
+
* Returns a NEW array; the input is not mutated.
|
|
74
|
+
*
|
|
75
|
+
* @param {Array<object>} messages
|
|
76
|
+
* @returns {Array<object>}
|
|
77
|
+
*/
|
|
78
|
+
export function mergeConsecutiveMessages(messages) {
|
|
79
|
+
if (!Array.isArray(messages)) return messages;
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const msg of messages) {
|
|
82
|
+
const prev = out[out.length - 1];
|
|
83
|
+
if (
|
|
84
|
+
prev &&
|
|
85
|
+
msg &&
|
|
86
|
+
typeof msg.role === "string" &&
|
|
87
|
+
MERGEABLE_ROLES.has(msg.role) &&
|
|
88
|
+
prev.role === msg.role
|
|
89
|
+
) {
|
|
90
|
+
out[out.length - 1] = {
|
|
91
|
+
...prev,
|
|
92
|
+
content: joinContent(prev.content, msg.content),
|
|
93
|
+
};
|
|
94
|
+
} else {
|
|
95
|
+
out.push(msg);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* In-place variant: collapse consecutive mergeable-role turns by mutating the
|
|
103
|
+
* given array (same reference is kept, so closures over it stay valid). Used by
|
|
104
|
+
* the interactive REPL, whose `messages` array is the persistent conversation
|
|
105
|
+
* and is handed straight to a mutating `coreAgentLoop` — collapsing a copy would
|
|
106
|
+
* leave the degenerate pair in the persistent history and re-break later turns.
|
|
107
|
+
* Only fire it on a still tool-free transcript (the first turn after a
|
|
108
|
+
* degenerate resume); see callers.
|
|
109
|
+
*
|
|
110
|
+
* @param {Array<object>} messages
|
|
111
|
+
* @returns {boolean} whether anything was collapsed
|
|
112
|
+
*/
|
|
113
|
+
export function collapseConsecutiveMessagesInPlace(messages) {
|
|
114
|
+
if (!Array.isArray(messages)) return false;
|
|
115
|
+
const merged = mergeConsecutiveMessages(messages);
|
|
116
|
+
if (merged.length === messages.length) return false;
|
|
117
|
+
messages.splice(0, messages.length, ...merged);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const _internal = { toBlocks, joinContent, MERGEABLE_ROLES };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{E as N,r as k,I as m,J as F,U as d,c as i,K as l,V as u,S,F as R,Z as q,o as I,a as A,P as g,Q as x,R as C,_ as E}from"./vendor-BvqAck49.js";import{u as M,_ as H,d as _}from"./index-Bc1y36ct.js";import{a as W,b as D,c as j}from"./parsers-UEvh_ShA.js";import{R as J}from"./icons-DP3uiYxy.js";const B=N("providers",()=>{const K=k(!1),o=k(null),v=k([]),e=k([]),w=k(null);async function U(){const s=M();K.value=!0;try{const{output:f}=await s.execute("llm providers",15e3);v.value=W(f);const p=v.value.find(h=>h.active);p&&(w.value=p.name);const{output:b}=await s.execute("llm models",15e3);e.value=D(b)}catch(f){console.error("Failed to load providers:",f)}finally{K.value=!1}}async function y(s){await M().execute(`llm switch ${s}`,1e4),v.value.forEach(p=>p.active=p.name===s),w.value=s}async function a(s){const f=M();o.value=s;try{const{output:p,exitCode:b}=await f.execute("llm test",3e4),h=v.value.find(L=>L.name===s);return h&&(h.status=b===0?"ok":"error"),b===0}catch{const p=v.value.find(b=>b.name===s);return p&&(p.status="error"),!1}finally{o.value=null}}return{loading:K,testing:o,providers:v,localModels:e,activeProvider:w,loadProviders:U,switchProvider:y,testProvider:a}}),Q={__name:"Providers",setup(K,{expose:o}){o();const v=B(),e=M(),w=k(!1),U=k(!1),y=k(!1),a=A({provider:void 0,model:"",apiKey:"",baseUrl:"",temperature:.7,maxTokens:4096});let s={};const f=["ollama","openai","anthropic","volcengine","deepseek","gemini","groq","mistral","custom","zhipu"],p={ollama:"Ollama(本地)",openai:"OpenAI",anthropic:"Anthropic",volcengine:"火山引擎(豆包)",deepseek:"DeepSeek",gemini:"Google Gemini",groq:"Groq",mistral:"Mistral",custom:"Custom(自定义)",zhipu:"智谱 AI"},b=f.map(n=>({value:n,label:p[n]||n}));async function h(n){try{await v.switchProvider(n),_.success(`已切换到 ${n}`)}catch(t){_.error(`切换失败: ${t.message}`)}}async function L(n){await v.testProvider(n)?_.success(`${n} 连接正常`):_.warning(`${n} 连接失败,请检查配置`)}async function z(){w.value=!0;try{const{output:n}=await e.execute("config list",15e3),t=j(n);t.provider&&(a.provider=t.provider),t.model&&(a.model=t.model),t.apiKey&&(a.apiKey=t.apiKey),t.baseUrl&&(a.baseUrl=t.baseUrl),t.temperature!==void 0&&(a.temperature=t.temperature),t.maxTokens!==void 0&&(a.maxTokens=t.maxTokens),s={provider:t.provider??"",model:t.model??"",apiKey:t.apiKey??"",baseUrl:t.baseUrl??"",temperature:t.temperature??a.temperature,maxTokens:t.maxTokens??a.maxTokens},_.success("配置已加载")}catch(n){_.error(`加载配置失败: ${n.message}`)}finally{w.value=!1}}async function O(){U.value=!0;const n=[],t=[];try{if(a.provider&&a.provider!==s.provider)try{await e.execute(`config set llm.provider ${a.provider}`,1e4),t.push("provider")}catch(c){n.push(`provider: ${c.message}`)}if(a.model&&a.model!==s.model)try{await e.execute(`config set llm.model ${a.model}`,1e4),t.push("model")}catch(c){n.push(`model: ${c.message}`)}if(a.apiKey&&a.apiKey!==s.apiKey)try{await e.execute(`config set llm.apiKey ${a.apiKey}`,1e4),t.push("apiKey")}catch(c){n.push(`apiKey: ${c.message}`)}if(a.baseUrl!==void 0&&a.baseUrl!==s.baseUrl)try{await e.execute(`config set llm.baseUrl ${a.baseUrl}`,1e4),t.push("baseUrl")}catch(c){n.push(`baseUrl: ${c.message}`)}if(a.temperature!==s.temperature)try{await e.execute(`config set llm.temperature ${a.temperature}`,1e4),t.push("temperature")}catch(c){n.push(`temperature: ${c.message}`)}if(a.maxTokens!==s.maxTokens)try{await e.execute(`config set llm.maxTokens ${a.maxTokens}`,1e4),t.push("maxTokens")}catch(c){n.push(`maxTokens: ${c.message}`)}n.length>0?_.warning(`部分配置保存失败: ${n.join("; ")}`):t.length>0?(_.success(`已保存 ${t.length} 项配置`),s={...a},(t.includes("apiKey")||t.includes("provider")||t.includes("baseUrl")||t.includes("model"))&&(y.value=!0),t.includes("provider")&&v.loadProviders()):_.info("没有检测到配置变更")}catch(c){_.error(`保存配置失败: ${c.message}`)}finally{U.value=!1}}function P(){a.provider=s.provider||void 0,a.model=s.model||"",a.apiKey=s.apiKey||"",a.baseUrl=s.baseUrl||"",a.temperature=s.temperature??.7,a.maxTokens=s.maxTokens??4096}I(()=>{v.loadProviders(),z()});const T={providersStore:v,ws:e,configLoading:w,configSaving:U,showRestartHint:y,configForm:a,get loadedConfig(){return s},set loadedConfig(n){s=n},knownProviders:f,providerLabels:p,providerOptions:b,switchProvider:h,test:L,loadConfig:z,saveConfig:O,resetForm:P,onMounted:I,reactive:A,ref:k,get ReloadOutlined(){return J},get message(){return _},get useProvidersStore(){return B},get useWsStore(){return M},get parseLlmConfigOutput(){return j}};return Object.defineProperty(T,"__isScriptSetup",{enumerable:!1,value:!0}),T}},Z={style:{display:"flex","align-items":"center","justify-content":"space-between","margin-bottom":"24px"}},X={key:1,style:{"text-align":"center",padding:"60px"}},Y={class:"providers-grid"},$={style:{display:"flex","align-items":"center",gap:"12px","margin-bottom":"12px"}},ee={style:{"font-size":"24px"}},oe={style:{flex:"1"}},ae={style:{color:"#e0e0e0","font-weight":"500"}},te={style:{color:"var(--text-secondary)","font-size":"11px","font-family":"monospace"}},re={style:{display:"flex",gap:"8px","justify-content":"flex-end"}},se={key:0,style:{"margin-top":"24px"}},ne={style:{color:"#ccc","font-family":"monospace","font-size":"13px"}},ie={style:{display:"flex","align-items":"center","justify-content":"space-between","margin-bottom":"16px"}},le={class:"config-grid"},de={style:{display:"flex","align-items":"center",gap:"12px"}},ce={style:{display:"flex",gap:"12px","justify-content":"flex-end","margin-top":"16px"}};function me(K,o,v,e,w,U){const y=m("a-button"),a=m("a-alert"),s=m("a-spin"),f=m("a-tag"),p=m("a-badge"),b=m("a-card"),h=m("a-list-item"),L=m("a-list"),z=m("a-divider"),O=m("a-select"),P=m("a-form-item"),T=m("a-input"),n=m("a-input-password"),t=m("a-slider"),c=m("a-input-number"),V=m("a-form");return g(),F("div",null,[d("div",Z,[o[10]||(o[10]=d("div",null,[d("h2",{class:"page-title"},"LLM 配置"),d("p",{class:"page-sub"},"管理 AI 服务提供商")],-1)),i(y,{type:"primary",ghost:"",loading:e.providersStore.loading,onClick:o[0]||(o[0]=r=>e.providersStore.loadProviders())},{icon:l(()=>[i(e.ReloadOutlined)]),default:l(()=>[o[9]||(o[9]=x(" 刷新 ",-1))]),_:1},8,["loading"])]),u(" Active Provider Banner "),e.providersStore.activeProvider?(g(),S(a,{key:0,type:"success",style:{"margin-bottom":"20px",background:"rgba(41,162,112,.08)","border-color":"rgba(41,162,112,.3)"},"show-icon":""},{message:l(()=>[d("span",null,[o[11]||(o[11]=x("当前激活:",-1)),d("strong",null,C(e.providersStore.activeProvider),1)])]),_:1})):u("v-if",!0),u(" Loading "),e.providersStore.loading?(g(),F("div",X,[i(s,{size:"large"}),o[12]||(o[12]=d("div",{style:{color:"var(--text-muted)","margin-top":"12px"}},"加载 LLM 信息中...",-1))])):(g(),F(R,{key:2},[u(" Providers Grid "),d("div",null,[d("div",Y,[(g(!0),F(R,null,q(e.providersStore.providers,r=>(g(),S(b,{key:r.name,class:E(["provider-card",{active:r.active}]),style:{background:"var(--bg-card)"},size:"small"},{default:l(()=>[d("div",$,[d("span",ee,C(r.icon),1),d("div",oe,[d("div",ae,C(r.label),1),d("div",te,C(r.name),1)]),d("div",null,[r.active?(g(),S(f,{key:0,color:"green"},{default:l(()=>[...o[13]||(o[13]=[x("活跃",-1)])]),_:1})):r.status==="ok"?(g(),S(p,{key:1,status:"success",text:""})):r.status==="error"?(g(),S(p,{key:2,status:"error",text:""})):u("v-if",!0)])]),d("div",re,[i(y,{size:"small",style:{background:"var(--bg-card-hover)","border-color":"var(--border-color)"},loading:e.providersStore.testing===r.name,onClick:G=>e.test(r.name)},{default:l(()=>[...o[14]||(o[14]=[x(" 测试 ",-1)])]),_:1},8,["loading","onClick"]),r.active?(g(),S(f,{key:1,color:"green",style:{margin:"0","line-height":"24px"}},{default:l(()=>[...o[16]||(o[16]=[x("当前",-1)])]),_:1})):(g(),S(y,{key:0,size:"small",type:"primary",ghost:"",onClick:G=>e.switchProvider(r.name)},{default:l(()=>[...o[15]||(o[15]=[x(" 切换 ",-1)])]),_:1},8,["onClick"]))])]),_:2},1032,["class"]))),128))]),u(" Local Models (Ollama) "),e.providersStore.localModels.length?(g(),F("div",se,[o[17]||(o[17]=d("h3",{style:{color:"#ccc","font-size":"15px","margin-bottom":"12px"}}," 本地模型(Ollama) ",-1)),i(L,{"data-source":e.providersStore.localModels,size:"small",style:{background:"var(--bg-card)","border-radius":"8px",border:"1px solid var(--border-color)"}},{renderItem:l(({item:r})=>[i(h,{style:{padding:"10px 16px","border-color":"#252525"}},{actions:l(()=>[i(f,{color:"cyan",style:{"font-size":"10px"}},{default:l(()=>[x(C(r.size||"local"),1)]),_:2},1024)]),default:l(()=>[d("span",ne,C(r.name),1)]),_:2},1024)]),_:1},8,["data-source"])])):u("v-if",!0),u(" LLM Parameter Settings "),i(z,{style:{"border-color":"var(--border-color)",margin:"32px 0 24px"}}),e.showRestartHint?(g(),S(a,{key:1,type:"warning","show-icon":"",closable:"",style:{"margin-bottom":"16px"},message:"配置已保存,但当前聊天会话仍在用旧 key",description:"桌面端的 LLM 会话只在新建会话时重读配置。请新建一个 Chat,或重启应用,新 key/Provider 才会生效。",onClose:o[1]||(o[1]=r=>e.showRestartHint=!1)})):u("v-if",!0),d("div",ie,[o[19]||(o[19]=d("h3",{class:"section-title"},"LLM 参数设置",-1)),i(y,{size:"small",loading:e.configLoading,onClick:e.loadConfig,style:{background:"var(--bg-card-hover)","border-color":"var(--border-color)"}},{icon:l(()=>[i(e.ReloadOutlined)]),default:l(()=>[o[18]||(o[18]=x(" 加载配置 ",-1))]),_:1},8,["loading"])]),i(b,{class:"config-card",style:{background:"var(--bg-card)","border-color":"var(--border-color)"}},{default:l(()=>[i(V,{model:e.configForm,layout:"vertical",class:"config-form"},{default:l(()=>[d("div",le,[u(" Provider "),i(P,{label:"Provider(提供商)"},{default:l(()=>[i(O,{value:e.configForm.provider,"onUpdate:value":o[2]||(o[2]=r=>e.configForm.provider=r),placeholder:"选择 LLM 提供商",options:e.providerOptions,style:{width:"100%"},disabled:e.configSaving},null,8,["value","options","disabled"])]),_:1}),u(" Model "),i(P,{label:"Model(模型名称)"},{default:l(()=>[i(T,{value:e.configForm.model,"onUpdate:value":o[3]||(o[3]=r=>e.configForm.model=r),placeholder:"例如 doubao-seed-1-6-251015","allow-clear":"",disabled:e.configSaving},null,8,["value","disabled"])]),_:1}),u(" API Key "),i(P,{label:"API Key"},{default:l(()=>[i(n,{value:e.configForm.apiKey,"onUpdate:value":o[4]||(o[4]=r=>e.configForm.apiKey=r),placeholder:"输入 API Key",disabled:e.configSaving},null,8,["value","disabled"])]),_:1}),u(" Base URL "),i(P,{label:"Base URL"},{default:l(()=>[i(T,{value:e.configForm.baseUrl,"onUpdate:value":o[5]||(o[5]=r=>e.configForm.baseUrl=r),placeholder:"例如 https://ark.cn-beijing.volces.com/api/v3","allow-clear":"",disabled:e.configSaving},null,8,["value","disabled"])]),_:1}),u(" Temperature "),i(P,{label:"Temperature(温度)"},{default:l(()=>[d("div",de,[i(t,{value:e.configForm.temperature,"onUpdate:value":o[6]||(o[6]=r=>e.configForm.temperature=r),min:0,max:2,step:.1,style:{flex:"1"},disabled:e.configSaving},null,8,["value","disabled"]),i(c,{value:e.configForm.temperature,"onUpdate:value":o[7]||(o[7]=r=>e.configForm.temperature=r),min:0,max:2,step:.1,precision:1,style:{width:"80px"},disabled:e.configSaving},null,8,["value","disabled"])])]),_:1}),u(" Max Tokens "),i(P,{label:"Max Tokens(最大输出长度)"},{default:l(()=>[i(c,{value:e.configForm.maxTokens,"onUpdate:value":o[8]||(o[8]=r=>e.configForm.maxTokens=r),min:256,max:128e3,step:256,style:{width:"100%"},placeholder:"4096",disabled:e.configSaving},null,8,["value","disabled"])]),_:1})]),d("div",ce,[i(y,{onClick:e.resetForm,disabled:e.configSaving,style:{background:"var(--bg-card-hover)","border-color":"var(--border-color)"}},{default:l(()=>[...o[20]||(o[20]=[x(" 重置 ",-1)])]),_:1},8,["disabled"]),i(y,{type:"primary",loading:e.configSaving,onClick:e.saveConfig},{default:l(()=>[...o[21]||(o[21]=[x(" 保存配置 ",-1)])]),_:1},8,["loading"])])]),_:1},8,["model"])]),_:1})])],2112))])}const ge=H(Q,[["render",me],["__scopeId","data-v-64155d06"],["__file","/tmp/cc-web-panel-nw0Wu0/repo/packages/web-panel/src/views/Providers.vue"]]);export{ge as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{E as I,b as C,r as b,I as m,J as y,U as a,R as g,c as i,K as u,V as h,F as w,Z as L,N as O,o as M,P as p,Q as S,S as R}from"./vendor-BvqAck49.js";import{u as E,_ as N}from"./index-Bc1y36ct.js";import{u as P}from"./useShellMode-CgR0wCYM.js";import{p as V}from"./parsers-UEvh_ShA.js";import{u as z}from"./chat-33Oq8wz3.js";import{a9 as j,ap as q,R as B}from"./icons-DP3uiYxy.js";const A=I("skills",()=>{const _=b(!1),t=b([]),n=b(""),e=b("all"),k=C(()=>{const o=new Set(["all"]);return t.value.forEach(l=>{l.category&&o.add(l.category),l.executionMode&&o.add(l.executionMode)}),[...o]}),v=C(()=>{let o=t.value;if(e.value!=="all"&&(o=o.filter(l=>l.category===e.value||l.executionMode===e.value)),n.value){const l=n.value.toLowerCase();o=o.filter(d=>d.name?.toLowerCase().includes(l)||d.description?.toLowerCase().includes(l)||d.title?.toLowerCase().includes(l))}return o});async function f(){const o=E();_.value=!0;try{if(P().isEmbedded){const c=await o.sendRaw({type:"skill.list"},2e4);c?.ok&&Array.isArray(c.result?.skills)?t.value=c.result.skills:(console.error("skill.list custom topic failed:",c?.error),t.value=[]);return}const{output:l}=await o.execute("skill list",2e4),d=V(l);t.value=d}catch(l){console.error("Failed to load skills:",l)}finally{_.value=!1}}return{loading:_,allSkills:t,searchQuery:n,selectedCategory:e,categories:k,filteredSkills:v,loadSkills:f}}),F={__name:"Skills",setup(_,{expose:t}){t();const n=A(),e=z(),k=O(),v=C(()=>n.categories.filter(r=>r!=="all").slice(0,6));function f(r){return{"built-in":"内置","cli-direct":"CLI",agent:"Agent","llm-query":"LLM",hybrid:"混合",workspace:"工作区",marketplace:"市场"}[r]||r}function o(r){return{"cli-direct":"CLI",agent:"Agent","llm-query":"LLM",hybrid:"混合","built-in":"内置"}[r]||r||"未知"}function l(r){return{"cli-direct":"cyan",agent:"purple","llm-query":"blue",hybrid:"orange","built-in":"green"}[r]||"default"}async function d(r){await e.createSession("agent"),k.push("/chat"),setTimeout(()=>{e.currentSessionId&&e.sendMessage(e.currentSessionId,`/skill run ${r}`)},300)}M(()=>{n.allSkills.length||n.loadSkills()});const c={skillsStore:n,chatStore:e,router:k,displayCategories:v,catLabel:f,modeLabel:o,modeColor:l,runSkill:d,computed:C,onMounted:M,get useRouter(){return O},get ReloadOutlined(){return B},get PlayCircleOutlined(){return q},get AppstoreOutlined(){return j},get useSkillsStore(){return A},get useChatStore(){return z}};return Object.defineProperty(c,"__isScriptSetup",{enumerable:!1,value:!0}),c}},Q={style:{display:"flex","align-items":"center","justify-content":"space-between","margin-bottom":"24px"}},U={class:"page-sub"},T={style:{display:"flex",gap:"12px","margin-bottom":"20px","flex-wrap":"wrap"}},W={key:0,style:{"text-align":"center",padding:"60px"}},D={class:"skills-grid"},G={style:{display:"flex","justify-content":"space-between","align-items":"flex-start"}},J={style:{flex:"1","min-width":"0"}},K={style:{"font-weight":"500",color:"#e0e0e0","font-size":"14px","margin-bottom":"4px","font-family":"monospace"}},Z={style:{color:"var(--text-secondary)","font-size":"12px","line-height":"1.5"}},H={style:{"margin-left":"12px","flex-shrink":"0"}},X={style:{"margin-top":"10px",display:"flex","justify-content":"flex-end"}},Y={style:{"text-align":"center",padding:"60px",color:"var(--text-muted)"}};function $(_,t,n,e,k,v){const f=m("a-button"),o=m("a-input-search"),l=m("a-radio-button"),d=m("a-radio-group"),c=m("a-spin"),r=m("a-tag"),x=m("a-card");return p(),y("div",null,[a("div",Q,[a("div",null,[t[3]||(t[3]=a("h2",{class:"page-title"},"技能管理",-1)),a("p",U,g(e.skillsStore.allSkills.length)+" 个可用技能",1)]),i(f,{type:"primary",ghost:"",loading:e.skillsStore.loading,onClick:t[0]||(t[0]=s=>e.skillsStore.loadSkills())},{icon:u(()=>[i(e.ReloadOutlined)]),default:u(()=>[t[4]||(t[4]=S(" 刷新 ",-1))]),_:1},8,["loading"])]),h(" Search & Filter "),a("div",T,[i(o,{value:e.skillsStore.searchQuery,"onUpdate:value":t[1]||(t[1]=s=>e.skillsStore.searchQuery=s),placeholder:"搜索技能名称或描述...",style:{width:"300px"},"allow-clear":""},null,8,["value"]),i(d,{value:e.skillsStore.selectedCategory,"onUpdate:value":t[2]||(t[2]=s=>e.skillsStore.selectedCategory=s),"button-style":"solid"},{default:u(()=>[i(l,{value:"all"},{default:u(()=>[...t[5]||(t[5]=[S("全部",-1)])]),_:1}),(p(!0),y(w,null,L(e.displayCategories,s=>(p(),R(l,{key:s,value:s},{default:u(()=>[S(g(e.catLabel(s)),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),h(" Loading State "),e.skillsStore.loading?(p(),y("div",W,[i(c,{size:"large"}),t[6]||(t[6]=a("div",{style:{color:"var(--text-muted)","margin-top":"12px"}},"加载技能中...",-1))])):e.skillsStore.filteredSkills.length?(p(),y(w,{key:1},[h(" Skills Grid "),a("div",D,[(p(!0),y(w,null,L(e.skillsStore.filteredSkills,s=>(p(),R(x,{key:s.name,class:"skill-card",size:"small",hoverable:"",style:{background:"var(--bg-card)","border-color":"var(--border-color)",cursor:"default"}},{default:u(()=>[a("div",G,[a("div",J,[a("div",K,g(s.name),1),a("div",Z,g(s.description||"暂无描述"),1)]),a("div",H,[i(r,{color:e.modeColor(s.executionMode),style:{"font-size":"10px"}},{default:u(()=>[S(g(e.modeLabel(s.executionMode)),1)]),_:2},1032,["color"])])]),a("div",X,[i(f,{size:"small",type:"primary",ghost:"",onClick:ee=>e.runSkill(s.name)},{icon:u(()=>[i(e.PlayCircleOutlined)]),default:u(()=>[t[7]||(t[7]=S(" 运行 ",-1))]),_:1},8,["onClick"])])]),_:2},1024))),128))])],2112)):(p(),y(w,{key:2},[h(" Empty State "),a("div",Y,[i(e.AppstoreOutlined,{style:{"font-size":"48px","margin-bottom":"16px",display:"block"}}),a("div",null,g(e.skillsStore.allSkills.length?"没有匹配的技能":"点击刷新加载技能列表"),1)])],2112))])}const ie=N(F,[["render",$],["__scopeId","data-v-d64d43f5"],["__file","/tmp/cc-web-panel-nw0Wu0/repo/packages/web-panel/src/views/Skills.vue"]]);export{ie as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{E as F,b as D,r as R,I as p,J as c,U as _,c as n,K as a,S as C,V as x,o as I,x as V,P as i,Q as k,R as r,F as T,Z as O}from"./vendor-BvqAck49.js";import{u as P,_ as E}from"./index-Bc1y36ct.js";import{R as U}from"./icons-DP3uiYxy.js";const B=F("tasks",()=>{const y=R([]),l=R(!1);let u=null,t=null,d=null;const b=D(()=>y.value.filter(e=>e.status==="running")),v=D(()=>y.value.filter(e=>e.status==="pending")),w=D(()=>y.value.filter(e=>e.status==="completed"||e.status==="failed"||e.status==="timeout"));async function s(){const e=P();l.value=!0;try{const g=await e.sendRaw({type:"tasks-list"});g&&Array.isArray(g.tasks)&&(y.value=g.tasks)}catch{}finally{l.value=!1}}async function m(e){const g=P();try{await g.sendRaw({type:"tasks-stop",taskId:e})}catch{}finally{await s()}}const f=R(null);function h(e=5e3){N(),s(),u=setInterval(s,e),S()}function N(){u&&(clearInterval(u),u=null),t&&(t(),t=null),d&&(clearTimeout(d),d=null)}function S(){const e=P();if(t)return;const g=z=>{z.type==="task:notification"&&z.payload?.task&&(f.value=z.payload.task,s(),d&&clearTimeout(d),d=setTimeout(()=>{f.value=null,d=null},8e3))};t=e.onRuntimeEvent(g)}function A(e){return!e||e<0?"-":e<1e3?`${e}ms`:e<6e4?`${(e/1e3).toFixed(1)}s`:`${(e/6e4).toFixed(1)}m`}function o(e){switch(e){case"running":return"processing";case"pending":return"default";case"completed":return"success";case"failed":return"error";case"timeout":return"warning";default:return"default"}}return{tasks:y,loading:l,running:b,pending:v,completed:w,lastNotification:f,fetchTasks:s,stopTask:m,startPolling:h,stopPolling:N,formatDuration:A,getStatusColor:o}}),L={__name:"Tasks",setup(y,{expose:l}){l();const u=B(),t=[{title:"状态",key:"status",width:90},{title:"描述",key:"description",ellipsis:!0},{title:"类型",dataIndex:"type",width:100},{title:"耗时",key:"duration",width:90},{title:"创建时间",key:"createdAt",width:180},{title:"结果",key:"result",ellipsis:!0},{title:"操作",key:"action",width:80}];function d(s){return s.status==="running"&&s.startedAt?u.formatDuration(Date.now()-s.startedAt):s.completedAt&&s.startedAt?u.formatDuration(s.completedAt-s.startedAt):"-"}function b(s){return s?new Date(s).toLocaleString():"-"}function v(s,m){return s?s.length>m?`${s.slice(0,m)}...`:s:""}I(()=>u.startPolling(5e3)),V(()=>u.stopPolling());const w={store:u,columns:t,getDuration:d,formatTime:b,truncate:v,onMounted:I,onUnmounted:V,get ReloadOutlined(){return U},get useTasksStore(){return B}};return Object.defineProperty(w,"__isScriptSetup",{enumerable:!1,value:!0}),w}},W={class:"page-header"},j={key:0,class:"error-text"},J={key:1},K={class:"task-header"},M={class:"task-desc"},Q={class:"task-id"},Z={class:"task-meta"},q=["title"],G={key:0,class:"error-text"},H={key:1,class:"success-text"},X={key:2,class:"muted-text"};function Y(y,l,u,t,d,b){const v=p("a-button"),w=p("a-space"),s=p("a-alert"),m=p("a-statistic"),f=p("a-card"),h=p("a-col"),N=p("a-row"),S=p("a-tag"),A=p("a-table");return i(),c("div",null,[_("div",W,[l[3]||(l[3]=_("div",null,[_("h2",{class:"page-title"},"后台任务"),_("p",{class:"page-sub"},"查看后台任务队列、实时通知和任务执行结果。")],-1)),n(w,null,{default:a(()=>[n(v,{ghost:"",loading:t.store.loading,onClick:l[0]||(l[0]=o=>t.store.fetchTasks())},{icon:a(()=>[n(t.ReloadOutlined)]),default:a(()=>[l[2]||(l[2]=k(" 刷新 ",-1))]),_:1},8,["loading"])]),_:1})]),t.store.lastNotification?(i(),C(s,{key:0,type:t.store.lastNotification.status==="completed"?"success":"error","show-icon":"",closable:"",class:"banner",onClose:l[1]||(l[1]=o=>t.store.lastNotification=null)},{message:a(()=>[k(" 任务"+r(t.store.lastNotification.status==="completed"?"完成":"结束")+": "+r(t.store.lastNotification.description||t.store.lastNotification.id.slice(0,16)),1)]),description:a(()=>[t.store.lastNotification.error?(i(),c("span",j,r(t.store.lastNotification.error),1)):t.store.lastNotification.result?(i(),c("span",J,r(t.truncate(String(t.store.lastNotification.result),120)),1)):x("v-if",!0)]),_:1},8,["type"])):x("v-if",!0),n(N,{gutter:[16,16],class:"stats-row"},{default:a(()=>[n(h,{xs:12,sm:6},{default:a(()=>[n(f,{class:"stat-card",size:"small"},{default:a(()=>[n(m,{title:"全部任务",value:t.store.tasks.length},null,8,["value"])]),_:1})]),_:1}),n(h,{xs:12,sm:6},{default:a(()=>[n(f,{class:"stat-card",size:"small"},{default:a(()=>[n(m,{title:"运行中",value:t.store.running.length},null,8,["value"])]),_:1})]),_:1}),n(h,{xs:12,sm:6},{default:a(()=>[n(f,{class:"stat-card",size:"small"},{default:a(()=>[n(m,{title:"等待中",value:t.store.pending.length},null,8,["value"])]),_:1})]),_:1}),n(h,{xs:12,sm:6},{default:a(()=>[n(f,{class:"stat-card",size:"small"},{default:a(()=>[n(m,{title:"已完成",value:t.store.completed.length},null,8,["value"])]),_:1})]),_:1})]),_:1}),t.store.running.length>0?(i(),C(f,{key:1,title:"运行中的任务",class:"panel-card",size:"small"},{default:a(()=>[(i(!0),c(T,null,O(t.store.running,o=>(i(),c("div",{key:o.id,class:"task-item running"},[_("div",K,[n(S,{color:t.store.getStatusColor(o.status)},{default:a(()=>[k(r(o.status.toUpperCase()),1)]),_:2},1032,["color"]),_("span",M,r(o.description),1),_("span",Q,r(o.id.slice(0,16)),1)]),_("div",Z,[_("span",null,"类型: "+r(o.type||"-"),1),_("span",null,"已运行: "+r(t.store.formatDuration(Date.now()-o.startedAt)),1),n(v,{size:"small",danger:"",onClick:e=>t.store.stopTask(o.id)},{default:a(()=>[...l[4]||(l[4]=[k("停止",-1)])]),_:1},8,["onClick"])])]))),128))]),_:1})):x("v-if",!0),n(f,{class:"panel-card"},{default:a(()=>[n(A,{columns:t.columns,"data-source":t.store.tasks,pagination:{pageSize:15,size:"small"},loading:t.store.loading,"row-key":"id",size:"small"},{bodyCell:a(({column:o,record:e})=>[o.key==="status"?(i(),C(S,{key:0,color:t.store.getStatusColor(e.status)},{default:a(()=>[k(r(e.status),1)]),_:2},1032,["color"])):o.key==="description"?(i(),c("span",{key:1,title:e.command},r(e.description),9,q)):o.key==="duration"?(i(),c(T,{key:2},[k(r(t.getDuration(e)),1)],64)):o.key==="createdAt"?(i(),c(T,{key:3},[k(r(t.formatTime(e.createdAt)),1)],64)):o.key==="result"?(i(),c(T,{key:4},[e.error?(i(),c("span",G,r(t.truncate(e.error,60)),1)):e.result?(i(),c("span",H,r(t.truncate(String(e.result),60)),1)):(i(),c("span",X,"-"))],64)):o.key==="action"?(i(),c(T,{key:5},[e.status==="running"?(i(),C(v,{key:0,size:"small",danger:"",onClick:g=>t.store.stopTask(e.id)},{default:a(()=>[...l[5]||(l[5]=[k(" 停止 ",-1)])]),_:1},8,["onClick"])):x("v-if",!0)],64)):x("v-if",!0)]),_:1},8,["data-source","loading"])]),_:1})])}const st=E(L,[["render",Y],["__scopeId","data-v-da5ff6a2"],["__file","/tmp/cc-web-panel-nw0Wu0/repo/packages/web-panel/src/views/Tasks.vue"]]);export{st as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{O as r}from"./index-Bc1y36ct.js";const o=((n,a,e)=>{r(n,`[ant-design-vue: ${a}] ${e}`)});export{o as d};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{C as o}from"./Col-uy6MSNXM.js";import{U as t}from"./index-Bc1y36ct.js";import"./vendor-BvqAck49.js";import"./index-czxh_xOe.js";import"./icons-DP3uiYxy.js";const s=t(o);export{s as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{A as o}from"./Row--IaheXkB.js";import{U as t}from"./index-Bc1y36ct.js";import"./vendor-BvqAck49.js";import"./responsiveObserve-Dmn9lUes.js";import"./useFlexGapSupport-D07_5fY3.js";import"./styleChecker-D3ay9a4W.js";import"./index-czxh_xOe.js";import"./icons-DP3uiYxy.js";const l=t(o);export{l as default};
|