@wrongstack/core 0.286.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/boot.d.ts.map +1 -1
- package/dist/coordination/brain-monitor.d.ts +15 -0
- package/dist/coordination/brain-monitor.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 +27 -8
- 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 +2 -3
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +36 -22
- 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.js +708 -442
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-message-codec.d.ts +21 -1
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +42 -1
- package/dist/coordination/mailbox-types.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 +834 -1465
- 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/compaction-core.d.ts.map +1 -1
- package/dist/execution/council-brain.d.ts.map +1 -1
- 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/index.d.ts +6 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1738 -161
- 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/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 +17 -7
- 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/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 +471 -294
- 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 -611
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4540 -2283
- 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 +10 -1298
- 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/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.js +7 -2
- package/dist/security/index.js.map +2 -2
- package/dist/security/secret-scrubber.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- 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/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +425 -717
- 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/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 +27 -3
- 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 +29 -8
- package/dist/types/index.js.map +3 -3
- 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/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/index.js +75 -8
- package/dist/utils/index.js.map +3 -3
- 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/instructions/cli/next-task-predictor.md +5 -3
- package/instructions/coordination/subagent-baseline.md +8 -7
- package/instructions/leader-after-task.md +18 -9
- 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 +5 -5
- 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 +278 -39
- package/package.json +2 -2
- package/skills/mailbox-bridge/SKILL.md +16 -7
- package/skills/output-standards/SKILL.md +16 -16
- package/skills/wrongstack-mailbox/SKILL.md +6 -0
- 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/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) {
|
|
@@ -2254,7 +2278,7 @@ var AutonomousRunner = class {
|
|
|
2254
2278
|
};
|
|
2255
2279
|
|
|
2256
2280
|
// src/utils/instruction-file.ts
|
|
2257
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2281
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
2258
2282
|
import * as path3 from "node:path";
|
|
2259
2283
|
import { fileURLToPath } from "node:url";
|
|
2260
2284
|
var textCache = /* @__PURE__ */ new Map();
|
|
@@ -2265,7 +2289,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
2265
2289
|
let resolved = "";
|
|
2266
2290
|
for (const root of instructionRootCandidates()) {
|
|
2267
2291
|
try {
|
|
2268
|
-
resolved =
|
|
2292
|
+
resolved = readFileSync2(path3.join(root, relativePath), "utf8").trimEnd();
|
|
2269
2293
|
break;
|
|
2270
2294
|
} catch {
|
|
2271
2295
|
}
|
|
@@ -2292,7 +2316,7 @@ function instructionRootCandidates() {
|
|
|
2292
2316
|
}
|
|
2293
2317
|
function isDirectory(candidate) {
|
|
2294
2318
|
try {
|
|
2295
|
-
return
|
|
2319
|
+
return statSync2(candidate).isDirectory();
|
|
2296
2320
|
} catch {
|
|
2297
2321
|
return false;
|
|
2298
2322
|
}
|
|
@@ -2565,6 +2589,22 @@ var RETRYABLE_BY_KIND = {
|
|
|
2565
2589
|
invalid_request: false,
|
|
2566
2590
|
unknown: false
|
|
2567
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
|
+
};
|
|
2568
2608
|
var ProviderError = class extends WrongStackError {
|
|
2569
2609
|
status;
|
|
2570
2610
|
retryable;
|
|
@@ -2646,6 +2686,10 @@ function kindToCode(kind) {
|
|
|
2646
2686
|
}
|
|
2647
2687
|
|
|
2648
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
|
+
}
|
|
2649
2693
|
function parseModelRef(ref) {
|
|
2650
2694
|
const trimmed = ref.trim();
|
|
2651
2695
|
const slash = trimmed.indexOf("/");
|
|
@@ -2661,8 +2705,174 @@ function parseModelRef(ref) {
|
|
|
2661
2705
|
}
|
|
2662
2706
|
return { model: trimmed };
|
|
2663
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
|
+
}
|
|
2664
2778
|
var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
|
|
2665
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
|
+
|
|
2666
2876
|
// src/execution/council-brain.ts
|
|
2667
2877
|
var COUNCIL_REFUSE_OPTION_ID = "council_refuse";
|
|
2668
2878
|
var BUILTIN_PERSONAS = {
|
|
@@ -2727,7 +2937,12 @@ ${personaText(voter.persona)}`,
|
|
|
2727
2937
|
if (!voter || result.status !== "fulfilled") return;
|
|
2728
2938
|
const parsed = parseOptionDecision(result.value, ballot);
|
|
2729
2939
|
if (parsed?.type !== "answer" || !parsed.optionId) return;
|
|
2730
|
-
votes.push({
|
|
2940
|
+
votes.push({
|
|
2941
|
+
seatId: String(i),
|
|
2942
|
+
voter,
|
|
2943
|
+
optionId: parsed.optionId,
|
|
2944
|
+
rationale: parsed.rationale
|
|
2945
|
+
});
|
|
2731
2946
|
});
|
|
2732
2947
|
return votes;
|
|
2733
2948
|
}
|
|
@@ -2823,44 +3038,44 @@ ${stanceLines}`,
|
|
|
2823
3038
|
}
|
|
2824
3039
|
const ballot = ballotOptions(request);
|
|
2825
3040
|
const votes = await collectVotes(request, ballot, digest);
|
|
2826
|
-
|
|
2827
|
-
|
|
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
|
+
);
|
|
2828
3060
|
}
|
|
2829
|
-
|
|
2830
|
-
(
|
|
2831
|
-
|
|
2832
|
-
|
|
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
|
+
}
|
|
2833
3066
|
return finish(request, {
|
|
2834
3067
|
type: "deny",
|
|
2835
3068
|
reason: `Council veto by ${voterLabel(vetoRefusal.voter, 0)}` + (vetoRefusal.rationale ? `: ${vetoRefusal.rationale}` : ".")
|
|
2836
3069
|
});
|
|
2837
3070
|
}
|
|
2838
|
-
|
|
2839
|
-
let castWeight = 0;
|
|
2840
|
-
for (const v of votes) {
|
|
2841
|
-
const w = v.voter.weight ?? 1;
|
|
2842
|
-
castWeight += w;
|
|
2843
|
-
weightByOption.set(v.optionId, (weightByOption.get(v.optionId) ?? 0) + w);
|
|
2844
|
-
}
|
|
2845
|
-
let winner = null;
|
|
2846
|
-
let contested = false;
|
|
2847
|
-
for (const [optionId, weight] of weightByOption) {
|
|
2848
|
-
if (!winner || weight > winner.weight) {
|
|
2849
|
-
winner = { optionId, weight };
|
|
2850
|
-
contested = false;
|
|
2851
|
-
} else if (weight === winner.weight) {
|
|
2852
|
-
contested = true;
|
|
2853
|
-
}
|
|
2854
|
-
}
|
|
2855
|
-
const decisive = winner !== null && !contested && winner.weight > approvalFraction * castWeight;
|
|
2856
|
-
let finalOptionId = decisive && winner ? winner.optionId : null;
|
|
3071
|
+
let finalOptionId = resolution.status === "decided" || resolution.status === "denied" ? resolution.optionId : null;
|
|
2857
3072
|
let viaJudge = false;
|
|
2858
|
-
if (
|
|
3073
|
+
if (resolution.status === "needs_judge") {
|
|
2859
3074
|
const judged = await judgeRound(
|
|
2860
3075
|
request,
|
|
2861
3076
|
ballot,
|
|
2862
3077
|
votes,
|
|
2863
|
-
|
|
3078
|
+
resolution.reason === "tie" ? "tie" : "no option cleared the approval threshold"
|
|
2864
3079
|
);
|
|
2865
3080
|
if (judged?.type === "answer" && judged.optionId) {
|
|
2866
3081
|
finalOptionId = judged.optionId;
|
|
@@ -3298,7 +3513,7 @@ function normalizeVoter(raw) {
|
|
|
3298
3513
|
if (raw.veto !== void 0) voter.veto = raw.veto;
|
|
3299
3514
|
return voter;
|
|
3300
3515
|
}
|
|
3301
|
-
function
|
|
3516
|
+
function requireFraction2(value, label) {
|
|
3302
3517
|
if (!Number.isFinite(value) || value <= 0 || value > 1) {
|
|
3303
3518
|
throw new Error(`Invalid ${label}: ${String(value)} (must be in (0, 1])`);
|
|
3304
3519
|
}
|
|
@@ -3446,10 +3661,10 @@ function createBrainRuntime(opts) {
|
|
|
3446
3661
|
c.voters = p.voters === null ? void 0 : p.voters.map(normalizeVoter);
|
|
3447
3662
|
}
|
|
3448
3663
|
if (p.quorum !== void 0) {
|
|
3449
|
-
c.quorum = p.quorum === null ? void 0 :
|
|
3664
|
+
c.quorum = p.quorum === null ? void 0 : requireFraction2(p.quorum, "council quorum");
|
|
3450
3665
|
}
|
|
3451
3666
|
if (p.approval !== void 0) {
|
|
3452
|
-
c.approval = p.approval === null ? void 0 :
|
|
3667
|
+
c.approval = p.approval === null ? void 0 : requireFraction2(p.approval, "council approval");
|
|
3453
3668
|
}
|
|
3454
3669
|
if (p.judge !== void 0) {
|
|
3455
3670
|
c.judge = p.judge === null ? void 0 : normalizeEntry(p.judge);
|
|
@@ -3570,6 +3785,1011 @@ function createBrainRuntime(opts) {
|
|
|
3570
3785
|
};
|
|
3571
3786
|
}
|
|
3572
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
|
|
4261
|
+
);
|
|
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
|
+
}
|
|
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) {
|
|
4353
|
+
return {
|
|
4354
|
+
seatId: seat.id,
|
|
4355
|
+
persona: seat.persona,
|
|
4356
|
+
status: "failed",
|
|
4357
|
+
error: errorMessage(error)
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
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) {
|
|
4373
|
+
return {
|
|
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
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
return {
|
|
4392
|
+
seatId: seat.id,
|
|
4393
|
+
persona: seat.persona,
|
|
4394
|
+
status: "valid",
|
|
4395
|
+
...parsed.vote,
|
|
4396
|
+
...metadata
|
|
4397
|
+
};
|
|
4398
|
+
}
|
|
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;
|
|
4691
|
+
return {
|
|
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]) } : {}
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
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
|
+
];
|
|
4741
|
+
}
|
|
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.`);
|
|
4751
|
+
}
|
|
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);
|
|
4791
|
+
}
|
|
4792
|
+
|
|
3573
4793
|
// src/storage/goal-store.ts
|
|
3574
4794
|
import * as fsp from "node:fs/promises";
|
|
3575
4795
|
var MAX_JOURNAL_ENTRIES = 500;
|
|
@@ -3943,7 +5163,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
3943
5163
|
}
|
|
3944
5164
|
if (newContent) {
|
|
3945
5165
|
next ??= messages.slice();
|
|
3946
|
-
next[i] = { ...msg, content: newContent };
|
|
5166
|
+
next[i] = { ...msg, content: newContent, _estTokens: void 0 };
|
|
3947
5167
|
changed = true;
|
|
3948
5168
|
}
|
|
3949
5169
|
if (compactionDebugEnabled()) {
|
|
@@ -4337,7 +5557,7 @@ function isDesignStack(v) {
|
|
|
4337
5557
|
|
|
4338
5558
|
// src/execution/design-kit-loader.ts
|
|
4339
5559
|
import { existsSync } from "node:fs";
|
|
4340
|
-
import * as
|
|
5560
|
+
import * as fs3 from "node:fs/promises";
|
|
4341
5561
|
import * as path4 from "node:path";
|
|
4342
5562
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4343
5563
|
var KIT_FILE = "KIT.md";
|
|
@@ -4377,17 +5597,17 @@ function parseKitFrontmatter(raw) {
|
|
|
4377
5597
|
else out[key] = items;
|
|
4378
5598
|
};
|
|
4379
5599
|
for (const line of lines) {
|
|
4380
|
-
const
|
|
4381
|
-
if (listKey &&
|
|
4382
|
-
const parsed = parseList(
|
|
5600
|
+
const bulletMatch = /^\s*-\s+(.*)$/.exec(line);
|
|
5601
|
+
if (listKey && bulletMatch) {
|
|
5602
|
+
const parsed = parseList(bulletMatch[1] ?? "");
|
|
4383
5603
|
if (listKey === "stacks") out.stacks.push(...parsed.filter(isDesignStack));
|
|
4384
5604
|
else out[listKey].push(...parsed);
|
|
4385
5605
|
continue;
|
|
4386
5606
|
}
|
|
4387
|
-
const
|
|
4388
|
-
if (!
|
|
4389
|
-
const key =
|
|
4390
|
-
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();
|
|
4391
5611
|
if (key === "tags" || key === "stacks" || key === "themes") {
|
|
4392
5612
|
if (rest) {
|
|
4393
5613
|
setList(key, parseList(rest));
|
|
@@ -4404,24 +5624,23 @@ function parseKitFrontmatter(raw) {
|
|
|
4404
5624
|
}
|
|
4405
5625
|
function narrowStackSections(body, stack) {
|
|
4406
5626
|
if (!stack) return body;
|
|
4407
|
-
const
|
|
4408
|
-
const
|
|
4409
|
-
let
|
|
4410
|
-
while ((
|
|
4411
|
-
|
|
4412
|
-
}
|
|
4413
|
-
if (
|
|
4414
|
-
const
|
|
4415
|
-
let result = body.slice(0,
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
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) {
|
|
4422
5641
|
result += `
|
|
4423
5642
|
|
|
4424
|
-
${body.slice(
|
|
5643
|
+
${body.slice(current.start, sectionEnd).trimEnd()}`;
|
|
4425
5644
|
}
|
|
4426
5645
|
}
|
|
4427
5646
|
return `${result}
|
|
@@ -4446,7 +5665,7 @@ var DefaultDesignKitLoader = class {
|
|
|
4446
5665
|
for (const { dir, source } of this.dirs) {
|
|
4447
5666
|
let entries;
|
|
4448
5667
|
try {
|
|
4449
|
-
entries = await
|
|
5668
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
4450
5669
|
} catch {
|
|
4451
5670
|
continue;
|
|
4452
5671
|
}
|
|
@@ -4454,7 +5673,7 @@ var DefaultDesignKitLoader = class {
|
|
|
4454
5673
|
if (!e.isDirectory()) continue;
|
|
4455
5674
|
const kitFile = path4.join(dir, e.name, KIT_FILE);
|
|
4456
5675
|
try {
|
|
4457
|
-
const raw = await
|
|
5676
|
+
const raw = await fs3.readFile(kitFile, "utf8");
|
|
4458
5677
|
const fm = parseKitFrontmatter(raw);
|
|
4459
5678
|
const id = fm.id ?? e.name;
|
|
4460
5679
|
if (!fm.name) continue;
|
|
@@ -4515,7 +5734,7 @@ var DefaultDesignKitLoader = class {
|
|
|
4515
5734
|
if (cached !== void 0) return cached;
|
|
4516
5735
|
const m = await this.find(id);
|
|
4517
5736
|
if (!m) throw new Error(`Design kit "${id}" not found`);
|
|
4518
|
-
const raw = await
|
|
5737
|
+
const raw = await fs3.readFile(m.path, "utf8");
|
|
4519
5738
|
const body = narrowStackSections(stripFrontmatter(raw), stack);
|
|
4520
5739
|
this.bodyCache.set(key, body);
|
|
4521
5740
|
return body;
|
|
@@ -4528,7 +5747,7 @@ var DefaultDesignKitLoader = class {
|
|
|
4528
5747
|
if (m) {
|
|
4529
5748
|
const tokensPath = path4.join(path4.dirname(m.path), TOKENS_FILE);
|
|
4530
5749
|
try {
|
|
4531
|
-
const raw = await
|
|
5750
|
+
const raw = await fs3.readFile(tokensPath, "utf8");
|
|
4532
5751
|
const parsed = JSON.parse(raw);
|
|
4533
5752
|
tokens = parsed;
|
|
4534
5753
|
} catch {
|
|
@@ -4586,7 +5805,7 @@ function _resetDesignKitLoaderMemo() {
|
|
|
4586
5805
|
|
|
4587
5806
|
// src/execution/design-project-store.ts
|
|
4588
5807
|
import { existsSync as existsSync2 } from "node:fs";
|
|
4589
|
-
import * as
|
|
5808
|
+
import * as fs4 from "node:fs/promises";
|
|
4590
5809
|
import * as path5 from "node:path";
|
|
4591
5810
|
var DESIGN_DIR = ".design";
|
|
4592
5811
|
function designProjectDir(projectRoot) {
|
|
@@ -4599,7 +5818,7 @@ async function loadProjectDesignRules(projectRoot) {
|
|
|
4599
5818
|
let rules;
|
|
4600
5819
|
for (const name of RULE_FILES) {
|
|
4601
5820
|
try {
|
|
4602
|
-
const txt = await
|
|
5821
|
+
const txt = await fs4.readFile(path5.join(designProjectDir(projectRoot), name), "utf8");
|
|
4603
5822
|
if (txt.trim()) {
|
|
4604
5823
|
rules = txt.trim();
|
|
4605
5824
|
break;
|
|
@@ -4620,7 +5839,7 @@ function parseOverrides(value) {
|
|
|
4620
5839
|
}
|
|
4621
5840
|
async function loadActiveKit(projectRoot) {
|
|
4622
5841
|
try {
|
|
4623
|
-
const raw = await
|
|
5842
|
+
const raw = await fs4.readFile(path5.join(designProjectDir(projectRoot), "active.json"), "utf8");
|
|
4624
5843
|
const parsed = JSON.parse(raw);
|
|
4625
5844
|
if (parsed && typeof parsed.kit === "string") {
|
|
4626
5845
|
return {
|
|
@@ -4653,11 +5872,11 @@ function applyTokenOverrides(tokens, overrides) {
|
|
|
4653
5872
|
}
|
|
4654
5873
|
async function ensureDesignDir(projectRoot) {
|
|
4655
5874
|
const dir = designProjectDir(projectRoot);
|
|
4656
|
-
await
|
|
5875
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
4657
5876
|
const gi = path5.join(dir, ".gitignore");
|
|
4658
5877
|
if (!existsSync2(gi)) {
|
|
4659
5878
|
try {
|
|
4660
|
-
await
|
|
5879
|
+
await fs4.writeFile(gi, "*\n");
|
|
4661
5880
|
} catch {
|
|
4662
5881
|
}
|
|
4663
5882
|
}
|
|
@@ -4668,17 +5887,17 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
|
|
|
4668
5887
|
const dir = await ensureDesignDir(projectRoot);
|
|
4669
5888
|
const record = { kit, stack: stack ?? null };
|
|
4670
5889
|
if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
|
|
4671
|
-
await
|
|
5890
|
+
await fs4.writeFile(path5.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
4672
5891
|
`);
|
|
4673
5892
|
const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
|
|
4674
5893
|
`;
|
|
4675
|
-
await
|
|
5894
|
+
await fs4.appendFile(path5.join(dir, "decisions.md"), line);
|
|
4676
5895
|
} catch {
|
|
4677
5896
|
}
|
|
4678
5897
|
}
|
|
4679
5898
|
async function clearPersistedActiveKit(projectRoot) {
|
|
4680
5899
|
try {
|
|
4681
|
-
await
|
|
5900
|
+
await fs4.rm(path5.join(designProjectDir(projectRoot), "active.json"), { force: true });
|
|
4682
5901
|
} catch {
|
|
4683
5902
|
}
|
|
4684
5903
|
}
|
|
@@ -4959,10 +6178,10 @@ function makeDesignVerifyToolCallMiddleware() {
|
|
|
4959
6178
|
const p = typeof input?.path === "string" ? input.path : "";
|
|
4960
6179
|
if (!p || !detectFrontendFile(p)) return out;
|
|
4961
6180
|
const ctx = out.ctx;
|
|
4962
|
-
const { default:
|
|
6181
|
+
const { default: fs9 } = await import("node:fs/promises");
|
|
4963
6182
|
const { default: nodePath } = await import("node:path");
|
|
4964
6183
|
const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
|
|
4965
|
-
const text = await
|
|
6184
|
+
const text = await fs9.readFile(abs, "utf8").catch(() => "");
|
|
4966
6185
|
if (!text) return out;
|
|
4967
6186
|
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
4968
6187
|
const rawTokens = await loader.readTokens(state.activeKit);
|
|
@@ -5082,11 +6301,25 @@ function buildRecoveryStrategies(opts) {
|
|
|
5082
6301
|
},
|
|
5083
6302
|
{
|
|
5084
6303
|
label: "rate_limit_backoff",
|
|
5085
|
-
async attempt(err) {
|
|
6304
|
+
async attempt(err, ctx) {
|
|
5086
6305
|
if (!(err instanceof ProviderError) || err.kind !== "rate_limit") return null;
|
|
5087
6306
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
5088
6307
|
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
5089
|
-
|
|
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
|
+
}
|
|
5090
6323
|
return { action: "retry", reason: "rate_limit_backoff" };
|
|
5091
6324
|
}
|
|
5092
6325
|
},
|
|
@@ -5952,16 +7185,18 @@ var IntelligentCompactor = class {
|
|
|
5952
7185
|
eliseThreshold;
|
|
5953
7186
|
summarizerPrompt;
|
|
5954
7187
|
summarizerModel;
|
|
7188
|
+
oneShotOrchestrator;
|
|
5955
7189
|
constructor(opts) {
|
|
5956
7190
|
this.provider = opts.provider;
|
|
5957
|
-
this.warnThreshold = opts.warnThreshold ?? 0.
|
|
5958
|
-
this.softThreshold = opts.softThreshold ?? 0.
|
|
5959
|
-
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;
|
|
5960
7194
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
5961
7195
|
this.preserveK = opts.preserveK ?? 4;
|
|
5962
|
-
this.eliseThreshold = opts.eliseThreshold ??
|
|
7196
|
+
this.eliseThreshold = opts.eliseThreshold ?? 300;
|
|
5963
7197
|
this.summarizerPrompt = opts.summarizerPrompt ?? readBundledInstructionText("llm/intelligent-compactor-summarizer.md");
|
|
5964
7198
|
this.summarizerModel = opts.summarizerModel;
|
|
7199
|
+
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
5965
7200
|
}
|
|
5966
7201
|
async compact(ctx, opts = {}) {
|
|
5967
7202
|
const beforeTokens = estimateMessages(ctx.messages);
|
|
@@ -5974,7 +7209,7 @@ var IntelligentCompactor = class {
|
|
|
5974
7209
|
let collapsedDigest;
|
|
5975
7210
|
if (aggressive) {
|
|
5976
7211
|
const phase2 = await this.summarizeAncientTurns(ctx);
|
|
5977
|
-
|
|
7212
|
+
reductions.push({ phase: "summary", saved: Math.max(0, phase2.saved) });
|
|
5978
7213
|
collapsedDigest = phase2.digest;
|
|
5979
7214
|
} else if (load >= this.warnThreshold) {
|
|
5980
7215
|
const saved2 = this.elide(ctx);
|
|
@@ -6028,9 +7263,15 @@ var IntelligentCompactor = class {
|
|
|
6028
7263
|
} catch {
|
|
6029
7264
|
summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
6030
7265
|
}
|
|
7266
|
+
const smartDigest = buildSmartDigest(toSummarize);
|
|
7267
|
+
const enriched = smartDigest ? `[CRITICAL]
|
|
7268
|
+
${smartDigest}
|
|
7269
|
+
|
|
7270
|
+
[SUMMARY]
|
|
7271
|
+
${summaryText}` : summaryText;
|
|
6031
7272
|
const summaryMsg = {
|
|
6032
7273
|
role: "system",
|
|
6033
|
-
content: `[prior_turns_summary: ${
|
|
7274
|
+
content: `[prior_turns_summary: ${enriched}]`
|
|
6034
7275
|
};
|
|
6035
7276
|
const summaryTokens = estimateMessages([summaryMsg]);
|
|
6036
7277
|
const tail = ctx.messages.slice(boundary);
|
|
@@ -6038,27 +7279,41 @@ var IntelligentCompactor = class {
|
|
|
6038
7279
|
return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
|
|
6039
7280
|
}
|
|
6040
7281
|
async callSummarizer(messages, ctx) {
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
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)";
|
|
6059
7315
|
}
|
|
6060
|
-
|
|
6061
|
-
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
7316
|
+
return "(empty summary)";
|
|
6062
7317
|
}
|
|
6063
7318
|
messagesToText(messages) {
|
|
6064
7319
|
const lines = [];
|
|
@@ -6075,6 +7330,22 @@ var IntelligentCompactor = class {
|
|
|
6075
7330
|
}
|
|
6076
7331
|
return [{ type: "text", text: lines.join("\n") }];
|
|
6077
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
|
+
}
|
|
6078
7349
|
};
|
|
6079
7350
|
|
|
6080
7351
|
// src/execution/model-runtime.ts
|
|
@@ -6762,7 +8033,7 @@ function normalizeSubagentStructuredReport(value) {
|
|
|
6762
8033
|
const nextSteps = stringArray(value["suggested_next_steps"]);
|
|
6763
8034
|
const confidence = value["confidence"];
|
|
6764
8035
|
const completion = value["completion"];
|
|
6765
|
-
const remainingWork =
|
|
8036
|
+
const remainingWork = optionalString2(value["remaining_work"]);
|
|
6766
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(
|
|
6767
8038
|
(item) => item.trim().length > MAX_ITEM_CHARS
|
|
6768
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) {
|
|
@@ -6788,7 +8059,7 @@ function isRecord4(value) {
|
|
|
6788
8059
|
function nonEmptyString(value) {
|
|
6789
8060
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
6790
8061
|
}
|
|
6791
|
-
function
|
|
8062
|
+
function optionalString2(value) {
|
|
6792
8063
|
return value === void 0 ? void 0 : nonEmptyString(value);
|
|
6793
8064
|
}
|
|
6794
8065
|
function stringArray(value) {
|
|
@@ -7073,7 +8344,7 @@ var TOOLS = {
|
|
|
7073
8344
|
};
|
|
7074
8345
|
|
|
7075
8346
|
// src/coordination/agents/agent-prompts.ts
|
|
7076
|
-
import { readFileSync as
|
|
8347
|
+
import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
7077
8348
|
import * as os2 from "node:os";
|
|
7078
8349
|
import * as path6 from "node:path";
|
|
7079
8350
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -7088,7 +8359,7 @@ function agentPrompt(id) {
|
|
|
7088
8359
|
let resolved = "";
|
|
7089
8360
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
7090
8361
|
try {
|
|
7091
|
-
resolved =
|
|
8362
|
+
resolved = readFileSync3(path6.join(dir, fileName), "utf8").trimEnd();
|
|
7092
8363
|
break;
|
|
7093
8364
|
} catch {
|
|
7094
8365
|
}
|
|
@@ -7118,7 +8389,7 @@ function agentPromptDirCandidates(envDir) {
|
|
|
7118
8389
|
}
|
|
7119
8390
|
function isDirectory2(candidate) {
|
|
7120
8391
|
try {
|
|
7121
|
-
return
|
|
8392
|
+
return statSync3(candidate).isDirectory();
|
|
7122
8393
|
} catch {
|
|
7123
8394
|
return false;
|
|
7124
8395
|
}
|
|
@@ -8806,19 +10077,19 @@ var FLEET_ROSTER = {
|
|
|
8806
10077
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
8807
10078
|
)
|
|
8808
10079
|
};
|
|
8809
|
-
var DEFAULT_IDLE_TIMEOUT_MS =
|
|
10080
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8810
10081
|
var FLEET_ROSTER_BUDGETS = {
|
|
8811
|
-
"audit-log": { timeoutMs:
|
|
8812
|
-
"bug-hunter": { timeoutMs:
|
|
8813
|
-
"refactor-planner": { timeoutMs:
|
|
8814
|
-
"security-scanner": { timeoutMs:
|
|
8815
|
-
critic: { timeoutMs:
|
|
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 },
|
|
8816
10087
|
"shadow-agent": {
|
|
8817
10088
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
8818
|
-
maxIterations:
|
|
8819
|
-
maxToolCalls:
|
|
8820
|
-
maxTokens:
|
|
8821
|
-
maxCostUsd:
|
|
10089
|
+
maxIterations: 1e3,
|
|
10090
|
+
maxToolCalls: 2500,
|
|
10091
|
+
maxTokens: 3e4,
|
|
10092
|
+
maxCostUsd: 0.5
|
|
8822
10093
|
},
|
|
8823
10094
|
...Object.fromEntries(
|
|
8824
10095
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
@@ -8826,8 +10097,8 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
8826
10097
|
};
|
|
8827
10098
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
8828
10099
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
8829
|
-
maxIterations:
|
|
8830
|
-
maxToolCalls:
|
|
10100
|
+
maxIterations: 2500,
|
|
10101
|
+
maxToolCalls: 7500
|
|
8831
10102
|
};
|
|
8832
10103
|
function applyRosterBudget(cfg) {
|
|
8833
10104
|
const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
|
|
@@ -10368,6 +11639,7 @@ var LLMSelector = class {
|
|
|
10368
11639
|
maxContextTokens;
|
|
10369
11640
|
systemPrompt;
|
|
10370
11641
|
maxOutputTokens;
|
|
11642
|
+
oneShotOrchestrator;
|
|
10371
11643
|
constructor(opts) {
|
|
10372
11644
|
this.provider = opts.provider;
|
|
10373
11645
|
this.model = opts.model ?? "unknown";
|
|
@@ -10379,6 +11651,7 @@ var LLMSelector = class {
|
|
|
10379
11651
|
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
10380
11652
|
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
10381
11653
|
this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
|
|
11654
|
+
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
10382
11655
|
}
|
|
10383
11656
|
async select(messages, maxToKeep) {
|
|
10384
11657
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
@@ -10402,12 +11675,23 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
10402
11675
|
let raw;
|
|
10403
11676
|
const ac = new AbortController();
|
|
10404
11677
|
try {
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
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
|
+
}
|
|
10411
11695
|
} catch (err) {
|
|
10412
11696
|
if (err instanceof Error) {
|
|
10413
11697
|
console.warn(
|
|
@@ -10527,12 +11811,12 @@ var SelectiveCompactor = class {
|
|
|
10527
11811
|
constructor(opts) {
|
|
10528
11812
|
this.provider = opts.provider;
|
|
10529
11813
|
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
|
|
10530
|
-
this.warnThreshold = opts.warnThreshold ?? 0.
|
|
10531
|
-
this.softThreshold = opts.softThreshold ?? 0.
|
|
10532
|
-
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;
|
|
10533
11817
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
10534
11818
|
this.preserveK = opts.preserveK ?? 4;
|
|
10535
|
-
this.eliseThreshold = opts.eliseThreshold ??
|
|
11819
|
+
this.eliseThreshold = opts.eliseThreshold ?? 300;
|
|
10536
11820
|
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
|
|
10537
11821
|
if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
10538
11822
|
console.warn(
|
|
@@ -10725,7 +12009,7 @@ Summarize the following message range:`;
|
|
|
10725
12009
|
};
|
|
10726
12010
|
|
|
10727
12011
|
// src/execution/skill-loader.ts
|
|
10728
|
-
import * as
|
|
12012
|
+
import * as fs5 from "node:fs/promises";
|
|
10729
12013
|
import * as path7 from "node:path";
|
|
10730
12014
|
|
|
10731
12015
|
// src/skills/foreign-sources.ts
|
|
@@ -10956,7 +12240,7 @@ async function entryIsDirectory(dir, entry) {
|
|
|
10956
12240
|
if (entry.isDirectory()) return true;
|
|
10957
12241
|
if (entry.isSymbolicLink()) {
|
|
10958
12242
|
try {
|
|
10959
|
-
return (await
|
|
12243
|
+
return (await fs5.stat(path7.join(dir, entry.name))).isDirectory();
|
|
10960
12244
|
} catch {
|
|
10961
12245
|
return false;
|
|
10962
12246
|
}
|
|
@@ -10999,12 +12283,12 @@ var DefaultSkillLoader = class {
|
|
|
10999
12283
|
const seen = /* @__PURE__ */ new Set();
|
|
11000
12284
|
for (const { dir, source, originTool } of this.dirs) {
|
|
11001
12285
|
try {
|
|
11002
|
-
const entries = await
|
|
12286
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
11003
12287
|
for (const e of entries) {
|
|
11004
12288
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
11005
12289
|
const skillFile = path7.join(dir, e.name, "SKILL.md");
|
|
11006
12290
|
try {
|
|
11007
|
-
const raw = await
|
|
12291
|
+
const raw = await fs5.readFile(skillFile, "utf8");
|
|
11008
12292
|
const fm = parseSkillFrontmatter(raw);
|
|
11009
12293
|
if (!fm.name || !fm.description) continue;
|
|
11010
12294
|
if (!isValidSkillNameFormat(fm.name)) continue;
|
|
@@ -11076,7 +12360,7 @@ var DefaultSkillLoader = class {
|
|
|
11076
12360
|
if (cached !== void 0) return cached;
|
|
11077
12361
|
const m = await this.find(name);
|
|
11078
12362
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
11079
|
-
const body = await
|
|
12363
|
+
const body = await fs5.readFile(m.path, "utf8");
|
|
11080
12364
|
this.bodyCache.set(key, body);
|
|
11081
12365
|
return body;
|
|
11082
12366
|
}
|
|
@@ -11089,9 +12373,9 @@ var DefaultSkillLoader = class {
|
|
|
11089
12373
|
const savePath = path7.join(path7.dirname(m.path), "SKILL.save.md");
|
|
11090
12374
|
let result;
|
|
11091
12375
|
try {
|
|
11092
|
-
result = await
|
|
12376
|
+
result = await fs5.readFile(savePath, "utf8");
|
|
11093
12377
|
} catch {
|
|
11094
|
-
const full = await
|
|
12378
|
+
const full = await fs5.readFile(m.path, "utf8");
|
|
11095
12379
|
const body = stripFrontmatter2(full);
|
|
11096
12380
|
const compact = compactSkillBody(body);
|
|
11097
12381
|
if (compact) {
|
|
@@ -11119,11 +12403,11 @@ function parseDescriptionFromText(desc) {
|
|
|
11119
12403
|
}
|
|
11120
12404
|
|
|
11121
12405
|
// src/execution/prompt-loader.ts
|
|
11122
|
-
import * as
|
|
12406
|
+
import * as fs7 from "node:fs/promises";
|
|
11123
12407
|
import * as path9 from "node:path";
|
|
11124
12408
|
|
|
11125
12409
|
// src/storage/prompt-store.ts
|
|
11126
|
-
import * as
|
|
12410
|
+
import * as fs6 from "node:fs/promises";
|
|
11127
12411
|
import * as path8 from "node:path";
|
|
11128
12412
|
var SCHEMA_VERSION = 2;
|
|
11129
12413
|
function migratePromptEntry(raw) {
|
|
@@ -11185,12 +12469,12 @@ var DefaultPromptStore = class {
|
|
|
11185
12469
|
await ensureDir(this.dir);
|
|
11186
12470
|
const entries = [];
|
|
11187
12471
|
try {
|
|
11188
|
-
const files = await
|
|
12472
|
+
const files = await fs6.readdir(this.dir);
|
|
11189
12473
|
for (const file of files) {
|
|
11190
12474
|
if (!file.endsWith(".json")) continue;
|
|
11191
12475
|
try {
|
|
11192
12476
|
const raw = JSON.parse(
|
|
11193
|
-
await
|
|
12477
|
+
await fs6.readFile(path8.join(this.dir, file), "utf8")
|
|
11194
12478
|
);
|
|
11195
12479
|
const migrated = migratePromptEntry(raw.entry);
|
|
11196
12480
|
if (migrated) entries.push(migrated);
|
|
@@ -11206,7 +12490,7 @@ var DefaultPromptStore = class {
|
|
|
11206
12490
|
async get(id) {
|
|
11207
12491
|
const file = path8.join(this.dir, `${id}.json`);
|
|
11208
12492
|
try {
|
|
11209
|
-
const raw = JSON.parse(await
|
|
12493
|
+
const raw = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
11210
12494
|
return migratePromptEntry(raw.entry);
|
|
11211
12495
|
} catch {
|
|
11212
12496
|
return null;
|
|
@@ -11221,7 +12505,7 @@ var DefaultPromptStore = class {
|
|
|
11221
12505
|
async delete(id) {
|
|
11222
12506
|
const file = path8.join(this.dir, `${id}.json`);
|
|
11223
12507
|
try {
|
|
11224
|
-
await
|
|
12508
|
+
await fs6.unlink(file);
|
|
11225
12509
|
return true;
|
|
11226
12510
|
} catch {
|
|
11227
12511
|
return false;
|
|
@@ -11421,7 +12705,7 @@ var DefaultPromptLoader = class {
|
|
|
11421
12705
|
const files = await walkJson(dir);
|
|
11422
12706
|
for (const file of files) {
|
|
11423
12707
|
try {
|
|
11424
|
-
const parsed = JSON.parse(await
|
|
12708
|
+
const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
11425
12709
|
const migrated = migratePromptEntry(parsed);
|
|
11426
12710
|
if (migrated) out.push({ ...migrated, source: "builtin" });
|
|
11427
12711
|
} catch {
|
|
@@ -11435,7 +12719,7 @@ async function walkJson(dir) {
|
|
|
11435
12719
|
const out = [];
|
|
11436
12720
|
let entries;
|
|
11437
12721
|
try {
|
|
11438
|
-
entries = await
|
|
12722
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
11439
12723
|
} catch {
|
|
11440
12724
|
return out;
|
|
11441
12725
|
}
|
|
@@ -11612,7 +12896,8 @@ var ProviderBackedCompactor = class {
|
|
|
11612
12896
|
}
|
|
11613
12897
|
return new IntelligentCompactor({
|
|
11614
12898
|
...common,
|
|
11615
|
-
summarizerModel: this.opts.summarizerModel
|
|
12899
|
+
summarizerModel: this.opts.summarizerModel,
|
|
12900
|
+
oneShotOrchestrator: this.opts.oneShotOrchestrator
|
|
11616
12901
|
});
|
|
11617
12902
|
}
|
|
11618
12903
|
};
|
|
@@ -11627,9 +12912,12 @@ function readPolicy(ctx) {
|
|
|
11627
12912
|
// src/execution/tool-executor.ts
|
|
11628
12913
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11629
12914
|
import { isDeepStrictEqual } from "node:util";
|
|
11630
|
-
import * as
|
|
12915
|
+
import * as fs8 from "node:fs/promises";
|
|
11631
12916
|
import * as path10 from "node:path";
|
|
11632
12917
|
|
|
12918
|
+
// src/types/tool-executor.ts
|
|
12919
|
+
var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
|
|
12920
|
+
|
|
11633
12921
|
// src/types/tool-markers.ts
|
|
11634
12922
|
var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
|
|
11635
12923
|
|
|
@@ -11746,6 +13034,18 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
11746
13034
|
* Returns the execution results and the remaining output budget.
|
|
11747
13035
|
*/
|
|
11748
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) {
|
|
11749
13049
|
let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
|
|
11750
13050
|
const runOne = async (use0) => {
|
|
11751
13051
|
const start = Date.now();
|
|
@@ -11916,8 +13216,9 @@ ${errorDetails}`,
|
|
|
11916
13216
|
);
|
|
11917
13217
|
if (post.additionalContext) {
|
|
11918
13218
|
if (post.contextAs === "separate") {
|
|
11919
|
-
|
|
11920
|
-
|
|
13219
|
+
ctx.pendingPostToolContext = ctx.pendingPostToolContext ? `${ctx.pendingPostToolContext}
|
|
13220
|
+
|
|
13221
|
+
${post.additionalContext}` : post.additionalContext;
|
|
11921
13222
|
} else {
|
|
11922
13223
|
const appended = `
|
|
11923
13224
|
|
|
@@ -12017,14 +13318,56 @@ ${post.additionalContext}`;
|
|
|
12017
13318
|
};
|
|
12018
13319
|
}
|
|
12019
13320
|
/**
|
|
12020
|
-
*
|
|
12021
|
-
*
|
|
12022
|
-
*
|
|
12023
|
-
*
|
|
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.
|
|
12024
13365
|
*/
|
|
12025
13366
|
async executeTool(tool, use, ctx, budget) {
|
|
12026
|
-
|
|
12027
|
-
|
|
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
|
+
});
|
|
12028
13371
|
}
|
|
12029
13372
|
/**
|
|
12030
13373
|
* Async "produce" phase: run the tool, serialize, scrub, and spill an
|
|
@@ -12379,11 +13722,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
12379
13722
|
}
|
|
12380
13723
|
try {
|
|
12381
13724
|
const dir = path10.join(wstackGlobalRoot(), "tool-output");
|
|
12382
|
-
await
|
|
13725
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
12383
13726
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
12384
13727
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12385
13728
|
const filePath = path10.join(dir, `${stamp}-${safeTool}-${randomUUID3()}.log`);
|
|
12386
|
-
await
|
|
13729
|
+
await fs8.writeFile(filePath, content, "utf8");
|
|
12387
13730
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
12388
13731
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
12389
13732
|
const previewBytes = Math.min(
|
|
@@ -12431,11 +13774,233 @@ function sliceUtf8Suffix(text, maxBytes) {
|
|
|
12431
13774
|
if (first >= 56320 && first <= 57343) start++;
|
|
12432
13775
|
return text.slice(start);
|
|
12433
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
|
+
}
|
|
12434
13982
|
export {
|
|
12435
13983
|
AutoCompactionMiddleware,
|
|
12436
13984
|
AutonomousRunner,
|
|
12437
13985
|
BRAIN_EVALUATION_CASE_VERSION,
|
|
13986
|
+
BUILTIN_COUNCIL_PERSONAS,
|
|
13987
|
+
BUILTIN_COUNCIL_PROFILES,
|
|
13988
|
+
COUNCIL_JUDGE_PROMPT_PATH,
|
|
13989
|
+
COUNCIL_REFUSAL_OPTION_ID,
|
|
12438
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,
|
|
12439
14004
|
DEFAULT_RECOVERY_STRATEGIES,
|
|
12440
14005
|
DefaultDesignKitLoader,
|
|
12441
14006
|
DefaultErrorHandler,
|
|
@@ -12446,6 +14011,8 @@ export {
|
|
|
12446
14011
|
EternalAutonomyEngine,
|
|
12447
14012
|
HybridCompactor,
|
|
12448
14013
|
IntelligentCompactor,
|
|
14014
|
+
MAX_COUNCIL_CONCURRENCY,
|
|
14015
|
+
OneShotOrchestrator,
|
|
12449
14016
|
ParallelEternalEngine,
|
|
12450
14017
|
SelectiveCompactor,
|
|
12451
14018
|
ToolExecutor,
|
|
@@ -12455,6 +14022,11 @@ export {
|
|
|
12455
14022
|
applyModelRuntime,
|
|
12456
14023
|
assembleBrainTiers,
|
|
12457
14024
|
buildBrainUserMessage,
|
|
14025
|
+
buildCouncilJudgeSystemPrompt,
|
|
14026
|
+
buildCouncilJudgeUserPrompt,
|
|
14027
|
+
buildCouncilQuestionPrompt,
|
|
14028
|
+
buildCouncilVoterSystemPrompt,
|
|
14029
|
+
buildCouncilVoterUserPrompt,
|
|
12458
14030
|
buildGoalPreamble,
|
|
12459
14031
|
buildRecoveryStrategies,
|
|
12460
14032
|
clearActiveKit,
|
|
@@ -12463,6 +14035,8 @@ export {
|
|
|
12463
14035
|
createAutonomyBrain,
|
|
12464
14036
|
createBrainRuntime,
|
|
12465
14037
|
createCouncilBrainArbiter,
|
|
14038
|
+
createCouncilPersonaRegistry,
|
|
14039
|
+
createCouncilProfileRegistry,
|
|
12466
14040
|
createStrategyCompactor,
|
|
12467
14041
|
createTieredBrainArbiter,
|
|
12468
14042
|
designProjectDir,
|
|
@@ -12479,12 +14053,15 @@ export {
|
|
|
12479
14053
|
makeDesignDetectUserInputMiddleware,
|
|
12480
14054
|
makeDesignStudioRequestMiddleware,
|
|
12481
14055
|
mergeModelRuntime,
|
|
14056
|
+
normalizeCouncilProfile,
|
|
12482
14057
|
parseOptionDecision,
|
|
12483
14058
|
recordKitChoice,
|
|
12484
14059
|
renderPrompt,
|
|
12485
14060
|
resolveBrainConfigDefaults,
|
|
12486
14061
|
resolveBundledDesignKitsDir,
|
|
12487
14062
|
resolveCacheForRequest,
|
|
14063
|
+
resolveCouncilProfile,
|
|
14064
|
+
resolveCouncilVotes,
|
|
12488
14065
|
resolveModelRuntime,
|
|
12489
14066
|
resolveReasoningForRequest,
|
|
12490
14067
|
runBrainEvaluation,
|