@wrongstack/core 0.298.3 → 0.300.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/chronicle/index.js +4 -1
- package/dist/coordination/agents/index.js +4 -1
- package/dist/coordination/director.d.ts +8 -0
- package/dist/coordination/fleet-manager.d.ts +48 -3
- package/dist/coordination/ifleet-manager.d.ts +2 -0
- package/dist/coordination/index.js +127 -24
- package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
- package/dist/core/fallback-model.d.ts +48 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +288 -34
- package/dist/core/instruction-template.d.ts +80 -0
- package/dist/core/system-prompt-blocks.d.ts +10 -1
- package/dist/core/system-prompt-builder.d.ts +35 -1
- package/dist/defaults/index.js +358 -117
- package/dist/design/index.js +4 -1
- package/dist/execution/autonomy-brain.d.ts +7 -0
- package/dist/execution/council-brain.d.ts +17 -2
- package/dist/execution/council-orchestrator.d.ts +23 -4
- package/dist/execution/council-personas.d.ts +10 -0
- package/dist/execution/council-prompts.d.ts +12 -1
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +412 -145
- package/dist/fleet-notifier.d.ts +9 -2
- package/dist/goal/index.js +4 -1
- package/dist/hooks/index.js +140 -10
- package/dist/hq/exposure.d.ts +0 -11
- package/dist/hq/index.js +34 -8
- package/dist/hq/protocol/client.d.ts +14 -1
- package/dist/hq/protocol/fleet.d.ts +22 -0
- package/dist/hq/protocol.js +12 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1718 -753
- package/dist/infrastructure/index.js +50 -2
- package/dist/infrastructure/mcp-servers.d.ts +35 -0
- package/dist/kernel/events/brain-events.d.ts +9 -0
- package/dist/kernel/events/provider-events.d.ts +49 -2
- package/dist/kernel/events/sdd-events.d.ts +2 -0
- package/dist/models/index.js +1 -1
- package/dist/plugin/api.d.ts +6 -0
- package/dist/plugin/config.d.ts +55 -0
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.js +138 -22
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +157 -42
- package/dist/security/permission-helpers.d.ts +23 -6
- package/dist/security/permission-policy.d.ts +16 -0
- package/dist/security/totp.d.ts +14 -0
- package/dist/storage/director-state.d.ts +7 -0
- package/dist/storage/index.js +46 -9
- package/dist/tools/council-tool.d.ts +1 -1
- package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
- package/dist/tools/index.js +449 -112
- package/dist/types/config/skills-fleet-brain.d.ts +4 -2
- package/dist/types/config/tools.d.ts +99 -0
- package/dist/types/council.d.ts +11 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.js +3 -3
- package/dist/types/multi-agent.d.ts +10 -0
- package/dist/types/one-shot-llm.d.ts +31 -3
- package/dist/types/plugin.d.ts +28 -0
- package/dist/types/session.d.ts +5 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/wstack-paths.d.ts +2 -0
- package/dist/worktree/index.js +47 -25
- package/dist/worktree/worktree-manager.d.ts +16 -10
- package/instructions/coordination/subagent-baseline.md +8 -0
- package/instructions/system-lite.md +83 -3
- package/instructions/system-pro.md +286 -97
- package/instructions/system.md +236 -85
- package/package.json +3 -3
package/dist/tools/index.js
CHANGED
|
@@ -46,6 +46,9 @@ var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
|
|
|
46
46
|
tags: ["users", "usability", "accessibility"]
|
|
47
47
|
})
|
|
48
48
|
]);
|
|
49
|
+
var BUILTIN_COUNCIL_PERSONA_IDS = Object.freeze(
|
|
50
|
+
BUILTIN_COUNCIL_PERSONAS.map((persona) => persona.id)
|
|
51
|
+
);
|
|
49
52
|
var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
|
|
50
53
|
byId;
|
|
51
54
|
constructor(personas = []) {
|
|
@@ -469,22 +472,29 @@ function buildCouncilJudgeUserPrompt(question, votes, opts = {}) {
|
|
|
469
472
|
"</council-ballots>"
|
|
470
473
|
].filter(Boolean).join("\n\n");
|
|
471
474
|
}
|
|
472
|
-
function
|
|
473
|
-
|
|
475
|
+
function validateCouncilOptions(options) {
|
|
476
|
+
const errors = [];
|
|
474
477
|
const seen = /* @__PURE__ */ new Set();
|
|
475
|
-
|
|
478
|
+
for (const option of options ?? []) {
|
|
476
479
|
const id = option.id.trim();
|
|
477
|
-
|
|
478
|
-
if (!
|
|
479
|
-
if (
|
|
480
|
-
if (seen.has(id)) throw new Error(`buildCouncilQuestionPrompt: duplicate option id "${id}".`);
|
|
480
|
+
if (!id) errors.push("Every option must have a non-empty `id`.");
|
|
481
|
+
if (!option.label.trim()) errors.push(`Option "${id || "<empty>"}" must have a label.`);
|
|
482
|
+
if (seen.has(id)) errors.push(`Duplicate option id "${id}".`);
|
|
481
483
|
seen.add(id);
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
484
|
+
}
|
|
485
|
+
return errors;
|
|
486
|
+
}
|
|
487
|
+
function normalizeOptions(options) {
|
|
488
|
+
if (!options) return [];
|
|
489
|
+
const errors = validateCouncilOptions(options);
|
|
490
|
+
if (errors.length > 0) {
|
|
491
|
+
throw new Error(`buildCouncilQuestionPrompt: ${errors.join("; ")}`);
|
|
492
|
+
}
|
|
493
|
+
return options.map((option) => ({
|
|
494
|
+
id: option.id.trim(),
|
|
495
|
+
label: option.label.trim(),
|
|
496
|
+
...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
|
|
497
|
+
}));
|
|
488
498
|
}
|
|
489
499
|
function requiredInstruction(path7) {
|
|
490
500
|
const text = readBundledInstructionText(path7);
|
|
@@ -592,6 +602,8 @@ function requireFraction(value, label) {
|
|
|
592
602
|
var COUNCIL_REFUSAL_OPTION_ID = "council_refuse";
|
|
593
603
|
var DEFAULT_COUNCIL_MAX_CONCURRENCY = 3;
|
|
594
604
|
var MAX_COUNCIL_CONCURRENCY = 8;
|
|
605
|
+
var OVERALL_TIMEOUT_REASON = "Council overall timeout exceeded.";
|
|
606
|
+
var CALL_CANCELLED_REASON = "Cancelled.";
|
|
595
607
|
var CouncilOrchestrator = class {
|
|
596
608
|
caller;
|
|
597
609
|
personas;
|
|
@@ -602,6 +614,17 @@ var CouncilOrchestrator = class {
|
|
|
602
614
|
fallbackProfileManager;
|
|
603
615
|
seatCaller;
|
|
604
616
|
judgeCaller;
|
|
617
|
+
/**
|
|
618
|
+
* Normalized ad-hoc profiles keyed by the caller's config object identity.
|
|
619
|
+
* The Brain adapter reuses ONE profile object for every decision, so this
|
|
620
|
+
* avoids re-validating + re-freezing it on every ask() without caching
|
|
621
|
+
* string-keyed registry lookups (those are already O(1)).
|
|
622
|
+
*
|
|
623
|
+
* Hosts must treat ad-hoc profile configs as IMMUTABLE once passed to
|
|
624
|
+
* ask(): the cache is keyed by object identity and never invalidated, so
|
|
625
|
+
* mutating a cached profile would silently serve the first snapshot.
|
|
626
|
+
*/
|
|
627
|
+
profileCache = /* @__PURE__ */ new WeakMap();
|
|
605
628
|
constructor(opts) {
|
|
606
629
|
if (!opts.caller && !opts.seatCaller && !opts.judgeCaller) {
|
|
607
630
|
throw new Error(
|
|
@@ -620,13 +643,33 @@ var CouncilOrchestrator = class {
|
|
|
620
643
|
this.seatCaller = opts.seatCaller;
|
|
621
644
|
this.judgeCaller = opts.judgeCaller;
|
|
622
645
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
646
|
+
/**
|
|
647
|
+
* Resolve the effective profile for a question. String ids and the default
|
|
648
|
+
* go through the registry (already O(1)); ad-hoc config objects are
|
|
649
|
+
* normalized once per stable object identity and cached, because hosts such
|
|
650
|
+
* as the Brain adapter pass the same profile object on every ask().
|
|
651
|
+
*/
|
|
652
|
+
resolveProfile(profile) {
|
|
653
|
+
if (typeof profile === "string" || profile === void 0) {
|
|
654
|
+
return resolveCouncilProfile(profile, {
|
|
655
|
+
registry: this.profiles,
|
|
656
|
+
personas: this.personas,
|
|
657
|
+
defaultProfile: this.defaultProfile
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
const cached = this.profileCache.get(profile);
|
|
661
|
+
if (cached) return cached;
|
|
662
|
+
const resolved = resolveCouncilProfile(profile, {
|
|
626
663
|
registry: this.profiles,
|
|
627
664
|
personas: this.personas,
|
|
628
665
|
defaultProfile: this.defaultProfile
|
|
629
666
|
});
|
|
667
|
+
this.profileCache.set(profile, resolved);
|
|
668
|
+
return resolved;
|
|
669
|
+
}
|
|
670
|
+
async ask(question) {
|
|
671
|
+
const startedAt = Date.now();
|
|
672
|
+
const profile = this.resolveProfile(question.profile);
|
|
630
673
|
validateRefusalCollision(question, this.refusalOptionId);
|
|
631
674
|
const timeoutSignal = AbortSignal.timeout(profile.overallTimeoutMs);
|
|
632
675
|
const signal = question.signal ? AbortSignal.any([question.signal, timeoutSignal]) : timeoutSignal;
|
|
@@ -643,11 +686,16 @@ var CouncilOrchestrator = class {
|
|
|
643
686
|
try {
|
|
644
687
|
return await this.callSeat(question, profile, seat, i, signal, usage);
|
|
645
688
|
} catch (error) {
|
|
689
|
+
const timedOut = signal.aborted && !question.signal?.aborted;
|
|
646
690
|
return {
|
|
647
691
|
seatId: seat.id,
|
|
648
692
|
persona: seat.persona,
|
|
649
|
-
|
|
650
|
-
|
|
693
|
+
// Only the caller's own cancel is a "cancelled" vote; the overall
|
|
694
|
+
// budget expiring is a failure (timeout), matching the envelope.
|
|
695
|
+
status: question.signal?.aborted ? "cancelled" : "failed",
|
|
696
|
+
// Canonical text for aborted-by-budget or cancelled seats, so one
|
|
697
|
+
// event does not surface a different string per code path.
|
|
698
|
+
error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : errorMessage(error)
|
|
651
699
|
};
|
|
652
700
|
}
|
|
653
701
|
}
|
|
@@ -657,7 +705,7 @@ var CouncilOrchestrator = class {
|
|
|
657
705
|
if (question.signal?.aborted) {
|
|
658
706
|
return resultEnvelope({
|
|
659
707
|
status: "cancelled",
|
|
660
|
-
reason:
|
|
708
|
+
reason: CALL_CANCELLED_REASON,
|
|
661
709
|
resolution: "none",
|
|
662
710
|
votes,
|
|
663
711
|
profile,
|
|
@@ -670,14 +718,17 @@ var CouncilOrchestrator = class {
|
|
|
670
718
|
if (timeoutSignal.aborted) {
|
|
671
719
|
return resultEnvelope({
|
|
672
720
|
status: "failed",
|
|
673
|
-
reason:
|
|
721
|
+
reason: OVERALL_TIMEOUT_REASON,
|
|
674
722
|
resolution: "none",
|
|
675
723
|
votes,
|
|
676
724
|
profile,
|
|
677
725
|
usage,
|
|
678
726
|
startedAt,
|
|
679
727
|
warnings,
|
|
680
|
-
|
|
728
|
+
// Seat-prefixed errors normally carry the canonical timeout text, but
|
|
729
|
+
// a signal-blind caller can resolve valid votes even after the budget
|
|
730
|
+
// expired — append the standalone entry only when nothing carries it.
|
|
731
|
+
errors: errors.some((entry) => entry.includes(OVERALL_TIMEOUT_REASON)) ? errors : [...errors, OVERALL_TIMEOUT_REASON]
|
|
681
732
|
});
|
|
682
733
|
}
|
|
683
734
|
if (!question.options || question.options.length === 0) {
|
|
@@ -704,7 +755,17 @@ var CouncilOrchestrator = class {
|
|
|
704
755
|
);
|
|
705
756
|
}
|
|
706
757
|
async callSeat(question, profile, seat, seatIndex, signal, usage) {
|
|
707
|
-
if (signal.aborted)
|
|
758
|
+
if (signal.aborted) {
|
|
759
|
+
return question.signal?.aborted ? cancelledVote(seat) : {
|
|
760
|
+
seatId: seat.id,
|
|
761
|
+
persona: seat.persona,
|
|
762
|
+
status: "failed",
|
|
763
|
+
...seat.target?.providerId ? { provider: seat.target.providerId } : {},
|
|
764
|
+
...seat.target?.model ? { model: seat.target.model } : {},
|
|
765
|
+
durationMs: 0,
|
|
766
|
+
error: OVERALL_TIMEOUT_REASON
|
|
767
|
+
};
|
|
768
|
+
}
|
|
708
769
|
let persona;
|
|
709
770
|
try {
|
|
710
771
|
persona = this.personas.require(seat.persona);
|
|
@@ -730,12 +791,15 @@ var CouncilOrchestrator = class {
|
|
|
730
791
|
});
|
|
731
792
|
const metadata = callMetadata(result);
|
|
732
793
|
if (result.error) {
|
|
794
|
+
const timedOut = signal.aborted && !question.signal?.aborted;
|
|
733
795
|
return {
|
|
734
796
|
seatId: seat.id,
|
|
735
797
|
persona: seat.persona,
|
|
736
|
-
status: signal
|
|
798
|
+
status: question.signal?.aborted ? "cancelled" : "failed",
|
|
737
799
|
...metadata,
|
|
738
|
-
|
|
800
|
+
// Canonical text for cancelled or aborted-by-budget seats, so one
|
|
801
|
+
// cancel/timeout event does not surface a raw provider string.
|
|
802
|
+
error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : result.error
|
|
739
803
|
};
|
|
740
804
|
}
|
|
741
805
|
const parsed = parseVote(result.text, question, this.refusalOptionId);
|
|
@@ -834,9 +898,27 @@ var CouncilOrchestrator = class {
|
|
|
834
898
|
signal,
|
|
835
899
|
usage
|
|
836
900
|
);
|
|
901
|
+
if (signal.aborted) {
|
|
902
|
+
const cancelled = question.signal?.aborted === true;
|
|
903
|
+
const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
|
|
904
|
+
return resultEnvelope({
|
|
905
|
+
status: cancelled ? "cancelled" : "failed",
|
|
906
|
+
reason,
|
|
907
|
+
resolution: "none",
|
|
908
|
+
votes,
|
|
909
|
+
profile,
|
|
910
|
+
usage,
|
|
911
|
+
startedAt,
|
|
912
|
+
warnings,
|
|
913
|
+
errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
|
|
914
|
+
judgeUsed: true
|
|
915
|
+
});
|
|
916
|
+
}
|
|
837
917
|
if (!judged.ok) {
|
|
838
918
|
return resultEnvelope({
|
|
839
|
-
|
|
919
|
+
// User cancel -> cancelled; overall budget expired mid-judge -> failed;
|
|
920
|
+
// otherwise the judge simply failed -> abstained (can't decide).
|
|
921
|
+
status: question.signal?.aborted ? "cancelled" : signal.aborted ? "failed" : "abstained",
|
|
840
922
|
reason: judged.error,
|
|
841
923
|
resolution: "none",
|
|
842
924
|
votes,
|
|
@@ -896,6 +978,19 @@ var CouncilOrchestrator = class {
|
|
|
896
978
|
});
|
|
897
979
|
}
|
|
898
980
|
if (!profile.judge) {
|
|
981
|
+
if (divergentStances(valid)) {
|
|
982
|
+
return resultEnvelope({
|
|
983
|
+
status: "abstained",
|
|
984
|
+
reason: "Council produced multiple distinct stances and has no judge to reconcile them.",
|
|
985
|
+
resolution: "none",
|
|
986
|
+
votes,
|
|
987
|
+
profile,
|
|
988
|
+
usage,
|
|
989
|
+
startedAt,
|
|
990
|
+
warnings,
|
|
991
|
+
errors
|
|
992
|
+
});
|
|
993
|
+
}
|
|
899
994
|
const first = valid[0];
|
|
900
995
|
if (!first) {
|
|
901
996
|
return resultEnvelope({
|
|
@@ -932,9 +1027,30 @@ var CouncilOrchestrator = class {
|
|
|
932
1027
|
signal,
|
|
933
1028
|
usage
|
|
934
1029
|
);
|
|
1030
|
+
if (signal.aborted) {
|
|
1031
|
+
const cancelled = question.signal?.aborted === true;
|
|
1032
|
+
const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
|
|
1033
|
+
return resultEnvelope({
|
|
1034
|
+
status: cancelled ? "cancelled" : "failed",
|
|
1035
|
+
reason,
|
|
1036
|
+
resolution: "none",
|
|
1037
|
+
votes,
|
|
1038
|
+
profile,
|
|
1039
|
+
usage,
|
|
1040
|
+
startedAt,
|
|
1041
|
+
warnings,
|
|
1042
|
+
errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
|
|
1043
|
+
judgeUsed: true
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
935
1046
|
if (!judged.ok) {
|
|
936
1047
|
return resultEnvelope({
|
|
937
|
-
|
|
1048
|
+
// User cancel -> cancelled; overall budget expired mid-judge -> failed;
|
|
1049
|
+
// otherwise the judge simply failed -> failed (open questions cannot
|
|
1050
|
+
// abstain for a judge failure — 'abstained' is reserved for quorum
|
|
1051
|
+
// failure and stance divergence; the option path maps this same
|
|
1052
|
+
// plain-judge-failure case to 'abstained' instead).
|
|
1053
|
+
status: question.signal?.aborted ? "cancelled" : "failed",
|
|
938
1054
|
reason: judged.error,
|
|
939
1055
|
resolution: "none",
|
|
940
1056
|
votes,
|
|
@@ -973,18 +1089,28 @@ var CouncilOrchestrator = class {
|
|
|
973
1089
|
signal,
|
|
974
1090
|
usage
|
|
975
1091
|
});
|
|
976
|
-
if (result.error)
|
|
1092
|
+
if (result.error) {
|
|
1093
|
+
if (signal.aborted && !question.signal?.aborted) {
|
|
1094
|
+
return { ok: false, error: OVERALL_TIMEOUT_REASON };
|
|
1095
|
+
}
|
|
1096
|
+
if (question.signal?.aborted) {
|
|
1097
|
+
return { ok: false, error: CALL_CANCELLED_REASON };
|
|
1098
|
+
}
|
|
1099
|
+
return { ok: false, error: result.error };
|
|
1100
|
+
}
|
|
977
1101
|
return parseJudge(result.text, question, this.refusalOptionId);
|
|
978
1102
|
}
|
|
979
1103
|
/**
|
|
980
|
-
* Resolve the effective LLM caller for a call. Voter seats
|
|
981
|
-
* `seatCaller(seatIndex)` when wired
|
|
982
|
-
*
|
|
983
|
-
*
|
|
1104
|
+
* Resolve the effective LLM caller for a call. Voter seats (defined
|
|
1105
|
+
* seatIndex) use `seatCaller(seatIndex)` when wired, otherwise the shared
|
|
1106
|
+
* `caller` — a seat never falls through to the judge caller. Judge seats
|
|
1107
|
+
* (seatIndex undefined) use `judgeCaller` if set, otherwise `seatCaller(0)`
|
|
1108
|
+
* if set, otherwise the shared `caller`.
|
|
984
1109
|
*/
|
|
985
1110
|
resolveCaller(seatIndex) {
|
|
986
|
-
if (seatIndex !== void 0
|
|
987
|
-
return this.seatCaller(seatIndex);
|
|
1111
|
+
if (seatIndex !== void 0) {
|
|
1112
|
+
if (this.seatCaller) return this.seatCaller(seatIndex);
|
|
1113
|
+
return this.caller ?? this.judgeCaller;
|
|
988
1114
|
}
|
|
989
1115
|
if (this.judgeCaller) return this.judgeCaller;
|
|
990
1116
|
if (this.seatCaller) return this.seatCaller(0);
|
|
@@ -993,6 +1119,7 @@ var CouncilOrchestrator = class {
|
|
|
993
1119
|
async safeCall(input) {
|
|
994
1120
|
const effectiveCaller = this.resolveCaller(input.seatIndex);
|
|
995
1121
|
const resolvedTarget = this.resolveCouncilTarget(input.target);
|
|
1122
|
+
const startedAt = Date.now();
|
|
996
1123
|
try {
|
|
997
1124
|
const result = await effectiveCaller.call({
|
|
998
1125
|
system: input.system,
|
|
@@ -1009,8 +1136,17 @@ var CouncilOrchestrator = class {
|
|
|
1009
1136
|
addUsage(input.usage, result);
|
|
1010
1137
|
return result;
|
|
1011
1138
|
} catch (error) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1139
|
+
const failed = {
|
|
1140
|
+
text: "",
|
|
1141
|
+
model: resolvedTarget?.model ?? "",
|
|
1142
|
+
provider: resolvedTarget?.providerId ?? "",
|
|
1143
|
+
tokens: { input: 0, output: 0, total: 0 },
|
|
1144
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
1145
|
+
fromFallback: false,
|
|
1146
|
+
error: errorMessage(error)
|
|
1147
|
+
};
|
|
1148
|
+
addUsage(input.usage, failed);
|
|
1149
|
+
return failed;
|
|
1014
1150
|
}
|
|
1015
1151
|
}
|
|
1016
1152
|
/**
|
|
@@ -1042,47 +1178,77 @@ var CouncilOrchestrator = class {
|
|
|
1042
1178
|
};
|
|
1043
1179
|
}
|
|
1044
1180
|
};
|
|
1045
|
-
function
|
|
1181
|
+
function parseCouncilResponse(text, question, refusalOptionId, opts) {
|
|
1182
|
+
const roleLabel = opts.role === "judge" ? "Judge" : "Voter";
|
|
1046
1183
|
const parsed = parseObject(text);
|
|
1047
1184
|
if (!parsed.ok && (!question.options || question.options.length === 0)) {
|
|
1048
1185
|
const fallback = text.trim();
|
|
1049
|
-
if (fallback) return { ok: true,
|
|
1050
|
-
return { ok: false, error:
|
|
1186
|
+
if (fallback) return { ok: true, value: { [opts.freeTextField]: fallback } };
|
|
1187
|
+
return { ok: false, error: `${roleLabel} returned an empty response.` };
|
|
1051
1188
|
}
|
|
1052
1189
|
if (!parsed.ok) return parsed;
|
|
1053
1190
|
const rationale = optionalString(parsed.value["rationale"]);
|
|
1054
1191
|
if (question.options && question.options.length > 0) {
|
|
1055
1192
|
const optionId = optionalString(parsed.value["optionId"]);
|
|
1056
|
-
const allowed = /* @__PURE__ */ new Set([
|
|
1193
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
1194
|
+
...question.options.map((option) => option.id.trim()),
|
|
1195
|
+
refusalOptionId
|
|
1196
|
+
]);
|
|
1057
1197
|
if (!optionId || !allowed.has(optionId)) {
|
|
1058
|
-
return { ok: false, error:
|
|
1198
|
+
return { ok: false, error: `${roleLabel} returned an unknown or missing optionId.` };
|
|
1059
1199
|
}
|
|
1060
|
-
return { ok: true,
|
|
1200
|
+
return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
|
|
1201
|
+
}
|
|
1202
|
+
const freeText = optionalString(parsed.value[opts.freeTextField]);
|
|
1203
|
+
if (!freeText) {
|
|
1204
|
+
return {
|
|
1205
|
+
ok: false,
|
|
1206
|
+
error: `${roleLabel} returned an empty or missing ${opts.freeTextField}.`
|
|
1207
|
+
};
|
|
1061
1208
|
}
|
|
1062
|
-
|
|
1063
|
-
if (!stance) return { ok: false, error: "Voter returned an empty or missing stance." };
|
|
1064
|
-
return { ok: true, vote: { stance, ...rationale ? { rationale } : {} } };
|
|
1209
|
+
return { ok: true, value: { [opts.freeTextField]: freeText, ...rationale ? { rationale } : {} } };
|
|
1065
1210
|
}
|
|
1066
|
-
function
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1211
|
+
function divergentStances(valid) {
|
|
1212
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1213
|
+
for (const vote of valid) {
|
|
1214
|
+
const normalized = vote.stance.trim().replace(/^["'`]+|["'`]+$/g, "").toLowerCase().replace(/[.!?;:,]+$/g, "").replace(/\s+/g, " ").trim();
|
|
1215
|
+
if (!normalized) return true;
|
|
1216
|
+
seen.add(normalized);
|
|
1217
|
+
if (seen.size > 1) return true;
|
|
1072
1218
|
}
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
function parseVote(text, question, refusalOptionId) {
|
|
1222
|
+
const parsed = parseCouncilResponse(text, question, refusalOptionId, {
|
|
1223
|
+
role: "voter",
|
|
1224
|
+
freeTextField: "stance"
|
|
1225
|
+
});
|
|
1073
1226
|
if (!parsed.ok) return parsed;
|
|
1074
|
-
const rationale =
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1227
|
+
const { optionId, stance, rationale } = parsed.value;
|
|
1228
|
+
return {
|
|
1229
|
+
ok: true,
|
|
1230
|
+
vote: {
|
|
1231
|
+
...optionId ? { optionId } : {},
|
|
1232
|
+
...stance ? { stance } : {},
|
|
1233
|
+
...rationale ? { rationale } : {}
|
|
1080
1234
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function parseJudge(text, question, refusalOptionId) {
|
|
1238
|
+
const parsed = parseCouncilResponse(text, question, refusalOptionId, {
|
|
1239
|
+
role: "judge",
|
|
1240
|
+
freeTextField: "answer"
|
|
1241
|
+
});
|
|
1242
|
+
if (!parsed.ok) return parsed;
|
|
1243
|
+
const { optionId, answer, rationale } = parsed.value;
|
|
1244
|
+
return {
|
|
1245
|
+
ok: true,
|
|
1246
|
+
value: {
|
|
1247
|
+
...optionId ? { optionId } : {},
|
|
1248
|
+
...answer ? { answer } : {},
|
|
1249
|
+
...rationale ? { rationale } : {}
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1086
1252
|
}
|
|
1087
1253
|
function parseObject(text) {
|
|
1088
1254
|
const trimmed = text.trim();
|
|
@@ -1126,7 +1292,7 @@ function callMetadata(result) {
|
|
|
1126
1292
|
};
|
|
1127
1293
|
}
|
|
1128
1294
|
function addUsage(usage, result) {
|
|
1129
|
-
usage.calls += 1;
|
|
1295
|
+
usage.calls += Math.max(1, result.attempts ?? 1);
|
|
1130
1296
|
usage.inputTokens += result.tokens.input;
|
|
1131
1297
|
usage.outputTokens += result.tokens.output;
|
|
1132
1298
|
usage.totalTokens += result.tokens.total;
|
|
@@ -1135,21 +1301,40 @@ function usageResult(usage, startedAt) {
|
|
|
1135
1301
|
return Object.freeze({ ...usage, durationMs: Math.max(0, Date.now() - startedAt) });
|
|
1136
1302
|
}
|
|
1137
1303
|
function cancelledVote(seat) {
|
|
1138
|
-
return {
|
|
1304
|
+
return {
|
|
1305
|
+
seatId: seat.id,
|
|
1306
|
+
persona: seat.persona,
|
|
1307
|
+
status: "cancelled",
|
|
1308
|
+
...seat.target?.providerId ? { provider: seat.target.providerId } : {},
|
|
1309
|
+
...seat.target?.model ? { model: seat.target.model } : {},
|
|
1310
|
+
durationMs: 0,
|
|
1311
|
+
error: CALL_CANCELLED_REASON
|
|
1312
|
+
};
|
|
1139
1313
|
}
|
|
1140
1314
|
function distinctTargetCount(votes, profile) {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1315
|
+
return new Set(distinctTargetKeys(votes, profile)).size;
|
|
1316
|
+
}
|
|
1317
|
+
function distinctTargetKeys(votes, profile) {
|
|
1318
|
+
const keys = [];
|
|
1319
|
+
for (const vote of votes) {
|
|
1320
|
+
if (vote.status !== "valid") continue;
|
|
1321
|
+
const provider = vote.provider?.trim() ?? "";
|
|
1322
|
+
const model = vote.model?.trim() ?? "";
|
|
1323
|
+
if (profile.distinctness === "provider") {
|
|
1324
|
+
if (provider) keys.push(provider);
|
|
1325
|
+
} else if (provider || model) {
|
|
1326
|
+
keys.push(`${provider}/${model}`);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return keys;
|
|
1145
1330
|
}
|
|
1146
1331
|
function distinctnessWarnings(votes, profile) {
|
|
1147
1332
|
if (profile.distinctness === "none") return [];
|
|
1148
|
-
const
|
|
1149
|
-
const distinct =
|
|
1150
|
-
if (
|
|
1333
|
+
const keys = distinctTargetKeys(votes, profile);
|
|
1334
|
+
const distinct = new Set(keys).size;
|
|
1335
|
+
if (keys.length > 1 && distinct < keys.length) {
|
|
1151
1336
|
return [
|
|
1152
|
-
`Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${
|
|
1337
|
+
`Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${keys.length} valid vote(s).`
|
|
1153
1338
|
];
|
|
1154
1339
|
}
|
|
1155
1340
|
return [];
|
|
@@ -1185,17 +1370,6 @@ async function mapConcurrent(items, concurrency, worker) {
|
|
|
1185
1370
|
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => run()));
|
|
1186
1371
|
return results;
|
|
1187
1372
|
}
|
|
1188
|
-
function emptyCallResult(error) {
|
|
1189
|
-
return {
|
|
1190
|
-
text: "",
|
|
1191
|
-
model: "",
|
|
1192
|
-
provider: "",
|
|
1193
|
-
tokens: { input: 0, output: 0, total: 0 },
|
|
1194
|
-
durationMs: 0,
|
|
1195
|
-
fromFallback: false,
|
|
1196
|
-
error
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
1373
|
function optionalString(value) {
|
|
1200
1374
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1201
1375
|
}
|
|
@@ -1238,7 +1412,7 @@ var INPUT_SCHEMA = {
|
|
|
1238
1412
|
},
|
|
1239
1413
|
profile: {
|
|
1240
1414
|
type: "string",
|
|
1241
|
-
description:
|
|
1415
|
+
description: 'Registered Council profile id (e.g. "balanced", "fast", "risk-review"). Defaults to the host-configured profile.'
|
|
1242
1416
|
}
|
|
1243
1417
|
},
|
|
1244
1418
|
required: ["question"],
|
|
@@ -1249,10 +1423,12 @@ function createCouncilTool(opts) {
|
|
|
1249
1423
|
...opts,
|
|
1250
1424
|
fallbackProfileManager: opts.fallbackProfileManager
|
|
1251
1425
|
});
|
|
1426
|
+
const profiles = opts.profiles ?? DEFAULT_COUNCIL_PROFILE_REGISTRY;
|
|
1427
|
+
const profileIds = profiles.list().map((profile) => profile.id);
|
|
1252
1428
|
return {
|
|
1253
1429
|
name: COUNCIL_TOOL_NAME,
|
|
1254
1430
|
description: "Ask an independent, multi-persona Council to evaluate a decision or synthesize an answer. Uses bounded parallel voters, quorum/veto/weighted resolution, optional judging, model routing, fallback chains, and cancellation.",
|
|
1255
|
-
usageHint:
|
|
1431
|
+
usageHint: `Use for consequential or disputed decisions that benefit from independent lenses. Provide \`options\` for a vote or omit them for an open answer. Keep context evidence-focused; the Council treats it as untrusted data. Available profiles: ${profileIds.join(", ")}.`,
|
|
1256
1432
|
category: "meta",
|
|
1257
1433
|
inputSchema: INPUT_SCHEMA,
|
|
1258
1434
|
permission: "auto",
|
|
@@ -1270,11 +1446,17 @@ function createCouncilTool(opts) {
|
|
|
1270
1446
|
};
|
|
1271
1447
|
return orchestrator.ask(question);
|
|
1272
1448
|
},
|
|
1273
|
-
validate: validateCouncilToolInput
|
|
1449
|
+
validate: (input) => validateCouncilToolInput(input, profileIds)
|
|
1274
1450
|
};
|
|
1275
1451
|
}
|
|
1276
|
-
function validateCouncilToolInput(input) {
|
|
1452
|
+
function validateCouncilToolInput(input, profileIds) {
|
|
1277
1453
|
const errors = [];
|
|
1454
|
+
if (typeof input.profile === "string" && input.profile.trim()) {
|
|
1455
|
+
const id = input.profile.trim();
|
|
1456
|
+
if (!profileIds.includes(id)) {
|
|
1457
|
+
errors.push(`Unknown \`profile\` "${id}". Available: ${profileIds.join(", ")}.`);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1278
1460
|
const question = input.question?.trim() ?? "";
|
|
1279
1461
|
if (!question) errors.push("`question` must not be empty.");
|
|
1280
1462
|
if (question.length > MAX_COUNCIL_QUESTION_CHARS) {
|
|
@@ -1286,14 +1468,7 @@ function validateCouncilToolInput(input) {
|
|
|
1286
1468
|
if ((input.options?.length ?? 0) > MAX_COUNCIL_TOOL_OPTIONS) {
|
|
1287
1469
|
errors.push(`\`options\` must not contain more than ${MAX_COUNCIL_TOOL_OPTIONS} items.`);
|
|
1288
1470
|
}
|
|
1289
|
-
|
|
1290
|
-
for (const option of input.options ?? []) {
|
|
1291
|
-
const id = option.id.trim();
|
|
1292
|
-
if (!id) errors.push("Every option must have a non-empty `id`.");
|
|
1293
|
-
if (!option.label.trim()) errors.push(`Option "${id || "<empty>"}" must have a label.`);
|
|
1294
|
-
if (ids.has(id)) errors.push(`Duplicate option id "${id}".`);
|
|
1295
|
-
ids.add(id);
|
|
1296
|
-
}
|
|
1471
|
+
errors.push(...validateCouncilOptions(input.options));
|
|
1297
1472
|
return errors;
|
|
1298
1473
|
}
|
|
1299
1474
|
|
|
@@ -1418,7 +1593,7 @@ var FsError = class extends WrongStackError {
|
|
|
1418
1593
|
var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)(?:\s+(?:has|have))?(?:\s+been)?[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit|usage[-_\s]*limit[-_\s]*(?:reached|exceeded)/i;
|
|
1419
1594
|
|
|
1420
1595
|
// src/types/provider.ts
|
|
1421
|
-
var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt
|
|
1596
|
+
var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|(?:prompt|request|input|messages?).{0,12}too (?:large|long)|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|context_length_exceeded/i;
|
|
1422
1597
|
var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
|
|
1423
1598
|
var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
|
|
1424
1599
|
function classifyProviderError(status, body, message) {
|
|
@@ -1428,7 +1603,7 @@ function classifyProviderError(status, body, message) {
|
|
|
1428
1603
|
if (status === 408) return "timeout";
|
|
1429
1604
|
if (status === 599) return "stream_hang";
|
|
1430
1605
|
if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
|
|
1431
|
-
if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
|
|
1606
|
+
if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
|
|
1432
1607
|
return "quota_exhausted";
|
|
1433
1608
|
}
|
|
1434
1609
|
if (type === "rate_limit_error" || status === 429) return "rate_limit";
|
|
@@ -1488,7 +1663,7 @@ var ProviderError = class extends WrongStackError {
|
|
|
1488
1663
|
const e = err;
|
|
1489
1664
|
const name = e.name;
|
|
1490
1665
|
if (typeof name !== "string" || !name.endsWith("Error")) return false;
|
|
1491
|
-
return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
|
|
1666
|
+
return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
|
|
1492
1667
|
}
|
|
1493
1668
|
constructor(message, status, retryable, providerId, opts = {}) {
|
|
1494
1669
|
const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
|
|
@@ -2216,7 +2391,9 @@ function safeProfileName(name) {
|
|
|
2216
2391
|
function activeProfileName(globalRoot) {
|
|
2217
2392
|
try {
|
|
2218
2393
|
const parsed = JSON.parse(fs.readFileSync(path2.join(globalRoot, "config.json"), "utf8"));
|
|
2219
|
-
return safeProfileName(
|
|
2394
|
+
return safeProfileName(
|
|
2395
|
+
typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
|
|
2396
|
+
);
|
|
2220
2397
|
} catch {
|
|
2221
2398
|
return "default";
|
|
2222
2399
|
}
|
|
@@ -2296,6 +2473,7 @@ function resolveWstackPaths(opts) {
|
|
|
2296
2473
|
projectPlan: path2.join(projectDir, "plan.json"),
|
|
2297
2474
|
projectAutophase: path2.join(projectDir, "autophase"),
|
|
2298
2475
|
projectSddBoards: path2.join(projectDir, "sdd-boards"),
|
|
2476
|
+
projectRequirementIntakes: path2.join(projectDir, "requirement-intakes"),
|
|
2299
2477
|
syncConfig: path2.join(profileDir, "sync.json"),
|
|
2300
2478
|
configHistoryDir: path2.join(globalRoot, "config-history"),
|
|
2301
2479
|
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
@@ -6185,8 +6363,18 @@ var SYSTEM_CONFIG_VIEW_SCHEMA = {
|
|
|
6185
6363
|
properties: {
|
|
6186
6364
|
section: {
|
|
6187
6365
|
type: "string",
|
|
6188
|
-
enum: [
|
|
6189
|
-
|
|
6366
|
+
enum: [
|
|
6367
|
+
"all",
|
|
6368
|
+
"providers",
|
|
6369
|
+
"models",
|
|
6370
|
+
"fallbacks",
|
|
6371
|
+
"matrix",
|
|
6372
|
+
"agents",
|
|
6373
|
+
"fleet",
|
|
6374
|
+
"refiner",
|
|
6375
|
+
"doctor"
|
|
6376
|
+
],
|
|
6377
|
+
description: "Which section to show: all (everything), providers (configured providers + keys), models (favorites + leader), fallbacks (chain + profiles + toggles), matrix (per-role assignments), agents (every catalog agent with resolved model), fleet (concurrency + lifetime spawn/token/cost budgets), refiner (goal refinement config), doctor (validate config and show issues/warnings). Default: all."
|
|
6190
6378
|
}
|
|
6191
6379
|
},
|
|
6192
6380
|
additionalProperties: false
|
|
@@ -6288,6 +6476,7 @@ ${content}`);
|
|
|
6288
6476
|
const profiles = config.fallbackProfiles ?? {};
|
|
6289
6477
|
const chain = config.fallbackModels ?? [];
|
|
6290
6478
|
const matrix = config.modelMatrix ?? {};
|
|
6479
|
+
const fleetBudget = config.fleet?.budget;
|
|
6291
6480
|
const bridge = config.fallbackBridge?.trim();
|
|
6292
6481
|
if (bridge) {
|
|
6293
6482
|
const parsed = parseModelRef(bridge);
|
|
@@ -6357,6 +6546,26 @@ ${content}`);
|
|
|
6357
6546
|
}
|
|
6358
6547
|
}
|
|
6359
6548
|
}
|
|
6549
|
+
if (typeof config.maxConcurrent === "number") {
|
|
6550
|
+
if (!Number.isFinite(config.maxConcurrent) || config.maxConcurrent < 0) {
|
|
6551
|
+
issues.push(`maxConcurrent must be a non-negative number (got ${config.maxConcurrent})`);
|
|
6552
|
+
} else if (config.maxConcurrent === 0) {
|
|
6553
|
+
warnings.push("maxConcurrent is 0 \u2014 subagent concurrency effectively disabled");
|
|
6554
|
+
} else {
|
|
6555
|
+
ok.push(`maxConcurrent ${config.maxConcurrent}`);
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
if (fleetBudget && typeof fleetBudget === "object") {
|
|
6559
|
+
for (const key of ["maxSpawns", "maxTokens", "maxCostUsd"]) {
|
|
6560
|
+
const v = fleetBudget[key];
|
|
6561
|
+
if (v === void 0) continue;
|
|
6562
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0) {
|
|
6563
|
+
issues.push(`fleet.budget.${key} must be a non-negative number (got ${JSON.stringify(v)})`);
|
|
6564
|
+
} else {
|
|
6565
|
+
ok.push(`fleet.budget.${key} ${v}`);
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6360
6569
|
for (const [key, entry] of Object.entries(matrix)) {
|
|
6361
6570
|
const eProvider = entry.provider ?? config.provider;
|
|
6362
6571
|
const eModel = entry.model;
|
|
@@ -6397,6 +6606,27 @@ ${content}`);
|
|
|
6397
6606
|
}
|
|
6398
6607
|
addSection("Configuration Doctor", lines.join("\n"));
|
|
6399
6608
|
}
|
|
6609
|
+
if (section === "all" || section === "fleet") {
|
|
6610
|
+
const fleet = config.fleet;
|
|
6611
|
+
const budget = fleet?.budget;
|
|
6612
|
+
const lifecycle = fleet?.lifecycle;
|
|
6613
|
+
const fmt = (n, unit = "") => typeof n === "number" && Number.isFinite(n) ? `${n}${unit}` : "(default)";
|
|
6614
|
+
addSection(
|
|
6615
|
+
"Fleet Budgets (configured ceilings)",
|
|
6616
|
+
[
|
|
6617
|
+
` maxConcurrent: ${typeof config.maxConcurrent === "number" ? config.maxConcurrent : "(default 4)"}`,
|
|
6618
|
+
` fleet.budget.maxSpawns: ${fmt(budget?.maxSpawns)} ${typeof budget?.maxSpawns !== "number" ? "\u2192 default 64" : ""}`,
|
|
6619
|
+
` fleet.budget.maxTokens: ${fmt(budget?.maxTokens)}`,
|
|
6620
|
+
` fleet.budget.maxCostUsd: ${fmt(budget?.maxCostUsd)}`,
|
|
6621
|
+
` fleet.lifecycle.idleTimeoutMs: ${fmt(lifecycle?.idleTimeoutMs, "ms")}`,
|
|
6622
|
+
` fleet.lifecycle.retireOnTaskComplete: ${lifecycle?.retireOnTaskComplete === void 0 ? "(default true)" : String(lifecycle.retireOnTaskComplete)}`,
|
|
6623
|
+
"",
|
|
6624
|
+
" Live used/remaining spawns are on /fleet status (not static config).",
|
|
6625
|
+
" Override ceilings: --max-concurrent / WRONGSTACK_MAX_CONCURRENT,",
|
|
6626
|
+
" --max-spawns / WRONGSTACK_MAX_SPAWNS, or fleet.budget.maxSpawns in profile."
|
|
6627
|
+
].join("\n")
|
|
6628
|
+
);
|
|
6629
|
+
}
|
|
6400
6630
|
if (section === "all" || section === "refiner") {
|
|
6401
6631
|
const ref = config.autonomy;
|
|
6402
6632
|
addSection(
|
|
@@ -7187,6 +7417,38 @@ var sshManagerServer = () => ({
|
|
|
7187
7417
|
permission: "confirm",
|
|
7188
7418
|
requestTimeoutMs: 18e4
|
|
7189
7419
|
});
|
|
7420
|
+
var requirementIntakeServer = () => ({
|
|
7421
|
+
name: "requirement-intake",
|
|
7422
|
+
description: "WrongStack Requirements Intake \u2014 list intake records and file new ones (project-scoped, --writable)",
|
|
7423
|
+
transport: "stdio",
|
|
7424
|
+
command: "wstack-requirement-intake-mcp",
|
|
7425
|
+
args: ["--project-root", ".", "--writable"],
|
|
7426
|
+
permission: "auto"
|
|
7427
|
+
});
|
|
7428
|
+
var kanbanServer = () => ({
|
|
7429
|
+
name: "kanban",
|
|
7430
|
+
description: "WrongStack Kanban \u2014 inspect and manage project work boards (project-scoped, manage tier, no destructive ops)",
|
|
7431
|
+
transport: "stdio",
|
|
7432
|
+
command: "wstack-kanban-mcp",
|
|
7433
|
+
args: ["--project-root", ".", "--writable"],
|
|
7434
|
+
permission: "confirm"
|
|
7435
|
+
});
|
|
7436
|
+
var mailboxServer = () => ({
|
|
7437
|
+
name: "mailbox",
|
|
7438
|
+
description: "WrongStack Mailbox \u2014 read and send project agent mail (project-scoped, no admin/credentials)",
|
|
7439
|
+
transport: "stdio",
|
|
7440
|
+
command: "wstack-mailbox-mcp",
|
|
7441
|
+
args: ["--project-root", ".", "--actor", "external-agent", "--writable"],
|
|
7442
|
+
permission: "auto"
|
|
7443
|
+
});
|
|
7444
|
+
var codebaseIndexServer = () => ({
|
|
7445
|
+
name: "codebase-index",
|
|
7446
|
+
description: "WrongStack Codebase Index \u2014 symbol search and dependency graphs (project-scoped, --writable)",
|
|
7447
|
+
transport: "stdio",
|
|
7448
|
+
command: "wstack-codebase-index-mcp",
|
|
7449
|
+
args: ["--project-root", ".", "--writable"],
|
|
7450
|
+
permission: "auto"
|
|
7451
|
+
});
|
|
7190
7452
|
var allServers = () => ({
|
|
7191
7453
|
filesystem: { ...filesystemServer(), enabled: false },
|
|
7192
7454
|
github: { ...githubServer(), enabled: false },
|
|
@@ -7201,7 +7463,11 @@ var allServers = () => ({
|
|
|
7201
7463
|
"zai-vision": { ...zaiVisionServer(), enabled: false },
|
|
7202
7464
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false },
|
|
7203
7465
|
playwright: { ...playwrightServer(), enabled: false },
|
|
7204
|
-
ssh: { ...sshManagerServer(), enabled: false }
|
|
7466
|
+
ssh: { ...sshManagerServer(), enabled: false },
|
|
7467
|
+
kanban: { ...kanbanServer(), enabled: false },
|
|
7468
|
+
mailbox: { ...mailboxServer(), enabled: false },
|
|
7469
|
+
"codebase-index": { ...codebaseIndexServer(), enabled: false },
|
|
7470
|
+
"requirement-intake": { ...requirementIntakeServer(), enabled: false }
|
|
7205
7471
|
});
|
|
7206
7472
|
|
|
7207
7473
|
// src/utils/config-json.ts
|
|
@@ -7682,6 +7948,7 @@ var OneShotOrchestrator = class {
|
|
|
7682
7948
|
tokens: { input: 0, output: 0, total: 0 },
|
|
7683
7949
|
durationMs: Math.round(performance.now() - startedAt),
|
|
7684
7950
|
fromFallback: false,
|
|
7951
|
+
attempts: 0,
|
|
7685
7952
|
error: "No provider or model could be resolved. Check your config."
|
|
7686
7953
|
};
|
|
7687
7954
|
}
|
|
@@ -7696,6 +7963,7 @@ var OneShotOrchestrator = class {
|
|
|
7696
7963
|
tokens: { input: 0, output: 0, total: 0 },
|
|
7697
7964
|
durationMs: Math.round(performance.now() - startedAt),
|
|
7698
7965
|
fromFallback: false,
|
|
7966
|
+
attempts: 0,
|
|
7699
7967
|
error: `Cannot build provider "${target.providerId}": ${err instanceof Error ? err.message : String(err)}`
|
|
7700
7968
|
};
|
|
7701
7969
|
}
|
|
@@ -7708,11 +7976,13 @@ var OneShotOrchestrator = class {
|
|
|
7708
7976
|
let fromFallback = false;
|
|
7709
7977
|
let lastError;
|
|
7710
7978
|
let fallbackEligible = false;
|
|
7979
|
+
let attempts = 0;
|
|
7711
7980
|
if (tracker && !tracker.isAvailable(target.providerId, target.model) || !evaluateModelCalendar(config.modelAvailabilitySchedule, target.providerId, target.model).allowed) {
|
|
7712
7981
|
this.opts.logger?.debug(
|
|
7713
7982
|
`one-shot: primary "${target.providerId}/${target.model}" is blocked \u2014 trying fallback`
|
|
7714
7983
|
);
|
|
7715
7984
|
} else {
|
|
7985
|
+
attempts += 1;
|
|
7716
7986
|
const primaryAttempt = await this.tryCall(
|
|
7717
7987
|
provider,
|
|
7718
7988
|
request,
|
|
@@ -7727,10 +7997,17 @@ var OneShotOrchestrator = class {
|
|
|
7727
7997
|
tracker?.recordSuccess(target.providerId, target.model);
|
|
7728
7998
|
servingProviderId = provider.id;
|
|
7729
7999
|
servingModel = target.model;
|
|
7730
|
-
return this.buildResult(result, servingProviderId, servingModel, false, startedAt);
|
|
8000
|
+
return this.buildResult(result, servingProviderId, servingModel, false, startedAt, attempts);
|
|
7731
8001
|
}
|
|
7732
8002
|
if (!fallbackEligible || chain.length === 0) {
|
|
7733
|
-
return this.buildErrorResult(
|
|
8003
|
+
return this.buildErrorResult(
|
|
8004
|
+
lastError,
|
|
8005
|
+
target.providerId,
|
|
8006
|
+
target.model,
|
|
8007
|
+
false,
|
|
8008
|
+
startedAt,
|
|
8009
|
+
attempts
|
|
8010
|
+
);
|
|
7734
8011
|
}
|
|
7735
8012
|
}
|
|
7736
8013
|
const estimatedTokens = estimateRequestTokens(request.messages, request.system, []).total;
|
|
@@ -7758,6 +8035,7 @@ var OneShotOrchestrator = class {
|
|
|
7758
8035
|
}
|
|
7759
8036
|
servingProviderId = fbProvider.id;
|
|
7760
8037
|
servingModel = entry.model;
|
|
8038
|
+
attempts += 1;
|
|
7761
8039
|
const attempt = await this.tryCall(
|
|
7762
8040
|
fbProvider,
|
|
7763
8041
|
this.buildRequest(input, entry.model),
|
|
@@ -7768,7 +8046,14 @@ var OneShotOrchestrator = class {
|
|
|
7768
8046
|
if (attempt.response) {
|
|
7769
8047
|
tracker?.recordSuccess(entry.providerId, entry.model);
|
|
7770
8048
|
fromFallback = true;
|
|
7771
|
-
return this.buildResult(
|
|
8049
|
+
return this.buildResult(
|
|
8050
|
+
attempt.response,
|
|
8051
|
+
servingProviderId,
|
|
8052
|
+
servingModel,
|
|
8053
|
+
true,
|
|
8054
|
+
startedAt,
|
|
8055
|
+
attempts
|
|
8056
|
+
);
|
|
7772
8057
|
}
|
|
7773
8058
|
lastError = attempt.error;
|
|
7774
8059
|
}
|
|
@@ -7777,7 +8062,8 @@ var OneShotOrchestrator = class {
|
|
|
7777
8062
|
servingProviderId,
|
|
7778
8063
|
servingModel,
|
|
7779
8064
|
fromFallback,
|
|
7780
|
-
startedAt
|
|
8065
|
+
startedAt,
|
|
8066
|
+
attempts
|
|
7781
8067
|
);
|
|
7782
8068
|
}
|
|
7783
8069
|
// ── Private helpers ─────────────────────────────────────────────
|
|
@@ -7788,7 +8074,8 @@ var OneShotOrchestrator = class {
|
|
|
7788
8074
|
resolveTarget(input, config) {
|
|
7789
8075
|
if (input.role && this.opts.modelRouter) {
|
|
7790
8076
|
const pick = this.opts.modelRouter.pickForTask(input.role, "");
|
|
7791
|
-
|
|
8077
|
+
const hasExplicitTarget = Boolean(input.providerId || input.model);
|
|
8078
|
+
if (pick && (pick.fromMatrix === true || !hasExplicitTarget)) {
|
|
7792
8079
|
return { providerId: pick.provider, model: pick.model };
|
|
7793
8080
|
}
|
|
7794
8081
|
}
|
|
@@ -7900,7 +8187,7 @@ var OneShotOrchestrator = class {
|
|
|
7900
8187
|
}
|
|
7901
8188
|
}
|
|
7902
8189
|
/** Build a success result from a provider Response. */
|
|
7903
|
-
buildResult(response, servingProviderId, servingModel, fromFallback, startedAt) {
|
|
8190
|
+
buildResult(response, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
|
|
7904
8191
|
const textBlocks = response.content.filter(isTextBlock);
|
|
7905
8192
|
const text = textBlocks.map((b) => b.text).join("\n").trim();
|
|
7906
8193
|
return {
|
|
@@ -7914,11 +8201,12 @@ var OneShotOrchestrator = class {
|
|
|
7914
8201
|
},
|
|
7915
8202
|
durationMs: Math.round(performance.now() - startedAt),
|
|
7916
8203
|
fromFallback,
|
|
8204
|
+
attempts,
|
|
7917
8205
|
stopReason: response.stopReason
|
|
7918
8206
|
};
|
|
7919
8207
|
}
|
|
7920
8208
|
/** Build a total-failure error result. */
|
|
7921
|
-
buildErrorResult(error, servingProviderId, servingModel, fromFallback, startedAt) {
|
|
8209
|
+
buildErrorResult(error, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
|
|
7922
8210
|
return {
|
|
7923
8211
|
text: "",
|
|
7924
8212
|
model: servingModel,
|
|
@@ -7926,6 +8214,7 @@ var OneShotOrchestrator = class {
|
|
|
7926
8214
|
tokens: { input: 0, output: 0, total: 0 },
|
|
7927
8215
|
durationMs: Math.round(performance.now() - startedAt),
|
|
7928
8216
|
fromFallback,
|
|
8217
|
+
attempts,
|
|
7929
8218
|
error: error instanceof Error ? error.message : String(error ?? "Unknown error")
|
|
7930
8219
|
};
|
|
7931
8220
|
}
|
|
@@ -8056,6 +8345,44 @@ function createOneShotLLMTool(opts) {
|
|
|
8056
8345
|
};
|
|
8057
8346
|
}
|
|
8058
8347
|
|
|
8348
|
+
// src/plugin/config.ts
|
|
8349
|
+
function pluginEntryMatchesName(configuredName, name, aliases = []) {
|
|
8350
|
+
for (const candidate of [name, ...aliases]) {
|
|
8351
|
+
if (configuredName === candidate) return true;
|
|
8352
|
+
if (configuredName === `@wrongstack/plugins/${candidate}`) return true;
|
|
8353
|
+
}
|
|
8354
|
+
return false;
|
|
8355
|
+
}
|
|
8356
|
+
function resolvePluginEnablement(input) {
|
|
8357
|
+
if (input.config?.features?.plugins === false) {
|
|
8358
|
+
return { enabled: false, source: "feature-flag" };
|
|
8359
|
+
}
|
|
8360
|
+
const names = [.../* @__PURE__ */ new Set([input.name, ...input.aliases ?? []])];
|
|
8361
|
+
const matches = input.matches ?? ((configuredName) => pluginEntryMatchesName(configuredName, input.name, input.aliases ?? []));
|
|
8362
|
+
const plugins = input.config?.plugins;
|
|
8363
|
+
if (Array.isArray(plugins)) {
|
|
8364
|
+
for (const candidate of plugins) {
|
|
8365
|
+
if (typeof candidate === "string") {
|
|
8366
|
+
if (matches(candidate)) return { enabled: true, source: "plugin-entry" };
|
|
8367
|
+
continue;
|
|
8368
|
+
}
|
|
8369
|
+
if (!isPluginEntry(candidate) || !matches(candidate.name)) continue;
|
|
8370
|
+
return { enabled: candidate.enabled !== false, source: "plugin-entry" };
|
|
8371
|
+
}
|
|
8372
|
+
}
|
|
8373
|
+
for (const name of names) {
|
|
8374
|
+
const enabled = input.config?.extensions?.[name]?.["enabled"];
|
|
8375
|
+
if (typeof enabled === "boolean") return { enabled, source: "extension" };
|
|
8376
|
+
}
|
|
8377
|
+
return { enabled: input.defaultState === "active", source: "default" };
|
|
8378
|
+
}
|
|
8379
|
+
function isPluginEntry(value) {
|
|
8380
|
+
return isRecord2(value) && typeof value["name"] === "string";
|
|
8381
|
+
}
|
|
8382
|
+
function isRecord2(value) {
|
|
8383
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8384
|
+
}
|
|
8385
|
+
|
|
8059
8386
|
// src/utils/json-schema-validate.ts
|
|
8060
8387
|
function validateAgainstSchema(value, schema) {
|
|
8061
8388
|
const errors = [];
|
|
@@ -8179,6 +8506,12 @@ function deepEqual(a, b) {
|
|
|
8179
8506
|
|
|
8180
8507
|
// src/tools/plugin-manager.ts
|
|
8181
8508
|
var PLUGIN_MANAGER_TOOL_NAME = "plugin_manager";
|
|
8509
|
+
var PLUGIN_VIEW_STATE_SOURCE = {
|
|
8510
|
+
"feature-flag": "feature_flag",
|
|
8511
|
+
"plugin-entry": "config",
|
|
8512
|
+
extension: "extension",
|
|
8513
|
+
default: "default"
|
|
8514
|
+
};
|
|
8182
8515
|
var INPUT_SCHEMA3 = {
|
|
8183
8516
|
type: "object",
|
|
8184
8517
|
properties: {
|
|
@@ -8410,10 +8743,14 @@ function buildPluginViews(opts) {
|
|
|
8410
8743
|
return catalog.map((entry) => {
|
|
8411
8744
|
const aliases = [...entry.aliases ?? []];
|
|
8412
8745
|
const names = /* @__PURE__ */ new Set([entry.name, ...aliases]);
|
|
8413
|
-
const
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8746
|
+
const { enabled, source } = resolvePluginEnablement({
|
|
8747
|
+
name: entry.name,
|
|
8748
|
+
aliases,
|
|
8749
|
+
defaultState: entry.defaultState,
|
|
8750
|
+
config,
|
|
8751
|
+
matches: (spec) => names.has(spec)
|
|
8752
|
+
});
|
|
8753
|
+
const stateSource = PLUGIN_VIEW_STATE_SOURCE[source];
|
|
8417
8754
|
const tools = pluginTools(opts.toolRegistry, entry.name, aliases);
|
|
8418
8755
|
const managerControl = isManagerLocked(config, entry.name, aliases) ? "locked" : "allowed";
|
|
8419
8756
|
return {
|