@caupulican/pi-adaptative 0.81.26 → 0.81.29
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 +45 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +4 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +19 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +30 -12
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +128 -36
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +6 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +35 -0
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
- package/dist/core/autonomy/bounded-completion.js +59 -19
- package/dist/core/autonomy/bounded-completion.js.map +1 -1
- package/dist/core/autonomy/contracts.d.ts +2 -0
- package/dist/core/autonomy/contracts.d.ts.map +1 -1
- package/dist/core/autonomy/contracts.js.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.d.ts +31 -0
- package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -0
- package/dist/core/autonomy/lane-tool-surface.js +96 -0
- package/dist/core/autonomy/lane-tool-surface.js.map +1 -0
- package/dist/core/autonomy/lane-tracker.d.ts +5 -0
- package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tracker.js +14 -3
- package/dist/core/autonomy/lane-tracker.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +28 -4
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +237 -28
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/billing-failover-controller.d.ts.map +1 -1
- package/dist/core/billing-failover-controller.js +1 -1
- package/dist/core/billing-failover-controller.js.map +1 -1
- package/dist/core/context/current-work-memory.d.ts +9 -0
- package/dist/core/context/current-work-memory.d.ts.map +1 -0
- package/dist/core/context/current-work-memory.js +61 -0
- package/dist/core/context/current-work-memory.js.map +1 -0
- package/dist/core/context/file-store-memory-provider.d.ts +9 -0
- package/dist/core/context/file-store-memory-provider.d.ts.map +1 -0
- package/dist/core/context/file-store-memory-provider.js +106 -0
- package/dist/core/context/file-store-memory-provider.js.map +1 -0
- package/dist/core/context/long-term-memory-trigger.d.ts +15 -0
- package/dist/core/context/long-term-memory-trigger.d.ts.map +1 -0
- package/dist/core/context/long-term-memory-trigger.js +39 -0
- package/dist/core/context/long-term-memory-trigger.js.map +1 -0
- package/dist/core/context/memory-prompt-block.d.ts +3 -1
- package/dist/core/context/memory-prompt-block.d.ts.map +1 -1
- package/dist/core/context/memory-prompt-block.js +20 -3
- package/dist/core/context/memory-prompt-block.js.map +1 -1
- package/dist/core/context/memory-prompt-budget.d.ts +17 -0
- package/dist/core/context/memory-prompt-budget.d.ts.map +1 -0
- package/dist/core/context/memory-prompt-budget.js +64 -0
- package/dist/core/context/memory-prompt-budget.js.map +1 -0
- package/dist/core/context/memory-provider-contract.d.ts.map +1 -1
- package/dist/core/context/memory-provider-contract.js +2 -5
- package/dist/core/context/memory-provider-contract.js.map +1 -1
- package/dist/core/context/memory-tier-composer.d.ts +27 -0
- package/dist/core/context/memory-tier-composer.d.ts.map +1 -0
- package/dist/core/context/memory-tier-composer.js +79 -0
- package/dist/core/context/memory-tier-composer.js.map +1 -0
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +2 -0
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/context-pipeline.d.ts +5 -3
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +15 -5
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/cost/cost-summary.d.ts +3 -1
- package/dist/core/cost/cost-summary.d.ts.map +1 -1
- package/dist/core/cost/cost-summary.js +3 -3
- package/dist/core/cost/cost-summary.js.map +1 -1
- package/dist/core/cost-guard.d.ts +17 -7
- package/dist/core/cost-guard.d.ts.map +1 -1
- package/dist/core/cost-guard.js +33 -10
- package/dist/core/cost-guard.js.map +1 -1
- package/dist/core/default-tool-surface.d.ts +9 -0
- package/dist/core/default-tool-surface.d.ts.map +1 -0
- package/dist/core/default-tool-surface.js +18 -0
- package/dist/core/default-tool-surface.js.map +1 -0
- package/dist/core/delegation/worker-result.d.ts.map +1 -1
- package/dist/core/delegation/worker-result.js +3 -0
- package/dist/core/delegation/worker-result.js.map +1 -1
- package/dist/core/delegation/worker-runner.d.ts +11 -5
- package/dist/core/delegation/worker-runner.d.ts.map +1 -1
- package/dist/core/delegation/worker-runner.js +77 -16
- package/dist/core/delegation/worker-runner.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +15 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +2 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +7 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/failure-corpus.d.ts.map +1 -1
- package/dist/core/failure-corpus.js +2 -4
- package/dist/core/failure-corpus.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +9 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.js +4 -3
- package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +12 -0
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +42 -4
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts +16 -0
- package/dist/core/local-runtime-controller.d.ts.map +1 -1
- package/dist/core/local-runtime-controller.js +107 -28
- package/dist/core/local-runtime-controller.js.map +1 -1
- package/dist/core/memory/memory-manager.d.ts +8 -3
- package/dist/core/memory/memory-manager.d.ts.map +1 -1
- package/dist/core/memory/memory-manager.js +27 -11
- package/dist/core/memory/memory-manager.js.map +1 -1
- package/dist/core/memory/memory-provider.d.ts +5 -1
- package/dist/core/memory/memory-provider.d.ts.map +1 -1
- package/dist/core/memory/memory-provider.js.map +1 -1
- package/dist/core/memory/providers/file-store.d.ts +3 -1
- package/dist/core/memory/providers/file-store.d.ts.map +1 -1
- package/dist/core/memory/providers/file-store.js +7 -2
- package/dist/core/memory/providers/file-store.js.map +1 -1
- package/dist/core/memory/providers/transcript-recall.d.ts +1 -0
- package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -1
- package/dist/core/memory/providers/transcript-recall.js +4 -7
- package/dist/core/memory/providers/transcript-recall.js.map +1 -1
- package/dist/core/memory-controller.d.ts +36 -10
- package/dist/core/memory-controller.d.ts.map +1 -1
- package/dist/core/memory-controller.js +137 -21
- package/dist/core/memory-controller.js.map +1 -1
- package/dist/core/model-registry.d.ts +25 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +91 -8
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +30 -11
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +33 -29
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/model-selection-controller.d.ts +3 -0
- package/dist/core/model-selection-controller.d.ts.map +1 -1
- package/dist/core/model-selection-controller.js +8 -1
- package/dist/core/model-selection-controller.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts +2 -0
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +10 -4
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/models/perf-profile.d.ts.map +1 -1
- package/dist/core/models/perf-profile.js +95 -33
- package/dist/core/models/perf-profile.js.map +1 -1
- package/dist/core/models/runtime-arbiter.d.ts +11 -0
- package/dist/core/models/runtime-arbiter.d.ts.map +1 -1
- package/dist/core/models/runtime-arbiter.js +35 -13
- package/dist/core/models/runtime-arbiter.js.map +1 -1
- package/dist/core/profile-filter-controller.d.ts +11 -0
- package/dist/core/profile-filter-controller.d.ts.map +1 -1
- package/dist/core/profile-filter-controller.js +56 -11
- package/dist/core/profile-filter-controller.js.map +1 -1
- package/dist/core/profile-registry.d.ts +2 -2
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +58 -7
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/reflection-controller.d.ts +9 -7
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +81 -12
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/research/research-runner.d.ts +2 -2
- package/dist/core/research/research-runner.d.ts.map +1 -1
- package/dist/core/research/research-runner.js +8 -6
- package/dist/core/research/research-runner.js.map +1 -1
- package/dist/core/resource-loader.d.ts +4 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +11 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +30 -3
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +140 -40
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/sdk.d.ts +8 -7
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +38 -5
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/security/secret-text.d.ts +3 -0
- package/dist/core/security/secret-text.d.ts.map +1 -0
- package/dist/core/security/secret-text.js +26 -0
- package/dist/core/security/secret-text.js.map +1 -0
- package/dist/core/settings-manager.d.ts +59 -17
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +300 -51
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +1 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +7 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.js +23 -1
- package/dist/core/system-prompt-builder.js.map +1 -1
- package/dist/core/tool-gate-controller.d.ts +3 -0
- package/dist/core/tool-gate-controller.d.ts.map +1 -1
- package/dist/core/tool-gate-controller.js +20 -15
- package/dist/core/tool-gate-controller.js.map +1 -1
- package/dist/core/tool-selection/expected-utility.d.ts +51 -0
- package/dist/core/tool-selection/expected-utility.d.ts.map +1 -0
- package/dist/core/tool-selection/expected-utility.js +85 -0
- package/dist/core/tool-selection/expected-utility.js.map +1 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts +82 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -0
- package/dist/core/tool-selection/tool-performance-store.js +249 -0
- package/dist/core/tool-selection/tool-performance-store.js.map +1 -0
- package/dist/core/tool-selection/tool-selection-controller.d.ts +41 -0
- package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -0
- package/dist/core/tool-selection/tool-selection-controller.js +199 -0
- package/dist/core/tool-selection/tool-selection-controller.js.map +1 -0
- package/dist/core/tools/delegate-status.d.ts +9 -0
- package/dist/core/tools/delegate-status.d.ts.map +1 -0
- package/dist/core/tools/delegate-status.js +52 -0
- package/dist/core/tools/delegate-status.js.map +1 -0
- package/dist/core/tools/delegate.d.ts +10 -0
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +36 -13
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/run-toolkit-script.d.ts +3 -0
- package/dist/core/tools/run-toolkit-script.d.ts.map +1 -1
- package/dist/core/tools/run-toolkit-script.js +17 -7
- package/dist/core/tools/run-toolkit-script.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +69 -29
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -0
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/config-backup.d.ts +7 -1
- package/dist/modes/interactive/config-backup.d.ts.map +1 -1
- package/dist/modes/interactive/config-backup.js +196 -32
- package/dist/modes/interactive/config-backup.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +22 -28
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +2 -0
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts +6 -4
- package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.js +248 -65
- package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
- package/dist/modes/interactive/report-commands.d.ts.map +1 -1
- package/dist/modes/interactive/report-commands.js +8 -2
- package/dist/modes/interactive/report-commands.js.map +1 -1
- package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -1
- package/dist/modes/interactive/settings-selector-flow.js +8 -0
- package/dist/modes/interactive/settings-selector-flow.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +3 -1
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/extensions.md +32 -0
- package/docs/integration-sweep-builder-blueprint-2026-07-09.md +365 -0
- package/docs/integration-sweep-resume-2026-07-09.md +407 -0
- package/docs/resources.md +15 -7
- package/docs/settings.md +56 -5
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/preset.ts +2 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +44 -35
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cost-summary.js","sourceRoot":"","sources":["../../../src/core/cost/cost-summary.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cost-summary.js","sourceRoot":"","sources":["../../../src/core/cost/cost-summary.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionEntry } from \"@caupulican/pi-agent-core/node\";\nimport type { AssistantMessage, Usage } from \"@caupulican/pi-ai\";\nimport { SPAWNED_USAGE_CUSTOM_TYPE, type SpawnedUsageReport } from \"../agent-session.ts\";\nimport type { DailyUsageTotals, DailyUsageWindow } from \"./daily-usage.ts\";\n\nexport interface CurrentSessionCostTotals {\n\townCost: number;\n\tsubagentCost: number;\n\tsubagentReports: number;\n\tcurrentCost: number;\n}\n\nexport interface SessionCostSummary extends CurrentSessionCostTotals {\n\ttodayCost: number;\n\ttodayOwnCost: number;\n\ttodaySubagentCost: number;\n\ttodayWindow: DailyUsageWindow;\n\ttodayRollover: \"local-midnight\";\n}\n\nfunction isFiniteCost(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction getUsageTotalCost(usage: Usage | undefined): number | undefined {\n\tconst total = usage?.cost?.total;\n\treturn isFiniteCost(total) ? total : undefined;\n}\n\nexport function aggregateCurrentSessionCostsFromEntries(entries: readonly SessionEntry[]): CurrentSessionCostTotals {\n\tlet ownCost = 0;\n\tlet subagentCost = 0;\n\tlet subagentReports = 0;\n\tconst seenSubagentReportIds = new Set<string>();\n\n\tfor (const entry of entries) {\n\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\tconst total = getUsageTotalCost((entry.message as AssistantMessage).usage);\n\t\t\tif (total !== undefined) ownCost += total;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (entry.type !== \"custom\" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE) continue;\n\t\tconst report = entry.data as SpawnedUsageReport | undefined;\n\t\tconst total = getUsageTotalCost(report?.usage);\n\t\tif (total === undefined) continue;\n\t\tif (report?.reportId) {\n\t\t\tif (seenSubagentReportIds.has(report.reportId)) continue;\n\t\t\tseenSubagentReportIds.add(report.reportId);\n\t\t}\n\t\tsubagentCost += total;\n\t\tsubagentReports += 1;\n\t}\n\n\treturn {\n\t\townCost,\n\t\tsubagentCost,\n\t\tsubagentReports,\n\t\tcurrentCost: ownCost + subagentCost,\n\t};\n}\n\nexport function createSessionCostSummary(args: {\n\tentries: readonly SessionEntry[];\n\tdailyTotals: DailyUsageTotals;\n\ttodayWindow: DailyUsageWindow;\n}): SessionCostSummary {\n\tconst current = aggregateCurrentSessionCostsFromEntries(args.entries);\n\treturn {\n\t\t...current,\n\t\ttodayCost: args.dailyTotals.totalCost,\n\t\ttodayOwnCost: args.dailyTotals.ownCost,\n\t\ttodaySubagentCost: args.dailyTotals.spawnedCost,\n\t\ttodayWindow: args.todayWindow,\n\t\ttodayRollover: \"local-midnight\",\n\t};\n}\n\nexport function hasCostSummarySignal(summary: SessionCostSummary): boolean {\n\treturn summary.currentCost > 0 || summary.todayCost > 0 || summary.subagentReports > 0;\n}\n\nexport function hasSubagentCostSignal(summary: Pick<SessionCostSummary, \"subagentReports\" | \"subagentCost\">): boolean {\n\treturn summary.subagentReports > 0 || summary.subagentCost > 0;\n}\n\nfunction formatCost(value: number, precision: number): string {\n\treturn `$${value.toFixed(precision)}`;\n}\n\nexport function formatFooterCostParts(\n\tsummary: SessionCostSummary,\n\tprecision = 3,\n\toptions: { subscription?: boolean } = {},\n): string[] {\n\tconst parts: string[] = [];\n\tif (summary.currentCost > 0 || hasSubagentCostSignal(summary) || options.subscription) {\n\t\tparts.push(`CURRENT:${formatCost(summary.currentCost, precision)}${options.subscription ? \" (sub)\" : \"\"}`);\n\t}\n\tif (summary.todayCost > 0) {\n\t\tparts.push(`TODAY:${formatCost(summary.todayCost, precision)}`);\n\t}\n\tif (hasSubagentCostSignal(summary)) {\n\t\tparts.push(`SUBAGENTS:${formatCost(summary.subagentCost, precision)} in CURRENT`);\n\t}\n\treturn parts;\n}\n\nexport function formatStatusCostSummary(summary: SessionCostSummary, precision = 4): string {\n\tconst parts = [\n\t\t`CURRENT ${formatCost(summary.currentCost, precision)}`,\n\t\t`TODAY ${formatCost(summary.todayCost, precision)}`,\n\t];\n\tif (hasSubagentCostSignal(summary)) {\n\t\tparts.push(`SUBAGENTS ${formatCost(summary.subagentCost, precision)} (included in CURRENT)`);\n\t}\n\treturn parts.join(\", \");\n}\n"]}
|
|
@@ -16,19 +16,26 @@ export interface ModelTokenCost {
|
|
|
16
16
|
/**
|
|
17
17
|
* Estimate the USD cost of one turn: the whole current context is billed as input, plus up to
|
|
18
18
|
* `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper
|
|
19
|
-
* cache-read rate
|
|
20
|
-
*
|
|
19
|
+
* cache-read rate. Fresh input is billed once at the higher of the uncached-input and cache-write
|
|
20
|
+
* rates because a provider may select that prefix for a new cache write. This is an UPPER bound on
|
|
21
|
+
* the turn (it also assumes the model emits its full output budget), which is what a spending ceiling
|
|
22
|
+
* should bound against.
|
|
21
23
|
*/
|
|
22
24
|
export declare function estimateTurnCostUsd(args: {
|
|
23
25
|
inputTokens: number;
|
|
24
26
|
maxOutputTokens: number;
|
|
25
27
|
cost: ModelTokenCost;
|
|
26
28
|
cachedInputTokens?: number;
|
|
29
|
+
longContextPricing?: {
|
|
30
|
+
thresholdTokens: number;
|
|
31
|
+
inputMultiplier: number;
|
|
32
|
+
outputMultiplier: number;
|
|
33
|
+
};
|
|
27
34
|
}): number;
|
|
28
35
|
/** What to do when a turn's projected cost exceeds the threshold. */
|
|
29
36
|
export type CostGuardAction = "warn" | "downgrade";
|
|
30
37
|
export interface CostGuardSettings {
|
|
31
|
-
/** Per-turn USD
|
|
38
|
+
/** Per-turn projected USD threshold. `0` disables the guard entirely. */
|
|
32
39
|
maxTurnUsd: number;
|
|
33
40
|
/** Over the ceiling: `warn` (surface a notice) or `downgrade` (also reduce reasoning effort). */
|
|
34
41
|
action: CostGuardAction;
|
|
@@ -44,12 +51,15 @@ export interface CostGuardDecision {
|
|
|
44
51
|
/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */
|
|
45
52
|
export declare function evaluateCostGuard(estUsd: number, settings: CostGuardSettings): CostGuardDecision;
|
|
46
53
|
/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */
|
|
47
|
-
declare const REASONING_LADDER: readonly ["xhigh", "high", "medium", "low", "minimal", "off"];
|
|
54
|
+
declare const REASONING_LADDER: readonly ["ultra", "max", "xhigh", "high", "medium", "low", "minimal", "off"];
|
|
48
55
|
export type ReasoningLevel = (typeof REASONING_LADDER)[number];
|
|
56
|
+
export type ReasoningEffortMap = Readonly<Partial<Record<ReasoningLevel, string | null>>>;
|
|
49
57
|
/**
|
|
50
|
-
* One step down the reasoning ladder (cost reduction) from `current`.
|
|
51
|
-
*
|
|
58
|
+
* One supported step down the reasoning ladder (cost reduction) from `current`. When
|
|
59
|
+
* `supportedLevels` is provided, unsupported intermediate levels are skipped instead of relying on a
|
|
60
|
+
* provider clamp that may move back upward. Returns `current` unchanged when already at the model's
|
|
61
|
+
* floor or unrecognized — the guard never raises effort, only lowers it.
|
|
52
62
|
*/
|
|
53
|
-
export declare function downgradeReasoning(current: string): ReasoningLevel | string;
|
|
63
|
+
export declare function downgradeReasoning(current: string, supportedLevels?: readonly ReasoningLevel[], effortMap?: ReasoningEffortMap): ReasoningLevel | string;
|
|
54
64
|
export {};
|
|
55
65
|
//# sourceMappingURL=cost-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cost-guard.d.ts","sourceRoot":"","sources":["../../src/core/cost-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gEAAgE;AAChE,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED
|
|
1
|
+
{"version":3,"file":"cost-guard.d.ts","sourceRoot":"","sources":["../../src/core/cost-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gEAAgE;AAChE,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC;CACpG,GAAG,MAAM,CAaT;AAED,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IACjC,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,iGAAiG;IACjG,MAAM,EAAE,eAAe,CAAC;CACxB;AAED,eAAO,MAAM,2BAA2B,EAAE,iBAGzC,CAAC;AAEF,MAAM,WAAW,iBAAiB;IACjC,iFAAiF;IACjF,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;CACxB;AAED,0GAA0G;AAC1G,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,iBAAiB,CAQhG;AAED,qGAAqG;AACrG,QAAA,MAAM,gBAAgB,+EAAgF,CAAC;AACvG,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAO1F;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,MAAM,EACf,eAAe,CAAC,EAAE,SAAS,cAAc,EAAE,EAC3C,SAAS,CAAC,EAAE,kBAAkB,GAC5B,cAAc,GAAG,MAAM,CAczB","sourcesContent":["/**\n * Proactive per-turn token cost guard (Hermes-parity superiority item #34).\n *\n * Hermes (and pi today) only react to context growth by compressing AFTER it's expensive. This estimates\n * the dollar cost of the NEXT LLM call BEFORE it is submitted, so the agent can warn the user or\n * automatically reduce reasoning effort before a runaway billing spike — a proactive ceiling, not a\n * reactive cleanup. Pure functions: no I/O, fully testable.\n */\n\n/** Per-million-token USD prices, as carried on `Model.cost`. */\nexport interface ModelTokenCost {\n\tinput: number;\n\toutput: number;\n\tcacheRead?: number;\n\tcacheWrite?: number;\n}\n\n/**\n * Estimate the USD cost of one turn: the whole current context is billed as input, plus up to\n * `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper\n * cache-read rate. Fresh input is billed once at the higher of the uncached-input and cache-write\n * rates because a provider may select that prefix for a new cache write. This is an UPPER bound on\n * the turn (it also assumes the model emits its full output budget), which is what a spending ceiling\n * should bound against.\n */\nexport function estimateTurnCostUsd(args: {\n\tinputTokens: number;\n\tmaxOutputTokens: number;\n\tcost: ModelTokenCost;\n\tcachedInputTokens?: number;\n\tlongContextPricing?: { thresholdTokens: number; inputMultiplier: number; outputMultiplier: number };\n}): number {\n\tconst { inputTokens, maxOutputTokens, cost } = args;\n\tconst cached = Math.max(0, Math.min(args.cachedInputTokens ?? 0, inputTokens));\n\tconst freshInput = inputTokens - cached;\n\tconst cacheReadRate = cost.cacheRead ?? cost.input;\n\tconst freshInputRate = Math.max(cost.input, cost.cacheWrite ?? cost.input);\n\tconst longContextPricing = args.longContextPricing;\n\tconst useLongContextTier = longContextPricing && inputTokens > longContextPricing.thresholdTokens;\n\tconst inputMultiplier = useLongContextTier ? longContextPricing.inputMultiplier : 1;\n\tconst outputMultiplier = useLongContextTier ? longContextPricing.outputMultiplier : 1;\n\tconst inputUsd = ((freshInput * freshInputRate + cached * cacheReadRate) * inputMultiplier) / 1_000_000;\n\tconst outputUsd = (Math.max(0, maxOutputTokens) * cost.output * outputMultiplier) / 1_000_000;\n\treturn inputUsd + outputUsd;\n}\n\n/** What to do when a turn's projected cost exceeds the threshold. */\nexport type CostGuardAction = \"warn\" | \"downgrade\";\n\nexport interface CostGuardSettings {\n\t/** Per-turn projected USD threshold. `0` disables the guard entirely. */\n\tmaxTurnUsd: number;\n\t/** Over the ceiling: `warn` (surface a notice) or `downgrade` (also reduce reasoning effort). */\n\taction: CostGuardAction;\n}\n\nexport const DEFAULT_COST_GUARD_SETTINGS: CostGuardSettings = {\n\tmaxTurnUsd: 2.5,\n\taction: \"warn\",\n};\n\nexport interface CostGuardDecision {\n\t/** True when the guard is enabled AND the projected cost exceeds the ceiling. */\n\tover: boolean;\n\testUsd: number;\n\tthresholdUsd: number;\n\taction: CostGuardAction;\n}\n\n/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */\nexport function evaluateCostGuard(estUsd: number, settings: CostGuardSettings): CostGuardDecision {\n\tconst enabled = settings.maxTurnUsd > 0;\n\treturn {\n\t\tover: enabled && estUsd > settings.maxTurnUsd,\n\t\testUsd,\n\t\tthresholdUsd: settings.maxTurnUsd,\n\t\taction: settings.action,\n\t};\n}\n\n/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */\nconst REASONING_LADDER = [\"ultra\", \"max\", \"xhigh\", \"high\", \"medium\", \"low\", \"minimal\", \"off\"] as const;\nexport type ReasoningLevel = (typeof REASONING_LADDER)[number];\nexport type ReasoningEffortMap = Readonly<Partial<Record<ReasoningLevel, string | null>>>;\n\nfunction effectiveReasoningEffort(level: ReasoningLevel, effortMap: ReasoningEffortMap | undefined): string | null {\n\tconst mapped = effortMap?.[level];\n\treturn mapped === undefined ? level : mapped;\n}\n\n/**\n * One supported step down the reasoning ladder (cost reduction) from `current`. When\n * `supportedLevels` is provided, unsupported intermediate levels are skipped instead of relying on a\n * provider clamp that may move back upward. Returns `current` unchanged when already at the model's\n * floor or unrecognized — the guard never raises effort, only lowers it.\n */\nexport function downgradeReasoning(\n\tcurrent: string,\n\tsupportedLevels?: readonly ReasoningLevel[],\n\teffortMap?: ReasoningEffortMap,\n): ReasoningLevel | string {\n\tconst i = REASONING_LADDER.indexOf(current as ReasoningLevel);\n\tif (i < 0) return current;\n\tif (!supportedLevels) {\n\t\treturn REASONING_LADDER[Math.min(i + 1, REASONING_LADDER.length - 1)];\n\t}\n\tconst supported = new Set<ReasoningLevel>(supportedLevels);\n\tconst currentEffort = effectiveReasoningEffort(current as ReasoningLevel, effortMap);\n\tfor (let nextIndex = i + 1; nextIndex < REASONING_LADDER.length; nextIndex++) {\n\t\tconst candidate = REASONING_LADDER[nextIndex];\n\t\tif (supported.has(candidate) && effectiveReasoningEffort(candidate, effortMap) !== currentEffort)\n\t\t\treturn candidate;\n\t}\n\treturn current;\n}\n"]}
|
package/dist/core/cost-guard.js
CHANGED
|
@@ -9,20 +9,27 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Estimate the USD cost of one turn: the whole current context is billed as input, plus up to
|
|
11
11
|
* `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper
|
|
12
|
-
* cache-read rate
|
|
13
|
-
*
|
|
12
|
+
* cache-read rate. Fresh input is billed once at the higher of the uncached-input and cache-write
|
|
13
|
+
* rates because a provider may select that prefix for a new cache write. This is an UPPER bound on
|
|
14
|
+
* the turn (it also assumes the model emits its full output budget), which is what a spending ceiling
|
|
15
|
+
* should bound against.
|
|
14
16
|
*/
|
|
15
17
|
export function estimateTurnCostUsd(args) {
|
|
16
18
|
const { inputTokens, maxOutputTokens, cost } = args;
|
|
17
19
|
const cached = Math.max(0, Math.min(args.cachedInputTokens ?? 0, inputTokens));
|
|
18
20
|
const freshInput = inputTokens - cached;
|
|
19
21
|
const cacheReadRate = cost.cacheRead ?? cost.input;
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
+
const freshInputRate = Math.max(cost.input, cost.cacheWrite ?? cost.input);
|
|
23
|
+
const longContextPricing = args.longContextPricing;
|
|
24
|
+
const useLongContextTier = longContextPricing && inputTokens > longContextPricing.thresholdTokens;
|
|
25
|
+
const inputMultiplier = useLongContextTier ? longContextPricing.inputMultiplier : 1;
|
|
26
|
+
const outputMultiplier = useLongContextTier ? longContextPricing.outputMultiplier : 1;
|
|
27
|
+
const inputUsd = ((freshInput * freshInputRate + cached * cacheReadRate) * inputMultiplier) / 1_000_000;
|
|
28
|
+
const outputUsd = (Math.max(0, maxOutputTokens) * cost.output * outputMultiplier) / 1_000_000;
|
|
22
29
|
return inputUsd + outputUsd;
|
|
23
30
|
}
|
|
24
31
|
export const DEFAULT_COST_GUARD_SETTINGS = {
|
|
25
|
-
maxTurnUsd:
|
|
32
|
+
maxTurnUsd: 2.5,
|
|
26
33
|
action: "warn",
|
|
27
34
|
};
|
|
28
35
|
/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */
|
|
@@ -36,15 +43,31 @@ export function evaluateCostGuard(estUsd, settings) {
|
|
|
36
43
|
};
|
|
37
44
|
}
|
|
38
45
|
/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */
|
|
39
|
-
const REASONING_LADDER = ["xhigh", "high", "medium", "low", "minimal", "off"];
|
|
46
|
+
const REASONING_LADDER = ["ultra", "max", "xhigh", "high", "medium", "low", "minimal", "off"];
|
|
47
|
+
function effectiveReasoningEffort(level, effortMap) {
|
|
48
|
+
const mapped = effortMap?.[level];
|
|
49
|
+
return mapped === undefined ? level : mapped;
|
|
50
|
+
}
|
|
40
51
|
/**
|
|
41
|
-
* One step down the reasoning ladder (cost reduction) from `current`.
|
|
42
|
-
*
|
|
52
|
+
* One supported step down the reasoning ladder (cost reduction) from `current`. When
|
|
53
|
+
* `supportedLevels` is provided, unsupported intermediate levels are skipped instead of relying on a
|
|
54
|
+
* provider clamp that may move back upward. Returns `current` unchanged when already at the model's
|
|
55
|
+
* floor or unrecognized — the guard never raises effort, only lowers it.
|
|
43
56
|
*/
|
|
44
|
-
export function downgradeReasoning(current) {
|
|
57
|
+
export function downgradeReasoning(current, supportedLevels, effortMap) {
|
|
45
58
|
const i = REASONING_LADDER.indexOf(current);
|
|
46
59
|
if (i < 0)
|
|
47
60
|
return current;
|
|
48
|
-
|
|
61
|
+
if (!supportedLevels) {
|
|
62
|
+
return REASONING_LADDER[Math.min(i + 1, REASONING_LADDER.length - 1)];
|
|
63
|
+
}
|
|
64
|
+
const supported = new Set(supportedLevels);
|
|
65
|
+
const currentEffort = effectiveReasoningEffort(current, effortMap);
|
|
66
|
+
for (let nextIndex = i + 1; nextIndex < REASONING_LADDER.length; nextIndex++) {
|
|
67
|
+
const candidate = REASONING_LADDER[nextIndex];
|
|
68
|
+
if (supported.has(candidate) && effectiveReasoningEffort(candidate, effortMap) !== currentEffort)
|
|
69
|
+
return candidate;
|
|
70
|
+
}
|
|
71
|
+
return current;
|
|
49
72
|
}
|
|
50
73
|
//# sourceMappingURL=cost-guard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cost-guard.js","sourceRoot":"","sources":["../../src/core/cost-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH
|
|
1
|
+
{"version":3,"file":"cost-guard.js","sourceRoot":"","sources":["../../src/core/cost-guard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Proactive per-turn token cost guard (Hermes-parity superiority item #34).\n *\n * Hermes (and pi today) only react to context growth by compressing AFTER it's expensive. This estimates\n * the dollar cost of the NEXT LLM call BEFORE it is submitted, so the agent can warn the user or\n * automatically reduce reasoning effort before a runaway billing spike — a proactive ceiling, not a\n * reactive cleanup. Pure functions: no I/O, fully testable.\n */\n\n/** Per-million-token USD prices, as carried on `Model.cost`. */\nexport interface ModelTokenCost {\n\tinput: number;\n\toutput: number;\n\tcacheRead?: number;\n\tcacheWrite?: number;\n}\n\n/**\n * Estimate the USD cost of one turn: the whole current context is billed as input, plus up to\n * `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper\n * cache-read rate. Fresh input is billed once at the higher of the uncached-input and cache-write\n * rates because a provider may select that prefix for a new cache write. This is an UPPER bound on\n * the turn (it also assumes the model emits its full output budget), which is what a spending ceiling\n * should bound against.\n */\nexport function estimateTurnCostUsd(args: {\n\tinputTokens: number;\n\tmaxOutputTokens: number;\n\tcost: ModelTokenCost;\n\tcachedInputTokens?: number;\n\tlongContextPricing?: { thresholdTokens: number; inputMultiplier: number; outputMultiplier: number };\n}): number {\n\tconst { inputTokens, maxOutputTokens, cost } = args;\n\tconst cached = Math.max(0, Math.min(args.cachedInputTokens ?? 0, inputTokens));\n\tconst freshInput = inputTokens - cached;\n\tconst cacheReadRate = cost.cacheRead ?? cost.input;\n\tconst freshInputRate = Math.max(cost.input, cost.cacheWrite ?? cost.input);\n\tconst longContextPricing = args.longContextPricing;\n\tconst useLongContextTier = longContextPricing && inputTokens > longContextPricing.thresholdTokens;\n\tconst inputMultiplier = useLongContextTier ? longContextPricing.inputMultiplier : 1;\n\tconst outputMultiplier = useLongContextTier ? longContextPricing.outputMultiplier : 1;\n\tconst inputUsd = ((freshInput * freshInputRate + cached * cacheReadRate) * inputMultiplier) / 1_000_000;\n\tconst outputUsd = (Math.max(0, maxOutputTokens) * cost.output * outputMultiplier) / 1_000_000;\n\treturn inputUsd + outputUsd;\n}\n\n/** What to do when a turn's projected cost exceeds the threshold. */\nexport type CostGuardAction = \"warn\" | \"downgrade\";\n\nexport interface CostGuardSettings {\n\t/** Per-turn projected USD threshold. `0` disables the guard entirely. */\n\tmaxTurnUsd: number;\n\t/** Over the ceiling: `warn` (surface a notice) or `downgrade` (also reduce reasoning effort). */\n\taction: CostGuardAction;\n}\n\nexport const DEFAULT_COST_GUARD_SETTINGS: CostGuardSettings = {\n\tmaxTurnUsd: 2.5,\n\taction: \"warn\",\n};\n\nexport interface CostGuardDecision {\n\t/** True when the guard is enabled AND the projected cost exceeds the ceiling. */\n\tover: boolean;\n\testUsd: number;\n\tthresholdUsd: number;\n\taction: CostGuardAction;\n}\n\n/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */\nexport function evaluateCostGuard(estUsd: number, settings: CostGuardSettings): CostGuardDecision {\n\tconst enabled = settings.maxTurnUsd > 0;\n\treturn {\n\t\tover: enabled && estUsd > settings.maxTurnUsd,\n\t\testUsd,\n\t\tthresholdUsd: settings.maxTurnUsd,\n\t\taction: settings.action,\n\t};\n}\n\n/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */\nconst REASONING_LADDER = [\"ultra\", \"max\", \"xhigh\", \"high\", \"medium\", \"low\", \"minimal\", \"off\"] as const;\nexport type ReasoningLevel = (typeof REASONING_LADDER)[number];\nexport type ReasoningEffortMap = Readonly<Partial<Record<ReasoningLevel, string | null>>>;\n\nfunction effectiveReasoningEffort(level: ReasoningLevel, effortMap: ReasoningEffortMap | undefined): string | null {\n\tconst mapped = effortMap?.[level];\n\treturn mapped === undefined ? level : mapped;\n}\n\n/**\n * One supported step down the reasoning ladder (cost reduction) from `current`. When\n * `supportedLevels` is provided, unsupported intermediate levels are skipped instead of relying on a\n * provider clamp that may move back upward. Returns `current` unchanged when already at the model's\n * floor or unrecognized — the guard never raises effort, only lowers it.\n */\nexport function downgradeReasoning(\n\tcurrent: string,\n\tsupportedLevels?: readonly ReasoningLevel[],\n\teffortMap?: ReasoningEffortMap,\n): ReasoningLevel | string {\n\tconst i = REASONING_LADDER.indexOf(current as ReasoningLevel);\n\tif (i < 0) return current;\n\tif (!supportedLevels) {\n\t\treturn REASONING_LADDER[Math.min(i + 1, REASONING_LADDER.length - 1)];\n\t}\n\tconst supported = new Set<ReasoningLevel>(supportedLevels);\n\tconst currentEffort = effectiveReasoningEffort(current as ReasoningLevel, effortMap);\n\tfor (let nextIndex = i + 1; nextIndex < REASONING_LADDER.length; nextIndex++) {\n\t\tconst candidate = REASONING_LADDER[nextIndex];\n\t\tif (supported.has(candidate) && effectiveReasoningEffort(candidate, effortMap) !== currentEffort)\n\t\t\treturn candidate;\n\t}\n\treturn current;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default built-in tool request shared by every AgentSession construction path.
|
|
3
|
+
*
|
|
4
|
+
* RuntimeBuilder still registers additional opt-in tools, while model capability and UAC profile
|
|
5
|
+
* filters may only narrow this request. Keeping the SDK and direct-runtime paths on one list avoids
|
|
6
|
+
* silently losing goal/delegation/toolkit capabilities in SDK-created interactive sessions.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_ACTIVE_TOOL_NAMES: readonly string[];
|
|
9
|
+
//# sourceMappingURL=default-tool-surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-tool-surface.d.ts","sourceRoot":"","sources":["../../src/core/default-tool-surface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAStD,CAAC","sourcesContent":["/**\n * Default built-in tool request shared by every AgentSession construction path.\n *\n * RuntimeBuilder still registers additional opt-in tools, while model capability and UAC profile\n * filters may only narrow this request. Keeping the SDK and direct-runtime paths on one list avoids\n * silently losing goal/delegation/toolkit capabilities in SDK-created interactive sessions.\n */\nexport const DEFAULT_ACTIVE_TOOL_NAMES: readonly string[] = [\n\t\"read\",\n\t\"bash\",\n\t\"edit\",\n\t\"write\",\n\t\"context_audit\",\n\t\"goal\",\n\t\"delegate\",\n\t\"run_toolkit_script\",\n];\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default built-in tool request shared by every AgentSession construction path.
|
|
3
|
+
*
|
|
4
|
+
* RuntimeBuilder still registers additional opt-in tools, while model capability and UAC profile
|
|
5
|
+
* filters may only narrow this request. Keeping the SDK and direct-runtime paths on one list avoids
|
|
6
|
+
* silently losing goal/delegation/toolkit capabilities in SDK-created interactive sessions.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_ACTIVE_TOOL_NAMES = [
|
|
9
|
+
"read",
|
|
10
|
+
"bash",
|
|
11
|
+
"edit",
|
|
12
|
+
"write",
|
|
13
|
+
"context_audit",
|
|
14
|
+
"goal",
|
|
15
|
+
"delegate",
|
|
16
|
+
"run_toolkit_script",
|
|
17
|
+
];
|
|
18
|
+
//# sourceMappingURL=default-tool-surface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-tool-surface.js","sourceRoot":"","sources":["../../src/core/default-tool-surface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC3D,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,eAAe;IACf,MAAM;IACN,UAAU;IACV,oBAAoB;CACpB,CAAC","sourcesContent":["/**\n * Default built-in tool request shared by every AgentSession construction path.\n *\n * RuntimeBuilder still registers additional opt-in tools, while model capability and UAC profile\n * filters may only narrow this request. Keeping the SDK and direct-runtime paths on one list avoids\n * silently losing goal/delegation/toolkit capabilities in SDK-created interactive sessions.\n */\nexport const DEFAULT_ACTIVE_TOOL_NAMES: readonly string[] = [\n\t\"read\",\n\t\"bash\",\n\t\"edit\",\n\t\"write\",\n\t\"context_audit\",\n\t\"goal\",\n\t\"delegate\",\n\t\"run_toolkit_script\",\n];\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-result.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-result.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAIzF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"worker-result.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-result.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAIzF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAQ9E;AAQD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CA4BpE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAWlE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,GAAG,WAAW,CAmHd","sourcesContent":["import path from \"node:path\";\nimport type { GateOutcome, WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport { checkPathScope } from \"../autonomy/path-scope.ts\";\nimport { cloneEvidenceBundleForStorage, isEvidenceBundle } from \"../research/evidence-bundle.ts\";\n\nexport function cloneWorkerResultForStorage(result: WorkerResult): WorkerResult {\n\treturn {\n\t\t...result,\n\t\t...(result.outputFormat ? { outputFormat: result.outputFormat } : {}),\n\t\tchangedFiles: [...result.changedFiles],\n\t\tblockers: result.blockers ? [...result.blockers] : undefined,\n\t\tevidence: result.evidence ? cloneEvidenceBundleForStorage(result.evidence) : undefined,\n\t};\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function isWorkerResult(value: unknown): value is WorkerResult {\n\tif (!isPlainRecord(value)) return false;\n\tconst obj = value as Record<string, unknown>;\n\n\tif (typeof obj.requestId !== \"string\") return false;\n\tif (typeof obj.status !== \"string\" || ![\"completed\", \"blocked\", \"failed\", \"cancelled\"].includes(obj.status)) {\n\t\treturn false;\n\t}\n\tif (typeof obj.summary !== \"string\") return false;\n\tif (obj.outputFormat !== undefined && obj.outputFormat !== \"structured\" && obj.outputFormat !== \"plain_text\")\n\t\treturn false;\n\n\tif (!Array.isArray(obj.changedFiles) || !obj.changedFiles.every((f) => typeof f === \"string\")) {\n\t\treturn false;\n\t}\n\n\tif (obj.blockers !== undefined) {\n\t\tif (!Array.isArray(obj.blockers) || !obj.blockers.every((b) => typeof b === \"string\")) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (obj.usageReportId !== undefined && typeof obj.usageReportId !== \"string\") return false;\n\tif (obj.createdAt !== undefined && typeof obj.createdAt !== \"string\") return false;\n\n\tif (obj.evidence !== undefined && !isEvidenceBundle(obj.evidence)) return false;\n\n\treturn true;\n}\n\nexport function requiresParentReview(result: WorkerResult): boolean {\n\tif (result.status !== \"completed\") {\n\t\treturn true;\n\t}\n\tif (result.blockers && result.blockers.length > 0) {\n\t\treturn true;\n\t}\n\tif (result.changedFiles.length > 0) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nexport function validateWorkerResult(args: {\n\trequest: WorkerRequest;\n\tresult: WorkerResult;\n\t/**\n\t * Baseline for relative paths — BOTH the runner's cwd-relative `changedFiles` and the\n\t * envelope's possibly-relative path scopes resolve against this. Defaults to process.cwd()\n\t * for callers whose session cwd is the process cwd.\n\t */\n\tcwd?: string;\n}): GateOutcome {\n\tconst { request, result } = args;\n\tconst baseDir = args.cwd ?? process.cwd();\n\n\tif (result.requestId !== request.id) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"request_id_mismatch\",\n\t\t\tmessage: `Result requestId '${result.requestId}' does not match request id '${request.id}'.`,\n\t\t};\n\t}\n\n\tif (result.status !== \"completed\") {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"worker_not_completed\",\n\t\t\tmessage: `Worker finished with status '${result.status}'.`,\n\t\t\tdetails: result.blockers && result.blockers.length > 0 ? { blockers: [...result.blockers] } : undefined,\n\t\t};\n\t}\n\n\tif (!result.usageReportId) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"missing_usage_report\",\n\t\t\tmessage: \"Completed worker result is missing usageReportId.\",\n\t\t};\n\t}\n\n\tif (result.blockers && result.blockers.length > 0) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"parent_review_required\",\n\t\t\tmessage: \"Completed worker result includes blockers and requires parent review.\",\n\t\t\tdetails: { blockers: [...result.blockers] },\n\t\t};\n\t}\n\n\tif (result.changedFiles.length > 0) {\n\t\tif (!request.envelope.allowedPaths || request.envelope.allowedPaths.length === 0) {\n\t\t\treturn {\n\t\t\t\toutcome: \"block\",\n\t\t\t\tgate: \"worker_result\",\n\t\t\t\treasonCode: \"missing_path_scope\",\n\t\t\t\tmessage: \"Worker changed files but no allowedPaths are configured in the envelope.\",\n\t\t\t};\n\t\t}\n\n\t\t// The runner reports changed files relative to the session cwd — resolve file and scope\n\t\t// roots against the SAME baseline. Resolving per-root double-prefixed nested names and\n\t\t// let a denied subtree slip past the deny rule.\n\t\tconst resolvedAllowed = request.envelope.allowedPaths.map((p) => path.resolve(baseDir, p));\n\t\tconst resolvedDenied = request.envelope.deniedPaths?.map((p) => path.resolve(baseDir, p));\n\t\tfor (const changedFile of result.changedFiles) {\n\t\t\tlet isInsideAny = false;\n\t\t\tlet isDenied = false;\n\n\t\t\tconst scopedChangedFile = path.resolve(baseDir, changedFile);\n\t\t\tfor (const root of resolvedAllowed) {\n\t\t\t\tconst decision = checkPathScope(\n\t\t\t\t\t{\n\t\t\t\t\t\troot,\n\t\t\t\t\t\tallowedPaths: resolvedAllowed,\n\t\t\t\t\t\tdeniedPaths: resolvedDenied,\n\t\t\t\t\t},\n\t\t\t\t\tscopedChangedFile,\n\t\t\t\t);\n\n\t\t\t\tif (decision.kind === \"denied\") {\n\t\t\t\t\tisDenied = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (decision.kind === \"inside\") {\n\t\t\t\t\tisInsideAny = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isDenied) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"worker_result\",\n\t\t\t\t\treasonCode: \"changed_file_denied\",\n\t\t\t\t\tmessage: `Worker changed file '${changedFile}' which matches a denied path.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!isInsideAny) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"worker_result\",\n\t\t\t\t\treasonCode: \"changed_file_outside_scope\",\n\t\t\t\t\tmessage: `Worker changed file '${changedFile}' outside allowed scope.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Files are inside scope, but worker output is untrusted\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"parent_review_required\",\n\t\t\tmessage: \"Worker changed files require parent review.\",\n\t\t};\n\t}\n\n\treturn {\n\t\toutcome: \"allow\",\n\t\tgate: \"worker_result\",\n\t\treasonCode: \"allowed\",\n\t\tmessage: \"Worker result is read-only and allowed.\",\n\t};\n}\n"]}
|
|
@@ -4,6 +4,7 @@ import { cloneEvidenceBundleForStorage, isEvidenceBundle } from "../research/evi
|
|
|
4
4
|
export function cloneWorkerResultForStorage(result) {
|
|
5
5
|
return {
|
|
6
6
|
...result,
|
|
7
|
+
...(result.outputFormat ? { outputFormat: result.outputFormat } : {}),
|
|
7
8
|
changedFiles: [...result.changedFiles],
|
|
8
9
|
blockers: result.blockers ? [...result.blockers] : undefined,
|
|
9
10
|
evidence: result.evidence ? cloneEvidenceBundleForStorage(result.evidence) : undefined,
|
|
@@ -26,6 +27,8 @@ export function isWorkerResult(value) {
|
|
|
26
27
|
}
|
|
27
28
|
if (typeof obj.summary !== "string")
|
|
28
29
|
return false;
|
|
30
|
+
if (obj.outputFormat !== undefined && obj.outputFormat !== "structured" && obj.outputFormat !== "plain_text")
|
|
31
|
+
return false;
|
|
29
32
|
if (!Array.isArray(obj.changedFiles) || !obj.changedFiles.every((f) => typeof f === "string")) {
|
|
30
33
|
return false;
|
|
31
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-result.js","sourceRoot":"","sources":["../../../src/core/delegation/worker-result.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport type { GateOutcome, WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport { checkPathScope } from \"../autonomy/path-scope.ts\";\nimport { cloneEvidenceBundleForStorage, isEvidenceBundle } from \"../research/evidence-bundle.ts\";\n\nexport function cloneWorkerResultForStorage(result: WorkerResult): WorkerResult {\n\treturn {\n\t\t...result,\n\t\tchangedFiles: [...result.changedFiles],\n\t\tblockers: result.blockers ? [...result.blockers] : undefined,\n\t\tevidence: result.evidence ? cloneEvidenceBundleForStorage(result.evidence) : undefined,\n\t};\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function isWorkerResult(value: unknown): value is WorkerResult {\n\tif (!isPlainRecord(value)) return false;\n\tconst obj = value as Record<string, unknown>;\n\n\tif (typeof obj.requestId !== \"string\") return false;\n\tif (typeof obj.status !== \"string\" || ![\"completed\", \"blocked\", \"failed\", \"cancelled\"].includes(obj.status)) {\n\t\treturn false;\n\t}\n\tif (typeof obj.summary !== \"string\") return false;\n\n\tif (!Array.isArray(obj.changedFiles) || !obj.changedFiles.every((f) => typeof f === \"string\")) {\n\t\treturn false;\n\t}\n\n\tif (obj.blockers !== undefined) {\n\t\tif (!Array.isArray(obj.blockers) || !obj.blockers.every((b) => typeof b === \"string\")) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (obj.usageReportId !== undefined && typeof obj.usageReportId !== \"string\") return false;\n\tif (obj.createdAt !== undefined && typeof obj.createdAt !== \"string\") return false;\n\n\tif (obj.evidence !== undefined && !isEvidenceBundle(obj.evidence)) return false;\n\n\treturn true;\n}\n\nexport function requiresParentReview(result: WorkerResult): boolean {\n\tif (result.status !== \"completed\") {\n\t\treturn true;\n\t}\n\tif (result.blockers && result.blockers.length > 0) {\n\t\treturn true;\n\t}\n\tif (result.changedFiles.length > 0) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nexport function validateWorkerResult(args: {\n\trequest: WorkerRequest;\n\tresult: WorkerResult;\n\t/**\n\t * Baseline for relative paths — BOTH the runner's cwd-relative `changedFiles` and the\n\t * envelope's possibly-relative path scopes resolve against this. Defaults to process.cwd()\n\t * for callers whose session cwd is the process cwd.\n\t */\n\tcwd?: string;\n}): GateOutcome {\n\tconst { request, result } = args;\n\tconst baseDir = args.cwd ?? process.cwd();\n\n\tif (result.requestId !== request.id) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"request_id_mismatch\",\n\t\t\tmessage: `Result requestId '${result.requestId}' does not match request id '${request.id}'.`,\n\t\t};\n\t}\n\n\tif (result.status !== \"completed\") {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"worker_not_completed\",\n\t\t\tmessage: `Worker finished with status '${result.status}'.`,\n\t\t\tdetails: result.blockers && result.blockers.length > 0 ? { blockers: [...result.blockers] } : undefined,\n\t\t};\n\t}\n\n\tif (!result.usageReportId) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"missing_usage_report\",\n\t\t\tmessage: \"Completed worker result is missing usageReportId.\",\n\t\t};\n\t}\n\n\tif (result.blockers && result.blockers.length > 0) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"parent_review_required\",\n\t\t\tmessage: \"Completed worker result includes blockers and requires parent review.\",\n\t\t\tdetails: { blockers: [...result.blockers] },\n\t\t};\n\t}\n\n\tif (result.changedFiles.length > 0) {\n\t\tif (!request.envelope.allowedPaths || request.envelope.allowedPaths.length === 0) {\n\t\t\treturn {\n\t\t\t\toutcome: \"block\",\n\t\t\t\tgate: \"worker_result\",\n\t\t\t\treasonCode: \"missing_path_scope\",\n\t\t\t\tmessage: \"Worker changed files but no allowedPaths are configured in the envelope.\",\n\t\t\t};\n\t\t}\n\n\t\t// The runner reports changed files relative to the session cwd — resolve file and scope\n\t\t// roots against the SAME baseline. Resolving per-root double-prefixed nested names and\n\t\t// let a denied subtree slip past the deny rule.\n\t\tconst resolvedAllowed = request.envelope.allowedPaths.map((p) => path.resolve(baseDir, p));\n\t\tconst resolvedDenied = request.envelope.deniedPaths?.map((p) => path.resolve(baseDir, p));\n\t\tfor (const changedFile of result.changedFiles) {\n\t\t\tlet isInsideAny = false;\n\t\t\tlet isDenied = false;\n\n\t\t\tconst scopedChangedFile = path.resolve(baseDir, changedFile);\n\t\t\tfor (const root of resolvedAllowed) {\n\t\t\t\tconst decision = checkPathScope(\n\t\t\t\t\t{\n\t\t\t\t\t\troot,\n\t\t\t\t\t\tallowedPaths: resolvedAllowed,\n\t\t\t\t\t\tdeniedPaths: resolvedDenied,\n\t\t\t\t\t},\n\t\t\t\t\tscopedChangedFile,\n\t\t\t\t);\n\n\t\t\t\tif (decision.kind === \"denied\") {\n\t\t\t\t\tisDenied = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (decision.kind === \"inside\") {\n\t\t\t\t\tisInsideAny = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isDenied) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"worker_result\",\n\t\t\t\t\treasonCode: \"changed_file_denied\",\n\t\t\t\t\tmessage: `Worker changed file '${changedFile}' which matches a denied path.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!isInsideAny) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"worker_result\",\n\t\t\t\t\treasonCode: \"changed_file_outside_scope\",\n\t\t\t\t\tmessage: `Worker changed file '${changedFile}' outside allowed scope.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Files are inside scope, but worker output is untrusted\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"parent_review_required\",\n\t\t\tmessage: \"Worker changed files require parent review.\",\n\t\t};\n\t}\n\n\treturn {\n\t\toutcome: \"allow\",\n\t\tgate: \"worker_result\",\n\t\treasonCode: \"allowed\",\n\t\tmessage: \"Worker result is read-only and allowed.\",\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"worker-result.js","sourceRoot":"","sources":["../../../src/core/delegation/worker-result.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport type { GateOutcome, WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport { checkPathScope } from \"../autonomy/path-scope.ts\";\nimport { cloneEvidenceBundleForStorage, isEvidenceBundle } from \"../research/evidence-bundle.ts\";\n\nexport function cloneWorkerResultForStorage(result: WorkerResult): WorkerResult {\n\treturn {\n\t\t...result,\n\t\t...(result.outputFormat ? { outputFormat: result.outputFormat } : {}),\n\t\tchangedFiles: [...result.changedFiles],\n\t\tblockers: result.blockers ? [...result.blockers] : undefined,\n\t\tevidence: result.evidence ? cloneEvidenceBundleForStorage(result.evidence) : undefined,\n\t};\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function isWorkerResult(value: unknown): value is WorkerResult {\n\tif (!isPlainRecord(value)) return false;\n\tconst obj = value as Record<string, unknown>;\n\n\tif (typeof obj.requestId !== \"string\") return false;\n\tif (typeof obj.status !== \"string\" || ![\"completed\", \"blocked\", \"failed\", \"cancelled\"].includes(obj.status)) {\n\t\treturn false;\n\t}\n\tif (typeof obj.summary !== \"string\") return false;\n\tif (obj.outputFormat !== undefined && obj.outputFormat !== \"structured\" && obj.outputFormat !== \"plain_text\")\n\t\treturn false;\n\n\tif (!Array.isArray(obj.changedFiles) || !obj.changedFiles.every((f) => typeof f === \"string\")) {\n\t\treturn false;\n\t}\n\n\tif (obj.blockers !== undefined) {\n\t\tif (!Array.isArray(obj.blockers) || !obj.blockers.every((b) => typeof b === \"string\")) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (obj.usageReportId !== undefined && typeof obj.usageReportId !== \"string\") return false;\n\tif (obj.createdAt !== undefined && typeof obj.createdAt !== \"string\") return false;\n\n\tif (obj.evidence !== undefined && !isEvidenceBundle(obj.evidence)) return false;\n\n\treturn true;\n}\n\nexport function requiresParentReview(result: WorkerResult): boolean {\n\tif (result.status !== \"completed\") {\n\t\treturn true;\n\t}\n\tif (result.blockers && result.blockers.length > 0) {\n\t\treturn true;\n\t}\n\tif (result.changedFiles.length > 0) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nexport function validateWorkerResult(args: {\n\trequest: WorkerRequest;\n\tresult: WorkerResult;\n\t/**\n\t * Baseline for relative paths — BOTH the runner's cwd-relative `changedFiles` and the\n\t * envelope's possibly-relative path scopes resolve against this. Defaults to process.cwd()\n\t * for callers whose session cwd is the process cwd.\n\t */\n\tcwd?: string;\n}): GateOutcome {\n\tconst { request, result } = args;\n\tconst baseDir = args.cwd ?? process.cwd();\n\n\tif (result.requestId !== request.id) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"request_id_mismatch\",\n\t\t\tmessage: `Result requestId '${result.requestId}' does not match request id '${request.id}'.`,\n\t\t};\n\t}\n\n\tif (result.status !== \"completed\") {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"worker_not_completed\",\n\t\t\tmessage: `Worker finished with status '${result.status}'.`,\n\t\t\tdetails: result.blockers && result.blockers.length > 0 ? { blockers: [...result.blockers] } : undefined,\n\t\t};\n\t}\n\n\tif (!result.usageReportId) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"missing_usage_report\",\n\t\t\tmessage: \"Completed worker result is missing usageReportId.\",\n\t\t};\n\t}\n\n\tif (result.blockers && result.blockers.length > 0) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"parent_review_required\",\n\t\t\tmessage: \"Completed worker result includes blockers and requires parent review.\",\n\t\t\tdetails: { blockers: [...result.blockers] },\n\t\t};\n\t}\n\n\tif (result.changedFiles.length > 0) {\n\t\tif (!request.envelope.allowedPaths || request.envelope.allowedPaths.length === 0) {\n\t\t\treturn {\n\t\t\t\toutcome: \"block\",\n\t\t\t\tgate: \"worker_result\",\n\t\t\t\treasonCode: \"missing_path_scope\",\n\t\t\t\tmessage: \"Worker changed files but no allowedPaths are configured in the envelope.\",\n\t\t\t};\n\t\t}\n\n\t\t// The runner reports changed files relative to the session cwd — resolve file and scope\n\t\t// roots against the SAME baseline. Resolving per-root double-prefixed nested names and\n\t\t// let a denied subtree slip past the deny rule.\n\t\tconst resolvedAllowed = request.envelope.allowedPaths.map((p) => path.resolve(baseDir, p));\n\t\tconst resolvedDenied = request.envelope.deniedPaths?.map((p) => path.resolve(baseDir, p));\n\t\tfor (const changedFile of result.changedFiles) {\n\t\t\tlet isInsideAny = false;\n\t\t\tlet isDenied = false;\n\n\t\t\tconst scopedChangedFile = path.resolve(baseDir, changedFile);\n\t\t\tfor (const root of resolvedAllowed) {\n\t\t\t\tconst decision = checkPathScope(\n\t\t\t\t\t{\n\t\t\t\t\t\troot,\n\t\t\t\t\t\tallowedPaths: resolvedAllowed,\n\t\t\t\t\t\tdeniedPaths: resolvedDenied,\n\t\t\t\t\t},\n\t\t\t\t\tscopedChangedFile,\n\t\t\t\t);\n\n\t\t\t\tif (decision.kind === \"denied\") {\n\t\t\t\t\tisDenied = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (decision.kind === \"inside\") {\n\t\t\t\t\tisInsideAny = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isDenied) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"worker_result\",\n\t\t\t\t\treasonCode: \"changed_file_denied\",\n\t\t\t\t\tmessage: `Worker changed file '${changedFile}' which matches a denied path.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!isInsideAny) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"worker_result\",\n\t\t\t\t\treasonCode: \"changed_file_outside_scope\",\n\t\t\t\t\tmessage: `Worker changed file '${changedFile}' outside allowed scope.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Files are inside scope, but worker output is untrusted\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"worker_result\",\n\t\t\treasonCode: \"parent_review_required\",\n\t\t\tmessage: \"Worker changed files require parent review.\",\n\t\t};\n\t}\n\n\treturn {\n\t\toutcome: \"allow\",\n\t\tgate: \"worker_result\",\n\t\treasonCode: \"allowed\",\n\t\tmessage: \"Worker result is read-only and allowed.\",\n\t};\n}\n"]}
|
|
@@ -5,19 +5,23 @@ import { type AppliedActionsReport, type WorkerAction } from "./worker-actions.t
|
|
|
5
5
|
* Pure orchestration for one bounded scout-worker delegation: bounded isolated completion ->
|
|
6
6
|
* parse -> `WorkerResult` -> parent validation via {@link validateWorkerResult}.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The injected completion may be a bounded child tool loop. Its tool surface is built and gated by
|
|
9
|
+
* the host; this module keeps the structured-output contract and treats every result as untrusted
|
|
10
|
+
* until parent validation succeeds.
|
|
11
11
|
*/
|
|
12
12
|
/** Static across calls so callers can use `cacheRetention: "short"`. */
|
|
13
13
|
export declare const WORKER_LANE_SYSTEM_PROMPT: string;
|
|
14
|
-
/** Write-capable variant (G2):
|
|
15
|
-
*
|
|
14
|
+
/** Write-capable variant (G2): direct tools are execution-gated and reported; structured actions
|
|
15
|
+
* remain as a fallback for models without a native tool channel and use the same path scope. */
|
|
16
16
|
export declare const WORKER_WRITE_LANE_SYSTEM_PROMPT: string;
|
|
17
17
|
export interface WorkerCompletion {
|
|
18
18
|
text: string;
|
|
19
19
|
costUsd: number;
|
|
20
20
|
stopReason: string;
|
|
21
|
+
/** Files successfully changed by the child tool loop before it produced the final JSON. */
|
|
22
|
+
changedFiles?: readonly string[];
|
|
23
|
+
/** Capability refusals or execution failures observed inside the child tool loop. */
|
|
24
|
+
blockers?: readonly string[];
|
|
21
25
|
}
|
|
22
26
|
export interface WorkerRunnerOptions {
|
|
23
27
|
request: WorkerRequest;
|
|
@@ -35,6 +39,8 @@ export interface WorkerRunnerOptions {
|
|
|
35
39
|
userPrompt: string;
|
|
36
40
|
signal?: AbortSignal;
|
|
37
41
|
}) => Promise<WorkerCompletion>;
|
|
42
|
+
/** Live successful child-tool mutations, including writes completed before timeout/cancellation. */
|
|
43
|
+
getChangedFiles?: () => readonly string[];
|
|
38
44
|
signal?: AbortSignal;
|
|
39
45
|
now?: () => string;
|
|
40
46
|
/** Enables the WRITE lane: only honored when the request envelope grants "write_files". The
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAwB,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,KAAK,oBAAoB,EAAsB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGvG;;;;;;;GAOG;AAEH,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,QAO1B,CAAC;AAEb;2FAC2F;AAC3F,eAAO,MAAM,+BAA+B,QAQhC,CAAC;AAEb,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;2DAEuD;IACvD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,oBAAoB,CAAC;IAC1E;gDAC4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,8FAA8F;IAC9F,UAAU,EAAE,WAAW,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAEpE;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CA0C9E;AAiDD,wBAAsB,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiHvF","sourcesContent":["import { runBoundedCompletion } from \"../autonomy/bounded-completion.ts\";\nimport type { EvidenceRef, Finding, GateOutcome, WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { LaneTerminalStatus } from \"../autonomy/lane-tracker.ts\";\nimport { createEvidenceBundle } from \"../research/evidence-bundle.ts\";\nimport { type AppliedActionsReport, parseWorkerActions, type WorkerAction } from \"./worker-actions.ts\";\nimport { validateWorkerResult } from \"./worker-result.ts\";\n\n/**\n * Pure orchestration for one bounded scout-worker delegation: bounded isolated completion ->\n * parse -> `WorkerResult` -> parent validation via {@link validateWorkerResult}.\n *\n * Slice scope: scout (read-only) workers only — the completion receives text prompts, no tools, so\n * `changedFiles` is always empty. Code-writing workers stay out until a real execution envelope\n * enforces path scope at tool level. Worker output is untrusted until the parent verifies it.\n */\n\n/** Static across calls so callers can use `cacheRetention: \"short\"`. */\nexport const WORKER_LANE_SYSTEM_PROMPT = [\n\t\"You are a bounded read-only scout worker delegated one task by a coding agent.\",\n\t\"You cannot run tools or change files; produce your best analysis of the delegated task.\",\n\t\"Respond with STRICT JSON only - no prose, no markdown fences:\",\n\t'{\"summary\":\"<what you concluded>\",\"status\":\"completed\"|\"blocked\",\"blockers\":[\"<why you are stuck>\"],\"findings\":[{\"summary\":\"<one concrete finding>\",\"confidence\":<0..1>}]}',\n\t'Use status \"blocked\" with blockers only when the task cannot be answered from the provided context.',\n\t\"Never invent file paths, APIs, or facts.\",\n].join(\"\\n\");\n\n/** Write-capable variant (G2): same contract plus a structured actions array — the model never\n * touches the filesystem; the runner applies actions through the envelope's path scope. */\nexport const WORKER_WRITE_LANE_SYSTEM_PROMPT = [\n\t\"You are a bounded code-writing worker delegated one task by a coding agent.\",\n\t\"You cannot run tools; you CHANGE FILES only by listing actions the runner applies for you.\",\n\t\"Respond with STRICT JSON only - no prose, no markdown fences:\",\n\t'{\"summary\":\"<what you did>\",\"status\":\"completed\"|\"blocked\",\"blockers\":[],\"findings\":[{\"summary\":\"<finding>\",\"confidence\":<0..1>}],\"actions\":[{\"op\":\"write\",\"path\":\"<relative path>\",\"content\":\"<full file content>\"},{\"op\":\"edit\",\"path\":\"<relative path>\",\"old\":\"<exact text>\",\"new\":\"<replacement>\"}]}',\n\t\"Only touch paths inside your delegated scope. Keep edits minimal and exact.\",\n\t'Use status \"blocked\" with blockers when the task cannot be done from the provided context.',\n\t\"Never invent file paths, APIs, or facts.\",\n].join(\"\\n\");\n\nexport interface WorkerCompletion {\n\ttext: string;\n\tcostUsd: number;\n\tstopReason: string;\n}\n\nexport interface WorkerRunnerOptions {\n\trequest: WorkerRequest;\n\t/** Budget for this delegation; a post-hoc breach marks the lane budget_exhausted. */\n\tmaxUsd: number;\n\t/** Wall-clock budget in milliseconds; 0 disables. */\n\tmaxWallClockMs: number;\n\t/**\n\t * Pre-allocated spawned-usage report id. Always stamped on the result so parent validation can\n\t * enforce the cost-visibility invariant (a completed result without a usage report is blocked).\n\t */\n\tusageReportId: string;\n\tcomplete: (args: { systemPrompt: string; userPrompt: string; signal?: AbortSignal }) => Promise<WorkerCompletion>;\n\tsignal?: AbortSignal;\n\tnow?: () => string;\n\t/** Enables the WRITE lane: only honored when the request envelope grants \"write_files\". The\n\t * runner applies the worker's structured actions through the envelope path scope; refusals\n\t * and failures become blockers, never silent drops. */\n\tapplyActions?: (actions: readonly WorkerAction[]) => AppliedActionsReport;\n\t/** Session cwd — the baseline for relative changed-file and envelope paths in parent\n\t * validation. Defaults to process.cwd(). */\n\tcwd?: string;\n}\n\nexport interface WorkerRunOutcome {\n\tresult: WorkerResult;\n\t/** Parent-review verdict from {@link validateWorkerResult}; worker output stays untrusted. */\n\tacceptance: GateOutcome;\n\taccepted: boolean;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n}\n\nexport function buildWorkerUserPrompt(request: WorkerRequest): string {\n\treturn `Delegated task: ${request.instructions}`;\n}\n\nexport interface ParsedWorkerOutput {\n\tsummary: string;\n\tstatus: \"completed\" | \"blocked\";\n\tblockers: string[];\n\tfindings: Array<{ summary: string; confidence?: number }>;\n\tactions: WorkerAction[];\n}\n\nexport function parseWorkerOutput(text: string): ParsedWorkerOutput | undefined {\n\tconst trimmed = text.trim();\n\tconst candidates: string[] = [trimmed];\n\tconst fenced = /```(?:json)?\\s*([\\s\\S]*?)```/.exec(trimmed);\n\tif (fenced?.[1]) candidates.push(fenced[1].trim());\n\tconst start = trimmed.indexOf(\"{\");\n\tconst end = trimmed.lastIndexOf(\"}\");\n\tif (start >= 0 && end > start) candidates.push(trimmed.slice(start, end + 1));\n\n\tfor (const candidate of candidates) {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(candidate);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) continue;\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tconst summary = record.summary;\n\t\tif (typeof summary !== \"string\" || summary.trim().length === 0) continue;\n\n\t\tconst status = record.status === \"blocked\" ? \"blocked\" : \"completed\";\n\t\tconst blockers = Array.isArray(record.blockers)\n\t\t\t? record.blockers.filter((blocker): blocker is string => typeof blocker === \"string\" && blocker.length > 0)\n\t\t\t: [];\n\t\tconst findings: Array<{ summary: string; confidence?: number }> = [];\n\t\tif (Array.isArray(record.findings)) {\n\t\t\tfor (const item of record.findings) {\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n\t\t\t\tconst findingSummary = (item as { summary?: unknown }).summary;\n\t\t\t\tif (typeof findingSummary !== \"string\" || findingSummary.trim().length === 0) continue;\n\t\t\t\tconst confidenceRaw = (item as { confidence?: unknown }).confidence;\n\t\t\t\tconst confidence =\n\t\t\t\t\ttypeof confidenceRaw === \"number\" && Number.isFinite(confidenceRaw)\n\t\t\t\t\t\t? Math.min(Math.max(confidenceRaw, 0), 1)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tfindings.push({ summary: findingSummary.trim(), confidence });\n\t\t\t}\n\t\t}\n\t\treturn { summary: summary.trim(), status, blockers, findings, actions: parseWorkerActions(record.actions) };\n\t}\n\treturn undefined;\n}\n\nfunction buildWorkerEvidence(request: WorkerRequest, findings: ParsedWorkerOutput[\"findings\"]) {\n\tif (findings.length === 0) return undefined;\n\tconst instructionsRef: EvidenceRef = {\n\t\tid: \"src-instructions\",\n\t\tkind: \"user\",\n\t\ttitle: \"Delegated task instructions\",\n\t\ttrusted: true,\n\t\texcerpt: request.instructions.slice(0, 2000),\n\t};\n\tconst synthesisRef: EvidenceRef = {\n\t\tid: \"src-worker\",\n\t\tkind: \"tool\",\n\t\ttitle: \"Scout-worker synthesis\",\n\t\ttrusted: false,\n\t};\n\tconst bundleFindings: Finding[] = findings.map((finding, index) => ({\n\t\tid: `finding-${index + 1}`,\n\t\tsummary: finding.summary,\n\t\tevidenceIds: [synthesisRef.id],\n\t\t...(finding.confidence !== undefined ? { confidence: finding.confidence } : {}),\n\t}));\n\treturn createEvidenceBundle({\n\t\tquery: `worker:${request.id}`,\n\t\tsources: [instructionsRef, synthesisRef],\n\t\tfindings: bundleFindings,\n\t});\n}\n\nfunction finishOutcome(args: {\n\trequest: WorkerRequest;\n\tresult: WorkerResult;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n\tcwd?: string;\n}): WorkerRunOutcome {\n\tconst acceptance = validateWorkerResult({ request: args.request, result: args.result, cwd: args.cwd });\n\treturn {\n\t\tresult: args.result,\n\t\tacceptance,\n\t\taccepted: acceptance.outcome === \"allow\",\n\t\tlaneStatus: args.laneStatus,\n\t\treasonCode: args.reasonCode,\n\t\tcostUsd: args.costUsd,\n\t};\n}\n\nexport async function runWorker(options: WorkerRunnerOptions): Promise<WorkerRunOutcome> {\n\tconst now = options.now ?? (() => new Date().toISOString());\n\tconst baseResult = {\n\t\trequestId: options.request.id,\n\t\tchangedFiles: [] as string[],\n\t\tusageReportId: options.usageReportId,\n\t\tcreatedAt: now(),\n\t};\n\n\t// The WRITE lane requires BOTH the envelope grant and a caller-supplied applier — either\n\t// alone keeps the read-only scout contract byte-for-byte.\n\tconst writeCapable =\n\t\toptions.request.envelope.capabilities.includes(\"write_files\") && options.applyActions !== undefined;\n\n\tconst bounded = await runBoundedCompletion({\n\t\tmaxWallClockMs: options.maxWallClockMs,\n\t\tsignal: options.signal,\n\t\texecute: (signal) =>\n\t\t\toptions.complete({\n\t\t\t\tsystemPrompt: writeCapable ? WORKER_WRITE_LANE_SYSTEM_PROMPT : WORKER_LANE_SYSTEM_PROMPT,\n\t\t\t\tuserPrompt: buildWorkerUserPrompt(options.request),\n\t\t\t\tsignal,\n\t\t\t}),\n\t});\n\tconst costUsd = bounded.completion?.costUsd ?? 0;\n\n\tif (bounded.failure) {\n\t\tconst cancelled = bounded.failure.status === \"canceled\" || bounded.failure.status === \"timeout\";\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult: {\n\t\t\t\t...baseResult,\n\t\t\t\tstatus: cancelled ? \"cancelled\" : \"failed\",\n\t\t\t\tsummary: `Worker did not complete: ${bounded.failure.reasonCode}`,\n\t\t\t},\n\t\t\tlaneStatus: bounded.failure.status,\n\t\t\treasonCode: bounded.failure.reasonCode,\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst completion = bounded.completion;\n\tif (!completion || completion.stopReason === \"error\" || completion.stopReason === \"aborted\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult: { ...baseResult, status: \"failed\", summary: \"Worker model call failed.\" },\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"model_error\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst parsed = parseWorkerOutput(completion.text);\n\tif (!parsed) {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult: { ...baseResult, status: \"failed\", summary: \"Worker output was not valid structured JSON.\" },\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"unparseable_output\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst evidence = buildWorkerEvidence(options.request, parsed.findings);\n\tlet changedFiles: string[] = [];\n\tconst actionBlockers: string[] = [];\n\tif (writeCapable && parsed.status !== \"blocked\" && parsed.actions.length > 0 && options.applyActions) {\n\t\t// Runner-side application through the envelope path scope: refusals and failures are\n\t\t// surfaced as blockers so a partially-applied change can never look like clean success.\n\t\tconst applied = options.applyActions(parsed.actions);\n\t\tchangedFiles = applied.changedFiles;\n\t\tfor (const refusal of applied.refused) {\n\t\t\tactionBlockers.push(`action refused (${refusal.path}): ${refusal.reason}`);\n\t\t}\n\t\tfor (const failure of applied.failed) {\n\t\t\tactionBlockers.push(`action failed (${failure.path}): ${failure.reason}`);\n\t\t}\n\t} else if (!writeCapable && parsed.actions.length > 0) {\n\t\tactionBlockers.push(\"worker emitted file actions without a write_files envelope grant; nothing was applied\");\n\t}\n\tconst allBlockers = [...parsed.blockers, ...actionBlockers];\n\tconst result: WorkerResult = {\n\t\t...baseResult,\n\t\tchangedFiles,\n\t\tstatus: parsed.status === \"blocked\" || allBlockers.length > 0 ? \"blocked\" : \"completed\",\n\t\tsummary: parsed.summary,\n\t\t...(allBlockers.length > 0 ? { blockers: allBlockers } : {}),\n\t\t...(evidence ? { evidence } : {}),\n\t};\n\n\tif (result.status === \"blocked\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult,\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"worker_blocked\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst overBudget = options.maxUsd > 0 && costUsd > options.maxUsd;\n\treturn finishOutcome({\n\t\trequest: options.request,\n\t\tcwd: options.cwd,\n\t\tresult,\n\t\tlaneStatus: overBudget ? \"budget_exhausted\" : \"succeeded\",\n\t\treasonCode: overBudget ? \"cost_budget_exceeded\" : \"worker_completed\",\n\t\tcostUsd,\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAwB,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,KAAK,oBAAoB,EAAsB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGvG;;;;;;;GAOG;AAEH,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,QAO1B,CAAC;AAEb;gGACgG;AAChG,eAAO,MAAM,+BAA+B,QAShC,CAAC;AAEb,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,2FAA2F;IAC3F,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,qFAAqF;IACrF,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClH,oGAAoG;IACpG,eAAe,CAAC,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;2DAEuD;IACvD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,oBAAoB,CAAC;IAC1E;gDAC4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,8FAA8F;IAC9F,UAAU,EAAE,WAAW,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAEpE;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AA8BD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAwC9E;AAiDD,wBAAsB,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgJvF","sourcesContent":["import { runBoundedCompletion } from \"../autonomy/bounded-completion.ts\";\nimport type { EvidenceRef, Finding, GateOutcome, WorkerRequest, WorkerResult } from \"../autonomy/contracts.ts\";\nimport type { LaneTerminalStatus } from \"../autonomy/lane-tracker.ts\";\nimport { createEvidenceBundle } from \"../research/evidence-bundle.ts\";\nimport { type AppliedActionsReport, parseWorkerActions, type WorkerAction } from \"./worker-actions.ts\";\nimport { validateWorkerResult } from \"./worker-result.ts\";\n\n/**\n * Pure orchestration for one bounded scout-worker delegation: bounded isolated completion ->\n * parse -> `WorkerResult` -> parent validation via {@link validateWorkerResult}.\n *\n * The injected completion may be a bounded child tool loop. Its tool surface is built and gated by\n * the host; this module keeps the structured-output contract and treats every result as untrusted\n * until parent validation succeeds.\n */\n\n/** Static across calls so callers can use `cacheRetention: \"short\"`. */\nexport const WORKER_LANE_SYSTEM_PROMPT = [\n\t\"You are a bounded read-only scout worker delegated one task by a coding agent.\",\n\t\"Use only the read-only tools provided to inspect the workspace. You cannot change files or delegate more workers.\",\n\t\"Respond with STRICT JSON only - no prose, no markdown fences:\",\n\t'{\"summary\":\"<what you concluded>\",\"status\":\"completed\"|\"blocked\",\"blockers\":[\"<why you are stuck>\"],\"findings\":[{\"summary\":\"<one concrete finding>\",\"confidence\":<0..1>}]}',\n\t'Use status \"blocked\" with blockers only when the task cannot be answered from the provided context.',\n\t\"Never invent file paths, APIs, or facts.\",\n].join(\"\\n\");\n\n/** Write-capable variant (G2): direct tools are execution-gated and reported; structured actions\n * remain as a fallback for models without a native tool channel and use the same path scope. */\nexport const WORKER_WRITE_LANE_SYSTEM_PROMPT = [\n\t\"You are a bounded code-writing worker delegated one task by a coding agent.\",\n\t\"Use only the provided read tools and path-scoped write/edit tools. You cannot run shell commands or delegate workers.\",\n\t\"Respond with STRICT JSON only - no prose, no markdown fences:\",\n\t'{\"summary\":\"<what you did>\",\"status\":\"completed\"|\"blocked\",\"blockers\":[],\"findings\":[{\"summary\":\"<finding>\",\"confidence\":<0..1>}],\"actions\":[{\"op\":\"write\",\"path\":\"<relative path>\",\"content\":\"<full file content>\"},{\"op\":\"edit\",\"path\":\"<relative path>\",\"old\":\"<exact text>\",\"new\":\"<replacement>\"}]}',\n\t\"Only touch paths inside your delegated scope. Keep edits minimal and exact.\",\n\t\"If you changed a file with a provided tool, do not repeat that change in actions; actions are for changes not already applied.\",\n\t'Use status \"blocked\" with blockers when the task cannot be done from the provided context.',\n\t\"Never invent file paths, APIs, or facts.\",\n].join(\"\\n\");\n\nexport interface WorkerCompletion {\n\ttext: string;\n\tcostUsd: number;\n\tstopReason: string;\n\t/** Files successfully changed by the child tool loop before it produced the final JSON. */\n\tchangedFiles?: readonly string[];\n\t/** Capability refusals or execution failures observed inside the child tool loop. */\n\tblockers?: readonly string[];\n}\n\nexport interface WorkerRunnerOptions {\n\trequest: WorkerRequest;\n\t/** Budget for this delegation; a post-hoc breach marks the lane budget_exhausted. */\n\tmaxUsd: number;\n\t/** Wall-clock budget in milliseconds; 0 disables. */\n\tmaxWallClockMs: number;\n\t/**\n\t * Pre-allocated spawned-usage report id. Always stamped on the result so parent validation can\n\t * enforce the cost-visibility invariant (a completed result without a usage report is blocked).\n\t */\n\tusageReportId: string;\n\tcomplete: (args: { systemPrompt: string; userPrompt: string; signal?: AbortSignal }) => Promise<WorkerCompletion>;\n\t/** Live successful child-tool mutations, including writes completed before timeout/cancellation. */\n\tgetChangedFiles?: () => readonly string[];\n\tsignal?: AbortSignal;\n\tnow?: () => string;\n\t/** Enables the WRITE lane: only honored when the request envelope grants \"write_files\". The\n\t * runner applies the worker's structured actions through the envelope path scope; refusals\n\t * and failures become blockers, never silent drops. */\n\tapplyActions?: (actions: readonly WorkerAction[]) => AppliedActionsReport;\n\t/** Session cwd — the baseline for relative changed-file and envelope paths in parent\n\t * validation. Defaults to process.cwd(). */\n\tcwd?: string;\n}\n\nexport interface WorkerRunOutcome {\n\tresult: WorkerResult;\n\t/** Parent-review verdict from {@link validateWorkerResult}; worker output stays untrusted. */\n\tacceptance: GateOutcome;\n\taccepted: boolean;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n}\n\nexport function buildWorkerUserPrompt(request: WorkerRequest): string {\n\treturn `Delegated task: ${request.instructions}`;\n}\n\nexport interface ParsedWorkerOutput {\n\tsummary: string;\n\tstatus: \"completed\" | \"blocked\";\n\tblockers: string[];\n\tfindings: Array<{ summary: string; confidence?: number }>;\n\tactions: WorkerAction[];\n}\n\nfunction balancedObjectCandidates(text: string): string[] {\n\tconst candidates: string[] = [];\n\tfor (let start = 0; start < text.length; start++) {\n\t\tif (text[start] !== \"{\") continue;\n\t\tlet depth = 0;\n\t\tlet inString = false;\n\t\tlet escaped = false;\n\t\tfor (let index = start; index < text.length; index++) {\n\t\t\tconst character = text[index];\n\t\t\tif (inString) {\n\t\t\t\tif (escaped) escaped = false;\n\t\t\t\telse if (character === \"\\\\\") escaped = true;\n\t\t\t\telse if (character === '\"') inString = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (character === '\"') {\n\t\t\t\tinString = true;\n\t\t\t} else if (character === \"{\") {\n\t\t\t\tdepth++;\n\t\t\t} else if (character === \"}\" && --depth === 0) {\n\t\t\t\tcandidates.push(text.slice(start, index + 1));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn candidates;\n}\n\nexport function parseWorkerOutput(text: string): ParsedWorkerOutput | undefined {\n\tconst trimmed = text.trim();\n\tconst candidates: string[] = [trimmed];\n\tconst fenced = /```(?:json)?\\s*([\\s\\S]*?)```/.exec(trimmed);\n\tif (fenced?.[1]) candidates.push(fenced[1].trim());\n\tcandidates.push(...balancedObjectCandidates(trimmed));\n\n\tfor (const candidate of candidates) {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(candidate);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) continue;\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tconst summary = record.summary;\n\t\tif (typeof summary !== \"string\" || summary.trim().length === 0) continue;\n\n\t\tconst status = record.status === \"blocked\" ? \"blocked\" : \"completed\";\n\t\tconst blockers = Array.isArray(record.blockers)\n\t\t\t? record.blockers.filter((blocker): blocker is string => typeof blocker === \"string\" && blocker.length > 0)\n\t\t\t: [];\n\t\tconst findings: Array<{ summary: string; confidence?: number }> = [];\n\t\tif (Array.isArray(record.findings)) {\n\t\t\tfor (const item of record.findings) {\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n\t\t\t\tconst findingSummary = (item as { summary?: unknown }).summary;\n\t\t\t\tif (typeof findingSummary !== \"string\" || findingSummary.trim().length === 0) continue;\n\t\t\t\tconst confidenceRaw = (item as { confidence?: unknown }).confidence;\n\t\t\t\tconst confidence =\n\t\t\t\t\ttypeof confidenceRaw === \"number\" && Number.isFinite(confidenceRaw)\n\t\t\t\t\t\t? Math.min(Math.max(confidenceRaw, 0), 1)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tfindings.push({ summary: findingSummary.trim(), confidence });\n\t\t\t}\n\t\t}\n\t\treturn { summary: summary.trim(), status, blockers, findings, actions: parseWorkerActions(record.actions) };\n\t}\n\treturn undefined;\n}\n\nfunction buildWorkerEvidence(request: WorkerRequest, findings: ParsedWorkerOutput[\"findings\"]) {\n\tif (findings.length === 0) return undefined;\n\tconst instructionsRef: EvidenceRef = {\n\t\tid: \"src-instructions\",\n\t\tkind: \"user\",\n\t\ttitle: \"Delegated task instructions\",\n\t\ttrusted: true,\n\t\texcerpt: request.instructions.slice(0, 2000),\n\t};\n\tconst synthesisRef: EvidenceRef = {\n\t\tid: \"src-worker\",\n\t\tkind: \"tool\",\n\t\ttitle: \"Scout-worker synthesis\",\n\t\ttrusted: false,\n\t};\n\tconst bundleFindings: Finding[] = findings.map((finding, index) => ({\n\t\tid: `finding-${index + 1}`,\n\t\tsummary: finding.summary,\n\t\tevidenceIds: [synthesisRef.id],\n\t\t...(finding.confidence !== undefined ? { confidence: finding.confidence } : {}),\n\t}));\n\treturn createEvidenceBundle({\n\t\tquery: `worker:${request.id}`,\n\t\tsources: [instructionsRef, synthesisRef],\n\t\tfindings: bundleFindings,\n\t});\n}\n\nfunction finishOutcome(args: {\n\trequest: WorkerRequest;\n\tresult: WorkerResult;\n\tlaneStatus: LaneTerminalStatus;\n\treasonCode: string;\n\tcostUsd: number;\n\tcwd?: string;\n}): WorkerRunOutcome {\n\tconst acceptance = validateWorkerResult({ request: args.request, result: args.result, cwd: args.cwd });\n\treturn {\n\t\tresult: args.result,\n\t\tacceptance,\n\t\taccepted: acceptance.outcome === \"allow\",\n\t\tlaneStatus: args.laneStatus,\n\t\treasonCode: args.reasonCode,\n\t\tcostUsd: args.costUsd,\n\t};\n}\n\nexport async function runWorker(options: WorkerRunnerOptions): Promise<WorkerRunOutcome> {\n\tconst now = options.now ?? (() => new Date().toISOString());\n\tconst baseResult = {\n\t\trequestId: options.request.id,\n\t\tchangedFiles: [] as string[],\n\t\tusageReportId: options.usageReportId,\n\t\tcreatedAt: now(),\n\t};\n\n\t// The WRITE lane requires BOTH the envelope grant and a caller-supplied applier — either\n\t// alone keeps the read-only scout contract byte-for-byte.\n\tconst writeCapable =\n\t\toptions.request.envelope.capabilities.includes(\"write_files\") && options.applyActions !== undefined;\n\n\tconst bounded = await runBoundedCompletion({\n\t\tmaxWallClockMs: options.maxWallClockMs,\n\t\tsignal: options.signal,\n\t\texecute: (signal) =>\n\t\t\toptions.complete({\n\t\t\t\tsystemPrompt: writeCapable ? WORKER_WRITE_LANE_SYSTEM_PROMPT : WORKER_LANE_SYSTEM_PROMPT,\n\t\t\t\tuserPrompt: buildWorkerUserPrompt(options.request),\n\t\t\t\tsignal,\n\t\t\t}),\n\t});\n\tconst costUsd = bounded.completion?.costUsd ?? 0;\n\tconst liveChangedFiles = [...new Set(options.getChangedFiles?.() ?? [])];\n\n\tif (bounded.failure) {\n\t\tconst cancelled = bounded.failure.status === \"canceled\" || bounded.failure.status === \"timeout\";\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult: {\n\t\t\t\t...baseResult,\n\t\t\t\tchangedFiles: liveChangedFiles,\n\t\t\t\tstatus: cancelled ? \"cancelled\" : \"failed\",\n\t\t\t\tsummary: `Worker did not complete: ${bounded.failure.reasonCode}`,\n\t\t\t},\n\t\t\tlaneStatus: bounded.failure.status,\n\t\t\treasonCode: bounded.failure.reasonCode,\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst completion = bounded.completion as WorkerCompletion | undefined;\n\tconst completionChangedFiles = [...new Set([...liveChangedFiles, ...(completion?.changedFiles ?? [])])];\n\tconst completionBaseResult = { ...baseResult, changedFiles: completionChangedFiles };\n\tif (!completion || completion.stopReason === \"error\" || completion.stopReason === \"aborted\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult: { ...completionBaseResult, status: \"failed\", summary: \"Worker model call failed.\" },\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"model_error\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst parsed = parseWorkerOutput(completion.text);\n\tif (!parsed) {\n\t\tconst readOnlyPlainText =\n\t\t\t!writeCapable &&\n\t\t\tcompletion.stopReason === \"stop\" &&\n\t\t\tcompletion.text.trim().length > 0 &&\n\t\t\tcompletionChangedFiles.length === 0;\n\t\tif (readOnlyPlainText) {\n\t\t\treturn finishOutcome({\n\t\t\t\trequest: options.request,\n\t\t\t\tcwd: options.cwd,\n\t\t\t\tresult: {\n\t\t\t\t\t...completionBaseResult,\n\t\t\t\t\tstatus: \"completed\",\n\t\t\t\t\toutputFormat: \"plain_text\",\n\t\t\t\t\tsummary: completion.text.trim().slice(0, 8000),\n\t\t\t\t},\n\t\t\t\tlaneStatus: \"succeeded\",\n\t\t\t\treasonCode: \"worker_completed_plain_text\",\n\t\t\t\tcostUsd,\n\t\t\t});\n\t\t}\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult: {\n\t\t\t\t...completionBaseResult,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tsummary: \"Worker output was not valid structured JSON.\",\n\t\t\t},\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"unparseable_output\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst evidence = buildWorkerEvidence(options.request, parsed.findings);\n\tlet changedFiles: string[] = [...completionChangedFiles];\n\tconst actionBlockers: string[] = [...(completion.blockers ?? [])];\n\tif (!writeCapable && completionChangedFiles.length > 0) {\n\t\tactionBlockers.push(\"worker reported file changes without a write_files envelope grant\");\n\t}\n\tif (writeCapable && parsed.status !== \"blocked\" && parsed.actions.length > 0 && options.applyActions) {\n\t\t// Runner-side application through the envelope path scope: refusals and failures are\n\t\t// surfaced as blockers so a partially-applied change can never look like clean success.\n\t\tconst applied = options.applyActions(parsed.actions);\n\t\tchangedFiles = [...new Set([...changedFiles, ...applied.changedFiles])];\n\t\tfor (const refusal of applied.refused) {\n\t\t\tactionBlockers.push(`action refused (${refusal.path}): ${refusal.reason}`);\n\t\t}\n\t\tfor (const failure of applied.failed) {\n\t\t\tactionBlockers.push(`action failed (${failure.path}): ${failure.reason}`);\n\t\t}\n\t} else if (!writeCapable && parsed.actions.length > 0) {\n\t\tactionBlockers.push(\"worker emitted file actions without a write_files envelope grant; nothing was applied\");\n\t}\n\tconst allBlockers = [...parsed.blockers, ...actionBlockers];\n\tconst result: WorkerResult = {\n\t\t...baseResult,\n\t\tchangedFiles,\n\t\tstatus: parsed.status === \"blocked\" || allBlockers.length > 0 ? \"blocked\" : \"completed\",\n\t\tsummary: parsed.summary,\n\t\t...(allBlockers.length > 0 ? { blockers: allBlockers } : {}),\n\t\t...(evidence ? { evidence } : {}),\n\t};\n\n\tif (result.status === \"blocked\") {\n\t\treturn finishOutcome({\n\t\t\trequest: options.request,\n\t\t\tcwd: options.cwd,\n\t\t\tresult,\n\t\t\tlaneStatus: \"failed\",\n\t\t\treasonCode: \"worker_blocked\",\n\t\t\tcostUsd,\n\t\t});\n\t}\n\n\tconst overBudget = options.maxUsd > 0 && costUsd > options.maxUsd;\n\treturn finishOutcome({\n\t\trequest: options.request,\n\t\tcwd: options.cwd,\n\t\tresult,\n\t\tlaneStatus: overBudget ? \"budget_exhausted\" : \"succeeded\",\n\t\treasonCode: overBudget ? \"cost_budget_exceeded\" : \"worker_completed\",\n\t\tcostUsd,\n\t});\n}\n"]}
|