synabun 2.0.10 → 2.1.0
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/CHANGELOG.md +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/exec-loop-command.js +4 -8
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +4 -4
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — evaluation formats in and out (P4, SK-086)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The suites and results other tools read and write, each read and written as its own dialect (master plan §3.6,
|
|
6
|
+
// 03a §2.2–2.5), never guessed into one:
|
|
7
|
+
//
|
|
8
|
+
// in the skill-creator's task file (`{ skill_name, evals: [{ id, prompt, expected_output, files, expectations }] }`),
|
|
9
|
+
// the Agent Skills evaluation guide's (the same with `assertions`), and the skill-creator's trigger set (a JSON
|
|
10
|
+
// array of `{ query, should_trigger }`). A case with both `assertions` and `expectations` that differ is
|
|
11
|
+
// refused (ambiguous), never merged. Duplicate requests (case, spacing and punctuation aside) become one case;
|
|
12
|
+
// the same request with two labels is refused. Each request is its own group, and the groups are split
|
|
13
|
+
// train / validation / test per stratum (task cases, positive triggers, near-misses) by a seeded shuffle, so
|
|
14
|
+
// the same file always gets the same split. The result is a synabun-evals/1 suite (ss-evals.js).
|
|
15
|
+
// out the skill-creator's task file and trigger set (the held-out cases in a file of their own: an outside
|
|
16
|
+
// optimiser must not select on them), its benchmark.json (runs[].configuration with_skill / without_skill /
|
|
17
|
+
// old_skill, a nested result, run_summary with signed string deltas, the actual repetitions and models, and
|
|
18
|
+
// null for what was not measured), a grading.json per trial (expectations with text, passed, evidence), the
|
|
19
|
+
// reviewer's feedback (`reviews[{ run_id, feedback, timestamp }]`), and the Agent Skills guide's benchmark
|
|
20
|
+
// (assertion_results, numeric deltas).
|
|
21
|
+
// No DOM.
|
|
22
|
+
|
|
23
|
+
import { EVALS_SCHEMA, EVAL_SPLITS, normalizedCaseText, validateEvals } from './ss-evals.js';
|
|
24
|
+
import { stats } from './ss-eval-grade.js';
|
|
25
|
+
|
|
26
|
+
export const IMPORT_DIALECTS = Object.freeze(['synabun-evals/1', 'skill-creator-tasks', 'agent-skills-tasks', 'skill-creator-triggers']);
|
|
27
|
+
export const SPLIT_SHARES = Object.freeze({ validation: 0.2, test: 0.2 });
|
|
28
|
+
const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
29
|
+
|
|
30
|
+
/** Which dialect a parsed JSON value is, or null. */
|
|
31
|
+
export function detectDialect(value) {
|
|
32
|
+
if (Array.isArray(value)) return value.length && value.every((x) => isObject(x) && typeof x.query === 'string' && typeof x.should_trigger === 'boolean') ? 'skill-creator-triggers' : null;
|
|
33
|
+
if (!isObject(value)) return null;
|
|
34
|
+
if (value.schema === EVALS_SCHEMA) return 'synabun-evals/1';
|
|
35
|
+
if (!Array.isArray(value.evals)) return null;
|
|
36
|
+
const hasExpectations = value.evals.some((c) => isObject(c) && Array.isArray(c.expectations));
|
|
37
|
+
const hasAssertions = value.evals.some((c) => isObject(c) && Array.isArray(c.assertions));
|
|
38
|
+
if (hasExpectations && !hasAssertions) return 'skill-creator-tasks';
|
|
39
|
+
if (hasAssertions && !hasExpectations) return 'agent-skills-tasks';
|
|
40
|
+
if (hasAssertions && hasExpectations) return 'skill-creator-tasks'; // case by case below: equal lists pass, others are refused
|
|
41
|
+
return 'agent-skills-tasks';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A lowercase slug of a text (an id from a prompt or a query), at most `max` characters. */
|
|
45
|
+
export function slugOf(text, max = 48) {
|
|
46
|
+
const s = normalizedCaseText(text).normalize('NFKD').replace(/[̀-ͯ]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, max).replace(/-+$/, '');
|
|
47
|
+
return /^[a-z0-9]/.test(s) ? s : `case-${s || 'x'}`.slice(0, max);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A seeded pseudo-random generator (mulberry32): the same seed, the same split. */
|
|
51
|
+
function seeded(seed) {
|
|
52
|
+
let a = (Number(seed) >>> 0) || 42;
|
|
53
|
+
return () => { a = (a + 0x6D2B79F5) >>> 0; let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
|
|
54
|
+
}
|
|
55
|
+
function shuffle(list, rnd) { const out = [...list]; for (let i = out.length - 1; i > 0; i--) { const j = Math.floor(rnd() * (i + 1)); [out[i], out[j]] = [out[j], out[i]]; } return out; }
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The split of each item of one stratum: one item trains; two give train and validation; three or more keep at least
|
|
59
|
+
* one of each, with about a fifth held out and a fifth for validation.
|
|
60
|
+
*/
|
|
61
|
+
export function splitStratum(ids, { seed = 42 } = {}) {
|
|
62
|
+
const order = shuffle(ids, seeded(seed));
|
|
63
|
+
const n = order.length;
|
|
64
|
+
const out = new Map();
|
|
65
|
+
if (!n) return out;
|
|
66
|
+
let test = n >= 3 ? Math.max(1, Math.round(n * SPLIT_SHARES.test)) : 0;
|
|
67
|
+
let validation = n >= 2 ? Math.max(1, Math.round(n * SPLIT_SHARES.validation)) : 0;
|
|
68
|
+
while (n - test - validation < 1) { if (test > validation && test > 0) test -= 1; else if (validation > 0) validation -= 1; else break; }
|
|
69
|
+
order.forEach((id, i) => out.set(id, i < test ? 'test' : i < test + validation ? 'validation' : 'train'));
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const textOf = (a) => (typeof a === 'string' ? a : isObject(a) && typeof a.text === 'string' ? a.text : null);
|
|
74
|
+
function sameList(a, b) { return a.length === b.length && a.every((x, i) => normalizedCaseText(textOf(x)) === normalizedCaseText(textOf(b[i]))); }
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Import other tools' suites into one synabun-evals/1 suite.
|
|
78
|
+
* @param {{ tasks?: string|object, triggers?: string|object[], skillName?: string, seed?: number }} input any of the two
|
|
79
|
+
* (text or parsed); a synabun-evals/1 suite passes through, checked
|
|
80
|
+
* @returns {{ ok: boolean, evals: object|null, dialects: string[], problems: {code, message}[], notes: string[] }}
|
|
81
|
+
*/
|
|
82
|
+
export function importEvals({ tasks = null, triggers = null, skillName = '', seed = 42 } = {}) {
|
|
83
|
+
const problems = [];
|
|
84
|
+
const notes = [];
|
|
85
|
+
const dialects = [];
|
|
86
|
+
const parse = (x, label) => {
|
|
87
|
+
if (x === null || x === undefined || x === '') return null;
|
|
88
|
+
if (typeof x !== 'string') return x;
|
|
89
|
+
try { return JSON.parse(x.replace(/^/, '')); } catch (e) { problems.push({ code: 'json', message: `${label} is not valid JSON (${e?.message || e}).` }); return undefined; }
|
|
90
|
+
};
|
|
91
|
+
const t = parse(tasks, 'The task file');
|
|
92
|
+
const g = parse(triggers, 'The trigger set');
|
|
93
|
+
if (t === undefined || g === undefined) return { ok: false, evals: null, dialects, problems, notes };
|
|
94
|
+
if (t === null && g === null) return { ok: false, evals: null, dialects, problems: [{ code: 'empty', message: 'Nothing to import: give a task file, a trigger set, or both.' }], notes };
|
|
95
|
+
// A SynaBun suite as it is (with a trigger set beside it, the trigger set is refused: one suite, one source).
|
|
96
|
+
if (t !== null && detectDialect(t) === 'synabun-evals/1') {
|
|
97
|
+
if (g !== null) return { ok: false, evals: null, dialects: ['synabun-evals/1'], problems: [{ code: 'two-suites', message: 'A SynaBun suite already has its trigger cases: import it alone.' }], notes };
|
|
98
|
+
const checked = validateEvals(t, { skillName });
|
|
99
|
+
return { ok: checked.ok, evals: checked.ok ? t : null, dialects: ['synabun-evals/1'], problems: checked.errors.map((e) => ({ code: e.code, message: `${e.path}: ${e.message}` })), notes: checked.warnings.map((w) => w.message) };
|
|
100
|
+
}
|
|
101
|
+
const taskCases = [];
|
|
102
|
+
if (t !== null) {
|
|
103
|
+
const d = detectDialect(t);
|
|
104
|
+
if (!d || d === 'skill-creator-triggers') problems.push({ code: 'dialect', message: d === 'skill-creator-triggers' ? 'The task file is a trigger set: give it as the trigger set.' : 'The task file is neither the skill-creator\'s nor the Agent Skills guide\'s format ({ "evals": [ … ] }).' });
|
|
105
|
+
else {
|
|
106
|
+
dialects.push(d);
|
|
107
|
+
t.evals.forEach((c, i) => {
|
|
108
|
+
if (!isObject(c)) { problems.push({ code: 'shape', message: `evals[${i}] is not an object.` }); return; }
|
|
109
|
+
const exp = Array.isArray(c.expectations) ? c.expectations : null;
|
|
110
|
+
const asr = Array.isArray(c.assertions) ? c.assertions : null;
|
|
111
|
+
if (exp && asr && !sameList(exp, asr)) { problems.push({ code: 'ambiguous-dialect', message: `evals[${i}] has both "assertions" and "expectations" and they differ: say which list is meant (keep one).` }); return; }
|
|
112
|
+
if (typeof c.prompt !== 'string' || !c.prompt.trim()) { problems.push({ code: 'empty', message: `evals[${i}] has no prompt.` }); return; }
|
|
113
|
+
const checks = (exp || asr || []).map(textOf);
|
|
114
|
+
if (checks.some((x) => x === null || !x.trim())) { problems.push({ code: 'assertion', message: `evals[${i}] has an empty or unreadable assertion.` }); return; }
|
|
115
|
+
const rawId = c.id === undefined || c.id === null ? '' : String(c.id);
|
|
116
|
+
taskCases.push({ rawId, prompt: c.prompt, expected: typeof c.expected_output === 'string' ? c.expected_output : '', files: Array.isArray(c.files) ? c.files.filter((f) => typeof f === 'string') : [], assertions: checks.map((x) => x.trim()) });
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const triggerCases = [];
|
|
121
|
+
if (g !== null) {
|
|
122
|
+
const d = detectDialect(g);
|
|
123
|
+
if (d !== 'skill-creator-triggers') problems.push({ code: 'dialect', message: 'The trigger set is not the skill-creator\'s format (a JSON array of { "query", "should_trigger" }).' });
|
|
124
|
+
else {
|
|
125
|
+
dialects.push(d);
|
|
126
|
+
g.forEach((c) => { if (c.query.trim()) triggerCases.push({ query: c.query, shouldTrigger: c.should_trigger }); });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (problems.length) return { ok: false, evals: null, dialects, problems, notes };
|
|
130
|
+
// One case per request: a repeat with the same label is dropped (said so); with another label, refused.
|
|
131
|
+
const seen = new Map();
|
|
132
|
+
const keep = (text, label, what) => {
|
|
133
|
+
const key = normalizedCaseText(text);
|
|
134
|
+
if (!seen.has(key)) { seen.set(key, label); return true; }
|
|
135
|
+
if (seen.get(key) !== label) problems.push({ code: 'conflicting-labels', message: `The same request appears twice with different labels: "${text.slice(0, 80)}".` });
|
|
136
|
+
else notes.push(`A repeated ${what} was kept once: "${text.slice(0, 80)}".`);
|
|
137
|
+
return false;
|
|
138
|
+
};
|
|
139
|
+
const tasksKept = taskCases.filter((c) => keep(c.prompt, 'task', 'task'));
|
|
140
|
+
const triggersKept = triggerCases.filter((c) => keep(c.query, c.shouldTrigger ? 'positive' : 'near-miss', 'trigger'));
|
|
141
|
+
if (problems.length) return { ok: false, evals: null, dialects, problems, notes };
|
|
142
|
+
// Stable ids: the source's own (as a slug) when it has one, else a slug of the text; never two the same.
|
|
143
|
+
const used = new Set();
|
|
144
|
+
const unique = (base) => { let id = base || 'case'; let i = 2; while (used.has(id)) id = `${base.slice(0, 58)}-${i++}`; used.add(id); return id; };
|
|
145
|
+
const tasksOut = tasksKept.map((c) => ({ ...c, id: unique(c.rawId ? (/^\d+$/.test(c.rawId) ? `eval-${c.rawId}` : slugOf(c.rawId)) : slugOf(c.prompt)) }));
|
|
146
|
+
const triggersOut = triggersKept.map((c) => ({ ...c, id: unique(slugOf(c.query)) }));
|
|
147
|
+
const splitOf = new Map([
|
|
148
|
+
...splitStratum(tasksOut.map((c) => c.id), { seed }),
|
|
149
|
+
...splitStratum(triggersOut.filter((c) => c.shouldTrigger).map((c) => c.id), { seed: seed + 1 }),
|
|
150
|
+
...splitStratum(triggersOut.filter((c) => !c.shouldTrigger).map((c) => c.id), { seed: seed + 2 }),
|
|
151
|
+
]);
|
|
152
|
+
const evals = {
|
|
153
|
+
schema: EVALS_SCHEMA,
|
|
154
|
+
skill_name: skillName || (isObject(t) && typeof t.skill_name === 'string' ? t.skill_name : ''),
|
|
155
|
+
evals: tasksOut.map((c) => ({ id: c.id, group: c.id, split: splitOf.get(c.id), prompt: c.prompt, expected_output: c.expected, files: c.files, assertions: c.assertions })),
|
|
156
|
+
triggers: triggersOut.map((c) => ({ id: c.id, group: c.id, split: splitOf.get(c.id), query: c.query, should_trigger: c.shouldTrigger })),
|
|
157
|
+
};
|
|
158
|
+
const checked = validateEvals(evals, { skillName: evals.skill_name });
|
|
159
|
+
if (!checked.ok) return { ok: false, evals: null, dialects, problems: checked.errors.map((e) => ({ code: e.code, message: `${e.path}: ${e.message}` })), notes };
|
|
160
|
+
notes.push(...checked.warnings.map((w) => w.message));
|
|
161
|
+
return { ok: true, evals, dialects, problems: [], notes };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A SynaBun suite in the skill-creator's files: the task file (expectations), the trigger set of the cases an
|
|
166
|
+
* optimiser may select on (train and validation), and the held-out trigger cases apart.
|
|
167
|
+
* → { tasks: { skill_name, evals }, triggerSet: [{ query, should_trigger }], heldOut: [{ query, should_trigger }] }
|
|
168
|
+
*/
|
|
169
|
+
export function exportSkillCreator(suite) {
|
|
170
|
+
const evals = Array.isArray(suite?.evals) ? suite.evals : [];
|
|
171
|
+
const triggers = Array.isArray(suite?.triggers) ? suite.triggers : [];
|
|
172
|
+
return {
|
|
173
|
+
tasks: {
|
|
174
|
+
skill_name: suite?.skill_name || '',
|
|
175
|
+
evals: evals.map((c, i) => ({ id: i + 1, prompt: c.prompt, expected_output: c.expected_output || '', files: c.files || [], expectations: (c.assertions || []).map(textOf).filter(Boolean) })),
|
|
176
|
+
},
|
|
177
|
+
triggerSet: triggers.filter((c) => c.split !== 'test').map((c) => ({ query: c.query, should_trigger: c.should_trigger })),
|
|
178
|
+
heldOut: triggers.filter((c) => c.split === 'test').map((c) => ({ query: c.query, should_trigger: c.should_trigger })),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const CONFIG_NAME = { candidate: 'with_skill' };
|
|
183
|
+
/** The skill-creator's configuration name of a trial: with_skill, and without_skill or old_skill (a revision baseline). */
|
|
184
|
+
export function configurationName(trial, run) {
|
|
185
|
+
if (trial.configuration === 'candidate') return CONFIG_NAME.candidate;
|
|
186
|
+
return run?.request?.baseline === 'revision' ? 'old_skill' : 'without_skill';
|
|
187
|
+
}
|
|
188
|
+
/** A stable id of a trial in the skill-creator's layout: never two the same (the trial id is part of it). */
|
|
189
|
+
export const exportRunId = (trial, run) => `eval-${trial.caseId}-${configurationName(trial, run)}-run-${trial.repetition}-${trial.trialId}`;
|
|
190
|
+
|
|
191
|
+
/** A trial's grading.json in the skill-creator's shape (unverifiable exported as not passed, said in the evidence). */
|
|
192
|
+
export function exportGrading(trial) {
|
|
193
|
+
const rows = (trial?.grade?.assertions || []).filter((a) => a.status !== 'error');
|
|
194
|
+
const expectations = rows.map((a) => ({ text: a.text, passed: a.status === 'passed', evidence: a.status === 'unverifiable' ? `unverifiable: ${a.evidence || ''}`.trim() : (a.evidence || '') }));
|
|
195
|
+
const passed = expectations.filter((e) => e.passed).length;
|
|
196
|
+
return { expectations, summary: { passed, failed: expectations.length - passed, total: expectations.length, pass_rate: expectations.length ? passed / expectations.length : null } };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const signed = (x, digits) => (x === null || x === undefined ? null : `${x >= 0 ? '+' : ''}${x.toFixed(digits)}`);
|
|
200
|
+
const statBlock = (s) => (s ? { mean: s.mean, stddev: s.sd, min: s.min, max: s.max } : null);
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A run in the skill-creator's benchmark.json shape: one entry per graded task trial, the actual repetitions and
|
|
204
|
+
* models, run_summary per configuration and the delta (with_skill minus the baseline configuration) as signed text.
|
|
205
|
+
* Tokens are not measured as tokens here, so they are null (never characters); a run without a baseline has no delta.
|
|
206
|
+
*/
|
|
207
|
+
export function exportBenchmark(run) {
|
|
208
|
+
const trials = (run?.trials || []).filter((t) => t.kind === 'task' && t.status === 'completed');
|
|
209
|
+
const runs = trials.map((t) => {
|
|
210
|
+
const g = exportGrading(t);
|
|
211
|
+
return {
|
|
212
|
+
eval_id: t.caseId, configuration: configurationName(t, run), run_number: t.repetition, run_id: exportRunId(t, run),
|
|
213
|
+
result: { pass_rate: g.summary.pass_rate, passed: g.summary.passed, failed: g.summary.failed, total: g.summary.total, time_seconds: Number.isFinite(t.durationMs) ? t.durationMs / 1000 : null, tokens: null, tool_calls: Array.isArray(t.toolCalls) ? t.toolCalls.length : null, errors: t.grade?.counts?.error ?? null, cost_usd: Number.isFinite(t.costUsd) ? t.costUsd : null },
|
|
214
|
+
expectations: g.expectations, notes: [],
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
const configs = [...new Set(runs.map((r) => r.configuration))];
|
|
218
|
+
const summary = {};
|
|
219
|
+
for (const c of configs) {
|
|
220
|
+
const mine = runs.filter((r) => r.configuration === c);
|
|
221
|
+
summary[c] = { pass_rate: statBlock(stats(mine.map((r) => r.result.pass_rate))), time_seconds: statBlock(stats(mine.map((r) => r.result.time_seconds))), tokens: null };
|
|
222
|
+
}
|
|
223
|
+
const base = configs.find((c) => c !== 'with_skill') || null;
|
|
224
|
+
if (summary.with_skill && base && summary[base]) {
|
|
225
|
+
const d = (k) => (summary.with_skill[k]?.mean !== undefined && summary[base][k]?.mean !== undefined ? summary.with_skill[k].mean - summary[base][k].mean : null);
|
|
226
|
+
summary.delta = { pass_rate: signed(d('pass_rate'), 2), time_seconds: signed(d('time_seconds'), 1), tokens: null, against: base };
|
|
227
|
+
}
|
|
228
|
+
const reps = [...new Set(trials.map((t) => t.repetition))].length || null;
|
|
229
|
+
return {
|
|
230
|
+
metadata: {
|
|
231
|
+
skill_name: run?.skill?.name || '', skill_path: null,
|
|
232
|
+
executor_model: [...new Set(trials.map((t) => { const r = t.actualRuntime || t.runtime; return `${r.provider}${r.model ? `/${r.model}` : ''}`; }))].join(', ') || null,
|
|
233
|
+
analyzer_model: [...new Set(trials.map((t) => t.grade?.grader).filter((r) => r?.provider).map((r) => `${r.provider}${r.model ? `/${r.model}` : ''}`))].join(', ') || null,
|
|
234
|
+
timestamp: run?.finishedAt || run?.createdAt || null,
|
|
235
|
+
evals_run: [...new Set(trials.map((t) => t.caseId))],
|
|
236
|
+
runs_per_configuration: reps,
|
|
237
|
+
synabun: { evalRunId: run?.evalRunId || null, revisionId: run?.skill?.revisionId || null, suiteDigest: run?.suite?.digest || null, experiment: run?.experiment?.digest || null, orientation: 'with_skill-minus-baseline' },
|
|
238
|
+
},
|
|
239
|
+
runs, run_summary: summary, notes: [],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** The reviewer's notes in the skill-creator's feedback.json shape: every trial, an empty note where there is none. */
|
|
244
|
+
export function exportFeedback(run) {
|
|
245
|
+
const fb = isObject(run?.feedback) ? run.feedback : {};
|
|
246
|
+
const reviews = (run?.trials || []).filter((t) => t.kind === 'task').map((t) => ({ run_id: exportRunId(t, run), feedback: fb[t.trialId]?.text || '', timestamp: fb[t.trialId]?.at || null }));
|
|
247
|
+
const reviewed = (run?.trials || []).filter((t) => t.kind === 'task').every((t) => fb[t.trialId]?.reviewed === true);
|
|
248
|
+
return { reviews, status: reviewed && reviews.length ? 'complete' : 'in_progress' };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** A run in the Agent Skills evaluation guide's shape: assertion_results per run, numeric deltas. */
|
|
252
|
+
export function exportAgentSkills(run) {
|
|
253
|
+
const trials = (run?.trials || []).filter((t) => t.kind === 'task' && t.status === 'completed');
|
|
254
|
+
const runs = trials.map((t) => ({
|
|
255
|
+
eval_id: t.caseId, configuration: configurationName(t, run), run: t.repetition,
|
|
256
|
+
assertion_results: (t.grade?.assertions || []).filter((a) => a.status !== 'error').map((a) => ({ assertion: a.text, passed: a.status === 'passed', evidence: a.evidence || '' })),
|
|
257
|
+
pass_rate: t.grade?.passRate ?? null,
|
|
258
|
+
}));
|
|
259
|
+
const rate = (c) => stats(runs.filter((r) => r.configuration === c).map((r) => r.pass_rate));
|
|
260
|
+
const w = rate('with_skill');
|
|
261
|
+
const baseName = runs.some((r) => r.configuration === 'old_skill') ? 'old_skill' : 'without_skill';
|
|
262
|
+
const b = rate(baseName);
|
|
263
|
+
return {
|
|
264
|
+
skill_name: run?.skill?.name || '', runs,
|
|
265
|
+
benchmark: { with_skill: { pass_rate: w?.mean ?? null, n: w?.n ?? 0 }, [baseName]: { pass_rate: b?.mean ?? null, n: b?.n ?? 0 }, delta: { pass_rate: w && b ? w.mean - b.mean : null } },
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { EVAL_SPLITS };
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — a skill's evaluation: grades, verdicts and what a run shows (P4)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// How a trial's answer is graded and what a run's report says, the same on the server (lib/skills/evals.js grades
|
|
6
|
+
// and keeps the record) and the page (the Test tab draws it, compares two runs). The upstream traps are handled here
|
|
7
|
+
// (03a §2.3, master plan §3.6):
|
|
8
|
+
// - an assertion's status is passed, failed, unverifiable (the grader could not tell, or passed it without evidence)
|
|
9
|
+
// or error (nothing graded it: the grader failed); a pass needs evidence, and the arithmetic is recomputed here,
|
|
10
|
+
// never taken from a grader;
|
|
11
|
+
// - a missing measurement stays null (never 0), a token count is never made from characters, and a standard
|
|
12
|
+
// deviation is the sample one over the actual n (none for n < 2);
|
|
13
|
+
// - the comparison is explicit: candidate minus baseline, paired by case and repetition, with the number of pairs;
|
|
14
|
+
// a run without a baseline shows no uplift;
|
|
15
|
+
// - an execution status (a trial that failed or timed out) is not an outcome: it is counted apart, never as a
|
|
16
|
+
// failed assertion or a trigger that did not fire.
|
|
17
|
+
// No DOM.
|
|
18
|
+
|
|
19
|
+
export const ASSERTION_STATUSES = Object.freeze(['passed', 'failed', 'unverifiable', 'error']);
|
|
20
|
+
export const TRIGGER_METHODS = Object.freeze(['native_skill_tool', 'successful_skill_file_read']);
|
|
21
|
+
export const GRADING_VERSION = 'synabun-grade/1';
|
|
22
|
+
export const TRIGGER_THRESHOLD = 0.5;
|
|
23
|
+
const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
24
|
+
const clip = (s, n) => { const t = String(s ?? ''); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
|
|
25
|
+
|
|
26
|
+
/** The line (1-based) of a character offset. */
|
|
27
|
+
function lineAt(text, index) { let n = 1; for (let i = 0; i < index && i < text.length; i++) if (text[i] === '\n') n += 1; return n; }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One deterministic check of an assertion, over the trial's answer and the files it wrote.
|
|
31
|
+
* @param {object} check ss-evals.js checkProblem()'s shape
|
|
32
|
+
* @param {{ answer: string, files: Map<string, string|null> }} ctx `files`: path → its text, null for a file that is
|
|
33
|
+
* not text; a path missing from the map was not written
|
|
34
|
+
* @param {{ regexTest?: (source, flags, text) => boolean|null }} [o] how a pattern is run (the server: with a time
|
|
35
|
+
* limit; null = it could not be run)
|
|
36
|
+
* @returns {{ status: 'passed'|'failed'|'error', evidence: string }}
|
|
37
|
+
*/
|
|
38
|
+
export function runCheck(check, ctx, { regexTest = null } = {}) {
|
|
39
|
+
const files = ctx?.files instanceof Map ? ctx.files : new Map();
|
|
40
|
+
const answer = typeof ctx?.answer === 'string' ? ctx.answer : '';
|
|
41
|
+
const where = check.in && check.in !== 'answer' ? check.in : null;
|
|
42
|
+
const target = () => {
|
|
43
|
+
if (!where) return { text: answer, label: 'the answer' };
|
|
44
|
+
if (!files.has(where)) return { missing: true, label: where };
|
|
45
|
+
const text = files.get(where);
|
|
46
|
+
return text === null ? { binary: true, label: where } : { text, label: where };
|
|
47
|
+
};
|
|
48
|
+
const fold = (s) => (check.ignoreCase ? s.toLowerCase() : s);
|
|
49
|
+
switch (check.type) {
|
|
50
|
+
case 'contains':
|
|
51
|
+
case 'not_contains': {
|
|
52
|
+
const t = target();
|
|
53
|
+
if (t.missing) return { status: check.type === 'contains' ? 'failed' : 'passed', evidence: `${t.label} was not written.` };
|
|
54
|
+
if (t.binary) return { status: 'error', evidence: `${t.label} is not text.` };
|
|
55
|
+
const at = fold(t.text).indexOf(fold(String(check.value)));
|
|
56
|
+
if (check.type === 'contains') return at >= 0 ? { status: 'passed', evidence: `"${clip(check.value, 80)}" is in ${t.label} (line ${lineAt(t.text, at)}).` } : { status: 'failed', evidence: `"${clip(check.value, 80)}" is not in ${t.label}.` };
|
|
57
|
+
return at >= 0 ? { status: 'failed', evidence: `"${clip(check.value, 80)}" is in ${t.label} (line ${lineAt(t.text, at)}).` } : { status: 'passed', evidence: `"${clip(check.value, 80)}" is not in ${t.label}.` };
|
|
58
|
+
}
|
|
59
|
+
case 'regex': {
|
|
60
|
+
const t = target();
|
|
61
|
+
if (t.missing) return { status: 'failed', evidence: `${t.label} was not written.` };
|
|
62
|
+
if (t.binary) return { status: 'error', evidence: `${t.label} is not text.` };
|
|
63
|
+
let hit = null;
|
|
64
|
+
try {
|
|
65
|
+
hit = typeof regexTest === 'function' ? regexTest(String(check.value), check.flags || '', t.text) : new RegExp(String(check.value), check.flags || '').test(t.text);
|
|
66
|
+
} catch { hit = null; }
|
|
67
|
+
if (hit === null) return { status: 'error', evidence: 'The pattern could not be run on this answer in time.' };
|
|
68
|
+
return hit ? { status: 'passed', evidence: `The pattern matches ${t.label}.` } : { status: 'failed', evidence: `The pattern does not match ${t.label}.` };
|
|
69
|
+
}
|
|
70
|
+
case 'file_exists': {
|
|
71
|
+
const path = check.path ?? check.in;
|
|
72
|
+
return files.has(path) ? { status: 'passed', evidence: `${path} was written.` } : { status: 'failed', evidence: `${path} was not written.` };
|
|
73
|
+
}
|
|
74
|
+
case 'json_valid': {
|
|
75
|
+
const path = check.path ?? check.in;
|
|
76
|
+
if (!files.has(path)) return { status: 'failed', evidence: `${path} was not written.` };
|
|
77
|
+
const text = files.get(path);
|
|
78
|
+
if (text === null) return { status: 'failed', evidence: `${path} is not text.` };
|
|
79
|
+
try { JSON.parse(String(text).replace(/^/, '')); return { status: 'passed', evidence: `${path} is valid JSON.` }; } catch (e) { return { status: 'failed', evidence: `${path} is not valid JSON (${clip(e?.message || e, 120)}).` }; }
|
|
80
|
+
}
|
|
81
|
+
case 'max_lines': {
|
|
82
|
+
const t = target();
|
|
83
|
+
if (t.missing) return { status: 'failed', evidence: `${t.label} was not written.` };
|
|
84
|
+
if (t.binary) return { status: 'error', evidence: `${t.label} is not text.` };
|
|
85
|
+
const lines = t.text.replace(/\n+$/, '').split('\n').length;
|
|
86
|
+
return lines <= check.value ? { status: 'passed', evidence: `${t.label} has ${lines} line${lines === 1 ? '' : 's'} (at most ${check.value}).` } : { status: 'failed', evidence: `${t.label} has ${lines} lines, more than ${check.value}.` };
|
|
87
|
+
}
|
|
88
|
+
default: return { status: 'error', evidence: 'Unknown check.' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The JSON a grader run answers with (its output schema): one verdict per assertion it was given, with evidence. */
|
|
93
|
+
export const GRADER_SCHEMA = Object.freeze({
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
assertions: {
|
|
97
|
+
type: 'array',
|
|
98
|
+
items: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: { id: { type: 'string' }, verdict: { type: 'string', enum: ['passed', 'failed', 'unverifiable'] }, evidence: { type: 'string' } },
|
|
101
|
+
required: ['id', 'verdict', 'evidence'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
notes: { type: 'string' },
|
|
105
|
+
},
|
|
106
|
+
required: ['assertions'],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/** The JSON a blind judge answers with: which of A and B is the better answer to the request, or a tie. */
|
|
110
|
+
export const JUDGE_SCHEMA = Object.freeze({
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: { winner: { type: 'string', enum: ['A', 'B', 'tie'] }, reasoning: { type: 'string' } },
|
|
113
|
+
required: ['winner', 'reasoning'],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A grader's answer, checked against the assertions it was given: every one answered once (a second answer for the
|
|
118
|
+
* same id is ignored), an unknown id ignored, a pass without evidence downgraded to unverifiable, a missing answer an
|
|
119
|
+
* error. → { verdicts: Map(id → { status, evidence }), problems: string[] }
|
|
120
|
+
*/
|
|
121
|
+
export function readGraderAnswer(answer, assertions) {
|
|
122
|
+
const problems = [];
|
|
123
|
+
const verdicts = new Map();
|
|
124
|
+
const wanted = new Set(assertions.map((a) => a.id));
|
|
125
|
+
const list = isObject(answer) && Array.isArray(answer.assertions) ? answer.assertions : null;
|
|
126
|
+
if (!list) problems.push('The grader gave no list of verdicts.');
|
|
127
|
+
for (const item of list || []) {
|
|
128
|
+
if (!isObject(item) || typeof item.id !== 'string') { problems.push('A verdict without an id was ignored.'); continue; }
|
|
129
|
+
if (!wanted.has(item.id)) { problems.push(`A verdict for an unknown assertion (${clip(item.id, 40)}) was ignored.`); continue; }
|
|
130
|
+
if (verdicts.has(item.id)) { problems.push(`A second verdict for ${item.id} was ignored.`); continue; }
|
|
131
|
+
const evidence = typeof item.evidence === 'string' ? clip(item.evidence.trim(), 1200) : '';
|
|
132
|
+
let status = ['passed', 'failed', 'unverifiable'].includes(item.verdict) ? item.verdict : 'unverifiable';
|
|
133
|
+
if (status === 'passed' && !evidence) { status = 'unverifiable'; problems.push(`${item.id}: passed without evidence, so counted as unverifiable.`); }
|
|
134
|
+
verdicts.set(item.id, { status, evidence });
|
|
135
|
+
}
|
|
136
|
+
for (const id of wanted) if (!verdicts.has(id)) verdicts.set(id, { status: 'error', evidence: 'The grader did not answer this assertion.' });
|
|
137
|
+
return { verdicts, problems };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* One trial's grade from its checks and its grader's verdicts. `rubric` is false when every assertion is a check.
|
|
142
|
+
* The pass rate is passed over what was graded (an error is no grade: it is counted apart); null when nothing was.
|
|
143
|
+
* → { assertions: [{ id, text, kind, required, by, status, evidence }], counts, passRate, requiredPassed, graded }
|
|
144
|
+
*/
|
|
145
|
+
export function gradeTrial(assertions, { checks = new Map(), verdicts = null, graderError = null } = {}) {
|
|
146
|
+
const out = [];
|
|
147
|
+
const counts = { passed: 0, failed: 0, unverifiable: 0, error: 0, total: assertions.length };
|
|
148
|
+
for (const a of assertions) {
|
|
149
|
+
let row;
|
|
150
|
+
if (a.check) row = checks.get(a.id) || { status: 'error', evidence: 'The check did not run.' };
|
|
151
|
+
else if (verdicts instanceof Map && verdicts.has(a.id)) row = verdicts.get(a.id);
|
|
152
|
+
else row = { status: 'error', evidence: graderError ? clip(graderError, 300) : 'No grader graded this assertion.' };
|
|
153
|
+
const status = ASSERTION_STATUSES.includes(row.status) ? row.status : 'error';
|
|
154
|
+
counts[status] += 1;
|
|
155
|
+
out.push({ id: a.id, text: a.text, kind: a.kind || 'outcome', required: a.required !== false, by: a.check ? 'check' : 'model', status, evidence: clip(row.evidence, 1200) });
|
|
156
|
+
}
|
|
157
|
+
const graded = counts.total - counts.error;
|
|
158
|
+
const requiredRows = out.filter((r) => r.required && r.status !== 'error');
|
|
159
|
+
return {
|
|
160
|
+
assertions: out, counts, graded,
|
|
161
|
+
passRate: graded > 0 ? counts.passed / graded : null,
|
|
162
|
+
requiredPassed: requiredRows.length ? requiredRows.every((r) => r.status === 'passed') : null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A trigger case's verdict over its repetitions: how often the runtime picked the skill among the trials that
|
|
168
|
+
* completed with a reading (a failed or timed-out trial, or one whose selection could not be read, is not a "no").
|
|
169
|
+
* A positive passes at a rate of `threshold` or more, a near-miss under it.
|
|
170
|
+
* → { completed, failed, triggers, rate, expected, passed, method }
|
|
171
|
+
*/
|
|
172
|
+
export function triggerVerdict(trials, { expected, threshold = TRIGGER_THRESHOLD } = {}) {
|
|
173
|
+
let completed = 0; let failed = 0; let triggers = 0;
|
|
174
|
+
const methods = new Set();
|
|
175
|
+
for (const t of trials) {
|
|
176
|
+
const observed = t?.trigger?.observed;
|
|
177
|
+
if (t?.status !== 'completed' || (observed !== true && observed !== false)) { failed += 1; continue; }
|
|
178
|
+
completed += 1;
|
|
179
|
+
if (observed) { triggers += 1; if (t.trigger.method) methods.add(t.trigger.method); }
|
|
180
|
+
}
|
|
181
|
+
const rate = completed ? triggers / completed : null;
|
|
182
|
+
const passed = rate === null ? null : (expected ? rate >= threshold : rate < threshold);
|
|
183
|
+
// The weaker evidence wins the label: a file read is an inference, a native skill call is seen.
|
|
184
|
+
const method = methods.has('successful_skill_file_read') ? 'successful_skill_file_read' : methods.has('native_skill_tool') ? 'native_skill_tool' : null;
|
|
185
|
+
return { completed, failed, triggers, rate, expected: !!expected, passed, method };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** mean, sample standard deviation (n - 1; null under 2), n, min, max of numbers (nulls left out); null for none. */
|
|
189
|
+
export function stats(values) {
|
|
190
|
+
const v = values.filter((x) => typeof x === 'number' && Number.isFinite(x));
|
|
191
|
+
if (!v.length) return null;
|
|
192
|
+
const n = v.length;
|
|
193
|
+
const mean = v.reduce((s, x) => s + x, 0) / n;
|
|
194
|
+
const sd = n >= 2 ? Math.sqrt(v.reduce((s, x) => s + (x - mean) ** 2, 0) / (n - 1)) : null;
|
|
195
|
+
return { mean, sd, n, min: Math.min(...v), max: Math.max(...v) };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const executedRuntime = (trial) => trial.actualRuntime || trial.runtime || {};
|
|
199
|
+
|
|
200
|
+
const runtimeKeyOf = (r) => `${r.provider}${r.model ? `/${r.model}` : ''}`;
|
|
201
|
+
const pct = (x) => (x === null || x === undefined ? null : Math.round(x * 1000) / 10);
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* What a run shows, per runtime: task cases with and without the skill (paired by case and repetition: candidate
|
|
205
|
+
* minus baseline), trigger queries (positives and near-misses, precision and recall), preference fidelity, cost and
|
|
206
|
+
* time; then notes about the suite (an assertion every answer passes or fails, one that does not tell the
|
|
207
|
+
* configurations apart, a case the skill made worse). `cases` is readSuite()'s list.
|
|
208
|
+
*/
|
|
209
|
+
export function summarizeRun(run, cases = []) {
|
|
210
|
+
const byId = new Map(cases.map((c) => [c.id, c]));
|
|
211
|
+
const trials = Array.isArray(run?.trials) ? run.trials : [];
|
|
212
|
+
const keys = [...new Set(trials.map((t) => runtimeKeyOf(executedRuntime(t))))];
|
|
213
|
+
const runtimes = keys.map((key) => {
|
|
214
|
+
const mine = trials.filter((t) => runtimeKeyOf(executedRuntime(t)) === key);
|
|
215
|
+
const runtime = executedRuntime(mine[0] || {});
|
|
216
|
+
const taskIds = [...new Set(mine.filter((t) => t.kind === 'task').map((t) => t.caseId))];
|
|
217
|
+
const taskCases = taskIds.map((caseId) => {
|
|
218
|
+
const all = mine.filter((t) => t.kind === 'task' && t.caseId === caseId);
|
|
219
|
+
const rate = (config) => all.filter((t) => t.configuration === config && t.status === 'completed').map((t) => t.grade?.passRate ?? null);
|
|
220
|
+
const cand = stats(rate('candidate'));
|
|
221
|
+
const base = stats(rate('baseline'));
|
|
222
|
+
// Pairs: the same repetition of both configurations, both graded.
|
|
223
|
+
const pairs = [];
|
|
224
|
+
for (const t of all.filter((x) => x.configuration === 'candidate')) {
|
|
225
|
+
const b = all.find((x) => x.configuration === 'baseline' && x.repetition === t.repetition);
|
|
226
|
+
const a1 = t.status === 'completed' ? t.grade?.passRate ?? null : null;
|
|
227
|
+
const b1 = b && b.status === 'completed' ? b.grade?.passRate ?? null : null;
|
|
228
|
+
if (a1 !== null && b1 !== null) pairs.push(a1 - b1);
|
|
229
|
+
}
|
|
230
|
+
const delta = pairs.length ? pairs.reduce((s, x) => s + x, 0) / pairs.length : null;
|
|
231
|
+
const judged = (run.comparisons || []).filter((c) => c.caseId === caseId && c.runtimeKey === key);
|
|
232
|
+
const wins = { candidate: 0, baseline: 0, tie: 0, inconclusive: 0 };
|
|
233
|
+
for (const j of judged) wins[j.winner in wins ? j.winner : 'inconclusive'] += 1;
|
|
234
|
+
const failedRuns = all.filter((t) => ['failed', 'timed_out'].includes(t.status)).length;
|
|
235
|
+
return {
|
|
236
|
+
caseId, split: all[0]?.split || byId.get(caseId)?.split || null,
|
|
237
|
+
candidate: cand, baseline: base, delta, pairs: pairs.length, failedRuns,
|
|
238
|
+
outcome: delta === null ? 'n/a' : delta > 0.0001 ? 'better' : delta < -0.0001 ? 'worse' : 'same',
|
|
239
|
+
comparison: judged.length ? wins : null,
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
const allPairs = taskCases.flatMap((c) => (c.delta === null ? [] : [{ delta: c.delta, n: c.pairs }]));
|
|
243
|
+
const candRates = mine.filter((t) => t.kind === 'task' && t.configuration === 'candidate' && t.status === 'completed').map((t) => t.grade?.passRate ?? null);
|
|
244
|
+
const baseRates = mine.filter((t) => t.kind === 'task' && t.configuration === 'baseline' && t.status === 'completed').map((t) => t.grade?.passRate ?? null);
|
|
245
|
+
const triggerIds = [...new Set(mine.filter((t) => t.kind === 'trigger').map((t) => t.caseId))];
|
|
246
|
+
const triggerCases = triggerIds.map((caseId) => {
|
|
247
|
+
const c = byId.get(caseId);
|
|
248
|
+
const all = mine.filter((t) => t.kind === 'trigger' && t.caseId === caseId);
|
|
249
|
+
const expected = c ? c.shouldTrigger : !!all[0]?.expected;
|
|
250
|
+
return { caseId, split: all[0]?.split || c?.split || null, ...triggerVerdict(all, { expected }) };
|
|
251
|
+
});
|
|
252
|
+
const pos = triggerCases.filter((c) => c.expected);
|
|
253
|
+
const neg = triggerCases.filter((c) => !c.expected);
|
|
254
|
+
const read = (list) => list.filter((c) => c.passed !== null);
|
|
255
|
+
// Precision and recall over the queries that have a reading: a positive picked is a true positive, a near-miss
|
|
256
|
+
// picked a false positive, a positive not picked a false negative.
|
|
257
|
+
const tp = read(pos).filter((c) => c.passed).length;
|
|
258
|
+
const fp = read(neg).filter((c) => !c.passed).length;
|
|
259
|
+
const fn = read(pos).filter((c) => !c.passed).length;
|
|
260
|
+
const pref = mine.filter((t) => t.kind === 'task' && t.configuration === 'candidate' && t.status === 'completed')
|
|
261
|
+
.flatMap((t) => (t.grade?.assertions || []).filter((a) => a.kind === 'preference' && a.status !== 'error'));
|
|
262
|
+
const cost = mine.reduce((s, t) => s + (Number(t.costUsd) || 0) + (Number(t.grade?.costUsd) || 0), 0);
|
|
263
|
+
const durations = mine.filter((t) => Number.isFinite(t.durationMs)).map((t) => t.durationMs);
|
|
264
|
+
return {
|
|
265
|
+
key, provider: runtime.provider, model: runtime.model || null,
|
|
266
|
+
requestedRuntimes: [...new Map(mine.map((t) => [runtimeKeyOf(t.runtime), t.runtime])).values()],
|
|
267
|
+
task: taskIds.length ? {
|
|
268
|
+
candidate: stats(candRates), baseline: stats(baseRates), cases: taskCases,
|
|
269
|
+
delta: allPairs.length ? allPairs.reduce((s, x) => s + x.delta, 0) / allPairs.length : null,
|
|
270
|
+
pairs: allPairs.reduce((s, x) => s + x.n, 0), orientation: 'candidate-minus-baseline',
|
|
271
|
+
} : null,
|
|
272
|
+
trigger: triggerIds.length ? {
|
|
273
|
+
cases: triggerCases, queries: triggerCases.length, passed: triggerCases.filter((c) => c.passed).length, unread: triggerCases.filter((c) => c.passed === null).length,
|
|
274
|
+
positives: { passed: pos.filter((c) => c.passed).length, total: pos.length }, nearMisses: { passed: neg.filter((c) => c.passed).length, total: neg.length },
|
|
275
|
+
precision: tp + fp ? tp / (tp + fp) : null, recall: tp + fn ? tp / (tp + fn) : null,
|
|
276
|
+
} : null,
|
|
277
|
+
preference: pref.length ? { passed: pref.filter((a) => a.status === 'passed').length, total: pref.length } : null,
|
|
278
|
+
costUsd: Math.round(cost * 10000) / 10000,
|
|
279
|
+
durationMs: stats(durations),
|
|
280
|
+
failedRuns: mine.filter((t) => ['failed', 'timed_out'].includes(t.status)).length,
|
|
281
|
+
skipped: mine.filter((t) => t.status === 'skipped').length,
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
return { runtimes, notes: benchmarkNotes(run), gradingVersion: GRADING_VERSION };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* What the run says about the suite itself (the skill-creator's analyzer notes, as data): an assertion every graded
|
|
289
|
+
* answer passes (it measures nothing), one every answer fails (a broken or impossible check), one that passes with
|
|
290
|
+
* and without the skill alike (it does not tell them apart), and a case where the skill did worse than no skill.
|
|
291
|
+
* → [{ code: 'always-pass'|'always-fail'|'not-discriminating'|'skill-worse', caseId, assertionId? }]
|
|
292
|
+
*/
|
|
293
|
+
export function benchmarkNotes(run) {
|
|
294
|
+
const notes = [];
|
|
295
|
+
const trials = (run?.trials || []).filter((t) => t.kind === 'task' && t.status === 'completed' && t.grade);
|
|
296
|
+
const byCase = new Map();
|
|
297
|
+
for (const t of trials) { if (!byCase.has(t.caseId)) byCase.set(t.caseId, []); byCase.get(t.caseId).push(t); }
|
|
298
|
+
for (const [caseId, list] of byCase) {
|
|
299
|
+
const ids = [...new Set(list.flatMap((t) => t.grade.assertions.map((a) => a.id)))];
|
|
300
|
+
for (const id of ids) {
|
|
301
|
+
const rows = list.map((t) => ({ config: t.configuration, a: t.grade.assertions.find((x) => x.id === id) })).filter((r) => r.a && r.a.status !== 'error' && r.a.status !== 'unverifiable');
|
|
302
|
+
if (rows.length < 2) continue;
|
|
303
|
+
const passes = rows.filter((r) => r.a.status === 'passed').length;
|
|
304
|
+
const configs = new Set(rows.map((r) => r.config));
|
|
305
|
+
if (passes === rows.length && configs.size > 1) notes.push({ code: 'not-discriminating', caseId, assertionId: id });
|
|
306
|
+
else if (passes === rows.length) notes.push({ code: 'always-pass', caseId, assertionId: id });
|
|
307
|
+
else if (passes === 0) notes.push({ code: 'always-fail', caseId, assertionId: id });
|
|
308
|
+
}
|
|
309
|
+
const rate = (config) => stats(list.filter((t) => t.configuration === config).map((t) => t.grade.passRate));
|
|
310
|
+
const c = rate('candidate'); const b = rate('baseline');
|
|
311
|
+
if (c && b && c.mean < b.mean) notes.push({ code: 'skill-worse', caseId });
|
|
312
|
+
}
|
|
313
|
+
return notes.slice(0, 50);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Two runs of a skill side by side, case by case (the same runtime key in both): the change of each case's pass rate
|
|
318
|
+
* or trigger reading. A case only one of them ran is listed with the other side empty. Says whether the two ran the
|
|
319
|
+
* same suite and the same revision (otherwise a change can come from the cases, not the skill).
|
|
320
|
+
*/
|
|
321
|
+
export function compareRuns(a, b, cases = []) {
|
|
322
|
+
const sa = summarizeRun(a, cases);
|
|
323
|
+
const sb = summarizeRun(b, cases);
|
|
324
|
+
const rows = [];
|
|
325
|
+
const keys = [...new Set([...sa.runtimes.map((r) => r.key), ...sb.runtimes.map((r) => r.key)])];
|
|
326
|
+
for (const key of keys) {
|
|
327
|
+
const ra = sa.runtimes.find((r) => r.key === key) || null;
|
|
328
|
+
const rb = sb.runtimes.find((r) => r.key === key) || null;
|
|
329
|
+
const taskIds = [...new Set([...(ra?.task?.cases || []), ...(rb?.task?.cases || [])].map((c) => c.caseId))];
|
|
330
|
+
for (const caseId of taskIds) {
|
|
331
|
+
const ca = ra?.task?.cases.find((c) => c.caseId === caseId) || null;
|
|
332
|
+
const cb = rb?.task?.cases.find((c) => c.caseId === caseId) || null;
|
|
333
|
+
const va = ca?.candidate?.mean ?? null; const vb = cb?.candidate?.mean ?? null;
|
|
334
|
+
rows.push({ key, kind: 'task', caseId, a: va, b: vb, change: va !== null && vb !== null ? vb - va : null });
|
|
335
|
+
}
|
|
336
|
+
const triggerIds = [...new Set([...(ra?.trigger?.cases || []), ...(rb?.trigger?.cases || [])].map((c) => c.caseId))];
|
|
337
|
+
for (const caseId of triggerIds) {
|
|
338
|
+
const ca = ra?.trigger?.cases.find((c) => c.caseId === caseId) || null;
|
|
339
|
+
const cb = rb?.trigger?.cases.find((c) => c.caseId === caseId) || null;
|
|
340
|
+
rows.push({ key, kind: 'trigger', caseId, a: ca?.passed ?? null, b: cb?.passed ?? null, rateA: ca?.rate ?? null, rateB: cb?.rate ?? null, change: ca?.passed === null || cb?.passed === null || !ca || !cb ? null : Number(cb.passed) - Number(ca.passed) });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
rows, sameSuite: !!a?.suite?.digest && a.suite.digest === b?.suite?.digest, sameRevision: !!a?.skill?.revisionId && a.skill.revisionId === b?.skill?.revisionId,
|
|
345
|
+
sameExperiment: !!a?.experiment?.digest && a.experiment.digest === b?.experiment?.digest,
|
|
346
|
+
summary: { a: sa, b: sb },
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Why a run's result no longer describes the skill as it is now, or []: its revision is not the latest, its suite changed. */
|
|
351
|
+
export function staleness(run, { latestRevisionId = null, latestSuiteDigest = null } = {}) {
|
|
352
|
+
const out = [];
|
|
353
|
+
if (latestRevisionId && run?.skill?.revisionId && run.skill.revisionId !== latestRevisionId) out.push('revision');
|
|
354
|
+
if (latestSuiteDigest && run?.suite?.digest && run.suite.digest !== latestSuiteDigest) out.push('suite');
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** A pass rate as a percentage with one decimal (null stays null). */
|
|
359
|
+
export const percent = pct;
|