@mastra/code-sdk 1.0.3-alpha.1 → 1.1.0-alpha.2
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 +38 -0
- package/dist/acp/agent.js +155 -167
- package/dist/acp/agent.js.map +1 -1
- package/dist/acp/event-mapper.js +193 -168
- package/dist/acp/event-mapper.js.map +1 -1
- package/dist/acp/index.js +60 -49
- package/dist/acp/index.js.map +1 -1
- package/dist/acp/server.js +31 -30
- package/dist/acp/server.js.map +1 -1
- package/dist/acp.js +1 -4
- package/dist/agents/credential-resolver.js +40 -29
- package/dist/agents/credential-resolver.js.map +1 -1
- package/dist/agents/custom-provider-source.js +16 -9
- package/dist/agents/custom-provider-source.js.map +1 -1
- package/dist/agents/instructions.js +27 -36
- package/dist/agents/instructions.js.map +1 -1
- package/dist/agents/mastracode-gateway.js +355 -428
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/memory.js +87 -78
- package/dist/agents/memory.js.map +1 -1
- package/dist/agents/model.js +114 -119
- package/dist/agents/model.js.map +1 -1
- package/dist/agents/modes/build.js +12 -13
- package/dist/agents/modes/build.js.map +1 -1
- package/dist/agents/modes/explore.js +14 -13
- package/dist/agents/modes/explore.js.map +1 -1
- package/dist/agents/modes/plan.js +14 -15
- package/dist/agents/modes/plan.js.map +1 -1
- package/dist/agents/prompts/agent-instructions.js +78 -69
- package/dist/agents/prompts/agent-instructions.js.map +1 -1
- package/dist/agents/prompts/build.js +19 -16
- package/dist/agents/prompts/build.js.map +1 -1
- package/dist/agents/prompts/fast.js +8 -4
- package/dist/agents/prompts/fast.js.map +1 -1
- package/dist/agents/prompts/index.js +48 -47
- package/dist/agents/prompts/index.js.map +1 -1
- package/dist/agents/prompts/model.js +6 -5
- package/dist/agents/prompts/model.js.map +1 -1
- package/dist/agents/prompts/plan.js +20 -16
- package/dist/agents/prompts/plan.js.map +1 -1
- package/dist/agents/prompts/tool-guidance.js +77 -109
- package/dist/agents/prompts/tool-guidance.js.map +1 -1
- package/dist/agents/sandbox-filesystem.js +245 -259
- package/dist/agents/sandbox-filesystem.js.map +1 -1
- package/dist/agents/sandbox-reattach.js +9 -11
- package/dist/agents/sandbox-reattach.js.map +1 -1
- package/dist/agents/thread-caveman-state.js +92 -81
- package/dist/agents/thread-caveman-state.js.map +1 -1
- package/dist/agents/tool-availability.js +62 -80
- package/dist/agents/tool-availability.js.map +1 -1
- package/dist/agents/tools.js +99 -139
- package/dist/agents/tools.js.map +1 -1
- package/dist/agents/workspace.js +232 -225
- package/dist/agents/workspace.js.map +1 -1
- package/dist/analytics.js +139 -128
- package/dist/analytics.js.map +1 -1
- package/dist/auth/authorization-input.js +39 -25
- package/dist/auth/authorization-input.js.map +1 -1
- package/dist/auth/device-code.js +112 -80
- package/dist/auth/device-code.js.map +1 -1
- package/dist/auth/index.js +2 -9
- package/dist/auth/pkce.js +27 -16
- package/dist/auth/pkce.js.map +1 -1
- package/dist/auth/providers/anthropic.js +107 -93
- package/dist/auth/providers/anthropic.js.map +1 -1
- package/dist/auth/providers/github-copilot.js +304 -293
- package/dist/auth/providers/github-copilot.js.map +1 -1
- package/dist/auth/providers/openai-codex.js +442 -460
- package/dist/auth/providers/openai-codex.js.map +1 -1
- package/dist/auth/providers/xai.js +179 -145
- package/dist/auth/providers/xai.js.map +1 -1
- package/dist/auth/storage.js +182 -175
- package/dist/auth/storage.js.map +1 -1
- package/dist/auth/types.js +1 -1
- package/dist/clipboard/index.js +206 -172
- package/dist/clipboard/index.js.map +1 -1
- package/dist/constants.js +11 -16
- package/dist/constants.js.map +1 -1
- package/dist/error-classification.js +19 -11
- package/dist/error-classification.js.map +1 -1
- package/dist/evals/context-builder.js +145 -104
- package/dist/evals/context-builder.js.map +1 -1
- package/dist/evals/index.js +4 -7
- package/dist/evals/scorers/classify-command.js +62 -41
- package/dist/evals/scorers/classify-command.js.map +1 -1
- package/dist/evals/scorers/efficiency.js +239 -181
- package/dist/evals/scorers/efficiency.js.map +1 -1
- package/dist/evals/scorers/extract-tools.js +51 -47
- package/dist/evals/scorers/extract-tools.js.map +1 -1
- package/dist/evals/scorers/index.js +1 -5
- package/dist/evals/scorers/outcome.js +321 -212
- package/dist/evals/scorers/outcome.js.map +1 -1
- package/dist/goal-manager.js +266 -261
- package/dist/goal-manager.js.map +1 -1
- package/dist/goal-signal.js +14 -13
- package/dist/goal-signal.js.map +1 -1
- package/dist/headless/cli.js +153 -154
- package/dist/headless/cli.js.map +1 -1
- package/dist/headless/flags.js +180 -178
- package/dist/headless/flags.js.map +1 -1
- package/dist/headless/format.js +72 -71
- package/dist/headless/format.js.map +1 -1
- package/dist/headless/index.js +5 -31
- package/dist/headless/policy.js +35 -31
- package/dist/headless/policy.js.map +1 -1
- package/dist/headless/run-mc.js +332 -334
- package/dist/headless/run-mc.js.map +1 -1
- package/dist/headless/types.js +16 -7
- package/dist/headless/types.js.map +1 -1
- package/dist/hooks/config.js +56 -61
- package/dist/hooks/config.js.map +1 -1
- package/dist/hooks/executor.js +118 -116
- package/dist/hooks/executor.js.map +1 -1
- package/dist/hooks/index.js +4 -15
- package/dist/hooks/manager.js +221 -280
- package/dist/hooks/manager.js.map +1 -1
- package/dist/hooks/types.js +5 -4
- package/dist/hooks/types.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +632 -712
- package/dist/index.js.map +1 -1
- package/dist/ipc/ipc-reporter.js +19 -22
- package/dist/ipc/ipc-reporter.js.map +1 -1
- package/dist/lsp/client.js +248 -306
- package/dist/lsp/client.js.map +1 -1
- package/dist/lsp/index.js +1 -6
- package/dist/lsp/language.js +39 -43
- package/dist/lsp/language.js.map +1 -1
- package/dist/lsp/manager.js +78 -93
- package/dist/lsp/manager.js.map +1 -1
- package/dist/lsp/server.js +151 -120
- package/dist/lsp/server.js.map +1 -1
- package/dist/lsp/workspace.js +50 -47
- package/dist/lsp/workspace.js.map +1 -1
- package/dist/mcp/config.js +210 -199
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/index.js +2 -9
- package/dist/mcp/manager.js +482 -475
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/types.js +1 -1
- package/dist/onboarding/custom-packs.js +14 -17
- package/dist/onboarding/custom-packs.js.map +1 -1
- package/dist/onboarding/custom-providers.js +22 -20
- package/dist/onboarding/custom-providers.js.map +1 -1
- package/dist/onboarding/index.d.ts +1 -1
- package/dist/onboarding/index.d.ts.map +1 -1
- package/dist/onboarding/index.js +3 -21
- package/dist/onboarding/om-settings.d.ts +5 -0
- package/dist/onboarding/om-settings.d.ts.map +1 -1
- package/dist/onboarding/om-settings.js +41 -20
- package/dist/onboarding/om-settings.js.map +1 -1
- package/dist/onboarding/packs.d.ts +4 -6
- package/dist/onboarding/packs.d.ts.map +1 -1
- package/dist/onboarding/packs.js +154 -105
- package/dist/onboarding/packs.js.map +1 -1
- package/dist/onboarding/settings.js +575 -517
- package/dist/onboarding/settings.js.map +1 -1
- package/dist/permissions.js +112 -99
- package/dist/permissions.js.map +1 -1
- package/dist/plugin.js +21 -23
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/dependencies.js +64 -87
- package/dist/plugins/dependencies.js.map +1 -1
- package/dist/plugins/install.js +159 -169
- package/dist/plugins/install.js.map +1 -1
- package/dist/plugins/loader.js +170 -185
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/manager.js +374 -380
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/manifest.js +38 -63
- package/dist/plugins/manifest.js.map +1 -1
- package/dist/plugins/package-link.js +48 -64
- package/dist/plugins/package-link.js.map +1 -1
- package/dist/plugins/paths.js +18 -19
- package/dist/plugins/paths.js.map +1 -1
- package/dist/plugins/registry.js +97 -81
- package/dist/plugins/registry.js.map +1 -1
- package/dist/plugins/scaffold.js +62 -98
- package/dist/plugins/scaffold.js.map +1 -1
- package/dist/plugins/types.js +1 -1
- package/dist/processors/plan-rejection-abort.js +45 -44
- package/dist/processors/plan-rejection-abort.js.map +1 -1
- package/dist/providers/amazon-bedrock-gateway.js +134 -125
- package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
- package/dist/providers/amazon-bedrock.js +68 -47
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/claude-max.js +153 -133
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/github-copilot.js +232 -230
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/openai-codex.js +271 -260
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/xai.js +57 -50
- package/dist/providers/xai.js.map +1 -1
- package/dist/schema.js +91 -89
- package/dist/schema.js.map +1 -1
- package/dist/theme-palette.js +18 -14
- package/dist/theme-palette.js.map +1 -1
- package/dist/tool-names.js +46 -37
- package/dist/tool-names.js.map +1 -1
- package/dist/tools/index.js +2 -8
- package/dist/tools/request-sandbox-access.js +71 -80
- package/dist/tools/request-sandbox-access.js.map +1 -1
- package/dist/tools/utils.js +26 -16
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/web-search.js +51 -55
- package/dist/tools/web-search.js.map +1 -1
- package/dist/utils/binaries.js +65 -61
- package/dist/utils/binaries.js.map +1 -1
- package/dist/utils/debug-log.js +51 -47
- package/dist/utils/debug-log.js.map +1 -1
- package/dist/utils/errors.js +157 -192
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/gateway-sync.js +49 -37
- package/dist/utils/gateway-sync.js.map +1 -1
- package/dist/utils/path-security.js +7 -6
- package/dist/utils/path-security.js.map +1 -1
- package/dist/utils/plan-diff.js +58 -39
- package/dist/utils/plan-diff.js.map +1 -1
- package/dist/utils/plans.js +92 -74
- package/dist/utils/plans.js.map +1 -1
- package/dist/utils/project.js +318 -256
- package/dist/utils/project.js.map +1 -1
- package/dist/utils/signals-pubsub.js +117 -99
- package/dist/utils/signals-pubsub.js.map +1 -1
- package/dist/utils/slash-command-loader.js +118 -154
- package/dist/utils/slash-command-loader.js.map +1 -1
- package/dist/utils/slash-command-processor.js +92 -83
- package/dist/utils/slash-command-processor.js.map +1 -1
- package/dist/utils/stdin-pipe.js +87 -63
- package/dist/utils/stdin-pipe.js.map +1 -1
- package/dist/utils/storage-factory.js +104 -84
- package/dist/utils/storage-factory.js.map +1 -1
- package/dist/utils/storage-maintenance.js +249 -211
- package/dist/utils/storage-maintenance.js.map +1 -1
- package/dist/utils/thread-lock.js +96 -92
- package/dist/utils/thread-lock.js.map +1 -1
- package/dist/utils/token-estimator.js +12 -12
- package/dist/utils/token-estimator.js.map +1 -1
- package/dist/utils/update-check.d.ts +1 -1
- package/dist/utils/update-check.js +341 -259
- package/dist/utils/update-check.js.map +1 -1
- package/dist/voice/stt-registry.js +131 -113
- package/dist/voice/stt-registry.js.map +1 -1
- package/package.json +12 -12
- package/dist/acp.js.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/evals/index.js.map +0 -1
- package/dist/evals/scorers/index.js.map +0 -1
- package/dist/headless/index.js.map +0 -1
- package/dist/hooks/index.js.map +0 -1
- package/dist/lsp/index.js.map +0 -1
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/types.js.map +0 -1
- package/dist/onboarding/index.js.map +0 -1
- package/dist/plugins/types.js.map +0 -1
- package/dist/tools/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/evals/scorers/outcome.ts"],"sourcesContent":["/**\n * MastraCode Outcome Scorer (Code-based, Agent type, always-on)\n *\n * Grades the objective outcome of a coding session by checking hard facts:\n * - Did the build/typecheck pass?\n * - Did tests pass?\n * - What was the tool error rate?\n * - Did the agent get stuck in a loop?\n * - Was there a regression (build passed earlier, failed later)?\n * - Did the agent use ask_user unnecessarily? (autonomy)\n *\n * This scorer is always-on (no sampling) because it's cheap — it only\n * reads exit codes and error states from tool results already in the messages.\n *\n * Scoring philosophy (from Anthropic's eval guidance):\n * \"Grade outcomes, not paths\" — we check what happened, not how.\n * Partial credit: a session that passes build but fails tests scores\n * higher than one that fails build entirely.\n *\n * Dimensions that don't apply to a session (e.g. build for a docs-only task)\n * are excluded from the weighted average, so sessions aren't penalized for\n * irrelevant criteria.\n */\n\nimport type { MastraDBMessage } from '@mastra/core/agent';\nimport { createScorer, filterRun } from '@mastra/core/evals';\n\nimport { isBuildCommand, isTestCommand, getExitCode } from './classify-command.js';\nimport type { ExtractedToolCall } from './extract-tools.js';\nimport { extractToolCalls } from './extract-tools.js';\n\n// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n// CONFIGURATION — Adjust weights and thresholds here\n// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n/**\n * Dimension weights. Used to compute the weighted average of applicable dimensions.\n * Dimensions that don't apply (e.g. no build ran) are excluded from the average.\n */\nconst WEIGHTS = {\n build: 0.3,\n tests: 0.25,\n toolErrors: 0.2,\n loops: 0.1,\n regression: 0.1,\n autonomy: 0.05,\n} as const;\n\n/**\n * Scoring thresholds and multipliers.\n */\nconst THRESHOLDS = {\n /** Multiplier for tool error rate penalty. Score = max(0, 1 - rate × this). */\n toolErrorPenaltyMultiplier: 1.0,\n\n /** How many consecutive identical calls (or same-error repeats) before it's a \"loop\". */\n loopMinRepetitions: 3,\n\n /** Score penalty per detected loop. */\n loopPenaltyPerOccurrence: 0.3,\n\n /** Score deducted per ask_user call. */\n autonomyPenaltyPerAsk: 0.25,\n\n /** Score when a command ran but exit code is ambiguous. */\n ambiguousExitScore: 0.75,\n\n /** Minimum tool calls required to produce a meaningful score. */\n minToolCalls: 1,\n\n /** Tool names whose errors are expected/benign (not penalized). */\n benignErrorTools: ['search_content', 'find_files', 'lsp_inspect'] as string[],\n} as const;\n\n// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\ntype DimensionResult = { score: number; detail: string; applicable: boolean };\n\n/**\n * Check if the primary verb of an execute_command is a build/typecheck command.\n * Splits compound commands (&&, ;) and checks the first segment.\n */\n/** Extract command string from tool call args, then delegate to shared classifier. */\nfunction isBuildArgs(args: Record<string, unknown>): boolean {\n return isBuildCommand(String(args.command ?? ''));\n}\n\nfunction isTestArgs(args: Record<string, unknown>): boolean {\n return isTestCommand(String(args.command ?? ''));\n}\n\n/**\n * Score build/typecheck outcome (final state).\n */\nfunction scoreBuild(execResults: ExtractedToolCall[]): DimensionResult {\n const builds = execResults.filter(r => r.toolName === 'execute_command' && isBuildArgs(r.args));\n if (builds.length === 0) return { score: 0, detail: 'No build/typecheck ran', applicable: false };\n\n const last = builds[builds.length - 1]!;\n if (last.isError) return { score: 0, detail: 'Build/typecheck tool errored', applicable: true };\n\n const exitCode = getExitCode(last.result);\n if (exitCode === 0) return { score: 1, detail: 'Build/typecheck passed', applicable: true };\n if (exitCode !== null && exitCode !== 0)\n return { score: 0, detail: `Build failed (exit ${exitCode})`, applicable: true };\n\n // Fallback: check result text for TS errors\n const text = typeof last.result === 'string' ? last.result : JSON.stringify(last.result ?? '');\n if (/error TS\\d+|Cannot find module|is not assignable/i.test(text)) {\n return { score: 0, detail: 'Build failed (TypeScript errors in output)', applicable: true };\n }\n\n return { score: THRESHOLDS.ambiguousExitScore, detail: 'Build ran, outcome unclear', applicable: true };\n}\n\n/**\n * Score test outcome (final state).\n */\nfunction scoreTests(execResults: ExtractedToolCall[]): DimensionResult {\n const tests = execResults.filter(r => r.toolName === 'execute_command' && isTestArgs(r.args));\n if (tests.length === 0) return { score: 0, detail: 'No tests ran', applicable: false };\n\n const last = tests[tests.length - 1]!;\n if (last.isError) return { score: 0, detail: 'Test command errored', applicable: true };\n\n const exitCode = getExitCode(last.result);\n if (exitCode === 0) return { score: 1, detail: 'Tests passed', applicable: true };\n if (exitCode !== null && exitCode !== 0)\n return { score: 0, detail: `Tests failed (exit ${exitCode})`, applicable: true };\n\n const text = typeof last.result === 'string' ? last.result : JSON.stringify(last.result ?? '');\n if (/\\d+ (?:tests? )?passed|✓|PASS/i.test(text) && !/\\bfail(?:ed|ure)?\\b|\\berror\\b/i.test(text)) {\n return { score: 1, detail: 'Tests passed (inferred)', applicable: true };\n }\n if (/\\bFAIL\\b|failed|✗|✘/i.test(text)) {\n return { score: 0, detail: 'Tests failed (inferred)', applicable: true };\n }\n\n return { score: THRESHOLDS.ambiguousExitScore, detail: 'Tests ran, outcome unclear', applicable: true };\n}\n\n/**\n * Score tool error rate, excluding benign errors from expected-to-fail tools.\n */\nfunction scoreToolErrors(results: ExtractedToolCall[]): DimensionResult {\n if (results.length === 0) return { score: 1, detail: 'No tool calls', applicable: false };\n\n // Only count errors from non-benign tools\n const nonBenign = results.filter(r => !THRESHOLDS.benignErrorTools.includes(r.toolName));\n if (nonBenign.length === 0) return { score: 1, detail: 'All tool calls are benign-error tools', applicable: true };\n\n const errors = nonBenign.filter(r => r.isError);\n const rate = errors.length / nonBenign.length;\n\n if (rate === 0) return { score: 1, detail: 'No tool errors', applicable: true };\n\n const score = Math.max(0, 1 - rate * THRESHOLDS.toolErrorPenaltyMultiplier);\n return {\n score,\n detail: `${errors.length}/${nonBenign.length} tools errored (${(rate * 100).toFixed(0)}%)`,\n applicable: true,\n };\n}\n\n/**\n * Detect stuck loops: same tool + same args appearing 3+ times consecutively,\n * or same tool + same args erroring 3+ times total.\n */\nfunction scoreStuckLoops(results: ExtractedToolCall[]): DimensionResult {\n if (results.length < 3) return { score: 1, detail: 'Too few calls for loop detection', applicable: true };\n\n const fingerprint = (r: ExtractedToolCall) => `${r.toolName}:${JSON.stringify(r.args)}`;\n\n // Check consecutive identical calls\n let maxConsecutive = 1;\n let currentRun = 1;\n for (let i = 1; i < results.length; i++) {\n if (fingerprint(results[i]!) === fingerprint(results[i - 1]!)) {\n currentRun++;\n maxConsecutive = Math.max(maxConsecutive, currentRun);\n } else {\n currentRun = 1;\n }\n }\n\n // Check repeated error fingerprints\n const errorCounts = new Map<string, number>();\n for (const r of results) {\n if (!r.isError) continue;\n const fp = fingerprint(r);\n errorCounts.set(fp, (errorCounts.get(fp) ?? 0) + 1);\n }\n const maxErrorRepeat = Math.max(0, ...[...errorCounts.values()]);\n\n const minReps = THRESHOLDS.loopMinRepetitions;\n const loopSeverity = Math.max(\n maxConsecutive >= minReps ? maxConsecutive - (minReps - 1) : 0,\n maxErrorRepeat >= minReps ? maxErrorRepeat - (minReps - 1) : 0,\n );\n\n if (loopSeverity === 0) return { score: 1, detail: 'No stuck loops', applicable: true };\n\n const score = Math.max(0, 1 - loopSeverity * THRESHOLDS.loopPenaltyPerOccurrence);\n const details: string[] = [];\n if (maxConsecutive >= minReps) details.push(`${maxConsecutive} consecutive identical calls`);\n if (maxErrorRepeat >= minReps) details.push(`same error repeated ${maxErrorRepeat}x`);\n return { score, detail: details.join('; '), applicable: true };\n}\n\n/**\n * Detect regressions: build/test passed at some point, then failed later.\n * Gives partial credit if the agent recovered (final state passes).\n */\nfunction scoreRegression(execResults: ExtractedToolCall[]): DimensionResult {\n const regressions: { label: string; recovered: boolean }[] = [];\n\n for (const [label, filter] of [\n ['Build', isBuildArgs],\n ['Tests', isTestArgs],\n ] as const) {\n const cmds = execResults.filter(r => r.toolName === 'execute_command' && filter(r.args));\n if (cmds.length < 2) continue;\n\n let sawPass = false;\n let sawRegression = false;\n for (const cmd of cmds) {\n const exit = getExitCode(cmd.result);\n if (exit === 0) {\n if (sawRegression) {\n // Recovered from regression\n regressions.push({ label, recovered: true });\n sawRegression = false;\n }\n sawPass = true;\n }\n if (sawPass && exit !== null && exit !== 0) {\n sawRegression = true;\n }\n }\n\n if (sawRegression) {\n // Regression persisted at end of session\n regressions.push({ label, recovered: false });\n }\n }\n\n if (regressions.length === 0) return { score: 1, detail: 'No regressions', applicable: true };\n\n // Persistent regressions are severe (0), recovered regressions get partial credit (0.5)\n const persistentCount = regressions.filter(r => !r.recovered).length;\n const recoveredCount = regressions.filter(r => r.recovered).length;\n const score = Math.max(0, 1 - persistentCount * 0.5 - recoveredCount * 0.1);\n const details = regressions.map(\n r => `${r.label} ${r.recovered ? 'regressed then recovered' : 'regressed (persisted)'}`,\n );\n return { score, detail: details.join('; '), applicable: true };\n}\n\n/**\n * Score autonomy: penalize ask_user calls.\n * 0 calls = 1.0, each call reduces by 0.25 (up to a floor of 0).\n */\nfunction scoreAutonomy(results: ExtractedToolCall[]): DimensionResult {\n const askUserCalls = results.filter(r => r.toolName === 'ask_user');\n const count = askUserCalls.length;\n\n if (count === 0) return { score: 1, detail: 'No ask_user calls (fully autonomous)', applicable: true };\n\n const score = Math.max(0, 1 - count * THRESHOLDS.autonomyPenaltyPerAsk);\n return { score, detail: `${count} ask_user call${count > 1 ? 's' : ''}`, applicable: true };\n}\n\n/**\n * Compute weighted average of only applicable dimensions.\n * Returns null if no dimensions are applicable.\n */\nfunction weightedAverage(dimensions: Record<string, DimensionResult>, weights: Record<string, number>): number | null {\n let totalWeight = 0;\n let weightedSum = 0;\n\n for (const [key, result] of Object.entries(dimensions)) {\n if (!result.applicable) continue;\n const weight = weights[key] ?? 0;\n totalWeight += weight;\n weightedSum += result.score * weight;\n }\n\n if (totalWeight === 0) return null;\n return weightedSum / totalWeight;\n}\n\nexport function createOutcomeScorer() {\n return createScorer({\n id: 'mastracode-outcome',\n name: 'MastraCode Outcome',\n description:\n 'Grades coding session outcomes: build/test pass, tool errors, stuck loops, regressions, autonomy. Always-on.',\n type: 'agent',\n prepareRun: filterRun({ partTypes: ['tool-invocation', 'text'] }),\n })\n .preprocess(async ({ run }) => {\n const messages = (run.output ?? []) as MastraDBMessage[];\n const allResults = extractToolCalls(messages);\n const totalCalls = allResults.length;\n\n // H1: Empty sessions or sessions with no tool calls → score 0\n if (totalCalls < THRESHOLDS.minToolCalls) {\n const empty: DimensionResult = { score: 0, detail: 'No tool calls', applicable: false };\n return {\n empty: true as const,\n build: empty,\n tests: empty,\n toolErrors: empty,\n loops: empty,\n regression: empty,\n autonomy: empty,\n totalCalls,\n };\n }\n\n const execResults = allResults.filter(r => r.toolName === 'execute_command');\n\n return {\n empty: false as const,\n build: scoreBuild(execResults),\n tests: scoreTests(execResults),\n toolErrors: scoreToolErrors(allResults),\n loops: scoreStuckLoops(allResults),\n regression: scoreRegression(execResults),\n autonomy: scoreAutonomy(allResults),\n totalCalls,\n };\n })\n .generateScore(({ results }) => {\n const p = results.preprocessStepResult;\n if (p.empty) return 0;\n\n const score = weightedAverage(\n {\n build: p.build,\n tests: p.tests,\n toolErrors: p.toolErrors,\n loops: p.loops,\n regression: p.regression,\n autonomy: p.autonomy,\n },\n WEIGHTS,\n );\n\n return score === null ? 0 : Math.round(score * 100) / 100;\n })\n .generateReason(({ results, score }) => {\n const p = results.preprocessStepResult;\n if (p.empty) {\n return `Score: 0 (${p.totalCalls} tool calls — below minimum threshold for scoring)`;\n }\n\n const pct = (w: number) => `${(w * 100).toFixed(0)}%`;\n const dimLine = (name: string, weight: number, r: DimensionResult) =>\n r.applicable\n ? `${name} (${pct(weight)}): ${r.detail} [${r.score}]`\n : `${name}: ${r.detail} [N/A — excluded from average]`;\n\n const parts: string[] = [`Score: ${score} (${p.totalCalls} tool calls total)`];\n parts.push(dimLine('Build', WEIGHTS.build, p.build));\n parts.push(dimLine('Tests', WEIGHTS.tests, p.tests));\n parts.push(dimLine('Tool errors', WEIGHTS.toolErrors, p.toolErrors));\n parts.push(dimLine('Loops', WEIGHTS.loops, p.loops));\n parts.push(dimLine('Regression', WEIGHTS.regression, p.regression));\n parts.push(dimLine('Autonomy', WEIGHTS.autonomy, p.autonomy));\n\n return parts.join('\\n');\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"file":"outcome.js","names":[],"sources":["../../../src/evals/scorers/outcome.ts"],"sourcesContent":["/**\n * MastraCode Outcome Scorer (Code-based, Agent type, always-on)\n *\n * Grades the objective outcome of a coding session by checking hard facts:\n * - Did the build/typecheck pass?\n * - Did tests pass?\n * - What was the tool error rate?\n * - Did the agent get stuck in a loop?\n * - Was there a regression (build passed earlier, failed later)?\n * - Did the agent use ask_user unnecessarily? (autonomy)\n *\n * This scorer is always-on (no sampling) because it's cheap — it only\n * reads exit codes and error states from tool results already in the messages.\n *\n * Scoring philosophy (from Anthropic's eval guidance):\n * \"Grade outcomes, not paths\" — we check what happened, not how.\n * Partial credit: a session that passes build but fails tests scores\n * higher than one that fails build entirely.\n *\n * Dimensions that don't apply to a session (e.g. build for a docs-only task)\n * are excluded from the weighted average, so sessions aren't penalized for\n * irrelevant criteria.\n */\n\nimport type { MastraDBMessage } from '@mastra/core/agent';\nimport { createScorer, filterRun } from '@mastra/core/evals';\n\nimport { isBuildCommand, isTestCommand, getExitCode } from './classify-command.js';\nimport type { ExtractedToolCall } from './extract-tools.js';\nimport { extractToolCalls } from './extract-tools.js';\n\n// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n// CONFIGURATION — Adjust weights and thresholds here\n// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n/**\n * Dimension weights. Used to compute the weighted average of applicable dimensions.\n * Dimensions that don't apply (e.g. no build ran) are excluded from the average.\n */\nconst WEIGHTS = {\n build: 0.3,\n tests: 0.25,\n toolErrors: 0.2,\n loops: 0.1,\n regression: 0.1,\n autonomy: 0.05,\n} as const;\n\n/**\n * Scoring thresholds and multipliers.\n */\nconst THRESHOLDS = {\n /** Multiplier for tool error rate penalty. Score = max(0, 1 - rate × this). */\n toolErrorPenaltyMultiplier: 1.0,\n\n /** How many consecutive identical calls (or same-error repeats) before it's a \"loop\". */\n loopMinRepetitions: 3,\n\n /** Score penalty per detected loop. */\n loopPenaltyPerOccurrence: 0.3,\n\n /** Score deducted per ask_user call. */\n autonomyPenaltyPerAsk: 0.25,\n\n /** Score when a command ran but exit code is ambiguous. */\n ambiguousExitScore: 0.75,\n\n /** Minimum tool calls required to produce a meaningful score. */\n minToolCalls: 1,\n\n /** Tool names whose errors are expected/benign (not penalized). */\n benignErrorTools: ['search_content', 'find_files', 'lsp_inspect'] as string[],\n} as const;\n\n// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\ntype DimensionResult = { score: number; detail: string; applicable: boolean };\n\n/**\n * Check if the primary verb of an execute_command is a build/typecheck command.\n * Splits compound commands (&&, ;) and checks the first segment.\n */\n/** Extract command string from tool call args, then delegate to shared classifier. */\nfunction isBuildArgs(args: Record<string, unknown>): boolean {\n return isBuildCommand(String(args.command ?? ''));\n}\n\nfunction isTestArgs(args: Record<string, unknown>): boolean {\n return isTestCommand(String(args.command ?? ''));\n}\n\n/**\n * Score build/typecheck outcome (final state).\n */\nfunction scoreBuild(execResults: ExtractedToolCall[]): DimensionResult {\n const builds = execResults.filter(r => r.toolName === 'execute_command' && isBuildArgs(r.args));\n if (builds.length === 0) return { score: 0, detail: 'No build/typecheck ran', applicable: false };\n\n const last = builds[builds.length - 1]!;\n if (last.isError) return { score: 0, detail: 'Build/typecheck tool errored', applicable: true };\n\n const exitCode = getExitCode(last.result);\n if (exitCode === 0) return { score: 1, detail: 'Build/typecheck passed', applicable: true };\n if (exitCode !== null && exitCode !== 0)\n return { score: 0, detail: `Build failed (exit ${exitCode})`, applicable: true };\n\n // Fallback: check result text for TS errors\n const text = typeof last.result === 'string' ? last.result : JSON.stringify(last.result ?? '');\n if (/error TS\\d+|Cannot find module|is not assignable/i.test(text)) {\n return { score: 0, detail: 'Build failed (TypeScript errors in output)', applicable: true };\n }\n\n return { score: THRESHOLDS.ambiguousExitScore, detail: 'Build ran, outcome unclear', applicable: true };\n}\n\n/**\n * Score test outcome (final state).\n */\nfunction scoreTests(execResults: ExtractedToolCall[]): DimensionResult {\n const tests = execResults.filter(r => r.toolName === 'execute_command' && isTestArgs(r.args));\n if (tests.length === 0) return { score: 0, detail: 'No tests ran', applicable: false };\n\n const last = tests[tests.length - 1]!;\n if (last.isError) return { score: 0, detail: 'Test command errored', applicable: true };\n\n const exitCode = getExitCode(last.result);\n if (exitCode === 0) return { score: 1, detail: 'Tests passed', applicable: true };\n if (exitCode !== null && exitCode !== 0)\n return { score: 0, detail: `Tests failed (exit ${exitCode})`, applicable: true };\n\n const text = typeof last.result === 'string' ? last.result : JSON.stringify(last.result ?? '');\n if (/\\d+ (?:tests? )?passed|✓|PASS/i.test(text) && !/\\bfail(?:ed|ure)?\\b|\\berror\\b/i.test(text)) {\n return { score: 1, detail: 'Tests passed (inferred)', applicable: true };\n }\n if (/\\bFAIL\\b|failed|✗|✘/i.test(text)) {\n return { score: 0, detail: 'Tests failed (inferred)', applicable: true };\n }\n\n return { score: THRESHOLDS.ambiguousExitScore, detail: 'Tests ran, outcome unclear', applicable: true };\n}\n\n/**\n * Score tool error rate, excluding benign errors from expected-to-fail tools.\n */\nfunction scoreToolErrors(results: ExtractedToolCall[]): DimensionResult {\n if (results.length === 0) return { score: 1, detail: 'No tool calls', applicable: false };\n\n // Only count errors from non-benign tools\n const nonBenign = results.filter(r => !THRESHOLDS.benignErrorTools.includes(r.toolName));\n if (nonBenign.length === 0) return { score: 1, detail: 'All tool calls are benign-error tools', applicable: true };\n\n const errors = nonBenign.filter(r => r.isError);\n const rate = errors.length / nonBenign.length;\n\n if (rate === 0) return { score: 1, detail: 'No tool errors', applicable: true };\n\n const score = Math.max(0, 1 - rate * THRESHOLDS.toolErrorPenaltyMultiplier);\n return {\n score,\n detail: `${errors.length}/${nonBenign.length} tools errored (${(rate * 100).toFixed(0)}%)`,\n applicable: true,\n };\n}\n\n/**\n * Detect stuck loops: same tool + same args appearing 3+ times consecutively,\n * or same tool + same args erroring 3+ times total.\n */\nfunction scoreStuckLoops(results: ExtractedToolCall[]): DimensionResult {\n if (results.length < 3) return { score: 1, detail: 'Too few calls for loop detection', applicable: true };\n\n const fingerprint = (r: ExtractedToolCall) => `${r.toolName}:${JSON.stringify(r.args)}`;\n\n // Check consecutive identical calls\n let maxConsecutive = 1;\n let currentRun = 1;\n for (let i = 1; i < results.length; i++) {\n if (fingerprint(results[i]!) === fingerprint(results[i - 1]!)) {\n currentRun++;\n maxConsecutive = Math.max(maxConsecutive, currentRun);\n } else {\n currentRun = 1;\n }\n }\n\n // Check repeated error fingerprints\n const errorCounts = new Map<string, number>();\n for (const r of results) {\n if (!r.isError) continue;\n const fp = fingerprint(r);\n errorCounts.set(fp, (errorCounts.get(fp) ?? 0) + 1);\n }\n const maxErrorRepeat = Math.max(0, ...[...errorCounts.values()]);\n\n const minReps = THRESHOLDS.loopMinRepetitions;\n const loopSeverity = Math.max(\n maxConsecutive >= minReps ? maxConsecutive - (minReps - 1) : 0,\n maxErrorRepeat >= minReps ? maxErrorRepeat - (minReps - 1) : 0,\n );\n\n if (loopSeverity === 0) return { score: 1, detail: 'No stuck loops', applicable: true };\n\n const score = Math.max(0, 1 - loopSeverity * THRESHOLDS.loopPenaltyPerOccurrence);\n const details: string[] = [];\n if (maxConsecutive >= minReps) details.push(`${maxConsecutive} consecutive identical calls`);\n if (maxErrorRepeat >= minReps) details.push(`same error repeated ${maxErrorRepeat}x`);\n return { score, detail: details.join('; '), applicable: true };\n}\n\n/**\n * Detect regressions: build/test passed at some point, then failed later.\n * Gives partial credit if the agent recovered (final state passes).\n */\nfunction scoreRegression(execResults: ExtractedToolCall[]): DimensionResult {\n const regressions: { label: string; recovered: boolean }[] = [];\n\n for (const [label, filter] of [\n ['Build', isBuildArgs],\n ['Tests', isTestArgs],\n ] as const) {\n const cmds = execResults.filter(r => r.toolName === 'execute_command' && filter(r.args));\n if (cmds.length < 2) continue;\n\n let sawPass = false;\n let sawRegression = false;\n for (const cmd of cmds) {\n const exit = getExitCode(cmd.result);\n if (exit === 0) {\n if (sawRegression) {\n // Recovered from regression\n regressions.push({ label, recovered: true });\n sawRegression = false;\n }\n sawPass = true;\n }\n if (sawPass && exit !== null && exit !== 0) {\n sawRegression = true;\n }\n }\n\n if (sawRegression) {\n // Regression persisted at end of session\n regressions.push({ label, recovered: false });\n }\n }\n\n if (regressions.length === 0) return { score: 1, detail: 'No regressions', applicable: true };\n\n // Persistent regressions are severe (0), recovered regressions get partial credit (0.5)\n const persistentCount = regressions.filter(r => !r.recovered).length;\n const recoveredCount = regressions.filter(r => r.recovered).length;\n const score = Math.max(0, 1 - persistentCount * 0.5 - recoveredCount * 0.1);\n const details = regressions.map(\n r => `${r.label} ${r.recovered ? 'regressed then recovered' : 'regressed (persisted)'}`,\n );\n return { score, detail: details.join('; '), applicable: true };\n}\n\n/**\n * Score autonomy: penalize ask_user calls.\n * 0 calls = 1.0, each call reduces by 0.25 (up to a floor of 0).\n */\nfunction scoreAutonomy(results: ExtractedToolCall[]): DimensionResult {\n const askUserCalls = results.filter(r => r.toolName === 'ask_user');\n const count = askUserCalls.length;\n\n if (count === 0) return { score: 1, detail: 'No ask_user calls (fully autonomous)', applicable: true };\n\n const score = Math.max(0, 1 - count * THRESHOLDS.autonomyPenaltyPerAsk);\n return { score, detail: `${count} ask_user call${count > 1 ? 's' : ''}`, applicable: true };\n}\n\n/**\n * Compute weighted average of only applicable dimensions.\n * Returns null if no dimensions are applicable.\n */\nfunction weightedAverage(dimensions: Record<string, DimensionResult>, weights: Record<string, number>): number | null {\n let totalWeight = 0;\n let weightedSum = 0;\n\n for (const [key, result] of Object.entries(dimensions)) {\n if (!result.applicable) continue;\n const weight = weights[key] ?? 0;\n totalWeight += weight;\n weightedSum += result.score * weight;\n }\n\n if (totalWeight === 0) return null;\n return weightedSum / totalWeight;\n}\n\nexport function createOutcomeScorer() {\n return createScorer({\n id: 'mastracode-outcome',\n name: 'MastraCode Outcome',\n description:\n 'Grades coding session outcomes: build/test pass, tool errors, stuck loops, regressions, autonomy. Always-on.',\n type: 'agent',\n prepareRun: filterRun({ partTypes: ['tool-invocation', 'text'] }),\n })\n .preprocess(async ({ run }) => {\n const messages = (run.output ?? []) as MastraDBMessage[];\n const allResults = extractToolCalls(messages);\n const totalCalls = allResults.length;\n\n // H1: Empty sessions or sessions with no tool calls → score 0\n if (totalCalls < THRESHOLDS.minToolCalls) {\n const empty: DimensionResult = { score: 0, detail: 'No tool calls', applicable: false };\n return {\n empty: true as const,\n build: empty,\n tests: empty,\n toolErrors: empty,\n loops: empty,\n regression: empty,\n autonomy: empty,\n totalCalls,\n };\n }\n\n const execResults = allResults.filter(r => r.toolName === 'execute_command');\n\n return {\n empty: false as const,\n build: scoreBuild(execResults),\n tests: scoreTests(execResults),\n toolErrors: scoreToolErrors(allResults),\n loops: scoreStuckLoops(allResults),\n regression: scoreRegression(execResults),\n autonomy: scoreAutonomy(allResults),\n totalCalls,\n };\n })\n .generateScore(({ results }) => {\n const p = results.preprocessStepResult;\n if (p.empty) return 0;\n\n const score = weightedAverage(\n {\n build: p.build,\n tests: p.tests,\n toolErrors: p.toolErrors,\n loops: p.loops,\n regression: p.regression,\n autonomy: p.autonomy,\n },\n WEIGHTS,\n );\n\n return score === null ? 0 : Math.round(score * 100) / 100;\n })\n .generateReason(({ results, score }) => {\n const p = results.preprocessStepResult;\n if (p.empty) {\n return `Score: 0 (${p.totalCalls} tool calls — below minimum threshold for scoring)`;\n }\n\n const pct = (w: number) => `${(w * 100).toFixed(0)}%`;\n const dimLine = (name: string, weight: number, r: DimensionResult) =>\n r.applicable\n ? `${name} (${pct(weight)}): ${r.detail} [${r.score}]`\n : `${name}: ${r.detail} [N/A — excluded from average]`;\n\n const parts: string[] = [`Score: ${score} (${p.totalCalls} tool calls total)`];\n parts.push(dimLine('Build', WEIGHTS.build, p.build));\n parts.push(dimLine('Tests', WEIGHTS.tests, p.tests));\n parts.push(dimLine('Tool errors', WEIGHTS.toolErrors, p.toolErrors));\n parts.push(dimLine('Loops', WEIGHTS.loops, p.loops));\n parts.push(dimLine('Regression', WEIGHTS.regression, p.regression));\n parts.push(dimLine('Autonomy', WEIGHTS.autonomy, p.autonomy));\n\n return parts.join('\\n');\n });\n}\n"],"mappings":";;;;;;;;AAuCA,MAAM,UAAU;CACd,OAAO;CACP,OAAO;CACP,YAAY;CACZ,OAAO;CACP,YAAY;CACZ,UAAU;AACZ;;;;AAKA,MAAM,aAAa;;CAEjB,4BAA4B;;CAG5B,oBAAoB;;CAGpB,0BAA0B;;CAG1B,uBAAuB;;CAGvB,oBAAoB;;CAGpB,cAAc;;CAGd,kBAAkB;EAAC;EAAkB;EAAc;CAAa;AAClE;;;;;;AAWA,SAAS,YAAY,MAAwC;CAC3D,OAAO,eAAe,OAAO,KAAK,WAAW,EAAE,CAAC;AAClD;AAEA,SAAS,WAAW,MAAwC;CAC1D,OAAO,cAAc,OAAO,KAAK,WAAW,EAAE,CAAC;AACjD;;;;AAKA,SAAS,WAAW,aAAmD;CACrE,MAAM,SAAS,YAAY,QAAO,MAAK,EAAE,aAAa,qBAAqB,YAAY,EAAE,IAAI,CAAC;CAC9F,IAAI,OAAO,WAAW,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAA0B,YAAY;CAAM;CAEhG,MAAM,OAAO,OAAO,OAAO,SAAS;CACpC,IAAI,KAAK,SAAS,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAgC,YAAY;CAAK;CAE9F,MAAM,WAAW,YAAY,KAAK,MAAM;CACxC,IAAI,aAAa,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAA0B,YAAY;CAAK;CAC1F,IAAI,aAAa,QAAQ,aAAa,GACpC,OAAO;EAAE,OAAO;EAAG,QAAQ,sBAAsB,SAAS;EAAI,YAAY;CAAK;CAGjF,MAAM,OAAO,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS,KAAK,UAAU,KAAK,UAAU,EAAE;CAC7F,IAAI,oDAAoD,KAAK,IAAI,GAC/D,OAAO;EAAE,OAAO;EAAG,QAAQ;EAA8C,YAAY;CAAK;CAG5F,OAAO;EAAE,OAAO,WAAW;EAAoB,QAAQ;EAA8B,YAAY;CAAK;AACxG;;;;AAKA,SAAS,WAAW,aAAmD;CACrE,MAAM,QAAQ,YAAY,QAAO,MAAK,EAAE,aAAa,qBAAqB,WAAW,EAAE,IAAI,CAAC;CAC5F,IAAI,MAAM,WAAW,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAgB,YAAY;CAAM;CAErF,MAAM,OAAO,MAAM,MAAM,SAAS;CAClC,IAAI,KAAK,SAAS,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAwB,YAAY;CAAK;CAEtF,MAAM,WAAW,YAAY,KAAK,MAAM;CACxC,IAAI,aAAa,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAgB,YAAY;CAAK;CAChF,IAAI,aAAa,QAAQ,aAAa,GACpC,OAAO;EAAE,OAAO;EAAG,QAAQ,sBAAsB,SAAS;EAAI,YAAY;CAAK;CAEjF,MAAM,OAAO,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS,KAAK,UAAU,KAAK,UAAU,EAAE;CAC7F,IAAI,iCAAiC,KAAK,IAAI,KAAK,CAAC,iCAAiC,KAAK,IAAI,GAC5F,OAAO;EAAE,OAAO;EAAG,QAAQ;EAA2B,YAAY;CAAK;CAEzE,IAAI,uBAAuB,KAAK,IAAI,GAClC,OAAO;EAAE,OAAO;EAAG,QAAQ;EAA2B,YAAY;CAAK;CAGzE,OAAO;EAAE,OAAO,WAAW;EAAoB,QAAQ;EAA8B,YAAY;CAAK;AACxG;;;;AAKA,SAAS,gBAAgB,SAA+C;CACtE,IAAI,QAAQ,WAAW,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAiB,YAAY;CAAM;CAGxF,MAAM,YAAY,QAAQ,QAAO,MAAK,CAAC,WAAW,iBAAiB,SAAS,EAAE,QAAQ,CAAC;CACvF,IAAI,UAAU,WAAW,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAyC,YAAY;CAAK;CAEjH,MAAM,SAAS,UAAU,QAAO,MAAK,EAAE,OAAO;CAC9C,MAAM,OAAO,OAAO,SAAS,UAAU;CAEvC,IAAI,SAAS,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAkB,YAAY;CAAK;CAG9E,OAAO;EACL,OAFY,KAAK,IAAI,GAAG,IAAI,OAAO,WAAW,0BAE1C;EACJ,QAAQ,GAAG,OAAO,OAAO,GAAG,UAAU,OAAO,mBAAmB,OAAO,IAAA,CAAK,QAAQ,CAAC,EAAE;EACvF,YAAY;CACd;AACF;;;;;AAMA,SAAS,gBAAgB,SAA+C;CACtE,IAAI,QAAQ,SAAS,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAoC,YAAY;CAAK;CAExG,MAAM,eAAe,MAAyB,GAAG,EAAE,SAAS,GAAG,KAAK,UAAU,EAAE,IAAI;CAGpF,IAAI,iBAAiB;CACrB,IAAI,aAAa;CACjB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAClC,IAAI,YAAY,QAAQ,EAAG,MAAM,YAAY,QAAQ,IAAI,EAAG,GAAG;EAC7D;EACA,iBAAiB,KAAK,IAAI,gBAAgB,UAAU;CACtD,OACE,aAAa;CAKjB,MAAM,8BAAc,IAAI,IAAoB;CAC5C,KAAK,MAAM,KAAK,SAAS;EACvB,IAAI,CAAC,EAAE,SAAS;EAChB,MAAM,KAAK,YAAY,CAAC;EACxB,YAAY,IAAI,KAAK,YAAY,IAAI,EAAE,KAAK,KAAK,CAAC;CACpD;CACA,MAAM,iBAAiB,KAAK,IAAI,GAAG,GAAG,CAAC,GAAG,YAAY,OAAO,CAAC,CAAC;CAE/D,MAAM,UAAU,WAAW;CAC3B,MAAM,eAAe,KAAK,IACxB,kBAAkB,UAAU,kBAAkB,UAAU,KAAK,GAC7D,kBAAkB,UAAU,kBAAkB,UAAU,KAAK,CAC/D;CAEA,IAAI,iBAAiB,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAkB,YAAY;CAAK;CAEtF,MAAM,QAAQ,KAAK,IAAI,GAAG,IAAI,eAAe,WAAW,wBAAwB;CAChF,MAAM,UAAoB,CAAC;CAC3B,IAAI,kBAAkB,SAAS,QAAQ,KAAK,GAAG,eAAe,6BAA6B;CAC3F,IAAI,kBAAkB,SAAS,QAAQ,KAAK,uBAAuB,eAAe,EAAE;CACpF,OAAO;EAAE;EAAO,QAAQ,QAAQ,KAAK,IAAI;EAAG,YAAY;CAAK;AAC/D;;;;;AAMA,SAAS,gBAAgB,aAAmD;CAC1E,MAAM,cAAuD,CAAC;CAE9D,KAAK,MAAM,CAAC,OAAO,WAAW,CAC5B,CAAC,SAAS,WAAW,GACrB,CAAC,SAAS,UAAU,CACtB,GAAY;EACV,MAAM,OAAO,YAAY,QAAO,MAAK,EAAE,aAAa,qBAAqB,OAAO,EAAE,IAAI,CAAC;EACvF,IAAI,KAAK,SAAS,GAAG;EAErB,IAAI,UAAU;EACd,IAAI,gBAAgB;EACpB,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,OAAO,YAAY,IAAI,MAAM;GACnC,IAAI,SAAS,GAAG;IACd,IAAI,eAAe;KAEjB,YAAY,KAAK;MAAE;MAAO,WAAW;KAAK,CAAC;KAC3C,gBAAgB;IAClB;IACA,UAAU;GACZ;GACA,IAAI,WAAW,SAAS,QAAQ,SAAS,GACvC,gBAAgB;EAEpB;EAEA,IAAI,eAEF,YAAY,KAAK;GAAE;GAAO,WAAW;EAAM,CAAC;CAEhD;CAEA,IAAI,YAAY,WAAW,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAkB,YAAY;CAAK;CAG5F,MAAM,kBAAkB,YAAY,QAAO,MAAK,CAAC,EAAE,SAAS,CAAC,CAAC;CAC9D,MAAM,iBAAiB,YAAY,QAAO,MAAK,EAAE,SAAS,CAAC,CAAC;CAK5D,OAAO;EAAE,OAJK,KAAK,IAAI,GAAG,IAAI,kBAAkB,KAAM,iBAAiB,EAI1D;EAAG,QAHA,YAAY,KAC1B,MAAK,GAAG,EAAE,MAAM,GAAG,EAAE,YAAY,6BAA6B,yBAElC,CAAC,CAAC,KAAK,IAAI;EAAG,YAAY;CAAK;AAC/D;;;;;AAMA,SAAS,cAAc,SAA+C;CAEpE,MAAM,QADe,QAAQ,QAAO,MAAK,EAAE,aAAa,UAC/B,CAAC,CAAC;CAE3B,IAAI,UAAU,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAwC,YAAY;CAAK;CAGrG,OAAO;EAAE,OADK,KAAK,IAAI,GAAG,IAAI,QAAQ,WAAW,qBACpC;EAAG,QAAQ,GAAG,MAAM,gBAAgB,QAAQ,IAAI,MAAM;EAAM,YAAY;CAAK;AAC5F;;;;;AAMA,SAAS,gBAAgB,YAA6C,SAAgD;CACpH,IAAI,cAAc;CAClB,IAAI,cAAc;CAElB,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,UAAU,GAAG;EACtD,IAAI,CAAC,OAAO,YAAY;EACxB,MAAM,SAAS,QAAQ,QAAQ;EAC/B,eAAe;EACf,eAAe,OAAO,QAAQ;CAChC;CAEA,IAAI,gBAAgB,GAAG,OAAO;CAC9B,OAAO,cAAc;AACvB;AAEA,SAAgB,sBAAsB;CACpC,OAAO,aAAa;EAClB,IAAI;EACJ,MAAM;EACN,aACE;EACF,MAAM;EACN,YAAY,UAAU,EAAE,WAAW,CAAC,mBAAmB,MAAM,EAAE,CAAC;CAClE,CAAC,CAAC,CACC,WAAW,OAAO,EAAE,UAAU;EAE7B,MAAM,aAAa,iBADD,IAAI,UAAU,CAAC,CACW;EAC5C,MAAM,aAAa,WAAW;EAG9B,IAAI,aAAa,WAAW,cAAc;GACxC,MAAM,QAAyB;IAAE,OAAO;IAAG,QAAQ;IAAiB,YAAY;GAAM;GACtF,OAAO;IACL,OAAO;IACP,OAAO;IACP,OAAO;IACP,YAAY;IACZ,OAAO;IACP,YAAY;IACZ,UAAU;IACV;GACF;EACF;EAEA,MAAM,cAAc,WAAW,QAAO,MAAK,EAAE,aAAa,iBAAiB;EAE3E,OAAO;GACL,OAAO;GACP,OAAO,WAAW,WAAW;GAC7B,OAAO,WAAW,WAAW;GAC7B,YAAY,gBAAgB,UAAU;GACtC,OAAO,gBAAgB,UAAU;GACjC,YAAY,gBAAgB,WAAW;GACvC,UAAU,cAAc,UAAU;GAClC;EACF;CACF,CAAC,CAAC,CACD,eAAe,EAAE,cAAc;EAC9B,MAAM,IAAI,QAAQ;EAClB,IAAI,EAAE,OAAO,OAAO;EAEpB,MAAM,QAAQ,gBACZ;GACE,OAAO,EAAE;GACT,OAAO,EAAE;GACT,YAAY,EAAE;GACd,OAAO,EAAE;GACT,YAAY,EAAE;GACd,UAAU,EAAE;EACd,GACA,OACF;EAEA,OAAO,UAAU,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG,IAAI;CACxD,CAAC,CAAC,CACD,gBAAgB,EAAE,SAAS,YAAY;EACtC,MAAM,IAAI,QAAQ;EAClB,IAAI,EAAE,OACJ,OAAO,aAAa,EAAE,WAAW;EAGnC,MAAM,OAAO,MAAc,IAAI,IAAI,IAAA,CAAK,QAAQ,CAAC,EAAE;EACnD,MAAM,WAAW,MAAc,QAAgB,MAC7C,EAAE,aACE,GAAG,KAAK,IAAI,IAAI,MAAM,EAAE,KAAK,EAAE,OAAO,IAAI,EAAE,MAAM,KAClD,GAAG,KAAK,IAAI,EAAE,OAAO;EAE3B,MAAM,QAAkB,CAAC,UAAU,MAAM,IAAI,EAAE,WAAW,mBAAmB;EAC7E,MAAM,KAAK,QAAQ,SAAS,QAAQ,OAAO,EAAE,KAAK,CAAC;EACnD,MAAM,KAAK,QAAQ,SAAS,QAAQ,OAAO,EAAE,KAAK,CAAC;EACnD,MAAM,KAAK,QAAQ,eAAe,QAAQ,YAAY,EAAE,UAAU,CAAC;EACnE,MAAM,KAAK,QAAQ,SAAS,QAAQ,OAAO,EAAE,KAAK,CAAC;EACnD,MAAM,KAAK,QAAQ,cAAc,QAAQ,YAAY,EAAE,UAAU,CAAC;EAClE,MAAM,KAAK,QAAQ,YAAY,QAAQ,UAAU,EAAE,QAAQ,CAAC;EAE5D,OAAO,MAAM,KAAK,IAAI;CACxB,CAAC;AACL"}
|
package/dist/goal-manager.js
CHANGED
|
@@ -1,268 +1,273 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { getGoalActivityDurationMs } from "@mastra/core/agent";
|
|
3
1
|
import { loadSettings } from "./onboarding/settings.js";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { getGoalActivityDurationMs } from "@mastra/core/agent";
|
|
4
|
+
//#region src/goal-manager.ts
|
|
5
|
+
/**
|
|
6
|
+
* GoalManager — persistent cross-turn goals, backed by the Agent's native goal
|
|
7
|
+
* mechanism.
|
|
8
|
+
*
|
|
9
|
+
* The objective lives in the durable `threadState` `'goal'` slot (via
|
|
10
|
+
* `agent.setObjective`/`getObjective`/`clearObjective`/`updateObjectiveOptions`)
|
|
11
|
+
* and is judged in-loop by the core goal step. This manager is a thin adapter:
|
|
12
|
+
* it keeps a synchronous in-memory view of the current objective for the TUI
|
|
13
|
+
* (status line, modal, keyboard shortcuts) and delegates persistence to the
|
|
14
|
+
* agent. There is no standalone judge agent and no between-turn re-invocation —
|
|
15
|
+
* the core goal step drives continuation and surfaces progress via `goal` stream
|
|
16
|
+
* chunks.
|
|
17
|
+
*/
|
|
4
18
|
const DEFAULT_MAX_TURNS = 50;
|
|
5
19
|
const THREAD_GOAL_KEY = "goal";
|
|
6
20
|
function normalizeActiveDurationMs(value) {
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
class GoalManager {
|
|
10
|
-
/** Synchronous in-memory view of the active objective record (source of truth is ThreadState). */
|
|
11
|
-
record = null;
|
|
12
|
-
threadId;
|
|
13
|
-
agentId;
|
|
14
|
-
persistGoalOnNextThreadCreate = false;
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Synchronous TUI surface
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
getGoal() {
|
|
19
|
-
if (!this.record) return null;
|
|
20
|
-
const { judgeModelId, maxTurns } = this.effectiveSettings(this.record);
|
|
21
|
-
return {
|
|
22
|
-
id: this.record.id,
|
|
23
|
-
objective: this.record.objective,
|
|
24
|
-
status: this.record.status,
|
|
25
|
-
turnsUsed: this.record.runsUsed,
|
|
26
|
-
maxTurns,
|
|
27
|
-
judgeModelId,
|
|
28
|
-
startedAt: new Date(this.record.startedAt).toISOString(),
|
|
29
|
-
activeDurationMs: this.agentId && this.threadId ? getGoalActivityDurationMs({
|
|
30
|
-
agentId: this.agentId,
|
|
31
|
-
threadId: this.threadId,
|
|
32
|
-
objectiveId: this.record.id,
|
|
33
|
-
activeDurationMs: this.record.activeDurationMs
|
|
34
|
-
}) : normalizeActiveDurationMs(this.record.activeDurationMs)
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
isActive() {
|
|
38
|
-
return this.record?.status === "active";
|
|
39
|
-
}
|
|
40
|
-
persistOnNextThreadCreate() {
|
|
41
|
-
this.persistGoalOnNextThreadCreate = true;
|
|
42
|
-
}
|
|
43
|
-
consumePersistOnNextThreadCreate() {
|
|
44
|
-
if (!this.persistGoalOnNextThreadCreate) return false;
|
|
45
|
-
this.persistGoalOnNextThreadCreate = false;
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// Objective lifecycle (ThreadState-backed via the agent)
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
/**
|
|
52
|
-
* Set a new objective. Persists to ThreadState via `agent.setObjective` and
|
|
53
|
-
* updates the in-memory view. Only the provided settings are persisted into
|
|
54
|
-
* the record; unset ones fall back to the agent's `goal` config at read time.
|
|
55
|
-
*/
|
|
56
|
-
async setGoal(state, objective, judgeModelId, maxTurns = DEFAULT_MAX_TURNS) {
|
|
57
|
-
const threadId = state.session.thread.getId();
|
|
58
|
-
const agent = this.getAgent(state);
|
|
59
|
-
const now = Date.now();
|
|
60
|
-
const id = randomUUID();
|
|
61
|
-
this.threadId = threadId ?? void 0;
|
|
62
|
-
this.agentId = agent?.id;
|
|
63
|
-
if (agent && threadId) {
|
|
64
|
-
const persisted = await agent.setObjective(objective, {
|
|
65
|
-
id,
|
|
66
|
-
threadId,
|
|
67
|
-
resourceId: state.session.identity.getResourceId(),
|
|
68
|
-
...judgeModelId ? { judgeModelId } : {},
|
|
69
|
-
maxRuns: maxTurns
|
|
70
|
-
});
|
|
71
|
-
this.record = persisted ? { ...persisted, id: persisted.id ?? id } : this.localRecord(objective, judgeModelId, maxTurns, now, id);
|
|
72
|
-
} else {
|
|
73
|
-
this.record = this.localRecord(objective, judgeModelId, maxTurns, now, id);
|
|
74
|
-
}
|
|
75
|
-
return this.getGoal();
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Update the judge model / max-runs defaults. Persists into the active record
|
|
79
|
-
* (so the override is remembered in thread state) when a goal is set.
|
|
80
|
-
*/
|
|
81
|
-
async updateJudgeDefaults(state, judgeModelId, maxTurns) {
|
|
82
|
-
if (!this.record) return null;
|
|
83
|
-
const threadId = state.session.thread.getId();
|
|
84
|
-
const agent = this.getAgent(state);
|
|
85
|
-
if (agent && threadId) {
|
|
86
|
-
const updated = await agent.updateObjectiveOptions({
|
|
87
|
-
threadId,
|
|
88
|
-
...judgeModelId ? { judgeModelId } : {},
|
|
89
|
-
maxRuns: maxTurns
|
|
90
|
-
});
|
|
91
|
-
if (updated) this.record = { ...updated, id: this.record.id };
|
|
92
|
-
} else {
|
|
93
|
-
this.record = {
|
|
94
|
-
...this.record,
|
|
95
|
-
...judgeModelId ? { judgeModelId } : {},
|
|
96
|
-
maxRuns: maxTurns,
|
|
97
|
-
updatedAt: Date.now()
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
return this.getGoal();
|
|
101
|
-
}
|
|
102
|
-
pause(reason) {
|
|
103
|
-
if (this.record && this.record.status === "active") {
|
|
104
|
-
this.record = { ...this.record, status: "paused", pausedReason: reason, updatedAt: Date.now() };
|
|
105
|
-
}
|
|
106
|
-
return this.getGoal();
|
|
107
|
-
}
|
|
108
|
-
resume() {
|
|
109
|
-
if (this.record && this.record.status === "paused") {
|
|
110
|
-
this.record = { ...this.record, status: "active", pausedReason: void 0, updatedAt: Date.now() };
|
|
111
|
-
}
|
|
112
|
-
return this.getGoal();
|
|
113
|
-
}
|
|
114
|
-
markDone() {
|
|
115
|
-
if (this.record) {
|
|
116
|
-
this.record = { ...this.record, status: "done", updatedAt: Date.now() };
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
clear() {
|
|
120
|
-
this.record = null;
|
|
121
|
-
this.threadId = void 0;
|
|
122
|
-
this.agentId = void 0;
|
|
123
|
-
this.persistGoalOnNextThreadCreate = false;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Sync the latest objective record from ThreadState into the in-memory view.
|
|
127
|
-
* Called from the `goal` stream-chunk handler after each evaluation.
|
|
128
|
-
*/
|
|
129
|
-
applyEvaluation(update) {
|
|
130
|
-
if (!this.record) return null;
|
|
131
|
-
this.record = { ...this.record, runsUsed: update.runsUsed, status: update.status, updatedAt: Date.now() };
|
|
132
|
-
return this.getGoal();
|
|
133
|
-
}
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
// Persistence
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
/**
|
|
138
|
-
* Persist the active objective to ThreadState via the agent. The objective
|
|
139
|
-
* record is the source of truth; the legacy thread-metadata key is cleared so
|
|
140
|
-
* stale state from older sessions does not resurface.
|
|
141
|
-
*/
|
|
142
|
-
async saveToThread(state) {
|
|
143
|
-
const threadId = state.session.thread.getId();
|
|
144
|
-
const agent = this.getAgent(state);
|
|
145
|
-
try {
|
|
146
|
-
if (agent && threadId) {
|
|
147
|
-
if (this.record) {
|
|
148
|
-
const updated = await agent.updateObjectiveOptions({
|
|
149
|
-
threadId,
|
|
150
|
-
status: this.record.status,
|
|
151
|
-
...this.record.pausedReason ? { pausedReason: this.record.pausedReason } : {},
|
|
152
|
-
...this.record.judgeModelId ? { judgeModelId: this.record.judgeModelId } : {},
|
|
153
|
-
...this.record.maxRuns !== void 0 ? { maxRuns: this.record.maxRuns } : {}
|
|
154
|
-
});
|
|
155
|
-
if (!updated) {
|
|
156
|
-
const desiredStatus = this.record.status;
|
|
157
|
-
await agent.setObjective(this.record.objective, {
|
|
158
|
-
id: this.record.id,
|
|
159
|
-
threadId,
|
|
160
|
-
resourceId: state.session.identity.getResourceId(),
|
|
161
|
-
activeDurationMs: normalizeActiveDurationMs(this.record.activeDurationMs),
|
|
162
|
-
...this.record.judgeModelId ? { judgeModelId: this.record.judgeModelId } : {},
|
|
163
|
-
...this.record.maxRuns !== void 0 ? { maxRuns: this.record.maxRuns } : {}
|
|
164
|
-
});
|
|
165
|
-
if (desiredStatus !== "active") {
|
|
166
|
-
await agent.updateObjectiveOptions({
|
|
167
|
-
threadId,
|
|
168
|
-
status: desiredStatus,
|
|
169
|
-
...this.record.pausedReason ? { pausedReason: this.record.pausedReason } : {}
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
await agent.clearObjective({ threadId });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
await state.session.thread.setSetting({ key: THREAD_GOAL_KEY, value: void 0 });
|
|
178
|
-
} catch {
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Load the objective from ThreadState (called on thread switch). Falls back to
|
|
183
|
-
* the legacy thread-metadata goal for threads created before this migration.
|
|
184
|
-
*/
|
|
185
|
-
async loadFromThread(state) {
|
|
186
|
-
this.persistGoalOnNextThreadCreate = false;
|
|
187
|
-
const threadId = state.session.thread.getId();
|
|
188
|
-
const agent = this.getAgent(state);
|
|
189
|
-
this.threadId = threadId ?? void 0;
|
|
190
|
-
this.agentId = agent?.id;
|
|
191
|
-
if (agent && threadId) {
|
|
192
|
-
try {
|
|
193
|
-
const record = await agent.getObjective({ threadId });
|
|
194
|
-
if (record) {
|
|
195
|
-
this.record = {
|
|
196
|
-
...record,
|
|
197
|
-
id: record.id ?? randomUUID(),
|
|
198
|
-
activeDurationMs: normalizeActiveDurationMs(record.activeDurationMs)
|
|
199
|
-
};
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
this.record = null;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Legacy entry point retained for thread-switch call sites that only have the
|
|
209
|
-
* thread metadata available. Hydrates from a previously-persisted GoalState.
|
|
210
|
-
*/
|
|
211
|
-
loadFromThreadMetadata(metadata) {
|
|
212
|
-
const saved = metadata?.[THREAD_GOAL_KEY];
|
|
213
|
-
this.persistGoalOnNextThreadCreate = false;
|
|
214
|
-
this.threadId = void 0;
|
|
215
|
-
this.agentId = void 0;
|
|
216
|
-
if (saved && saved.objective && saved.status) {
|
|
217
|
-
this.record = {
|
|
218
|
-
objective: saved.objective,
|
|
219
|
-
status: saved.status,
|
|
220
|
-
runsUsed: saved.turnsUsed ?? 0,
|
|
221
|
-
activeDurationMs: normalizeActiveDurationMs(saved.activeDurationMs),
|
|
222
|
-
maxRuns: saved.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
223
|
-
judgeModelId: saved.judgeModelId ?? "",
|
|
224
|
-
startedAt: saved.startedAt ? Date.parse(saved.startedAt) || Date.now() : Date.now(),
|
|
225
|
-
updatedAt: Date.now(),
|
|
226
|
-
id: saved.id ?? randomUUID()
|
|
227
|
-
};
|
|
228
|
-
} else {
|
|
229
|
-
this.record = null;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// ---------------------------------------------------------------------------
|
|
233
|
-
// Private
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
getAgent(state) {
|
|
236
|
-
try {
|
|
237
|
-
return state.controller.getCurrentAgent(state.session);
|
|
238
|
-
} catch {
|
|
239
|
-
return void 0;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
/** Resolve effective judge model + max runs (record value → settings default). */
|
|
243
|
-
effectiveSettings(record) {
|
|
244
|
-
const settings = loadSettings();
|
|
245
|
-
return {
|
|
246
|
-
judgeModelId: record.judgeModelId ?? settings.models.goalJudgeModel ?? "",
|
|
247
|
-
maxTurns: record.maxRuns ?? settings.models.goalMaxTurns ?? DEFAULT_MAX_TURNS
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
localRecord(objective, judgeModelId, maxTurns, now, id) {
|
|
251
|
-
return {
|
|
252
|
-
objective,
|
|
253
|
-
status: "active",
|
|
254
|
-
runsUsed: 0,
|
|
255
|
-
activeDurationMs: 0,
|
|
256
|
-
maxRuns: maxTurns,
|
|
257
|
-
...judgeModelId ? { judgeModelId } : {},
|
|
258
|
-
startedAt: now,
|
|
259
|
-
updatedAt: now,
|
|
260
|
-
id
|
|
261
|
-
};
|
|
262
|
-
}
|
|
21
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
263
22
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
23
|
+
var GoalManager = class {
|
|
24
|
+
/** Synchronous in-memory view of the active objective record (source of truth is ThreadState). */
|
|
25
|
+
record = null;
|
|
26
|
+
threadId;
|
|
27
|
+
agentId;
|
|
28
|
+
persistGoalOnNextThreadCreate = false;
|
|
29
|
+
getGoal() {
|
|
30
|
+
if (!this.record) return null;
|
|
31
|
+
const { judgeModelId, maxTurns } = this.effectiveSettings(this.record);
|
|
32
|
+
return {
|
|
33
|
+
id: this.record.id,
|
|
34
|
+
objective: this.record.objective,
|
|
35
|
+
status: this.record.status,
|
|
36
|
+
turnsUsed: this.record.runsUsed,
|
|
37
|
+
maxTurns,
|
|
38
|
+
judgeModelId,
|
|
39
|
+
startedAt: new Date(this.record.startedAt).toISOString(),
|
|
40
|
+
activeDurationMs: this.agentId && this.threadId ? getGoalActivityDurationMs({
|
|
41
|
+
agentId: this.agentId,
|
|
42
|
+
threadId: this.threadId,
|
|
43
|
+
objectiveId: this.record.id,
|
|
44
|
+
activeDurationMs: this.record.activeDurationMs
|
|
45
|
+
}) : normalizeActiveDurationMs(this.record.activeDurationMs)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
isActive() {
|
|
49
|
+
return this.record?.status === "active";
|
|
50
|
+
}
|
|
51
|
+
persistOnNextThreadCreate() {
|
|
52
|
+
this.persistGoalOnNextThreadCreate = true;
|
|
53
|
+
}
|
|
54
|
+
consumePersistOnNextThreadCreate() {
|
|
55
|
+
if (!this.persistGoalOnNextThreadCreate) return false;
|
|
56
|
+
this.persistGoalOnNextThreadCreate = false;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Set a new objective. Persists to ThreadState via `agent.setObjective` and
|
|
61
|
+
* updates the in-memory view. Only the provided settings are persisted into
|
|
62
|
+
* the record; unset ones fall back to the agent's `goal` config at read time.
|
|
63
|
+
*/
|
|
64
|
+
async setGoal(state, objective, judgeModelId, maxTurns = 50) {
|
|
65
|
+
const threadId = state.session.thread.getId();
|
|
66
|
+
const agent = this.getAgent(state);
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const id = randomUUID();
|
|
69
|
+
this.threadId = threadId ?? void 0;
|
|
70
|
+
this.agentId = agent?.id;
|
|
71
|
+
if (agent && threadId) {
|
|
72
|
+
const persisted = await agent.setObjective(objective, {
|
|
73
|
+
id,
|
|
74
|
+
threadId,
|
|
75
|
+
resourceId: state.session.identity.getResourceId(),
|
|
76
|
+
...judgeModelId ? { judgeModelId } : {},
|
|
77
|
+
maxRuns: maxTurns
|
|
78
|
+
});
|
|
79
|
+
this.record = persisted ? {
|
|
80
|
+
...persisted,
|
|
81
|
+
id: persisted.id ?? id
|
|
82
|
+
} : this.localRecord(objective, judgeModelId, maxTurns, now, id);
|
|
83
|
+
} else this.record = this.localRecord(objective, judgeModelId, maxTurns, now, id);
|
|
84
|
+
return this.getGoal();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Update the judge model / max-runs defaults. Persists into the active record
|
|
88
|
+
* (so the override is remembered in thread state) when a goal is set.
|
|
89
|
+
*/
|
|
90
|
+
async updateJudgeDefaults(state, judgeModelId, maxTurns) {
|
|
91
|
+
if (!this.record) return null;
|
|
92
|
+
const threadId = state.session.thread.getId();
|
|
93
|
+
const agent = this.getAgent(state);
|
|
94
|
+
if (agent && threadId) {
|
|
95
|
+
const updated = await agent.updateObjectiveOptions({
|
|
96
|
+
threadId,
|
|
97
|
+
...judgeModelId ? { judgeModelId } : {},
|
|
98
|
+
maxRuns: maxTurns
|
|
99
|
+
});
|
|
100
|
+
if (updated) this.record = {
|
|
101
|
+
...updated,
|
|
102
|
+
id: this.record.id
|
|
103
|
+
};
|
|
104
|
+
} else this.record = {
|
|
105
|
+
...this.record,
|
|
106
|
+
...judgeModelId ? { judgeModelId } : {},
|
|
107
|
+
maxRuns: maxTurns,
|
|
108
|
+
updatedAt: Date.now()
|
|
109
|
+
};
|
|
110
|
+
return this.getGoal();
|
|
111
|
+
}
|
|
112
|
+
pause(reason) {
|
|
113
|
+
if (this.record && this.record.status === "active") this.record = {
|
|
114
|
+
...this.record,
|
|
115
|
+
status: "paused",
|
|
116
|
+
pausedReason: reason,
|
|
117
|
+
updatedAt: Date.now()
|
|
118
|
+
};
|
|
119
|
+
return this.getGoal();
|
|
120
|
+
}
|
|
121
|
+
resume() {
|
|
122
|
+
if (this.record && this.record.status === "paused") this.record = {
|
|
123
|
+
...this.record,
|
|
124
|
+
status: "active",
|
|
125
|
+
pausedReason: void 0,
|
|
126
|
+
updatedAt: Date.now()
|
|
127
|
+
};
|
|
128
|
+
return this.getGoal();
|
|
129
|
+
}
|
|
130
|
+
markDone() {
|
|
131
|
+
if (this.record) this.record = {
|
|
132
|
+
...this.record,
|
|
133
|
+
status: "done",
|
|
134
|
+
updatedAt: Date.now()
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
clear() {
|
|
138
|
+
this.record = null;
|
|
139
|
+
this.threadId = void 0;
|
|
140
|
+
this.agentId = void 0;
|
|
141
|
+
this.persistGoalOnNextThreadCreate = false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Sync the latest objective record from ThreadState into the in-memory view.
|
|
145
|
+
* Called from the `goal` stream-chunk handler after each evaluation.
|
|
146
|
+
*/
|
|
147
|
+
applyEvaluation(update) {
|
|
148
|
+
if (!this.record) return null;
|
|
149
|
+
this.record = {
|
|
150
|
+
...this.record,
|
|
151
|
+
runsUsed: update.runsUsed,
|
|
152
|
+
status: update.status,
|
|
153
|
+
updatedAt: Date.now()
|
|
154
|
+
};
|
|
155
|
+
return this.getGoal();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Persist the active objective to ThreadState via the agent. The objective
|
|
159
|
+
* record is the source of truth; the legacy thread-metadata key is cleared so
|
|
160
|
+
* stale state from older sessions does not resurface.
|
|
161
|
+
*/
|
|
162
|
+
async saveToThread(state) {
|
|
163
|
+
const threadId = state.session.thread.getId();
|
|
164
|
+
const agent = this.getAgent(state);
|
|
165
|
+
try {
|
|
166
|
+
if (agent && threadId) if (this.record) {
|
|
167
|
+
if (!await agent.updateObjectiveOptions({
|
|
168
|
+
threadId,
|
|
169
|
+
status: this.record.status,
|
|
170
|
+
...this.record.pausedReason ? { pausedReason: this.record.pausedReason } : {},
|
|
171
|
+
...this.record.judgeModelId ? { judgeModelId: this.record.judgeModelId } : {},
|
|
172
|
+
...this.record.maxRuns !== void 0 ? { maxRuns: this.record.maxRuns } : {}
|
|
173
|
+
})) {
|
|
174
|
+
const desiredStatus = this.record.status;
|
|
175
|
+
await agent.setObjective(this.record.objective, {
|
|
176
|
+
id: this.record.id,
|
|
177
|
+
threadId,
|
|
178
|
+
resourceId: state.session.identity.getResourceId(),
|
|
179
|
+
activeDurationMs: normalizeActiveDurationMs(this.record.activeDurationMs),
|
|
180
|
+
...this.record.judgeModelId ? { judgeModelId: this.record.judgeModelId } : {},
|
|
181
|
+
...this.record.maxRuns !== void 0 ? { maxRuns: this.record.maxRuns } : {}
|
|
182
|
+
});
|
|
183
|
+
if (desiredStatus !== "active") await agent.updateObjectiveOptions({
|
|
184
|
+
threadId,
|
|
185
|
+
status: desiredStatus,
|
|
186
|
+
...this.record.pausedReason ? { pausedReason: this.record.pausedReason } : {}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} else await agent.clearObjective({ threadId });
|
|
190
|
+
await state.session.thread.setSetting({
|
|
191
|
+
key: THREAD_GOAL_KEY,
|
|
192
|
+
value: void 0
|
|
193
|
+
});
|
|
194
|
+
} catch {}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Load the objective from ThreadState (called on thread switch). Falls back to
|
|
198
|
+
* the legacy thread-metadata goal for threads created before this migration.
|
|
199
|
+
*/
|
|
200
|
+
async loadFromThread(state) {
|
|
201
|
+
this.persistGoalOnNextThreadCreate = false;
|
|
202
|
+
const threadId = state.session.thread.getId();
|
|
203
|
+
const agent = this.getAgent(state);
|
|
204
|
+
this.threadId = threadId ?? void 0;
|
|
205
|
+
this.agentId = agent?.id;
|
|
206
|
+
if (agent && threadId) try {
|
|
207
|
+
const record = await agent.getObjective({ threadId });
|
|
208
|
+
if (record) {
|
|
209
|
+
this.record = {
|
|
210
|
+
...record,
|
|
211
|
+
id: record.id ?? randomUUID(),
|
|
212
|
+
activeDurationMs: normalizeActiveDurationMs(record.activeDurationMs)
|
|
213
|
+
};
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
this.record = null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Legacy entry point retained for thread-switch call sites that only have the
|
|
221
|
+
* thread metadata available. Hydrates from a previously-persisted GoalState.
|
|
222
|
+
*/
|
|
223
|
+
loadFromThreadMetadata(metadata) {
|
|
224
|
+
const saved = metadata?.[THREAD_GOAL_KEY];
|
|
225
|
+
this.persistGoalOnNextThreadCreate = false;
|
|
226
|
+
this.threadId = void 0;
|
|
227
|
+
this.agentId = void 0;
|
|
228
|
+
if (saved && saved.objective && saved.status) this.record = {
|
|
229
|
+
objective: saved.objective,
|
|
230
|
+
status: saved.status,
|
|
231
|
+
runsUsed: saved.turnsUsed ?? 0,
|
|
232
|
+
activeDurationMs: normalizeActiveDurationMs(saved.activeDurationMs),
|
|
233
|
+
maxRuns: saved.maxTurns ?? 50,
|
|
234
|
+
judgeModelId: saved.judgeModelId ?? "",
|
|
235
|
+
startedAt: saved.startedAt ? Date.parse(saved.startedAt) || Date.now() : Date.now(),
|
|
236
|
+
updatedAt: Date.now(),
|
|
237
|
+
id: saved.id ?? randomUUID()
|
|
238
|
+
};
|
|
239
|
+
else this.record = null;
|
|
240
|
+
}
|
|
241
|
+
getAgent(state) {
|
|
242
|
+
try {
|
|
243
|
+
return state.controller.getCurrentAgent(state.session);
|
|
244
|
+
} catch {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Resolve effective judge model + max runs (record value → settings default). */
|
|
249
|
+
effectiveSettings(record) {
|
|
250
|
+
const settings = loadSettings();
|
|
251
|
+
return {
|
|
252
|
+
judgeModelId: record.judgeModelId ?? settings.models.goalJudgeModel ?? "",
|
|
253
|
+
maxTurns: record.maxRuns ?? settings.models.goalMaxTurns ?? 50
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
localRecord(objective, judgeModelId, maxTurns, now, id) {
|
|
257
|
+
return {
|
|
258
|
+
objective,
|
|
259
|
+
status: "active",
|
|
260
|
+
runsUsed: 0,
|
|
261
|
+
activeDurationMs: 0,
|
|
262
|
+
maxRuns: maxTurns,
|
|
263
|
+
...judgeModelId ? { judgeModelId } : {},
|
|
264
|
+
startedAt: now,
|
|
265
|
+
updatedAt: now,
|
|
266
|
+
id
|
|
267
|
+
};
|
|
268
|
+
}
|
|
267
269
|
};
|
|
270
|
+
//#endregion
|
|
271
|
+
export { DEFAULT_MAX_TURNS, GoalManager };
|
|
272
|
+
|
|
268
273
|
//# sourceMappingURL=goal-manager.js.map
|