dsh-llm-verifier 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +301 -301
  3. package/cordis.patch.yml +4 -4
  4. package/lib/client.js +176 -14
  5. package/lib/index.js +418 -43
  6. package/lib/types/client-i18n.d.ts +27 -0
  7. package/lib/types/client-i18n.js +18 -0
  8. package/lib/types/client.d.ts +15 -2
  9. package/lib/types/client.js +134 -9
  10. package/lib/types/config.d.ts +4 -0
  11. package/lib/types/config.js +5 -3
  12. package/lib/types/index.d.ts +2 -0
  13. package/lib/types/index.js +185 -4
  14. package/lib/types/plan-gate.d.ts +14 -0
  15. package/lib/types/plan-gate.js +70 -0
  16. package/lib/types/router.d.ts +10 -1
  17. package/lib/types/router.js +37 -7
  18. package/lib/types/session.js +25 -10
  19. package/lib/types/team-gate.d.ts +11 -0
  20. package/lib/types/team-gate.js +51 -0
  21. package/package.json +112 -112
  22. package/src/auto.test.ts +86 -86
  23. package/src/auto.ts +133 -133
  24. package/src/cache.ts +115 -115
  25. package/src/caller.test.ts +34 -34
  26. package/src/caller.ts +151 -151
  27. package/src/client-i18n.ts +18 -0
  28. package/src/client.tsx +385 -213
  29. package/src/config.ts +189 -183
  30. package/src/core.test.ts +71 -71
  31. package/src/core.ts +205 -205
  32. package/src/engine.test.ts +136 -136
  33. package/src/engine.ts +140 -140
  34. package/src/images.ts +33 -33
  35. package/src/index.ts +416 -247
  36. package/src/parity.test.ts +85 -85
  37. package/src/plan-gate.test.ts +56 -0
  38. package/src/plan-gate.ts +77 -0
  39. package/src/router.test.ts +107 -89
  40. package/src/router.ts +349 -309
  41. package/src/session.test.ts +53 -36
  42. package/src/session.ts +117 -103
  43. package/src/statistics.test.ts +80 -80
  44. package/src/statistics.ts +328 -328
  45. package/src/team-gate.test.ts +56 -0
  46. package/src/team-gate.ts +63 -0
  47. package/src/top-logprobs.test.ts +84 -84
  48. package/src/top-logprobs.ts +153 -153
  49. package/src/topic-storage.test.ts +31 -31
  50. package/src/topic-storage.ts +42 -42
@@ -44,7 +44,8 @@ interface VerifierSettingsProps {
44
44
  remote: VerifierRemote;
45
45
  }
