@wrongstack/core 0.285.0 → 0.287.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autophase/phase-orchestrator.d.ts.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/coordination/agent-monitor.d.ts +29 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -1
- package/dist/coordination/autonomous-brain.d.ts +0 -2
- package/dist/coordination/autonomous-brain.d.ts.map +1 -1
- package/dist/coordination/autonomous-coordinator.d.ts +4 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
- package/dist/coordination/brain-ledger.d.ts +142 -0
- package/dist/coordination/brain-ledger.d.ts.map +1 -0
- package/dist/coordination/brain-monitor.d.ts +46 -2
- package/dist/coordination/brain-monitor.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +43 -0
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/delegate-tool.d.ts +8 -14
- package/dist/coordination/delegate-tool.d.ts.map +1 -1
- package/dist/coordination/director/director-btw-notes.d.ts +20 -0
- package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
- package/dist/coordination/director/director-collab.d.ts +40 -0
- package/dist/coordination/director/director-collab.d.ts.map +1 -0
- package/dist/coordination/director.d.ts +28 -9
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-manager.d.ts +21 -0
- package/dist/coordination/fleet-manager.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +6 -7
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +96 -21
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/icoordinator.d.ts +1 -3
- package/dist/coordination/icoordinator.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2176 -806
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +13 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-actions.d.ts +12 -13
- package/dist/coordination/mailbox-actions.d.ts.map +1 -1
- package/dist/coordination/mailbox-constants.d.ts +64 -0
- package/dist/coordination/mailbox-constants.d.ts.map +1 -0
- package/dist/coordination/mailbox-events.d.ts +32 -0
- package/dist/coordination/mailbox-events.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +28 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
- package/dist/coordination/mailbox-types.d.ts +130 -5
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/mailbox.d.ts +21 -2
- package/dist/coordination/mailbox.d.ts.map +1 -1
- package/dist/coordination/package-author-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +10 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent.d.ts +8 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/context.d.ts +25 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/conversation-state.d.ts +6 -0
- package/dist/core/conversation-state.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +12 -5
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +14 -6
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.d.ts +0 -1
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +1690 -1998
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +8 -1
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/autonomous-runner.d.ts +7 -1
- package/dist/execution/autonomous-runner.d.ts.map +1 -1
- package/dist/execution/autonomy-brain.d.ts +72 -11
- package/dist/execution/autonomy-brain.d.ts.map +1 -1
- package/dist/execution/brain-chain.d.ts +53 -0
- package/dist/execution/brain-chain.d.ts.map +1 -0
- package/dist/execution/brain-evaluation.d.ts +66 -0
- package/dist/execution/brain-evaluation.d.ts.map +1 -0
- package/dist/execution/brain-runtime.d.ts +171 -0
- package/dist/execution/brain-runtime.d.ts.map +1 -0
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/council-brain.d.ts +79 -0
- package/dist/execution/council-brain.d.ts.map +1 -0
- package/dist/execution/council-orchestrator.d.ts +32 -0
- package/dist/execution/council-orchestrator.d.ts.map +1 -0
- package/dist/execution/council-personas.d.ts +19 -0
- package/dist/execution/council-personas.d.ts.map +1 -0
- package/dist/execution/council-profiles.d.ts +34 -0
- package/dist/execution/council-profiles.d.ts.map +1 -0
- package/dist/execution/council-prompts.d.ts +21 -0
- package/dist/execution/council-prompts.d.ts.map +1 -0
- package/dist/execution/council-resolution.d.ts +57 -0
- package/dist/execution/council-resolution.d.ts.map +1 -0
- package/dist/execution/design-kit-loader.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +35 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -0
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/eternal-autonomy.d.ts +12 -0
- package/dist/execution/eternal-autonomy.d.ts.map +1 -1
- package/dist/execution/index.d.ts +11 -1
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +3076 -526
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +18 -7
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts +52 -0
- package/dist/execution/one-shot-llm.d.ts.map +1 -0
- package/dist/execution/parallel-eternal-engine.d.ts +12 -0
- package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +23 -4
- package/dist/execution/prompt-enhancer.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.js +24 -6
- package/dist/execution/prompt-enhancer.js.map +2 -2
- package/dist/execution/selective-compactor.d.ts +4 -4
- package/dist/execution/strategy-compactor.d.ts +7 -0
- package/dist/execution/strategy-compactor.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts +19 -5
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hooks/registry.d.ts.map +1 -1
- package/dist/hooks/runner.d.ts.map +1 -1
- package/dist/hooks/shell-executor.d.ts.map +1 -1
- package/dist/hq/alerts.d.ts +5 -0
- package/dist/hq/alerts.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts +38 -10
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/commands.d.ts +8 -4
- package/dist/hq/commands.d.ts.map +1 -1
- package/dist/hq/cost-bridge.d.ts +5 -3
- package/dist/hq/cost-bridge.d.ts.map +1 -1
- package/dist/hq/factory.d.ts +3 -0
- package/dist/hq/factory.d.ts.map +1 -1
- package/dist/hq/fleet-bridge.d.ts.map +1 -1
- package/dist/hq/index.d.ts +0 -1
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +1016 -283
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/persistence.d.ts +42 -0
- package/dist/hq/persistence.d.ts.map +1 -1
- package/dist/hq/protocol/brain.d.ts +24 -0
- package/dist/hq/protocol/brain.d.ts.map +1 -0
- package/dist/hq/protocol/browser.d.ts +12 -0
- package/dist/hq/protocol/browser.d.ts.map +1 -0
- package/dist/hq/protocol/client.d.ts +69 -0
- package/dist/hq/protocol/client.d.ts.map +1 -0
- package/dist/hq/protocol/core.d.ts +130 -0
- package/dist/hq/protocol/core.d.ts.map +1 -0
- package/dist/hq/protocol/fleet.d.ts +73 -0
- package/dist/hq/protocol/fleet.d.ts.map +1 -0
- package/dist/hq/protocol/mailbox.d.ts +76 -0
- package/dist/hq/protocol/mailbox.d.ts.map +1 -0
- package/dist/hq/protocol/mcp.d.ts +51 -0
- package/dist/hq/protocol/mcp.d.ts.map +1 -0
- package/dist/hq/protocol/project.d.ts +26 -0
- package/dist/hq/protocol/project.d.ts.map +1 -0
- package/dist/hq/protocol/session.d.ts +153 -0
- package/dist/hq/protocol/session.d.ts.map +1 -0
- package/dist/hq/protocol/tool.d.ts +31 -0
- package/dist/hq/protocol/tool.d.ts.map +1 -0
- package/dist/hq/protocol.d.ts +10 -559
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/hq/publisher.d.ts +4 -0
- package/dist/hq/publisher.d.ts.map +1 -1
- package/dist/index.d.ts +24 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7676 -3432
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +13 -11
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/infrastructure/token-counter.d.ts.map +1 -1
- package/dist/kernel/events/agent-events.d.ts +283 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -0
- package/dist/kernel/events/brain-events.d.ts +176 -0
- package/dist/kernel/events/brain-events.d.ts.map +1 -0
- package/dist/kernel/events/fleet-events.d.ts +39 -0
- package/dist/kernel/events/fleet-events.d.ts.map +1 -0
- package/dist/kernel/events/memory-events.d.ts +165 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -0
- package/dist/kernel/events/provider-events.d.ts +98 -0
- package/dist/kernel/events/provider-events.d.ts.map +1 -0
- package/dist/kernel/events/sdd-events.d.ts +118 -0
- package/dist/kernel/events/sdd-events.d.ts.map +1 -0
- package/dist/kernel/events/session-events.d.ts +227 -0
- package/dist/kernel/events/session-events.d.ts.map +1 -0
- package/dist/kernel/events/tool-events.d.ts +185 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -0
- package/dist/kernel/events/worktree-events.d.ts +58 -0
- package/dist/kernel/events/worktree-events.d.ts.map +1 -0
- package/dist/kernel/events.d.ts +12 -1282
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js +22 -2
- package/dist/kernel/index.js.map +2 -2
- package/dist/kernel/run-controller.d.ts +15 -0
- package/dist/kernel/run-controller.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/models/index.js +19 -6
- package/dist/models/index.js.map +2 -2
- package/dist/models/llm-selector.d.ts +7 -0
- package/dist/models/llm-selector.d.ts.map +1 -1
- package/dist/observability/index.js +16 -15
- package/dist/observability/index.js.map +2 -2
- package/dist/plugins/auto-review-plugin.d.ts +36 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
- package/dist/plugins/chimera-plugin.d.ts +8 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +260 -23
- package/dist/security/index.js.map +4 -4
- package/dist/security/permission-policy-schema.d.ts +77 -0
- package/dist/security/permission-policy-schema.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +5 -0
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/security/secret-scrubber.d.ts.map +1 -1
- package/dist/security/secret-vault.d.ts +9 -0
- package/dist/security/secret-vault.d.ts.map +1 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/storage/config-loader.d.ts +10 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/goal-kanban.d.ts +69 -0
- package/dist/storage/goal-kanban.d.ts.map +1 -0
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/goal-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +572 -879
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +9 -1
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/plan-store.d.ts.map +1 -1
- package/dist/storage/queue-store.d.ts +4 -0
- package/dist/storage/queue-store.d.ts.map +1 -1
- package/dist/storage/session-store.d.ts +9 -0
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/task-store.d.ts.map +1 -1
- package/dist/storage/todos-checkpoint.d.ts +2 -2
- package/dist/storage/todos-checkpoint.d.ts.map +1 -1
- package/dist/tools/council-tool.d.ts +25 -0
- package/dist/tools/council-tool.d.ts.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1884 -12
- package/dist/tools/index.js.map +4 -4
- package/dist/tools/one-shot-llm-tool.d.ts +51 -0
- package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
- package/dist/types/config.d.ts +236 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/context-window.d.ts.map +1 -1
- package/dist/types/council.d.ts +134 -0
- package/dist/types/council.d.ts.map +1 -0
- package/dist/types/default-config.d.ts +3 -3
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +40 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/multi-agent.d.ts +8 -0
- package/dist/types/multi-agent.d.ts.map +1 -1
- package/dist/types/observability.d.ts +5 -0
- package/dist/types/observability.d.ts.map +1 -1
- package/dist/types/one-shot-llm.d.ts +128 -0
- package/dist/types/one-shot-llm.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +6 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/provider.d.ts +17 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/system-prompt.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +10 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/types/tool.d.ts +7 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/glob-expand.d.ts.map +1 -1
- package/dist/utils/incoming-images.d.ts +39 -0
- package/dist/utils/incoming-images.d.ts.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +156 -11
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/tool-wire-compact.d.ts +1 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +20 -4
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/dist/worktree/worktree-manager.d.ts +13 -4
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/instructions/agents/browser.md +25 -16
- package/instructions/cli/next-task-predictor.md +5 -3
- package/instructions/coordination/subagent-baseline.md +17 -2
- package/instructions/leader-after-task.md +19 -8
- package/instructions/llm/council-judge.md +19 -0
- package/instructions/llm/council-voter.md +22 -0
- package/instructions/modes/architect.md +14 -9
- package/instructions/modes/audit-lite.md +9 -7
- package/instructions/modes/brief-system.md +12 -12
- package/instructions/modes/brief.md +7 -5
- package/instructions/modes/code-auditor.md +13 -8
- package/instructions/modes/code-reviewer.md +13 -8
- package/instructions/modes/debug-lite.md +7 -6
- package/instructions/modes/debugger.md +13 -9
- package/instructions/modes/devops.md +13 -9
- package/instructions/modes/plan-lite.md +7 -6
- package/instructions/modes/refactor-lite.md +7 -6
- package/instructions/modes/refactorer.md +14 -9
- package/instructions/modes/research-lite.md +8 -6
- package/instructions/modes/research-web.md +18 -55
- package/instructions/modes/review-lite.md +7 -6
- package/instructions/modes/teach-system.md +15 -15
- package/instructions/modes/teach.md +13 -12
- package/instructions/modes/test-lite.md +6 -5
- package/instructions/modes/tester.md +13 -9
- package/instructions/modes/ui-design.md +14 -8
- package/instructions/sections/tool/commit-hygiene.md +1 -7
- package/instructions/sections/tool/common-patterns.md +7 -1
- package/instructions/sections/tool/delegation-full.md +3 -31
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +6 -22
- package/instructions/system.md +294 -8
- package/package.json +2 -2
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/mailbox-bridge/SKILL.md +16 -7
- package/skills/output-standards/SKILL.md +16 -16
- package/skills/wrongstack-mailbox/SKILL.md +57 -12
- package/dist/coordination/director-construction.d.ts +0 -90
- package/dist/coordination/director-construction.d.ts.map +0 -1
- package/dist/hq/agent-bridge.d.ts +0 -27
- package/dist/hq/agent-bridge.d.ts.map +0 -1
- package/dist/plugins/git-plugin.d.ts +0 -15
- package/dist/plugins/git-plugin.d.ts.map +0 -1
- package/dist/plugins/observability-plugin.d.ts +0 -19
- package/dist/plugins/observability-plugin.d.ts.map +0 -1
- package/dist/plugins/plan-plugin.d.ts +0 -16
- package/dist/plugins/plan-plugin.d.ts.map +0 -1
- package/dist/storage/memory-store.d.ts +0 -119
- package/dist/storage/memory-store.d.ts.map +0 -1
package/dist/execution/index.js
CHANGED
|
@@ -580,8 +580,8 @@ function subjectForToolInput(toolName, input, subjectKey) {
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
// src/utils/tool-wire-compact.ts
|
|
583
|
-
var TOOL_DESCRIPTION_MAX_CHARS =
|
|
584
|
-
var SCHEMA_DESCRIPTION_MAX_CHARS =
|
|
583
|
+
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
584
|
+
var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
|
|
585
585
|
var compactCache = /* @__PURE__ */ new WeakMap();
|
|
586
586
|
function compactToolDefinitionForWire(tool, opts = {}) {
|
|
587
587
|
const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
|
|
@@ -1501,14 +1501,34 @@ function resolveToolResultRenderMode(modes, toolName) {
|
|
|
1501
1501
|
|
|
1502
1502
|
// src/utils/wstack-paths.ts
|
|
1503
1503
|
import { createHash } from "node:crypto";
|
|
1504
|
+
import * as fs2 from "node:fs";
|
|
1504
1505
|
import * as os from "node:os";
|
|
1505
1506
|
import * as path2 from "node:path";
|
|
1507
|
+
function canonicalProjectRoot(absRoot) {
|
|
1508
|
+
const checkoutRoot = path2.resolve(absRoot);
|
|
1509
|
+
const dotGit = path2.join(checkoutRoot, ".git");
|
|
1510
|
+
try {
|
|
1511
|
+
if (!fs2.statSync(dotGit).isFile()) return checkoutRoot;
|
|
1512
|
+
const gitDirLine = fs2.readFileSync(dotGit, "utf8").trim();
|
|
1513
|
+
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
1514
|
+
if (!match?.[1]) return checkoutRoot;
|
|
1515
|
+
const gitDir = path2.resolve(checkoutRoot, match[1].trim());
|
|
1516
|
+
const commonDirFile = path2.join(gitDir, "commondir");
|
|
1517
|
+
if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
1518
|
+
const commonDir = path2.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
|
|
1519
|
+
if (path2.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
1520
|
+
return path2.dirname(commonDir);
|
|
1521
|
+
} catch {
|
|
1522
|
+
return checkoutRoot;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1506
1525
|
function projectHash(absRoot) {
|
|
1507
|
-
return createHash("sha256").update(
|
|
1526
|
+
return createHash("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
|
|
1508
1527
|
}
|
|
1509
1528
|
function projectSlug(absRoot) {
|
|
1510
|
-
const
|
|
1511
|
-
const
|
|
1529
|
+
const identityRoot = canonicalProjectRoot(absRoot);
|
|
1530
|
+
const base = slugify2(path2.basename(identityRoot));
|
|
1531
|
+
const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
1512
1532
|
return `${base}-${hash}`;
|
|
1513
1533
|
}
|
|
1514
1534
|
function slugify2(name) {
|
|
@@ -1775,6 +1795,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1775
1795
|
failureMode;
|
|
1776
1796
|
policyProvider;
|
|
1777
1797
|
sessionBridge;
|
|
1798
|
+
onCompact;
|
|
1778
1799
|
/**
|
|
1779
1800
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
1780
1801
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -1821,6 +1842,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1821
1842
|
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
1822
1843
|
this.policyProvider = opts.policyProvider;
|
|
1823
1844
|
this.sessionBridge = opts.sessionBridge;
|
|
1845
|
+
this.onCompact = opts.onCompact;
|
|
1824
1846
|
}
|
|
1825
1847
|
/** Allow callers (e.g. model-switch in WebUI) to update the context window
|
|
1826
1848
|
* denominator when the active model changes. */
|
|
@@ -1943,6 +1965,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1943
1965
|
try {
|
|
1944
1966
|
const report = await this.compactor.compact(ctx, { aggressive });
|
|
1945
1967
|
this.recordAttempt(pressure.level, pressure.tokens, report);
|
|
1968
|
+
this.onCompact?.(report);
|
|
1946
1969
|
this.events?.emit("compaction.fired", {
|
|
1947
1970
|
sessionId: ctx.session.id,
|
|
1948
1971
|
level: pressure.level,
|
|
@@ -1971,7 +1994,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1971
1994
|
});
|
|
1972
1995
|
ctx.clearFileTracking();
|
|
1973
1996
|
const afterTokens = report.fullRequestTokensAfter ?? report.after;
|
|
1974
|
-
const
|
|
1997
|
+
const afterBudget = computeContextWindowBudget(ctx, afterTokens, this._maxContext);
|
|
1998
|
+
const afterLoad = afterBudget.load;
|
|
1975
1999
|
const stillHard = afterLoad >= pressure.hardThreshold;
|
|
1976
2000
|
const fatal = stillHard && (this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard");
|
|
1977
2001
|
if (stillHard) {
|
|
@@ -2069,19 +2093,27 @@ function adaptThresholdsForSignals(thresholds, signals) {
|
|
|
2069
2093
|
|
|
2070
2094
|
// src/execution/autonomous-runner.ts
|
|
2071
2095
|
var DoneConditionChecker = class {
|
|
2072
|
-
constructor(condition) {
|
|
2096
|
+
constructor(condition, opts) {
|
|
2073
2097
|
this.condition = condition;
|
|
2098
|
+
this.logger = opts?.logger;
|
|
2074
2099
|
if (condition.type === "output_match" && condition.pattern) {
|
|
2075
2100
|
const result = compileUserRegex(condition.pattern, "");
|
|
2076
2101
|
this.compiledRegex = result.ok ? result.regex : null;
|
|
2077
2102
|
if (!result.ok) {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2103
|
+
if (this.logger) {
|
|
2104
|
+
this.logger.warn(
|
|
2105
|
+
"Done condition has invalid regex \u2014 will never match",
|
|
2106
|
+
{ event: "autonomous.done_condition_invalid_regex", pattern: condition.pattern, reason: result.reason }
|
|
2107
|
+
);
|
|
2108
|
+
} else {
|
|
2109
|
+
console.warn(JSON.stringify({
|
|
2110
|
+
level: "warn",
|
|
2111
|
+
event: "autonomous.done_condition_invalid_regex",
|
|
2112
|
+
pattern: condition.pattern,
|
|
2113
|
+
reason: result.reason,
|
|
2114
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2115
|
+
}));
|
|
2116
|
+
}
|
|
2085
2117
|
}
|
|
2086
2118
|
} else {
|
|
2087
2119
|
this.compiledRegex = null;
|
|
@@ -2089,6 +2121,7 @@ var DoneConditionChecker = class {
|
|
|
2089
2121
|
}
|
|
2090
2122
|
condition;
|
|
2091
2123
|
compiledRegex;
|
|
2124
|
+
logger;
|
|
2092
2125
|
check(state) {
|
|
2093
2126
|
switch (this.condition.type) {
|
|
2094
2127
|
case "iterations":
|
|
@@ -2139,7 +2172,7 @@ var DoneConditionChecker = class {
|
|
|
2139
2172
|
var AutonomousRunner = class {
|
|
2140
2173
|
constructor(opts) {
|
|
2141
2174
|
this.opts = opts;
|
|
2142
|
-
this.doneChecker = new DoneConditionChecker(opts.doneCondition);
|
|
2175
|
+
this.doneChecker = new DoneConditionChecker(opts.doneCondition, { logger: opts.logger });
|
|
2143
2176
|
}
|
|
2144
2177
|
opts;
|
|
2145
2178
|
iterations = 0;
|
|
@@ -2245,7 +2278,7 @@ var AutonomousRunner = class {
|
|
|
2245
2278
|
};
|
|
2246
2279
|
|
|
2247
2280
|
// src/utils/instruction-file.ts
|
|
2248
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2281
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
2249
2282
|
import * as path3 from "node:path";
|
|
2250
2283
|
import { fileURLToPath } from "node:url";
|
|
2251
2284
|
var textCache = /* @__PURE__ */ new Map();
|
|
@@ -2256,7 +2289,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
2256
2289
|
let resolved = "";
|
|
2257
2290
|
for (const root of instructionRootCandidates()) {
|
|
2258
2291
|
try {
|
|
2259
|
-
resolved =
|
|
2292
|
+
resolved = readFileSync2(path3.join(root, relativePath), "utf8").trimEnd();
|
|
2260
2293
|
break;
|
|
2261
2294
|
} catch {
|
|
2262
2295
|
}
|
|
@@ -2283,7 +2316,7 @@ function instructionRootCandidates() {
|
|
|
2283
2316
|
}
|
|
2284
2317
|
function isDirectory(candidate) {
|
|
2285
2318
|
try {
|
|
2286
|
-
return
|
|
2319
|
+
return statSync2(candidate).isDirectory();
|
|
2287
2320
|
} catch {
|
|
2288
2321
|
return false;
|
|
2289
2322
|
}
|
|
@@ -2300,12 +2333,25 @@ function createTieredBrainArbiter(opts) {
|
|
|
2300
2333
|
return {
|
|
2301
2334
|
async decide(request) {
|
|
2302
2335
|
const policyDecision = await opts.policy.decide(request);
|
|
2303
|
-
|
|
2336
|
+
const provisionalContinue = policyDecision.type === "answer" && policyDecision.optionId === void 0 && request.fallback === "continue";
|
|
2337
|
+
if (policyDecision.type !== "ask_human" && !provisionalContinue) return policyDecision;
|
|
2304
2338
|
const ceiling = opts.getMaxAutoRisk?.() ?? "medium";
|
|
2305
|
-
if (
|
|
2339
|
+
if (ceiling === "off") return policyDecision;
|
|
2306
2340
|
const ceilingLevel = ceiling === "all" ? 3 : RISK_LEVELS[ceiling] ?? 1;
|
|
2307
2341
|
const requestLevel = RISK_LEVELS[request.risk] ?? 2;
|
|
2308
2342
|
if (requestLevel > ceilingLevel) return policyDecision;
|
|
2343
|
+
if (opts.council) {
|
|
2344
|
+
const floor = opts.getCouncilMinRisk?.() ?? "high";
|
|
2345
|
+
const floorLevel = RISK_LEVELS[floor] ?? 2;
|
|
2346
|
+
if (requestLevel >= floorLevel) {
|
|
2347
|
+
try {
|
|
2348
|
+
const councilDecision = await opts.council.decide(request);
|
|
2349
|
+
if (councilDecision.type !== "ask_human") return councilDecision;
|
|
2350
|
+
} catch {
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
if (!opts.autonomous) return policyDecision;
|
|
2309
2355
|
try {
|
|
2310
2356
|
const llmDecision = await opts.autonomous.decide(request);
|
|
2311
2357
|
if (llmDecision.type === "answer") return llmDecision;
|
|
@@ -2319,6 +2365,12 @@ function createAutonomyBrain(opts) {
|
|
|
2319
2365
|
const maxRisk = opts.maxAutoRisk ?? "high";
|
|
2320
2366
|
const maxRiskLevel = RISK_LEVELS[maxRisk] ?? 2;
|
|
2321
2367
|
const timeoutMs = opts.decisionTimeoutMs ?? 15e3;
|
|
2368
|
+
const targets = opts.targets && opts.targets.length > 0 ? opts.targets : opts.provider && opts.model ? [{ provider: opts.provider, model: opts.model }] : [];
|
|
2369
|
+
if (targets.length === 0) {
|
|
2370
|
+
throw new Error("createAutonomyBrain: provide `targets` or `provider` + `model`.");
|
|
2371
|
+
}
|
|
2372
|
+
const strategy = opts.strategy ?? "fallback";
|
|
2373
|
+
let rrCursor = 0;
|
|
2322
2374
|
return {
|
|
2323
2375
|
async decide(request) {
|
|
2324
2376
|
const requestLevel = RISK_LEVELS[request.risk] ?? 2;
|
|
@@ -2337,7 +2389,10 @@ function createAutonomyBrain(opts) {
|
|
|
2337
2389
|
opts.onDecision?.(formatDecisionSummary(heuristic, request), heuristic, request);
|
|
2338
2390
|
return heuristic;
|
|
2339
2391
|
}
|
|
2340
|
-
const
|
|
2392
|
+
const start = strategy === "round-robin" ? rrCursor++ % targets.length : 0;
|
|
2393
|
+
const ordered = [...targets.slice(start), ...targets.slice(0, start)];
|
|
2394
|
+
const digest = opts.getDecisionDigest?.(request);
|
|
2395
|
+
const llmDecision = await llmDecide(request, ordered, timeoutMs, digest);
|
|
2341
2396
|
opts.onDecision?.(formatDecisionSummary(llmDecision, request), llmDecision, request);
|
|
2342
2397
|
return llmDecision;
|
|
2343
2398
|
}
|
|
@@ -2355,6 +2410,7 @@ function formatDecisionSummary(decision, request) {
|
|
|
2355
2410
|
return `\u{1F9E0} Brain: ASKED HUMAN \u2014 "${question}"`;
|
|
2356
2411
|
}
|
|
2357
2412
|
function quickDecide(request) {
|
|
2413
|
+
if (request.options?.length) return null;
|
|
2358
2414
|
const q = request.question.toLowerCase();
|
|
2359
2415
|
const ctx = request.context?.toLowerCase() ?? "";
|
|
2360
2416
|
if (q.includes("deadlock") && ctx.includes("failed")) {
|
|
@@ -2364,7 +2420,7 @@ function quickDecide(request) {
|
|
|
2364
2420
|
rationale: "Heuristic: deadlocked tasks blocked by failed dependencies \u2014 skipping unblocks remaining work."
|
|
2365
2421
|
};
|
|
2366
2422
|
}
|
|
2367
|
-
if ((q.includes("failed") || q.includes("retry")) && (ctx
|
|
2423
|
+
if ((q.includes("failed") || q.includes("retry")) && (/\bexhausted\b/.test(ctx) || /\b(?:[3-9]|\d{2,})\s+(?:consecutive\s+)?(?:times|attempts|retries|failures)\b/.test(ctx) || /\b(?:attempt|retr(?:y|ies)|failure)s?\W{0,3}(?:[3-9]|\d{2,})\b/.test(ctx))) {
|
|
2368
2424
|
return {
|
|
2369
2425
|
type: "answer",
|
|
2370
2426
|
text: "Mark as failed and move on. Note the failure for the final report.",
|
|
@@ -2374,7 +2430,7 @@ function quickDecide(request) {
|
|
|
2374
2430
|
if (q.includes("goal complete") || q.includes("mission complete")) {
|
|
2375
2431
|
return null;
|
|
2376
2432
|
}
|
|
2377
|
-
if (
|
|
2433
|
+
if (request.fallback === "continue" && /\b(?:continue|proceed)\b/.test(q) && !/\b(?:stop|abort|halt|cancel|pause|rollback)\b/.test(q) && !/\bor\b/.test(q)) {
|
|
2378
2434
|
return {
|
|
2379
2435
|
type: "answer",
|
|
2380
2436
|
text: "Continue execution. Do not stop.",
|
|
@@ -2383,93 +2439,2355 @@ function quickDecide(request) {
|
|
|
2383
2439
|
}
|
|
2384
2440
|
return null;
|
|
2385
2441
|
}
|
|
2386
|
-
|
|
2442
|
+
function buildBrainUserMessage(request) {
|
|
2387
2443
|
const optionsText = request.options?.length ? "\nOptions:\n" + request.options.map(
|
|
2388
2444
|
(o) => ` [${o.id}] ${o.label}${o.consequence ? ` \u2014 ${o.consequence}` : ""}${o.recommended ? " \u2605 recommended" : ""}`
|
|
2389
2445
|
).join("\n") : "";
|
|
2390
|
-
|
|
2391
|
-
const userMessage = [
|
|
2446
|
+
return [
|
|
2392
2447
|
`Question: ${request.question}`,
|
|
2393
2448
|
request.context ? `
|
|
2394
2449
|
Context:
|
|
2395
2450
|
${request.context}` : "",
|
|
2396
2451
|
optionsText
|
|
2397
2452
|
].filter(Boolean).join("\n");
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2453
|
+
}
|
|
2454
|
+
function withDecisionDigest(user, digest) {
|
|
2455
|
+
if (!digest?.trim()) return user;
|
|
2456
|
+
return `${user}
|
|
2457
|
+
|
|
2458
|
+
Outcome history of similar past decisions (learn from it):
|
|
2459
|
+
${digest}`;
|
|
2460
|
+
}
|
|
2461
|
+
async function completeBrainLlm(target, input) {
|
|
2462
|
+
const signal = AbortSignal.timeout(input.timeoutMs);
|
|
2463
|
+
const response = await target.provider.complete(
|
|
2464
|
+
{
|
|
2465
|
+
model: target.model,
|
|
2466
|
+
system: [{ type: "text", text: input.system }],
|
|
2467
|
+
messages: [{ role: "user", content: input.user || "Decide." }],
|
|
2468
|
+
maxTokens: input.maxTokens ?? 200
|
|
2469
|
+
},
|
|
2470
|
+
{ signal }
|
|
2471
|
+
);
|
|
2472
|
+
return extractText(response).trim();
|
|
2473
|
+
}
|
|
2474
|
+
async function llmDecide(request, targets, timeoutMs, digest) {
|
|
2475
|
+
const systemPrompt = readBundledInstructionText("llm/autonomy-brain.md");
|
|
2476
|
+
const userMessage = withDecisionDigest(buildBrainUserMessage(request), digest);
|
|
2477
|
+
let text = null;
|
|
2478
|
+
for (const target of targets) {
|
|
2479
|
+
try {
|
|
2480
|
+
text = await completeBrainLlm(target, {
|
|
2481
|
+
system: systemPrompt,
|
|
2482
|
+
user: userMessage,
|
|
2483
|
+
timeoutMs
|
|
2484
|
+
});
|
|
2485
|
+
break;
|
|
2486
|
+
} catch {
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
if (text === null) {
|
|
2490
|
+
if (request.fallback === "continue") {
|
|
2491
|
+
return {
|
|
2492
|
+
type: "answer",
|
|
2493
|
+
text: "Continue (Autonomy Brain LLM unavailable \u2014 using fallback)."
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
|
|
2497
|
+
}
|
|
2498
|
+
if (request.options?.length) {
|
|
2499
|
+
const parsed = parseOptionDecision(text, request.options);
|
|
2500
|
+
if (parsed) {
|
|
2501
|
+
return parsed;
|
|
2502
|
+
}
|
|
2503
|
+
return {
|
|
2504
|
+
type: "deny",
|
|
2505
|
+
reason: "Autonomy Brain returned no exact valid option id."
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
return {
|
|
2509
|
+
type: "answer",
|
|
2510
|
+
text: text || (request.fallback === "continue" ? "Continue execution." : "Denied by autonomy policy."),
|
|
2511
|
+
rationale: text || void 0
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
function parseOptionDecision(rawText, options) {
|
|
2515
|
+
const text = rawText.trim();
|
|
2516
|
+
const byId = new Map(options.map((option2) => [option2.id, option2]));
|
|
2517
|
+
const wholeFence = /^```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?```$/i.exec(text);
|
|
2518
|
+
const jsonCandidate = wholeFence ? (wholeFence[1] ?? "").trim() : text;
|
|
2519
|
+
const parsed = safeParse(jsonCandidate, 16384);
|
|
2520
|
+
if (parsed.ok && parsed.value && typeof parsed.value.optionId === "string") {
|
|
2521
|
+
const option2 = byId.get(parsed.value.optionId);
|
|
2522
|
+
if (!option2) return null;
|
|
2523
|
+
return {
|
|
2524
|
+
type: "answer",
|
|
2525
|
+
optionId: option2.id,
|
|
2526
|
+
text: option2.label,
|
|
2527
|
+
rationale: typeof parsed.value.rationale === "string" && parsed.value.rationale.trim() ? parsed.value.rationale.trim() : void 0
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
const legacy = /^\s*\[([^\]\r\n]+)\](?:\s*(?:—|-|:)\s*)?([\s\S]*)$/.exec(text);
|
|
2531
|
+
if (!legacy) return null;
|
|
2532
|
+
const option = byId.get((legacy[1] ?? "").trim());
|
|
2533
|
+
if (!option) return null;
|
|
2534
|
+
const rationale = (legacy[2] ?? "").trim();
|
|
2535
|
+
return {
|
|
2536
|
+
type: "answer",
|
|
2537
|
+
optionId: option.id,
|
|
2538
|
+
text: option.label,
|
|
2539
|
+
rationale: rationale || void 0
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
function extractText(result) {
|
|
2543
|
+
if (!result || typeof result !== "object") return "";
|
|
2544
|
+
const r = result;
|
|
2545
|
+
if (Array.isArray(r.content)) {
|
|
2546
|
+
return r.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
|
|
2547
|
+
}
|
|
2548
|
+
if (Array.isArray(r.choices)) {
|
|
2549
|
+
return r.choices[0]?.message?.content ?? "";
|
|
2550
|
+
}
|
|
2551
|
+
return typeof r.text === "string" ? r.text : "";
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// src/types/provider.ts
|
|
2555
|
+
var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
|
|
2556
|
+
var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
|
|
2557
|
+
function classifyProviderError(status, body, message) {
|
|
2558
|
+
const type = body?.type;
|
|
2559
|
+
if (status === 0) return "network";
|
|
2560
|
+
if (status === 408) return "timeout";
|
|
2561
|
+
if (status === 599) return "stream_hang";
|
|
2562
|
+
if (type === "rate_limit_error" || status === 429) return "rate_limit";
|
|
2563
|
+
if (type === "overloaded_error" || status === 529) return "overloaded";
|
|
2564
|
+
if (status >= 500) return "server";
|
|
2565
|
+
if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
|
|
2566
|
+
return "auth";
|
|
2567
|
+
}
|
|
2568
|
+
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
2569
|
+
if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
|
|
2570
|
+
if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
|
|
2571
|
+
return "context_overflow";
|
|
2572
|
+
}
|
|
2573
|
+
if (status >= 400) return "invalid_request";
|
|
2574
|
+
return "unknown";
|
|
2575
|
+
}
|
|
2576
|
+
function isRetryableKind(kind) {
|
|
2577
|
+
return RETRYABLE_BY_KIND[kind];
|
|
2578
|
+
}
|
|
2579
|
+
var RETRYABLE_BY_KIND = {
|
|
2580
|
+
rate_limit: true,
|
|
2581
|
+
overloaded: true,
|
|
2582
|
+
server: true,
|
|
2583
|
+
timeout: true,
|
|
2584
|
+
network: true,
|
|
2585
|
+
stream_hang: true,
|
|
2586
|
+
auth: false,
|
|
2587
|
+
context_overflow: false,
|
|
2588
|
+
content_filter: false,
|
|
2589
|
+
invalid_request: false,
|
|
2590
|
+
unknown: false
|
|
2591
|
+
};
|
|
2592
|
+
function isFallbackWorthy(kind) {
|
|
2593
|
+
return FALLBACK_WORTHY_BY_KIND[kind];
|
|
2594
|
+
}
|
|
2595
|
+
var FALLBACK_WORTHY_BY_KIND = {
|
|
2596
|
+
rate_limit: true,
|
|
2597
|
+
overloaded: true,
|
|
2598
|
+
server: true,
|
|
2599
|
+
timeout: true,
|
|
2600
|
+
network: true,
|
|
2601
|
+
stream_hang: true,
|
|
2602
|
+
auth: false,
|
|
2603
|
+
context_overflow: false,
|
|
2604
|
+
content_filter: false,
|
|
2605
|
+
invalid_request: false,
|
|
2606
|
+
unknown: false
|
|
2607
|
+
};
|
|
2608
|
+
var ProviderError = class extends WrongStackError {
|
|
2609
|
+
status;
|
|
2610
|
+
retryable;
|
|
2611
|
+
providerId;
|
|
2612
|
+
/** Canonical failure classification — see {@link ProviderErrorKind}. */
|
|
2613
|
+
kind;
|
|
2614
|
+
body;
|
|
2615
|
+
constructor(message, status, retryable, providerId, opts = {}) {
|
|
2616
|
+
const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
|
|
2617
|
+
super({
|
|
2618
|
+
message,
|
|
2619
|
+
code: kindToCode(kind),
|
|
2620
|
+
subsystem: "provider",
|
|
2621
|
+
severity: status >= 500 ? "error" : "warning",
|
|
2622
|
+
recoverable: retryable,
|
|
2623
|
+
context: { providerId, status },
|
|
2624
|
+
cause: opts.cause
|
|
2625
|
+
});
|
|
2626
|
+
this.name = "ProviderError";
|
|
2627
|
+
this.status = status;
|
|
2628
|
+
this.retryable = retryable;
|
|
2629
|
+
this.providerId = providerId;
|
|
2630
|
+
this.kind = kind;
|
|
2631
|
+
this.body = opts.body;
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Render a one-line, user-facing description. Designed for the CLI/TUI
|
|
2635
|
+
* status line and the agent's retry warning. Avoids dumping raw JSON
|
|
2636
|
+
* (which is what users see today when a 529 lands and the log message
|
|
2637
|
+
* includes the full `{"type":"error",...}` body).
|
|
2638
|
+
*
|
|
2639
|
+
* Examples:
|
|
2640
|
+
* "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
|
|
2641
|
+
* "openai rate limited (429): Retry after 12s"
|
|
2642
|
+
* "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
|
|
2643
|
+
* "groq HTTP 500 (server error)"
|
|
2644
|
+
*/
|
|
2645
|
+
describe() {
|
|
2646
|
+
const kind = describeStatus(this.status, this.body?.type);
|
|
2647
|
+
const head = `${this.providerId} ${kind}`;
|
|
2648
|
+
const detail = this.body?.message?.trim();
|
|
2649
|
+
const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
|
|
2650
|
+
if (detail && detail.length > 0) {
|
|
2651
|
+
return `${head}: ${truncate(detail, 240)}${reqId}`;
|
|
2652
|
+
}
|
|
2653
|
+
return `${head}${reqId}`;
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
function describeStatus(status, type) {
|
|
2657
|
+
if (status === 0) return "network error";
|
|
2658
|
+
if (status === 599) return `stream hang (${status})`;
|
|
2659
|
+
if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
|
|
2660
|
+
if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
|
|
2661
|
+
if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
|
|
2662
|
+
if (type === "permission_error" || status === 403) return `forbidden (${status})`;
|
|
2663
|
+
if (type === "not_found_error" || status === 404) return `not found (${status})`;
|
|
2664
|
+
if (type === "content_filter") return `content filtered (${status})`;
|
|
2665
|
+
if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
|
|
2666
|
+
if (status === 408) return `timeout (${status})`;
|
|
2667
|
+
if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
|
|
2668
|
+
if (type) return `${type} (${status})`;
|
|
2669
|
+
return `HTTP ${status}`;
|
|
2670
|
+
}
|
|
2671
|
+
var KIND_TO_CODE = {
|
|
2672
|
+
network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
2673
|
+
timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
2674
|
+
rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
2675
|
+
auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
|
|
2676
|
+
overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
|
|
2677
|
+
context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
|
|
2678
|
+
server: ERROR_CODES.PROVIDER_SERVER_ERROR,
|
|
2679
|
+
stream_hang: ERROR_CODES.PROVIDER_SERVER_ERROR,
|
|
2680
|
+
content_filter: ERROR_CODES.PROVIDER_INVALID_REQUEST,
|
|
2681
|
+
invalid_request: ERROR_CODES.PROVIDER_INVALID_REQUEST,
|
|
2682
|
+
unknown: ERROR_CODES.PROVIDER_INVALID_REQUEST
|
|
2683
|
+
};
|
|
2684
|
+
function kindToCode(kind) {
|
|
2685
|
+
return KIND_TO_CODE[kind];
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
// src/core/fallback-model.ts
|
|
2689
|
+
function visibleProviderModels(config, providerId, providerModels) {
|
|
2690
|
+
const entry = config.providers?.[providerId];
|
|
2691
|
+
return entry?.models !== void 0 ? [...entry.models] : providerModels;
|
|
2692
|
+
}
|
|
2693
|
+
function parseModelRef(ref) {
|
|
2694
|
+
const trimmed = ref.trim();
|
|
2695
|
+
const slash = trimmed.indexOf("/");
|
|
2696
|
+
if (slash !== -1) {
|
|
2697
|
+
return {
|
|
2698
|
+
provider: trimmed.slice(0, slash) || void 0,
|
|
2699
|
+
model: trimmed.slice(slash + 1).trim()
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
const parts = trimmed.split(/\s+/);
|
|
2703
|
+
if (parts.length >= 2) {
|
|
2704
|
+
return { provider: parts[0], model: parts.slice(1).join(" ") };
|
|
2705
|
+
}
|
|
2706
|
+
return { model: trimmed };
|
|
2707
|
+
}
|
|
2708
|
+
function formatModelRef(ref, defaultProvider) {
|
|
2709
|
+
const provider = ref.provider ?? defaultProvider;
|
|
2710
|
+
return provider ? `${provider}/${ref.model}` : ref.model;
|
|
2711
|
+
}
|
|
2712
|
+
function normalizeModelRef(ref, defaultProvider) {
|
|
2713
|
+
const parsed = parseModelRef(ref);
|
|
2714
|
+
return formatModelRef(parsed, defaultProvider);
|
|
2715
|
+
}
|
|
2716
|
+
function fallbackProfileChain(config, profileName) {
|
|
2717
|
+
if (!profileName) return [];
|
|
2718
|
+
const chain = config.fallbackProfiles?.[profileName];
|
|
2719
|
+
return Array.isArray(chain) ? chain.filter((ref) => parseModelRef(ref).model) : [];
|
|
2720
|
+
}
|
|
2721
|
+
function providerHasKey(entry) {
|
|
2722
|
+
if (!entry) return false;
|
|
2723
|
+
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
|
|
2724
|
+
if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => k?.apiKey)) return true;
|
|
2725
|
+
if (Array.isArray(entry.envVars) && entry.envVars.some((v) => !!process.env[v])) return true;
|
|
2726
|
+
return false;
|
|
2727
|
+
}
|
|
2728
|
+
var SMART_DEFAULT_MAX = 4;
|
|
2729
|
+
function smartDefaultFallbackChain(config) {
|
|
2730
|
+
const leaderProvider = config.provider;
|
|
2731
|
+
const leaderModel = config.model;
|
|
2732
|
+
const providers = config.providers ?? {};
|
|
2733
|
+
const favoriteSet = new Set(
|
|
2734
|
+
(config.favoriteModels ?? []).map((ref) => normalizeModelRef(ref, leaderProvider))
|
|
2735
|
+
);
|
|
2736
|
+
const hasFavorites = favoriteSet.size > 0;
|
|
2737
|
+
const favoritesOnly = config.favoriteModelsOnly === true;
|
|
2738
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2739
|
+
const favoriteRefs = [];
|
|
2740
|
+
const sameProvider = [];
|
|
2741
|
+
const crossProvider = [];
|
|
2742
|
+
const ids = Object.keys(providers).sort(
|
|
2743
|
+
(a, b) => a === leaderProvider ? -1 : b === leaderProvider ? 1 : a.localeCompare(b)
|
|
2744
|
+
);
|
|
2745
|
+
for (const id of ids) {
|
|
2746
|
+
const entry = providers[id];
|
|
2747
|
+
if (!providerHasKey(entry)) continue;
|
|
2748
|
+
const models = visibleProviderModels(config, id, entry?.models ?? []);
|
|
2749
|
+
for (const model of models) {
|
|
2750
|
+
if (id === leaderProvider && model === leaderModel) continue;
|
|
2751
|
+
const ref = `${id}/${model}`;
|
|
2752
|
+
if (seen.has(ref)) continue;
|
|
2753
|
+
seen.add(ref);
|
|
2754
|
+
if (favoriteSet.has(ref)) {
|
|
2755
|
+
favoriteRefs.push(ref);
|
|
2756
|
+
continue;
|
|
2757
|
+
}
|
|
2758
|
+
if (favoritesOnly && hasFavorites) continue;
|
|
2759
|
+
(id === leaderProvider ? sameProvider : crossProvider).push(ref);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return [...favoriteRefs, ...sameProvider, ...crossProvider].slice(0, SMART_DEFAULT_MAX);
|
|
2763
|
+
}
|
|
2764
|
+
function effectiveFallbackChain(config) {
|
|
2765
|
+
const explicit = config.fallbackModels ?? [];
|
|
2766
|
+
const filteredExplicit = explicit.filter((ref) => {
|
|
2767
|
+
const parsed = parseModelRef(ref);
|
|
2768
|
+
if (!parsed.model) return false;
|
|
2769
|
+
const providerId = parsed.provider ?? config.provider;
|
|
2770
|
+
const entry = config.providers?.[providerId];
|
|
2771
|
+
if (!entry?.models) return true;
|
|
2772
|
+
return entry.models.includes(parsed.model);
|
|
2773
|
+
});
|
|
2774
|
+
if (filteredExplicit.length > 0) return filteredExplicit;
|
|
2775
|
+
if (config.fallbackAuto === false) return [];
|
|
2776
|
+
return smartDefaultFallbackChain(config);
|
|
2777
|
+
}
|
|
2778
|
+
var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
|
|
2779
|
+
|
|
2780
|
+
// src/execution/council-resolution.ts
|
|
2781
|
+
function resolveCouncilVotes(input) {
|
|
2782
|
+
validateInput(input);
|
|
2783
|
+
const seatById = new Map(input.seats.map((seat) => [seat.id, seat]));
|
|
2784
|
+
const validVotes = [];
|
|
2785
|
+
const votedSeatIds = /* @__PURE__ */ new Set();
|
|
2786
|
+
for (const vote of input.votes) {
|
|
2787
|
+
if (!seatById.has(vote.seatId) || votedSeatIds.has(vote.seatId)) continue;
|
|
2788
|
+
votedSeatIds.add(vote.seatId);
|
|
2789
|
+
validVotes.push(vote);
|
|
2790
|
+
}
|
|
2791
|
+
if (validVotes.length / input.seats.length < input.quorumFraction) {
|
|
2792
|
+
return {
|
|
2793
|
+
status: "abstained",
|
|
2794
|
+
reason: "quorum_not_met",
|
|
2795
|
+
validVoteCount: validVotes.length,
|
|
2796
|
+
seatCount: input.seats.length
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
const veto = validVotes.find(
|
|
2800
|
+
(vote) => vote.optionId === input.refusalOptionId && seatById.get(vote.seatId)?.veto === true
|
|
2801
|
+
);
|
|
2802
|
+
if (veto) {
|
|
2803
|
+
return {
|
|
2804
|
+
status: "denied",
|
|
2805
|
+
method: "veto",
|
|
2806
|
+
optionId: veto.optionId,
|
|
2807
|
+
seatId: veto.seatId
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
const weightByOption = /* @__PURE__ */ new Map();
|
|
2811
|
+
let castWeight = 0;
|
|
2812
|
+
for (const vote of validVotes) {
|
|
2813
|
+
const weight = seatById.get(vote.seatId)?.weight ?? 1;
|
|
2814
|
+
castWeight += weight;
|
|
2815
|
+
weightByOption.set(vote.optionId, (weightByOption.get(vote.optionId) ?? 0) + weight);
|
|
2816
|
+
}
|
|
2817
|
+
let winner;
|
|
2818
|
+
let contested = false;
|
|
2819
|
+
for (const [optionId, weight] of weightByOption) {
|
|
2820
|
+
if (!winner || weight > winner.weight) {
|
|
2821
|
+
winner = { optionId, weight };
|
|
2822
|
+
contested = false;
|
|
2823
|
+
} else if (weight === winner.weight) {
|
|
2824
|
+
contested = true;
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
const decisive = winner !== void 0 && !contested && winner.weight > input.approvalFraction * castWeight;
|
|
2828
|
+
if (!decisive || !winner) {
|
|
2829
|
+
return {
|
|
2830
|
+
status: "needs_judge",
|
|
2831
|
+
reason: contested ? "tie" : "approval_threshold_not_met",
|
|
2832
|
+
castWeight
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
if (winner.optionId === input.refusalOptionId) {
|
|
2836
|
+
return {
|
|
2837
|
+
status: "denied",
|
|
2838
|
+
method: "refusal",
|
|
2839
|
+
optionId: winner.optionId,
|
|
2840
|
+
winningWeight: winner.weight,
|
|
2841
|
+
castWeight
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
return {
|
|
2845
|
+
status: "decided",
|
|
2846
|
+
method: "majority",
|
|
2847
|
+
optionId: winner.optionId,
|
|
2848
|
+
winningWeight: winner.weight,
|
|
2849
|
+
castWeight
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
function validateInput(input) {
|
|
2853
|
+
if (input.seats.length === 0) {
|
|
2854
|
+
throw new Error("resolveCouncilVotes: at least one seat is required.");
|
|
2855
|
+
}
|
|
2856
|
+
requireFraction(input.quorumFraction, "quorumFraction");
|
|
2857
|
+
requireFraction(input.approvalFraction, "approvalFraction");
|
|
2858
|
+
const seatIds = /* @__PURE__ */ new Set();
|
|
2859
|
+
for (const seat of input.seats) {
|
|
2860
|
+
if (!seat.id.trim()) throw new Error("resolveCouncilVotes: seat id must not be empty.");
|
|
2861
|
+
if (seatIds.has(seat.id)) {
|
|
2862
|
+
throw new Error(`resolveCouncilVotes: duplicate seat id "${seat.id}".`);
|
|
2863
|
+
}
|
|
2864
|
+
seatIds.add(seat.id);
|
|
2865
|
+
if (seat.weight !== void 0 && (!Number.isFinite(seat.weight) || seat.weight <= 0)) {
|
|
2866
|
+
throw new Error(`resolveCouncilVotes: invalid weight for seat "${seat.id}".`);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function requireFraction(value, label) {
|
|
2871
|
+
if (!Number.isFinite(value) || value <= 0 || value > 1) {
|
|
2872
|
+
throw new Error(`resolveCouncilVotes: ${label} must be in (0, 1].`);
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
// src/execution/council-brain.ts
|
|
2877
|
+
var COUNCIL_REFUSE_OPTION_ID = "council_refuse";
|
|
2878
|
+
var BUILTIN_PERSONAS = {
|
|
2879
|
+
executor: "PERSONA: You hold the EXECUTOR seat. You are biased toward forward progress \u2014 stalled work is the failure mode you exist to prevent. Favor the option that keeps the system moving, unless doing so is clearly reckless.",
|
|
2880
|
+
skeptic: "PERSONA: You hold the SKEPTIC seat. Your job is to find the concrete reason the proposed action is wrong, unsafe, or based on a false premise. If you find one, vote against it or refuse. Do not oppose for its own sake \u2014 a sound proposal deserves your vote.",
|
|
2881
|
+
auditor: "PERSONA: You hold the AUDITOR seat. You judge through the cost/waste lens: budget already burned, expected cost of each option, and the odds of throwing resources at a dead end. Prefer the option with the best expected value per token spent."
|
|
2882
|
+
};
|
|
2883
|
+
function personaText(persona) {
|
|
2884
|
+
if (!persona) return BUILTIN_PERSONAS["executor"] ?? "";
|
|
2885
|
+
return BUILTIN_PERSONAS[persona] ?? `PERSONA: ${persona}`;
|
|
2886
|
+
}
|
|
2887
|
+
function voterLabel(voter, index) {
|
|
2888
|
+
return voter.label ?? `${voter.persona ?? "voter"}#${index + 1} (${voter.model})`;
|
|
2889
|
+
}
|
|
2890
|
+
function ballotOptions(request) {
|
|
2891
|
+
const options = request.options ?? [];
|
|
2892
|
+
if (options.some((o) => o.id === COUNCIL_REFUSE_OPTION_ID)) return options;
|
|
2893
|
+
return [
|
|
2894
|
+
...options,
|
|
2895
|
+
{
|
|
2896
|
+
id: COUNCIL_REFUSE_OPTION_ID,
|
|
2897
|
+
label: "Refuse \u2014 none of these options should be taken",
|
|
2898
|
+
consequence: "The proposed action is not taken at all."
|
|
2899
|
+
}
|
|
2900
|
+
];
|
|
2901
|
+
}
|
|
2902
|
+
function createCouncilBrainArbiter(opts) {
|
|
2903
|
+
if (opts.voters.length === 0) {
|
|
2904
|
+
throw new Error("createCouncilBrainArbiter: at least one voter is required.");
|
|
2905
|
+
}
|
|
2906
|
+
const quorumFraction = opts.quorumFraction ?? 0.5;
|
|
2907
|
+
const approvalFraction = opts.approvalFraction ?? 0.5;
|
|
2908
|
+
const timeoutMs = opts.decisionTimeoutMs ?? 15e3;
|
|
2909
|
+
const baseSystem = readBundledInstructionText("llm/autonomy-brain.md");
|
|
2910
|
+
const abstain = (request, why) => ({
|
|
2911
|
+
type: "ask_human",
|
|
2912
|
+
prompt: `Council abstained (${why}): ${request.question}`,
|
|
2913
|
+
options: request.options,
|
|
2914
|
+
rationale: `Council could not reach a decision: ${why}.`
|
|
2915
|
+
});
|
|
2916
|
+
const finish = (request, decision) => {
|
|
2917
|
+
opts.onDecision?.(formatDecisionSummary(decision, request), decision, request);
|
|
2918
|
+
return decision;
|
|
2919
|
+
};
|
|
2920
|
+
async function collectVotes(request, ballot, digest) {
|
|
2921
|
+
const ballotRequest = { ...request, options: ballot };
|
|
2922
|
+
const user = withDecisionDigest(buildBrainUserMessage(ballotRequest), digest);
|
|
2923
|
+
const settled = await Promise.allSettled(
|
|
2924
|
+
opts.voters.map(
|
|
2925
|
+
(voter) => completeBrainLlm(voter, {
|
|
2926
|
+
system: `${baseSystem}
|
|
2927
|
+
|
|
2928
|
+
${personaText(voter.persona)}`,
|
|
2929
|
+
user,
|
|
2930
|
+
timeoutMs
|
|
2931
|
+
})
|
|
2932
|
+
)
|
|
2933
|
+
);
|
|
2934
|
+
const votes = [];
|
|
2935
|
+
settled.forEach((result, i) => {
|
|
2936
|
+
const voter = opts.voters[i];
|
|
2937
|
+
if (!voter || result.status !== "fulfilled") return;
|
|
2938
|
+
const parsed = parseOptionDecision(result.value, ballot);
|
|
2939
|
+
if (parsed?.type !== "answer" || !parsed.optionId) return;
|
|
2940
|
+
votes.push({
|
|
2941
|
+
seatId: String(i),
|
|
2942
|
+
voter,
|
|
2943
|
+
optionId: parsed.optionId,
|
|
2944
|
+
rationale: parsed.rationale
|
|
2945
|
+
});
|
|
2946
|
+
});
|
|
2947
|
+
return votes;
|
|
2948
|
+
}
|
|
2949
|
+
async function judgeRound(request, ballot, votes, reason) {
|
|
2950
|
+
if (!opts.judge) return null;
|
|
2951
|
+
const voteLines = votes.map(
|
|
2952
|
+
(v, i) => `- ${voterLabel(v.voter, i)} voted [${v.optionId}]` + (v.rationale ? ` \u2014 ${v.rationale}` : "")
|
|
2953
|
+
).join("\n");
|
|
2954
|
+
const judgeRequest = {
|
|
2955
|
+
...request,
|
|
2956
|
+
options: ballot,
|
|
2957
|
+
context: [
|
|
2958
|
+
request.context ?? "",
|
|
2959
|
+
"",
|
|
2960
|
+
`Council votes (${reason} \u2014 you are the judge; issue the final decision):`,
|
|
2961
|
+
voteLines
|
|
2962
|
+
].join("\n").trim()
|
|
2963
|
+
};
|
|
2964
|
+
try {
|
|
2965
|
+
const text = await completeBrainLlm(opts.judge, {
|
|
2966
|
+
system: `${baseSystem}
|
|
2967
|
+
|
|
2968
|
+
PERSONA: You are the council JUDGE. The voting seats disagreed or were too close to call. Weigh their rationales and issue the single final decision.`,
|
|
2969
|
+
user: buildBrainUserMessage(judgeRequest),
|
|
2970
|
+
timeoutMs,
|
|
2971
|
+
maxTokens: 300
|
|
2972
|
+
});
|
|
2973
|
+
const parsed = parseOptionDecision(text, ballot);
|
|
2974
|
+
if (parsed?.type === "answer" && parsed.optionId) return parsed;
|
|
2975
|
+
return null;
|
|
2976
|
+
} catch {
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return {
|
|
2981
|
+
async decide(request) {
|
|
2982
|
+
const digest = opts.getDecisionDigest?.(request);
|
|
2983
|
+
if (!request.options?.length) {
|
|
2984
|
+
const user = withDecisionDigest(buildBrainUserMessage(request), digest);
|
|
2985
|
+
const settled = await Promise.allSettled(
|
|
2986
|
+
opts.voters.map(
|
|
2987
|
+
(voter) => completeBrainLlm(voter, {
|
|
2988
|
+
system: `${baseSystem}
|
|
2989
|
+
|
|
2990
|
+
${personaText(voter.persona)}`,
|
|
2991
|
+
user,
|
|
2992
|
+
timeoutMs
|
|
2993
|
+
})
|
|
2994
|
+
)
|
|
2995
|
+
);
|
|
2996
|
+
const stances = [];
|
|
2997
|
+
settled.forEach((result, i) => {
|
|
2998
|
+
const voter = opts.voters[i];
|
|
2999
|
+
if (!voter || result.status !== "fulfilled" || !result.value.trim()) return;
|
|
3000
|
+
stances.push({ voter, text: result.value.trim() });
|
|
3001
|
+
});
|
|
3002
|
+
if (stances.length / opts.voters.length < quorumFraction) {
|
|
3003
|
+
return finish(request, abstain(request, "quorum not met"));
|
|
3004
|
+
}
|
|
3005
|
+
if (opts.judge) {
|
|
3006
|
+
const stanceLines = stances.map((s, i) => `- ${voterLabel(s.voter, i)}: ${s.text}`).join("\n");
|
|
3007
|
+
try {
|
|
3008
|
+
const text = await completeBrainLlm(opts.judge, {
|
|
3009
|
+
system: `${baseSystem}
|
|
3010
|
+
|
|
3011
|
+
PERSONA: You are the council JUDGE. Synthesize the seats\u2019 independent stances below into the single final decision (1-2 sentences).`,
|
|
3012
|
+
user: `${buildBrainUserMessage(request)}
|
|
3013
|
+
|
|
3014
|
+
Council stances:
|
|
3015
|
+
${stanceLines}`,
|
|
3016
|
+
timeoutMs,
|
|
3017
|
+
maxTokens: 300
|
|
3018
|
+
});
|
|
3019
|
+
if (text) {
|
|
3020
|
+
return finish(request, {
|
|
3021
|
+
type: "answer",
|
|
3022
|
+
text,
|
|
3023
|
+
rationale: `Council synthesis of ${stances.length}/${opts.voters.length} stances.`
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
} catch {
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
const first = stances[0];
|
|
3030
|
+
if (first) {
|
|
3031
|
+
return finish(request, {
|
|
3032
|
+
type: "answer",
|
|
3033
|
+
text: first.text,
|
|
3034
|
+
rationale: `Council (no judge): stance of ${voterLabel(first.voter, 0)}.`
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
return finish(request, abstain(request, "no usable stance"));
|
|
3038
|
+
}
|
|
3039
|
+
const ballot = ballotOptions(request);
|
|
3040
|
+
const votes = await collectVotes(request, ballot, digest);
|
|
3041
|
+
const resolution = resolveCouncilVotes({
|
|
3042
|
+
seats: opts.voters.map((voter, i) => ({
|
|
3043
|
+
id: String(i),
|
|
3044
|
+
weight: voter.weight,
|
|
3045
|
+
veto: voter.veto
|
|
3046
|
+
})),
|
|
3047
|
+
votes: votes.map((vote) => ({ seatId: vote.seatId, optionId: vote.optionId })),
|
|
3048
|
+
refusalOptionId: COUNCIL_REFUSE_OPTION_ID,
|
|
3049
|
+
quorumFraction,
|
|
3050
|
+
approvalFraction
|
|
3051
|
+
});
|
|
3052
|
+
if (resolution.status === "abstained") {
|
|
3053
|
+
return finish(
|
|
3054
|
+
request,
|
|
3055
|
+
abstain(
|
|
3056
|
+
request,
|
|
3057
|
+
`quorum not met (${resolution.validVoteCount}/${resolution.seatCount} votes)`
|
|
3058
|
+
)
|
|
3059
|
+
);
|
|
3060
|
+
}
|
|
3061
|
+
if (resolution.status === "denied" && resolution.method === "veto") {
|
|
3062
|
+
const vetoRefusal = votes.find((vote) => vote.seatId === resolution.seatId);
|
|
3063
|
+
if (!vetoRefusal) {
|
|
3064
|
+
return finish(request, abstain(request, "veto ballot could not be resolved"));
|
|
3065
|
+
}
|
|
3066
|
+
return finish(request, {
|
|
3067
|
+
type: "deny",
|
|
3068
|
+
reason: `Council veto by ${voterLabel(vetoRefusal.voter, 0)}` + (vetoRefusal.rationale ? `: ${vetoRefusal.rationale}` : ".")
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
let finalOptionId = resolution.status === "decided" || resolution.status === "denied" ? resolution.optionId : null;
|
|
3072
|
+
let viaJudge = false;
|
|
3073
|
+
if (resolution.status === "needs_judge") {
|
|
3074
|
+
const judged = await judgeRound(
|
|
3075
|
+
request,
|
|
3076
|
+
ballot,
|
|
3077
|
+
votes,
|
|
3078
|
+
resolution.reason === "tie" ? "tie" : "no option cleared the approval threshold"
|
|
3079
|
+
);
|
|
3080
|
+
if (judged?.type === "answer" && judged.optionId) {
|
|
3081
|
+
finalOptionId = judged.optionId;
|
|
3082
|
+
viaJudge = true;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
if (!finalOptionId) {
|
|
3086
|
+
return finish(request, abstain(request, "split vote and no judge verdict"));
|
|
3087
|
+
}
|
|
3088
|
+
const supporting = votes.filter((v) => v.optionId === finalOptionId);
|
|
3089
|
+
const voteSummary = votes.map((v, i) => `${voterLabel(v.voter, i)}\u2192[${v.optionId}]`).join(", ");
|
|
3090
|
+
const rationale = (supporting.find((v) => v.rationale)?.rationale ?? "") + ` (council${viaJudge ? " via judge" : ""}: ${voteSummary})`;
|
|
3091
|
+
if (finalOptionId === COUNCIL_REFUSE_OPTION_ID) {
|
|
3092
|
+
return finish(request, {
|
|
3093
|
+
type: "deny",
|
|
3094
|
+
reason: `Council refused the proposed action.${rationale}`
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
const option = request.options.find((o) => o.id === finalOptionId);
|
|
3098
|
+
return finish(request, {
|
|
3099
|
+
type: "answer",
|
|
3100
|
+
optionId: finalOptionId,
|
|
3101
|
+
text: option?.label ?? finalOptionId,
|
|
3102
|
+
rationale: rationale.trim()
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
// src/execution/brain-chain.ts
|
|
3109
|
+
var DEFAULT_SEATS = [
|
|
3110
|
+
{ persona: "executor" },
|
|
3111
|
+
{ persona: "skeptic", veto: true },
|
|
3112
|
+
{ persona: "auditor" }
|
|
3113
|
+
];
|
|
3114
|
+
function assembleBrainTiers(opts) {
|
|
3115
|
+
const brainCfg = opts.brainConfig;
|
|
3116
|
+
const toEntry = (entry) => typeof entry === "string" ? parseModelRef(entry) : entry;
|
|
3117
|
+
const buildTarget = (entry) => {
|
|
3118
|
+
if (!entry.model) return null;
|
|
3119
|
+
const providerId = entry.provider ?? opts.defaultProviderId;
|
|
3120
|
+
try {
|
|
3121
|
+
const provider = providerId === opts.defaultProviderId ? opts.sessionProvider() : opts.resolveProvider(providerId, entry.model);
|
|
3122
|
+
if (!provider) return null;
|
|
3123
|
+
return { provider, model: entry.model, label: `${providerId}/${entry.model}` };
|
|
3124
|
+
} catch {
|
|
3125
|
+
return null;
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
const poolTargets = (brainCfg?.models ?? []).map(toEntry).map(buildTarget).filter((t) => t !== null);
|
|
3129
|
+
const autonomous = poolTargets.length > 0 ? createAutonomyBrain({
|
|
3130
|
+
targets: poolTargets,
|
|
3131
|
+
strategy: brainCfg?.strategy ?? "fallback",
|
|
3132
|
+
maxAutoRisk: "all",
|
|
3133
|
+
// the tiered ceiling gates risk — keep inner permissive
|
|
3134
|
+
decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
|
|
3135
|
+
getDecisionDigest: opts.getDecisionDigest
|
|
3136
|
+
}) : {
|
|
3137
|
+
decide: (request) => createAutonomyBrain({
|
|
3138
|
+
provider: opts.sessionProvider(),
|
|
3139
|
+
model: opts.sessionModel(),
|
|
3140
|
+
maxAutoRisk: "all",
|
|
3141
|
+
decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
|
|
3142
|
+
getDecisionDigest: opts.getDecisionDigest
|
|
3143
|
+
}).decide(request)
|
|
3144
|
+
};
|
|
3145
|
+
const councilCfg = brainCfg?.council;
|
|
3146
|
+
const explicitVoters = (councilCfg?.voters ?? []).map(
|
|
3147
|
+
(v) => typeof v === "string" ? { entry: toEntry(v), cfg: {} } : { entry: v, cfg: v }
|
|
3148
|
+
);
|
|
3149
|
+
const voters = (explicitVoters.length > 0 ? explicitVoters.map(({ entry, cfg }, i) => {
|
|
3150
|
+
const target = buildTarget(entry);
|
|
3151
|
+
if (!target) return null;
|
|
3152
|
+
const seat = DEFAULT_SEATS[i % DEFAULT_SEATS.length];
|
|
3153
|
+
return {
|
|
3154
|
+
...target,
|
|
3155
|
+
persona: cfg.persona ?? seat?.persona,
|
|
3156
|
+
weight: cfg.weight,
|
|
3157
|
+
veto: cfg.veto ?? seat?.veto
|
|
3158
|
+
};
|
|
3159
|
+
}) : poolTargets.slice(0, DEFAULT_SEATS.length).map((target, i) => ({
|
|
3160
|
+
...target,
|
|
3161
|
+
...DEFAULT_SEATS[i % DEFAULT_SEATS.length]
|
|
3162
|
+
}))).filter((v) => v !== null);
|
|
3163
|
+
const judgeEntry = councilCfg?.judge ? typeof councilCfg.judge === "string" ? toEntry(councilCfg.judge) : councilCfg.judge : void 0;
|
|
3164
|
+
const judge = (judgeEntry ? buildTarget(judgeEntry) : null) ?? poolTargets[0] ?? voters[0];
|
|
3165
|
+
const councilEnabled = councilCfg?.enabled ?? voters.length >= 2;
|
|
3166
|
+
const council = councilEnabled && voters.length >= 2 ? createCouncilBrainArbiter({
|
|
3167
|
+
voters,
|
|
3168
|
+
judge,
|
|
3169
|
+
quorumFraction: councilCfg?.quorum,
|
|
3170
|
+
approvalFraction: councilCfg?.approval,
|
|
3171
|
+
decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
|
|
3172
|
+
getDecisionDigest: opts.getDecisionDigest
|
|
3173
|
+
}) : void 0;
|
|
3174
|
+
return {
|
|
3175
|
+
autonomous,
|
|
3176
|
+
council,
|
|
3177
|
+
getCouncilMinRisk: () => councilCfg?.minRisk ?? "high",
|
|
3178
|
+
poolLabels: poolTargets.map((t) => t.label ?? t.model),
|
|
3179
|
+
councilLabels: council ? voters.map((v) => `${v.label ?? v.model} (${v.persona ?? "voter"}${v.veto ? ", veto" : ""})`) : []
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
// src/execution/brain-evaluation.ts
|
|
3184
|
+
var BRAIN_EVALUATION_CASE_VERSION = 1;
|
|
3185
|
+
function isRecord3(value) {
|
|
3186
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3187
|
+
}
|
|
3188
|
+
function isStringArray(value) {
|
|
3189
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
3190
|
+
}
|
|
3191
|
+
function validateBrainEvaluationCase(value) {
|
|
3192
|
+
const diagnostics = [];
|
|
3193
|
+
if (!isRecord3(value)) return { ok: false, diagnostics: ["case must be an object"] };
|
|
3194
|
+
if (value["version"] !== BRAIN_EVALUATION_CASE_VERSION) {
|
|
3195
|
+
diagnostics.push(`version must be ${BRAIN_EVALUATION_CASE_VERSION}`);
|
|
3196
|
+
}
|
|
3197
|
+
if (typeof value["id"] !== "string" || value["id"].trim().length === 0) {
|
|
3198
|
+
diagnostics.push("id must be a non-empty string");
|
|
3199
|
+
}
|
|
3200
|
+
const request = value["request"];
|
|
3201
|
+
if (!isRecord3(request)) {
|
|
3202
|
+
diagnostics.push("request must be an object");
|
|
3203
|
+
} else {
|
|
3204
|
+
if (typeof request["id"] !== "string" || request["id"].trim().length === 0) {
|
|
3205
|
+
diagnostics.push("request.id must be a non-empty string");
|
|
3206
|
+
}
|
|
3207
|
+
if (!["autophase", "director", "tool", "user", "system"].includes(String(request["source"]))) {
|
|
3208
|
+
diagnostics.push("request.source is invalid");
|
|
3209
|
+
}
|
|
3210
|
+
if (typeof request["question"] !== "string" || request["question"].trim().length === 0) {
|
|
3211
|
+
diagnostics.push("request.question must be a non-empty string");
|
|
3212
|
+
}
|
|
3213
|
+
if (!["low", "medium", "high", "critical"].includes(String(request["risk"]))) {
|
|
3214
|
+
diagnostics.push("request.risk is invalid");
|
|
3215
|
+
}
|
|
3216
|
+
if (!["ask_human", "deny", "continue"].includes(String(request["fallback"]))) {
|
|
3217
|
+
diagnostics.push("request.fallback is invalid");
|
|
3218
|
+
}
|
|
3219
|
+
if (request["options"] !== void 0) {
|
|
3220
|
+
if (!Array.isArray(request["options"])) {
|
|
3221
|
+
diagnostics.push("request.options must be an array");
|
|
3222
|
+
} else {
|
|
3223
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3224
|
+
for (const [index, option] of request["options"].entries()) {
|
|
3225
|
+
if (!isRecord3(option)) {
|
|
3226
|
+
diagnostics.push(`request.options[${index}] must be an object`);
|
|
3227
|
+
continue;
|
|
3228
|
+
}
|
|
3229
|
+
const id = option["id"];
|
|
3230
|
+
if (typeof id !== "string" || id.trim().length === 0) {
|
|
3231
|
+
diagnostics.push(`request.options[${index}].id must be a non-empty string`);
|
|
3232
|
+
} else if (ids.has(id)) {
|
|
3233
|
+
diagnostics.push(`request option id "${id}" is duplicated`);
|
|
3234
|
+
} else {
|
|
3235
|
+
ids.add(id);
|
|
3236
|
+
}
|
|
3237
|
+
if (typeof option["label"] !== "string" || option["label"].trim().length === 0) {
|
|
3238
|
+
diagnostics.push(`request.options[${index}].label must be a non-empty string`);
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
const expectations = value["expectations"];
|
|
3245
|
+
if (expectations !== void 0 && !isRecord3(expectations)) {
|
|
3246
|
+
diagnostics.push("expectations must be an object");
|
|
3247
|
+
} else if (isRecord3(expectations)) {
|
|
3248
|
+
const allowedTypes = expectations["allowedDecisionTypes"];
|
|
3249
|
+
if (allowedTypes !== void 0 && (!isStringArray(allowedTypes) || allowedTypes.length === 0 || allowedTypes.some((entry) => !["answer", "ask_human", "deny"].includes(entry)))) {
|
|
3250
|
+
diagnostics.push("expectations.allowedDecisionTypes is invalid");
|
|
3251
|
+
}
|
|
3252
|
+
for (const field of ["allowedOptionIds", "unsafeOptionIds"]) {
|
|
3253
|
+
const entries = expectations[field];
|
|
3254
|
+
if (entries !== void 0 && !isStringArray(entries)) {
|
|
3255
|
+
diagnostics.push(`expectations.${field} must be a string array`);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
if (isRecord3(request) && Array.isArray(request["options"])) {
|
|
3259
|
+
const requestIds = new Set(
|
|
3260
|
+
request["options"].filter(isRecord3).map((option) => option["id"]).filter((id) => typeof id === "string")
|
|
3261
|
+
);
|
|
3262
|
+
for (const field of ["allowedOptionIds", "unsafeOptionIds"]) {
|
|
3263
|
+
const entries = expectations[field];
|
|
3264
|
+
if (!isStringArray(entries)) continue;
|
|
3265
|
+
for (const id of entries) {
|
|
3266
|
+
if (!requestIds.has(id))
|
|
3267
|
+
diagnostics.push(`expectations.${field} contains unknown id "${id}"`);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
for (const field of ["requireOptionId", "allowEscalation"]) {
|
|
3272
|
+
const setting = expectations[field];
|
|
3273
|
+
if (setting !== void 0 && typeof setting !== "boolean") {
|
|
3274
|
+
diagnostics.push(`expectations.${field} must be a boolean`);
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
return diagnostics.length > 0 ? { ok: false, diagnostics } : { ok: true, value };
|
|
3279
|
+
}
|
|
3280
|
+
function assessDecision(fixture, decision) {
|
|
3281
|
+
const failures = [];
|
|
3282
|
+
const expectations = fixture.expectations;
|
|
3283
|
+
if (expectations?.allowedDecisionTypes && !expectations.allowedDecisionTypes.includes(decision.type)) {
|
|
3284
|
+
failures.push({
|
|
3285
|
+
code: "unexpected_decision_type",
|
|
3286
|
+
message: `decision type "${decision.type}" is not allowed`
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
if (decision.type === "ask_human" && expectations?.allowEscalation !== true) {
|
|
3290
|
+
failures.push({
|
|
3291
|
+
code: "unexpected_escalation",
|
|
3292
|
+
message: "case escalated to a human without allowEscalation"
|
|
3293
|
+
});
|
|
3294
|
+
}
|
|
3295
|
+
if (decision.type !== "answer") return failures;
|
|
3296
|
+
const requestOptionIds = new Set(fixture.request.options?.map((option) => option.id) ?? []);
|
|
3297
|
+
if (decision.optionId === void 0) {
|
|
3298
|
+
if (expectations?.requireOptionId || expectations?.allowedOptionIds) {
|
|
3299
|
+
failures.push({
|
|
3300
|
+
code: "missing_option_id",
|
|
3301
|
+
message: "answer did not return the required exact option id"
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
return failures;
|
|
3305
|
+
}
|
|
3306
|
+
if (!requestOptionIds.has(decision.optionId)) {
|
|
3307
|
+
failures.push({
|
|
3308
|
+
code: "invalid_option_id",
|
|
3309
|
+
message: `answer returned unknown option id "${decision.optionId}"`
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
if (expectations?.allowedOptionIds && !expectations.allowedOptionIds.includes(decision.optionId)) {
|
|
3313
|
+
failures.push({
|
|
3314
|
+
code: "disallowed_option_id",
|
|
3315
|
+
message: `option id "${decision.optionId}" is not allowed for this case`
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
if (expectations?.unsafeOptionIds?.includes(decision.optionId)) {
|
|
3319
|
+
failures.push({
|
|
3320
|
+
code: "unsafe_option_id",
|
|
3321
|
+
message: `unsafe option id "${decision.optionId}" was selected`
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
return failures;
|
|
3325
|
+
}
|
|
3326
|
+
async function runBrainEvaluation(arbiter, cases) {
|
|
3327
|
+
const results = [];
|
|
3328
|
+
const seenCaseIds = /* @__PURE__ */ new Set();
|
|
3329
|
+
for (const rawCase of cases) {
|
|
3330
|
+
const validation = validateBrainEvaluationCase(rawCase);
|
|
3331
|
+
if (!validation.ok) {
|
|
3332
|
+
const caseId = isRecord3(rawCase) && typeof rawCase["id"] === "string" ? rawCase["id"] : "<invalid>";
|
|
3333
|
+
results.push({
|
|
3334
|
+
caseId,
|
|
3335
|
+
passed: false,
|
|
3336
|
+
failures: validation.diagnostics.map((message) => ({ code: "invalid_fixture", message })),
|
|
3337
|
+
latencyMs: 0
|
|
3338
|
+
});
|
|
3339
|
+
continue;
|
|
3340
|
+
}
|
|
3341
|
+
const fixture = validation.value;
|
|
3342
|
+
if (seenCaseIds.has(fixture.id)) {
|
|
3343
|
+
results.push({
|
|
3344
|
+
caseId: fixture.id,
|
|
3345
|
+
passed: false,
|
|
3346
|
+
failures: [{ code: "invalid_fixture", message: `case id "${fixture.id}" is duplicated` }],
|
|
3347
|
+
latencyMs: 0
|
|
3348
|
+
});
|
|
3349
|
+
continue;
|
|
3350
|
+
}
|
|
3351
|
+
seenCaseIds.add(fixture.id);
|
|
3352
|
+
const startedAt = performance.now();
|
|
3353
|
+
try {
|
|
3354
|
+
const decision = await arbiter.decide(structuredClone(fixture.request));
|
|
3355
|
+
const failures = assessDecision(fixture, decision);
|
|
3356
|
+
results.push({
|
|
3357
|
+
caseId: fixture.id,
|
|
3358
|
+
passed: failures.length === 0,
|
|
3359
|
+
decision,
|
|
3360
|
+
failures,
|
|
3361
|
+
latencyMs: Math.max(0, performance.now() - startedAt)
|
|
3362
|
+
});
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
results.push({
|
|
3365
|
+
caseId: fixture.id,
|
|
3366
|
+
passed: false,
|
|
3367
|
+
failures: [
|
|
3368
|
+
{
|
|
3369
|
+
code: "arbiter_error",
|
|
3370
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3371
|
+
}
|
|
3372
|
+
],
|
|
3373
|
+
latencyMs: Math.max(0, performance.now() - startedAt)
|
|
3374
|
+
});
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
const optionResults = results.filter(
|
|
3378
|
+
(result) => result.decision?.type === "answer" && result.decision.optionId !== void 0
|
|
3379
|
+
);
|
|
3380
|
+
const invalidOptionCount = results.reduce(
|
|
3381
|
+
(count, result) => count + result.failures.filter((failure) => failure.code === "invalid_option_id").length,
|
|
3382
|
+
0
|
|
3383
|
+
);
|
|
3384
|
+
const totalLatency = results.reduce((sum, result) => sum + result.latencyMs, 0);
|
|
3385
|
+
const metrics = {
|
|
3386
|
+
totalCases: results.length,
|
|
3387
|
+
passedCases: results.filter((result) => result.passed).length,
|
|
3388
|
+
failedCases: results.filter((result) => !result.passed).length,
|
|
3389
|
+
optionDecisionCount: optionResults.length,
|
|
3390
|
+
invalidOptionCount,
|
|
3391
|
+
validOptionRate: optionResults.length === 0 ? 1 : (optionResults.length - invalidOptionCount) / optionResults.length,
|
|
3392
|
+
unsafeAllowanceCount: results.reduce(
|
|
3393
|
+
(count, result) => count + result.failures.filter((failure) => failure.code === "unsafe_option_id").length,
|
|
3394
|
+
0
|
|
3395
|
+
),
|
|
3396
|
+
unnecessaryEscalationCount: results.reduce(
|
|
3397
|
+
(count, result) => count + result.failures.filter((failure) => failure.code === "unexpected_escalation").length,
|
|
3398
|
+
0
|
|
3399
|
+
),
|
|
3400
|
+
averageLatencyMs: results.length === 0 ? 0 : totalLatency / results.length
|
|
3401
|
+
};
|
|
3402
|
+
return { version: BRAIN_EVALUATION_CASE_VERSION, results, metrics };
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
// src/coordination/brain.ts
|
|
3406
|
+
var DefaultBrainArbiter = class {
|
|
3407
|
+
allowLowRiskAutoAnswer;
|
|
3408
|
+
constructor(opts = {}) {
|
|
3409
|
+
this.allowLowRiskAutoAnswer = opts.allowLowRiskAutoAnswer ?? true;
|
|
3410
|
+
}
|
|
3411
|
+
async decide(request) {
|
|
3412
|
+
const recommended = request.options?.find((option) => option.recommended);
|
|
3413
|
+
if (this.allowLowRiskAutoAnswer && request.risk === "low" && recommended) {
|
|
3414
|
+
return {
|
|
3415
|
+
type: "answer",
|
|
3416
|
+
optionId: recommended.id,
|
|
3417
|
+
text: recommended.label,
|
|
3418
|
+
rationale: "Low-risk request with an explicit recommended option."
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
switch (request.fallback) {
|
|
3422
|
+
case "deny":
|
|
3423
|
+
return {
|
|
3424
|
+
type: "deny",
|
|
3425
|
+
reason: `Brain could not safely decide: ${request.question}`
|
|
3426
|
+
};
|
|
3427
|
+
case "continue":
|
|
3428
|
+
return {
|
|
3429
|
+
type: "answer",
|
|
3430
|
+
text: "Continue with the caller default.",
|
|
3431
|
+
rationale: "No safe Brain decision was available; request fallback is continue."
|
|
3432
|
+
};
|
|
3433
|
+
case "ask_human":
|
|
3434
|
+
return {
|
|
3435
|
+
type: "ask_human",
|
|
3436
|
+
prompt: formatHumanPrompt(request),
|
|
3437
|
+
options: request.options,
|
|
3438
|
+
rationale: "Decision requires human authority or lacks a safe automatic option."
|
|
3439
|
+
};
|
|
3440
|
+
default:
|
|
3441
|
+
return {
|
|
3442
|
+
type: "deny",
|
|
3443
|
+
reason: `Brain could not safely decide (unknown fallback: ${String(request.fallback)}): ${request.question}`
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
function formatHumanPrompt(request) {
|
|
3449
|
+
const lines = [
|
|
3450
|
+
`Brain requires human decision for ${request.source}:`,
|
|
3451
|
+
`Question: ${request.question}`
|
|
3452
|
+
];
|
|
3453
|
+
if (request.context?.trim()) {
|
|
3454
|
+
lines.push("", "Context:", request.context.trim());
|
|
3455
|
+
}
|
|
3456
|
+
if (request.options?.length) {
|
|
3457
|
+
lines.push("", "Options:");
|
|
3458
|
+
for (const option of request.options) {
|
|
3459
|
+
const risk = option.risk ? ` [risk: ${option.risk}]` : "";
|
|
3460
|
+
const consequence = option.consequence ? ` \u2014 ${option.consequence}` : "";
|
|
3461
|
+
lines.push(`- ${option.id}: ${option.label}${risk}${consequence}`);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
lines.push("", `Risk: ${request.risk}`);
|
|
3465
|
+
return lines.join("\n");
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
// src/coordination/brain-ledger.ts
|
|
3469
|
+
function createLedgerGuardBrainArbiter(opts) {
|
|
3470
|
+
const denyAfter = opts.denyAfter ?? 3;
|
|
3471
|
+
return {
|
|
3472
|
+
async decide(request) {
|
|
3473
|
+
if (denyAfter > 0) {
|
|
3474
|
+
const streak = opts.failureStreakFor(request);
|
|
3475
|
+
if (streak >= denyAfter) {
|
|
3476
|
+
return {
|
|
3477
|
+
type: "deny",
|
|
3478
|
+
reason: `Ledger guard: the last ${streak} approved decisions of this kind all ended in observed failures \u2014 denying deterministically until a success is recorded. (${request.question.slice(0, 120)})`
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
return opts.inner.decide(request);
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// src/execution/brain-runtime.ts
|
|
3488
|
+
var FAST_PATH_KEYS = /* @__PURE__ */ new Set(["mode", "maxAutoRisk", "humanTimeoutMs"]);
|
|
3489
|
+
var AUTO_RISK_LEVELS = /* @__PURE__ */ new Set(["off", "low", "medium", "high", "all"]);
|
|
3490
|
+
var COUNCIL_MIN_RISKS = /* @__PURE__ */ new Set(["medium", "high", "critical"]);
|
|
3491
|
+
function normalizeEntry(raw) {
|
|
3492
|
+
const parsed = typeof raw === "string" ? parseModelRef(raw) : raw;
|
|
3493
|
+
const model = parsed.model?.trim();
|
|
3494
|
+
if (!model) {
|
|
3495
|
+
throw new Error(
|
|
3496
|
+
`Invalid model ref: ${typeof raw === "string" ? `"${raw}"` : JSON.stringify(raw)} (expected "model" or "provider/model")`
|
|
3497
|
+
);
|
|
3498
|
+
}
|
|
3499
|
+
const provider = parsed.provider?.trim();
|
|
3500
|
+
return provider ? { provider, model } : { model };
|
|
3501
|
+
}
|
|
3502
|
+
function normalizeVoter(raw) {
|
|
3503
|
+
if (typeof raw === "string") return normalizeEntry(raw);
|
|
3504
|
+
const base = normalizeEntry(raw);
|
|
3505
|
+
const voter = { ...base };
|
|
3506
|
+
if (raw.persona !== void 0) voter.persona = raw.persona;
|
|
3507
|
+
if (raw.weight !== void 0) {
|
|
3508
|
+
if (!Number.isFinite(raw.weight) || raw.weight <= 0) {
|
|
3509
|
+
throw new Error(`Invalid voter weight: ${String(raw.weight)} (must be a positive number)`);
|
|
3510
|
+
}
|
|
3511
|
+
voter.weight = raw.weight;
|
|
3512
|
+
}
|
|
3513
|
+
if (raw.veto !== void 0) voter.veto = raw.veto;
|
|
3514
|
+
return voter;
|
|
3515
|
+
}
|
|
3516
|
+
function requireFraction2(value, label) {
|
|
3517
|
+
if (!Number.isFinite(value) || value <= 0 || value > 1) {
|
|
3518
|
+
throw new Error(`Invalid ${label}: ${String(value)} (must be in (0, 1])`);
|
|
3519
|
+
}
|
|
3520
|
+
return value;
|
|
3521
|
+
}
|
|
3522
|
+
function requirePositiveMs(value, label) {
|
|
3523
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
3524
|
+
throw new Error(
|
|
3525
|
+
`Invalid ${label}: ${String(value)} (must be a positive number of milliseconds)`
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
return Math.round(value);
|
|
3529
|
+
}
|
|
3530
|
+
function compactEntry(entry) {
|
|
3531
|
+
return entry.provider ? `${entry.provider}/${entry.model}` : entry.model;
|
|
3532
|
+
}
|
|
3533
|
+
function compactVoter(voter) {
|
|
3534
|
+
if (voter.persona === void 0 && voter.weight === void 0 && voter.veto === void 0) {
|
|
3535
|
+
return compactEntry(voter);
|
|
3536
|
+
}
|
|
3537
|
+
return { ...voter };
|
|
3538
|
+
}
|
|
3539
|
+
function resolveBrainConfigDefaults(brain, ctx = {}) {
|
|
3540
|
+
const cfg = { ...brain ?? {} };
|
|
3541
|
+
if (cfg.models === void 0 && ctx.fallbackModels && ctx.fallbackModels.length > 0) {
|
|
3542
|
+
cfg.models = [...ctx.fallbackModels];
|
|
3543
|
+
}
|
|
3544
|
+
const seatCount = cfg.council?.voters?.length ?? cfg.models?.length ?? 0;
|
|
3545
|
+
const councilLikely = cfg.council?.enabled ?? seatCount >= 2;
|
|
3546
|
+
if (cfg.mode === void 0) cfg.mode = "headless";
|
|
3547
|
+
if (cfg.maxAutoRisk === void 0) cfg.maxAutoRisk = councilLikely ? "all" : "high";
|
|
3548
|
+
if (cfg.humanTimeoutMs === void 0) cfg.humanTimeoutMs = 12e4;
|
|
3549
|
+
return cfg;
|
|
3550
|
+
}
|
|
3551
|
+
function normalizeInitial(config) {
|
|
3552
|
+
const cfg = { ...config ?? {} };
|
|
3553
|
+
const safe = (items, map) => (items ?? []).flatMap((item) => {
|
|
3554
|
+
try {
|
|
3555
|
+
return [map(item)];
|
|
3556
|
+
} catch {
|
|
3557
|
+
return [];
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
if (cfg.models) cfg.models = safe(cfg.models, normalizeEntry);
|
|
3561
|
+
if (cfg.council) {
|
|
3562
|
+
const council = { ...cfg.council };
|
|
3563
|
+
if (council.voters) council.voters = safe(council.voters, normalizeVoter);
|
|
3564
|
+
if (council.judge) {
|
|
3565
|
+
try {
|
|
3566
|
+
council.judge = normalizeEntry(council.judge);
|
|
3567
|
+
} catch {
|
|
3568
|
+
council.judge = void 0;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
cfg.council = council;
|
|
3572
|
+
}
|
|
3573
|
+
return cfg;
|
|
3574
|
+
}
|
|
3575
|
+
function createBrainRuntime(opts) {
|
|
3576
|
+
let cfg = normalizeInitial(opts.initialConfig);
|
|
3577
|
+
let poolLabels = [];
|
|
3578
|
+
let councilLabels = [];
|
|
3579
|
+
let current;
|
|
3580
|
+
const getDecisionDigest = (request) => {
|
|
3581
|
+
const ledger = opts.ledger;
|
|
3582
|
+
if (!ledger?.isEnabled()) return void 0;
|
|
3583
|
+
return ledger.getDecisionDigest?.(request);
|
|
3584
|
+
};
|
|
3585
|
+
function rebuild() {
|
|
3586
|
+
const tiers = assembleBrainTiers({
|
|
3587
|
+
brainConfig: cfg,
|
|
3588
|
+
defaultProviderId: opts.defaultProviderId,
|
|
3589
|
+
sessionProvider: opts.sessionProvider,
|
|
3590
|
+
sessionModel: opts.sessionModel,
|
|
3591
|
+
resolveProvider: opts.resolveProvider,
|
|
3592
|
+
getDecisionDigest
|
|
3593
|
+
});
|
|
3594
|
+
poolLabels = tiers.poolLabels;
|
|
3595
|
+
councilLabels = tiers.councilLabels;
|
|
3596
|
+
const tiered = createTieredBrainArbiter({
|
|
3597
|
+
policy: new DefaultBrainArbiter(),
|
|
3598
|
+
autonomous: tiers.autonomous,
|
|
3599
|
+
getMaxAutoRisk: () => cfg.maxAutoRisk ?? "medium",
|
|
3600
|
+
council: tiers.council,
|
|
3601
|
+
getCouncilMinRisk: tiers.getCouncilMinRisk
|
|
3602
|
+
});
|
|
3603
|
+
const streakFor = opts.ledger?.failureStreakFor;
|
|
3604
|
+
current = streakFor && opts.ledger?.isEnabled() ? createLedgerGuardBrainArbiter({
|
|
3605
|
+
inner: tiered,
|
|
3606
|
+
failureStreakFor: streakFor,
|
|
3607
|
+
denyAfter: cfg.ledger?.autoDenyAfterFailures
|
|
3608
|
+
}) : tiered;
|
|
3609
|
+
}
|
|
3610
|
+
rebuild();
|
|
3611
|
+
function mergePatch(patch) {
|
|
3612
|
+
const next = { ...cfg, council: cfg.council ? { ...cfg.council } : void 0 };
|
|
3613
|
+
let rebuildNeeded = false;
|
|
3614
|
+
const structural = () => {
|
|
3615
|
+
rebuildNeeded = true;
|
|
3616
|
+
};
|
|
3617
|
+
if (patch.mode !== void 0) {
|
|
3618
|
+
if (patch.mode !== "headless" && patch.mode !== "interactive") {
|
|
3619
|
+
throw new Error(`Invalid mode: ${String(patch.mode)}`);
|
|
3620
|
+
}
|
|
3621
|
+
next.mode = patch.mode;
|
|
3622
|
+
}
|
|
3623
|
+
if (patch.maxAutoRisk !== void 0) {
|
|
3624
|
+
if (!AUTO_RISK_LEVELS.has(patch.maxAutoRisk)) {
|
|
3625
|
+
throw new Error(`Invalid maxAutoRisk: ${String(patch.maxAutoRisk)}`);
|
|
3626
|
+
}
|
|
3627
|
+
next.maxAutoRisk = patch.maxAutoRisk;
|
|
3628
|
+
}
|
|
3629
|
+
if (patch.humanTimeoutMs !== void 0) {
|
|
3630
|
+
next.humanTimeoutMs = patch.humanTimeoutMs === null ? void 0 : requirePositiveMs(patch.humanTimeoutMs, "humanTimeoutMs");
|
|
3631
|
+
}
|
|
3632
|
+
if (patch.models !== void 0) {
|
|
3633
|
+
next.models = patch.models === null ? void 0 : patch.models.map(normalizeEntry);
|
|
3634
|
+
structural();
|
|
3635
|
+
}
|
|
3636
|
+
if (patch.strategy !== void 0) {
|
|
3637
|
+
if (patch.strategy !== null && patch.strategy !== "fallback" && patch.strategy !== "round-robin") {
|
|
3638
|
+
throw new Error(`Invalid strategy: ${String(patch.strategy)}`);
|
|
3639
|
+
}
|
|
3640
|
+
next.strategy = patch.strategy ?? void 0;
|
|
3641
|
+
structural();
|
|
3642
|
+
}
|
|
3643
|
+
if (patch.decisionTimeoutMs !== void 0) {
|
|
3644
|
+
next.decisionTimeoutMs = patch.decisionTimeoutMs === null ? void 0 : requirePositiveMs(patch.decisionTimeoutMs, "decisionTimeoutMs");
|
|
3645
|
+
structural();
|
|
3646
|
+
}
|
|
3647
|
+
if (patch.council !== void 0) {
|
|
3648
|
+
if (patch.council === null) {
|
|
3649
|
+
next.council = void 0;
|
|
3650
|
+
} else {
|
|
3651
|
+
const c = { ...next.council ?? {} };
|
|
3652
|
+
const p = patch.council;
|
|
3653
|
+
if (p.enabled !== void 0) c.enabled = p.enabled === null ? void 0 : p.enabled;
|
|
3654
|
+
if (p.minRisk !== void 0) {
|
|
3655
|
+
if (p.minRisk !== null && !COUNCIL_MIN_RISKS.has(p.minRisk)) {
|
|
3656
|
+
throw new Error(`Invalid council minRisk: ${String(p.minRisk)}`);
|
|
3657
|
+
}
|
|
3658
|
+
c.minRisk = p.minRisk ?? void 0;
|
|
3659
|
+
}
|
|
3660
|
+
if (p.voters !== void 0) {
|
|
3661
|
+
c.voters = p.voters === null ? void 0 : p.voters.map(normalizeVoter);
|
|
3662
|
+
}
|
|
3663
|
+
if (p.quorum !== void 0) {
|
|
3664
|
+
c.quorum = p.quorum === null ? void 0 : requireFraction2(p.quorum, "council quorum");
|
|
3665
|
+
}
|
|
3666
|
+
if (p.approval !== void 0) {
|
|
3667
|
+
c.approval = p.approval === null ? void 0 : requireFraction2(p.approval, "council approval");
|
|
3668
|
+
}
|
|
3669
|
+
if (p.judge !== void 0) {
|
|
3670
|
+
c.judge = p.judge === null ? void 0 : normalizeEntry(p.judge);
|
|
3671
|
+
}
|
|
3672
|
+
next.council = c;
|
|
3673
|
+
}
|
|
3674
|
+
structural();
|
|
3675
|
+
}
|
|
3676
|
+
if (patch.ledger !== void 0) {
|
|
3677
|
+
if (patch.ledger === null) {
|
|
3678
|
+
next.ledger = void 0;
|
|
3679
|
+
} else {
|
|
3680
|
+
const l = { ...next.ledger ?? {} };
|
|
3681
|
+
if (patch.ledger.enabled !== void 0) l.enabled = patch.ledger.enabled;
|
|
3682
|
+
if (patch.ledger.autoDenyAfterFailures !== void 0) {
|
|
3683
|
+
const n = patch.ledger.autoDenyAfterFailures;
|
|
3684
|
+
if (n !== null && (!Number.isInteger(n) || n < 0)) {
|
|
3685
|
+
throw new Error(
|
|
3686
|
+
`Invalid autoDenyAfterFailures: ${String(n)} (must be an integer >= 0)`
|
|
3687
|
+
);
|
|
3688
|
+
}
|
|
3689
|
+
l.autoDenyAfterFailures = n ?? void 0;
|
|
3690
|
+
}
|
|
3691
|
+
next.ledger = l;
|
|
3692
|
+
}
|
|
3693
|
+
structural();
|
|
3694
|
+
}
|
|
3695
|
+
for (const key of Object.keys(patch)) {
|
|
3696
|
+
if (!FAST_PATH_KEYS.has(key) && patch[key] !== void 0) {
|
|
3697
|
+
if (!["models", "strategy", "decisionTimeoutMs", "council", "ledger"].includes(key)) {
|
|
3698
|
+
throw new Error(`Unknown brain config field: ${key}`);
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
return { next, rebuildNeeded };
|
|
3703
|
+
}
|
|
3704
|
+
function getSnapshot() {
|
|
3705
|
+
const councilCfg = cfg.council;
|
|
3706
|
+
const voters = (councilCfg?.voters ?? []).map((v) => normalizeVoter(v));
|
|
3707
|
+
return {
|
|
3708
|
+
mode: cfg.mode ?? "interactive",
|
|
3709
|
+
maxAutoRisk: cfg.maxAutoRisk ?? "medium",
|
|
3710
|
+
models: (cfg.models ?? []).map((m) => normalizeEntry(m)),
|
|
3711
|
+
strategy: cfg.strategy ?? "fallback",
|
|
3712
|
+
decisionTimeoutMs: cfg.decisionTimeoutMs,
|
|
3713
|
+
humanTimeoutMs: cfg.humanTimeoutMs,
|
|
3714
|
+
council: {
|
|
3715
|
+
enabled: councilLabels.length > 0,
|
|
3716
|
+
configured: councilCfg?.enabled,
|
|
3717
|
+
minRisk: councilCfg?.minRisk ?? "high",
|
|
3718
|
+
voters,
|
|
3719
|
+
quorum: councilCfg?.quorum,
|
|
3720
|
+
approval: councilCfg?.approval,
|
|
3721
|
+
judge: councilCfg?.judge ? normalizeEntry(councilCfg.judge) : void 0
|
|
3722
|
+
},
|
|
3723
|
+
ledger: {
|
|
3724
|
+
enabled: opts.ledger?.isEnabled() ?? false,
|
|
3725
|
+
autoDenyAfterFailures: cfg.ledger?.autoDenyAfterFailures,
|
|
3726
|
+
path: opts.ledger?.getPath()
|
|
3727
|
+
},
|
|
3728
|
+
poolLabels: [...poolLabels],
|
|
3729
|
+
councilLabels: [...councilLabels],
|
|
3730
|
+
usingSessionModel: (cfg.models ?? []).length === 0
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
function getConfig() {
|
|
3734
|
+
const out = {};
|
|
3735
|
+
if (cfg.mode !== void 0) out.mode = cfg.mode;
|
|
3736
|
+
if (cfg.maxAutoRisk !== void 0) out.maxAutoRisk = cfg.maxAutoRisk;
|
|
3737
|
+
if (cfg.models?.length) out.models = cfg.models.map((m) => compactEntry(normalizeEntry(m)));
|
|
3738
|
+
if (cfg.strategy !== void 0) out.strategy = cfg.strategy;
|
|
3739
|
+
if (cfg.decisionTimeoutMs !== void 0) out.decisionTimeoutMs = cfg.decisionTimeoutMs;
|
|
3740
|
+
if (cfg.humanTimeoutMs !== void 0) out.humanTimeoutMs = cfg.humanTimeoutMs;
|
|
3741
|
+
if (cfg.council !== void 0) {
|
|
3742
|
+
const c = cfg.council;
|
|
3743
|
+
const outCouncil = {};
|
|
3744
|
+
if (c.enabled !== void 0) outCouncil.enabled = c.enabled;
|
|
3745
|
+
if (c.minRisk !== void 0) outCouncil.minRisk = c.minRisk;
|
|
3746
|
+
if (c.voters?.length)
|
|
3747
|
+
outCouncil.voters = c.voters.map((v) => compactVoter(normalizeVoter(v)));
|
|
3748
|
+
if (c.quorum !== void 0) outCouncil.quorum = c.quorum;
|
|
3749
|
+
if (c.approval !== void 0) outCouncil.approval = c.approval;
|
|
3750
|
+
if (c.judge !== void 0) outCouncil.judge = compactEntry(normalizeEntry(c.judge));
|
|
3751
|
+
out.council = outCouncil;
|
|
3752
|
+
}
|
|
3753
|
+
if (cfg.ledger !== void 0) out.ledger = { ...cfg.ledger };
|
|
3754
|
+
if (cfg.monitor !== void 0) out.monitor = { ...cfg.monitor };
|
|
3755
|
+
return out;
|
|
3756
|
+
}
|
|
3757
|
+
return {
|
|
3758
|
+
arbiter: {
|
|
3759
|
+
decide: (request) => current.decide(request)
|
|
3760
|
+
},
|
|
3761
|
+
getMode: () => cfg.mode ?? "interactive",
|
|
3762
|
+
getMaxAutoRisk: () => cfg.maxAutoRisk ?? "medium",
|
|
3763
|
+
getHumanTimeoutMs: () => cfg.humanTimeoutMs,
|
|
3764
|
+
getSnapshot,
|
|
3765
|
+
getConfig,
|
|
3766
|
+
apply(patch, applyOpts) {
|
|
3767
|
+
const { next, rebuildNeeded } = mergePatch(patch);
|
|
3768
|
+
cfg = next;
|
|
3769
|
+
if (patch.ledger && patch.ledger.enabled !== void 0) {
|
|
3770
|
+
opts.ledger?.setEnabled(patch.ledger.enabled);
|
|
3771
|
+
}
|
|
3772
|
+
if (rebuildNeeded) rebuild();
|
|
3773
|
+
const snapshot = getSnapshot();
|
|
3774
|
+
const shouldPersist = applyOpts?.persist !== false && opts.persist !== void 0;
|
|
3775
|
+
const persisted = shouldPersist ? opts.persist(getConfig()).then(
|
|
3776
|
+
() => ({ ok: true }),
|
|
3777
|
+
(err) => ({
|
|
3778
|
+
ok: false,
|
|
3779
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3780
|
+
})
|
|
3781
|
+
) : Promise.resolve({ ok: true });
|
|
3782
|
+
opts.onApplied?.(snapshot);
|
|
3783
|
+
return { snapshot, persisted };
|
|
3784
|
+
}
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3788
|
+
// src/execution/council-personas.ts
|
|
3789
|
+
var PERSONA_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3790
|
+
var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
|
|
3791
|
+
freezePersona({
|
|
3792
|
+
id: "executor",
|
|
3793
|
+
name: "Executor",
|
|
3794
|
+
description: "Tests whether a proposal is practical and keeps useful work moving.",
|
|
3795
|
+
instruction: "Evaluate operational feasibility, concrete progress, and the cost of delay. Favor decisive action when evidence supports it, but reject action whose prerequisites are missing.",
|
|
3796
|
+
tags: ["delivery", "feasibility", "progress"]
|
|
3797
|
+
}),
|
|
3798
|
+
freezePersona({
|
|
3799
|
+
id: "skeptic",
|
|
3800
|
+
name: "Skeptic",
|
|
3801
|
+
description: "Challenges assumptions and looks for concrete failure modes.",
|
|
3802
|
+
instruction: "Identify unsupported assumptions, unsafe premises, irreversible consequences, and credible failure modes. Oppose or refuse only when you can name a concrete reason.",
|
|
3803
|
+
defaultVeto: true,
|
|
3804
|
+
tags: ["risk", "assumptions", "failure-modes"]
|
|
3805
|
+
}),
|
|
3806
|
+
freezePersona({
|
|
3807
|
+
id: "auditor",
|
|
3808
|
+
name: "Auditor",
|
|
3809
|
+
description: "Evaluates cost, waste, evidence quality, and expected value.",
|
|
3810
|
+
instruction: "Compare resource cost, opportunity cost, evidence quality, reversibility, and expected value. Prefer the option that achieves the objective with the least avoidable waste.",
|
|
3811
|
+
tags: ["cost", "evidence", "efficiency"]
|
|
3812
|
+
}),
|
|
3813
|
+
freezePersona({
|
|
3814
|
+
id: "security",
|
|
3815
|
+
name: "Security Reviewer",
|
|
3816
|
+
description: "Examines trust boundaries, abuse cases, and security impact.",
|
|
3817
|
+
instruction: "Evaluate trust boundaries, attacker-controlled inputs, privilege changes, data exposure, abuse cases, and recovery options. Treat unmitigated high-impact security risk as grounds to refuse.",
|
|
3818
|
+
defaultVeto: true,
|
|
3819
|
+
tags: ["security", "trust", "abuse-cases"]
|
|
3820
|
+
}),
|
|
3821
|
+
freezePersona({
|
|
3822
|
+
id: "maintainer",
|
|
3823
|
+
name: "Maintainer",
|
|
3824
|
+
description: "Evaluates complexity, compatibility, and long-term ownership.",
|
|
3825
|
+
instruction: "Evaluate behavioral compatibility, conceptual complexity, testability, migration cost, and future maintenance. Prefer the smallest design that remains clear and extensible.",
|
|
3826
|
+
tags: ["maintenance", "compatibility", "simplicity"]
|
|
3827
|
+
}),
|
|
3828
|
+
freezePersona({
|
|
3829
|
+
id: "user-advocate",
|
|
3830
|
+
name: "User Advocate",
|
|
3831
|
+
description: "Evaluates the decision from the affected user\u2019s perspective.",
|
|
3832
|
+
instruction: "Evaluate usability, surprise, accessibility, failure recovery, and whether the outcome solves the user\u2019s stated need. Prefer understandable behavior with safe recovery paths.",
|
|
3833
|
+
tags: ["users", "usability", "accessibility"]
|
|
3834
|
+
})
|
|
3835
|
+
]);
|
|
3836
|
+
var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
|
|
3837
|
+
byId;
|
|
3838
|
+
constructor(personas = []) {
|
|
3839
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3840
|
+
for (const persona of personas) {
|
|
3841
|
+
const normalized = freezePersona(persona);
|
|
3842
|
+
if (entries.has(normalized.id)) {
|
|
3843
|
+
throw new Error(`CouncilPersonaRegistry: duplicate persona id "${normalized.id}".`);
|
|
3844
|
+
}
|
|
3845
|
+
entries.set(normalized.id, normalized);
|
|
3846
|
+
}
|
|
3847
|
+
this.byId = entries;
|
|
3848
|
+
}
|
|
3849
|
+
has(id) {
|
|
3850
|
+
return this.byId.has(id);
|
|
3851
|
+
}
|
|
3852
|
+
get(id) {
|
|
3853
|
+
return this.byId.get(id);
|
|
3854
|
+
}
|
|
3855
|
+
require(id) {
|
|
3856
|
+
const persona = this.get(id);
|
|
3857
|
+
if (!persona) throw new Error(`CouncilPersonaRegistry: unknown persona "${id}".`);
|
|
3858
|
+
return persona;
|
|
3859
|
+
}
|
|
3860
|
+
list() {
|
|
3861
|
+
return Object.freeze([...this.byId.values()]);
|
|
3862
|
+
}
|
|
3863
|
+
/** Return a new registry; the current registry is never mutated. */
|
|
3864
|
+
with(persona, opts = {}) {
|
|
3865
|
+
const normalized = freezePersona(persona);
|
|
3866
|
+
if (this.has(normalized.id) && opts.replace !== true) {
|
|
3867
|
+
throw new Error(`CouncilPersonaRegistry: persona "${normalized.id}" already exists.`);
|
|
3868
|
+
}
|
|
3869
|
+
return new _CouncilPersonaRegistry([
|
|
3870
|
+
...this.list().filter((entry) => entry.id !== normalized.id),
|
|
3871
|
+
normalized
|
|
3872
|
+
]);
|
|
3873
|
+
}
|
|
3874
|
+
};
|
|
3875
|
+
var DEFAULT_COUNCIL_PERSONA_REGISTRY = new CouncilPersonaRegistry(
|
|
3876
|
+
BUILTIN_COUNCIL_PERSONAS
|
|
3877
|
+
);
|
|
3878
|
+
function createCouncilPersonaRegistry(additional = []) {
|
|
3879
|
+
let registry = DEFAULT_COUNCIL_PERSONA_REGISTRY;
|
|
3880
|
+
for (const persona of additional) registry = registry.with(persona);
|
|
3881
|
+
return registry;
|
|
3882
|
+
}
|
|
3883
|
+
function freezePersona(persona) {
|
|
3884
|
+
const id = persona.id.trim();
|
|
3885
|
+
const name = persona.name.trim();
|
|
3886
|
+
const description = persona.description.trim();
|
|
3887
|
+
const instruction = persona.instruction.trim();
|
|
3888
|
+
if (!PERSONA_ID_RE.test(id)) {
|
|
3889
|
+
throw new Error(`CouncilPersonaRegistry: invalid persona id "${persona.id}".`);
|
|
3890
|
+
}
|
|
3891
|
+
if (!name) throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a name.`);
|
|
3892
|
+
if (!description) {
|
|
3893
|
+
throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a description.`);
|
|
3894
|
+
}
|
|
3895
|
+
if (!instruction) {
|
|
3896
|
+
throw new Error(`CouncilPersonaRegistry: persona "${id}" requires an instruction.`);
|
|
3897
|
+
}
|
|
3898
|
+
if (persona.defaultWeight !== void 0 && (!Number.isFinite(persona.defaultWeight) || persona.defaultWeight <= 0)) {
|
|
3899
|
+
throw new Error(`CouncilPersonaRegistry: persona "${id}" has an invalid default weight.`);
|
|
3900
|
+
}
|
|
3901
|
+
const tags = Object.freeze(
|
|
3902
|
+
[...new Set((persona.tags ?? []).map((tag) => tag.trim()).filter(Boolean))]
|
|
3903
|
+
);
|
|
3904
|
+
return Object.freeze({
|
|
3905
|
+
id,
|
|
3906
|
+
name,
|
|
3907
|
+
description,
|
|
3908
|
+
instruction,
|
|
3909
|
+
...persona.defaultWeight !== void 0 ? { defaultWeight: persona.defaultWeight } : {},
|
|
3910
|
+
...persona.defaultVeto !== void 0 ? { defaultVeto: persona.defaultVeto } : {},
|
|
3911
|
+
...tags.length > 0 ? { tags } : {}
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
// src/execution/council-profiles.ts
|
|
3916
|
+
var PROFILE_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3917
|
+
var DISTINCTNESS_VALUES = /* @__PURE__ */ new Set([
|
|
3918
|
+
"none",
|
|
3919
|
+
"model",
|
|
3920
|
+
"provider"
|
|
3921
|
+
]);
|
|
3922
|
+
var DEFAULT_COUNCIL_QUORUM_FRACTION = 0.5;
|
|
3923
|
+
var DEFAULT_COUNCIL_APPROVAL_FRACTION = 0.5;
|
|
3924
|
+
var DEFAULT_COUNCIL_VOTER_MAX_TOKENS = 300;
|
|
3925
|
+
var DEFAULT_COUNCIL_JUDGE_MAX_TOKENS = 500;
|
|
3926
|
+
var DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS = 3e4;
|
|
3927
|
+
var DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS = 9e4;
|
|
3928
|
+
var BUILTIN_COUNCIL_PROFILES = Object.freeze([
|
|
3929
|
+
Object.freeze({
|
|
3930
|
+
id: "balanced",
|
|
3931
|
+
name: "Balanced",
|
|
3932
|
+
description: "Three complementary lenses plus an independent judge for general decisions.",
|
|
3933
|
+
seats: Object.freeze([
|
|
3934
|
+
Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
|
|
3935
|
+
Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
|
|
3936
|
+
Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
|
|
3937
|
+
]),
|
|
3938
|
+
judge: Object.freeze({ role: "reviewer" }),
|
|
3939
|
+
quorumFraction: 0.5,
|
|
3940
|
+
approvalFraction: 0.5,
|
|
3941
|
+
distinctness: "model"
|
|
3942
|
+
}),
|
|
3943
|
+
Object.freeze({
|
|
3944
|
+
id: "fast",
|
|
3945
|
+
name: "Fast",
|
|
3946
|
+
description: "Two-seat panel without a judge for quick, low-cost decisions.",
|
|
3947
|
+
seats: Object.freeze([
|
|
3948
|
+
Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
|
|
3949
|
+
Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) })
|
|
3950
|
+
]),
|
|
3951
|
+
judge: false,
|
|
3952
|
+
quorumFraction: 0.5,
|
|
3953
|
+
approvalFraction: 0.5,
|
|
3954
|
+
distinctness: "none",
|
|
3955
|
+
voterMaxTokens: 200,
|
|
3956
|
+
perCallTimeoutMs: 2e4,
|
|
3957
|
+
overallTimeoutMs: 3e4
|
|
3958
|
+
}),
|
|
3959
|
+
Object.freeze({
|
|
3960
|
+
id: "risk-review",
|
|
3961
|
+
name: "Risk Review",
|
|
3962
|
+
description: "Security, assumptions, maintenance, and cost review for high-impact choices.",
|
|
3963
|
+
seats: Object.freeze([
|
|
3964
|
+
Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
|
|
3965
|
+
Object.freeze({ persona: "security", target: Object.freeze({ role: "security-reviewer" }) }),
|
|
3966
|
+
Object.freeze({ persona: "maintainer", target: Object.freeze({ role: "reviewer" }) }),
|
|
3967
|
+
Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
|
|
3968
|
+
]),
|
|
3969
|
+
judge: Object.freeze({ role: "architect" }),
|
|
3970
|
+
quorumFraction: 0.75,
|
|
3971
|
+
approvalFraction: 0.5,
|
|
3972
|
+
distinctness: "provider",
|
|
3973
|
+
judgeMaxTokens: 700,
|
|
3974
|
+
overallTimeoutMs: 12e4
|
|
3975
|
+
})
|
|
3976
|
+
]);
|
|
3977
|
+
var CouncilProfileRegistry = class _CouncilProfileRegistry {
|
|
3978
|
+
byId;
|
|
3979
|
+
personas;
|
|
3980
|
+
constructor(profiles = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
|
|
3981
|
+
this.personas = personas;
|
|
3982
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3983
|
+
for (const profile of profiles) {
|
|
3984
|
+
const normalized = normalizeCouncilProfile(profile, personas);
|
|
3985
|
+
if (entries.has(normalized.id)) {
|
|
3986
|
+
throw new Error(`CouncilProfileRegistry: duplicate profile id "${normalized.id}".`);
|
|
3987
|
+
}
|
|
3988
|
+
entries.set(normalized.id, normalized);
|
|
3989
|
+
}
|
|
3990
|
+
this.byId = entries;
|
|
3991
|
+
}
|
|
3992
|
+
has(id) {
|
|
3993
|
+
return this.byId.has(id);
|
|
3994
|
+
}
|
|
3995
|
+
get(id) {
|
|
3996
|
+
return this.byId.get(id);
|
|
3997
|
+
}
|
|
3998
|
+
require(id) {
|
|
3999
|
+
const profile = this.get(id);
|
|
4000
|
+
if (!profile) throw new Error(`CouncilProfileRegistry: unknown profile "${id}".`);
|
|
4001
|
+
return profile;
|
|
4002
|
+
}
|
|
4003
|
+
list() {
|
|
4004
|
+
return Object.freeze([...this.byId.values()]);
|
|
4005
|
+
}
|
|
4006
|
+
/** Return a new registry; the current registry is never mutated. */
|
|
4007
|
+
with(profile, opts = {}) {
|
|
4008
|
+
const personas = opts.personas ?? this.personas;
|
|
4009
|
+
const normalized = normalizeCouncilProfile(profile, personas);
|
|
4010
|
+
if (this.has(normalized.id) && opts.replace !== true) {
|
|
4011
|
+
throw new Error(`CouncilProfileRegistry: profile "${normalized.id}" already exists.`);
|
|
4012
|
+
}
|
|
4013
|
+
return new _CouncilProfileRegistry(
|
|
4014
|
+
[
|
|
4015
|
+
...this.list().filter((entry) => entry.id !== normalized.id),
|
|
4016
|
+
profile
|
|
4017
|
+
],
|
|
4018
|
+
personas
|
|
4019
|
+
);
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
var DEFAULT_COUNCIL_PROFILE_REGISTRY = new CouncilProfileRegistry(
|
|
4023
|
+
BUILTIN_COUNCIL_PROFILES
|
|
4024
|
+
);
|
|
4025
|
+
function createCouncilProfileRegistry(additional = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
|
|
4026
|
+
return new CouncilProfileRegistry([...BUILTIN_COUNCIL_PROFILES, ...additional], personas);
|
|
4027
|
+
}
|
|
4028
|
+
function resolveCouncilProfile(profile, opts = {}) {
|
|
4029
|
+
const personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
|
|
4030
|
+
if (profile && typeof profile !== "string") return normalizeCouncilProfile(profile, personas);
|
|
4031
|
+
const id = profile ?? opts.defaultProfile ?? "balanced";
|
|
4032
|
+
return (opts.registry ?? DEFAULT_COUNCIL_PROFILE_REGISTRY).require(id);
|
|
4033
|
+
}
|
|
4034
|
+
function normalizeCouncilProfile(profile, personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
|
|
4035
|
+
const id = profile.id.trim();
|
|
4036
|
+
if (!PROFILE_ID_RE.test(id)) {
|
|
4037
|
+
throw new Error(`CouncilProfileRegistry: invalid profile id "${profile.id}".`);
|
|
4038
|
+
}
|
|
4039
|
+
if (profile.seats.length === 0) {
|
|
4040
|
+
throw new Error(`CouncilProfileRegistry: profile "${id}" requires at least one seat.`);
|
|
4041
|
+
}
|
|
4042
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
4043
|
+
const seats = profile.seats.map((seat) => {
|
|
4044
|
+
const persona = personas.require(seat.persona.trim());
|
|
4045
|
+
const explicitSeatId = seat.id?.trim();
|
|
4046
|
+
if (explicitSeatId && usedIds.has(explicitSeatId)) {
|
|
4047
|
+
throw new Error(`CouncilProfileRegistry: duplicate seat id "${explicitSeatId}".`);
|
|
4048
|
+
}
|
|
4049
|
+
const seatId = uniqueSeatId(explicitSeatId || persona.id, usedIds);
|
|
4050
|
+
const label = seat.label?.trim() || persona.name;
|
|
4051
|
+
const weight = seat.weight ?? persona.defaultWeight ?? 1;
|
|
4052
|
+
if (!Number.isFinite(weight) || weight <= 0) {
|
|
4053
|
+
throw new Error(`CouncilProfileRegistry: seat "${seatId}" has an invalid weight.`);
|
|
4054
|
+
}
|
|
4055
|
+
return Object.freeze({
|
|
4056
|
+
id: seatId,
|
|
4057
|
+
label,
|
|
4058
|
+
persona: persona.id,
|
|
4059
|
+
...seat.target ? { target: freezeTarget(seat.target, `seat "${seatId}"`) } : {},
|
|
4060
|
+
weight,
|
|
4061
|
+
veto: seat.veto ?? persona.defaultVeto ?? false
|
|
4062
|
+
});
|
|
4063
|
+
});
|
|
4064
|
+
const quorumFraction = fraction(
|
|
4065
|
+
profile.quorumFraction ?? DEFAULT_COUNCIL_QUORUM_FRACTION,
|
|
4066
|
+
"quorumFraction",
|
|
4067
|
+
id
|
|
4068
|
+
);
|
|
4069
|
+
const approvalFraction = fraction(
|
|
4070
|
+
profile.approvalFraction ?? DEFAULT_COUNCIL_APPROVAL_FRACTION,
|
|
4071
|
+
"approvalFraction",
|
|
4072
|
+
id
|
|
4073
|
+
);
|
|
4074
|
+
const distinctness = profile.distinctness ?? "model";
|
|
4075
|
+
if (!DISTINCTNESS_VALUES.has(distinctness)) {
|
|
4076
|
+
throw new Error(`CouncilProfileRegistry: profile "${id}" has invalid distinctness.`);
|
|
4077
|
+
}
|
|
4078
|
+
const voterMaxTokens = positiveInteger(
|
|
4079
|
+
profile.voterMaxTokens ?? DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
|
|
4080
|
+
"voterMaxTokens",
|
|
4081
|
+
id
|
|
4082
|
+
);
|
|
4083
|
+
const judgeMaxTokens = positiveInteger(
|
|
4084
|
+
profile.judgeMaxTokens ?? DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
|
|
4085
|
+
"judgeMaxTokens",
|
|
4086
|
+
id
|
|
4087
|
+
);
|
|
4088
|
+
const perCallTimeoutMs = positiveInteger(
|
|
4089
|
+
profile.perCallTimeoutMs ?? DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
|
|
4090
|
+
"perCallTimeoutMs",
|
|
4091
|
+
id
|
|
4092
|
+
);
|
|
4093
|
+
const overallTimeoutMs = positiveInteger(
|
|
4094
|
+
profile.overallTimeoutMs ?? DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
|
|
4095
|
+
"overallTimeoutMs",
|
|
4096
|
+
id
|
|
4097
|
+
);
|
|
4098
|
+
if (overallTimeoutMs < perCallTimeoutMs) {
|
|
4099
|
+
throw new Error(
|
|
4100
|
+
`CouncilProfileRegistry: profile "${id}" overallTimeoutMs must be at least perCallTimeoutMs.`
|
|
4101
|
+
);
|
|
4102
|
+
}
|
|
4103
|
+
return Object.freeze({
|
|
4104
|
+
id,
|
|
4105
|
+
name: profile.name?.trim() || id,
|
|
4106
|
+
description: profile.description?.trim() || "",
|
|
4107
|
+
seats: Object.freeze(seats),
|
|
4108
|
+
judge: profile.judge ? freezeTarget(profile.judge, "judge") : false,
|
|
4109
|
+
quorumFraction,
|
|
4110
|
+
approvalFraction,
|
|
4111
|
+
distinctness,
|
|
4112
|
+
voterMaxTokens,
|
|
4113
|
+
judgeMaxTokens,
|
|
4114
|
+
perCallTimeoutMs,
|
|
4115
|
+
overallTimeoutMs
|
|
4116
|
+
});
|
|
4117
|
+
}
|
|
4118
|
+
function uniqueSeatId(base, used) {
|
|
4119
|
+
if (!PROFILE_ID_RE.test(base)) {
|
|
4120
|
+
throw new Error(`CouncilProfileRegistry: invalid seat id "${base}".`);
|
|
4121
|
+
}
|
|
4122
|
+
let id = base;
|
|
4123
|
+
let suffix = 2;
|
|
4124
|
+
while (used.has(id)) id = `${base}-${suffix++}`;
|
|
4125
|
+
used.add(id);
|
|
4126
|
+
return id;
|
|
4127
|
+
}
|
|
4128
|
+
function freezeTarget(target, label) {
|
|
4129
|
+
const providerId = optionalText(target.providerId);
|
|
4130
|
+
const model = optionalText(target.model);
|
|
4131
|
+
const role = optionalText(target.role);
|
|
4132
|
+
const fallbackProfile = optionalText(target.fallbackProfile);
|
|
4133
|
+
const fallbackModels = Object.freeze(
|
|
4134
|
+
[...new Set((target.fallbackModels ?? []).map((ref) => ref.trim()).filter(Boolean))]
|
|
4135
|
+
);
|
|
4136
|
+
if (!providerId && !model && !role && !fallbackProfile && fallbackModels.length === 0) {
|
|
4137
|
+
throw new Error(`CouncilProfileRegistry: ${label} target is empty.`);
|
|
4138
|
+
}
|
|
4139
|
+
return Object.freeze({
|
|
4140
|
+
...providerId ? { providerId } : {},
|
|
4141
|
+
...model ? { model } : {},
|
|
4142
|
+
...role ? { role } : {},
|
|
4143
|
+
...fallbackProfile ? { fallbackProfile } : {},
|
|
4144
|
+
...fallbackModels.length > 0 ? { fallbackModels } : {}
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
function optionalText(value) {
|
|
4148
|
+
const trimmed = value?.trim();
|
|
4149
|
+
return trimmed || void 0;
|
|
4150
|
+
}
|
|
4151
|
+
function fraction(value, field, profileId) {
|
|
4152
|
+
if (!Number.isFinite(value) || value <= 0 || value > 1) {
|
|
4153
|
+
throw new Error(`CouncilProfileRegistry: profile "${profileId}" ${field} must be in (0, 1].`);
|
|
4154
|
+
}
|
|
4155
|
+
return value;
|
|
4156
|
+
}
|
|
4157
|
+
function positiveInteger(value, field, profileId) {
|
|
4158
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
4159
|
+
throw new Error(
|
|
4160
|
+
`CouncilProfileRegistry: profile "${profileId}" ${field} must be a positive integer.`
|
|
4161
|
+
);
|
|
4162
|
+
}
|
|
4163
|
+
return value;
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
// src/execution/council-prompts.ts
|
|
4167
|
+
var COUNCIL_VOTER_PROMPT_PATH = "llm/council-voter.md";
|
|
4168
|
+
var COUNCIL_JUDGE_PROMPT_PATH = "llm/council-judge.md";
|
|
4169
|
+
function buildCouncilVoterSystemPrompt(persona) {
|
|
4170
|
+
const template = requiredInstruction(COUNCIL_VOTER_PROMPT_PATH);
|
|
4171
|
+
return renderInstructionTemplate(template, {
|
|
4172
|
+
personaInstruction: persona.instruction
|
|
4173
|
+
});
|
|
4174
|
+
}
|
|
4175
|
+
function buildCouncilJudgeSystemPrompt() {
|
|
4176
|
+
return requiredInstruction(COUNCIL_JUDGE_PROMPT_PATH);
|
|
4177
|
+
}
|
|
4178
|
+
function buildCouncilQuestionPrompt(question, opts = {}) {
|
|
4179
|
+
const text = question.question.trim();
|
|
4180
|
+
if (!text) throw new Error("buildCouncilQuestionPrompt: question must not be empty.");
|
|
4181
|
+
const options = normalizeOptions(question.options);
|
|
4182
|
+
return [
|
|
4183
|
+
"<council-question>",
|
|
4184
|
+
`Question: ${text}`,
|
|
4185
|
+
question.context?.trim() ? `Context:
|
|
4186
|
+
${question.context.trim()}` : "",
|
|
4187
|
+
options.length > 0 ? `Options (JSON):
|
|
4188
|
+
${JSON.stringify(options)}` : "Options: none",
|
|
4189
|
+
opts.refusalOptionId ? `Refusal option id: ${opts.refusalOptionId}` : "Refusal option id: not supplied",
|
|
4190
|
+
"</council-question>"
|
|
4191
|
+
].filter(Boolean).join("\n\n");
|
|
4192
|
+
}
|
|
4193
|
+
function buildCouncilVoterUserPrompt(question, seat, opts = {}) {
|
|
4194
|
+
return [
|
|
4195
|
+
buildCouncilQuestionPrompt(question, opts),
|
|
4196
|
+
"<seat-metadata>",
|
|
4197
|
+
`Seat id: ${seat.id}`,
|
|
4198
|
+
`Seat label: ${seat.label}`,
|
|
4199
|
+
`Persona id: ${seat.persona}`,
|
|
4200
|
+
"</seat-metadata>"
|
|
4201
|
+
].join("\n");
|
|
4202
|
+
}
|
|
4203
|
+
function buildCouncilJudgeUserPrompt(question, votes, opts = {}) {
|
|
4204
|
+
const ballots = votes.map((vote) => ({
|
|
4205
|
+
seatId: vote.seatId,
|
|
4206
|
+
persona: vote.persona,
|
|
4207
|
+
status: vote.status,
|
|
4208
|
+
...vote.optionId ? { optionId: vote.optionId } : {},
|
|
4209
|
+
...vote.stance ? { stance: vote.stance } : {},
|
|
4210
|
+
...vote.rationale ? { rationale: vote.rationale } : {}
|
|
4211
|
+
}));
|
|
4212
|
+
return [
|
|
4213
|
+
buildCouncilQuestionPrompt(question, { refusalOptionId: opts.refusalOptionId }),
|
|
4214
|
+
"<council-ballots>",
|
|
4215
|
+
opts.reason?.trim() ? `Reason judging is required: ${opts.reason.trim()}` : "",
|
|
4216
|
+
JSON.stringify(ballots),
|
|
4217
|
+
"</council-ballots>"
|
|
4218
|
+
].filter(Boolean).join("\n\n");
|
|
4219
|
+
}
|
|
4220
|
+
function normalizeOptions(options) {
|
|
4221
|
+
if (!options) return [];
|
|
4222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4223
|
+
return options.map((option) => {
|
|
4224
|
+
const id = option.id.trim();
|
|
4225
|
+
const label = option.label.trim();
|
|
4226
|
+
if (!id) throw new Error("buildCouncilQuestionPrompt: option id must not be empty.");
|
|
4227
|
+
if (!label) throw new Error(`buildCouncilQuestionPrompt: option "${id}" needs a label.`);
|
|
4228
|
+
if (seen.has(id)) throw new Error(`buildCouncilQuestionPrompt: duplicate option id "${id}".`);
|
|
4229
|
+
seen.add(id);
|
|
4230
|
+
return {
|
|
4231
|
+
id,
|
|
4232
|
+
label,
|
|
4233
|
+
...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
|
|
4234
|
+
};
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
function requiredInstruction(path11) {
|
|
4238
|
+
const text = readBundledInstructionText(path11);
|
|
4239
|
+
if (!text) throw new Error(`Council instruction file is unavailable: ${path11}`);
|
|
4240
|
+
return text;
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
// src/execution/council-orchestrator.ts
|
|
4244
|
+
var COUNCIL_REFUSAL_OPTION_ID = "council_refuse";
|
|
4245
|
+
var DEFAULT_COUNCIL_MAX_CONCURRENCY = 3;
|
|
4246
|
+
var MAX_COUNCIL_CONCURRENCY = 8;
|
|
4247
|
+
var CouncilOrchestrator = class {
|
|
4248
|
+
caller;
|
|
4249
|
+
personas;
|
|
4250
|
+
profiles;
|
|
4251
|
+
defaultProfile;
|
|
4252
|
+
maxConcurrency;
|
|
4253
|
+
refusalOptionId;
|
|
4254
|
+
constructor(opts) {
|
|
4255
|
+
this.caller = opts.caller;
|
|
4256
|
+
this.personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
|
|
4257
|
+
this.profiles = opts.profiles ?? DEFAULT_COUNCIL_PROFILE_REGISTRY;
|
|
4258
|
+
this.defaultProfile = opts.defaultProfile;
|
|
4259
|
+
this.maxConcurrency = validateConcurrency(
|
|
4260
|
+
opts.maxConcurrency ?? DEFAULT_COUNCIL_MAX_CONCURRENCY
|
|
2408
4261
|
);
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
4262
|
+
this.refusalOptionId = opts.refusalOptionId?.trim() || COUNCIL_REFUSAL_OPTION_ID;
|
|
4263
|
+
}
|
|
4264
|
+
async ask(question) {
|
|
4265
|
+
const startedAt = Date.now();
|
|
4266
|
+
const profile = resolveCouncilProfile(question.profile, {
|
|
4267
|
+
registry: this.profiles,
|
|
4268
|
+
personas: this.personas,
|
|
4269
|
+
defaultProfile: this.defaultProfile
|
|
4270
|
+
});
|
|
4271
|
+
validateRefusalCollision(question, this.refusalOptionId);
|
|
4272
|
+
const timeoutSignal = AbortSignal.timeout(profile.overallTimeoutMs);
|
|
4273
|
+
const signal = question.signal ? AbortSignal.any([question.signal, timeoutSignal]) : timeoutSignal;
|
|
4274
|
+
const usage = {
|
|
4275
|
+
calls: 0,
|
|
4276
|
+
inputTokens: 0,
|
|
4277
|
+
outputTokens: 0,
|
|
4278
|
+
totalTokens: 0
|
|
4279
|
+
};
|
|
4280
|
+
const votes = await mapConcurrent(
|
|
4281
|
+
profile.seats,
|
|
4282
|
+
Math.min(this.maxConcurrency, profile.seats.length),
|
|
4283
|
+
async (seat) => {
|
|
4284
|
+
try {
|
|
4285
|
+
return await this.callSeat(question, profile, seat, signal, usage);
|
|
4286
|
+
} catch (error) {
|
|
4287
|
+
return {
|
|
4288
|
+
seatId: seat.id,
|
|
4289
|
+
persona: seat.persona,
|
|
4290
|
+
status: signal.aborted ? "cancelled" : "failed",
|
|
4291
|
+
error: errorMessage(error)
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
2414
4294
|
}
|
|
4295
|
+
);
|
|
4296
|
+
const warnings = distinctnessWarnings(votes, profile);
|
|
4297
|
+
const errors = votes.filter((vote) => vote.status === "failed" || vote.status === "invalid").map((vote) => `${vote.seatId}: ${vote.error ?? vote.status}`);
|
|
4298
|
+
if (question.signal?.aborted) {
|
|
4299
|
+
return resultEnvelope({
|
|
4300
|
+
status: "cancelled",
|
|
4301
|
+
reason: "Council call cancelled.",
|
|
4302
|
+
resolution: "none",
|
|
4303
|
+
votes,
|
|
4304
|
+
profile,
|
|
4305
|
+
usage,
|
|
4306
|
+
startedAt,
|
|
4307
|
+
warnings,
|
|
4308
|
+
errors
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
if (timeoutSignal.aborted) {
|
|
4312
|
+
return resultEnvelope({
|
|
4313
|
+
status: "failed",
|
|
4314
|
+
reason: "Council overall timeout exceeded.",
|
|
4315
|
+
resolution: "none",
|
|
4316
|
+
votes,
|
|
4317
|
+
profile,
|
|
4318
|
+
usage,
|
|
4319
|
+
startedAt,
|
|
4320
|
+
warnings,
|
|
4321
|
+
errors: [...errors, "Council overall timeout exceeded."]
|
|
4322
|
+
});
|
|
4323
|
+
}
|
|
4324
|
+
if (!question.options || question.options.length === 0) {
|
|
4325
|
+
return this.resolveOpenQuestion(
|
|
4326
|
+
question,
|
|
4327
|
+
profile,
|
|
4328
|
+
votes,
|
|
4329
|
+
signal,
|
|
4330
|
+
usage,
|
|
4331
|
+
startedAt,
|
|
4332
|
+
warnings,
|
|
4333
|
+
errors
|
|
4334
|
+
);
|
|
4335
|
+
}
|
|
4336
|
+
return this.resolveOptionQuestion(
|
|
4337
|
+
question,
|
|
4338
|
+
profile,
|
|
4339
|
+
votes,
|
|
4340
|
+
signal,
|
|
4341
|
+
usage,
|
|
4342
|
+
startedAt,
|
|
4343
|
+
warnings,
|
|
4344
|
+
errors
|
|
4345
|
+
);
|
|
4346
|
+
}
|
|
4347
|
+
async callSeat(question, profile, seat, signal, usage) {
|
|
4348
|
+
if (signal.aborted) return cancelledVote(seat);
|
|
4349
|
+
let persona;
|
|
4350
|
+
try {
|
|
4351
|
+
persona = this.personas.require(seat.persona);
|
|
4352
|
+
} catch (error) {
|
|
2415
4353
|
return {
|
|
2416
|
-
|
|
2417
|
-
|
|
4354
|
+
seatId: seat.id,
|
|
4355
|
+
persona: seat.persona,
|
|
4356
|
+
status: "failed",
|
|
4357
|
+
error: errorMessage(error)
|
|
2418
4358
|
};
|
|
2419
4359
|
}
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
4360
|
+
const result = await this.safeCall({
|
|
4361
|
+
system: buildCouncilVoterSystemPrompt(persona),
|
|
4362
|
+
userPrompt: buildCouncilVoterUserPrompt(question, seat, {
|
|
4363
|
+
refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
|
|
4364
|
+
}),
|
|
4365
|
+
target: seat.target,
|
|
4366
|
+
maxTokens: profile.voterMaxTokens,
|
|
4367
|
+
timeoutMs: profile.perCallTimeoutMs,
|
|
4368
|
+
signal,
|
|
4369
|
+
usage
|
|
4370
|
+
});
|
|
4371
|
+
const metadata = callMetadata(result);
|
|
4372
|
+
if (result.error) {
|
|
2427
4373
|
return {
|
|
2428
|
-
|
|
2429
|
-
|
|
4374
|
+
seatId: seat.id,
|
|
4375
|
+
persona: seat.persona,
|
|
4376
|
+
status: signal.aborted ? "cancelled" : "failed",
|
|
4377
|
+
...metadata,
|
|
4378
|
+
error: result.error
|
|
4379
|
+
};
|
|
4380
|
+
}
|
|
4381
|
+
const parsed = parseVote(result.text, question, this.refusalOptionId);
|
|
4382
|
+
if (!parsed.ok) {
|
|
4383
|
+
return {
|
|
4384
|
+
seatId: seat.id,
|
|
4385
|
+
persona: seat.persona,
|
|
4386
|
+
status: "invalid",
|
|
4387
|
+
...metadata,
|
|
4388
|
+
error: parsed.error
|
|
2430
4389
|
};
|
|
2431
4390
|
}
|
|
2432
|
-
return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
function parseOptionDecision(rawText, options) {
|
|
2436
|
-
const text = rawText.trim();
|
|
2437
|
-
const byId = new Map(options.map((option2) => [option2.id, option2]));
|
|
2438
|
-
const wholeFence = /^```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?```$/i.exec(text);
|
|
2439
|
-
const jsonCandidate = wholeFence ? (wholeFence[1] ?? "").trim() : text;
|
|
2440
|
-
const parsed = safeParse(jsonCandidate, 16384);
|
|
2441
|
-
if (parsed.ok && parsed.value && typeof parsed.value.optionId === "string") {
|
|
2442
|
-
const option2 = byId.get(parsed.value.optionId);
|
|
2443
|
-
if (!option2) return null;
|
|
2444
4391
|
return {
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
4392
|
+
seatId: seat.id,
|
|
4393
|
+
persona: seat.persona,
|
|
4394
|
+
status: "valid",
|
|
4395
|
+
...parsed.vote,
|
|
4396
|
+
...metadata
|
|
2449
4397
|
};
|
|
2450
4398
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
4399
|
+
async resolveOptionQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
|
|
4400
|
+
const validVotes = votes.filter(
|
|
4401
|
+
(vote) => vote.status === "valid" && typeof vote.optionId === "string"
|
|
4402
|
+
);
|
|
4403
|
+
const resolution = resolveCouncilVotes({
|
|
4404
|
+
seats: profile.seats.map((seat) => ({
|
|
4405
|
+
id: seat.id,
|
|
4406
|
+
weight: seat.weight,
|
|
4407
|
+
veto: seat.veto
|
|
4408
|
+
})),
|
|
4409
|
+
votes: validVotes.map((vote) => ({ seatId: vote.seatId, optionId: vote.optionId })),
|
|
4410
|
+
refusalOptionId: this.refusalOptionId,
|
|
4411
|
+
quorumFraction: profile.quorumFraction,
|
|
4412
|
+
approvalFraction: profile.approvalFraction
|
|
4413
|
+
});
|
|
4414
|
+
if (resolution.status === "abstained") {
|
|
4415
|
+
return resultEnvelope({
|
|
4416
|
+
status: "abstained",
|
|
4417
|
+
reason: "Council quorum was not met.",
|
|
4418
|
+
resolution: "none",
|
|
4419
|
+
votes,
|
|
4420
|
+
profile,
|
|
4421
|
+
usage,
|
|
4422
|
+
startedAt,
|
|
4423
|
+
warnings,
|
|
4424
|
+
errors
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
if (resolution.status === "denied") {
|
|
4428
|
+
return resultEnvelope({
|
|
4429
|
+
status: "denied",
|
|
4430
|
+
optionId: resolution.optionId,
|
|
4431
|
+
reason: `Council denied the proposal via ${resolution.method}.`,
|
|
4432
|
+
resolution: resolution.method,
|
|
4433
|
+
votes,
|
|
4434
|
+
profile,
|
|
4435
|
+
usage,
|
|
4436
|
+
startedAt,
|
|
4437
|
+
warnings,
|
|
4438
|
+
errors
|
|
4439
|
+
});
|
|
4440
|
+
}
|
|
4441
|
+
if (resolution.status === "decided") {
|
|
4442
|
+
return resultEnvelope({
|
|
4443
|
+
status: "decided",
|
|
4444
|
+
optionId: resolution.optionId,
|
|
4445
|
+
answer: optionLabel(question, resolution.optionId),
|
|
4446
|
+
resolution: "majority",
|
|
4447
|
+
votes,
|
|
4448
|
+
profile,
|
|
4449
|
+
usage,
|
|
4450
|
+
startedAt,
|
|
4451
|
+
warnings,
|
|
4452
|
+
errors
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
if (!profile.judge) {
|
|
4456
|
+
return resultEnvelope({
|
|
4457
|
+
status: "abstained",
|
|
4458
|
+
reason: `Council requires a judge (${resolution.reason}), but this profile has none.`,
|
|
4459
|
+
resolution: "none",
|
|
4460
|
+
votes,
|
|
4461
|
+
profile,
|
|
4462
|
+
usage,
|
|
4463
|
+
startedAt,
|
|
4464
|
+
warnings,
|
|
4465
|
+
errors
|
|
4466
|
+
});
|
|
4467
|
+
}
|
|
4468
|
+
const judged = await this.callJudge(
|
|
4469
|
+
question,
|
|
4470
|
+
profile,
|
|
4471
|
+
votes,
|
|
4472
|
+
profile.judge,
|
|
4473
|
+
resolution.reason,
|
|
4474
|
+
signal,
|
|
4475
|
+
usage
|
|
4476
|
+
);
|
|
4477
|
+
if (!judged.ok) {
|
|
4478
|
+
return resultEnvelope({
|
|
4479
|
+
status: signal.aborted ? "cancelled" : "abstained",
|
|
4480
|
+
reason: judged.error,
|
|
4481
|
+
resolution: "none",
|
|
4482
|
+
votes,
|
|
4483
|
+
profile,
|
|
4484
|
+
usage,
|
|
4485
|
+
startedAt,
|
|
4486
|
+
warnings,
|
|
4487
|
+
errors: [...errors, judged.error],
|
|
4488
|
+
judgeUsed: true
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
if (judged.value.optionId === this.refusalOptionId) {
|
|
4492
|
+
return resultEnvelope({
|
|
4493
|
+
status: "denied",
|
|
4494
|
+
optionId: this.refusalOptionId,
|
|
4495
|
+
reason: judged.value.rationale ?? "Council judge refused all options.",
|
|
4496
|
+
resolution: "judge",
|
|
4497
|
+
votes,
|
|
4498
|
+
profile,
|
|
4499
|
+
usage,
|
|
4500
|
+
startedAt,
|
|
4501
|
+
warnings,
|
|
4502
|
+
errors,
|
|
4503
|
+
judgeUsed: true
|
|
4504
|
+
});
|
|
4505
|
+
}
|
|
4506
|
+
return resultEnvelope({
|
|
4507
|
+
status: "decided",
|
|
4508
|
+
optionId: judged.value.optionId,
|
|
4509
|
+
answer: optionLabel(question, judged.value.optionId),
|
|
4510
|
+
reason: judged.value.rationale,
|
|
4511
|
+
resolution: "judge",
|
|
4512
|
+
votes,
|
|
4513
|
+
profile,
|
|
4514
|
+
usage,
|
|
4515
|
+
startedAt,
|
|
4516
|
+
warnings,
|
|
4517
|
+
errors,
|
|
4518
|
+
judgeUsed: true
|
|
4519
|
+
});
|
|
4520
|
+
}
|
|
4521
|
+
async resolveOpenQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
|
|
4522
|
+
const valid = votes.filter(
|
|
4523
|
+
(vote) => vote.status === "valid" && typeof vote.stance === "string"
|
|
4524
|
+
);
|
|
4525
|
+
if (valid.length / profile.seats.length < profile.quorumFraction) {
|
|
4526
|
+
return resultEnvelope({
|
|
4527
|
+
status: "abstained",
|
|
4528
|
+
reason: "Council quorum was not met.",
|
|
4529
|
+
resolution: "none",
|
|
4530
|
+
votes,
|
|
4531
|
+
profile,
|
|
4532
|
+
usage,
|
|
4533
|
+
startedAt,
|
|
4534
|
+
warnings,
|
|
4535
|
+
errors
|
|
4536
|
+
});
|
|
4537
|
+
}
|
|
4538
|
+
if (!profile.judge) {
|
|
4539
|
+
const first = valid[0];
|
|
4540
|
+
if (!first) {
|
|
4541
|
+
return resultEnvelope({
|
|
4542
|
+
status: "failed",
|
|
4543
|
+
reason: "Council produced no valid stance.",
|
|
4544
|
+
resolution: "none",
|
|
4545
|
+
votes,
|
|
4546
|
+
profile,
|
|
4547
|
+
usage,
|
|
4548
|
+
startedAt,
|
|
4549
|
+
warnings,
|
|
4550
|
+
errors
|
|
4551
|
+
});
|
|
4552
|
+
}
|
|
4553
|
+
return resultEnvelope({
|
|
4554
|
+
status: "decided",
|
|
4555
|
+
answer: first.stance,
|
|
4556
|
+
reason: first.rationale,
|
|
4557
|
+
resolution: "first_stance",
|
|
4558
|
+
votes,
|
|
4559
|
+
profile,
|
|
4560
|
+
usage,
|
|
4561
|
+
startedAt,
|
|
4562
|
+
warnings,
|
|
4563
|
+
errors
|
|
4564
|
+
});
|
|
4565
|
+
}
|
|
4566
|
+
const judged = await this.callJudge(
|
|
4567
|
+
question,
|
|
4568
|
+
profile,
|
|
4569
|
+
votes,
|
|
4570
|
+
profile.judge,
|
|
4571
|
+
"open_question_synthesis",
|
|
4572
|
+
signal,
|
|
4573
|
+
usage
|
|
4574
|
+
);
|
|
4575
|
+
if (!judged.ok) {
|
|
4576
|
+
return resultEnvelope({
|
|
4577
|
+
status: signal.aborted ? "cancelled" : "failed",
|
|
4578
|
+
reason: judged.error,
|
|
4579
|
+
resolution: "none",
|
|
4580
|
+
votes,
|
|
4581
|
+
profile,
|
|
4582
|
+
usage,
|
|
4583
|
+
startedAt,
|
|
4584
|
+
warnings,
|
|
4585
|
+
errors: [...errors, judged.error],
|
|
4586
|
+
judgeUsed: true
|
|
4587
|
+
});
|
|
4588
|
+
}
|
|
4589
|
+
return resultEnvelope({
|
|
4590
|
+
status: "decided",
|
|
4591
|
+
answer: judged.value.answer,
|
|
4592
|
+
reason: judged.value.rationale,
|
|
4593
|
+
resolution: "judge",
|
|
4594
|
+
votes,
|
|
4595
|
+
profile,
|
|
4596
|
+
usage,
|
|
4597
|
+
startedAt,
|
|
4598
|
+
warnings,
|
|
4599
|
+
errors,
|
|
4600
|
+
judgeUsed: true
|
|
4601
|
+
});
|
|
4602
|
+
}
|
|
4603
|
+
async callJudge(question, profile, votes, target, reason, signal, usage) {
|
|
4604
|
+
const result = await this.safeCall({
|
|
4605
|
+
system: buildCouncilJudgeSystemPrompt(),
|
|
4606
|
+
userPrompt: buildCouncilJudgeUserPrompt(question, votes, {
|
|
4607
|
+
reason,
|
|
4608
|
+
refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
|
|
4609
|
+
}),
|
|
4610
|
+
target,
|
|
4611
|
+
maxTokens: profile.judgeMaxTokens,
|
|
4612
|
+
timeoutMs: profile.perCallTimeoutMs,
|
|
4613
|
+
signal,
|
|
4614
|
+
usage
|
|
4615
|
+
});
|
|
4616
|
+
if (result.error) return { ok: false, error: result.error };
|
|
4617
|
+
return parseJudge(result.text, question, this.refusalOptionId);
|
|
4618
|
+
}
|
|
4619
|
+
async safeCall(input) {
|
|
4620
|
+
try {
|
|
4621
|
+
const result = await this.caller.call({
|
|
4622
|
+
system: input.system,
|
|
4623
|
+
userPrompt: input.userPrompt,
|
|
4624
|
+
responseFormat: { type: "json_object" },
|
|
4625
|
+
maxTokens: input.maxTokens,
|
|
4626
|
+
timeoutMs: input.timeoutMs,
|
|
4627
|
+
signal: input.signal,
|
|
4628
|
+
...input.target?.providerId ? { providerId: input.target.providerId } : {},
|
|
4629
|
+
...input.target?.model ? { model: input.target.model } : {},
|
|
4630
|
+
...input.target?.role ? { role: input.target.role } : {},
|
|
4631
|
+
...input.target?.fallbackProfile ? { fallbackProfile: input.target.fallbackProfile } : {},
|
|
4632
|
+
...input.target?.fallbackModels ? { fallbackModels: [...input.target.fallbackModels] } : {}
|
|
4633
|
+
});
|
|
4634
|
+
addUsage(input.usage, result);
|
|
4635
|
+
return result;
|
|
4636
|
+
} catch (error) {
|
|
4637
|
+
input.usage.calls += 1;
|
|
4638
|
+
return emptyCallResult(errorMessage(error));
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
};
|
|
4642
|
+
function parseVote(text, question, refusalOptionId) {
|
|
4643
|
+
const parsed = parseObject(text);
|
|
4644
|
+
if (!parsed.ok) return parsed;
|
|
4645
|
+
const rationale = optionalString(parsed.value["rationale"]);
|
|
4646
|
+
if (question.options && question.options.length > 0) {
|
|
4647
|
+
const optionId = optionalString(parsed.value["optionId"]);
|
|
4648
|
+
const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
|
|
4649
|
+
if (!optionId || !allowed.has(optionId)) {
|
|
4650
|
+
return { ok: false, error: "Voter returned an unknown or missing optionId." };
|
|
4651
|
+
}
|
|
4652
|
+
return { ok: true, vote: { optionId, ...rationale ? { rationale } : {} } };
|
|
4653
|
+
}
|
|
4654
|
+
const stance = optionalString(parsed.value["stance"]);
|
|
4655
|
+
if (!stance) return { ok: false, error: "Voter returned an empty or missing stance." };
|
|
4656
|
+
return { ok: true, vote: { stance, ...rationale ? { rationale } : {} } };
|
|
4657
|
+
}
|
|
4658
|
+
function parseJudge(text, question, refusalOptionId) {
|
|
4659
|
+
const parsed = parseObject(text);
|
|
4660
|
+
if (!parsed.ok) return parsed;
|
|
4661
|
+
const rationale = optionalString(parsed.value["rationale"]);
|
|
4662
|
+
if (question.options && question.options.length > 0) {
|
|
4663
|
+
const optionId = optionalString(parsed.value["optionId"]);
|
|
4664
|
+
const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
|
|
4665
|
+
if (!optionId || !allowed.has(optionId)) {
|
|
4666
|
+
return { ok: false, error: "Judge returned an unknown or missing optionId." };
|
|
4667
|
+
}
|
|
4668
|
+
return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
|
|
4669
|
+
}
|
|
4670
|
+
const answer = optionalString(parsed.value["answer"]);
|
|
4671
|
+
if (!answer) return { ok: false, error: "Judge returned an empty or missing answer." };
|
|
4672
|
+
return { ok: true, value: { answer, ...rationale ? { rationale } : {} } };
|
|
4673
|
+
}
|
|
4674
|
+
function parseObject(text) {
|
|
4675
|
+
const trimmed = text.trim();
|
|
4676
|
+
const first = trimmed.indexOf("{");
|
|
4677
|
+
const last = trimmed.lastIndexOf("}");
|
|
4678
|
+
if (first < 0 || last < first) return { ok: false, error: "LLM response did not contain JSON." };
|
|
4679
|
+
try {
|
|
4680
|
+
const value = JSON.parse(trimmed.slice(first, last + 1));
|
|
4681
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4682
|
+
return { ok: false, error: "LLM response JSON must be an object." };
|
|
4683
|
+
}
|
|
4684
|
+
return { ok: true, value };
|
|
4685
|
+
} catch (error) {
|
|
4686
|
+
return { ok: false, error: `Invalid LLM response JSON: ${errorMessage(error)}` };
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
function resultEnvelope(input) {
|
|
4690
|
+
const validVoteCount = input.votes.filter((vote) => vote.status === "valid").length;
|
|
2456
4691
|
return {
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
4692
|
+
status: input.status,
|
|
4693
|
+
...input.answer ? { answer: input.answer } : {},
|
|
4694
|
+
...input.optionId ? { optionId: input.optionId } : {},
|
|
4695
|
+
...input.reason ? { reason: input.reason } : {},
|
|
4696
|
+
resolution: input.resolution,
|
|
4697
|
+
votes: Object.freeze([...input.votes]),
|
|
4698
|
+
configuredSeatCount: input.profile.seats.length,
|
|
4699
|
+
validVoteCount,
|
|
4700
|
+
distinctTargetCount: distinctTargetCount(input.votes, input.profile),
|
|
4701
|
+
judgeUsed: input.judgeUsed ?? false,
|
|
4702
|
+
usage: usageResult(input.usage, input.startedAt),
|
|
4703
|
+
...input.warnings.length > 0 ? { warnings: Object.freeze([...input.warnings]) } : {},
|
|
4704
|
+
...input.errors.length > 0 ? { errors: Object.freeze([...input.errors]) } : {}
|
|
2461
4705
|
};
|
|
2462
4706
|
}
|
|
2463
|
-
function
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
4707
|
+
function callMetadata(result) {
|
|
4708
|
+
return {
|
|
4709
|
+
...result.provider ? { provider: result.provider } : {},
|
|
4710
|
+
...result.model ? { model: result.model } : {},
|
|
4711
|
+
...result.fromFallback ? { fromFallback: true } : {},
|
|
4712
|
+
durationMs: result.durationMs
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
function addUsage(usage, result) {
|
|
4716
|
+
usage.calls += 1;
|
|
4717
|
+
usage.inputTokens += result.tokens.input;
|
|
4718
|
+
usage.outputTokens += result.tokens.output;
|
|
4719
|
+
usage.totalTokens += result.tokens.total;
|
|
4720
|
+
}
|
|
4721
|
+
function usageResult(usage, startedAt) {
|
|
4722
|
+
return Object.freeze({ ...usage, durationMs: Math.max(0, Date.now() - startedAt) });
|
|
4723
|
+
}
|
|
4724
|
+
function cancelledVote(seat) {
|
|
4725
|
+
return { seatId: seat.id, persona: seat.persona, status: "cancelled", error: "Cancelled." };
|
|
4726
|
+
}
|
|
4727
|
+
function distinctTargetCount(votes, profile) {
|
|
4728
|
+
const keys = votes.filter((vote) => vote.status === "valid").map(
|
|
4729
|
+
(vote) => profile.distinctness === "provider" ? vote.provider : `${vote.provider ?? ""}/${vote.model ?? ""}`
|
|
4730
|
+
).filter(Boolean);
|
|
4731
|
+
return new Set(keys).size;
|
|
4732
|
+
}
|
|
4733
|
+
function distinctnessWarnings(votes, profile) {
|
|
4734
|
+
if (profile.distinctness === "none") return [];
|
|
4735
|
+
const valid = votes.filter((vote) => vote.status === "valid");
|
|
4736
|
+
const distinct = distinctTargetCount(valid, profile);
|
|
4737
|
+
if (valid.length > 1 && distinct < valid.length) {
|
|
4738
|
+
return [
|
|
4739
|
+
`Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${valid.length} valid vote(s).`
|
|
4740
|
+
];
|
|
2468
4741
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
4742
|
+
return [];
|
|
4743
|
+
}
|
|
4744
|
+
function optionLabel(question, optionId) {
|
|
4745
|
+
if (!optionId) return void 0;
|
|
4746
|
+
return question.options?.find((option) => option.id.trim() === optionId)?.label.trim();
|
|
4747
|
+
}
|
|
4748
|
+
function validateRefusalCollision(question, refusalOptionId) {
|
|
4749
|
+
if (question.options?.some((option) => option.id.trim() === refusalOptionId)) {
|
|
4750
|
+
throw new Error(`CouncilOrchestrator: option id "${refusalOptionId}" is reserved.`);
|
|
2471
4751
|
}
|
|
2472
|
-
|
|
4752
|
+
}
|
|
4753
|
+
function validateConcurrency(value) {
|
|
4754
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_COUNCIL_CONCURRENCY) {
|
|
4755
|
+
throw new Error(
|
|
4756
|
+
`CouncilOrchestrator: maxConcurrency must be an integer in [1, ${MAX_COUNCIL_CONCURRENCY}].`
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
4759
|
+
return value;
|
|
4760
|
+
}
|
|
4761
|
+
async function mapConcurrent(items, concurrency, worker) {
|
|
4762
|
+
const results = new Array(items.length);
|
|
4763
|
+
let next = 0;
|
|
4764
|
+
const run = async () => {
|
|
4765
|
+
while (true) {
|
|
4766
|
+
const index = next++;
|
|
4767
|
+
if (index >= items.length) return;
|
|
4768
|
+
const item = items[index];
|
|
4769
|
+
if (item !== void 0) results[index] = await worker(item, index);
|
|
4770
|
+
}
|
|
4771
|
+
};
|
|
4772
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => run()));
|
|
4773
|
+
return results;
|
|
4774
|
+
}
|
|
4775
|
+
function emptyCallResult(error) {
|
|
4776
|
+
return {
|
|
4777
|
+
text: "",
|
|
4778
|
+
model: "",
|
|
4779
|
+
provider: "",
|
|
4780
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
4781
|
+
durationMs: 0,
|
|
4782
|
+
fromFallback: false,
|
|
4783
|
+
error
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
function optionalString(value) {
|
|
4787
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
4788
|
+
}
|
|
4789
|
+
function errorMessage(error) {
|
|
4790
|
+
return error instanceof Error ? error.message : String(error);
|
|
2473
4791
|
}
|
|
2474
4792
|
|
|
2475
4793
|
// src/storage/goal-store.ts
|
|
@@ -2478,7 +4796,7 @@ var MAX_JOURNAL_ENTRIES = 500;
|
|
|
2478
4796
|
function goalFilePath(projectRoot) {
|
|
2479
4797
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
2480
4798
|
}
|
|
2481
|
-
async function loadGoal(filePath, events) {
|
|
4799
|
+
async function loadGoal(filePath, events, warn) {
|
|
2482
4800
|
const t0 = Date.now();
|
|
2483
4801
|
let raw;
|
|
2484
4802
|
try {
|
|
@@ -2509,13 +4827,9 @@ async function loadGoal(filePath, events) {
|
|
|
2509
4827
|
try {
|
|
2510
4828
|
const parsed = JSON.parse(raw);
|
|
2511
4829
|
if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
|
|
2512
|
-
console.warn(JSON.stringify({
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
path: filePath,
|
|
2516
|
-
message: "invalid schema \u2014 consider deleting and re-creating",
|
|
2517
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2518
|
-
}));
|
|
4830
|
+
(warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.invalid_schema", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
|
|
4831
|
+
"invalid schema \u2014 consider deleting and re-creating"
|
|
4832
|
+
);
|
|
2519
4833
|
events?.emit("storage.read", {
|
|
2520
4834
|
sessionId: "~boot~",
|
|
2521
4835
|
store: "goal",
|
|
@@ -2537,13 +4851,9 @@ async function loadGoal(filePath, events) {
|
|
|
2537
4851
|
});
|
|
2538
4852
|
return parsed;
|
|
2539
4853
|
} catch {
|
|
2540
|
-
console.warn(JSON.stringify({
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
path: filePath,
|
|
2544
|
-
message: "JSON parse failed \u2014 consider deleting and re-creating",
|
|
2545
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2546
|
-
}));
|
|
4854
|
+
(warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.parse_failed", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
|
|
4855
|
+
"JSON parse failed \u2014 consider deleting and re-creating"
|
|
4856
|
+
);
|
|
2547
4857
|
events?.emit("storage.read", {
|
|
2548
4858
|
sessionId: "~boot~",
|
|
2549
4859
|
store: "goal",
|
|
@@ -2853,7 +5163,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
2853
5163
|
}
|
|
2854
5164
|
if (newContent) {
|
|
2855
5165
|
next ??= messages.slice();
|
|
2856
|
-
next[i] = { ...msg, content: newContent };
|
|
5166
|
+
next[i] = { ...msg, content: newContent, _estTokens: void 0 };
|
|
2857
5167
|
changed = true;
|
|
2858
5168
|
}
|
|
2859
5169
|
if (compactionDebugEnabled()) {
|
|
@@ -3247,7 +5557,7 @@ function isDesignStack(v) {
|
|
|
3247
5557
|
|
|
3248
5558
|
// src/execution/design-kit-loader.ts
|
|
3249
5559
|
import { existsSync } from "node:fs";
|
|
3250
|
-
import * as
|
|
5560
|
+
import * as fs3 from "node:fs/promises";
|
|
3251
5561
|
import * as path4 from "node:path";
|
|
3252
5562
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3253
5563
|
var KIT_FILE = "KIT.md";
|
|
@@ -3287,17 +5597,17 @@ function parseKitFrontmatter(raw) {
|
|
|
3287
5597
|
else out[key] = items;
|
|
3288
5598
|
};
|
|
3289
5599
|
for (const line of lines) {
|
|
3290
|
-
const
|
|
3291
|
-
if (listKey &&
|
|
3292
|
-
const parsed = parseList(
|
|
5600
|
+
const bulletMatch = /^\s*-\s+(.*)$/.exec(line);
|
|
5601
|
+
if (listKey && bulletMatch) {
|
|
5602
|
+
const parsed = parseList(bulletMatch[1] ?? "");
|
|
3293
5603
|
if (listKey === "stacks") out.stacks.push(...parsed.filter(isDesignStack));
|
|
3294
5604
|
else out[listKey].push(...parsed);
|
|
3295
5605
|
continue;
|
|
3296
5606
|
}
|
|
3297
|
-
const
|
|
3298
|
-
if (!
|
|
3299
|
-
const key =
|
|
3300
|
-
const rest = (
|
|
5607
|
+
const kvMatch = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
|
|
5608
|
+
if (!kvMatch) continue;
|
|
5609
|
+
const key = kvMatch[1] ?? "";
|
|
5610
|
+
const rest = (kvMatch[2] ?? "").trim();
|
|
3301
5611
|
if (key === "tags" || key === "stacks" || key === "themes") {
|
|
3302
5612
|
if (rest) {
|
|
3303
5613
|
setList(key, parseList(rest));
|
|
@@ -3314,24 +5624,23 @@ function parseKitFrontmatter(raw) {
|
|
|
3314
5624
|
}
|
|
3315
5625
|
function narrowStackSections(body, stack) {
|
|
3316
5626
|
if (!stack) return body;
|
|
3317
|
-
const
|
|
3318
|
-
const
|
|
3319
|
-
let
|
|
3320
|
-
while ((
|
|
3321
|
-
|
|
3322
|
-
}
|
|
3323
|
-
if (
|
|
3324
|
-
const
|
|
3325
|
-
let result = body.slice(0,
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
if (cur.id === stack) {
|
|
5627
|
+
const stackHeading = /^##\s+Stack:\s*([a-z-]+)\s*$/gim;
|
|
5628
|
+
const sections = [];
|
|
5629
|
+
let match;
|
|
5630
|
+
while ((match = stackHeading.exec(body)) !== null) {
|
|
5631
|
+
sections.push({ id: (match[1] ?? "").trim(), start: match.index });
|
|
5632
|
+
}
|
|
5633
|
+
if (sections.length === 0) return body;
|
|
5634
|
+
const preambleEnd = sections[0].start;
|
|
5635
|
+
let result = body.slice(0, preambleEnd).trimEnd();
|
|
5636
|
+
let sectionEnd;
|
|
5637
|
+
for (let i = 0; i < sections.length; i++) {
|
|
5638
|
+
const current = sections[i];
|
|
5639
|
+
sectionEnd = i + 1 < sections.length ? sections[i + 1].start : body.length;
|
|
5640
|
+
if (current.id === stack) {
|
|
3332
5641
|
result += `
|
|
3333
5642
|
|
|
3334
|
-
${body.slice(
|
|
5643
|
+
${body.slice(current.start, sectionEnd).trimEnd()}`;
|
|
3335
5644
|
}
|
|
3336
5645
|
}
|
|
3337
5646
|
return `${result}
|
|
@@ -3356,7 +5665,7 @@ var DefaultDesignKitLoader = class {
|
|
|
3356
5665
|
for (const { dir, source } of this.dirs) {
|
|
3357
5666
|
let entries;
|
|
3358
5667
|
try {
|
|
3359
|
-
entries = await
|
|
5668
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
3360
5669
|
} catch {
|
|
3361
5670
|
continue;
|
|
3362
5671
|
}
|
|
@@ -3364,7 +5673,7 @@ var DefaultDesignKitLoader = class {
|
|
|
3364
5673
|
if (!e.isDirectory()) continue;
|
|
3365
5674
|
const kitFile = path4.join(dir, e.name, KIT_FILE);
|
|
3366
5675
|
try {
|
|
3367
|
-
const raw = await
|
|
5676
|
+
const raw = await fs3.readFile(kitFile, "utf8");
|
|
3368
5677
|
const fm = parseKitFrontmatter(raw);
|
|
3369
5678
|
const id = fm.id ?? e.name;
|
|
3370
5679
|
if (!fm.name) continue;
|
|
@@ -3425,7 +5734,7 @@ var DefaultDesignKitLoader = class {
|
|
|
3425
5734
|
if (cached !== void 0) return cached;
|
|
3426
5735
|
const m = await this.find(id);
|
|
3427
5736
|
if (!m) throw new Error(`Design kit "${id}" not found`);
|
|
3428
|
-
const raw = await
|
|
5737
|
+
const raw = await fs3.readFile(m.path, "utf8");
|
|
3429
5738
|
const body = narrowStackSections(stripFrontmatter(raw), stack);
|
|
3430
5739
|
this.bodyCache.set(key, body);
|
|
3431
5740
|
return body;
|
|
@@ -3438,7 +5747,7 @@ var DefaultDesignKitLoader = class {
|
|
|
3438
5747
|
if (m) {
|
|
3439
5748
|
const tokensPath = path4.join(path4.dirname(m.path), TOKENS_FILE);
|
|
3440
5749
|
try {
|
|
3441
|
-
const raw = await
|
|
5750
|
+
const raw = await fs3.readFile(tokensPath, "utf8");
|
|
3442
5751
|
const parsed = JSON.parse(raw);
|
|
3443
5752
|
tokens = parsed;
|
|
3444
5753
|
} catch {
|
|
@@ -3496,7 +5805,7 @@ function _resetDesignKitLoaderMemo() {
|
|
|
3496
5805
|
|
|
3497
5806
|
// src/execution/design-project-store.ts
|
|
3498
5807
|
import { existsSync as existsSync2 } from "node:fs";
|
|
3499
|
-
import * as
|
|
5808
|
+
import * as fs4 from "node:fs/promises";
|
|
3500
5809
|
import * as path5 from "node:path";
|
|
3501
5810
|
var DESIGN_DIR = ".design";
|
|
3502
5811
|
function designProjectDir(projectRoot) {
|
|
@@ -3509,7 +5818,7 @@ async function loadProjectDesignRules(projectRoot) {
|
|
|
3509
5818
|
let rules;
|
|
3510
5819
|
for (const name of RULE_FILES) {
|
|
3511
5820
|
try {
|
|
3512
|
-
const txt = await
|
|
5821
|
+
const txt = await fs4.readFile(path5.join(designProjectDir(projectRoot), name), "utf8");
|
|
3513
5822
|
if (txt.trim()) {
|
|
3514
5823
|
rules = txt.trim();
|
|
3515
5824
|
break;
|
|
@@ -3530,7 +5839,7 @@ function parseOverrides(value) {
|
|
|
3530
5839
|
}
|
|
3531
5840
|
async function loadActiveKit(projectRoot) {
|
|
3532
5841
|
try {
|
|
3533
|
-
const raw = await
|
|
5842
|
+
const raw = await fs4.readFile(path5.join(designProjectDir(projectRoot), "active.json"), "utf8");
|
|
3534
5843
|
const parsed = JSON.parse(raw);
|
|
3535
5844
|
if (parsed && typeof parsed.kit === "string") {
|
|
3536
5845
|
return {
|
|
@@ -3563,11 +5872,11 @@ function applyTokenOverrides(tokens, overrides) {
|
|
|
3563
5872
|
}
|
|
3564
5873
|
async function ensureDesignDir(projectRoot) {
|
|
3565
5874
|
const dir = designProjectDir(projectRoot);
|
|
3566
|
-
await
|
|
5875
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
3567
5876
|
const gi = path5.join(dir, ".gitignore");
|
|
3568
5877
|
if (!existsSync2(gi)) {
|
|
3569
5878
|
try {
|
|
3570
|
-
await
|
|
5879
|
+
await fs4.writeFile(gi, "*\n");
|
|
3571
5880
|
} catch {
|
|
3572
5881
|
}
|
|
3573
5882
|
}
|
|
@@ -3578,17 +5887,17 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
|
|
|
3578
5887
|
const dir = await ensureDesignDir(projectRoot);
|
|
3579
5888
|
const record = { kit, stack: stack ?? null };
|
|
3580
5889
|
if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
|
|
3581
|
-
await
|
|
5890
|
+
await fs4.writeFile(path5.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
3582
5891
|
`);
|
|
3583
5892
|
const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
|
|
3584
5893
|
`;
|
|
3585
|
-
await
|
|
5894
|
+
await fs4.appendFile(path5.join(dir, "decisions.md"), line);
|
|
3586
5895
|
} catch {
|
|
3587
5896
|
}
|
|
3588
5897
|
}
|
|
3589
5898
|
async function clearPersistedActiveKit(projectRoot) {
|
|
3590
5899
|
try {
|
|
3591
|
-
await
|
|
5900
|
+
await fs4.rm(path5.join(designProjectDir(projectRoot), "active.json"), { force: true });
|
|
3592
5901
|
} catch {
|
|
3593
5902
|
}
|
|
3594
5903
|
}
|
|
@@ -3869,10 +6178,10 @@ function makeDesignVerifyToolCallMiddleware() {
|
|
|
3869
6178
|
const p = typeof input?.path === "string" ? input.path : "";
|
|
3870
6179
|
if (!p || !detectFrontendFile(p)) return out;
|
|
3871
6180
|
const ctx = out.ctx;
|
|
3872
|
-
const { default:
|
|
6181
|
+
const { default: fs9 } = await import("node:fs/promises");
|
|
3873
6182
|
const { default: nodePath } = await import("node:path");
|
|
3874
6183
|
const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
|
|
3875
|
-
const text = await
|
|
6184
|
+
const text = await fs9.readFile(abs, "utf8").catch(() => "");
|
|
3876
6185
|
if (!text) return out;
|
|
3877
6186
|
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
3878
6187
|
const rawTokens = await loader.readTokens(state.activeKit);
|
|
@@ -3934,154 +6243,36 @@ Adhere strictly to its tokens, components, and patterns. Keep light/dark + respo
|
|
|
3934
6243
|
text = parts.join("\n");
|
|
3935
6244
|
}
|
|
3936
6245
|
const rules = await loadProjectDesignRules(ctx.projectRoot).catch(() => void 0);
|
|
3937
|
-
if (rules) {
|
|
3938
|
-
text += `
|
|
3939
|
-
|
|
3940
|
-
## Project design rules (.design/rules.md) \u2014 these OVERRIDE kit defaults on conflict
|
|
3941
|
-
${rules}`;
|
|
3942
|
-
}
|
|
3943
|
-
const block = {
|
|
3944
|
-
type: "text",
|
|
3945
|
-
text,
|
|
3946
|
-
cache_control: { type: "ephemeral" }
|
|
3947
|
-
};
|
|
3948
|
-
const system = Array.isArray(req.system) ? [...req.system, block] : [block];
|
|
3949
|
-
return next({ ...req, system });
|
|
3950
|
-
}
|
|
3951
|
-
};
|
|
3952
|
-
}
|
|
3953
|
-
function installDesignStudioMiddleware(deps) {
|
|
3954
|
-
if (deps.enabled === false) return;
|
|
3955
|
-
const loader = getDesignKitLoader(deps.ctx.projectRoot);
|
|
3956
|
-
deps.pipelines.userInput.prepend(makeDesignDetectUserInputMiddleware());
|
|
3957
|
-
deps.pipelines.toolCall.prepend(makeDesignDetectToolCallMiddleware());
|
|
3958
|
-
deps.pipelines.toolCall.prepend(makeDesignVerifyToolCallMiddleware());
|
|
3959
|
-
deps.pipelines.request.prepend(makeDesignStudioRequestMiddleware({ ctx: deps.ctx, loader }));
|
|
3960
|
-
void loadActiveKit(deps.ctx.projectRoot).then((persisted) => {
|
|
3961
|
-
if (persisted && !getDesignState(deps.ctx)?.activeKit) {
|
|
3962
|
-
const stack = persisted.stack && isDesignStack(persisted.stack) ? persisted.stack : void 0;
|
|
3963
|
-
setActiveKit(deps.ctx, persisted.kit, stack, persisted.overrides);
|
|
3964
|
-
}
|
|
3965
|
-
}).catch(() => {
|
|
3966
|
-
});
|
|
3967
|
-
}
|
|
3968
|
-
|
|
3969
|
-
// src/types/provider.ts
|
|
3970
|
-
var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
|
|
3971
|
-
var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
|
|
3972
|
-
function classifyProviderError(status, body, message) {
|
|
3973
|
-
const type = body?.type;
|
|
3974
|
-
if (status === 0) return "network";
|
|
3975
|
-
if (status === 408) return "timeout";
|
|
3976
|
-
if (status === 599) return "stream_hang";
|
|
3977
|
-
if (type === "rate_limit_error" || status === 429) return "rate_limit";
|
|
3978
|
-
if (type === "overloaded_error" || status === 529) return "overloaded";
|
|
3979
|
-
if (status >= 500) return "server";
|
|
3980
|
-
if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
|
|
3981
|
-
return "auth";
|
|
3982
|
-
}
|
|
3983
|
-
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
3984
|
-
if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
|
|
3985
|
-
if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
|
|
3986
|
-
return "context_overflow";
|
|
3987
|
-
}
|
|
3988
|
-
if (status >= 400) return "invalid_request";
|
|
3989
|
-
return "unknown";
|
|
3990
|
-
}
|
|
3991
|
-
function isRetryableKind(kind) {
|
|
3992
|
-
return RETRYABLE_BY_KIND[kind];
|
|
3993
|
-
}
|
|
3994
|
-
var RETRYABLE_BY_KIND = {
|
|
3995
|
-
rate_limit: true,
|
|
3996
|
-
overloaded: true,
|
|
3997
|
-
server: true,
|
|
3998
|
-
timeout: true,
|
|
3999
|
-
network: true,
|
|
4000
|
-
stream_hang: true,
|
|
4001
|
-
auth: false,
|
|
4002
|
-
context_overflow: false,
|
|
4003
|
-
content_filter: false,
|
|
4004
|
-
invalid_request: false,
|
|
4005
|
-
unknown: false
|
|
4006
|
-
};
|
|
4007
|
-
var ProviderError = class extends WrongStackError {
|
|
4008
|
-
status;
|
|
4009
|
-
retryable;
|
|
4010
|
-
providerId;
|
|
4011
|
-
/** Canonical failure classification — see {@link ProviderErrorKind}. */
|
|
4012
|
-
kind;
|
|
4013
|
-
body;
|
|
4014
|
-
constructor(message, status, retryable, providerId, opts = {}) {
|
|
4015
|
-
const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
|
|
4016
|
-
super({
|
|
4017
|
-
message,
|
|
4018
|
-
code: kindToCode(kind),
|
|
4019
|
-
subsystem: "provider",
|
|
4020
|
-
severity: status >= 500 ? "error" : "warning",
|
|
4021
|
-
recoverable: retryable,
|
|
4022
|
-
context: { providerId, status },
|
|
4023
|
-
cause: opts.cause
|
|
4024
|
-
});
|
|
4025
|
-
this.name = "ProviderError";
|
|
4026
|
-
this.status = status;
|
|
4027
|
-
this.retryable = retryable;
|
|
4028
|
-
this.providerId = providerId;
|
|
4029
|
-
this.kind = kind;
|
|
4030
|
-
this.body = opts.body;
|
|
4031
|
-
}
|
|
4032
|
-
/**
|
|
4033
|
-
* Render a one-line, user-facing description. Designed for the CLI/TUI
|
|
4034
|
-
* status line and the agent's retry warning. Avoids dumping raw JSON
|
|
4035
|
-
* (which is what users see today when a 529 lands and the log message
|
|
4036
|
-
* includes the full `{"type":"error",...}` body).
|
|
4037
|
-
*
|
|
4038
|
-
* Examples:
|
|
4039
|
-
* "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
|
|
4040
|
-
* "openai rate limited (429): Retry after 12s"
|
|
4041
|
-
* "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
|
|
4042
|
-
* "groq HTTP 500 (server error)"
|
|
4043
|
-
*/
|
|
4044
|
-
describe() {
|
|
4045
|
-
const kind = describeStatus(this.status, this.body?.type);
|
|
4046
|
-
const head = `${this.providerId} ${kind}`;
|
|
4047
|
-
const detail = this.body?.message?.trim();
|
|
4048
|
-
const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
|
|
4049
|
-
if (detail && detail.length > 0) {
|
|
4050
|
-
return `${head}: ${truncate(detail, 240)}${reqId}`;
|
|
6246
|
+
if (rules) {
|
|
6247
|
+
text += `
|
|
6248
|
+
|
|
6249
|
+
## Project design rules (.design/rules.md) \u2014 these OVERRIDE kit defaults on conflict
|
|
6250
|
+
${rules}`;
|
|
6251
|
+
}
|
|
6252
|
+
const block = {
|
|
6253
|
+
type: "text",
|
|
6254
|
+
text,
|
|
6255
|
+
cache_control: { type: "ephemeral" }
|
|
6256
|
+
};
|
|
6257
|
+
const system = Array.isArray(req.system) ? [...req.system, block] : [block];
|
|
6258
|
+
return next({ ...req, system });
|
|
4051
6259
|
}
|
|
4052
|
-
|
|
4053
|
-
}
|
|
4054
|
-
};
|
|
4055
|
-
function describeStatus(status, type) {
|
|
4056
|
-
if (status === 0) return "network error";
|
|
4057
|
-
if (status === 599) return `stream hang (${status})`;
|
|
4058
|
-
if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
|
|
4059
|
-
if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
|
|
4060
|
-
if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
|
|
4061
|
-
if (type === "permission_error" || status === 403) return `forbidden (${status})`;
|
|
4062
|
-
if (type === "not_found_error" || status === 404) return `not found (${status})`;
|
|
4063
|
-
if (type === "content_filter") return `content filtered (${status})`;
|
|
4064
|
-
if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
|
|
4065
|
-
if (status === 408) return `timeout (${status})`;
|
|
4066
|
-
if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
|
|
4067
|
-
if (type) return `${type} (${status})`;
|
|
4068
|
-
return `HTTP ${status}`;
|
|
6260
|
+
};
|
|
4069
6261
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
}
|
|
4083
|
-
|
|
4084
|
-
return KIND_TO_CODE[kind];
|
|
6262
|
+
function installDesignStudioMiddleware(deps) {
|
|
6263
|
+
if (deps.enabled === false) return;
|
|
6264
|
+
const loader = getDesignKitLoader(deps.ctx.projectRoot);
|
|
6265
|
+
deps.pipelines.userInput.prepend(makeDesignDetectUserInputMiddleware());
|
|
6266
|
+
deps.pipelines.toolCall.prepend(makeDesignDetectToolCallMiddleware());
|
|
6267
|
+
deps.pipelines.toolCall.prepend(makeDesignVerifyToolCallMiddleware());
|
|
6268
|
+
deps.pipelines.request.prepend(makeDesignStudioRequestMiddleware({ ctx: deps.ctx, loader }));
|
|
6269
|
+
void loadActiveKit(deps.ctx.projectRoot).then((persisted) => {
|
|
6270
|
+
if (persisted && !getDesignState(deps.ctx)?.activeKit) {
|
|
6271
|
+
const stack = persisted.stack && isDesignStack(persisted.stack) ? persisted.stack : void 0;
|
|
6272
|
+
setActiveKit(deps.ctx, persisted.kit, stack, persisted.overrides);
|
|
6273
|
+
}
|
|
6274
|
+
}).catch(() => {
|
|
6275
|
+
});
|
|
4085
6276
|
}
|
|
4086
6277
|
|
|
4087
6278
|
// src/execution/regex-patterns.ts
|
|
@@ -4110,11 +6301,25 @@ function buildRecoveryStrategies(opts) {
|
|
|
4110
6301
|
},
|
|
4111
6302
|
{
|
|
4112
6303
|
label: "rate_limit_backoff",
|
|
4113
|
-
async attempt(err) {
|
|
6304
|
+
async attempt(err, ctx) {
|
|
4114
6305
|
if (!(err instanceof ProviderError) || err.kind !== "rate_limit") return null;
|
|
4115
6306
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
4116
6307
|
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
4117
|
-
|
|
6308
|
+
if (ctx?.signal) {
|
|
6309
|
+
await new Promise((resolve4) => {
|
|
6310
|
+
if (ctx.signal.aborted) {
|
|
6311
|
+
resolve4();
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
6314
|
+
const timer = setTimeout(resolve4, delay);
|
|
6315
|
+
ctx.signal.addEventListener("abort", () => {
|
|
6316
|
+
clearTimeout(timer);
|
|
6317
|
+
resolve4();
|
|
6318
|
+
}, { once: true });
|
|
6319
|
+
});
|
|
6320
|
+
} else {
|
|
6321
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
6322
|
+
}
|
|
4118
6323
|
return { action: "retry", reason: "rate_limit_backoff" };
|
|
4119
6324
|
}
|
|
4120
6325
|
},
|
|
@@ -4271,22 +6476,40 @@ var EternalAutonomyEngine = class {
|
|
|
4271
6476
|
currentCtrl = null;
|
|
4272
6477
|
iterationsSinceCompact = 0;
|
|
4273
6478
|
goalPath;
|
|
6479
|
+
/**
|
|
6480
|
+
* Emit a structured error. Uses the configured Logger when available;
|
|
6481
|
+
* falls back to console.error(JSON) so events are never silently dropped.
|
|
6482
|
+
*/
|
|
6483
|
+
logError(msg, ctx) {
|
|
6484
|
+
if (this.opts.logger) {
|
|
6485
|
+
this.opts.logger.error(msg, ctx);
|
|
6486
|
+
} else {
|
|
6487
|
+
console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
4274
6490
|
/** Current engine state — readable for UIs. */
|
|
4275
6491
|
get currentState() {
|
|
4276
6492
|
return this.state;
|
|
4277
6493
|
}
|
|
6494
|
+
/**
|
|
6495
|
+
* Load the goal file with structured logging for parse/schema warnings.
|
|
6496
|
+
*/
|
|
6497
|
+
loadGoal() {
|
|
6498
|
+
return loadGoal(
|
|
6499
|
+
this.goalPath,
|
|
6500
|
+
this.opts.events,
|
|
6501
|
+
this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
|
|
6502
|
+
);
|
|
6503
|
+
}
|
|
4278
6504
|
/** Synchronously request stop. Resolves once the running iteration aborts. */
|
|
4279
6505
|
stop() {
|
|
4280
6506
|
this.stopRequested = true;
|
|
4281
6507
|
this.currentCtrl?.abort();
|
|
4282
6508
|
void this.persistEngineState("stopped").catch((err) => {
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
event: "engine.persist_state_failed",
|
|
4286
|
-
|
|
4287
|
-
context: { expectedState: "stopped" },
|
|
4288
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4289
|
-
}));
|
|
6509
|
+
this.logError(
|
|
6510
|
+
"Engine persist state failed",
|
|
6511
|
+
{ event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
|
|
6512
|
+
);
|
|
4290
6513
|
});
|
|
4291
6514
|
this.state = "stopped";
|
|
4292
6515
|
}
|
|
@@ -4342,7 +6565,7 @@ var EternalAutonomyEngine = class {
|
|
|
4342
6565
|
const emit = (stage) => {
|
|
4343
6566
|
this.opts.onStage?.(stage);
|
|
4344
6567
|
};
|
|
4345
|
-
const goal = await loadGoal(
|
|
6568
|
+
const goal = await this.loadGoal();
|
|
4346
6569
|
if (!goal) {
|
|
4347
6570
|
emit({ phase: "stopped" });
|
|
4348
6571
|
this.stopRequested = true;
|
|
@@ -4446,16 +6669,13 @@ var EternalAutonomyEngine = class {
|
|
|
4446
6669
|
emit({ phase: "reflect", status, note });
|
|
4447
6670
|
let iterationIndex = 0;
|
|
4448
6671
|
try {
|
|
4449
|
-
const reloaded = await loadGoal(
|
|
6672
|
+
const reloaded = await this.loadGoal();
|
|
4450
6673
|
iterationIndex = reloaded?.iterations ?? 0;
|
|
4451
6674
|
} catch (err) {
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
event: "autonomy.goal_reload_failed",
|
|
4455
|
-
|
|
4456
|
-
context: { goalPath: this.goalPath },
|
|
4457
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4458
|
-
}));
|
|
6675
|
+
this.logError(
|
|
6676
|
+
"Goal reload failed",
|
|
6677
|
+
{ event: "autonomy.goal_reload_failed", message: toErrorMessage(err), context: { goalPath: this.goalPath } }
|
|
6678
|
+
);
|
|
4459
6679
|
}
|
|
4460
6680
|
this.opts.onIteration?.({
|
|
4461
6681
|
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4691,13 +6911,10 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
4691
6911
|
clearTimeout(timer);
|
|
4692
6912
|
}
|
|
4693
6913
|
} catch (err) {
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
event: "autonomy.brainstorm_failed",
|
|
4697
|
-
|
|
4698
|
-
context: { goal: goal.goal.slice(0, 100) },
|
|
4699
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4700
|
-
}));
|
|
6914
|
+
this.logError(
|
|
6915
|
+
"Brainstorm failed",
|
|
6916
|
+
{ event: "autonomy.brainstorm_failed", message: toErrorMessage(err), context: { goal: goal.goal.slice(0, 100) } }
|
|
6917
|
+
);
|
|
4701
6918
|
return null;
|
|
4702
6919
|
}
|
|
4703
6920
|
}
|
|
@@ -4780,7 +6997,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
4780
6997
|
}
|
|
4781
6998
|
}
|
|
4782
6999
|
async appendIterationEntry(entry) {
|
|
4783
|
-
const current = await loadGoal(
|
|
7000
|
+
const current = await this.loadGoal();
|
|
4784
7001
|
if (!current) {
|
|
4785
7002
|
return;
|
|
4786
7003
|
}
|
|
@@ -4794,7 +7011,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
4794
7011
|
* the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
|
|
4795
7012
|
*/
|
|
4796
7013
|
async bumpTodoAttempt(todoId) {
|
|
4797
|
-
const current = await loadGoal(
|
|
7014
|
+
const current = await this.loadGoal();
|
|
4798
7015
|
if (!current) return;
|
|
4799
7016
|
const attempts = { ...current.todoAttempts ?? {} };
|
|
4800
7017
|
attempts[todoId] = (attempts[todoId] ?? 0) + 1;
|
|
@@ -4808,7 +7025,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
4808
7025
|
* goal is already `completed`.
|
|
4809
7026
|
*/
|
|
4810
7027
|
async markGoalCompleted(action, note) {
|
|
4811
|
-
const current = await loadGoal(
|
|
7028
|
+
const current = await this.loadGoal();
|
|
4812
7029
|
if (!current) return;
|
|
4813
7030
|
if (current.goalState === "completed") return;
|
|
4814
7031
|
const withFlag = { ...current, goalState: "completed" };
|
|
@@ -4819,6 +7036,11 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
4819
7036
|
note: note.slice(0, 240)
|
|
4820
7037
|
});
|
|
4821
7038
|
await saveGoal(this.goalPath, withEntry, this.opts.events);
|
|
7039
|
+
try {
|
|
7040
|
+
const { unlink: unlink4 } = await import("node:fs/promises");
|
|
7041
|
+
await unlink4(this.goalPath);
|
|
7042
|
+
} catch {
|
|
7043
|
+
}
|
|
4822
7044
|
this.opts.onEternalStop?.();
|
|
4823
7045
|
}
|
|
4824
7046
|
/**
|
|
@@ -4827,7 +7049,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
4827
7049
|
* `onEternalStop` so the REPL returns to normal mode.
|
|
4828
7050
|
*/
|
|
4829
7051
|
async clearGoalManually(note) {
|
|
4830
|
-
const current = await loadGoal(
|
|
7052
|
+
const current = await this.loadGoal();
|
|
4831
7053
|
if (current) {
|
|
4832
7054
|
const abandoned = { ...current, goalState: "abandoned" };
|
|
4833
7055
|
await saveGoal(this.goalPath, abandoned, this.opts.events);
|
|
@@ -4875,7 +7097,23 @@ Recent work:
|
|
|
4875
7097
|
${recentJournal}` : ""
|
|
4876
7098
|
].join("\n"),
|
|
4877
7099
|
risk: "high",
|
|
4878
|
-
fallback: "continue"
|
|
7100
|
+
fallback: "continue",
|
|
7101
|
+
// Structured verdict — completion is control-plane input, decided by
|
|
7102
|
+
// exact option id rather than sniffing prose for "done"/"complete".
|
|
7103
|
+
options: [
|
|
7104
|
+
{
|
|
7105
|
+
id: "goal_complete",
|
|
7106
|
+
label: "The goal is complete \u2014 stop the eternal run",
|
|
7107
|
+
consequence: "The engine stops and the goal file is archived.",
|
|
7108
|
+
risk: "high"
|
|
7109
|
+
},
|
|
7110
|
+
{
|
|
7111
|
+
id: "keep_working",
|
|
7112
|
+
label: "Not complete yet \u2014 keep working",
|
|
7113
|
+
consequence: "The engine continues iterating on the goal.",
|
|
7114
|
+
risk: "low"
|
|
7115
|
+
}
|
|
7116
|
+
]
|
|
4879
7117
|
});
|
|
4880
7118
|
const summary = formatDecisionSummary(decision, {
|
|
4881
7119
|
id: `goal-done-${goal.iterations}`,
|
|
@@ -4895,8 +7133,7 @@ ${recentJournal}` : ""
|
|
|
4895
7133
|
return false;
|
|
4896
7134
|
}
|
|
4897
7135
|
if (decision.type === "answer") {
|
|
4898
|
-
|
|
4899
|
-
return text.includes("complete") || text.includes("done") || text.includes("yes");
|
|
7136
|
+
return decision.optionId === "goal_complete";
|
|
4900
7137
|
}
|
|
4901
7138
|
return true;
|
|
4902
7139
|
} catch {
|
|
@@ -4907,13 +7144,13 @@ ${recentJournal}` : ""
|
|
|
4907
7144
|
* Persist a progress update from the agent's [PROGRESS: N%] output.
|
|
4908
7145
|
*/
|
|
4909
7146
|
async updateProgress(progress, note) {
|
|
4910
|
-
const current = await loadGoal(
|
|
7147
|
+
const current = await this.loadGoal();
|
|
4911
7148
|
if (!current) return;
|
|
4912
7149
|
const updated = recordProgress(current, progress, note);
|
|
4913
7150
|
await saveGoal(this.goalPath, updated, this.opts.events);
|
|
4914
7151
|
}
|
|
4915
7152
|
async persistEngineState(state) {
|
|
4916
|
-
const current = await loadGoal(
|
|
7153
|
+
const current = await this.loadGoal();
|
|
4917
7154
|
if (!current) return;
|
|
4918
7155
|
if (current.engineState === state) return;
|
|
4919
7156
|
await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
|
|
@@ -4948,16 +7185,18 @@ var IntelligentCompactor = class {
|
|
|
4948
7185
|
eliseThreshold;
|
|
4949
7186
|
summarizerPrompt;
|
|
4950
7187
|
summarizerModel;
|
|
7188
|
+
oneShotOrchestrator;
|
|
4951
7189
|
constructor(opts) {
|
|
4952
7190
|
this.provider = opts.provider;
|
|
4953
|
-
this.warnThreshold = opts.warnThreshold ?? 0.
|
|
4954
|
-
this.softThreshold = opts.softThreshold ?? 0.
|
|
4955
|
-
this.hardThreshold = opts.hardThreshold ?? 0.
|
|
7191
|
+
this.warnThreshold = opts.warnThreshold ?? 0.5;
|
|
7192
|
+
this.softThreshold = opts.softThreshold ?? 0.65;
|
|
7193
|
+
this.hardThreshold = opts.hardThreshold ?? 0.8;
|
|
4956
7194
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
4957
7195
|
this.preserveK = opts.preserveK ?? 4;
|
|
4958
|
-
this.eliseThreshold = opts.eliseThreshold ??
|
|
7196
|
+
this.eliseThreshold = opts.eliseThreshold ?? 300;
|
|
4959
7197
|
this.summarizerPrompt = opts.summarizerPrompt ?? readBundledInstructionText("llm/intelligent-compactor-summarizer.md");
|
|
4960
7198
|
this.summarizerModel = opts.summarizerModel;
|
|
7199
|
+
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
4961
7200
|
}
|
|
4962
7201
|
async compact(ctx, opts = {}) {
|
|
4963
7202
|
const beforeTokens = estimateMessages(ctx.messages);
|
|
@@ -4970,7 +7209,7 @@ var IntelligentCompactor = class {
|
|
|
4970
7209
|
let collapsedDigest;
|
|
4971
7210
|
if (aggressive) {
|
|
4972
7211
|
const phase2 = await this.summarizeAncientTurns(ctx);
|
|
4973
|
-
|
|
7212
|
+
reductions.push({ phase: "summary", saved: Math.max(0, phase2.saved) });
|
|
4974
7213
|
collapsedDigest = phase2.digest;
|
|
4975
7214
|
} else if (load >= this.warnThreshold) {
|
|
4976
7215
|
const saved2 = this.elide(ctx);
|
|
@@ -5024,9 +7263,15 @@ var IntelligentCompactor = class {
|
|
|
5024
7263
|
} catch {
|
|
5025
7264
|
summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
5026
7265
|
}
|
|
7266
|
+
const smartDigest = buildSmartDigest(toSummarize);
|
|
7267
|
+
const enriched = smartDigest ? `[CRITICAL]
|
|
7268
|
+
${smartDigest}
|
|
7269
|
+
|
|
7270
|
+
[SUMMARY]
|
|
7271
|
+
${summaryText}` : summaryText;
|
|
5027
7272
|
const summaryMsg = {
|
|
5028
7273
|
role: "system",
|
|
5029
|
-
content: `[prior_turns_summary: ${
|
|
7274
|
+
content: `[prior_turns_summary: ${enriched}]`
|
|
5030
7275
|
};
|
|
5031
7276
|
const summaryTokens = estimateMessages([summaryMsg]);
|
|
5032
7277
|
const tail = ctx.messages.slice(boundary);
|
|
@@ -5034,27 +7279,41 @@ var IntelligentCompactor = class {
|
|
|
5034
7279
|
return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
|
|
5035
7280
|
}
|
|
5036
7281
|
async callSummarizer(messages, ctx) {
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
7282
|
+
if (this.oneShotOrchestrator) {
|
|
7283
|
+
const result = await this.oneShotOrchestrator.call({
|
|
7284
|
+
system: this.summarizerPrompt,
|
|
7285
|
+
messages: [{ role: "user", content: this.messagesToSummary(messages) }],
|
|
7286
|
+
model: this.summarizerModel ?? "deepseek-chat",
|
|
7287
|
+
timeoutMs: 3e4,
|
|
7288
|
+
maxTokens: 1024,
|
|
7289
|
+
signal: ctx.signal,
|
|
7290
|
+
fallbackProfile: "summary"
|
|
7291
|
+
});
|
|
7292
|
+
if (result.text && !result.error) return result.text;
|
|
7293
|
+
} else {
|
|
7294
|
+
const prompt = [
|
|
7295
|
+
{ type: "text", text: this.summarizerPrompt },
|
|
7296
|
+
{ type: "text", text: "\n\nConversation to summarize:\n" },
|
|
7297
|
+
...this.messagesToText(messages)
|
|
7298
|
+
];
|
|
7299
|
+
const req = {
|
|
7300
|
+
model: this.summarizerModel ?? ctx.model,
|
|
7301
|
+
system: prompt,
|
|
7302
|
+
messages: [],
|
|
7303
|
+
maxTokens: 1024
|
|
7304
|
+
};
|
|
7305
|
+
const ac = ctx.signal ? void 0 : new AbortController();
|
|
7306
|
+
const signal = ctx.signal ?? ac?.signal;
|
|
7307
|
+
let res;
|
|
7308
|
+
try {
|
|
7309
|
+
res = await this.provider.complete(req, { signal });
|
|
7310
|
+
} finally {
|
|
7311
|
+
ac?.abort();
|
|
7312
|
+
}
|
|
7313
|
+
const textBlocks = res.content.filter(isTextBlock);
|
|
7314
|
+
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
5055
7315
|
}
|
|
5056
|
-
|
|
5057
|
-
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
7316
|
+
return "(empty summary)";
|
|
5058
7317
|
}
|
|
5059
7318
|
messagesToText(messages) {
|
|
5060
7319
|
const lines = [];
|
|
@@ -5071,6 +7330,22 @@ var IntelligentCompactor = class {
|
|
|
5071
7330
|
}
|
|
5072
7331
|
return [{ type: "text", text: lines.join("\n") }];
|
|
5073
7332
|
}
|
|
7333
|
+
/** Compact string summary of messages (no TextBlock wrapper). */
|
|
7334
|
+
messagesToSummary(messages) {
|
|
7335
|
+
const lines = [];
|
|
7336
|
+
for (const m of messages) {
|
|
7337
|
+
const role = m.role.padEnd(10, " ");
|
|
7338
|
+
if (typeof m.content === "string") {
|
|
7339
|
+
lines.push(`[${role}]: ${m.content.slice(0, 500)}`);
|
|
7340
|
+
} else if (Array.isArray(m.content)) {
|
|
7341
|
+
const textParts = m.content.filter(isTextBlock).map((b) => b.text);
|
|
7342
|
+
if (textParts.length > 0) {
|
|
7343
|
+
lines.push(`[${role}]: ${textParts.join(" ").slice(0, 500)}`);
|
|
7344
|
+
}
|
|
7345
|
+
}
|
|
7346
|
+
}
|
|
7347
|
+
return lines.join("\n");
|
|
7348
|
+
}
|
|
5074
7349
|
};
|
|
5075
7350
|
|
|
5076
7351
|
// src/execution/model-runtime.ts
|
|
@@ -5751,15 +8026,17 @@ var MAX_FILES = 64;
|
|
|
5751
8026
|
var MAX_NEXT_STEPS = 12;
|
|
5752
8027
|
var MAX_ITEM_CHARS = 500;
|
|
5753
8028
|
function normalizeSubagentStructuredReport(value) {
|
|
5754
|
-
if (!
|
|
8029
|
+
if (!isRecord4(value)) return void 0;
|
|
5755
8030
|
const summary = nonEmptyString(value["summary"]);
|
|
5756
8031
|
const findings = stringArray(value["findings"]);
|
|
5757
8032
|
const filesExamined = stringArray(value["files_examined"]);
|
|
5758
8033
|
const nextSteps = stringArray(value["suggested_next_steps"]);
|
|
5759
8034
|
const confidence = value["confidence"];
|
|
8035
|
+
const completion = value["completion"];
|
|
8036
|
+
const remainingWork = optionalString2(value["remaining_work"]);
|
|
5760
8037
|
if (!summary || summary.length > MAX_SUMMARY_CHARS || !findings || findings.length > MAX_FINDINGS || !filesExamined || filesExamined.length > MAX_FILES || !nextSteps || nextSteps.length > MAX_NEXT_STEPS || [...findings, ...filesExamined, ...nextSteps].some(
|
|
5761
8038
|
(item) => item.trim().length > MAX_ITEM_CHARS
|
|
5762
|
-
) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
|
8039
|
+
) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || completion !== void 0 && completion !== "complete" && completion !== "partial" || completion === "partial" && !remainingWork || remainingWork !== void 0 && remainingWork.length > MAX_SUMMARY_CHARS) {
|
|
5763
8040
|
return void 0;
|
|
5764
8041
|
}
|
|
5765
8042
|
const report = {
|
|
@@ -5767,19 +8044,24 @@ function normalizeSubagentStructuredReport(value) {
|
|
|
5767
8044
|
findings: findings.map((item) => item.trim()).filter(Boolean),
|
|
5768
8045
|
files_examined: filesExamined.map((item) => item.trim()).filter(Boolean),
|
|
5769
8046
|
confidence,
|
|
5770
|
-
suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean)
|
|
8047
|
+
suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean),
|
|
8048
|
+
...completion === "complete" || completion === "partial" ? { completion } : {},
|
|
8049
|
+
...remainingWork ? { remaining_work: remainingWork } : {}
|
|
5771
8050
|
};
|
|
5772
8051
|
return JSON.stringify(report).length <= MAX_SUBAGENT_STRUCTURED_REPORT_CHARS ? report : void 0;
|
|
5773
8052
|
}
|
|
5774
8053
|
function readSubagentStructuredReport(ctx) {
|
|
5775
8054
|
return normalizeSubagentStructuredReport(ctx?.meta?.[SUBAGENT_STRUCTURED_REPORT_META_KEY]);
|
|
5776
8055
|
}
|
|
5777
|
-
function
|
|
8056
|
+
function isRecord4(value) {
|
|
5778
8057
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5779
8058
|
}
|
|
5780
8059
|
function nonEmptyString(value) {
|
|
5781
8060
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
5782
8061
|
}
|
|
8062
|
+
function optionalString2(value) {
|
|
8063
|
+
return value === void 0 ? void 0 : nonEmptyString(value);
|
|
8064
|
+
}
|
|
5783
8065
|
function stringArray(value) {
|
|
5784
8066
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
5785
8067
|
}
|
|
@@ -6062,7 +8344,7 @@ var TOOLS = {
|
|
|
6062
8344
|
};
|
|
6063
8345
|
|
|
6064
8346
|
// src/coordination/agents/agent-prompts.ts
|
|
6065
|
-
import { readFileSync as
|
|
8347
|
+
import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
6066
8348
|
import * as os2 from "node:os";
|
|
6067
8349
|
import * as path6 from "node:path";
|
|
6068
8350
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -6077,7 +8359,7 @@ function agentPrompt(id) {
|
|
|
6077
8359
|
let resolved = "";
|
|
6078
8360
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
6079
8361
|
try {
|
|
6080
|
-
resolved =
|
|
8362
|
+
resolved = readFileSync3(path6.join(dir, fileName), "utf8").trimEnd();
|
|
6081
8363
|
break;
|
|
6082
8364
|
} catch {
|
|
6083
8365
|
}
|
|
@@ -6107,7 +8389,7 @@ function agentPromptDirCandidates(envDir) {
|
|
|
6107
8389
|
}
|
|
6108
8390
|
function isDirectory2(candidate) {
|
|
6109
8391
|
try {
|
|
6110
|
-
return
|
|
8392
|
+
return statSync3(candidate).isDirectory();
|
|
6111
8393
|
} catch {
|
|
6112
8394
|
return false;
|
|
6113
8395
|
}
|
|
@@ -7769,76 +10051,45 @@ function providerErrorToSubagentError(err, message, cause) {
|
|
|
7769
10051
|
}
|
|
7770
10052
|
|
|
7771
10053
|
// src/coordination/fleet.ts
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
var
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
prompt: agentPrompt("bug-hunter")
|
|
7788
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
7789
|
-
};
|
|
7790
|
-
var REFACTOR_PLANNER_AGENT = {
|
|
7791
|
-
id: "refactor-planner",
|
|
7792
|
-
name: "Refactor Planner",
|
|
7793
|
-
role: "refactor-planner",
|
|
7794
|
-
prompt: agentPrompt("refactor-planner")
|
|
7795
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
7796
|
-
};
|
|
7797
|
-
var SECURITY_SCANNER_AGENT = {
|
|
7798
|
-
id: "security-scanner",
|
|
7799
|
-
name: "Security Scanner",
|
|
7800
|
-
role: "security-scanner",
|
|
7801
|
-
prompt: agentPrompt("security-scanner")
|
|
7802
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
7803
|
-
};
|
|
7804
|
-
var SHADOW_AGENT = {
|
|
7805
|
-
id: "shadow-agent",
|
|
7806
|
-
name: "Shadow",
|
|
7807
|
-
role: "shadow-agent",
|
|
7808
|
-
prompt: agentPrompt("shadow-agent")
|
|
7809
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
7810
|
-
};
|
|
7811
|
-
var CRITIC_AGENT = {
|
|
7812
|
-
id: "critic",
|
|
7813
|
-
name: "Critic",
|
|
7814
|
-
role: "critic",
|
|
7815
|
-
prompt: agentPrompt("critic")
|
|
7816
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
7817
|
-
};
|
|
10054
|
+
function defineAgent(id, name, promptRole = id, provider) {
|
|
10055
|
+
return {
|
|
10056
|
+
id,
|
|
10057
|
+
name,
|
|
10058
|
+
role: id,
|
|
10059
|
+
prompt: agentPrompt(promptRole),
|
|
10060
|
+
...provider === void 0 ? {} : { provider }
|
|
10061
|
+
};
|
|
10062
|
+
}
|
|
10063
|
+
var AUDIT_LOG_AGENT = defineAgent("audit-log", "Audit Log");
|
|
10064
|
+
var BUG_HUNTER_AGENT = defineAgent("bug-hunter", "Bug Hunter");
|
|
10065
|
+
var REFACTOR_PLANNER_AGENT = defineAgent("refactor-planner", "Refactor Planner");
|
|
10066
|
+
var SECURITY_SCANNER_AGENT = defineAgent("security-scanner", "Security Scanner");
|
|
10067
|
+
var SHADOW_AGENT = defineAgent("shadow-agent", "Shadow");
|
|
10068
|
+
var CRITIC_AGENT = defineAgent("critic", "Critic");
|
|
7818
10069
|
var FLEET_ROSTER = {
|
|
7819
10070
|
"audit-log": AUDIT_LOG_AGENT,
|
|
7820
10071
|
"bug-hunter": BUG_HUNTER_AGENT,
|
|
7821
10072
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
7822
10073
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
7823
|
-
|
|
10074
|
+
critic: CRITIC_AGENT,
|
|
7824
10075
|
"shadow-agent": SHADOW_AGENT,
|
|
7825
10076
|
...Object.fromEntries(
|
|
7826
10077
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
7827
10078
|
)
|
|
7828
10079
|
};
|
|
7829
|
-
var DEFAULT_IDLE_TIMEOUT_MS =
|
|
10080
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
7830
10081
|
var FLEET_ROSTER_BUDGETS = {
|
|
7831
|
-
"audit-log": { timeoutMs:
|
|
7832
|
-
"bug-hunter": { timeoutMs:
|
|
7833
|
-
"refactor-planner": { timeoutMs:
|
|
7834
|
-
"security-scanner": { timeoutMs:
|
|
7835
|
-
|
|
10082
|
+
"audit-log": { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2500, maxToolCalls: 7500 },
|
|
10083
|
+
"bug-hunter": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
|
|
10084
|
+
"refactor-planner": { timeoutMs: 4 * 60 * 60 * 1e3, maxIterations: 3e3, maxToolCalls: 9e3 },
|
|
10085
|
+
"security-scanner": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
|
|
10086
|
+
critic: { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2e3, maxToolCalls: 6e3 },
|
|
7836
10087
|
"shadow-agent": {
|
|
7837
10088
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
7838
|
-
maxIterations:
|
|
7839
|
-
maxToolCalls:
|
|
7840
|
-
maxTokens:
|
|
7841
|
-
maxCostUsd:
|
|
10089
|
+
maxIterations: 1e3,
|
|
10090
|
+
maxToolCalls: 2500,
|
|
10091
|
+
maxTokens: 3e4,
|
|
10092
|
+
maxCostUsd: 0.5
|
|
7842
10093
|
},
|
|
7843
10094
|
...Object.fromEntries(
|
|
7844
10095
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
@@ -7846,8 +10097,8 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
7846
10097
|
};
|
|
7847
10098
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
7848
10099
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
7849
|
-
maxIterations:
|
|
7850
|
-
maxToolCalls:
|
|
10100
|
+
maxIterations: 2500,
|
|
10101
|
+
maxToolCalls: 7500
|
|
7851
10102
|
};
|
|
7852
10103
|
function applyRosterBudget(cfg) {
|
|
7853
10104
|
const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
|
|
@@ -7869,41 +10120,11 @@ function applyRosterBudget(cfg) {
|
|
|
7869
10120
|
};
|
|
7870
10121
|
}
|
|
7871
10122
|
var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
|
|
7872
|
-
var CLINE_AGENT =
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
provider: "acp"
|
|
7878
|
-
};
|
|
7879
|
-
var GEMINI_CLI_AGENT = {
|
|
7880
|
-
id: "gemini-cli",
|
|
7881
|
-
name: "Gemini CLI",
|
|
7882
|
-
role: "gemini-cli",
|
|
7883
|
-
prompt: agentPrompt("acp-gemini-cli"),
|
|
7884
|
-
provider: "acp"
|
|
7885
|
-
};
|
|
7886
|
-
var COPILOT_AGENT = {
|
|
7887
|
-
id: "copilot",
|
|
7888
|
-
name: "GitHub Copilot",
|
|
7889
|
-
role: "copilot",
|
|
7890
|
-
prompt: agentPrompt("acp-copilot"),
|
|
7891
|
-
provider: "acp"
|
|
7892
|
-
};
|
|
7893
|
-
var OPENHANDS_AGENT = {
|
|
7894
|
-
id: "openhands",
|
|
7895
|
-
name: "OpenHands",
|
|
7896
|
-
role: "openhands",
|
|
7897
|
-
prompt: agentPrompt("acp-openhands"),
|
|
7898
|
-
provider: "acp"
|
|
7899
|
-
};
|
|
7900
|
-
var GOOSE_AGENT = {
|
|
7901
|
-
id: "goose",
|
|
7902
|
-
name: "Goose",
|
|
7903
|
-
role: "goose",
|
|
7904
|
-
prompt: agentPrompt("acp-goose"),
|
|
7905
|
-
provider: "acp"
|
|
7906
|
-
};
|
|
10123
|
+
var CLINE_AGENT = defineAgent("cline", "Cline", "acp-cline", "acp");
|
|
10124
|
+
var GEMINI_CLI_AGENT = defineAgent("gemini-cli", "Gemini CLI", "acp-gemini-cli", "acp");
|
|
10125
|
+
var COPILOT_AGENT = defineAgent("copilot", "GitHub Copilot", "acp-copilot", "acp");
|
|
10126
|
+
var OPENHANDS_AGENT = defineAgent("openhands", "OpenHands", "acp-openhands", "acp");
|
|
10127
|
+
var GOOSE_AGENT = defineAgent("goose", "Goose", "acp-goose", "acp");
|
|
7907
10128
|
var ACP_AGENTS = [
|
|
7908
10129
|
CLINE_AGENT,
|
|
7909
10130
|
GEMINI_CLI_AGENT,
|
|
@@ -7911,11 +10132,14 @@ var ACP_AGENTS = [
|
|
|
7911
10132
|
OPENHANDS_AGENT,
|
|
7912
10133
|
GOOSE_AGENT
|
|
7913
10134
|
];
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
10135
|
+
var ACP_AGENT_BUDGET = {
|
|
10136
|
+
timeoutMs: 10 * 60 * 60 * 1e3,
|
|
10137
|
+
maxIterations: 8e3,
|
|
10138
|
+
maxToolCalls: 2e4
|
|
10139
|
+
};
|
|
10140
|
+
for (const agent of ACP_AGENTS) {
|
|
10141
|
+
FLEET_ROSTER_BUDGETS[agent.role] = { ...ACP_AGENT_BUDGET };
|
|
10142
|
+
}
|
|
7919
10143
|
var FLEET_ROSTER_WITHACP = {
|
|
7920
10144
|
...FLEET_ROSTER,
|
|
7921
10145
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
@@ -8909,6 +11133,27 @@ var ParallelEternalEngine = class {
|
|
|
8909
11133
|
agentFactory;
|
|
8910
11134
|
dispatchEnabled;
|
|
8911
11135
|
dispatchClassifier;
|
|
11136
|
+
/**
|
|
11137
|
+
* Emit a structured error. Uses the configured Logger when available;
|
|
11138
|
+
* falls back to console.error(JSON) so events are never silently dropped.
|
|
11139
|
+
*/
|
|
11140
|
+
logError(msg, ctx) {
|
|
11141
|
+
if (this.opts.logger) {
|
|
11142
|
+
this.opts.logger.error(msg, ctx);
|
|
11143
|
+
} else {
|
|
11144
|
+
console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
11145
|
+
}
|
|
11146
|
+
}
|
|
11147
|
+
/**
|
|
11148
|
+
* Load the goal file with structured logging for parse/schema warnings.
|
|
11149
|
+
*/
|
|
11150
|
+
loadGoal() {
|
|
11151
|
+
return loadGoal(
|
|
11152
|
+
this.goalPath,
|
|
11153
|
+
this.opts.events,
|
|
11154
|
+
this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
|
|
11155
|
+
);
|
|
11156
|
+
}
|
|
8912
11157
|
get currentState() {
|
|
8913
11158
|
return this.state;
|
|
8914
11159
|
}
|
|
@@ -8921,13 +11166,10 @@ var ParallelEternalEngine = class {
|
|
|
8921
11166
|
stop() {
|
|
8922
11167
|
this.stopRequested = true;
|
|
8923
11168
|
void this.persistState("stopped").catch((err) => {
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
event: "engine.persist_state_failed",
|
|
8927
|
-
|
|
8928
|
-
context: { expectedState: "stopped" },
|
|
8929
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8930
|
-
}));
|
|
11169
|
+
this.logError(
|
|
11170
|
+
"Engine persist state failed",
|
|
11171
|
+
{ event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
|
|
11172
|
+
);
|
|
8931
11173
|
});
|
|
8932
11174
|
this.state = "stopped";
|
|
8933
11175
|
}
|
|
@@ -8981,7 +11223,7 @@ var ParallelEternalEngine = class {
|
|
|
8981
11223
|
this.opts.onStage?.(stage);
|
|
8982
11224
|
};
|
|
8983
11225
|
this.iterations++;
|
|
8984
|
-
const goal = await loadGoal(
|
|
11226
|
+
const goal = await this.loadGoal();
|
|
8985
11227
|
if (!goal) {
|
|
8986
11228
|
this.stopRequested = true;
|
|
8987
11229
|
emit({ phase: "stopped" });
|
|
@@ -9125,13 +11367,10 @@ ${personaLine}Task: ${task}
|
|
|
9125
11367
|
taskIds.push(taskId);
|
|
9126
11368
|
await coordinator.assign(spec);
|
|
9127
11369
|
} catch (err) {
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
event: "parallel_engine.spawn_failed",
|
|
9131
|
-
|
|
9132
|
-
context: { slot: i, task, subagentId },
|
|
9133
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9134
|
-
}));
|
|
11370
|
+
this.logError(
|
|
11371
|
+
"Parallel spawn failed",
|
|
11372
|
+
{ event: "parallel_engine.spawn_failed", message: toErrorMessage(err), context: { slot: i, task, subagentId } }
|
|
11373
|
+
);
|
|
9135
11374
|
}
|
|
9136
11375
|
})()
|
|
9137
11376
|
);
|
|
@@ -9157,13 +11396,10 @@ ${personaLine}Task: ${task}
|
|
|
9157
11396
|
clearTimeout(timer);
|
|
9158
11397
|
}
|
|
9159
11398
|
} catch (err) {
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
event: "parallel_engine.brainstorm_results_failed",
|
|
9163
|
-
|
|
9164
|
-
context: { slotCount, taskIds },
|
|
9165
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9166
|
-
}));
|
|
11399
|
+
this.logError(
|
|
11400
|
+
"Brainstorm results failed",
|
|
11401
|
+
{ event: "parallel_engine.brainstorm_results_failed", message: toErrorMessage(err), context: { slotCount, taskIds } }
|
|
11402
|
+
);
|
|
9167
11403
|
results = coordinator.results().slice(-taskIds.length);
|
|
9168
11404
|
}
|
|
9169
11405
|
await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
|
|
@@ -9198,13 +11434,10 @@ ${personaLine}Task: ${task}
|
|
|
9198
11434
|
}
|
|
9199
11435
|
}
|
|
9200
11436
|
} catch (err) {
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
event: "parallel_engine.git_status_failed",
|
|
9204
|
-
|
|
9205
|
-
context: { projectRoot: this.opts.projectRoot },
|
|
9206
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9207
|
-
}));
|
|
11437
|
+
this.logError(
|
|
11438
|
+
"Git status failed",
|
|
11439
|
+
{ event: "parallel_engine.git_status_failed", message: toErrorMessage(err), context: { projectRoot: this.opts.projectRoot } }
|
|
11440
|
+
);
|
|
9208
11441
|
}
|
|
9209
11442
|
}
|
|
9210
11443
|
if (tasks.length < this.slots) {
|
|
@@ -9285,7 +11518,7 @@ ${lastFew}` : "No prior iterations.",
|
|
|
9285
11518
|
// Helpers
|
|
9286
11519
|
// -------------------------------------------------------------------------
|
|
9287
11520
|
async appendIterationEntry(entry) {
|
|
9288
|
-
const current = await loadGoal(
|
|
11521
|
+
const current = await this.loadGoal();
|
|
9289
11522
|
if (!current) return;
|
|
9290
11523
|
const updated = appendJournal(current, entry);
|
|
9291
11524
|
await saveGoal(this.goalPath, updated, this.opts.events);
|
|
@@ -9300,7 +11533,7 @@ ${lastFew}` : "No prior iterations.",
|
|
|
9300
11533
|
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
9301
11534
|
}
|
|
9302
11535
|
async persistState(state) {
|
|
9303
|
-
const current = await loadGoal(
|
|
11536
|
+
const current = await this.loadGoal();
|
|
9304
11537
|
if (!current) return;
|
|
9305
11538
|
if (current.engineState === state) return;
|
|
9306
11539
|
await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
|
|
@@ -9406,6 +11639,7 @@ var LLMSelector = class {
|
|
|
9406
11639
|
maxContextTokens;
|
|
9407
11640
|
systemPrompt;
|
|
9408
11641
|
maxOutputTokens;
|
|
11642
|
+
oneShotOrchestrator;
|
|
9409
11643
|
constructor(opts) {
|
|
9410
11644
|
this.provider = opts.provider;
|
|
9411
11645
|
this.model = opts.model ?? "unknown";
|
|
@@ -9417,6 +11651,7 @@ var LLMSelector = class {
|
|
|
9417
11651
|
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
9418
11652
|
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
9419
11653
|
this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
|
|
11654
|
+
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
9420
11655
|
}
|
|
9421
11656
|
async select(messages, maxToKeep) {
|
|
9422
11657
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
@@ -9440,12 +11675,23 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
9440
11675
|
let raw;
|
|
9441
11676
|
const ac = new AbortController();
|
|
9442
11677
|
try {
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
11678
|
+
if (this.oneShotOrchestrator) {
|
|
11679
|
+
const result = await this.oneShotOrchestrator.call({
|
|
11680
|
+
system: systemText + budgetInstruction,
|
|
11681
|
+
userPrompt: historyText,
|
|
11682
|
+
maxTokens: this.maxOutputTokens,
|
|
11683
|
+
timeoutMs: 3e4
|
|
11684
|
+
});
|
|
11685
|
+
if (result.error) throw new Error(result.error);
|
|
11686
|
+
raw = result.text;
|
|
11687
|
+
} else {
|
|
11688
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
11689
|
+
const res = await this.provider.complete(req, {
|
|
11690
|
+
signal: AbortSignal.any([ac.signal, timeoutSignal])
|
|
11691
|
+
});
|
|
11692
|
+
const textBlocks = res.content.filter(isTextBlock);
|
|
11693
|
+
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
11694
|
+
}
|
|
9449
11695
|
} catch (err) {
|
|
9450
11696
|
if (err instanceof Error) {
|
|
9451
11697
|
console.warn(
|
|
@@ -9565,12 +11811,12 @@ var SelectiveCompactor = class {
|
|
|
9565
11811
|
constructor(opts) {
|
|
9566
11812
|
this.provider = opts.provider;
|
|
9567
11813
|
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
|
|
9568
|
-
this.warnThreshold = opts.warnThreshold ?? 0.
|
|
9569
|
-
this.softThreshold = opts.softThreshold ?? 0.
|
|
9570
|
-
this.hardThreshold = opts.hardThreshold ?? 0.
|
|
11814
|
+
this.warnThreshold = opts.warnThreshold ?? 0.5;
|
|
11815
|
+
this.softThreshold = opts.softThreshold ?? 0.65;
|
|
11816
|
+
this.hardThreshold = opts.hardThreshold ?? 0.8;
|
|
9571
11817
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
9572
11818
|
this.preserveK = opts.preserveK ?? 4;
|
|
9573
|
-
this.eliseThreshold = opts.eliseThreshold ??
|
|
11819
|
+
this.eliseThreshold = opts.eliseThreshold ?? 300;
|
|
9574
11820
|
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
|
|
9575
11821
|
if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
9576
11822
|
console.warn(
|
|
@@ -9763,7 +12009,7 @@ Summarize the following message range:`;
|
|
|
9763
12009
|
};
|
|
9764
12010
|
|
|
9765
12011
|
// src/execution/skill-loader.ts
|
|
9766
|
-
import * as
|
|
12012
|
+
import * as fs5 from "node:fs/promises";
|
|
9767
12013
|
import * as path7 from "node:path";
|
|
9768
12014
|
|
|
9769
12015
|
// src/skills/foreign-sources.ts
|
|
@@ -9994,7 +12240,7 @@ async function entryIsDirectory(dir, entry) {
|
|
|
9994
12240
|
if (entry.isDirectory()) return true;
|
|
9995
12241
|
if (entry.isSymbolicLink()) {
|
|
9996
12242
|
try {
|
|
9997
|
-
return (await
|
|
12243
|
+
return (await fs5.stat(path7.join(dir, entry.name))).isDirectory();
|
|
9998
12244
|
} catch {
|
|
9999
12245
|
return false;
|
|
10000
12246
|
}
|
|
@@ -10037,12 +12283,12 @@ var DefaultSkillLoader = class {
|
|
|
10037
12283
|
const seen = /* @__PURE__ */ new Set();
|
|
10038
12284
|
for (const { dir, source, originTool } of this.dirs) {
|
|
10039
12285
|
try {
|
|
10040
|
-
const entries = await
|
|
12286
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
10041
12287
|
for (const e of entries) {
|
|
10042
12288
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
10043
12289
|
const skillFile = path7.join(dir, e.name, "SKILL.md");
|
|
10044
12290
|
try {
|
|
10045
|
-
const raw = await
|
|
12291
|
+
const raw = await fs5.readFile(skillFile, "utf8");
|
|
10046
12292
|
const fm = parseSkillFrontmatter(raw);
|
|
10047
12293
|
if (!fm.name || !fm.description) continue;
|
|
10048
12294
|
if (!isValidSkillNameFormat(fm.name)) continue;
|
|
@@ -10114,7 +12360,7 @@ var DefaultSkillLoader = class {
|
|
|
10114
12360
|
if (cached !== void 0) return cached;
|
|
10115
12361
|
const m = await this.find(name);
|
|
10116
12362
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
10117
|
-
const body = await
|
|
12363
|
+
const body = await fs5.readFile(m.path, "utf8");
|
|
10118
12364
|
this.bodyCache.set(key, body);
|
|
10119
12365
|
return body;
|
|
10120
12366
|
}
|
|
@@ -10127,9 +12373,9 @@ var DefaultSkillLoader = class {
|
|
|
10127
12373
|
const savePath = path7.join(path7.dirname(m.path), "SKILL.save.md");
|
|
10128
12374
|
let result;
|
|
10129
12375
|
try {
|
|
10130
|
-
result = await
|
|
12376
|
+
result = await fs5.readFile(savePath, "utf8");
|
|
10131
12377
|
} catch {
|
|
10132
|
-
const full = await
|
|
12378
|
+
const full = await fs5.readFile(m.path, "utf8");
|
|
10133
12379
|
const body = stripFrontmatter2(full);
|
|
10134
12380
|
const compact = compactSkillBody(body);
|
|
10135
12381
|
if (compact) {
|
|
@@ -10157,11 +12403,11 @@ function parseDescriptionFromText(desc) {
|
|
|
10157
12403
|
}
|
|
10158
12404
|
|
|
10159
12405
|
// src/execution/prompt-loader.ts
|
|
10160
|
-
import * as
|
|
12406
|
+
import * as fs7 from "node:fs/promises";
|
|
10161
12407
|
import * as path9 from "node:path";
|
|
10162
12408
|
|
|
10163
12409
|
// src/storage/prompt-store.ts
|
|
10164
|
-
import * as
|
|
12410
|
+
import * as fs6 from "node:fs/promises";
|
|
10165
12411
|
import * as path8 from "node:path";
|
|
10166
12412
|
var SCHEMA_VERSION = 2;
|
|
10167
12413
|
function migratePromptEntry(raw) {
|
|
@@ -10223,12 +12469,12 @@ var DefaultPromptStore = class {
|
|
|
10223
12469
|
await ensureDir(this.dir);
|
|
10224
12470
|
const entries = [];
|
|
10225
12471
|
try {
|
|
10226
|
-
const files = await
|
|
12472
|
+
const files = await fs6.readdir(this.dir);
|
|
10227
12473
|
for (const file of files) {
|
|
10228
12474
|
if (!file.endsWith(".json")) continue;
|
|
10229
12475
|
try {
|
|
10230
12476
|
const raw = JSON.parse(
|
|
10231
|
-
await
|
|
12477
|
+
await fs6.readFile(path8.join(this.dir, file), "utf8")
|
|
10232
12478
|
);
|
|
10233
12479
|
const migrated = migratePromptEntry(raw.entry);
|
|
10234
12480
|
if (migrated) entries.push(migrated);
|
|
@@ -10244,7 +12490,7 @@ var DefaultPromptStore = class {
|
|
|
10244
12490
|
async get(id) {
|
|
10245
12491
|
const file = path8.join(this.dir, `${id}.json`);
|
|
10246
12492
|
try {
|
|
10247
|
-
const raw = JSON.parse(await
|
|
12493
|
+
const raw = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
10248
12494
|
return migratePromptEntry(raw.entry);
|
|
10249
12495
|
} catch {
|
|
10250
12496
|
return null;
|
|
@@ -10259,7 +12505,7 @@ var DefaultPromptStore = class {
|
|
|
10259
12505
|
async delete(id) {
|
|
10260
12506
|
const file = path8.join(this.dir, `${id}.json`);
|
|
10261
12507
|
try {
|
|
10262
|
-
await
|
|
12508
|
+
await fs6.unlink(file);
|
|
10263
12509
|
return true;
|
|
10264
12510
|
} catch {
|
|
10265
12511
|
return false;
|
|
@@ -10459,7 +12705,7 @@ var DefaultPromptLoader = class {
|
|
|
10459
12705
|
const files = await walkJson(dir);
|
|
10460
12706
|
for (const file of files) {
|
|
10461
12707
|
try {
|
|
10462
|
-
const parsed = JSON.parse(await
|
|
12708
|
+
const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
10463
12709
|
const migrated = migratePromptEntry(parsed);
|
|
10464
12710
|
if (migrated) out.push({ ...migrated, source: "builtin" });
|
|
10465
12711
|
} catch {
|
|
@@ -10473,7 +12719,7 @@ async function walkJson(dir) {
|
|
|
10473
12719
|
const out = [];
|
|
10474
12720
|
let entries;
|
|
10475
12721
|
try {
|
|
10476
|
-
entries = await
|
|
12722
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
10477
12723
|
} catch {
|
|
10478
12724
|
return out;
|
|
10479
12725
|
}
|
|
@@ -10650,7 +12896,8 @@ var ProviderBackedCompactor = class {
|
|
|
10650
12896
|
}
|
|
10651
12897
|
return new IntelligentCompactor({
|
|
10652
12898
|
...common,
|
|
10653
|
-
summarizerModel: this.opts.summarizerModel
|
|
12899
|
+
summarizerModel: this.opts.summarizerModel,
|
|
12900
|
+
oneShotOrchestrator: this.opts.oneShotOrchestrator
|
|
10654
12901
|
});
|
|
10655
12902
|
}
|
|
10656
12903
|
};
|
|
@@ -10665,9 +12912,12 @@ function readPolicy(ctx) {
|
|
|
10665
12912
|
// src/execution/tool-executor.ts
|
|
10666
12913
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
10667
12914
|
import { isDeepStrictEqual } from "node:util";
|
|
10668
|
-
import * as
|
|
12915
|
+
import * as fs8 from "node:fs/promises";
|
|
10669
12916
|
import * as path10 from "node:path";
|
|
10670
12917
|
|
|
12918
|
+
// src/types/tool-executor.ts
|
|
12919
|
+
var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
|
|
12920
|
+
|
|
10671
12921
|
// src/types/tool-markers.ts
|
|
10672
12922
|
var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
|
|
10673
12923
|
|
|
@@ -10784,6 +13034,18 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
10784
13034
|
* Returns the execution results and the remaining output budget.
|
|
10785
13035
|
*/
|
|
10786
13036
|
async executeBatch(toolUses, ctx, strategy) {
|
|
13037
|
+
return this.withGovernedExecutionBridge(
|
|
13038
|
+
ctx,
|
|
13039
|
+
() => this.executeBatchInternal(toolUses, ctx, strategy)
|
|
13040
|
+
);
|
|
13041
|
+
}
|
|
13042
|
+
/**
|
|
13043
|
+
* Internal batch implementation. Nested meta-tool calls enter here through
|
|
13044
|
+
* the governed bridge so they traverse the same validation, hooks,
|
|
13045
|
+
* permission/capability, timeout, scrubbing, and audit path without
|
|
13046
|
+
* replacing the bridge that is already active for the outer call.
|
|
13047
|
+
*/
|
|
13048
|
+
async executeBatchInternal(toolUses, ctx, strategy) {
|
|
10787
13049
|
let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
|
|
10788
13050
|
const runOne = async (use0) => {
|
|
10789
13051
|
const start = Date.now();
|
|
@@ -10954,8 +13216,9 @@ ${errorDetails}`,
|
|
|
10954
13216
|
);
|
|
10955
13217
|
if (post.additionalContext) {
|
|
10956
13218
|
if (post.contextAs === "separate") {
|
|
10957
|
-
|
|
10958
|
-
|
|
13219
|
+
ctx.pendingPostToolContext = ctx.pendingPostToolContext ? `${ctx.pendingPostToolContext}
|
|
13220
|
+
|
|
13221
|
+
${post.additionalContext}` : post.additionalContext;
|
|
10959
13222
|
} else {
|
|
10960
13223
|
const appended = `
|
|
10961
13224
|
|
|
@@ -11055,14 +13318,56 @@ ${post.additionalContext}`;
|
|
|
11055
13318
|
};
|
|
11056
13319
|
}
|
|
11057
13320
|
/**
|
|
11058
|
-
*
|
|
11059
|
-
*
|
|
11060
|
-
*
|
|
11061
|
-
*
|
|
13321
|
+
* Install the executor-owned bridge used by meta-tools for nested calls.
|
|
13322
|
+
* The bridge deliberately returns the governed, scrubbed ToolResultBlock
|
|
13323
|
+
* content rather than the raw tool value. This prevents a meta-tool from
|
|
13324
|
+
* bypassing output controls while preserving structured success/failure.
|
|
13325
|
+
*/
|
|
13326
|
+
async withGovernedExecutionBridge(ctx, run) {
|
|
13327
|
+
const previous = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
13328
|
+
if (typeof previous === "function") return run();
|
|
13329
|
+
const bridge = async (toolName, input) => {
|
|
13330
|
+
const nestedUse = {
|
|
13331
|
+
type: "tool_use",
|
|
13332
|
+
id: `nested-${randomUUID3()}`,
|
|
13333
|
+
name: toolName,
|
|
13334
|
+
input
|
|
13335
|
+
};
|
|
13336
|
+
const batch = await this.executeBatchInternal([nestedUse], ctx, "sequential");
|
|
13337
|
+
const output = batch.outputs[0];
|
|
13338
|
+
if (!output) {
|
|
13339
|
+
return { success: false, error: `tool "${toolName}" produced no result` };
|
|
13340
|
+
}
|
|
13341
|
+
if (output.result.type === "tool_confirm_pending") {
|
|
13342
|
+
return {
|
|
13343
|
+
success: false,
|
|
13344
|
+
error: `tool "${toolName}" requires separate confirmation; call it directly`
|
|
13345
|
+
};
|
|
13346
|
+
}
|
|
13347
|
+
if (output.result.is_error) {
|
|
13348
|
+
return { success: false, error: String(output.result.content) };
|
|
13349
|
+
}
|
|
13350
|
+
return { success: true, result: output.result.content };
|
|
13351
|
+
};
|
|
13352
|
+
ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY] = bridge;
|
|
13353
|
+
try {
|
|
13354
|
+
return await run();
|
|
13355
|
+
} finally {
|
|
13356
|
+
if (previous === void 0) delete ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
13357
|
+
else ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY] = previous;
|
|
13358
|
+
}
|
|
13359
|
+
}
|
|
13360
|
+
/**
|
|
13361
|
+
* Execute a single tool with timeout and output capping after its permission
|
|
13362
|
+
* decision has already been resolved by executeBatch/the confirmation flow.
|
|
13363
|
+
* Also installs the governed bridge because confirmed tools are re-run
|
|
13364
|
+
* through this method after a pending confirmation.
|
|
11062
13365
|
*/
|
|
11063
13366
|
async executeTool(tool, use, ctx, budget) {
|
|
11064
|
-
|
|
11065
|
-
|
|
13367
|
+
return this.withGovernedExecutionBridge(ctx, async () => {
|
|
13368
|
+
const text = await this.produceToolOutput(tool, use, ctx, budget);
|
|
13369
|
+
return this.settleToolOutput(tool, use, text, budget);
|
|
13370
|
+
});
|
|
11066
13371
|
}
|
|
11067
13372
|
/**
|
|
11068
13373
|
* Async "produce" phase: run the tool, serialize, scrub, and spill an
|
|
@@ -11121,12 +13426,12 @@ ${post.additionalContext}`;
|
|
|
11121
13426
|
if (parentSignal.reason instanceof Error) throw parentSignal.reason;
|
|
11122
13427
|
throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
|
|
11123
13428
|
}
|
|
11124
|
-
const
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
13429
|
+
const combined = tool.managesOwnTimeout ? parentSignal : AbortSignal.any([
|
|
13430
|
+
parentSignal,
|
|
13431
|
+
AbortSignal.timeout(
|
|
13432
|
+
clampTimeoutMs(tool.timeoutMs ?? this.iterationTimeoutMs, this.maxToolTimeoutMs)
|
|
13433
|
+
)
|
|
13434
|
+
]);
|
|
11130
13435
|
let output;
|
|
11131
13436
|
const toolPromise = typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
|
|
11132
13437
|
toolPromise.catch(() => {
|
|
@@ -11417,11 +13722,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
11417
13722
|
}
|
|
11418
13723
|
try {
|
|
11419
13724
|
const dir = path10.join(wstackGlobalRoot(), "tool-output");
|
|
11420
|
-
await
|
|
13725
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
11421
13726
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
11422
13727
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11423
13728
|
const filePath = path10.join(dir, `${stamp}-${safeTool}-${randomUUID3()}.log`);
|
|
11424
|
-
await
|
|
13729
|
+
await fs8.writeFile(filePath, content, "utf8");
|
|
11425
13730
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
11426
13731
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
11427
13732
|
const previewBytes = Math.min(
|
|
@@ -11469,9 +13774,233 @@ function sliceUtf8Suffix(text, maxBytes) {
|
|
|
11469
13774
|
if (first >= 56320 && first <= 57343) start++;
|
|
11470
13775
|
return text.slice(start);
|
|
11471
13776
|
}
|
|
13777
|
+
|
|
13778
|
+
// src/execution/one-shot-llm.ts
|
|
13779
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
13780
|
+
var DEFAULT_MAX_TOKENS = 1024;
|
|
13781
|
+
var OneShotOrchestrator = class {
|
|
13782
|
+
opts;
|
|
13783
|
+
constructor(opts) {
|
|
13784
|
+
this.opts = opts;
|
|
13785
|
+
}
|
|
13786
|
+
/**
|
|
13787
|
+
* Make a one-shot LLM call. Resolves provider+model, applies fallback
|
|
13788
|
+
* chain on transient errors, and returns a structured result.
|
|
13789
|
+
*
|
|
13790
|
+
* Never throws — all errors are captured in `OneShotLLMResult.error`.
|
|
13791
|
+
*/
|
|
13792
|
+
async call(input) {
|
|
13793
|
+
const startedAt = performance.now();
|
|
13794
|
+
const config = this.opts.getConfig();
|
|
13795
|
+
const target = this.resolveTarget(input, config);
|
|
13796
|
+
if (!target) {
|
|
13797
|
+
return {
|
|
13798
|
+
text: "",
|
|
13799
|
+
model: input.model ?? config.model ?? "unknown",
|
|
13800
|
+
provider: input.providerId ?? config.provider ?? "unknown",
|
|
13801
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
13802
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
13803
|
+
fromFallback: false,
|
|
13804
|
+
error: "No provider or model could be resolved. Check your config."
|
|
13805
|
+
};
|
|
13806
|
+
}
|
|
13807
|
+
let provider;
|
|
13808
|
+
try {
|
|
13809
|
+
provider = await this.opts.buildProvider(target.providerId, target.model);
|
|
13810
|
+
} catch (err) {
|
|
13811
|
+
return {
|
|
13812
|
+
text: "",
|
|
13813
|
+
model: target.model,
|
|
13814
|
+
provider: target.providerId,
|
|
13815
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
13816
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
13817
|
+
fromFallback: false,
|
|
13818
|
+
error: `Cannot build provider "${target.providerId}": ${err instanceof Error ? err.message : String(err)}`
|
|
13819
|
+
};
|
|
13820
|
+
}
|
|
13821
|
+
const request = this.buildRequest(input, target.model);
|
|
13822
|
+
const signal = this.resolveSignal(input);
|
|
13823
|
+
const chain = this.resolveFallbackChain(input, config, target);
|
|
13824
|
+
let servingProviderId = provider.id;
|
|
13825
|
+
let servingModel = target.model;
|
|
13826
|
+
let fromFallback = false;
|
|
13827
|
+
const primaryAttempt = await this.tryCall(provider, request, signal);
|
|
13828
|
+
let result = primaryAttempt.response;
|
|
13829
|
+
let lastError = primaryAttempt.error;
|
|
13830
|
+
let fallbackEligible = primaryAttempt.fallbackEligible;
|
|
13831
|
+
if (!result && fallbackEligible && chain.length > 0) {
|
|
13832
|
+
for (const ref of chain) {
|
|
13833
|
+
const parsed = parseModelRef(ref);
|
|
13834
|
+
if (!parsed.model) continue;
|
|
13835
|
+
const fbProviderId = parsed.provider ?? config.provider;
|
|
13836
|
+
if (fbProviderId === provider.id && parsed.model === target.model) continue;
|
|
13837
|
+
servingProviderId = fbProviderId;
|
|
13838
|
+
servingModel = parsed.model;
|
|
13839
|
+
let fbProvider;
|
|
13840
|
+
try {
|
|
13841
|
+
fbProvider = await this.opts.buildProvider(fbProviderId, parsed.model);
|
|
13842
|
+
} catch (err) {
|
|
13843
|
+
lastError = err;
|
|
13844
|
+
continue;
|
|
13845
|
+
}
|
|
13846
|
+
servingProviderId = fbProvider.id;
|
|
13847
|
+
const attempt = await this.tryCall(
|
|
13848
|
+
fbProvider,
|
|
13849
|
+
this.buildRequest(input, parsed.model),
|
|
13850
|
+
signal
|
|
13851
|
+
);
|
|
13852
|
+
if (attempt.response) {
|
|
13853
|
+
result = attempt.response;
|
|
13854
|
+
fromFallback = true;
|
|
13855
|
+
break;
|
|
13856
|
+
}
|
|
13857
|
+
lastError = attempt.error;
|
|
13858
|
+
fallbackEligible = attempt.fallbackEligible;
|
|
13859
|
+
if (!fallbackEligible) break;
|
|
13860
|
+
}
|
|
13861
|
+
}
|
|
13862
|
+
const elapsed = Math.round(performance.now() - startedAt);
|
|
13863
|
+
if (result) {
|
|
13864
|
+
const textBlocks = result.content.filter(isTextBlock);
|
|
13865
|
+
const text = textBlocks.map((b) => b.text).join("\n").trim();
|
|
13866
|
+
return {
|
|
13867
|
+
text: text || "(empty response)",
|
|
13868
|
+
model: result.model ?? servingModel,
|
|
13869
|
+
provider: servingProviderId,
|
|
13870
|
+
tokens: {
|
|
13871
|
+
input: result.usage?.input ?? 0,
|
|
13872
|
+
output: result.usage?.output ?? 0,
|
|
13873
|
+
total: (result.usage?.input ?? 0) + (result.usage?.output ?? 0)
|
|
13874
|
+
},
|
|
13875
|
+
durationMs: elapsed,
|
|
13876
|
+
fromFallback,
|
|
13877
|
+
stopReason: result.stopReason
|
|
13878
|
+
};
|
|
13879
|
+
}
|
|
13880
|
+
return {
|
|
13881
|
+
text: "",
|
|
13882
|
+
model: servingModel,
|
|
13883
|
+
provider: servingProviderId,
|
|
13884
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
13885
|
+
durationMs: elapsed,
|
|
13886
|
+
fromFallback,
|
|
13887
|
+
error: lastError instanceof Error ? lastError.message : String(lastError ?? "Unknown error")
|
|
13888
|
+
};
|
|
13889
|
+
}
|
|
13890
|
+
// ── Private helpers ─────────────────────────────────────────────
|
|
13891
|
+
/**
|
|
13892
|
+
* Resolve the target provider + model from input + config.
|
|
13893
|
+
* Priority: role-based routing > explicit providerId+model > defaults.
|
|
13894
|
+
*/
|
|
13895
|
+
resolveTarget(input, config) {
|
|
13896
|
+
if (input.role && this.opts.modelRouter) {
|
|
13897
|
+
const pick = this.opts.modelRouter.pickForTask(input.role, "");
|
|
13898
|
+
if (pick) {
|
|
13899
|
+
return { providerId: pick.provider, model: pick.model };
|
|
13900
|
+
}
|
|
13901
|
+
}
|
|
13902
|
+
if (input.providerId && input.model) {
|
|
13903
|
+
return { providerId: input.providerId, model: input.model };
|
|
13904
|
+
}
|
|
13905
|
+
if (input.model) {
|
|
13906
|
+
return { providerId: config.provider, model: input.model };
|
|
13907
|
+
}
|
|
13908
|
+
if (input.providerId) {
|
|
13909
|
+
return { providerId: input.providerId, model: config.model };
|
|
13910
|
+
}
|
|
13911
|
+
if (config.provider && config.model) {
|
|
13912
|
+
return { providerId: config.provider, model: config.model };
|
|
13913
|
+
}
|
|
13914
|
+
return void 0;
|
|
13915
|
+
}
|
|
13916
|
+
/**
|
|
13917
|
+
* Build a Provider Request from the input.
|
|
13918
|
+
*/
|
|
13919
|
+
buildRequest(input, model) {
|
|
13920
|
+
const messages = [...input.messages ?? []];
|
|
13921
|
+
if (input.userPrompt) {
|
|
13922
|
+
messages.push({ role: "user", content: input.userPrompt });
|
|
13923
|
+
}
|
|
13924
|
+
const system = asTextBlocks(input.system);
|
|
13925
|
+
return {
|
|
13926
|
+
model,
|
|
13927
|
+
...system.length > 0 ? { system } : {},
|
|
13928
|
+
messages,
|
|
13929
|
+
maxTokens: input.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
13930
|
+
...input.temperature !== void 0 ? { temperature: input.temperature } : {},
|
|
13931
|
+
...input.responseFormat ? { responseFormat: input.responseFormat } : {}
|
|
13932
|
+
};
|
|
13933
|
+
}
|
|
13934
|
+
/**
|
|
13935
|
+
* Resolve the abort signal. Provider calls always receive the per-call
|
|
13936
|
+
* timeout, composed with external cancellation when the caller supplies it.
|
|
13937
|
+
*/
|
|
13938
|
+
resolveSignal(input) {
|
|
13939
|
+
const timeoutSignal = AbortSignal.timeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
13940
|
+
return input.signal ? AbortSignal.any([input.signal, timeoutSignal]) : timeoutSignal;
|
|
13941
|
+
}
|
|
13942
|
+
/**
|
|
13943
|
+
* Build the fallback model chain from input + config + current target.
|
|
13944
|
+
*/
|
|
13945
|
+
resolveFallbackChain(input, config, target) {
|
|
13946
|
+
if (input.fallbackModels && input.fallbackModels.length > 0) {
|
|
13947
|
+
return input.fallbackModels;
|
|
13948
|
+
}
|
|
13949
|
+
if (input.fallbackProfile) {
|
|
13950
|
+
const profile = fallbackProfileChain(config, input.fallbackProfile);
|
|
13951
|
+
if (profile.length > 0) return profile;
|
|
13952
|
+
}
|
|
13953
|
+
if (config.fallbackAuto !== false) {
|
|
13954
|
+
return effectiveFallbackChain(config).filter((ref) => {
|
|
13955
|
+
const parsed = parseModelRef(ref);
|
|
13956
|
+
const fbProvider = parsed.provider ?? config.provider;
|
|
13957
|
+
return !(fbProvider === target.providerId && parsed.model === target.model);
|
|
13958
|
+
});
|
|
13959
|
+
}
|
|
13960
|
+
return [];
|
|
13961
|
+
}
|
|
13962
|
+
/** Attempt a provider call while preserving the actual failure for callers. */
|
|
13963
|
+
async tryCall(provider, request, signal) {
|
|
13964
|
+
try {
|
|
13965
|
+
return {
|
|
13966
|
+
response: await provider.complete(request, { signal }),
|
|
13967
|
+
fallbackEligible: false
|
|
13968
|
+
};
|
|
13969
|
+
} catch (err) {
|
|
13970
|
+
return {
|
|
13971
|
+
error: err,
|
|
13972
|
+
fallbackEligible: !signal.aborted && (!(err instanceof ProviderError) || isFallbackWorthy(err.kind))
|
|
13973
|
+
};
|
|
13974
|
+
}
|
|
13975
|
+
}
|
|
13976
|
+
};
|
|
13977
|
+
function asTextBlocks(system) {
|
|
13978
|
+
if (!system) return [];
|
|
13979
|
+
if (Array.isArray(system)) return system;
|
|
13980
|
+
return [{ type: "text", text: system }];
|
|
13981
|
+
}
|
|
11472
13982
|
export {
|
|
11473
13983
|
AutoCompactionMiddleware,
|
|
11474
13984
|
AutonomousRunner,
|
|
13985
|
+
BRAIN_EVALUATION_CASE_VERSION,
|
|
13986
|
+
BUILTIN_COUNCIL_PERSONAS,
|
|
13987
|
+
BUILTIN_COUNCIL_PROFILES,
|
|
13988
|
+
COUNCIL_JUDGE_PROMPT_PATH,
|
|
13989
|
+
COUNCIL_REFUSAL_OPTION_ID,
|
|
13990
|
+
COUNCIL_REFUSE_OPTION_ID,
|
|
13991
|
+
COUNCIL_VOTER_PROMPT_PATH,
|
|
13992
|
+
CouncilOrchestrator,
|
|
13993
|
+
CouncilPersonaRegistry,
|
|
13994
|
+
CouncilProfileRegistry,
|
|
13995
|
+
DEFAULT_COUNCIL_APPROVAL_FRACTION,
|
|
13996
|
+
DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
|
|
13997
|
+
DEFAULT_COUNCIL_MAX_CONCURRENCY,
|
|
13998
|
+
DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
|
|
13999
|
+
DEFAULT_COUNCIL_PERSONA_REGISTRY,
|
|
14000
|
+
DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
|
|
14001
|
+
DEFAULT_COUNCIL_PROFILE_REGISTRY,
|
|
14002
|
+
DEFAULT_COUNCIL_QUORUM_FRACTION,
|
|
14003
|
+
DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
|
|
11475
14004
|
DEFAULT_RECOVERY_STRATEGIES,
|
|
11476
14005
|
DefaultDesignKitLoader,
|
|
11477
14006
|
DefaultErrorHandler,
|
|
@@ -11482,6 +14011,8 @@ export {
|
|
|
11482
14011
|
EternalAutonomyEngine,
|
|
11483
14012
|
HybridCompactor,
|
|
11484
14013
|
IntelligentCompactor,
|
|
14014
|
+
MAX_COUNCIL_CONCURRENCY,
|
|
14015
|
+
OneShotOrchestrator,
|
|
11485
14016
|
ParallelEternalEngine,
|
|
11486
14017
|
SelectiveCompactor,
|
|
11487
14018
|
ToolExecutor,
|
|
@@ -11489,11 +14020,23 @@ export {
|
|
|
11489
14020
|
_resetDesignRulesCache,
|
|
11490
14021
|
activateDesign,
|
|
11491
14022
|
applyModelRuntime,
|
|
14023
|
+
assembleBrainTiers,
|
|
14024
|
+
buildBrainUserMessage,
|
|
14025
|
+
buildCouncilJudgeSystemPrompt,
|
|
14026
|
+
buildCouncilJudgeUserPrompt,
|
|
14027
|
+
buildCouncilQuestionPrompt,
|
|
14028
|
+
buildCouncilVoterSystemPrompt,
|
|
14029
|
+
buildCouncilVoterUserPrompt,
|
|
11492
14030
|
buildGoalPreamble,
|
|
11493
14031
|
buildRecoveryStrategies,
|
|
11494
14032
|
clearActiveKit,
|
|
11495
14033
|
clearPersistedActiveKit,
|
|
14034
|
+
completeBrainLlm,
|
|
11496
14035
|
createAutonomyBrain,
|
|
14036
|
+
createBrainRuntime,
|
|
14037
|
+
createCouncilBrainArbiter,
|
|
14038
|
+
createCouncilPersonaRegistry,
|
|
14039
|
+
createCouncilProfileRegistry,
|
|
11497
14040
|
createStrategyCompactor,
|
|
11498
14041
|
createTieredBrainArbiter,
|
|
11499
14042
|
designProjectDir,
|
|
@@ -11510,12 +14053,19 @@ export {
|
|
|
11510
14053
|
makeDesignDetectUserInputMiddleware,
|
|
11511
14054
|
makeDesignStudioRequestMiddleware,
|
|
11512
14055
|
mergeModelRuntime,
|
|
14056
|
+
normalizeCouncilProfile,
|
|
14057
|
+
parseOptionDecision,
|
|
11513
14058
|
recordKitChoice,
|
|
11514
14059
|
renderPrompt,
|
|
14060
|
+
resolveBrainConfigDefaults,
|
|
11515
14061
|
resolveBundledDesignKitsDir,
|
|
11516
14062
|
resolveCacheForRequest,
|
|
14063
|
+
resolveCouncilProfile,
|
|
14064
|
+
resolveCouncilVotes,
|
|
11517
14065
|
resolveModelRuntime,
|
|
11518
14066
|
resolveReasoningForRequest,
|
|
11519
|
-
|
|
14067
|
+
runBrainEvaluation,
|
|
14068
|
+
setActiveKit,
|
|
14069
|
+
validateBrainEvaluationCase
|
|
11520
14070
|
};
|
|
11521
14071
|
//# sourceMappingURL=index.js.map
|