@wrongstack/core 0.313.0 → 0.316.1
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/chronicle/index.js +84 -6391
- package/dist/chronicle/legacy-journal-import.d.ts +0 -6
- package/dist/chronicle/metrics-ingest.d.ts +14 -0
- package/dist/chronicle/metrics-schema.d.ts +10 -1
- package/dist/chronicle/metrics-store.d.ts +20 -2
- package/dist/chronicle/project-server.js +98 -4874
- package/dist/chronicle/sink.d.ts +13 -0
- package/dist/chronicle/sqlite-journal.d.ts +20 -2
- package/dist/chunk-25LSLC3G.js +334 -0
- package/dist/chunk-27QIURLN.js +1073 -0
- package/dist/chunk-2FK4WTFF.js +207 -0
- package/dist/chunk-2YU4WVBW.js +134 -0
- package/dist/chunk-3AGWVSS7.js +4608 -0
- package/dist/chunk-3BDV7RL3.js +211 -0
- package/dist/chunk-3ECENVHL.js +325 -0
- package/dist/chunk-3XPVB7BA.js +135 -0
- package/dist/chunk-45WI72ZB.js +16884 -0
- package/dist/chunk-4BMVPOHO.js +3748 -0
- package/dist/chunk-4I7HGVVA.js +363 -0
- package/dist/chunk-4LCZVJVF.js +317 -0
- package/dist/chunk-4SJNS7OB.js +217 -0
- package/dist/chunk-4VWO3ZXL.js +342 -0
- package/dist/chunk-57GEWK5X.js +26 -0
- package/dist/chunk-5BMIE7QG.js +46 -0
- package/dist/chunk-5S437PRC.js +50 -0
- package/dist/chunk-5XDUGTKN.js +511 -0
- package/dist/chunk-5YPSXQXM.js +1443 -0
- package/dist/chunk-6FHWXLPT.js +10 -0
- package/dist/chunk-6H5GEOET.js +2406 -0
- package/dist/chunk-6NWEVFEL.js +151 -0
- package/dist/chunk-AYW5YINX.js +58 -0
- package/dist/chunk-B4AMLZPB.js +9 -0
- package/dist/chunk-B7F4WVP4.js +65 -0
- package/dist/chunk-BJHVULHO.js +55 -0
- package/dist/chunk-BPBKKBUK.js +2085 -0
- package/dist/chunk-BYD345OX.js +589 -0
- package/dist/chunk-C27KDLOX.js +279 -0
- package/dist/chunk-CU546ESW.js +597 -0
- package/dist/chunk-DLHHXUAM.js +172 -0
- package/dist/chunk-DOTHQSJ2.js +429 -0
- package/dist/chunk-DYLPMV4Q.js +711 -0
- package/dist/chunk-EODDVJBM.js +82 -0
- package/dist/chunk-F3JWNST7.js +361 -0
- package/dist/chunk-FC2ZGRF3.js +140 -0
- package/dist/chunk-FCBTO76Z.js +5786 -0
- package/dist/chunk-FHZVFYSL.js +396 -0
- package/dist/chunk-FNXVT7HF.js +52 -0
- package/dist/chunk-FWNLEZYG.js +259 -0
- package/dist/chunk-FXQLIX3K.js +211 -0
- package/dist/chunk-G5S3S5ZN.js +38 -0
- package/dist/chunk-GDQ7CZRQ.js +116 -0
- package/dist/chunk-GJJGJB2R.js +1374 -0
- package/dist/chunk-GTCU6DZ3.js +1 -0
- package/dist/chunk-HA4ZYA6T.js +178 -0
- package/dist/chunk-HI3JGZR2.js +188 -0
- package/dist/chunk-HMLIGAU7.js +395 -0
- package/dist/chunk-HXKZ7OBQ.js +303 -0
- package/dist/chunk-I7DALG7J.js +292 -0
- package/dist/chunk-IRUTEHCE.js +960 -0
- package/dist/chunk-IYFBFX34.js +86 -0
- package/dist/chunk-J2WRWR37.js +1104 -0
- package/dist/chunk-JFPJ6LLQ.js +129 -0
- package/dist/chunk-JMULFFLN.js +9 -0
- package/dist/chunk-JO4MGB6Z.js +1264 -0
- package/dist/chunk-JVKB2TBL.js +48 -0
- package/dist/chunk-JX7VBZDK.js +31 -0
- package/dist/chunk-K6BXGITA.js +931 -0
- package/dist/chunk-KDQJCT2J.js +45 -0
- package/dist/chunk-KJ2ZTQSL.js +47 -0
- package/dist/chunk-KZMSZV32.js +187 -0
- package/dist/chunk-LA7CLDRW.js +129 -0
- package/dist/chunk-LHOA5DWA.js +28 -0
- package/dist/chunk-LSMMOS2Z.js +119 -0
- package/dist/chunk-LX5QWLBL.js +27 -0
- package/dist/chunk-LXWCMQNJ.js +789 -0
- package/dist/chunk-M27LIEKV.js +2829 -0
- package/dist/chunk-MJPHJRAL.js +663 -0
- package/dist/chunk-MTL4O5YR.js +228 -0
- package/dist/chunk-NDBIPFNY.js +102 -0
- package/dist/chunk-NEMIWEXC.js +1199 -0
- package/dist/chunk-NHJ75HXY.js +1 -0
- package/dist/chunk-NIOAGFPB.js +1510 -0
- package/dist/chunk-NS52ISWS.js +355 -0
- package/dist/chunk-NSOFPKGT.js +5352 -0
- package/dist/chunk-NVIZG63R.js +757 -0
- package/dist/chunk-O37BQ65S.js +21 -0
- package/dist/chunk-OOYZPOXM.js +308 -0
- package/dist/chunk-OSGLYW5J.js +28 -0
- package/dist/chunk-PFCMSSUF.js +1640 -0
- package/dist/chunk-PFZO3C6H.js +14 -0
- package/dist/chunk-PGRVXIMY.js +158 -0
- package/dist/chunk-PHH7LD45.js +113 -0
- package/dist/chunk-PISBFBJN.js +109 -0
- package/dist/chunk-POOOIWE5.js +9 -0
- package/dist/chunk-PQNS3U7Z.js +1240 -0
- package/dist/chunk-QSTSJOCK.js +402 -0
- package/dist/chunk-RFQMOKWK.js +282 -0
- package/dist/chunk-RJIZEJ42.js +30 -0
- package/dist/chunk-RTOTNK3P.js +484 -0
- package/dist/chunk-RXKSDUAU.js +1172 -0
- package/dist/chunk-S45JPKQN.js +3317 -0
- package/dist/chunk-SAFHACFA.js +152 -0
- package/dist/chunk-SQJVFEBV.js +41 -0
- package/dist/chunk-SVT4RNTR.js +49 -0
- package/dist/chunk-SWUFN7XM.js +43 -0
- package/dist/chunk-SXMVYC5H.js +174 -0
- package/dist/chunk-SZ6AYUIZ.js +23 -0
- package/dist/chunk-TBRL2RCC.js +9 -0
- package/dist/chunk-TCCHINEF.js +655 -0
- package/dist/chunk-THEGTRAG.js +316 -0
- package/dist/chunk-TR4LQE7L.js +2256 -0
- package/dist/chunk-TRT2HRWZ.js +18 -0
- package/dist/chunk-TWJMGXCI.js +900 -0
- package/dist/chunk-U45AXT5D.js +1023 -0
- package/dist/chunk-U4FWBSO3.js +509 -0
- package/dist/chunk-UBGDX6FI.js +147 -0
- package/dist/chunk-UJILKYES.js +75 -0
- package/dist/chunk-ULMAQV3T.js +234 -0
- package/dist/chunk-VVBP6KGT.js +9 -0
- package/dist/chunk-W5VITDM2.js +168 -0
- package/dist/chunk-WAHQVNWT.js +194 -0
- package/dist/chunk-WBPVKEEE.js +227 -0
- package/dist/chunk-WGGYRBXH.js +5434 -0
- package/dist/chunk-WLYKU45P.js +5770 -0
- package/dist/chunk-WP7BVANJ.js +53 -0
- package/dist/chunk-WQQON3SV.js +473 -0
- package/dist/chunk-WQTOVMGV.js +119 -0
- package/dist/chunk-XBDP4QPW.js +7 -0
- package/dist/chunk-XEBHSJLT.js +103 -0
- package/dist/chunk-XF4VUMKW.js +231 -0
- package/dist/chunk-XQ2ECNK3.js +1 -0
- package/dist/chunk-Y5YXDM7D.js +74 -0
- package/dist/chunk-YOPAUVU4.js +2588 -0
- package/dist/chunk-YPT4A46F.js +362 -0
- package/dist/chunk-YWXT3JNM.js +425 -0
- package/dist/chunk-YY6APYUM.js +49 -0
- package/dist/chunk-Z2IJUO45.js +691 -0
- package/dist/chunk-ZHZ2Y2DH.js +1643 -0
- package/dist/chunk-ZT7JNIGX.js +152 -0
- package/dist/coordination/agent-monitor.d.ts +9 -1
- package/dist/coordination/agent-subagent-runner.d.ts +1 -1
- package/dist/coordination/agents/agent-prompts.d.ts +8 -0
- package/dist/coordination/agents/index.d.ts +33 -1
- package/dist/coordination/agents/index.js +184 -6269
- package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
- package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
- package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
- package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
- package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
- package/dist/coordination/agents/types.d.ts +13 -2
- package/dist/coordination/brain-monitor.d.ts +30 -7
- package/dist/coordination/collab-debug.d.ts +1 -1
- package/dist/coordination/collab-pause.d.ts +1 -1
- package/dist/coordination/delegate-tool.d.ts +7 -5
- package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
- package/dist/coordination/director-options.d.ts +7 -0
- package/dist/coordination/director-spawn-model.d.ts +17 -0
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/director.d.ts +12 -0
- package/dist/coordination/dispatcher.d.ts +8 -0
- package/dist/coordination/fleet-bus.d.ts +35 -4
- package/dist/coordination/fleet-supervisor.d.ts +10 -2
- package/dist/coordination/fleet.d.ts +16 -7
- package/dist/coordination/index.d.ts +4 -0
- package/dist/coordination/index.js +402 -34324
- package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
- package/dist/coordination/mailbox-http-router.d.ts +4 -3
- package/dist/coordination/mailbox-http-validation.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +48 -3276
- package/dist/coordination/mailbox-types.d.ts +24 -6
- package/dist/coordination/model-tier-leader.d.ts +122 -0
- package/dist/coordination/model-tier.d.ts +122 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
- package/dist/coordination/origin-session.d.ts +16 -0
- package/dist/coordination/provider-status-tracker.d.ts +41 -5
- package/dist/coordination/session-note-hub.d.ts +44 -0
- package/dist/coordination/session-note-tool.d.ts +10 -0
- package/dist/core/agent.d.ts +15 -2
- package/dist/core/context.d.ts +22 -3
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +239 -12360
- package/dist/core/model-ref.js +5 -23
- package/dist/core/provider-runner.d.ts +36 -1
- package/dist/core/request-conversation-binding.d.ts +37 -0
- package/dist/core/session-notes.d.ts +25 -0
- package/dist/core/system-prompt-builder.d.ts +11 -2
- package/dist/core/system-prompt-variants.d.ts +78 -0
- package/dist/design/index.js +50 -1087
- package/dist/execution/auto-compaction-middleware.d.ts +11 -0
- package/dist/execution/autonomy-brain.d.ts +2 -1
- package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
- package/dist/execution/compaction-core.d.ts +4 -4
- package/dist/execution/eternal-autonomy.d.ts +1 -1
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1615 -21895
- package/dist/execution/model-runtime.d.ts +0 -7
- package/dist/execution/parallel-eternal-engine.d.ts +7 -7
- package/dist/execution/prompt-enhancer.js +16 -535
- package/dist/execution/retry-policy.d.ts +31 -1
- package/dist/extension/index.js +7 -311
- package/dist/goal/index.js +33 -3435
- package/dist/hooks/index.js +14 -1138
- package/dist/hq/index.js +114 -5139
- package/dist/hq/persistence/event-log.d.ts +0 -1
- package/dist/hq/persistence.d.ts +4 -4
- package/dist/hq/protocol.js +21 -668
- package/dist/hq/publisher.d.ts +2 -0
- package/dist/index.d.ts +14 -11
- package/dist/index.js +1502 -92655
- package/dist/infrastructure/index.d.ts +4 -4
- package/dist/infrastructure/index.js +504 -5230
- package/dist/infrastructure/token-counter.d.ts +9 -0
- package/dist/kernel/events/agent-events.d.ts +77 -3
- package/dist/kernel/events/brain-events.d.ts +12 -0
- package/dist/kernel/events/provider-events.d.ts +22 -1
- package/dist/kernel/events/session-events.d.ts +14 -0
- package/dist/kernel/events/tool-events.d.ts +13 -0
- package/dist/kernel/index.js +14 -992
- package/dist/mailbox-attach.d.ts +1 -1
- package/dist/models/index.js +41 -1117
- package/dist/notifications/index.js +3 -108
- package/dist/observability/event-bridge.d.ts +25 -5
- package/dist/observability/index.d.ts +9 -8
- package/dist/observability/index.js +477 -631
- package/dist/observability/tool-usage-source.d.ts +65 -0
- package/dist/plugin/index.d.ts +5 -0
- package/dist/plugin/index.js +600 -14835
- package/dist/plugins/auto-review-plugin.d.ts +3 -2
- package/dist/plugins/review-finding-commands.d.ts +0 -8
- package/dist/plugins/review-finding-store.js +22 -0
- package/dist/plugins/review-finding-types.js +19 -0
- package/dist/plugins/review-report-integration.js +21 -0
- package/dist/plugins/review-report-store.js +20 -0
- package/dist/plugins/review-report-types.js +13 -0
- package/dist/prompts/index.js +14 -948
- package/dist/registry/index.js +9 -822
- package/dist/registry/tool-registry.d.ts +59 -9
- package/dist/registry-7N4O3E7W.js +14 -0
- package/dist/replay/index.js +6 -84
- package/dist/security/capabilities.d.ts +6 -0
- package/dist/security/file-permissions.d.ts +1 -1
- package/dist/security/index.js +161 -2235
- package/dist/security/permission-policy.d.ts +12 -1
- package/dist/security/secret-scrubber.d.ts +15 -0
- package/dist/session-catalog/index.d.ts +1 -0
- package/dist/session-catalog/index.js +41 -2103
- package/dist/session-catalog/project-server.js +59 -1435
- package/dist/session-catalog/protocol.d.ts +32 -1
- package/dist/session-catalog/registry.d.ts +17 -1
- package/dist/session-catalog/session-agents.d.ts +56 -0
- package/dist/session-catalog/store-rebuild.d.ts +14 -0
- package/dist/session-catalog/store-schema.d.ts +26 -0
- package/dist/session-catalog/store.d.ts +27 -1
- package/dist/session-note-attach.d.ts +9 -0
- package/dist/skills/index.js +35 -1578
- package/dist/statusline/index.d.ts +43 -0
- package/dist/statusline/index.js +102 -0
- package/dist/storage/board-store-port.d.ts +2 -2
- package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
- package/dist/storage/config-loader.d.ts +3 -2
- package/dist/storage/file-session-writer.d.ts +4 -2
- package/dist/storage/goal-coordination.d.ts +6 -1
- package/dist/storage/goal-kanban.d.ts +4 -1
- package/dist/storage/index.d.ts +12 -4
- package/dist/storage/index.js +198 -15601
- package/dist/storage/input-history-store.d.ts +2 -4
- package/dist/storage/memory-curator.d.ts +133 -0
- package/dist/storage/session-agent-attribution.d.ts +39 -0
- package/dist/storage/session-doctor.d.ts +109 -0
- package/dist/storage/session-event-bridge.d.ts +5 -7
- package/dist/storage/session-outcome.d.ts +42 -0
- package/dist/storage/session-recovery.d.ts +55 -0
- package/dist/storage/session-store/load-session-data.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +1 -1
- package/dist/storage/session-store/resume-session.d.ts +0 -2
- package/dist/storage/session-store/types.d.ts +8 -0
- package/dist/storage/session-store.d.ts +3 -3
- package/dist/storage/session-summary-tracker.d.ts +8 -1
- package/dist/storage/session-write-buffer.d.ts +18 -0
- package/dist/storage/session-writer-truncate.d.ts +2 -0
- package/dist/tasking/index.js +10 -676
- package/dist/tools/fallback-manage-tools.d.ts +6 -4
- package/dist/tools/index.js +52 -9590
- package/dist/tools/model-tier-set-tool.d.ts +66 -0
- package/dist/types/agent-bridge.d.ts +2 -1
- package/dist/types/config/autonomy.d.ts +5 -0
- package/dist/types/config/mcp-features.d.ts +7 -0
- package/dist/types/config/model-tiers.d.ts +148 -0
- package/dist/types/config/root.d.ts +9 -0
- package/dist/types/config/tools.d.ts +73 -0
- package/dist/types/config/ui.d.ts +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/context.d.ts +2 -0
- package/dist/types/default-config.d.ts +6 -0
- package/dist/types/errors.d.ts +20 -1
- package/dist/types/index.d.ts +11 -9
- package/dist/types/index.js +131 -1342
- package/dist/types/multi-agent.d.ts +43 -0
- package/dist/types/plugin.d.ts +1 -1
- package/dist/types/provider-runner.d.ts +8 -0
- package/dist/types/provider.d.ts +15 -0
- package/dist/types/runtime-capability-manifest.d.ts +1 -1
- package/dist/types/secret-scrubber.d.ts +15 -0
- package/dist/types/session-markers.d.ts +37 -10
- package/dist/types/session-markers.js +8 -144
- package/dist/types/session-timeline.d.ts +197 -0
- package/dist/types/session-timeline.js +12 -0
- package/dist/types/session.d.ts +172 -11
- package/dist/types/system-prompt.d.ts +27 -0
- package/dist/utils/config-backup.d.ts +9 -1
- package/dist/utils/error.js +3 -4
- package/dist/utils/expect-defined.js +3 -9
- package/dist/utils/heap-watchdog.js +6 -706
- package/dist/utils/index.js +271 -5685
- package/dist/utils/message-invariants.d.ts +4 -1
- package/dist/utils/regex-guard.d.ts +1 -1
- package/dist/utils/sage-output-block.js +5 -42
- package/dist/utils/socket-path.d.ts +1 -1
- package/dist/utils/token-estimate.d.ts +6 -2
- package/dist/utils/wstack-paths.d.ts +2 -0
- package/dist/wiring/proxy-rewrite.d.ts +46 -0
- package/dist/wiring/proxy-rewrite.js +24 -121
- package/dist/worktree/index.js +6 -887
- package/instructions/agents/explore-companion.md +45 -18
- package/instructions/agents/memory-curator.md +34 -22
- package/instructions/coordination/director-preamble.md +12 -7
- package/instructions/coordination/subagent-baseline.md +26 -5
- package/instructions/leader-after-task.md +4 -1
- package/instructions/llm/chimera-review.md +30 -9
- package/instructions/llm/memory-curator.md +31 -0
- package/instructions/sections/tool/delegation-compact.md +2 -0
- package/instructions/sections/tool/delegation-full.md +5 -1
- package/instructions/sections/tool/mailbox-compact.md +5 -1
- package/instructions/sections/tool/mailbox-full.md +3 -0
- package/instructions/sections/tool/session-note-compact.md +3 -0
- package/instructions/sections/tool/session-note-full.md +15 -0
- package/instructions/system-lite.md +9 -4
- package/instructions/system-pro.md +66 -143
- package/instructions/system.md +73 -175
- package/package.json +32 -4
- package/dist/coordination/agents.d.ts +0 -8
- package/dist/coordination/transport.d.ts +0 -2
- package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
|
@@ -0,0 +1,1640 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readBundledInstructionText,
|
|
3
|
+
renderInstructionTemplate
|
|
4
|
+
} from "./chunk-5S437PRC.js";
|
|
5
|
+
import {
|
|
6
|
+
evaluateModelCalendar
|
|
7
|
+
} from "./chunk-WQQON3SV.js";
|
|
8
|
+
import {
|
|
9
|
+
estimateRequestTokens
|
|
10
|
+
} from "./chunk-HMLIGAU7.js";
|
|
11
|
+
import {
|
|
12
|
+
ProviderError,
|
|
13
|
+
isFallbackWorthy
|
|
14
|
+
} from "./chunk-3ECENVHL.js";
|
|
15
|
+
import {
|
|
16
|
+
isTextBlock
|
|
17
|
+
} from "./chunk-O37BQ65S.js";
|
|
18
|
+
|
|
19
|
+
// src/execution/council-orchestrator-helpers.ts
|
|
20
|
+
var COUNCIL_REFUSAL_OPTION_ID = "council_refuse";
|
|
21
|
+
var DEFAULT_COUNCIL_MAX_CONCURRENCY = 3;
|
|
22
|
+
var MAX_COUNCIL_CONCURRENCY = 8;
|
|
23
|
+
var OVERALL_TIMEOUT_REASON = "Council overall timeout exceeded.";
|
|
24
|
+
var CALL_CANCELLED_REASON = "Cancelled.";
|
|
25
|
+
function validateConcurrency(value) {
|
|
26
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_COUNCIL_CONCURRENCY) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`CouncilOrchestrator: maxConcurrency must be an integer in [1, ${MAX_COUNCIL_CONCURRENCY}].`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function validateRefusalCollision(question, refusalOptionId) {
|
|
34
|
+
if (question.options?.some((option) => option.id.trim() === refusalOptionId)) {
|
|
35
|
+
throw new Error(`CouncilOrchestrator: option id "${refusalOptionId}" is reserved.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function optionLabel(question, optionId) {
|
|
39
|
+
if (!optionId) return void 0;
|
|
40
|
+
return question.options?.find((option) => option.id.trim() === optionId)?.label.trim();
|
|
41
|
+
}
|
|
42
|
+
async function mapConcurrent(items, concurrency, worker) {
|
|
43
|
+
const results = new Array(items.length);
|
|
44
|
+
let next = 0;
|
|
45
|
+
const run = async () => {
|
|
46
|
+
while (true) {
|
|
47
|
+
const index = next++;
|
|
48
|
+
if (index >= items.length) return;
|
|
49
|
+
const item = items[index];
|
|
50
|
+
if (item !== void 0) results[index] = await worker(item, index);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => run()));
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
function distinctTargetKeys(votes, profile) {
|
|
57
|
+
const keys = [];
|
|
58
|
+
for (const vote of votes) {
|
|
59
|
+
if (vote.status !== "valid") continue;
|
|
60
|
+
const provider = vote.provider?.trim() ?? "";
|
|
61
|
+
const model = vote.model?.trim() ?? "";
|
|
62
|
+
if (profile.distinctness === "provider") {
|
|
63
|
+
if (provider) keys.push(provider);
|
|
64
|
+
} else if (provider || model) {
|
|
65
|
+
keys.push(`${provider}/${model}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return keys;
|
|
69
|
+
}
|
|
70
|
+
function distinctTargetCount(votes, profile) {
|
|
71
|
+
return new Set(distinctTargetKeys(votes, profile)).size;
|
|
72
|
+
}
|
|
73
|
+
function distinctnessWarnings(votes, profile) {
|
|
74
|
+
if (profile.distinctness === "none") return [];
|
|
75
|
+
const keys = distinctTargetKeys(votes, profile);
|
|
76
|
+
const distinct = new Set(keys).size;
|
|
77
|
+
if (keys.length > 1 && distinct < keys.length) {
|
|
78
|
+
return [
|
|
79
|
+
`Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${keys.length} valid vote(s).`
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
function cancelledVote(seat) {
|
|
85
|
+
return {
|
|
86
|
+
seatId: seat.id,
|
|
87
|
+
persona: seat.persona,
|
|
88
|
+
status: "cancelled",
|
|
89
|
+
...seat.target?.providerId ? { provider: seat.target.providerId } : {},
|
|
90
|
+
...seat.target?.model ? { model: seat.target.model } : {},
|
|
91
|
+
durationMs: 0,
|
|
92
|
+
error: CALL_CANCELLED_REASON
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function callMetadata(result) {
|
|
96
|
+
return {
|
|
97
|
+
...result.provider ? { provider: result.provider } : {},
|
|
98
|
+
...result.model ? { model: result.model } : {},
|
|
99
|
+
...result.fromFallback ? { fromFallback: true } : {},
|
|
100
|
+
durationMs: result.durationMs
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function addUsage(usage, result) {
|
|
104
|
+
usage.calls += Math.max(1, result.attempts ?? 1);
|
|
105
|
+
usage.inputTokens += result.tokens.input;
|
|
106
|
+
usage.outputTokens += result.tokens.output;
|
|
107
|
+
usage.totalTokens += result.tokens.total;
|
|
108
|
+
}
|
|
109
|
+
function usageResult(usage, startedAt) {
|
|
110
|
+
return Object.freeze({ ...usage, durationMs: Math.max(0, Date.now() - startedAt) });
|
|
111
|
+
}
|
|
112
|
+
function resultEnvelope(input) {
|
|
113
|
+
const validVoteCount = input.votes.filter((vote) => vote.status === "valid").length;
|
|
114
|
+
return {
|
|
115
|
+
status: input.status,
|
|
116
|
+
...input.answer ? { answer: input.answer } : {},
|
|
117
|
+
...input.optionId ? { optionId: input.optionId } : {},
|
|
118
|
+
...input.reason ? { reason: input.reason } : {},
|
|
119
|
+
resolution: input.resolution,
|
|
120
|
+
votes: Object.freeze([...input.votes]),
|
|
121
|
+
configuredSeatCount: input.profile.seats.length,
|
|
122
|
+
validVoteCount,
|
|
123
|
+
distinctTargetCount: distinctTargetCount(input.votes, input.profile),
|
|
124
|
+
judgeUsed: input.judgeUsed ?? false,
|
|
125
|
+
usage: usageResult(input.usage, input.startedAt),
|
|
126
|
+
...input.warnings.length > 0 ? { warnings: Object.freeze([...input.warnings]) } : {},
|
|
127
|
+
...input.errors.length > 0 ? { errors: Object.freeze([...input.errors]) } : {}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/execution/council-personas.ts
|
|
132
|
+
var PERSONA_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
133
|
+
var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
|
|
134
|
+
freezePersona({
|
|
135
|
+
id: "executor",
|
|
136
|
+
name: "Executor",
|
|
137
|
+
description: "Tests whether a proposal is practical and keeps useful work moving.",
|
|
138
|
+
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.",
|
|
139
|
+
tags: ["delivery", "feasibility", "progress"]
|
|
140
|
+
}),
|
|
141
|
+
freezePersona({
|
|
142
|
+
id: "skeptic",
|
|
143
|
+
name: "Skeptic",
|
|
144
|
+
description: "Challenges assumptions and looks for concrete failure modes.",
|
|
145
|
+
instruction: "Identify unsupported assumptions, unsafe premises, irreversible consequences, and credible failure modes. Oppose or refuse only when you can name a concrete reason.",
|
|
146
|
+
defaultVeto: true,
|
|
147
|
+
tags: ["risk", "assumptions", "failure-modes"]
|
|
148
|
+
}),
|
|
149
|
+
freezePersona({
|
|
150
|
+
id: "auditor",
|
|
151
|
+
name: "Auditor",
|
|
152
|
+
description: "Evaluates cost, waste, evidence quality, and expected value.",
|
|
153
|
+
instruction: "Compare resource cost, opportunity cost, evidence quality, reversibility, and expected value. Prefer the option that achieves the objective with the least avoidable waste.",
|
|
154
|
+
tags: ["cost", "evidence", "efficiency"]
|
|
155
|
+
}),
|
|
156
|
+
freezePersona({
|
|
157
|
+
id: "security",
|
|
158
|
+
name: "Security Reviewer",
|
|
159
|
+
description: "Examines trust boundaries, abuse cases, and security impact.",
|
|
160
|
+
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.",
|
|
161
|
+
defaultVeto: true,
|
|
162
|
+
tags: ["security", "trust", "abuse-cases"]
|
|
163
|
+
}),
|
|
164
|
+
freezePersona({
|
|
165
|
+
id: "maintainer",
|
|
166
|
+
name: "Maintainer",
|
|
167
|
+
description: "Evaluates complexity, compatibility, and long-term ownership.",
|
|
168
|
+
instruction: "Evaluate behavioral compatibility, conceptual complexity, testability, migration cost, and future maintenance. Prefer the smallest design that remains clear and extensible.",
|
|
169
|
+
tags: ["maintenance", "compatibility", "simplicity"]
|
|
170
|
+
}),
|
|
171
|
+
freezePersona({
|
|
172
|
+
id: "user-advocate",
|
|
173
|
+
name: "User Advocate",
|
|
174
|
+
description: "Evaluates the decision from the affected user\u2019s perspective.",
|
|
175
|
+
instruction: "Evaluate usability, surprise, accessibility, failure recovery, and whether the outcome solves the user\u2019s stated need. Prefer understandable behavior with safe recovery paths.",
|
|
176
|
+
tags: ["users", "usability", "accessibility"]
|
|
177
|
+
})
|
|
178
|
+
]);
|
|
179
|
+
var BUILTIN_COUNCIL_PERSONA_IDS = Object.freeze(
|
|
180
|
+
BUILTIN_COUNCIL_PERSONAS.map((persona) => persona.id)
|
|
181
|
+
);
|
|
182
|
+
var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
|
|
183
|
+
byId;
|
|
184
|
+
constructor(personas = []) {
|
|
185
|
+
const entries = /* @__PURE__ */ new Map();
|
|
186
|
+
for (const persona of personas) {
|
|
187
|
+
const normalized = freezePersona(persona);
|
|
188
|
+
if (entries.has(normalized.id)) {
|
|
189
|
+
throw new Error(`CouncilPersonaRegistry: duplicate persona id "${normalized.id}".`);
|
|
190
|
+
}
|
|
191
|
+
entries.set(normalized.id, normalized);
|
|
192
|
+
}
|
|
193
|
+
this.byId = entries;
|
|
194
|
+
}
|
|
195
|
+
has(id) {
|
|
196
|
+
return this.byId.has(id);
|
|
197
|
+
}
|
|
198
|
+
get(id) {
|
|
199
|
+
return this.byId.get(id);
|
|
200
|
+
}
|
|
201
|
+
require(id) {
|
|
202
|
+
const persona = this.get(id);
|
|
203
|
+
if (!persona) throw new Error(`CouncilPersonaRegistry: unknown persona "${id}".`);
|
|
204
|
+
return persona;
|
|
205
|
+
}
|
|
206
|
+
list() {
|
|
207
|
+
return Object.freeze([...this.byId.values()]);
|
|
208
|
+
}
|
|
209
|
+
/** Return a new registry; the current registry is never mutated. */
|
|
210
|
+
with(persona, opts = {}) {
|
|
211
|
+
const normalized = freezePersona(persona);
|
|
212
|
+
if (this.has(normalized.id) && opts.replace !== true) {
|
|
213
|
+
throw new Error(`CouncilPersonaRegistry: persona "${normalized.id}" already exists.`);
|
|
214
|
+
}
|
|
215
|
+
return new _CouncilPersonaRegistry([
|
|
216
|
+
...this.list().filter((entry) => entry.id !== normalized.id),
|
|
217
|
+
normalized
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var DEFAULT_COUNCIL_PERSONA_REGISTRY = new CouncilPersonaRegistry(
|
|
222
|
+
BUILTIN_COUNCIL_PERSONAS
|
|
223
|
+
);
|
|
224
|
+
function createCouncilPersonaRegistry(additional = []) {
|
|
225
|
+
let registry = DEFAULT_COUNCIL_PERSONA_REGISTRY;
|
|
226
|
+
for (const persona of additional) registry = registry.with(persona);
|
|
227
|
+
return registry;
|
|
228
|
+
}
|
|
229
|
+
function freezePersona(persona) {
|
|
230
|
+
const id = persona.id.trim();
|
|
231
|
+
const name = persona.name.trim();
|
|
232
|
+
const description = persona.description.trim();
|
|
233
|
+
const instruction = persona.instruction.trim();
|
|
234
|
+
if (!PERSONA_ID_RE.test(id)) {
|
|
235
|
+
throw new Error(`CouncilPersonaRegistry: invalid persona id "${persona.id}".`);
|
|
236
|
+
}
|
|
237
|
+
if (!name) throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a name.`);
|
|
238
|
+
if (!description) {
|
|
239
|
+
throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a description.`);
|
|
240
|
+
}
|
|
241
|
+
if (!instruction) {
|
|
242
|
+
throw new Error(`CouncilPersonaRegistry: persona "${id}" requires an instruction.`);
|
|
243
|
+
}
|
|
244
|
+
if (persona.defaultWeight !== void 0 && (!Number.isFinite(persona.defaultWeight) || persona.defaultWeight <= 0)) {
|
|
245
|
+
throw new Error(`CouncilPersonaRegistry: persona "${id}" has an invalid default weight.`);
|
|
246
|
+
}
|
|
247
|
+
const tags = Object.freeze(
|
|
248
|
+
[...new Set((persona.tags ?? []).map((tag) => tag.trim()).filter(Boolean))]
|
|
249
|
+
);
|
|
250
|
+
return Object.freeze({
|
|
251
|
+
id,
|
|
252
|
+
name,
|
|
253
|
+
description,
|
|
254
|
+
instruction,
|
|
255
|
+
...persona.defaultWeight !== void 0 ? { defaultWeight: persona.defaultWeight } : {},
|
|
256
|
+
...persona.defaultVeto !== void 0 ? { defaultVeto: persona.defaultVeto } : {},
|
|
257
|
+
...tags.length > 0 ? { tags } : {}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/execution/council-profiles.ts
|
|
262
|
+
var PROFILE_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
263
|
+
var DISTINCTNESS_VALUES = /* @__PURE__ */ new Set([
|
|
264
|
+
"none",
|
|
265
|
+
"model",
|
|
266
|
+
"provider"
|
|
267
|
+
]);
|
|
268
|
+
var DEFAULT_COUNCIL_QUORUM_FRACTION = 0.5;
|
|
269
|
+
var DEFAULT_COUNCIL_APPROVAL_FRACTION = 0.5;
|
|
270
|
+
var DEFAULT_COUNCIL_VOTER_MAX_TOKENS = 300;
|
|
271
|
+
var DEFAULT_COUNCIL_JUDGE_MAX_TOKENS = 500;
|
|
272
|
+
var DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS = 3e4;
|
|
273
|
+
var DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS = 9e4;
|
|
274
|
+
var BUILTIN_COUNCIL_PROFILES = Object.freeze([
|
|
275
|
+
Object.freeze({
|
|
276
|
+
id: "balanced",
|
|
277
|
+
name: "Balanced",
|
|
278
|
+
description: "Three complementary lenses plus an independent judge for general decisions.",
|
|
279
|
+
seats: Object.freeze([
|
|
280
|
+
Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
|
|
281
|
+
Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
|
|
282
|
+
Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
|
|
283
|
+
]),
|
|
284
|
+
judge: Object.freeze({ role: "reviewer" }),
|
|
285
|
+
quorumFraction: 0.5,
|
|
286
|
+
approvalFraction: 0.5,
|
|
287
|
+
distinctness: "model"
|
|
288
|
+
}),
|
|
289
|
+
Object.freeze({
|
|
290
|
+
id: "fast",
|
|
291
|
+
name: "Fast",
|
|
292
|
+
description: "Two-seat panel without a judge for quick, low-cost decisions.",
|
|
293
|
+
seats: Object.freeze([
|
|
294
|
+
Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
|
|
295
|
+
Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) })
|
|
296
|
+
]),
|
|
297
|
+
judge: false,
|
|
298
|
+
quorumFraction: 0.5,
|
|
299
|
+
approvalFraction: 0.5,
|
|
300
|
+
distinctness: "none",
|
|
301
|
+
voterMaxTokens: 200,
|
|
302
|
+
perCallTimeoutMs: 2e4,
|
|
303
|
+
overallTimeoutMs: 3e4
|
|
304
|
+
}),
|
|
305
|
+
Object.freeze({
|
|
306
|
+
id: "risk-review",
|
|
307
|
+
name: "Risk Review",
|
|
308
|
+
description: "Security, assumptions, maintenance, and cost review for high-impact choices.",
|
|
309
|
+
seats: Object.freeze([
|
|
310
|
+
Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
|
|
311
|
+
Object.freeze({ persona: "security", target: Object.freeze({ role: "security-reviewer" }) }),
|
|
312
|
+
Object.freeze({ persona: "maintainer", target: Object.freeze({ role: "reviewer" }) }),
|
|
313
|
+
Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
|
|
314
|
+
]),
|
|
315
|
+
judge: Object.freeze({ role: "architect" }),
|
|
316
|
+
quorumFraction: 0.75,
|
|
317
|
+
approvalFraction: 0.5,
|
|
318
|
+
distinctness: "provider",
|
|
319
|
+
judgeMaxTokens: 700,
|
|
320
|
+
overallTimeoutMs: 12e4
|
|
321
|
+
})
|
|
322
|
+
]);
|
|
323
|
+
var CouncilProfileRegistry = class _CouncilProfileRegistry {
|
|
324
|
+
byId;
|
|
325
|
+
personas;
|
|
326
|
+
constructor(profiles = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
|
|
327
|
+
this.personas = personas;
|
|
328
|
+
const entries = /* @__PURE__ */ new Map();
|
|
329
|
+
for (const profile of profiles) {
|
|
330
|
+
const normalized = normalizeCouncilProfile(profile, personas);
|
|
331
|
+
if (entries.has(normalized.id)) {
|
|
332
|
+
throw new Error(`CouncilProfileRegistry: duplicate profile id "${normalized.id}".`);
|
|
333
|
+
}
|
|
334
|
+
entries.set(normalized.id, normalized);
|
|
335
|
+
}
|
|
336
|
+
this.byId = entries;
|
|
337
|
+
}
|
|
338
|
+
has(id) {
|
|
339
|
+
return this.byId.has(id);
|
|
340
|
+
}
|
|
341
|
+
get(id) {
|
|
342
|
+
return this.byId.get(id);
|
|
343
|
+
}
|
|
344
|
+
require(id) {
|
|
345
|
+
const profile = this.get(id);
|
|
346
|
+
if (!profile) throw new Error(`CouncilProfileRegistry: unknown profile "${id}".`);
|
|
347
|
+
return profile;
|
|
348
|
+
}
|
|
349
|
+
list() {
|
|
350
|
+
return Object.freeze([...this.byId.values()]);
|
|
351
|
+
}
|
|
352
|
+
/** Return a new registry; the current registry is never mutated. */
|
|
353
|
+
with(profile, opts = {}) {
|
|
354
|
+
const personas = opts.personas ?? this.personas;
|
|
355
|
+
const normalized = normalizeCouncilProfile(profile, personas);
|
|
356
|
+
if (this.has(normalized.id) && opts.replace !== true) {
|
|
357
|
+
throw new Error(`CouncilProfileRegistry: profile "${normalized.id}" already exists.`);
|
|
358
|
+
}
|
|
359
|
+
return new _CouncilProfileRegistry(
|
|
360
|
+
[
|
|
361
|
+
...this.list().filter((entry) => entry.id !== normalized.id),
|
|
362
|
+
profile
|
|
363
|
+
],
|
|
364
|
+
personas
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
var DEFAULT_COUNCIL_PROFILE_REGISTRY = new CouncilProfileRegistry(
|
|
369
|
+
BUILTIN_COUNCIL_PROFILES
|
|
370
|
+
);
|
|
371
|
+
function createCouncilProfileRegistry(additional = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
|
|
372
|
+
return new CouncilProfileRegistry([...BUILTIN_COUNCIL_PROFILES, ...additional], personas);
|
|
373
|
+
}
|
|
374
|
+
function resolveCouncilProfile(profile, opts = {}) {
|
|
375
|
+
const personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
|
|
376
|
+
if (profile && typeof profile !== "string") return normalizeCouncilProfile(profile, personas);
|
|
377
|
+
const id = profile ?? opts.defaultProfile ?? "balanced";
|
|
378
|
+
return (opts.registry ?? DEFAULT_COUNCIL_PROFILE_REGISTRY).require(id);
|
|
379
|
+
}
|
|
380
|
+
function normalizeCouncilProfile(profile, personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
|
|
381
|
+
const id = profile.id.trim();
|
|
382
|
+
if (!PROFILE_ID_RE.test(id)) {
|
|
383
|
+
throw new Error(`CouncilProfileRegistry: invalid profile id "${profile.id}".`);
|
|
384
|
+
}
|
|
385
|
+
if (profile.seats.length === 0) {
|
|
386
|
+
throw new Error(`CouncilProfileRegistry: profile "${id}" requires at least one seat.`);
|
|
387
|
+
}
|
|
388
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
389
|
+
const seats = profile.seats.map((seat) => {
|
|
390
|
+
const persona = personas.require(seat.persona.trim());
|
|
391
|
+
const explicitSeatId = seat.id?.trim();
|
|
392
|
+
if (explicitSeatId && usedIds.has(explicitSeatId)) {
|
|
393
|
+
throw new Error(`CouncilProfileRegistry: duplicate seat id "${explicitSeatId}".`);
|
|
394
|
+
}
|
|
395
|
+
const seatId = uniqueSeatId(explicitSeatId || persona.id, usedIds);
|
|
396
|
+
const label = seat.label?.trim() || persona.name;
|
|
397
|
+
const weight = seat.weight ?? persona.defaultWeight ?? 1;
|
|
398
|
+
if (!Number.isFinite(weight) || weight <= 0) {
|
|
399
|
+
throw new Error(`CouncilProfileRegistry: seat "${seatId}" has an invalid weight.`);
|
|
400
|
+
}
|
|
401
|
+
return Object.freeze({
|
|
402
|
+
id: seatId,
|
|
403
|
+
label,
|
|
404
|
+
persona: persona.id,
|
|
405
|
+
...seat.target ? { target: freezeTarget(seat.target, `seat "${seatId}"`) } : {},
|
|
406
|
+
weight,
|
|
407
|
+
veto: seat.veto ?? persona.defaultVeto ?? false
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
const quorumFraction = fraction(
|
|
411
|
+
profile.quorumFraction ?? DEFAULT_COUNCIL_QUORUM_FRACTION,
|
|
412
|
+
"quorumFraction",
|
|
413
|
+
id
|
|
414
|
+
);
|
|
415
|
+
const approvalFraction = fraction(
|
|
416
|
+
profile.approvalFraction ?? DEFAULT_COUNCIL_APPROVAL_FRACTION,
|
|
417
|
+
"approvalFraction",
|
|
418
|
+
id
|
|
419
|
+
);
|
|
420
|
+
const distinctness = profile.distinctness ?? "model";
|
|
421
|
+
if (!DISTINCTNESS_VALUES.has(distinctness)) {
|
|
422
|
+
throw new Error(`CouncilProfileRegistry: profile "${id}" has invalid distinctness.`);
|
|
423
|
+
}
|
|
424
|
+
const voterMaxTokens = positiveInteger(
|
|
425
|
+
profile.voterMaxTokens ?? DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
|
|
426
|
+
"voterMaxTokens",
|
|
427
|
+
id
|
|
428
|
+
);
|
|
429
|
+
const judgeMaxTokens = positiveInteger(
|
|
430
|
+
profile.judgeMaxTokens ?? DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
|
|
431
|
+
"judgeMaxTokens",
|
|
432
|
+
id
|
|
433
|
+
);
|
|
434
|
+
const perCallTimeoutMs = positiveInteger(
|
|
435
|
+
profile.perCallTimeoutMs ?? DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
|
|
436
|
+
"perCallTimeoutMs",
|
|
437
|
+
id
|
|
438
|
+
);
|
|
439
|
+
const overallTimeoutMs = positiveInteger(
|
|
440
|
+
profile.overallTimeoutMs ?? DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
|
|
441
|
+
"overallTimeoutMs",
|
|
442
|
+
id
|
|
443
|
+
);
|
|
444
|
+
if (overallTimeoutMs < perCallTimeoutMs) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`CouncilProfileRegistry: profile "${id}" overallTimeoutMs must be at least perCallTimeoutMs.`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
return Object.freeze({
|
|
450
|
+
id,
|
|
451
|
+
name: profile.name?.trim() || id,
|
|
452
|
+
description: profile.description?.trim() || "",
|
|
453
|
+
seats: Object.freeze(seats),
|
|
454
|
+
judge: profile.judge ? freezeTarget(profile.judge, "judge") : false,
|
|
455
|
+
quorumFraction,
|
|
456
|
+
approvalFraction,
|
|
457
|
+
distinctness,
|
|
458
|
+
voterMaxTokens,
|
|
459
|
+
judgeMaxTokens,
|
|
460
|
+
perCallTimeoutMs,
|
|
461
|
+
overallTimeoutMs
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
function uniqueSeatId(base, used) {
|
|
465
|
+
if (!PROFILE_ID_RE.test(base)) {
|
|
466
|
+
throw new Error(`CouncilProfileRegistry: invalid seat id "${base}".`);
|
|
467
|
+
}
|
|
468
|
+
let id = base;
|
|
469
|
+
let suffix = 2;
|
|
470
|
+
while (used.has(id)) id = `${base}-${suffix++}`;
|
|
471
|
+
used.add(id);
|
|
472
|
+
return id;
|
|
473
|
+
}
|
|
474
|
+
function freezeTarget(target, label) {
|
|
475
|
+
const providerId = optionalText(target.providerId);
|
|
476
|
+
const model = optionalText(target.model);
|
|
477
|
+
const role = optionalText(target.role);
|
|
478
|
+
const fallbackProfile = optionalText(target.fallbackProfile);
|
|
479
|
+
const fallbackModels = Object.freeze(
|
|
480
|
+
[...new Set((target.fallbackModels ?? []).map((ref) => ref.trim()).filter(Boolean))]
|
|
481
|
+
);
|
|
482
|
+
if (!providerId && !model && !role && !fallbackProfile && fallbackModels.length === 0) {
|
|
483
|
+
throw new Error(`CouncilProfileRegistry: ${label} target is empty.`);
|
|
484
|
+
}
|
|
485
|
+
return Object.freeze({
|
|
486
|
+
...providerId ? { providerId } : {},
|
|
487
|
+
...model ? { model } : {},
|
|
488
|
+
...role ? { role } : {},
|
|
489
|
+
...fallbackProfile ? { fallbackProfile } : {},
|
|
490
|
+
...fallbackModels.length > 0 ? { fallbackModels } : {}
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
function optionalText(value) {
|
|
494
|
+
const trimmed = value?.trim();
|
|
495
|
+
return trimmed || void 0;
|
|
496
|
+
}
|
|
497
|
+
function fraction(value, field, profileId) {
|
|
498
|
+
if (!Number.isFinite(value) || value <= 0 || value > 1) {
|
|
499
|
+
throw new Error(`CouncilProfileRegistry: profile "${profileId}" ${field} must be in (0, 1].`);
|
|
500
|
+
}
|
|
501
|
+
return value;
|
|
502
|
+
}
|
|
503
|
+
function positiveInteger(value, field, profileId) {
|
|
504
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
`CouncilProfileRegistry: profile "${profileId}" ${field} must be a positive integer.`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return value;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/execution/council-prompts.ts
|
|
513
|
+
var COUNCIL_VOTER_PROMPT_PATH = "llm/council-voter.md";
|
|
514
|
+
var COUNCIL_JUDGE_PROMPT_PATH = "llm/council-judge.md";
|
|
515
|
+
function buildCouncilVoterSystemPrompt(persona) {
|
|
516
|
+
const template = requiredInstruction(COUNCIL_VOTER_PROMPT_PATH);
|
|
517
|
+
return renderInstructionTemplate(template, {
|
|
518
|
+
personaInstruction: persona.instruction
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
function buildCouncilJudgeSystemPrompt() {
|
|
522
|
+
return requiredInstruction(COUNCIL_JUDGE_PROMPT_PATH);
|
|
523
|
+
}
|
|
524
|
+
function buildCouncilQuestionPrompt(question, opts = {}) {
|
|
525
|
+
const text = question.question.trim();
|
|
526
|
+
if (!text) throw new Error("buildCouncilQuestionPrompt: question must not be empty.");
|
|
527
|
+
const options = normalizeOptions(question.options);
|
|
528
|
+
return [
|
|
529
|
+
"<council-question>",
|
|
530
|
+
`Question: ${text}`,
|
|
531
|
+
question.context?.trim() ? `Context:
|
|
532
|
+
${question.context.trim()}` : "",
|
|
533
|
+
options.length > 0 ? `Options (JSON):
|
|
534
|
+
${JSON.stringify(options)}` : "Options: none",
|
|
535
|
+
opts.refusalOptionId ? `Refusal option id: ${opts.refusalOptionId}` : "Refusal option id: not supplied",
|
|
536
|
+
"</council-question>"
|
|
537
|
+
].filter(Boolean).join("\n\n");
|
|
538
|
+
}
|
|
539
|
+
function buildCouncilVoterUserPrompt(question, seat, opts = {}) {
|
|
540
|
+
return [
|
|
541
|
+
buildCouncilQuestionPrompt(question, opts),
|
|
542
|
+
"<seat-metadata>",
|
|
543
|
+
`Seat id: ${seat.id}`,
|
|
544
|
+
`Seat label: ${seat.label}`,
|
|
545
|
+
`Persona id: ${seat.persona}`,
|
|
546
|
+
`Vote weight: ${seat.weight}`,
|
|
547
|
+
seat.veto ? "Veto power: yes \u2014 your refusal unilaterally denies the proposal." : "",
|
|
548
|
+
"</seat-metadata>"
|
|
549
|
+
].filter(Boolean).join("\n");
|
|
550
|
+
}
|
|
551
|
+
function buildCouncilJudgeUserPrompt(question, votes, opts = {}) {
|
|
552
|
+
const ballots = votes.map((vote) => ({
|
|
553
|
+
seatId: vote.seatId,
|
|
554
|
+
persona: vote.persona,
|
|
555
|
+
status: vote.status,
|
|
556
|
+
...vote.optionId ? { optionId: vote.optionId } : {},
|
|
557
|
+
...vote.stance ? { stance: vote.stance } : {},
|
|
558
|
+
...vote.rationale ? { rationale: vote.rationale } : {}
|
|
559
|
+
}));
|
|
560
|
+
return [
|
|
561
|
+
buildCouncilQuestionPrompt(question, { refusalOptionId: opts.refusalOptionId }),
|
|
562
|
+
"<council-ballots>",
|
|
563
|
+
opts.reason?.trim() ? `Reason judging is required: ${opts.reason.trim()}` : "",
|
|
564
|
+
JSON.stringify(ballots),
|
|
565
|
+
"</council-ballots>"
|
|
566
|
+
].filter(Boolean).join("\n\n");
|
|
567
|
+
}
|
|
568
|
+
function validateCouncilOptions(options) {
|
|
569
|
+
const errors = [];
|
|
570
|
+
const seen = /* @__PURE__ */ new Set();
|
|
571
|
+
for (const option of options ?? []) {
|
|
572
|
+
const id = option.id.trim();
|
|
573
|
+
if (!id) errors.push("Every option must have a non-empty `id`.");
|
|
574
|
+
if (!option.label.trim()) errors.push(`Option "${id || "<empty>"}" must have a label.`);
|
|
575
|
+
if (seen.has(id)) errors.push(`Duplicate option id "${id}".`);
|
|
576
|
+
seen.add(id);
|
|
577
|
+
}
|
|
578
|
+
return errors;
|
|
579
|
+
}
|
|
580
|
+
function normalizeOptions(options) {
|
|
581
|
+
if (!options) return [];
|
|
582
|
+
const errors = validateCouncilOptions(options);
|
|
583
|
+
if (errors.length > 0) {
|
|
584
|
+
throw new Error(`buildCouncilQuestionPrompt: ${errors.join("; ")}`);
|
|
585
|
+
}
|
|
586
|
+
return options.map((option) => ({
|
|
587
|
+
id: option.id.trim(),
|
|
588
|
+
label: option.label.trim(),
|
|
589
|
+
...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
|
|
590
|
+
}));
|
|
591
|
+
}
|
|
592
|
+
function requiredInstruction(path) {
|
|
593
|
+
const text = readBundledInstructionText(path);
|
|
594
|
+
if (!text) throw new Error(`Council instruction file is unavailable: ${path}`);
|
|
595
|
+
return text;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/execution/council-resolution.ts
|
|
599
|
+
function resolveCouncilVotes(input) {
|
|
600
|
+
validateInput(input);
|
|
601
|
+
const seatById = new Map(input.seats.map((seat) => [seat.id, seat]));
|
|
602
|
+
const validVotes = [];
|
|
603
|
+
const votedSeatIds = /* @__PURE__ */ new Set();
|
|
604
|
+
for (const vote of input.votes) {
|
|
605
|
+
if (!seatById.has(vote.seatId) || votedSeatIds.has(vote.seatId)) continue;
|
|
606
|
+
votedSeatIds.add(vote.seatId);
|
|
607
|
+
validVotes.push(vote);
|
|
608
|
+
}
|
|
609
|
+
if (validVotes.length / input.seats.length < input.quorumFraction) {
|
|
610
|
+
return {
|
|
611
|
+
status: "abstained",
|
|
612
|
+
reason: "quorum_not_met",
|
|
613
|
+
validVoteCount: validVotes.length,
|
|
614
|
+
seatCount: input.seats.length
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const veto = validVotes.find(
|
|
618
|
+
(vote) => vote.optionId === input.refusalOptionId && seatById.get(vote.seatId)?.veto === true
|
|
619
|
+
);
|
|
620
|
+
if (veto) {
|
|
621
|
+
return {
|
|
622
|
+
status: "denied",
|
|
623
|
+
method: "veto",
|
|
624
|
+
optionId: veto.optionId,
|
|
625
|
+
seatId: veto.seatId
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const weightByOption = /* @__PURE__ */ new Map();
|
|
629
|
+
let castWeight = 0;
|
|
630
|
+
for (const vote of validVotes) {
|
|
631
|
+
const weight = seatById.get(vote.seatId)?.weight ?? 1;
|
|
632
|
+
castWeight += weight;
|
|
633
|
+
weightByOption.set(vote.optionId, (weightByOption.get(vote.optionId) ?? 0) + weight);
|
|
634
|
+
}
|
|
635
|
+
let winner;
|
|
636
|
+
let contested = false;
|
|
637
|
+
for (const [optionId, weight] of weightByOption) {
|
|
638
|
+
if (!winner || weight > winner.weight) {
|
|
639
|
+
winner = { optionId, weight };
|
|
640
|
+
contested = false;
|
|
641
|
+
} else if (weight === winner.weight) {
|
|
642
|
+
contested = true;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const decisive = winner !== void 0 && !contested && winner.weight > input.approvalFraction * castWeight;
|
|
646
|
+
if (!decisive || !winner) {
|
|
647
|
+
return {
|
|
648
|
+
status: "needs_judge",
|
|
649
|
+
reason: contested ? "tie" : "approval_threshold_not_met",
|
|
650
|
+
castWeight
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
if (winner.optionId === input.refusalOptionId) {
|
|
654
|
+
return {
|
|
655
|
+
status: "denied",
|
|
656
|
+
method: "refusal",
|
|
657
|
+
optionId: winner.optionId,
|
|
658
|
+
winningWeight: winner.weight,
|
|
659
|
+
castWeight
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
status: "decided",
|
|
664
|
+
method: "majority",
|
|
665
|
+
optionId: winner.optionId,
|
|
666
|
+
winningWeight: winner.weight,
|
|
667
|
+
castWeight
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function validateInput(input) {
|
|
671
|
+
if (input.seats.length === 0) {
|
|
672
|
+
throw new Error("resolveCouncilVotes: at least one seat is required.");
|
|
673
|
+
}
|
|
674
|
+
requireFraction(input.quorumFraction, "quorumFraction");
|
|
675
|
+
requireFraction(input.approvalFraction, "approvalFraction");
|
|
676
|
+
const seatIds = /* @__PURE__ */ new Set();
|
|
677
|
+
for (const seat of input.seats) {
|
|
678
|
+
if (!seat.id.trim()) throw new Error("resolveCouncilVotes: seat id must not be empty.");
|
|
679
|
+
if (seatIds.has(seat.id)) {
|
|
680
|
+
throw new Error(`resolveCouncilVotes: duplicate seat id "${seat.id}".`);
|
|
681
|
+
}
|
|
682
|
+
seatIds.add(seat.id);
|
|
683
|
+
if (seat.weight !== void 0 && (!Number.isFinite(seat.weight) || seat.weight <= 0)) {
|
|
684
|
+
throw new Error(`resolveCouncilVotes: invalid weight for seat "${seat.id}".`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function requireFraction(value, label) {
|
|
689
|
+
if (!Number.isFinite(value) || value <= 0 || value > 1) {
|
|
690
|
+
throw new Error(`resolveCouncilVotes: ${label} must be in (0, 1].`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/execution/council-response-parser.ts
|
|
695
|
+
function parseObject(text) {
|
|
696
|
+
const trimmed = text.trim();
|
|
697
|
+
const first = trimmed.indexOf("{");
|
|
698
|
+
const last = trimmed.lastIndexOf("}");
|
|
699
|
+
if (first < 0 || last < first) return { ok: false, error: "LLM response did not contain JSON." };
|
|
700
|
+
try {
|
|
701
|
+
const value = JSON.parse(trimmed.slice(first, last + 1));
|
|
702
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
703
|
+
return { ok: false, error: "LLM response JSON must be an object." };
|
|
704
|
+
}
|
|
705
|
+
return { ok: true, value };
|
|
706
|
+
} catch (error) {
|
|
707
|
+
return { ok: false, error: `Invalid LLM response JSON: ${errorMessage(error)}` };
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function optionalString(value) {
|
|
711
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
712
|
+
}
|
|
713
|
+
function errorMessage(error) {
|
|
714
|
+
return error instanceof Error ? error.message : String(error);
|
|
715
|
+
}
|
|
716
|
+
function parseCouncilResponse(text, question, refusalOptionId, opts) {
|
|
717
|
+
const roleLabel = opts.role === "judge" ? "Judge" : "Voter";
|
|
718
|
+
const parsed = parseObject(text);
|
|
719
|
+
if (!parsed.ok && (!question.options || question.options.length === 0)) {
|
|
720
|
+
const fallback = text.trim();
|
|
721
|
+
if (fallback) return { ok: true, value: { [opts.freeTextField]: fallback } };
|
|
722
|
+
return { ok: false, error: `${roleLabel} returned an empty response.` };
|
|
723
|
+
}
|
|
724
|
+
if (!parsed.ok) return parsed;
|
|
725
|
+
const rationale = optionalString(parsed.value["rationale"]);
|
|
726
|
+
if (question.options && question.options.length > 0) {
|
|
727
|
+
const optionId = optionalString(parsed.value["optionId"]);
|
|
728
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
729
|
+
...question.options.map((option) => option.id.trim()),
|
|
730
|
+
refusalOptionId
|
|
731
|
+
]);
|
|
732
|
+
if (!optionId || !allowed.has(optionId)) {
|
|
733
|
+
return { ok: false, error: `${roleLabel} returned an unknown or missing optionId.` };
|
|
734
|
+
}
|
|
735
|
+
return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
|
|
736
|
+
}
|
|
737
|
+
const freeText = optionalString(parsed.value[opts.freeTextField]);
|
|
738
|
+
if (!freeText) {
|
|
739
|
+
return {
|
|
740
|
+
ok: false,
|
|
741
|
+
error: `${roleLabel} returned an empty or missing ${opts.freeTextField}.`
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
ok: true,
|
|
746
|
+
value: { [opts.freeTextField]: freeText, ...rationale ? { rationale } : {} }
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function divergentStances(valid) {
|
|
750
|
+
const seen = /* @__PURE__ */ new Set();
|
|
751
|
+
for (const vote of valid) {
|
|
752
|
+
const normalized = vote.stance.trim().replace(/^["'`]+|["'`]+$/g, "").toLowerCase().replace(/[.!?;:,]+$/g, "").replace(/\s+/g, " ").trim();
|
|
753
|
+
if (!normalized) return true;
|
|
754
|
+
seen.add(normalized);
|
|
755
|
+
if (seen.size > 1) return true;
|
|
756
|
+
}
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
function parseVote(text, question, refusalOptionId) {
|
|
760
|
+
const parsed = parseCouncilResponse(text, question, refusalOptionId, {
|
|
761
|
+
role: "voter",
|
|
762
|
+
freeTextField: "stance"
|
|
763
|
+
});
|
|
764
|
+
if (!parsed.ok) return parsed;
|
|
765
|
+
const { optionId, stance, rationale } = parsed.value;
|
|
766
|
+
return {
|
|
767
|
+
ok: true,
|
|
768
|
+
vote: {
|
|
769
|
+
...optionId ? { optionId } : {},
|
|
770
|
+
...stance ? { stance } : {},
|
|
771
|
+
...rationale ? { rationale } : {}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function parseJudge(text, question, refusalOptionId) {
|
|
776
|
+
const parsed = parseCouncilResponse(text, question, refusalOptionId, {
|
|
777
|
+
role: "judge",
|
|
778
|
+
freeTextField: "answer"
|
|
779
|
+
});
|
|
780
|
+
if (!parsed.ok) return parsed;
|
|
781
|
+
const { optionId, answer, rationale } = parsed.value;
|
|
782
|
+
return {
|
|
783
|
+
ok: true,
|
|
784
|
+
value: {
|
|
785
|
+
...optionId ? { optionId } : {},
|
|
786
|
+
...answer ? { answer } : {},
|
|
787
|
+
...rationale ? { rationale } : {}
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function withTruncationNote(error, result, maxTokens) {
|
|
792
|
+
if (result.stopReason !== "max_tokens") return error;
|
|
793
|
+
return `${error} (response truncated at maxTokens=${maxTokens} \u2014 reasoning models may need a larger budget)`;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/execution/council-orchestrator.ts
|
|
797
|
+
var CouncilOrchestrator = class {
|
|
798
|
+
caller;
|
|
799
|
+
personas;
|
|
800
|
+
profiles;
|
|
801
|
+
defaultProfile;
|
|
802
|
+
maxConcurrency;
|
|
803
|
+
refusalOptionId;
|
|
804
|
+
fallbackProfileManager;
|
|
805
|
+
seatCaller;
|
|
806
|
+
judgeCaller;
|
|
807
|
+
constructor(opts) {
|
|
808
|
+
if (!opts.caller && !opts.seatCaller && !opts.judgeCaller) {
|
|
809
|
+
throw new Error("CouncilOrchestrator: provide `caller`, `seatCaller`, or `judgeCaller`.");
|
|
810
|
+
}
|
|
811
|
+
this.caller = opts.caller;
|
|
812
|
+
this.personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
|
|
813
|
+
this.profiles = opts.profiles ?? DEFAULT_COUNCIL_PROFILE_REGISTRY;
|
|
814
|
+
this.defaultProfile = opts.defaultProfile;
|
|
815
|
+
this.maxConcurrency = validateConcurrency(
|
|
816
|
+
opts.maxConcurrency ?? DEFAULT_COUNCIL_MAX_CONCURRENCY
|
|
817
|
+
);
|
|
818
|
+
this.refusalOptionId = opts.refusalOptionId?.trim() || COUNCIL_REFUSAL_OPTION_ID;
|
|
819
|
+
this.fallbackProfileManager = opts.fallbackProfileManager;
|
|
820
|
+
this.seatCaller = opts.seatCaller;
|
|
821
|
+
this.judgeCaller = opts.judgeCaller;
|
|
822
|
+
}
|
|
823
|
+
resolveProfile(profile) {
|
|
824
|
+
return resolveCouncilProfile(profile, {
|
|
825
|
+
registry: this.profiles,
|
|
826
|
+
personas: this.personas,
|
|
827
|
+
defaultProfile: this.defaultProfile
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
async ask(question) {
|
|
831
|
+
const startedAt = Date.now();
|
|
832
|
+
const profile = this.resolveProfile(question.profile);
|
|
833
|
+
validateRefusalCollision(question, this.refusalOptionId);
|
|
834
|
+
const timeoutSignal = AbortSignal.timeout(profile.overallTimeoutMs);
|
|
835
|
+
const signal = question.signal ? AbortSignal.any([question.signal, timeoutSignal]) : timeoutSignal;
|
|
836
|
+
const usage = {
|
|
837
|
+
calls: 0,
|
|
838
|
+
inputTokens: 0,
|
|
839
|
+
outputTokens: 0,
|
|
840
|
+
totalTokens: 0
|
|
841
|
+
};
|
|
842
|
+
const votes = await mapConcurrent(
|
|
843
|
+
profile.seats,
|
|
844
|
+
Math.min(this.maxConcurrency, profile.seats.length),
|
|
845
|
+
async (seat, i) => {
|
|
846
|
+
try {
|
|
847
|
+
return await this.callSeat(question, profile, seat, i, signal, usage);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
const timedOut = signal.aborted && !question.signal?.aborted;
|
|
850
|
+
return {
|
|
851
|
+
seatId: seat.id,
|
|
852
|
+
persona: seat.persona,
|
|
853
|
+
status: question.signal?.aborted ? "cancelled" : "failed",
|
|
854
|
+
error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : errorMessage(error)
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
const warnings = distinctnessWarnings(votes, profile);
|
|
860
|
+
const errors = votes.filter((vote) => vote.status === "failed" || vote.status === "invalid").map((vote) => `${vote.seatId}: ${vote.error ?? vote.status}`);
|
|
861
|
+
if (question.signal?.aborted) {
|
|
862
|
+
return resultEnvelope({
|
|
863
|
+
status: "cancelled",
|
|
864
|
+
reason: CALL_CANCELLED_REASON,
|
|
865
|
+
resolution: "none",
|
|
866
|
+
votes,
|
|
867
|
+
profile,
|
|
868
|
+
usage,
|
|
869
|
+
startedAt,
|
|
870
|
+
warnings,
|
|
871
|
+
errors
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
if (timeoutSignal.aborted) {
|
|
875
|
+
return resultEnvelope({
|
|
876
|
+
status: "failed",
|
|
877
|
+
reason: OVERALL_TIMEOUT_REASON,
|
|
878
|
+
resolution: "none",
|
|
879
|
+
votes,
|
|
880
|
+
profile,
|
|
881
|
+
usage,
|
|
882
|
+
startedAt,
|
|
883
|
+
warnings,
|
|
884
|
+
errors: errors.some((entry) => entry.includes(OVERALL_TIMEOUT_REASON)) ? errors : [...errors, OVERALL_TIMEOUT_REASON]
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
if (!question.options || question.options.length === 0) {
|
|
888
|
+
return this.resolveOpenQuestion(
|
|
889
|
+
question,
|
|
890
|
+
profile,
|
|
891
|
+
votes,
|
|
892
|
+
signal,
|
|
893
|
+
usage,
|
|
894
|
+
startedAt,
|
|
895
|
+
warnings,
|
|
896
|
+
errors
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
return this.resolveOptionQuestion(
|
|
900
|
+
question,
|
|
901
|
+
profile,
|
|
902
|
+
votes,
|
|
903
|
+
signal,
|
|
904
|
+
usage,
|
|
905
|
+
startedAt,
|
|
906
|
+
warnings,
|
|
907
|
+
errors
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
async callSeat(question, profile, seat, seatIndex, signal, usage) {
|
|
911
|
+
if (signal.aborted) {
|
|
912
|
+
return question.signal?.aborted ? cancelledVote(seat) : {
|
|
913
|
+
seatId: seat.id,
|
|
914
|
+
persona: seat.persona,
|
|
915
|
+
status: "failed",
|
|
916
|
+
...seat.target?.providerId ? { provider: seat.target.providerId } : {},
|
|
917
|
+
...seat.target?.model ? { model: seat.target.model } : {},
|
|
918
|
+
durationMs: 0,
|
|
919
|
+
error: OVERALL_TIMEOUT_REASON
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
let persona;
|
|
923
|
+
try {
|
|
924
|
+
persona = this.personas.require(seat.persona);
|
|
925
|
+
} catch (error) {
|
|
926
|
+
return {
|
|
927
|
+
seatId: seat.id,
|
|
928
|
+
persona: seat.persona,
|
|
929
|
+
status: "failed",
|
|
930
|
+
error: errorMessage(error)
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
const result = await this.safeCall({
|
|
934
|
+
system: buildCouncilVoterSystemPrompt(persona),
|
|
935
|
+
userPrompt: buildCouncilVoterUserPrompt(question, seat, {
|
|
936
|
+
refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
|
|
937
|
+
}),
|
|
938
|
+
target: seat.target,
|
|
939
|
+
maxTokens: profile.voterMaxTokens,
|
|
940
|
+
timeoutMs: profile.perCallTimeoutMs,
|
|
941
|
+
signal,
|
|
942
|
+
usage,
|
|
943
|
+
seatIndex
|
|
944
|
+
});
|
|
945
|
+
const metadata = callMetadata(result);
|
|
946
|
+
if (result.error) {
|
|
947
|
+
const timedOut = signal.aborted && !question.signal?.aborted;
|
|
948
|
+
return {
|
|
949
|
+
seatId: seat.id,
|
|
950
|
+
persona: seat.persona,
|
|
951
|
+
status: question.signal?.aborted ? "cancelled" : "failed",
|
|
952
|
+
...metadata,
|
|
953
|
+
error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : result.error
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
const parsed = parseVote(result.text, question, this.refusalOptionId);
|
|
957
|
+
if (!parsed.ok) {
|
|
958
|
+
return {
|
|
959
|
+
seatId: seat.id,
|
|
960
|
+
persona: seat.persona,
|
|
961
|
+
status: "invalid",
|
|
962
|
+
...metadata,
|
|
963
|
+
error: withTruncationNote(parsed.error, result, profile.voterMaxTokens)
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
seatId: seat.id,
|
|
968
|
+
persona: seat.persona,
|
|
969
|
+
status: "valid",
|
|
970
|
+
...parsed.vote,
|
|
971
|
+
...metadata
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
async resolveOptionQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
|
|
975
|
+
const validVotes = votes.filter(
|
|
976
|
+
(vote) => vote.status === "valid" && typeof vote.optionId === "string"
|
|
977
|
+
);
|
|
978
|
+
const resolution = resolveCouncilVotes({
|
|
979
|
+
seats: profile.seats.map((seat) => ({
|
|
980
|
+
id: seat.id,
|
|
981
|
+
weight: seat.weight,
|
|
982
|
+
veto: seat.veto
|
|
983
|
+
})),
|
|
984
|
+
votes: validVotes.map((vote) => ({ seatId: vote.seatId, optionId: vote.optionId })),
|
|
985
|
+
refusalOptionId: this.refusalOptionId,
|
|
986
|
+
quorumFraction: profile.quorumFraction,
|
|
987
|
+
approvalFraction: profile.approvalFraction
|
|
988
|
+
});
|
|
989
|
+
if (resolution.status === "abstained") {
|
|
990
|
+
return resultEnvelope({
|
|
991
|
+
status: "abstained",
|
|
992
|
+
reason: "Council quorum was not met.",
|
|
993
|
+
resolution: "none",
|
|
994
|
+
votes,
|
|
995
|
+
profile,
|
|
996
|
+
usage,
|
|
997
|
+
startedAt,
|
|
998
|
+
warnings,
|
|
999
|
+
errors
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
if (resolution.status === "denied") {
|
|
1003
|
+
return resultEnvelope({
|
|
1004
|
+
status: "denied",
|
|
1005
|
+
optionId: resolution.optionId,
|
|
1006
|
+
reason: `Council denied the proposal via ${resolution.method}.`,
|
|
1007
|
+
resolution: resolution.method,
|
|
1008
|
+
votes,
|
|
1009
|
+
profile,
|
|
1010
|
+
usage,
|
|
1011
|
+
startedAt,
|
|
1012
|
+
warnings,
|
|
1013
|
+
errors
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
if (resolution.status === "decided") {
|
|
1017
|
+
return resultEnvelope({
|
|
1018
|
+
status: "decided",
|
|
1019
|
+
optionId: resolution.optionId,
|
|
1020
|
+
answer: optionLabel(question, resolution.optionId),
|
|
1021
|
+
resolution: "majority",
|
|
1022
|
+
votes,
|
|
1023
|
+
profile,
|
|
1024
|
+
usage,
|
|
1025
|
+
startedAt,
|
|
1026
|
+
warnings,
|
|
1027
|
+
errors
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
if (!profile.judge) {
|
|
1031
|
+
return resultEnvelope({
|
|
1032
|
+
status: "abstained",
|
|
1033
|
+
reason: `Council requires a judge (${resolution.reason}), but this profile has none.`,
|
|
1034
|
+
resolution: "none",
|
|
1035
|
+
votes,
|
|
1036
|
+
profile,
|
|
1037
|
+
usage,
|
|
1038
|
+
startedAt,
|
|
1039
|
+
warnings,
|
|
1040
|
+
errors
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
const judged = await this.callJudge(
|
|
1044
|
+
question,
|
|
1045
|
+
profile,
|
|
1046
|
+
votes,
|
|
1047
|
+
profile.judge,
|
|
1048
|
+
resolution.reason,
|
|
1049
|
+
signal,
|
|
1050
|
+
usage
|
|
1051
|
+
);
|
|
1052
|
+
if (signal.aborted) {
|
|
1053
|
+
const cancelled = question.signal?.aborted === true;
|
|
1054
|
+
const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
|
|
1055
|
+
return resultEnvelope({
|
|
1056
|
+
status: cancelled ? "cancelled" : "failed",
|
|
1057
|
+
reason,
|
|
1058
|
+
resolution: "none",
|
|
1059
|
+
votes,
|
|
1060
|
+
profile,
|
|
1061
|
+
usage,
|
|
1062
|
+
startedAt,
|
|
1063
|
+
warnings,
|
|
1064
|
+
errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
|
|
1065
|
+
judgeUsed: true
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
if (!judged.ok) {
|
|
1069
|
+
return resultEnvelope({
|
|
1070
|
+
status: question.signal?.aborted ? "cancelled" : signal.aborted ? "failed" : "abstained",
|
|
1071
|
+
reason: judged.error,
|
|
1072
|
+
resolution: "none",
|
|
1073
|
+
votes,
|
|
1074
|
+
profile,
|
|
1075
|
+
usage,
|
|
1076
|
+
startedAt,
|
|
1077
|
+
warnings,
|
|
1078
|
+
errors: [...errors, judged.error],
|
|
1079
|
+
judgeUsed: true
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
if (judged.value.optionId === this.refusalOptionId) {
|
|
1083
|
+
return resultEnvelope({
|
|
1084
|
+
status: "denied",
|
|
1085
|
+
optionId: this.refusalOptionId,
|
|
1086
|
+
reason: judged.value.rationale ?? "Council judge refused all options.",
|
|
1087
|
+
resolution: "judge",
|
|
1088
|
+
votes,
|
|
1089
|
+
profile,
|
|
1090
|
+
usage,
|
|
1091
|
+
startedAt,
|
|
1092
|
+
warnings,
|
|
1093
|
+
errors,
|
|
1094
|
+
judgeUsed: true
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
return resultEnvelope({
|
|
1098
|
+
status: "decided",
|
|
1099
|
+
optionId: judged.value.optionId,
|
|
1100
|
+
answer: optionLabel(question, judged.value.optionId),
|
|
1101
|
+
reason: judged.value.rationale,
|
|
1102
|
+
resolution: "judge",
|
|
1103
|
+
votes,
|
|
1104
|
+
profile,
|
|
1105
|
+
usage,
|
|
1106
|
+
startedAt,
|
|
1107
|
+
warnings,
|
|
1108
|
+
errors,
|
|
1109
|
+
judgeUsed: true
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
async resolveOpenQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
|
|
1113
|
+
const valid = votes.filter(
|
|
1114
|
+
(vote) => vote.status === "valid" && typeof vote.stance === "string"
|
|
1115
|
+
);
|
|
1116
|
+
if (valid.length / profile.seats.length < profile.quorumFraction) {
|
|
1117
|
+
return resultEnvelope({
|
|
1118
|
+
status: "abstained",
|
|
1119
|
+
reason: "Council quorum was not met.",
|
|
1120
|
+
resolution: "none",
|
|
1121
|
+
votes,
|
|
1122
|
+
profile,
|
|
1123
|
+
usage,
|
|
1124
|
+
startedAt,
|
|
1125
|
+
warnings,
|
|
1126
|
+
errors
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
if (!profile.judge) {
|
|
1130
|
+
if (divergentStances(valid)) {
|
|
1131
|
+
return resultEnvelope({
|
|
1132
|
+
status: "abstained",
|
|
1133
|
+
reason: "Council produced multiple distinct stances and has no judge to reconcile them.",
|
|
1134
|
+
resolution: "none",
|
|
1135
|
+
votes,
|
|
1136
|
+
profile,
|
|
1137
|
+
usage,
|
|
1138
|
+
startedAt,
|
|
1139
|
+
warnings,
|
|
1140
|
+
errors
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
const first = valid[0];
|
|
1144
|
+
if (!first) {
|
|
1145
|
+
return resultEnvelope({
|
|
1146
|
+
status: "failed",
|
|
1147
|
+
reason: "Council produced no valid stance.",
|
|
1148
|
+
resolution: "none",
|
|
1149
|
+
votes,
|
|
1150
|
+
profile,
|
|
1151
|
+
usage,
|
|
1152
|
+
startedAt,
|
|
1153
|
+
warnings,
|
|
1154
|
+
errors
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
return resultEnvelope({
|
|
1158
|
+
status: "decided",
|
|
1159
|
+
answer: first.stance,
|
|
1160
|
+
reason: first.rationale,
|
|
1161
|
+
resolution: "first_stance",
|
|
1162
|
+
votes,
|
|
1163
|
+
profile,
|
|
1164
|
+
usage,
|
|
1165
|
+
startedAt,
|
|
1166
|
+
warnings,
|
|
1167
|
+
errors
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
const judged = await this.callJudge(
|
|
1171
|
+
question,
|
|
1172
|
+
profile,
|
|
1173
|
+
votes,
|
|
1174
|
+
profile.judge,
|
|
1175
|
+
"open_question_synthesis",
|
|
1176
|
+
signal,
|
|
1177
|
+
usage
|
|
1178
|
+
);
|
|
1179
|
+
if (signal.aborted) {
|
|
1180
|
+
const cancelled = question.signal?.aborted === true;
|
|
1181
|
+
const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
|
|
1182
|
+
return resultEnvelope({
|
|
1183
|
+
status: cancelled ? "cancelled" : "failed",
|
|
1184
|
+
reason,
|
|
1185
|
+
resolution: "none",
|
|
1186
|
+
votes,
|
|
1187
|
+
profile,
|
|
1188
|
+
usage,
|
|
1189
|
+
startedAt,
|
|
1190
|
+
warnings,
|
|
1191
|
+
errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
|
|
1192
|
+
judgeUsed: true
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
if (!judged.ok) {
|
|
1196
|
+
return resultEnvelope({
|
|
1197
|
+
status: question.signal?.aborted ? "cancelled" : "failed",
|
|
1198
|
+
reason: judged.error,
|
|
1199
|
+
resolution: "none",
|
|
1200
|
+
votes,
|
|
1201
|
+
profile,
|
|
1202
|
+
usage,
|
|
1203
|
+
startedAt,
|
|
1204
|
+
warnings,
|
|
1205
|
+
errors: [...errors, judged.error],
|
|
1206
|
+
judgeUsed: true
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
return resultEnvelope({
|
|
1210
|
+
status: "decided",
|
|
1211
|
+
answer: judged.value.answer,
|
|
1212
|
+
reason: judged.value.rationale,
|
|
1213
|
+
resolution: "judge",
|
|
1214
|
+
votes,
|
|
1215
|
+
profile,
|
|
1216
|
+
usage,
|
|
1217
|
+
startedAt,
|
|
1218
|
+
warnings,
|
|
1219
|
+
errors,
|
|
1220
|
+
judgeUsed: true
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
async callJudge(question, profile, votes, target, reason, signal, usage) {
|
|
1224
|
+
const result = await this.safeCall({
|
|
1225
|
+
system: buildCouncilJudgeSystemPrompt(),
|
|
1226
|
+
userPrompt: buildCouncilJudgeUserPrompt(question, votes, {
|
|
1227
|
+
reason,
|
|
1228
|
+
refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
|
|
1229
|
+
}),
|
|
1230
|
+
target,
|
|
1231
|
+
maxTokens: profile.judgeMaxTokens,
|
|
1232
|
+
timeoutMs: profile.perCallTimeoutMs,
|
|
1233
|
+
signal,
|
|
1234
|
+
usage
|
|
1235
|
+
});
|
|
1236
|
+
if (result.error) {
|
|
1237
|
+
if (signal.aborted && !question.signal?.aborted) {
|
|
1238
|
+
return { ok: false, error: OVERALL_TIMEOUT_REASON };
|
|
1239
|
+
}
|
|
1240
|
+
if (question.signal?.aborted) {
|
|
1241
|
+
return { ok: false, error: CALL_CANCELLED_REASON };
|
|
1242
|
+
}
|
|
1243
|
+
return { ok: false, error: result.error };
|
|
1244
|
+
}
|
|
1245
|
+
const judged = parseJudge(result.text, question, this.refusalOptionId);
|
|
1246
|
+
if (!judged.ok) {
|
|
1247
|
+
return {
|
|
1248
|
+
ok: false,
|
|
1249
|
+
error: withTruncationNote(judged.error, result, profile.judgeMaxTokens)
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
return judged;
|
|
1253
|
+
}
|
|
1254
|
+
resolveCaller(seatIndex) {
|
|
1255
|
+
if (seatIndex !== void 0) {
|
|
1256
|
+
if (this.seatCaller) return this.seatCaller(seatIndex);
|
|
1257
|
+
return this.caller ?? this.judgeCaller;
|
|
1258
|
+
}
|
|
1259
|
+
if (this.judgeCaller) return this.judgeCaller;
|
|
1260
|
+
if (this.seatCaller) return this.seatCaller(0);
|
|
1261
|
+
return this.caller;
|
|
1262
|
+
}
|
|
1263
|
+
async safeCall(input) {
|
|
1264
|
+
const effectiveCaller = this.resolveCaller(input.seatIndex);
|
|
1265
|
+
const resolvedTarget = this.resolveCouncilTarget(input.target);
|
|
1266
|
+
const startedAt = Date.now();
|
|
1267
|
+
try {
|
|
1268
|
+
const result = await effectiveCaller.call({
|
|
1269
|
+
system: input.system,
|
|
1270
|
+
userPrompt: input.userPrompt,
|
|
1271
|
+
responseFormat: { type: "json_object" },
|
|
1272
|
+
maxTokens: input.maxTokens,
|
|
1273
|
+
timeoutMs: input.timeoutMs,
|
|
1274
|
+
signal: input.signal,
|
|
1275
|
+
...resolvedTarget?.providerId ? { providerId: resolvedTarget.providerId } : {},
|
|
1276
|
+
...resolvedTarget?.model ? { model: resolvedTarget.model } : {},
|
|
1277
|
+
...resolvedTarget?.role ? { role: resolvedTarget.role } : {},
|
|
1278
|
+
...resolvedTarget?.fallbackModels && resolvedTarget.fallbackModels.length > 0 ? { fallbackModels: [...resolvedTarget.fallbackModels] } : {}
|
|
1279
|
+
});
|
|
1280
|
+
addUsage(input.usage, result);
|
|
1281
|
+
return result;
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
const failed = {
|
|
1284
|
+
text: "",
|
|
1285
|
+
model: resolvedTarget?.model ?? "",
|
|
1286
|
+
provider: resolvedTarget?.providerId ?? "",
|
|
1287
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
1288
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
1289
|
+
fromFallback: false,
|
|
1290
|
+
error: errorMessage(error)
|
|
1291
|
+
};
|
|
1292
|
+
addUsage(input.usage, failed);
|
|
1293
|
+
return failed;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
resolveCouncilTarget(target) {
|
|
1297
|
+
if (!target) return void 0;
|
|
1298
|
+
if (!target.fallbackProfile) return target;
|
|
1299
|
+
const mgr = this.fallbackProfileManager;
|
|
1300
|
+
if (!mgr) return target;
|
|
1301
|
+
const chain = mgr.resolve(target.fallbackProfile);
|
|
1302
|
+
if (chain.length === 0) return target;
|
|
1303
|
+
const combined = [
|
|
1304
|
+
...chain.map((e) => `${e.providerId}/${e.model}`),
|
|
1305
|
+
...target.fallbackModels ?? []
|
|
1306
|
+
];
|
|
1307
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1308
|
+
const deduped = combined.filter((ref) => {
|
|
1309
|
+
if (seen.has(ref)) return false;
|
|
1310
|
+
seen.add(ref);
|
|
1311
|
+
return true;
|
|
1312
|
+
});
|
|
1313
|
+
return {
|
|
1314
|
+
...target.providerId ? { providerId: target.providerId } : {},
|
|
1315
|
+
...target.model ? { model: target.model } : {},
|
|
1316
|
+
...target.role ? { role: target.role } : {},
|
|
1317
|
+
fallbackModels: deduped
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
// src/execution/one-shot-llm.ts
|
|
1323
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1324
|
+
var DEFAULT_MAX_TOKENS = 1024;
|
|
1325
|
+
var OneShotOrchestrator = class {
|
|
1326
|
+
opts;
|
|
1327
|
+
constructor(opts) {
|
|
1328
|
+
this.opts = opts;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Make a one-shot LLM call. Resolves provider+model, applies fallback
|
|
1332
|
+
* chain on transient errors, and returns a structured result.
|
|
1333
|
+
*
|
|
1334
|
+
* Never throws — all errors are captured in `OneShotLLMResult.error`.
|
|
1335
|
+
*/
|
|
1336
|
+
async call(input) {
|
|
1337
|
+
const startedAt = performance.now();
|
|
1338
|
+
const config = this.opts.getConfig();
|
|
1339
|
+
const target = this.resolveTarget(input, config);
|
|
1340
|
+
if (!target) {
|
|
1341
|
+
return {
|
|
1342
|
+
text: "",
|
|
1343
|
+
model: input.model ?? config.model ?? "unknown",
|
|
1344
|
+
provider: input.providerId ?? config.provider ?? "unknown",
|
|
1345
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
1346
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1347
|
+
fromFallback: false,
|
|
1348
|
+
attempts: 0,
|
|
1349
|
+
error: "No provider or model could be resolved. Check your config."
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
let provider;
|
|
1353
|
+
try {
|
|
1354
|
+
provider = await this.opts.buildProvider(target.providerId, target.model);
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
return {
|
|
1357
|
+
text: "",
|
|
1358
|
+
model: target.model,
|
|
1359
|
+
provider: target.providerId,
|
|
1360
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
1361
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1362
|
+
fromFallback: false,
|
|
1363
|
+
attempts: 0,
|
|
1364
|
+
error: `Cannot build provider "${target.providerId}": ${err instanceof Error ? err.message : String(err)}`
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
const request = this.buildRequest(input, target.model);
|
|
1368
|
+
const signal = this.resolveSignal(input);
|
|
1369
|
+
const chain = this.resolveFallbackChain(input, config, target);
|
|
1370
|
+
const tracker = this.opts.statusTracker;
|
|
1371
|
+
let servingProviderId = provider.id;
|
|
1372
|
+
let servingModel = target.model;
|
|
1373
|
+
let fromFallback = false;
|
|
1374
|
+
let lastError;
|
|
1375
|
+
let fallbackEligible = false;
|
|
1376
|
+
let attempts = 0;
|
|
1377
|
+
if (tracker && !tracker.isAvailable(target.providerId, target.model) || !evaluateModelCalendar(config.modelAvailabilitySchedule, target.providerId, target.model).allowed) {
|
|
1378
|
+
this.opts.logger?.debug(
|
|
1379
|
+
`one-shot: primary "${target.providerId}/${target.model}" is blocked \u2014 trying fallback`
|
|
1380
|
+
);
|
|
1381
|
+
} else {
|
|
1382
|
+
attempts += 1;
|
|
1383
|
+
const primaryAttempt = await this.tryCall(
|
|
1384
|
+
provider,
|
|
1385
|
+
request,
|
|
1386
|
+
signal,
|
|
1387
|
+
target.providerId,
|
|
1388
|
+
target.model
|
|
1389
|
+
);
|
|
1390
|
+
const result = primaryAttempt.response;
|
|
1391
|
+
lastError = primaryAttempt.error;
|
|
1392
|
+
fallbackEligible = primaryAttempt.fallbackEligible;
|
|
1393
|
+
if (result) {
|
|
1394
|
+
tracker?.recordSuccess(target.providerId, target.model);
|
|
1395
|
+
servingProviderId = provider.id;
|
|
1396
|
+
servingModel = target.model;
|
|
1397
|
+
return this.buildResult(result, servingProviderId, servingModel, false, startedAt, attempts);
|
|
1398
|
+
}
|
|
1399
|
+
if (!fallbackEligible || chain.length === 0) {
|
|
1400
|
+
return this.buildErrorResult(
|
|
1401
|
+
lastError,
|
|
1402
|
+
target.providerId,
|
|
1403
|
+
target.model,
|
|
1404
|
+
false,
|
|
1405
|
+
startedAt,
|
|
1406
|
+
attempts
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
const estimatedTokens = estimateRequestTokens(request.messages, request.system, []).total;
|
|
1411
|
+
const usableChain = tracker ? chain.filter((e) => tracker.isAvailable(e.providerId, e.model)) : chain;
|
|
1412
|
+
for (const entry of usableChain) {
|
|
1413
|
+
if (!evaluateModelCalendar(config.modelAvailabilitySchedule, entry.providerId, entry.model).allowed)
|
|
1414
|
+
continue;
|
|
1415
|
+
if (tracker && !tracker.isAvailable(entry.providerId, entry.model)) {
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (entry.providerId === provider.id && entry.model === target.model) continue;
|
|
1419
|
+
let fbProvider;
|
|
1420
|
+
try {
|
|
1421
|
+
fbProvider = await this.opts.buildProvider(entry.providerId, entry.model);
|
|
1422
|
+
} catch (err) {
|
|
1423
|
+
lastError = err;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
const fbMaxContext = fbProvider.capabilities.maxContext;
|
|
1427
|
+
if (typeof fbMaxContext === "number" && Number.isFinite(fbMaxContext) && fbMaxContext > 0 && estimatedTokens > fbMaxContext) {
|
|
1428
|
+
this.opts.logger?.debug(
|
|
1429
|
+
`one-shot: skipping "${entry.providerId}/${entry.model}" \u2014 context window (${fbMaxContext}) is smaller than request tokens (${estimatedTokens})`
|
|
1430
|
+
);
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
servingProviderId = fbProvider.id;
|
|
1434
|
+
servingModel = entry.model;
|
|
1435
|
+
attempts += 1;
|
|
1436
|
+
const attempt = await this.tryCall(
|
|
1437
|
+
fbProvider,
|
|
1438
|
+
this.buildRequest(input, entry.model),
|
|
1439
|
+
signal,
|
|
1440
|
+
entry.providerId,
|
|
1441
|
+
entry.model
|
|
1442
|
+
);
|
|
1443
|
+
if (attempt.response) {
|
|
1444
|
+
tracker?.recordSuccess(entry.providerId, entry.model);
|
|
1445
|
+
fromFallback = true;
|
|
1446
|
+
return this.buildResult(
|
|
1447
|
+
attempt.response,
|
|
1448
|
+
servingProviderId,
|
|
1449
|
+
servingModel,
|
|
1450
|
+
true,
|
|
1451
|
+
startedAt,
|
|
1452
|
+
attempts
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
lastError = attempt.error;
|
|
1456
|
+
}
|
|
1457
|
+
return this.buildErrorResult(
|
|
1458
|
+
lastError,
|
|
1459
|
+
servingProviderId,
|
|
1460
|
+
servingModel,
|
|
1461
|
+
fromFallback,
|
|
1462
|
+
startedAt,
|
|
1463
|
+
attempts
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
// ── Private helpers ─────────────────────────────────────────────
|
|
1467
|
+
/**
|
|
1468
|
+
* Resolve the target provider + model from input + config.
|
|
1469
|
+
* Priority: role-based routing > explicit providerId+model > defaults.
|
|
1470
|
+
*/
|
|
1471
|
+
resolveTarget(input, config) {
|
|
1472
|
+
if (input.role && this.opts.modelRouter) {
|
|
1473
|
+
const pick = this.opts.modelRouter.pickForTask(input.role, "");
|
|
1474
|
+
const hasExplicitTarget = Boolean(input.providerId || input.model);
|
|
1475
|
+
if (pick && (pick.fromMatrix === true || !hasExplicitTarget)) {
|
|
1476
|
+
return { providerId: pick.provider, model: pick.model };
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (input.providerId && input.model) {
|
|
1480
|
+
return { providerId: input.providerId, model: input.model };
|
|
1481
|
+
}
|
|
1482
|
+
if (input.model) {
|
|
1483
|
+
return { providerId: config.provider, model: input.model };
|
|
1484
|
+
}
|
|
1485
|
+
if (input.providerId) {
|
|
1486
|
+
return { providerId: input.providerId, model: config.model };
|
|
1487
|
+
}
|
|
1488
|
+
if (config.provider && config.model) {
|
|
1489
|
+
return { providerId: config.provider, model: config.model };
|
|
1490
|
+
}
|
|
1491
|
+
return void 0;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Build a Provider Request from the input.
|
|
1495
|
+
*/
|
|
1496
|
+
buildRequest(input, model) {
|
|
1497
|
+
const messages = [...input.messages ?? []];
|
|
1498
|
+
if (input.userPrompt) {
|
|
1499
|
+
messages.push({ role: "user", content: input.userPrompt });
|
|
1500
|
+
}
|
|
1501
|
+
const system = asTextBlocks(input.system);
|
|
1502
|
+
return {
|
|
1503
|
+
model,
|
|
1504
|
+
...system.length > 0 ? { system } : {},
|
|
1505
|
+
messages,
|
|
1506
|
+
maxTokens: input.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
1507
|
+
...input.temperature !== void 0 ? { temperature: input.temperature } : {},
|
|
1508
|
+
...input.responseFormat ? { responseFormat: input.responseFormat } : {}
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Resolve the abort signal. Provider calls always receive the per-call
|
|
1513
|
+
* timeout, composed with external cancellation when the caller supplies it.
|
|
1514
|
+
*/
|
|
1515
|
+
resolveSignal(input) {
|
|
1516
|
+
const timeoutSignal = AbortSignal.timeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
1517
|
+
return input.signal ? AbortSignal.any([input.signal, timeoutSignal]) : timeoutSignal;
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Build the fallback model chain from input + config + current target.
|
|
1521
|
+
* Delegates to {@link FallbackProfileManager.resolveCandidates} — the
|
|
1522
|
+
* shared constructor used by both the agent loop and the one-shot path,
|
|
1523
|
+
* so both produce identical ordering, depth, and fromExplicitSource
|
|
1524
|
+
* semantics.
|
|
1525
|
+
*
|
|
1526
|
+
* The injected {@link OneShotOrchestratorOptions.fallbackProfileManager}
|
|
1527
|
+
* is the only allowed manager — OneShot never owns a private snapshot
|
|
1528
|
+
* so a live `ConfigStore` change reaches every call without rebuilding
|
|
1529
|
+
* the manager.
|
|
1530
|
+
*/
|
|
1531
|
+
resolveFallbackChain(input, config, target) {
|
|
1532
|
+
return this.opts.fallbackProfileManager.resolveCandidates(target, {
|
|
1533
|
+
fallbackModels: input.fallbackModels && input.fallbackModels.length > 0 ? input.fallbackModels : config.fallbackModels,
|
|
1534
|
+
// The one-shot already tried `target` as the primary before entering
|
|
1535
|
+
// the chain. Passing it as `primary` suppresses the primary-insertion
|
|
1536
|
+
// step in resolveCandidates (primary === current → not pushed).
|
|
1537
|
+
primary: target
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
/** Attempt a provider call while preserving the actual failure for callers. */
|
|
1541
|
+
async tryCall(provider, request, signal, providerId, model) {
|
|
1542
|
+
try {
|
|
1543
|
+
const direct = (req) => provider.complete(req, { signal });
|
|
1544
|
+
const response = this.opts.wrapProviderCall ? await this.opts.wrapProviderCall(request, direct) : await direct(request);
|
|
1545
|
+
return { response, fallbackEligible: false };
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
if (err instanceof ProviderError && providerId && model) {
|
|
1548
|
+
this.opts.statusTracker?.recordFailure(
|
|
1549
|
+
providerId,
|
|
1550
|
+
model,
|
|
1551
|
+
err.kind,
|
|
1552
|
+
err.status,
|
|
1553
|
+
err.describe(),
|
|
1554
|
+
{ retryAfterMs: err.body?.retryAfterMs }
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
return {
|
|
1558
|
+
error: err,
|
|
1559
|
+
// Provider failures retain their normal fallback policy. A plain error
|
|
1560
|
+
// thrown by the host wrapper is a local refusal (for example,
|
|
1561
|
+
// prompt-firewall block mode), not evidence that another provider may
|
|
1562
|
+
// succeed; retrying would only replay the same blocked request and
|
|
1563
|
+
// misattribute the final error to the last fallback.
|
|
1564
|
+
fallbackEligible: !signal.aborted && (err instanceof ProviderError ? isFallbackWorthy(err.kind) : this.opts.wrapProviderCall === void 0)
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
/** Build a success result from a provider Response. */
|
|
1569
|
+
buildResult(response, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
|
|
1570
|
+
const textBlocks = response.content.filter(isTextBlock);
|
|
1571
|
+
const text = textBlocks.map((b) => b.text).join("\n").trim();
|
|
1572
|
+
return {
|
|
1573
|
+
text: text || "(empty response)",
|
|
1574
|
+
model: response.model ?? servingModel,
|
|
1575
|
+
provider: servingProviderId,
|
|
1576
|
+
tokens: {
|
|
1577
|
+
input: response.usage?.input ?? 0,
|
|
1578
|
+
output: response.usage?.output ?? 0,
|
|
1579
|
+
total: (response.usage?.input ?? 0) + (response.usage?.output ?? 0)
|
|
1580
|
+
},
|
|
1581
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1582
|
+
fromFallback,
|
|
1583
|
+
attempts,
|
|
1584
|
+
stopReason: response.stopReason
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
/** Build a total-failure error result. */
|
|
1588
|
+
buildErrorResult(error, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
|
|
1589
|
+
return {
|
|
1590
|
+
text: "",
|
|
1591
|
+
model: servingModel,
|
|
1592
|
+
provider: servingProviderId,
|
|
1593
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
1594
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1595
|
+
fromFallback,
|
|
1596
|
+
attempts,
|
|
1597
|
+
error: error instanceof Error ? error.message : String(error ?? "Unknown error")
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
function asTextBlocks(system) {
|
|
1602
|
+
if (!system) return [];
|
|
1603
|
+
if (Array.isArray(system)) return system;
|
|
1604
|
+
return [{ type: "text", text: system }];
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
export {
|
|
1608
|
+
COUNCIL_REFUSAL_OPTION_ID,
|
|
1609
|
+
DEFAULT_COUNCIL_MAX_CONCURRENCY,
|
|
1610
|
+
MAX_COUNCIL_CONCURRENCY,
|
|
1611
|
+
BUILTIN_COUNCIL_PERSONAS,
|
|
1612
|
+
BUILTIN_COUNCIL_PERSONA_IDS,
|
|
1613
|
+
CouncilPersonaRegistry,
|
|
1614
|
+
DEFAULT_COUNCIL_PERSONA_REGISTRY,
|
|
1615
|
+
createCouncilPersonaRegistry,
|
|
1616
|
+
DEFAULT_COUNCIL_QUORUM_FRACTION,
|
|
1617
|
+
DEFAULT_COUNCIL_APPROVAL_FRACTION,
|
|
1618
|
+
DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
|
|
1619
|
+
DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
|
|
1620
|
+
DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
|
|
1621
|
+
DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
|
|
1622
|
+
BUILTIN_COUNCIL_PROFILES,
|
|
1623
|
+
CouncilProfileRegistry,
|
|
1624
|
+
DEFAULT_COUNCIL_PROFILE_REGISTRY,
|
|
1625
|
+
createCouncilProfileRegistry,
|
|
1626
|
+
resolveCouncilProfile,
|
|
1627
|
+
normalizeCouncilProfile,
|
|
1628
|
+
COUNCIL_VOTER_PROMPT_PATH,
|
|
1629
|
+
COUNCIL_JUDGE_PROMPT_PATH,
|
|
1630
|
+
buildCouncilVoterSystemPrompt,
|
|
1631
|
+
buildCouncilJudgeSystemPrompt,
|
|
1632
|
+
buildCouncilQuestionPrompt,
|
|
1633
|
+
buildCouncilVoterUserPrompt,
|
|
1634
|
+
buildCouncilJudgeUserPrompt,
|
|
1635
|
+
validateCouncilOptions,
|
|
1636
|
+
resolveCouncilVotes,
|
|
1637
|
+
CouncilOrchestrator,
|
|
1638
|
+
OneShotOrchestrator
|
|
1639
|
+
};
|
|
1640
|
+
//# sourceMappingURL=chunk-PFCMSSUF.js.map
|