46
46
  interface StatisticsPageProps {
47
- sessionId: string;
47
+ sessionId?: string;
48
+ isGlobal?: boolean;
48
49
  rpc: {
49
50
  call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<{
50
51
  ok: boolean;
@@ -56,7 +57,19 @@ interface StatisticsPageProps {
56
57
  };
57
58
  }
58
59
  export declare function VerifierSettings({ remote }: VerifierSettingsProps): import("react").JSX.Element;
59
- export declare function StatisticsPage({ sessionId, rpc }: StatisticsPageProps): import("react").JSX.Element;
60
+ export declare function StatisticsPage({ sessionId, rpc, isGlobal }: StatisticsPageProps): import("react").JSX.Element;
61
+ export declare function VerifierSidebarIcon({ size, active }: {
62
+ size: number;
63
+ active?: boolean;
64
+ }): import("react").JSX.Element;
65
+ export declare function GlobalVerifierDashboard({ rpc }: {
66
+ rpc: any;
67
+ }): import("react").JSX.Element;
68
+ export declare function RightSidebarVerifierPanel({ rpc, sessionId }: {
69
+ rpc: any;
70
+ sessionId?: string;
71
+ }): import("react").JSX.Element;
72
+ export declare function RightSidebarVerifierTitle(): import("react").JSX.Element;
60
73
  export declare const inject: string[];
61
74
  export declare function apply(ctx: ClientContext): void;
62
75
  //# sourceMappingURL=client.d.ts.map
@@ -73,7 +73,7 @@ export function VerifierSettings({ remote }) {
73
73
  if (!loaded || !draft)
74
74
  return _jsxs("div", { style: shell, children: [_jsx("h2", { style: settingsHeading, children: t['settings.title'] }), _jsx("p", { style: settingsIntro, children: error ?? t['settings.loading'] }), error && _jsx("div", { children: _jsx(Button, { variant: "outline", onClick: () => void load(), children: t['settings.retry'] }) })] });
75
75
  const numeric = (key, min = 0) => _jsx(Input, { style: { width: '100%', height: 36, borderRadius: 8 }, type: "number", min: min, value: String(draft[key]), onChange: e => patch(key, Number(e.target.value)) });
76
- return _jsxs("div", { style: shell, children: [_jsx("h2", { style: settingsHeading, children: t['settings.title'] }), _jsx("p", { style: settingsIntro, children: t['settings.intro'] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.tools'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.enabled.title'], help: draft.enabled ? t['field.enabled.helpOn'] : t['field.enabled.helpOff'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.enabled, "aria-label": t['field.enabled.title'], onClick: () => patch('enabled', !draft.enabled), style: toggleStyle(draft.enabled), children: _jsx("span", { style: toggleThumbStyle(draft.enabled) }) })] }), !draft.enabled && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.enabled.warnDisabled'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.autoVerify'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMode.title'], help: t['field.autoVerifyMode.help'] }), _jsxs("select", { style: selectStyle, value: draft.autoVerifyMode, onChange: e => patch('autoVerifyMode', e.target.value), children: [_jsx("option", { value: "manual", children: t['field.autoVerifyMode.manual'] }), _jsx("option", { value: "smart", children: t['field.autoVerifyMode.smart'] }), _jsx("option", { value: "strict", children: t['field.autoVerifyMode.strict'] })] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteSemantic.title'], help: t['field.autoRouteSemantic.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoRouteSemantic, "aria-label": t['field.autoRouteSemantic.title'], onClick: () => patch('autoRouteSemantic', !draft.autoRouteSemantic), style: toggleStyle(draft.autoRouteSemantic), children: _jsx("span", { style: toggleThumbStyle(draft.autoRouteSemantic) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMinConfidence.title'], help: t['field.autoRouteMinConfidence.help'] }), numeric('autoRouteMinConfidence', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxCandidates.title'], help: t['field.autoRouteMaxCandidates.help'] }), numeric('autoRouteMaxCandidates', 3)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerTask.title'], help: t['field.autoRouteMaxPerTask.help'] }), numeric('autoRouteMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerSession.title'], help: t['field.autoRouteMaxPerSession.help'] }), numeric('autoRouteMaxPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoTrackCompletionThreshold.title'], help: t['field.autoTrackCompletionThreshold.help'] }), numeric('autoTrackCompletionThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxItemChars.title'], help: t['field.autoRouteMaxItemChars.help'] }), numeric('autoRouteMaxItemChars', 100)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxInputChars.title'], help: t['field.autoRouteMaxInputChars.help'] }), numeric('autoRouteMaxInputChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerTask.title'], help: t['field.autoMaxModelCallsPerTask.help'] }), numeric('autoMaxModelCallsPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerSession.title'], help: t['field.autoMaxModelCallsPerSession.help'] }), numeric('autoMaxModelCallsPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyThreshold.title'], help: t['field.autoVerifyThreshold.help'] }), numeric('autoVerifyThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyRepeats.title'], help: t['field.autoVerifyRepeats.help'] }), numeric('autoVerifyRepeats', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMinToolCalls.title'], help: t['field.autoVerifyMinToolCalls.help'] }), numeric('autoVerifyMinToolCalls', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxChars.title'], help: t['field.autoVerifyMaxChars.help'] }), numeric('autoVerifyMaxChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerTask.title'], help: t['field.autoVerifyMaxPerTask.help'] }), numeric('autoVerifyMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerSession.title'], help: t['field.autoVerifyMaxPerSession.help'] }), numeric('autoVerifyMaxPerSession', 1)] }), draft.autoVerifyMode !== 'manual' && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.autoVerify.warnNotice'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.model'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.provider.title'], help: t['field.provider.help'] }), _jsx("select", { style: selectStyle, value: draft.provider, onChange: e => { const provider = e.target.value; const first = loaded.groups.find(g => g.id === provider)?.models[0]; setDraft({ ...draft, provider, ...(first ? { model: first.id, reasoningEffort: first.reasoning?.defaultEffort } : {}) }); }, children: loaded.groups.map(g => _jsxs("option", { value: g.id, children: [g.name, " \u00B7 ", g.id] }, g.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.model.title'], help: t['field.model.help'] }), _jsx("select", { style: selectStyle, value: draft.model, onChange: e => { const model = e.target.value; const found = models.find(m => m.id === model); setDraft({ ...draft, model, ...(found?.reasoning?.defaultEffort ? { reasoningEffort: found.reasoning.defaultEffort } : { reasoningEffort: undefined }) }); }, children: models.map(m => _jsxs("option", { value: m.id, children: [m.name, " \u00B7 ", m.id] }, m.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.reasoningEffort.title'], help: t['field.reasoningEffort.help'] }), _jsxs("select", { style: selectStyle, value: draft.reasoningEffort ?? '', onChange: e => patch('reasoningEffort', e.target.value || undefined), children: [_jsx("option", { value: "", children: t['field.reasoningEffort.default'] }), efforts.map(e => _jsx("option", { value: e.id, children: e.name }, e.id))] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxTokens.title'], help: t['field.maxTokens.help'] }), numeric('maxTokens', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.execution'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxConcurrency.title'], help: t['field.maxConcurrency.help'] }), numeric('maxConcurrency', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxRetries.title'], help: t['field.maxRetries.help'] }), numeric('maxRetries', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.timeoutMs.title'], help: t['field.timeoutMs.help'] }), numeric('timeoutMs', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.cacheMaxEntries.title'], help: t['field.cacheMaxEntries.help'] }), numeric('cacheMaxEntries', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.cost'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedInputUsdPerMillion.title'], help: t['field.estimatedInputUsdPerMillion.help'] }), numeric('estimatedInputUsdPerMillion', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedOutputUsdPerMillion.title'], help: t['field.estimatedOutputUsdPerMillion.help'] }), numeric('estimatedOutputUsdPerMillion', 0)] })] }), loaded.failures.length > 0 && _jsxs("div", { style: { padding: '10px 12px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-bg)', color: 'var(--dsw-alias-state-warn-label)', fontSize: 12, lineHeight: '18px' }, children: [_jsx("div", { style: { fontWeight: 500, marginBottom: 3 }, children: t['settings.catalogFailures'] }), loaded.failures.map(x => _jsx("div", { children: x }, x))] }), error && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' }, children: error }), saved && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' }, children: t['settings.saved'] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, paddingTop: 4 }, children: [_jsx(Button, { variant: "outline", disabled: busy, onClick: () => void load(), children: t['settings.reload'] }), _jsx(Button, { variant: "primary", disabled: busy || !loaded.writable, onClick: () => void save(), children: busy ? t['settings.saving'] : t['settings.save'] })] })] });
76
+ return _jsxs("div", { style: shell, children: [_jsx("h2", { style: settingsHeading, children: t['settings.title'] }), _jsx("p", { style: settingsIntro, children: t['settings.intro'] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.tools'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.enabled.title'], help: draft.enabled ? t['field.enabled.helpOn'] : t['field.enabled.helpOff'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.enabled, "aria-label": t['field.enabled.title'], onClick: () => patch('enabled', !draft.enabled), style: toggleStyle(draft.enabled), children: _jsx("span", { style: toggleThumbStyle(draft.enabled) }) })] }), !draft.enabled && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.enabled.warnDisabled'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.autoVerify'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMode.title'], help: t['field.autoVerifyMode.help'] }), _jsxs("select", { style: selectStyle, value: draft.autoVerifyMode, onChange: e => patch('autoVerifyMode', e.target.value), children: [_jsx("option", { value: "manual", children: t['field.autoVerifyMode.manual'] }), _jsx("option", { value: "smart", children: t['field.autoVerifyMode.smart'] }), _jsx("option", { value: "strict", children: t['field.autoVerifyMode.strict'] })] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteSemantic.title'], help: t['field.autoRouteSemantic.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoRouteSemantic, "aria-label": t['field.autoRouteSemantic.title'], onClick: () => patch('autoRouteSemantic', !draft.autoRouteSemantic), style: toggleStyle(draft.autoRouteSemantic), children: _jsx("span", { style: toggleThumbStyle(draft.autoRouteSemantic) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyTeamTasks.title'], help: t['field.autoVerifyTeamTasks.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoVerifyTeamTasks ?? true, "aria-label": t['field.autoVerifyTeamTasks.title'], onClick: () => patch('autoVerifyTeamTasks', !(draft.autoVerifyTeamTasks ?? true)), style: toggleStyle(draft.autoVerifyTeamTasks ?? true), children: _jsx("span", { style: toggleThumbStyle(draft.autoVerifyTeamTasks ?? true) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyPlanMode.title'], help: t['field.autoVerifyPlanMode.help'] }), _jsx("button", { type: "button", role: "switch", "aria-checked": draft.autoVerifyPlanMode ?? true, "aria-label": t['field.autoVerifyPlanMode.title'], onClick: () => patch('autoVerifyPlanMode', !(draft.autoVerifyPlanMode ?? true)), style: toggleStyle(draft.autoVerifyPlanMode ?? true), children: _jsx("span", { style: toggleThumbStyle(draft.autoVerifyPlanMode ?? true) }) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMinConfidence.title'], help: t['field.autoRouteMinConfidence.help'] }), numeric('autoRouteMinConfidence', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxCandidates.title'], help: t['field.autoRouteMaxCandidates.help'] }), numeric('autoRouteMaxCandidates', 3)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerTask.title'], help: t['field.autoRouteMaxPerTask.help'] }), numeric('autoRouteMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxPerSession.title'], help: t['field.autoRouteMaxPerSession.help'] }), numeric('autoRouteMaxPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoTrackCompletionThreshold.title'], help: t['field.autoTrackCompletionThreshold.help'] }), numeric('autoTrackCompletionThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxItemChars.title'], help: t['field.autoRouteMaxItemChars.help'] }), numeric('autoRouteMaxItemChars', 100)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoRouteMaxInputChars.title'], help: t['field.autoRouteMaxInputChars.help'] }), numeric('autoRouteMaxInputChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerTask.title'], help: t['field.autoMaxModelCallsPerTask.help'] }), numeric('autoMaxModelCallsPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoMaxModelCallsPerSession.title'], help: t['field.autoMaxModelCallsPerSession.help'] }), numeric('autoMaxModelCallsPerSession', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyThreshold.title'], help: t['field.autoVerifyThreshold.help'] }), numeric('autoVerifyThreshold', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyRepeats.title'], help: t['field.autoVerifyRepeats.help'] }), numeric('autoVerifyRepeats', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMinToolCalls.title'], help: t['field.autoVerifyMinToolCalls.help'] }), numeric('autoVerifyMinToolCalls', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxChars.title'], help: t['field.autoVerifyMaxChars.help'] }), numeric('autoVerifyMaxChars', 1000)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerTask.title'], help: t['field.autoVerifyMaxPerTask.help'] }), numeric('autoVerifyMaxPerTask', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.autoVerifyMaxPerSession.title'], help: t['field.autoVerifyMaxPerSession.help'] }), numeric('autoVerifyMaxPerSession', 1)] }), draft.autoVerifyMode !== 'manual' && _jsx("p", { style: { margin: '8px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-warn-label)' }, children: t['field.autoVerify.warnNotice'] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.model'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.provider.title'], help: t['field.provider.help'] }), _jsx("select", { style: selectStyle, value: draft.provider, onChange: e => { const provider = e.target.value; const first = loaded.groups.find(g => g.id === provider)?.models[0]; setDraft({ ...draft, provider, ...(first ? { model: first.id, reasoningEffort: first.reasoning?.defaultEffort } : {}) }); }, children: loaded.groups.map(g => _jsxs("option", { value: g.id, children: [g.name, " \u00B7 ", g.id] }, g.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.model.title'], help: t['field.model.help'] }), _jsx("select", { style: selectStyle, value: draft.model, onChange: e => { const model = e.target.value; const found = models.find(m => m.id === model); setDraft({ ...draft, model, ...(found?.reasoning?.defaultEffort ? { reasoningEffort: found.reasoning.defaultEffort } : { reasoningEffort: undefined }) }); }, children: models.map(m => _jsxs("option", { value: m.id, children: [m.name, " \u00B7 ", m.id] }, m.id)) })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.reasoningEffort.title'], help: t['field.reasoningEffort.help'] }), _jsxs("select", { style: selectStyle, value: draft.reasoningEffort ?? '', onChange: e => patch('reasoningEffort', e.target.value || undefined), children: [_jsx("option", { value: "", children: t['field.reasoningEffort.default'] }), efforts.map(e => _jsx("option", { value: e.id, children: e.name }, e.id))] })] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxTokens.title'], help: t['field.maxTokens.help'] }), numeric('maxTokens', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.execution'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxConcurrency.title'], help: t['field.maxConcurrency.help'] }), numeric('maxConcurrency', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.maxRetries.title'], help: t['field.maxRetries.help'] }), numeric('maxRetries', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.timeoutMs.title'], help: t['field.timeoutMs.help'] }), numeric('timeoutMs', 1)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.cacheMaxEntries.title'], help: t['field.cacheMaxEntries.help'] }), numeric('cacheMaxEntries', 1)] })] }), _jsxs("section", { style: group, children: [_jsx(GroupTitle, { children: t['section.cost'] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedInputUsdPerMillion.title'], help: t['field.estimatedInputUsdPerMillion.help'] }), numeric('estimatedInputUsdPerMillion', 0)] }), _jsxs("div", { style: row, children: [_jsx(Label, { title: t['field.estimatedOutputUsdPerMillion.title'], help: t['field.estimatedOutputUsdPerMillion.help'] }), numeric('estimatedOutputUsdPerMillion', 0)] })] }), loaded.failures.length > 0 && _jsxs("div", { style: { padding: '10px 12px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-bg)', color: 'var(--dsw-alias-state-warn-label)', fontSize: 12, lineHeight: '18px' }, children: [_jsx("div", { style: { fontWeight: 500, marginBottom: 3 }, children: t['settings.catalogFailures'] }), loaded.failures.map(x => _jsx("div", { children: x }, x))] }), error && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' }, children: error }), saved && _jsx("p", { style: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' }, children: t['settings.saved'] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, paddingTop: 4 }, children: [_jsx(Button, { variant: "outline", disabled: busy, onClick: () => void load(), children: t['settings.reload'] }), _jsx(Button, { variant: "primary", disabled: busy || !loaded.writable, onClick: () => void save(), children: busy ? t['settings.saving'] : t['settings.save'] })] })] });
77
77
  }
78
78
  function Metric({ label, value, note, accent }) {
79
79
  return _jsxs("div", { style: { ...dashboardCard, padding: '16px 18px', minWidth: 0 }, children: [_jsx("div", { style: muted, children: label }), _jsx("div", { style: { fontSize: 25, fontWeight: 750, lineHeight: 1.2, margin: '7px 0 5px', color: accent }, children: value }), _jsx("div", { style: { ...muted, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: note })] });
@@ -96,23 +96,93 @@ function TrendChart({ daily, days, lang }) {
96
96
  const barWidth = Math.max(3, Math.min(18, innerW / Math.max(rows.length, 1) * .55));
97
97
  return _jsxs("div", { style: { width: '100%', overflowX: 'auto' }, children: [_jsxs("svg", { viewBox: `0 0 ${width} ${height}`, style: { display: 'block', width: '100%', minWidth: 620, height: 'auto' }, "aria-label": t['chart.ariaLabel'], children: [[0, .25, .5, .75, 1].map(ratio => _jsxs("g", { children: [_jsx("line", { x1: pad.l, x2: width - pad.r, y1: pad.t + innerH * ratio, y2: pad.t + innerH * ratio, stroke: "rgba(148,163,184,.16)" }), _jsx("text", { x: pad.l - 8, y: pad.t + innerH * ratio + 4, textAnchor: "end", fontSize: "10", fill: "var(--dsw-text-secondary)", children: Math.round(max * (1 - ratio)) })] }, ratio)), rows.map((row, index) => _jsx("rect", { x: x(index) - barWidth / 2, y: y(row.invocations), width: barWidth, height: pad.t + innerH - y(row.invocations), rx: "2", fill: "#4f8cff", opacity: ".82" }, row.date)), _jsx("polyline", { points: points, fill: "none", stroke: "#5ed7e8", strokeWidth: "2.4", strokeLinejoin: "round", strokeLinecap: "round" }), rows.map((row, index) => index % step === 0 || index === rows.length - 1 ? _jsxs("text", { x: x(index), y: height - 14, textAnchor: "middle", fontSize: "10", fill: "var(--dsw-text-secondary)", children: [Number(row.date.slice(5, 7)), "/", Number(row.date.slice(8, 10))] }, row.date) : null)] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'center', gap: 18, ...muted }, children: [_jsxs("span", { children: [_jsx("i", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: '#4f8cff', marginRight: 6 } }), t['chart.legendToolCalls']] }), _jsxs("span", { children: [_jsx("i", { style: { display: 'inline-block', width: 14, height: 2, background: '#5ed7e8', marginRight: 6, verticalAlign: 'middle' } }), t['chart.legendModelCalls']] })] })] });
98
98
  }
99
- export function StatisticsPage({ sessionId, rpc }) {
99
+ export function StatisticsPage({ sessionId, rpc, isGlobal }) {
100
100
  const lang = useLanguage();
101
101
  const t = dictionaries[lang];
102
102
  const labels = toolLabels[lang];
103
103
  const [days, setDays] = useState(30);
104
- const [sessionOnly, setSessionOnly] = useState(false);
104
+ const [sessionOnly, setSessionOnly] = useState(!isGlobal && Boolean(sessionId));
105
105
  const [data, setData] = useState(null);
106
106
  const [loading, setLoading] = useState(true);
107
107
  const [error, setError] = useState(null);
108
108
  const [refresh, setRefresh] = useState(0);
109
- useEffect(() => { const controller = new AbortController(); setLoading(true); setError(null); void rpc.call('/llm-verifier', 'statistics', { fromMs: startOfRange(days), toMs: endOfToday(), timezoneOffsetMinutes: new Date().getTimezoneOffset(), recentLimit: 50, ...(sessionOnly ? { sessionId: String(sessionId) } : {}) }, controller.signal).then((result) => { if (!result.ok)
110
- throw new Error(result.error?.message ?? t['stats.requestFailed']); setData(result.value); }, (cause) => { if (!controller.signal.aborted)
111
- throw cause; }).catch((cause) => { if (!controller.signal.aborted)
112
- setError(message(cause)); }).finally(() => { if (!controller.signal.aborted)
113
- setLoading(false); }); return () => controller.abort(); }, [days, sessionOnly, sessionId, refresh, rpc, t]);
109
+ useEffect(() => {
110
+ const controller = new AbortController();
111
+ setLoading(true);
112
+ setError(null);
113
+ const effectiveSessionId = sessionOnly && sessionId ? String(sessionId) : undefined;
114
+ const queryPayload = {
115
+ fromMs: startOfRange(days),
116
+ toMs: endOfToday(),
117
+ timezoneOffsetMinutes: new Date().getTimezoneOffset(),
118
+ recentLimit: 50,
119
+ ...(effectiveSessionId ? { sessionId: effectiveSessionId } : {}),
120
+ };
121
+ const fetchOverview = async () => {
122
+ if (rpc && typeof rpc.call === 'function') {
123
+ try {
124
+ const result = await rpc.call('/api', 'llm-verifier/statistics', queryPayload, controller.signal);
125
+ if (result && result.ok) {
126
+ return result.value;
127
+ }
128
+ if (result && result.ok === false) {
129
+ throw new Error(result.error?.message ?? t['stats.requestFailed']);
130
+ }
131
+ }
132
+ catch (rpcError) {
133
+ if (controller.signal.aborted)
134
+ throw rpcError;
135
+ console.warn('[llm-verifier] rpc.call failed, trying fetch fallback:', rpcError);
136
+ }
137
+ }
138
+ const response = await fetch('/api/llm-verifier/statistics', {
139
+ method: 'POST',
140
+ headers: { 'content-type': 'application/json' },
141
+ body: JSON.stringify(queryPayload),
142
+ signal: controller.signal,
143
+ });
144
+ if (!response.ok) {
145
+ throw new Error(`HTTP ${response.status}: ${response.statusText || t['stats.requestFailed']}`);
146
+ }
147
+ const data = await response.json();
148
+ if (data && data.ok === true) {
149
+ return data.value;
150
+ }
151
+ if (data && data.type === 'server-response' && data.result?.ok === true) {
152
+ return data.result.value;
153
+ }
154
+ throw new Error(data?.error?.message ?? data?.result?.error?.message ?? t['stats.requestFailed']);
155
+ };
156
+ void fetchOverview()
157
+ .then(overview => {
158
+ setData(overview);
159
+ })
160
+ .catch((cause) => {
161
+ if (!controller.signal.aborted)
162
+ setError(message(cause));
163
+ })
164
+ .finally(() => {
165
+ if (!controller.signal.aborted)
166
+ setLoading(false);
167
+ });
168
+ return () => controller.abort();
169
+ }, [days, sessionOnly, sessionId, refresh, rpc, t]);
114
170
  const totals = data?.totals;
115
- return _jsx("main", { style: { height: '100%', overflow: 'auto', boxSizing: 'border-box', padding: '22px clamp(16px, 3vw, 38px) 48px', color: 'var(--dsw-text-primary)', background: 'radial-gradient(circle at 10% 0%, rgba(115,77,255,.09), transparent 32%), radial-gradient(circle at 100% 8%, rgba(47,197,201,.07), transparent 28%)' }, children: _jsxs("div", { style: { maxWidth: 1180, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }, children: [_jsxs("header", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10 }, children: [_jsx("span", { style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 10, background: 'rgba(79,140,255,.14)', color: '#6da0ff' }, children: _jsx(IconDataOutline16, { size: 18 }) }), _jsx("h2", { style: { margin: 0, fontSize: 23 }, children: t['stats.pageTitle'] })] }), _jsx("p", { style: { margin: '7px 0 0 44px', ...muted }, children: tFormat(t['stats.updatedAt'], { time: data ? dateTime(data.generatedAt, lang) : '--' }) })] }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx("div", { style: { display: 'flex', padding: 3, borderRadius: 10, background: 'var(--dsw-surface-sunken)', border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))' }, children: [7, 30, 90].map(value => _jsx("button", { onClick: () => setDays(value), style: { border: 0, borderRadius: 7, padding: '6px 10px', cursor: 'pointer', color: days === value ? '#fff' : 'var(--dsw-text-secondary)', background: days === value ? '#3f68d8' : 'transparent' }, children: tFormat(t['stats.daysUnit'], { days: value }) }, value)) }), _jsx("button", { onClick: () => setSessionOnly(value => !value), style: { border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', borderRadius: 9, padding: '7px 11px', cursor: 'pointer', color: 'var(--dsw-text-primary)', background: sessionOnly ? 'rgba(79,140,255,.18)' : 'var(--dsw-surface-sunken)' }, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] }), _jsx("button", { title: t['stats.refresh'], onClick: () => setRefresh(value => value + 1), style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 9, border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', color: 'var(--dsw-text-primary)', background: 'var(--dsw-surface-sunken)', cursor: 'pointer' }, children: _jsx(IconRefreshOutline16, { size: 16 }) })] })] }), error && _jsxs("div", { style: { ...dashboardCard, padding: 18, borderColor: 'var(--dsw-danger, #e85858)', color: 'var(--dsw-danger, #e85858)' }, children: [error, _jsx("div", { style: { ...muted, marginTop: 6 }, children: t['stats.hostRestartHint'] })] }), loading && !data ? _jsx("div", { style: { ...dashboardCard, padding: 32, textAlign: 'center', ...muted }, children: t['stats.loading'] }) : _jsxs(_Fragment, { children: [_jsxs("section", { style: { ...dashboardCard, padding: '22px 24px', display: 'grid', gridTemplateColumns: 'minmax(220px,1.4fr) minmax(240px,1fr)', gap: 24, alignItems: 'center' }, children: [_jsxs("div", { children: [_jsx("div", { style: muted, children: tFormat(t['stats.costSummary'], { days }) }), _jsx("div", { style: { fontSize: 38, fontWeight: 780, letterSpacing: '-.03em', margin: '5px 0' }, children: money(totals?.estimatedCostUsd ?? 0) }), _jsx("div", { style: muted, children: tFormat(t['stats.callsSummary'], { invocations: compact(totals?.invocations ?? 0, lang), calls: compact(totals?.calls ?? 0, lang) }) })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr auto', gap: '9px 16px', fontSize: 13 }, children: [_jsx("span", { style: muted, children: t['stats.successCalls'] }), _jsxs("strong", { children: [compact(totals?.successes ?? 0, lang), " ", _jsxs("small", { style: { color: '#77d49b' }, children: ["\u25B2 ", ((totals?.successRate ?? 0) * 100).toFixed(1), "%"] })] }), _jsx("span", { style: muted, children: t['stats.failedCalls'] }), _jsx("strong", { children: compact(totals?.failures ?? 0, lang) }), _jsx("span", { style: muted, children: t['stats.avgDuration'] }), _jsx("strong", { children: duration(totals?.averageDurationMs ?? 0) })] })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 12 }, children: [_jsx(Metric, { label: t['metric.cacheHitRate'], value: ((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + '%', note: tFormat(t['metric.cacheHitNote'], { hits: compact(totals?.cacheHits ?? 0, lang), total: compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0), lang) }), accent: "#b7dd64" }), _jsx(Metric, { label: t['metric.tokens'], value: compact(totals?.tokens ?? 0, lang), note: tFormat(t['metric.tokensNote'], { input: compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0), lang), output: compact(totals?.outputTokens ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.avgModelCalls'], value: (totals?.invocations ?? 0) > 0 ? ((totals?.calls ?? 0) / (totals?.invocations ?? 1)).toFixed(1) : '0', note: tFormat(t['metric.avgModelCallsNote'], { attempts: compact(totals?.attempts ?? 0, lang), retries: compact(totals?.retries ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.scoringMode'], value: compact(totals?.topLogprobScores ?? 0, lang), note: tFormat(t['metric.scoringModeNote'], { explicit: compact(totals?.explicitTagScores ?? 0, lang) }) })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 20px 16px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("strong", { children: t['chart.title'] }), _jsx("span", { style: muted, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] })] }), _jsx(TrendChart, { daily: data?.daily ?? [], days: days, lang: lang })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['table.title'] }), _jsx("span", { style: muted, children: tFormat(t['table.toolCount'], { count: data?.tools.length ?? 0 }) })] }), _jsx("div", { style: { overflowX: 'auto' }, children: _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13, minWidth: 760 }, children: [_jsx("thead", { children: _jsx("tr", { style: { textAlign: 'left', color: 'var(--dsw-text-secondary)', background: 'var(--dsw-surface-sunken)' }, children: [t['table.colTool'], t['table.colInvocations'], t['table.colSuccessRate'], t['table.colAvgDuration'], t['table.colModelCalls'], t['table.colTokens'], t['table.colCacheHits'], t['table.colEstimatedCost']].map(value => _jsx("th", { style: { padding: '10px 12px', fontWeight: 500 }, children: value }, value)) }) }), _jsxs("tbody", { children: [(data?.tools ?? []).map(tool => _jsxs("tr", { style: { borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("td", { style: { padding: '13px 12px' }, children: [_jsx("span", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: toolColors[tool.toolName] ?? '#8691a8', marginRight: 8 } }), _jsx("strong", { children: labels[tool.toolName] ?? tool.toolName }), _jsx("div", { style: { ...muted, margin: '3px 0 0 16px' }, children: tool.toolName })] }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.invocations, lang) }), _jsxs("td", { style: { padding: '13px 12px', color: tool.successRate >= .9 ? '#77d49b' : tool.successRate >= .7 ? '#e3bd63' : '#ed7777' }, children: [(tool.successRate * 100).toFixed(1), "%"] }), _jsx("td", { style: { padding: '13px 12px' }, children: duration(tool.averageDurationMs) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.calls, lang) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.tokens, lang) }), _jsxs("td", { style: { padding: '13px 12px' }, children: [tool.cacheHits, "/", tool.cacheHits + tool.cacheMisses] }), _jsx("td", { style: { padding: '13px 12px' }, children: money(tool.estimatedCostUsd) })] }, tool.toolName)), (data?.tools.length ?? 0) === 0 && _jsx("tr", { children: _jsx("td", { colSpan: 8, style: { padding: 28, textAlign: 'center', ...muted }, children: t['table.empty'] }) })] })] }) })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(290px,.45fr)', gap: 16, alignItems: 'start' }, children: [_jsxs("div", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['recent.title'] }), _jsx("span", { style: muted, children: t['recent.maxCount'] })] }), _jsxs("div", { style: { maxHeight: 360, overflow: 'auto' }, children: [(data?.recent ?? []).map(item => _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'minmax(170px,1fr) auto', gap: 12, padding: '11px 8px', borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: '50%', background: item.success ? '#59c985' : '#e76565' } }), _jsx("strong", { style: { fontSize: 13 }, children: labels[item.toolName] ?? item.toolName }), _jsxs("span", { style: muted, children: [item.provider, "/", item.model] })] }), !item.success && _jsx("div", { title: item.errorMessage, style: { margin: '5px 0 0 15px', fontSize: 11, color: '#e76565', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: item.errorMessage ?? item.errorName })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("div", { style: { fontSize: 12 }, children: duration(item.durationMs) }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: dateTime(item.startedAt, lang) })] })] }, item.id)), (data?.recent.length ?? 0) === 0 && _jsx("div", { style: { padding: 24, textAlign: 'center', ...muted }, children: t['recent.empty'] })] })] }), _jsxs("div", { style: { ...dashboardCard, padding: '18px' }, children: [_jsx("strong", { children: t['models.title'] }), _jsxs("div", { style: { marginTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }, children: [(data?.models ?? []).map(model => _jsxs("div", { style: { padding: '11px 12px', borderRadius: 10, background: 'var(--dsw-surface-sunken)' }, children: [_jsx("div", { style: { fontWeight: 650, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis' }, children: model.model }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: model.provider }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: 9, fontSize: 12 }, children: [_jsx("span", { children: tFormat(t['models.calls'], { calls: compact(model.calls, lang) }) }), _jsx("span", { children: tFormat(t['models.tokens'], { tokens: compact(model.tokens, lang) }) }), _jsx("strong", { children: money(model.estimatedCostUsd) })] })] }, model.provider + '\0' + model.model)), (data?.models.length ?? 0) === 0 && _jsx("div", { style: muted, children: t['models.empty'] })] })] })] })] })] }) });
171
+ return _jsx("main", { style: { height: '100%', overflow: 'auto', boxSizing: 'border-box', padding: '22px clamp(16px, 3vw, 38px) 48px', color: 'var(--dsw-text-primary)', background: 'radial-gradient(circle at 10% 0%, rgba(115,77,255,.09), transparent 32%), radial-gradient(circle at 100% 8%, rgba(47,197,201,.07), transparent 28%)' }, children: _jsxs("div", { style: { maxWidth: 1180, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }, children: [_jsxs("header", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10 }, children: [_jsx("span", { style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 10, background: 'rgba(79,140,255,.14)', color: '#6da0ff' }, children: _jsx(IconDataOutline16, { size: 18 }) }), _jsx("h2", { style: { margin: 0, fontSize: 23 }, children: isGlobal ? t['global.panelTitle'] : t['stats.pageTitle'] })] }), _jsx("p", { style: { margin: '7px 0 0 44px', ...muted }, children: isGlobal ? t['global.panelIntro'] : tFormat(t['stats.updatedAt'], { time: data ? dateTime(data.generatedAt, lang) : '--' }) })] }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx("div", { style: { display: 'flex', padding: 3, borderRadius: 10, background: 'var(--dsw-surface-sunken)', border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))' }, children: [7, 30, 90].map(value => _jsx("button", { onClick: () => setDays(value), style: { border: 0, borderRadius: 7, padding: '6px 10px', cursor: 'pointer', color: days === value ? '#fff' : 'var(--dsw-text-secondary)', background: days === value ? '#3f68d8' : 'transparent' }, children: tFormat(t['stats.daysUnit'], { days: value }) }, value)) }), Boolean(sessionId) && _jsx("button", { onClick: () => setSessionOnly(value => !value), style: { border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', borderRadius: 9, padding: '7px 11px', cursor: 'pointer', color: 'var(--dsw-text-primary)', background: sessionOnly ? 'rgba(79,140,255,.18)' : 'var(--dsw-surface-sunken)' }, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] }), _jsx("button", { title: t['stats.refresh'], onClick: () => setRefresh(value => value + 1), style: { display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 9, border: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))', color: 'var(--dsw-text-primary)', background: 'var(--dsw-surface-sunken)', cursor: 'pointer' }, children: _jsx(IconRefreshOutline16, { size: 16 }) })] })] }), error && _jsxs("div", { style: { ...dashboardCard, padding: 18, borderColor: 'var(--dsw-danger, #e85858)', color: 'var(--dsw-danger, #e85858)' }, children: [error, _jsx("div", { style: { ...muted, marginTop: 6 }, children: t['stats.hostRestartHint'] })] }), loading && !data ? _jsx("div", { style: { ...dashboardCard, padding: 32, textAlign: 'center', ...muted }, children: t['stats.loading'] }) : _jsxs(_Fragment, { children: [_jsxs("section", { style: { ...dashboardCard, padding: '22px 24px', display: 'grid', gridTemplateColumns: 'minmax(220px,1.4fr) minmax(240px,1fr)', gap: 24, alignItems: 'center' }, children: [_jsxs("div", { children: [_jsx("div", { style: muted, children: tFormat(t['stats.costSummary'], { days }) }), _jsx("div", { style: { fontSize: 38, fontWeight: 780, letterSpacing: '-.03em', margin: '5px 0' }, children: money(totals?.estimatedCostUsd ?? 0) }), _jsx("div", { style: muted, children: tFormat(t['stats.callsSummary'], { invocations: compact(totals?.invocations ?? 0, lang), calls: compact(totals?.calls ?? 0, lang) }) })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr auto', gap: '9px 16px', fontSize: 13 }, children: [_jsx("span", { style: muted, children: t['stats.successCalls'] }), _jsxs("strong", { children: [compact(totals?.successes ?? 0, lang), " ", _jsxs("small", { style: { color: '#77d49b' }, children: ["\u25B2 ", ((totals?.successRate ?? 0) * 100).toFixed(1), "%"] })] }), _jsx("span", { style: muted, children: t['stats.failedCalls'] }), _jsx("strong", { children: compact(totals?.failures ?? 0, lang) }), _jsx("span", { style: muted, children: t['stats.avgDuration'] }), _jsx("strong", { children: duration(totals?.averageDurationMs ?? 0) })] })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 12 }, children: [_jsx(Metric, { label: t['metric.cacheHitRate'], value: ((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + '%', note: tFormat(t['metric.cacheHitNote'], { hits: compact(totals?.cacheHits ?? 0, lang), total: compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0), lang) }), accent: "#b7dd64" }), _jsx(Metric, { label: t['metric.tokens'], value: compact(totals?.tokens ?? 0, lang), note: tFormat(t['metric.tokensNote'], { input: compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0), lang), output: compact(totals?.outputTokens ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.avgModelCalls'], value: (totals?.invocations ?? 0) > 0 ? ((totals?.calls ?? 0) / (totals?.invocations ?? 1)).toFixed(1) : '0', note: tFormat(t['metric.avgModelCallsNote'], { attempts: compact(totals?.attempts ?? 0, lang), retries: compact(totals?.retries ?? 0, lang) }) }), _jsx(Metric, { label: t['metric.scoringMode'], value: compact(totals?.topLogprobScores ?? 0, lang), note: tFormat(t['metric.scoringModeNote'], { explicit: compact(totals?.explicitTagScores ?? 0, lang) }) })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 20px 16px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("strong", { children: t['chart.title'] }), _jsx("span", { style: muted, children: sessionOnly ? t['stats.currentSession'] : t['stats.allSessions'] })] }), _jsx(TrendChart, { daily: data?.daily ?? [], days: days, lang: lang })] }), _jsxs("section", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['table.title'] }), _jsx("span", { style: muted, children: tFormat(t['table.toolCount'], { count: data?.tools.length ?? 0 }) })] }), _jsx("div", { style: { overflowX: 'auto' }, children: _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13, minWidth: 760 }, children: [_jsx("thead", { children: _jsx("tr", { style: { textAlign: 'left', color: 'var(--dsw-text-secondary)', background: 'var(--dsw-surface-sunken)' }, children: [t['table.colTool'], t['table.colInvocations'], t['table.colSuccessRate'], t['table.colAvgDuration'], t['table.colModelCalls'], t['table.colTokens'], t['table.colCacheHits'], t['table.colEstimatedCost']].map(value => _jsx("th", { style: { padding: '10px 12px', fontWeight: 500 }, children: value }, value)) }) }), _jsxs("tbody", { children: [(data?.tools ?? []).map(tool => _jsxs("tr", { style: { borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("td", { style: { padding: '13px 12px' }, children: [_jsx("span", { style: { display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: toolColors[tool.toolName] ?? '#8691a8', marginRight: 8 } }), _jsx("strong", { children: labels[tool.toolName] ?? tool.toolName }), _jsx("div", { style: { ...muted, margin: '3px 0 0 16px' }, children: tool.toolName })] }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.invocations, lang) }), _jsxs("td", { style: { padding: '13px 12px', color: tool.successRate >= .9 ? '#77d49b' : tool.successRate >= .7 ? '#e3bd63' : '#ed7777' }, children: [(tool.successRate * 100).toFixed(1), "%"] }), _jsx("td", { style: { padding: '13px 12px' }, children: duration(tool.averageDurationMs) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.calls, lang) }), _jsx("td", { style: { padding: '13px 12px' }, children: compact(tool.tokens, lang) }), _jsxs("td", { style: { padding: '13px 12px' }, children: [tool.cacheHits, "/", tool.cacheHits + tool.cacheMisses] }), _jsx("td", { style: { padding: '13px 12px' }, children: money(tool.estimatedCostUsd) })] }, tool.toolName)), (data?.tools.length ?? 0) === 0 && _jsx("tr", { children: _jsx("td", { colSpan: 8, style: { padding: 28, textAlign: 'center', ...muted }, children: t['table.empty'] }) })] })] }) })] }), _jsxs("section", { style: { display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(290px,.45fr)', gap: 16, alignItems: 'start' }, children: [_jsxs("div", { style: { ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }, children: [_jsx("strong", { children: t['recent.title'] }), _jsx("span", { style: muted, children: t['recent.maxCount'] })] }), _jsxs("div", { style: { maxHeight: 360, overflow: 'auto' }, children: [(data?.recent ?? []).map(item => _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'minmax(170px,1fr) auto', gap: 12, padding: '11px 8px', borderTop: '1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))' }, children: [_jsxs("div", { children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: '50%', background: item.success ? '#59c985' : '#e76565' } }), _jsx("strong", { style: { fontSize: 13 }, children: labels[item.toolName] ?? item.toolName }), _jsxs("span", { style: muted, children: [item.provider, "/", item.model] })] }), !item.success && _jsx("div", { title: item.errorMessage, style: { margin: '5px 0 0 15px', fontSize: 11, color: '#e76565', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: item.errorMessage ?? item.errorName })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("div", { style: { fontSize: 12 }, children: duration(item.durationMs) }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: dateTime(item.startedAt, lang) })] })] }, item.id)), (data?.recent.length ?? 0) === 0 && _jsx("div", { style: { padding: 24, textAlign: 'center', ...muted }, children: t['recent.empty'] })] })] }), _jsxs("div", { style: { ...dashboardCard, padding: '18px' }, children: [_jsx("strong", { children: t['models.title'] }), _jsxs("div", { style: { marginTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }, children: [(data?.models ?? []).map(model => _jsxs("div", { style: { padding: '11px 12px', borderRadius: 10, background: 'var(--dsw-surface-sunken)' }, children: [_jsx("div", { style: { fontWeight: 650, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis' }, children: model.model }), _jsx("div", { style: { ...muted, marginTop: 3 }, children: model.provider }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: 9, fontSize: 12 }, children: [_jsx("span", { children: tFormat(t['models.calls'], { calls: compact(model.calls, lang) }) }), _jsx("span", { children: tFormat(t['models.tokens'], { tokens: compact(model.tokens, lang) }) }), _jsx("strong", { children: money(model.estimatedCostUsd) })] })] }, model.provider + '\0' + model.model)), (data?.models.length ?? 0) === 0 && _jsx("div", { style: muted, children: t['models.empty'] })] })] })] })] })] }) });
172
+ }
173
+ export function VerifierSidebarIcon({ size, active }) {
174
+ return _jsx("span", { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: size, height: size, color: active ? 'var(--dsw-alias-brand-primary, #4f8cff)' : 'currentColor' }, children: _jsx(IconDataOutline16, { size: Math.min(18, size) }) });
175
+ }
176
+ export function GlobalVerifierDashboard({ rpc }) {
177
+ return _jsx(StatisticsPage, { rpc: rpc, isGlobal: true });
178
+ }
179
+ export function RightSidebarVerifierPanel({ rpc, sessionId }) {
180
+ return (_jsx("div", { style: { height: '100%', overflow: 'hidden', display: 'flex', flexDirection: 'column' }, children: _jsx(StatisticsPage, { rpc: rpc, sessionId: sessionId }) }));
181
+ }
182
+ export function RightSidebarVerifierTitle() {
183
+ const lang = useLanguage();
184
+ const t = dictionaries[lang];
185
+ return _jsx("span", { style: { fontSize: 13, fontWeight: 500 }, children: t['slot.statistics'] });
116
186
  }
117
187
  export const inject = ['slots', 'connection', 'remote', 'remote.session', 'remote.settings'];
118
188
  export function apply(ctx) {
@@ -132,5 +202,60 @@ export function apply(ctx) {
132
202
  label: detectLanguage() === 'zh' ? zh['slot.statistics'] : en['slot.statistics'],
133
203
  inject: () => ({ rpc: connection.rpc }),
134
204
  }, StatisticsPage));
205
+ ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
206
+ name: 'sidebar.panellist',
207
+ id: 'llm-verifier',
208
+ order: 40,
209
+ label: detectLanguage() === 'zh' ? zh['slot.globalDashboard'] : en['slot.globalDashboard'],
210
+ }, VerifierSidebarIcon));
211
+ ctx.slots.inject('main', () => ctx.slots.register({
212
+ name: 'main',
213
+ key: 'llm-verifier',
214
+ inject: () => ({ rpc: connection.rpc }),
215
+ }, GlobalVerifierDashboard));
216
+ const VERIFIER_TAB_KIND = 'llm-verifier';
217
+ const VERIFIER_TAB_ID = 'dsh-llm-verifier';
218
+ const registerRightSidebar = (tabs) => {
219
+ if (!tabs || typeof tabs.register !== 'function')
220
+ return;
221
+ try {
222
+ tabs.register({
223
+ id: VERIFIER_TAB_ID,
224
+ kind: VERIFIER_TAB_KIND,
225
+ priority: 'extension',
226
+ title: () => (detectLanguage() === 'zh' ? zh['slot.statistics'] : en['slot.statistics']),
227
+ guide: [{
228
+ order: 45,
229
+ title: () => (detectLanguage() === 'zh' ? zh['guide.verifier.title'] : en['guide.verifier.title']),
230
+ description: () => (detectLanguage() === 'zh' ? zh['guide.verifier.desc'] : en['guide.verifier.desc']),
231
+ icon: VerifierSidebarIcon,
232
+ }],
233
+ });
234
+ }
235
+ catch { }
236
+ };
237
+ const existingTabs = typeof ctx.get === 'function' ? ctx.get('sidebarRightTabs') : undefined;
238
+ if (existingTabs) {
239
+ registerRightSidebar(existingTabs);
240
+ }
241
+ if (typeof ctx.inject === 'function') {
242
+ try {
243
+ ctx.inject(['sidebarRightTabs'], (subCtx) => {
244
+ const subTabs = typeof subCtx?.get === 'function' ? subCtx.get('sidebarRightTabs') : subCtx?.sidebarRightTabs;
245
+ if (subTabs)
246
+ registerRightSidebar(subTabs);
247
+ });
248
+ }
249
+ catch { }
250
+ }
251
+ ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
252
+ name: 'sidebar.right.pane.tab',
253
+ key: VERIFIER_TAB_ID,
254
+ inject: () => ({ rpc: connection.rpc }),
255
+ }, RightSidebarVerifierPanel));
256
+ ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({
257
+ name: 'sidebar.right.pane.tab.title',
258
+ key: VERIFIER_TAB_ID,
259
+ }, RightSidebarVerifierTitle));
135
260
  }
136
261
  //# sourceMappingURL=client.js.map
@@ -21,6 +21,8 @@ export interface Config {
21
21
  autoRouteMaxInputChars?: number;
22
22
  autoMaxModelCallsPerTask?: number;
23
23
  autoMaxModelCallsPerSession?: number;
24
+ autoVerifyTeamTasks?: boolean;
25
+ autoVerifyPlanMode?: boolean;
24
26
  provider?: string;
25
27
  model?: string;
26
28
  reasoningEffort?: string;
@@ -53,6 +55,8 @@ export interface ResolvedConfig {
53
55
  autoRouteMaxInputChars: number;
54
56
  autoMaxModelCallsPerTask: number;
55
57
  autoMaxModelCallsPerSession: number;
58
+ autoVerifyTeamTasks: boolean;
59
+ autoVerifyPlanMode: boolean;
56
60
  provider: string;
57
61
  model: string;
58
62
  reasoningEffort?: string;
@@ -19,8 +19,10 @@ export const Config = z.object({
19
19
  autoRouteMaxInputChars: z.number().step(1).min(1000).default(60000),
20
20
  autoMaxModelCallsPerTask: z.number().step(1).min(1).default(48),
21
21
  autoMaxModelCallsPerSession: z.number().step(1).min(1).default(160),
22
+ autoVerifyTeamTasks: z.boolean().default(true),
23
+ autoVerifyPlanMode: z.boolean().default(true),
22
24
  provider: z.string().default('deepseek-official'),
23
- model: z.string().default('deepseek-v4-flash'),
25
+ model: z.string().default('deepseek-flash'),
24
26
  reasoningEffort: z.string(),
25
27
  maxTokens: z.number().step(1).min(1).default(32768),
26
28
  timeoutMs: z.number().step(1).min(1).default(300000),
@@ -34,7 +36,7 @@ export const Config = z.object({
34
36
  });
35
37
  export function resolveConfig(config = {}) {
36
38
  const provider = (config.provider ?? 'deepseek-official').trim();
37
- const model = (config.model ?? 'deepseek-v4-flash').trim();
39
+ const model = (config.model ?? 'deepseek-flash').trim();
38
40
  if (!provider)
39
41
  throw new Error('llm-verifier: provider must be non-empty');
40
42
  if (!model)
@@ -92,7 +94,7 @@ export function resolveConfig(config = {}) {
92
94
  if (![estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion].every(value => Number.isFinite(value) && value >= 0))
93
95
  throw new Error('llm-verifier: estimated token prices must be finite non-negative numbers');
94
96
  const reasoningEffort = config.reasoningEffort?.trim();
95
- return { enabled: config.enabled ?? true, autoVerifyMode, autoVerifyThreshold, autoRouteSemantic: config.autoRouteSemantic ?? true, autoRouteMinConfidence, autoTrackCompletionThreshold, provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values };
97
+ return { enabled: config.enabled ?? true, autoVerifyMode, autoVerifyThreshold, autoRouteSemantic: config.autoRouteSemantic ?? true, autoRouteMinConfidence, autoTrackCompletionThreshold, autoVerifyTeamTasks: config.autoVerifyTeamTasks ?? true, autoVerifyPlanMode: config.autoVerifyPlanMode ?? true, provider, model, ...(reasoningEffort ? { reasoningEffort } : {}), maxRetries, cacheDir, estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion, ...values };
96
98
  }
97
99
  export function installVerifierSettings(ctx, entry, onChange) {
98
100
  let source = () => entry;
@@ -10,6 +10,8 @@ export * from './statistics.ts';
10
10
  export * from './topic-storage.ts';
11
11
  export * from './auto.ts';
12
12
  export * from './router.ts';
13
+ export * from './plan-gate.ts';
14
+ export * from './team-gate.ts';
13
15
  export { callVerifier, RequestLimiter, type VerifierClientConfig, type VerifierImage, type UsageStats, type VerifierCompletion } from './caller.ts';
14
16
  export declare function apply(ctx: Context, config?: Config): void;
15
17
  //# sourceMappingURL=index.d.ts.map
@@ -9,6 +9,8 @@ import { loadVerifierImages } from "./images.js";
9
9
  import { extractSession } from "./session.js";
10
10
  import { AutoVerificationBudget, analyzeAutoTask, automaticFeedback } from "./auto.js";
11
11
  import { AutoVerifierRouter, analyzeStructuredRoute, boundDecision, buildSemanticRoutePrompt, parseSemanticRoute, semanticDecision, semanticRouteHint } from "./router.js";
12
+ import { detectPlanExit, buildPlanPreReviewPrompt, parsePlanReviewVerdict } from "./plan-gate.js";
13
+ import { inspectTeamTasks, buildTeamTaskVerificationPrompt } from "./team-gate.js";
12
14
  import { StatisticsStore, emptyRunStats, errorDetails, mergeStatisticsOverviews, resolveStatisticsFile } from "./statistics.js";
13
15
  import { resolveTopicDataDir } from "./topic-storage.js";
14
16
  export const name = 'llm-verifier';
@@ -21,6 +23,8 @@ export * from "./statistics.js";
21
23
  export * from "./topic-storage.js";
22
24
  export * from "./auto.js";
23
25
  export * from "./router.js";
26
+ export * from "./plan-gate.js";
27
+ export * from "./team-gate.js";
24
28
  export { callVerifier, RequestLimiter } from "./caller.js";
25
29
  const criterionSchema = { type: 'object', additionalProperties: false, properties: { id: { type: 'string', required: true }, name: { type: 'string', required: true }, description: { type: 'string', required: true } } };
26
30
  const statsSchema = { type: 'object', additionalProperties: false, properties: { calls: { type: 'integer', required: true }, attempts: { type: 'integer', required: true }, retries: { type: 'integer', required: true }, inputTokens: { type: 'integer', required: true }, cachedInputTokens: { type: 'integer', required: true }, outputTokens: { type: 'integer', required: true }, reasoningTokens: { type: 'integer', required: true }, cacheHits: { type: 'integer', required: true }, cacheMisses: { type: 'integer', required: true }, estimatedCostUsd: { type: 'number', required: true }, topLogprobScores: { type: 'integer', required: true }, explicitTagScores: { type: 'integer', required: true } } };
@@ -116,9 +120,7 @@ export function apply(ctx, config = {}) {
116
120
  const extractTask = async (agent, fromSeq, toSeq, maxChars, signal) => extractSession(agent, async (ref) => { const stored = await services.attachments.readImage(ref, signal); return { data: stored.data, mediaType: stored.ref.mediaType }; }, { fromSeq, toSeq, includeAssistantText: true, maxChars });
117
121
  const routePolicy = (selected) => ({ mode: selected.autoVerifyMode, minConfidence: selected.autoRouteMinConfidence, maxCandidates: selected.autoRouteMaxCandidates, maxPerTask: selected.autoRouteMaxPerTask + selected.autoVerifyMaxPerTask, maxPerSession: selected.autoRouteMaxPerSession + selected.autoVerifyMaxPerSession, maxModelCallsPerTask: selected.autoMaxModelCallsPerTask, maxModelCallsPerSession: selected.autoMaxModelCallsPerSession, maxInputChars: selected.autoRouteMaxInputChars, maxItemChars: selected.autoRouteMaxItemChars });
118
122
  const routeFeedback = (decision, detail) => createUserMessage({ content: [{ type: 'text', text: '[Automatic verifier routing: ' + decision.kind + ']\n' + detail + '\nUse this independent result to continue the actual task. Do not merely restate the ranking or progress score; implement, correct, and verify the required work.' }], source: { kind: 'plugin', plugin: 'dsh-llm-verifier', form: 'notice', summary: 'Automatic verifier routed ' + decision.kind } });
119
- services.connection.rpc.handle('/llm-verifier', async (endpoint, payload) => {
120
- if (endpoint !== 'statistics')
121
- return rpcFailure('unknown llm-verifier endpoint');
123
+ const handleStatisticsQuery = async (payload) => {
122
124
  if (typeof payload !== 'object' || payload === null)
123
125
  return rpcFailure('statistics payload must be an object');
124
126
  const row = payload;
@@ -136,7 +138,103 @@ export function apply(ctx, config = {}) {
136
138
  catch (error) {
137
139
  return rpcFailure(error instanceof Error ? error.message : String(error));
138
140
  }
139
- }, { authority: 'loopback' });
141
+ };
142
+ // 1. 优先注册到官方 /api 共享通道 (connection.fetch.register)
143
+ const anyConn = services.connection;
144
+ if (anyConn && anyConn.fetch && typeof anyConn.fetch.register === 'function') {
145
+ try {
146
+ anyConn.fetch.register({
147
+ path: '/api/llm-verifier/statistics',
148
+ methods: ['POST'],
149
+ requestBody: 'buffered',
150
+ fetch: async (request) => {
151
+ let body;
152
+ try {
153
+ body = await request.json();
154
+ }
155
+ catch {
156
+ return new Response('body is not JSON', { status: 400 });
157
+ }
158
+ const isRpcEnvelope = typeof body === 'object' && body !== null && body.type === 'client-request' && typeof body.rpcId === 'string';
159
+ const rpcId = isRpcEnvelope ? body.rpcId : 'direct';
160
+ const payload = isRpcEnvelope ? body.payload : body;
161
+ const outcome = await handleStatisticsQuery(payload);
162
+ if (isRpcEnvelope) {
163
+ return Response.json({
164
+ type: 'server-response',
165
+ rpcId,
166
+ result: outcome,
167
+ });
168
+ }
169
+ return Response.json(outcome);
170
+ },
171
+ });
172
+ }
173
+ catch (e) {
174
+ ctx.logger.warn('failed to register /api/llm-verifier/statistics fetch route: ' + String(e));
175
+ }
176
+ }
177
+ // 2. 尝试向底层 webServer 挂载 /llm-verifier 前缀路由(若当前宿主提供 webServer 服务)
178
+ const mountWebServerRoute = (ws) => {
179
+ if (!ws || typeof ws.register !== 'function')
180
+ return;
181
+ try {
182
+ ws.register({
183
+ kind: 'prefix',
184
+ path: '/llm-verifier',
185
+ handler: (req, res) => {
186
+ if (req.method !== 'POST') {
187
+ res.writeHead(405);
188
+ res.end();
189
+ return;
190
+ }
191
+ let bodyStr = '';
192
+ req.on('data', (chunk) => { bodyStr += chunk.toString(); });
193
+ req.on('end', async () => {
194
+ try {
195
+ const body = JSON.parse(bodyStr);
196
+ const isRpcEnvelope = typeof body === 'object' && body !== null && body.type === 'client-request' && typeof body.rpcId === 'string';
197
+ const rpcId = isRpcEnvelope ? body.rpcId : 'direct';
198
+ const payload = isRpcEnvelope ? body.payload : body;
199
+ const outcome = await handleStatisticsQuery(payload);
200
+ res.writeHead(200, { 'content-type': 'application/json' });
201
+ if (isRpcEnvelope) {
202
+ res.end(JSON.stringify({ type: 'server-response', rpcId, result: outcome }));
203
+ }
204
+ else {
205
+ res.end(JSON.stringify(outcome));
206
+ }
207
+ }
208
+ catch (err) {
209
+ res.writeHead(400, { 'content-type': 'application/json' });
210
+ res.end(JSON.stringify({ error: String(err) }));
211
+ }
212
+ });
213
+ },
214
+ });
215
+ }
216
+ catch { }
217
+ };
218
+ if (typeof ctx.inject === 'function') {
219
+ try {
220
+ ctx.inject(['webServer'], (webCtx) => {
221
+ if (webCtx?.webServer)
222
+ mountWebServerRoute(webCtx.webServer);
223
+ });
224
+ }
225
+ catch { }
226
+ }
227
+ // 3. 保留原有 RPC channel 注册以保持纯单元测试和旧客户端兼容
228
+ try {
229
+ services.connection?.rpc?.handle('/llm-verifier', async (endpoint, payload) => {
230
+ if (endpoint !== 'statistics')
231
+ return rpcFailure('unknown llm-verifier endpoint');
232
+ return handleStatisticsQuery(payload);
233
+ }, { authority: 'loopback' });
234
+ }
235
+ catch (e) {
236
+ ctx.logger.warn('failed to register /llm-verifier rpc handler: ' + String(e));
237
+ }
140
238
  ctx.on('agent/disposed', ({ agent }) => { autoBudget.release(agent); autoRouter.release(agent); topics.delete(String(agent.id)); });
141
239
  ctx.on('agent/turn-stopping', async ({ agent, signal }) => {
142
240
  const selected = current();
@@ -147,6 +245,89 @@ export function apply(ctx, config = {}) {
147
245
  const admittedLastSeq = agent.session.events.at(-1)?.seq ?? -1;
148
246
  const snapshot = agent.session.events.filter(event => event.seq <= admittedLastSeq);
149
247
  const stillCurrent = () => !signal.aborted && (agent.session.events.at(-1)?.seq ?? -1) === admittedLastSeq;
248
+ if (selected.autoVerifyPlanMode) {
249
+ const planDetection = detectPlanExit(snapshot, evidence.taskStartSeq);
250
+ if (planDetection.hasExitPlanMode && planDetection.planText) {
251
+ const planFingerprint = stableHash({ phase: 'plan_review', callSeq: planDetection.callSeq, plan: planDetection.planText });
252
+ const planReservation = autoRouter.reserve(agent, 'plan_review', planFingerprint, 1, policy);
253
+ if (planReservation) {
254
+ try {
255
+ const extracted = await extractTask(agent, evidence.taskStartSeq, admittedLastSeq, selected.autoVerifyMaxChars, signal);
256
+ const prompt = buildPlanPreReviewPrompt(extracted.problem, planDetection.planText, selected.autoRouteMaxInputChars);
257
+ const classified = await classifyRoute(agent, prompt, signal);
258
+ if (!stillCurrent()) {
259
+ autoRouter.fail(agent, planReservation, false);
260
+ return;
261
+ }
262
+ const verdict = parsePlanReviewVerdict(classified.text);
263
+ if (verdict.score >= selected.autoVerifyThreshold) {
264
+ autoRouter.commit(agent, planReservation, admittedLastSeq);
265
+ }
266
+ else {
267
+ autoRouter.fail(agent, planReservation, selected.autoVerifyMode === 'strict');
268
+ agent.steer(createUserMessage({
269
+ content: [{
270
+ type: 'text',
271
+ text: '[Automatic Verifier Plan Pre-review]\nPlan pre-review scored ' + (verdict.score * 100).toFixed(1) + '% (Threshold: ' + (selected.autoVerifyThreshold * 100).toFixed(0) + '%).\n' + verdict.feedback + '\nAddress the identified issues or improve the plan before proceeding.'
272
+ }],
273
+ source: { kind: 'plugin', plugin: 'dsh-llm-verifier', form: 'notice', summary: 'Plan Pre-review Feedback' }
274
+ }));
275
+ return;
276
+ }
277
+ }
278
+ catch (error) {
279
+ autoRouter.fail(agent, planReservation, selected.autoVerifyMode === 'strict');
280
+ ctx.logger.warn('llm-verifier plan pre-review failed: ' + (error instanceof Error ? error.message : String(error)));
281
+ if (selected.autoVerifyMode === 'strict' && !signal.aborted) {
282
+ agent.steer(createUserMessage({ content: [{ type: 'text', text: '[Automatic Verifier Plan Pre-review]\nPlan pre-review failed: ' + (error instanceof Error ? error.message : String(error)) }], source: { kind: 'plugin', plugin: 'dsh-llm-verifier' } }));
283
+ return;
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+ if (selected.autoVerifyTeamTasks) {
290
+ const teamInspection = inspectTeamTasks(snapshot, evidence.taskStartSeq);
291
+ if (teamInspection.hasRecentCompletedTask && teamInspection.latestCompletedTask) {
292
+ const task = teamInspection.latestCompletedTask;
293
+ const taskFingerprint = stableHash({ phase: 'team_task', taskId: task.id, status: task.status, seq: teamInspection.completedSeq });
294
+ const taskReservation = autoRouter.reserve(agent, 'team_task', taskFingerprint, 1, policy);
295
+ if (taskReservation) {
296
+ try {
297
+ const extracted = await extractTask(agent, evidence.taskStartSeq, admittedLastSeq, selected.autoVerifyMaxChars, signal);
298
+ const prompt = buildTeamTaskVerificationPrompt(task, extracted.trace, selected.autoRouteMaxInputChars);
299
+ const classified = await classifyRoute(agent, prompt, signal);
300
+ if (!stillCurrent()) {
301
+ autoRouter.fail(agent, taskReservation, false);
302
+ return;
303
+ }
304
+ const verdict = parsePlanReviewVerdict(classified.text);
305
+ if (verdict.score >= selected.autoVerifyThreshold) {
306
+ autoRouter.commit(agent, taskReservation, admittedLastSeq);
307
+ }
308
+ else {
309
+ autoRouter.fail(agent, taskReservation, selected.autoVerifyMode === 'strict');
310
+ agent.steer(createUserMessage({
311
+ content: [{
312
+ type: 'text',
313
+ text: '[Automatic Verifier Team Task Gate]\nTask "' + task.subject + '" (#' + task.id + ') verification scored ' + (verdict.score * 100).toFixed(1) + '% (Threshold: ' + (selected.autoVerifyThreshold * 100).toFixed(0) + '%).\n' + verdict.feedback + '\nProvide verified execution evidence or resolve remaining issues before completing the task.'
314
+ }],
315
+ source: { kind: 'plugin', plugin: 'dsh-llm-verifier', form: 'notice', summary: 'Team Task Gate Feedback' }
316
+ }));
317
+ return;
318
+ }
319
+ }
320
+ catch (error) {
321
+ autoRouter.fail(agent, taskReservation, selected.autoVerifyMode === 'strict');
322
+ ctx.logger.warn('llm-verifier team task verification failed: ' + (error instanceof Error ? error.message : String(error)));
323
+ if (selected.autoVerifyMode === 'strict' && !signal.aborted) {
324
+ agent.steer(createUserMessage({ content: [{ type: 'text', text: '[Automatic Verifier Team Task Gate]\nTask verification failed: ' + (error instanceof Error ? error.message : String(error)) }], source: { kind: 'plugin', plugin: 'dsh-llm-verifier' } }));
325
+ return;
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
150
331
  let decision = boundDecision(analyzeStructuredRoute(snapshot, selected.autoRouteMaxCandidates, selected.autoRouteMaxItemChars), policy);
151
332
  if (decision === undefined && selected.autoRouteSemantic && (selected.autoVerifyMode === 'strict' || semanticRouteHint(snapshot))) {
152
333
  const fingerprint = stableHash({ phase: 'semantic', from: evidence.taskStartSeq, to: admittedLastSeq, model: selected.provider + '/' + selected.model });
@@ -0,0 +1,14 @@
1
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
2
+ export interface PlanModeDetection {
3
+ hasExitPlanMode: boolean;
4
+ callSeq?: number;
5
+ planText?: string;
6
+ }
7
+ export declare function detectPlanExit(events: readonly SessionEvent[], fromSeq?: number): PlanModeDetection;
8
+ export declare function buildPlanPreReviewPrompt(problem: string, planText: string, maxChars?: number): string;
9
+ export declare function parsePlanReviewVerdict(text: string): {
10
+ verdict: string;
11
+ score: number;
12
+ feedback: string;
13
+ };
14
+ //# sourceMappingURL=plan-gate.d.ts.map