@bridge_gpt/mcp-server 0.2.62 → 0.2.66
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/README.md +66 -15
- package/build/agent-launchers/claude-executor-adapter.js +145 -20
- package/build/agent-launchers/executor-adapter.js +81 -2
- package/build/agents.generated.js +10 -1
- package/build/bounded-wait.js +32 -1
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +7 -6
- package/build/conductor/bridge-api-client.js +565 -32
- package/build/conductor/bring-up-facts.js +3 -1
- package/build/conductor/doctor.js +23 -14
- package/build/conductor/epic-child-continuation.js +351 -0
- package/build/conductor/epic-implementer-cli.js +154 -17
- package/build/conductor/git-hooks.js +529 -118
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/hook-runtime-qualification.js +340 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness-sections.js +5 -4
- package/build/conductor/readiness.js +45 -12
- package/build/conductor/recovery-cli.js +859 -27
- package/build/conductor/recovery-operations.js +574 -5
- package/build/conductor/repair-contract.js +69 -3
- package/build/conductor-bundle-artifacts.js +1 -0
- package/build/credential-store.js +52 -13
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +3 -2
- package/build/doctor.js +147 -21
- package/build/drive-epic-key.js +12 -0
- package/build/drive-epic.js +1537 -125
- package/build/epic-coherence-input-cli.js +137 -0
- package/build/epic-coherence-input.js +920 -0
- package/build/epic-coherence-input.test-fixtures.js +175 -0
- package/build/epic-implementer-bin.js +41 -33
- package/build/epic-implementer-bundle-cli.js +14 -1
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +58 -19
- package/build/epic-run-status.js +5 -0
- package/build/epic-shared/bridge-client.js +46 -4
- package/build/executor/cli.js +39 -0
- package/build/executor/completion-contract.js +423 -0
- package/build/executor/completion-supervision.js +291 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/credentials.js +3 -1
- package/build/executor/deps.js +65 -1
- package/build/executor/env.js +12 -0
- package/build/executor/heartbeat.js +13 -3
- package/build/executor/http-client.js +91 -4
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +61 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +1550 -55
- package/build/executor/job-types.js +9 -6
- package/build/executor/preflight.js +189 -1
- package/build/executor/process-containment.js +756 -0
- package/build/executor/process.js +235 -5
- package/build/executor/prompt-spec.js +366 -1
- package/build/executor/provider-checks.js +69 -0
- package/build/executor/rebase-finalization.js +351 -0
- package/build/executor/reconcile-overlap-job.js +77 -84
- package/build/executor/required-verification.js +313 -0
- package/build/executor/required-work-ledger.js +1121 -0
- package/build/executor/results.js +142 -0
- package/build/executor/resume-pre-spawn.js +87 -4
- package/build/executor/runner.js +168 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/test-declaration.js +20 -0
- package/build/executor/types.js +13 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +70 -34
- package/build/executor/worker-finalization.js +168 -26
- package/build/executor/worker-guard-hook.js +75 -14
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/executor/worktree.js +13 -0
- package/build/executor-launch.js +658 -0
- package/build/index.js +594 -1152
- package/build/init.js +107 -4
- package/build/install-bridge-conductor.js +231 -41
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/merge-pull-request.js +1 -1
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +9 -8
- package/build/plan-discovery.js +137 -0
- package/build/plan-epic-implementer-eligibility.js +2 -2
- package/build/plane/cli.js +37 -3
- package/build/plane/member-logs.js +3 -0
- package/build/plane/member-roster.js +7 -1
- package/build/plane/shutdown.js +59 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +146 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/scripts/executor-protocol-contract-driver.js +13 -3
- package/build/setup-epic.js +208 -125
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +5 -4
- package/build/worktree-core.js +6 -5
- package/docs/CONDUCTOR.md +116 -2
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +2 -1
- package/pipelines/implement-ticket.json +1 -1
package/build/review-tickets.js
CHANGED
|
@@ -3,12 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Reuses the `start-tickets` terminal spawn layer directly, running reviews in
|
|
5
5
|
* the current `deps.cwd`. Intentionally does NOT create Worktrunk worktrees,
|
|
6
|
-
* provision MCP registrations, run venv hooks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* provision MCP registrations, or run venv hooks. One terminal tab per ticket
|
|
7
|
+
* is spawned with `/review-ticket <KEY> [--auto] [--rounds=<1|2>]` as the agent
|
|
8
|
+
* prompt. `--rounds` is OMITTED by default so the backend's difficulty-adaptive
|
|
9
|
+
* review policy chooses the shape; it is only forwarded when the caller passes
|
|
10
|
+
* an explicit `--rounds`/`--review` round override.
|
|
11
|
+
*
|
|
12
|
+
* BAPI-1343 — review-threshold model routing for spawned tabs: an explicit
|
|
13
|
+
* `--model` always wins and no tier lookup is made. Otherwise each ticket's
|
|
14
|
+
* `/tickets/{n}/model-tier?consumer=review` is fetched (the shared bounded
|
|
15
|
+
* `start-tickets` fetcher, at most 3 at once), and a tab gets `--model` only
|
|
16
|
+
* when the server echoes `consumer: "review"` and reports a threshold-decided
|
|
17
|
+
* (`configured` or `anomaly`) tier, mapped to an alias exactly as
|
|
18
|
+
* `start-tickets` maps it. Legacy mode, a failed or timed-out lookup, or an
|
|
19
|
+
* older server that does not echo the consumer passes no `--model` (today's
|
|
20
|
+
* behavior); there is no premium fallback. `--dry-run` makes no lookup, and the
|
|
21
|
+
* epic-dispatch path below is not re-routed.
|
|
12
22
|
*
|
|
13
23
|
* Optionally supports a supervisor-driven epic-dispatch path: when an
|
|
14
24
|
* `EpicDispatchIdentity` is threaded through `ReviewTicketsOptions.epic`, each
|
|
@@ -18,7 +28,7 @@
|
|
|
18
28
|
* This path is worktree-free and base-branch-free; the human CLI path leaves
|
|
19
29
|
* `epic` unset and retains byte-for-byte identical behavior.
|
|
20
30
|
*/
|
|
21
|
-
import { createDefaultStartTicketsDeps, detectTerminal, runWithConcurrency, buildAgentInvocation, shSquoteInner, powershellSquote, isCommandOnPath, resolveFirstCommandOnPath, tmuxSessionNameForTicket, TICKET_KEY_PATTERN, DEFAULT_MAX_PARALLEL, validateBranchName, fetchAndResolveBaseSha, fetchStartTicketsConfigField, resolveStartTicketsBridgeApiAccess, buildCommandProvisioningDeps, buildMcpProvisioningDeps, } from "./start-tickets.js";
|
|
31
|
+
import { createDefaultStartTicketsDeps, detectTerminal, runWithConcurrency, buildAgentInvocation, shSquoteInner, powershellSquote, isCommandOnPath, resolveFirstCommandOnPath, tmuxSessionNameForTicket, TICKET_KEY_PATTERN, DEFAULT_MAX_PARALLEL, validateBranchName, fetchAndResolveBaseSha, fetchStartTicketsConfigField, resolveStartTicketsBridgeApiAccess, buildCommandProvisioningDeps, buildMcpProvisioningDeps, fetchTicketModelTiersForKeys, isThresholdDecidedTierResponse, normalizeDifficultyModelTierOverrides, fetchCodexModelCatalog, resolveRoutingAlias, validateResolvedModelAliasForAgent, } from "./start-tickets.js";
|
|
22
32
|
import { provisionCommandsForWorktree } from "./command-provisioning.js";
|
|
23
33
|
import { resolveCodexLaunchScope } from "./codex-launch-scope.js";
|
|
24
34
|
import { WINDOWS_TERMINAL_COMMAND, WINDOWS_POWERSHELL_CANDIDATES, TMUX_COMMAND, isSupportedStartTicketsPlatform, unsupportedPlatformMessage, } from "./start-tickets-prereqs.js";
|
|
@@ -48,7 +58,7 @@ export function getReviewTicketsUsage() {
|
|
|
48
58
|
" --rounds=1|2 Review depth: 1=single-pass, 2=full two-pass (default: adaptive — backend routes by difficulty)",
|
|
49
59
|
" --review KEY=auto,rounds=1 Per-ticket review mode override (repeatable)",
|
|
50
60
|
" --agent claude|cursor-agent|codex Agent command to launch in each tab (default: claude)",
|
|
51
|
-
" --model VALUE Model alias
|
|
61
|
+
" --model VALUE Model alias for every tab (always wins). Unset: each tab is routed by the review threshold; legacy mode or an older server passes no --model",
|
|
52
62
|
" --max-parallel N Max tabs to spawn concurrently (default: 3)",
|
|
53
63
|
" --dry-run Print intended commands; creates no tabs and opens no sessions",
|
|
54
64
|
" --base-branch BRANCH Base branch to fetch+pin for grounding (default: config base_branch, else \"main\")",
|
|
@@ -527,12 +537,125 @@ export function buildReviewPlanRows(deps, options, agent, status, overrides = {}
|
|
|
527
537
|
rounds: mode.rounds,
|
|
528
538
|
agentName: options.agentName,
|
|
529
539
|
modelAlias: options.modelAlias,
|
|
540
|
+
// BAPI-1343: plan rows (the dry-run preview) never perform a tier lookup;
|
|
541
|
+
// they show only an explicit --model, or the agent default.
|
|
542
|
+
...(!options.epic && options.modelAlias ? { modelRoutingReason: "explicit" } : {}),
|
|
530
543
|
status,
|
|
531
544
|
command,
|
|
532
545
|
runId,
|
|
533
546
|
};
|
|
534
547
|
});
|
|
535
548
|
}
|
|
549
|
+
/**
|
|
550
|
+
* BAPI-1343 — resolve each spawned tab's model from the review threshold.
|
|
551
|
+
*
|
|
552
|
+
* One `/model-tier?consumer=review` request per key through the shared
|
|
553
|
+
* `start-tickets` fetcher (bounded at 3, never throws). A tab is routed only
|
|
554
|
+
* when the server echoes `consumer: "review"` AND reports a threshold-decided
|
|
555
|
+
* tier; the tier maps to an alias exactly as `start-tickets` maps it (registry
|
|
556
|
+
* plus `difficulty_model_tier_overrides`, read once per batch; the Codex
|
|
557
|
+
* catalog, fetched once per batch; `validateResolvedModelAliasForAgent`, run
|
|
558
|
+
* at most once per distinct alias per batch). Every other outcome returns
|
|
559
|
+
* `modelAlias: null` (no `--model`, today's behavior) — there is no premium
|
|
560
|
+
* fallback. Callers handle explicit `--model`, `--dry-run`, and epic dispatch
|
|
561
|
+
* before calling this, so it always performs lookups.
|
|
562
|
+
*/
|
|
563
|
+
export async function resolveReviewModelRoutingForKeys(deps, keys, agent, maxParallel) {
|
|
564
|
+
const decisions = new Map();
|
|
565
|
+
const noModel = (reason) => ({ modelAlias: null, reason });
|
|
566
|
+
let accessResult;
|
|
567
|
+
try {
|
|
568
|
+
accessResult = await resolveStartTicketsBridgeApiAccess(deps);
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
for (const key of keys)
|
|
572
|
+
decisions.set(key, noModel("lookup-failed"));
|
|
573
|
+
return decisions;
|
|
574
|
+
}
|
|
575
|
+
if (!accessResult.ok) {
|
|
576
|
+
for (const key of keys)
|
|
577
|
+
decisions.set(key, noModel("lookup-failed"));
|
|
578
|
+
return decisions;
|
|
579
|
+
}
|
|
580
|
+
const access = accessResult.access;
|
|
581
|
+
const tierMap = await fetchTicketModelTiersForKeys(access, keys, maxParallel, { consumer: "review" });
|
|
582
|
+
// Batch-scoped alias inputs, loaded lazily so a batch with no
|
|
583
|
+
// threshold-decided ticket makes no config/catalog request.
|
|
584
|
+
let aliasInputs = null;
|
|
585
|
+
const loadAliasInputs = () => {
|
|
586
|
+
if (!aliasInputs) {
|
|
587
|
+
aliasInputs = (async () => {
|
|
588
|
+
try {
|
|
589
|
+
if (agent.name === "codex") {
|
|
590
|
+
const catalogResult = await fetchCodexModelCatalog(access);
|
|
591
|
+
return catalogResult.ok ? { overrides: null, catalog: catalogResult.catalog } : null;
|
|
592
|
+
}
|
|
593
|
+
const raw = await fetchStartTicketsConfigField(access, "difficulty_model_tier_overrides");
|
|
594
|
+
return { overrides: normalizeDifficultyModelTierOverrides(raw), catalog: null };
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
})();
|
|
600
|
+
}
|
|
601
|
+
return aliasInputs;
|
|
602
|
+
};
|
|
603
|
+
const validations = new Map();
|
|
604
|
+
const validateOnce = (alias) => {
|
|
605
|
+
let pending = validations.get(alias);
|
|
606
|
+
if (!pending) {
|
|
607
|
+
pending = validateResolvedModelAliasForAgent(deps, agent, alias).catch(() => ({ ok: false, warning: "model validation failed" }));
|
|
608
|
+
validations.set(alias, pending);
|
|
609
|
+
}
|
|
610
|
+
return pending;
|
|
611
|
+
};
|
|
612
|
+
for (const key of keys) {
|
|
613
|
+
const result = tierMap.get(key);
|
|
614
|
+
if (!result || !result.ok) {
|
|
615
|
+
decisions.set(key, noModel("lookup-failed"));
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const value = result.value;
|
|
619
|
+
if (value.consumer !== "review") {
|
|
620
|
+
decisions.set(key, noModel("consumer-unrecognized"));
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (value.source === "fallback") {
|
|
624
|
+
// The server could not obtain a difficulty; its premium tier is a guess,
|
|
625
|
+
// and review routing has no premium fallback.
|
|
626
|
+
decisions.set(key, noModel("lookup-failed"));
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (!isThresholdDecidedTierResponse(value)) {
|
|
630
|
+
decisions.set(key, noModel("legacy"));
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (!value.tier) {
|
|
634
|
+
decisions.set(key, noModel("lookup-failed"));
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (!agent.supportsModelOverride) {
|
|
638
|
+
decisions.set(key, noModel("alias-unresolved"));
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const inputs = await loadAliasInputs();
|
|
642
|
+
const candidate = inputs ? resolveRoutingAlias(agent, value.tier, inputs.overrides, inputs.catalog) : null;
|
|
643
|
+
if (!candidate) {
|
|
644
|
+
decisions.set(key, noModel("alias-unresolved"));
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
const validation = await validateOnce(candidate);
|
|
648
|
+
if (!validation.ok) {
|
|
649
|
+
decisions.set(key, noModel("alias-unresolved"));
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
decisions.set(key, {
|
|
653
|
+
modelAlias: validation.alias,
|
|
654
|
+
reason: value.thresholdMode === "anomaly" ? "review-threshold:anomaly" : "review-threshold:configured",
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
return decisions;
|
|
658
|
+
}
|
|
536
659
|
/** Build a dry-run preview base context from parsed CLI options only — never fetches. */
|
|
537
660
|
function previewBaseContextFromOptions(options) {
|
|
538
661
|
if (options.noRefreshBase)
|
|
@@ -590,10 +713,25 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
590
713
|
return { ok: false, error: baseContextResult.error };
|
|
591
714
|
}
|
|
592
715
|
const baseContext = baseContextResult.context;
|
|
716
|
+
// BAPI-1343: per-tab review-threshold routing for the human CLI path only.
|
|
717
|
+
// An explicit --model wins with no lookup; the epic-dispatch path keeps its
|
|
718
|
+
// pre-feature shape (Epic workers take their tier from the stamped payload).
|
|
719
|
+
let routing = null;
|
|
720
|
+
if (!options.epic) {
|
|
721
|
+
if (options.modelAlias) {
|
|
722
|
+
routing = new Map(options.keys.map((key) => [key, { modelAlias: options.modelAlias ?? null, reason: "explicit" }]));
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
routing = await resolveReviewModelRoutingForKeys(deps, options.keys, agent, options.maxParallel);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
593
728
|
const terminal = detectTerminal(undefined, deps.env);
|
|
594
729
|
const rows = await runWithConcurrency(options.keys, options.maxParallel, async (key) => {
|
|
595
730
|
const mode = resolveEffectiveReviewMode(key, options);
|
|
596
|
-
const
|
|
731
|
+
const decision = routing?.get(key);
|
|
732
|
+
const modelAlias = decision ? decision.modelAlias : options.modelAlias;
|
|
733
|
+
const reasonField = decision ? { modelRoutingReason: decision.reason } : {};
|
|
734
|
+
const baseShellCommand = buildReviewAgentShellCommand(agent, key, mode, deps.platform, deps.cwd, modelAlias, baseContext, codexLaunchArgs);
|
|
597
735
|
let shellCommand = baseShellCommand;
|
|
598
736
|
let runId;
|
|
599
737
|
if (options.epic) {
|
|
@@ -608,7 +746,8 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
608
746
|
auto: mode.auto,
|
|
609
747
|
rounds: mode.rounds,
|
|
610
748
|
agentName: options.agentName,
|
|
611
|
-
modelAlias
|
|
749
|
+
modelAlias,
|
|
750
|
+
...reasonField,
|
|
612
751
|
status: "spawn-failed",
|
|
613
752
|
command: shellCommand,
|
|
614
753
|
runId,
|
|
@@ -625,7 +764,8 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
625
764
|
auto: mode.auto,
|
|
626
765
|
rounds: mode.rounds,
|
|
627
766
|
agentName: options.agentName,
|
|
628
|
-
modelAlias
|
|
767
|
+
modelAlias,
|
|
768
|
+
...reasonField,
|
|
629
769
|
status: "already-dispatched",
|
|
630
770
|
command: shellCommand,
|
|
631
771
|
runId,
|
|
@@ -645,7 +785,8 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
645
785
|
auto: mode.auto,
|
|
646
786
|
rounds: mode.rounds,
|
|
647
787
|
agentName: options.agentName,
|
|
648
|
-
modelAlias
|
|
788
|
+
modelAlias,
|
|
789
|
+
...reasonField,
|
|
649
790
|
status: "spawned",
|
|
650
791
|
command: shellCommand,
|
|
651
792
|
runId,
|
|
@@ -687,9 +828,11 @@ export function formatReviewTicketsSummaryReport(rows) {
|
|
|
687
828
|
const lines = ["Summary:"];
|
|
688
829
|
for (const row of rows) {
|
|
689
830
|
const modelPart = row.modelAlias ? row.modelAlias : "default";
|
|
831
|
+
// BAPI-1343: the closed, secret-free routing reason, when one was recorded.
|
|
832
|
+
const modelReasonPart = row.modelRoutingReason ? ` model_reason=${row.modelRoutingReason}` : "";
|
|
690
833
|
const roundsPart = row.rounds !== undefined ? String(row.rounds) : "adaptive";
|
|
691
834
|
const runIdPart = row.runId ? ` run_id=${row.runId}` : "";
|
|
692
|
-
lines.push(`${row.key} auto=${row.auto} rounds=${roundsPart} agent=${row.agentName} model=${modelPart} status=${row.status}${runIdPart}`);
|
|
835
|
+
lines.push(`${row.key} auto=${row.auto} rounds=${roundsPart} agent=${row.agentName} model=${modelPart}${modelReasonPart} status=${row.status}${runIdPart}`);
|
|
693
836
|
}
|
|
694
837
|
const failedRows = rows.filter((r) => r.status === "spawn-failed" || r.status === "already-dispatched");
|
|
695
838
|
if (failedRows.length > 0) {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
46
|
import path from "node:path";
|
|
47
47
|
import fs from "node:fs";
|
|
48
|
-
import { API, SymbolFlags } from "typescript/unstable/sync";
|
|
48
|
+
import { API, SymbolFlags, TypeFlags } from "typescript/unstable/sync";
|
|
49
49
|
import { isInterfaceDeclaration, isTypeAliasDeclaration } from "typescript/unstable/ast";
|
|
50
50
|
import { createExecutorHttpClient, RECONCILER_LIVENESS_HEADER, HEARTBEAT_STOP_FIELD, } from "../executor/http-client.js";
|
|
51
51
|
import { EXECUTOR_JOB_TYPE_VALUES, EXECUTOR_CLASSIFICATION_VALUES, EXECUTOR_MUTATION_OUTCOME_VALUES, RECONCILER_LIVENESS_VALUES, } from "../executor/types.js";
|
|
@@ -192,7 +192,15 @@ const PROJECTED_SHAPE_NAMES = [
|
|
|
192
192
|
"ExecutorFailureWireRequest",
|
|
193
193
|
"ExecutorProcessHeartbeatRequest",
|
|
194
194
|
];
|
|
195
|
-
/**
|
|
195
|
+
/**
|
|
196
|
+
* Return the closed string-literal members of *type*, or `undefined` when it is not one.
|
|
197
|
+
*
|
|
198
|
+
* `undefined`/`null` union members are skipped (BAPI-1301): an OPTIONAL
|
|
199
|
+
* literal property (`marker?: "a" | "b"`) is typed `"a" | "b" | undefined`
|
|
200
|
+
* under `strictNullChecks`, and its vocabulary is still exactly `["a", "b"]` —
|
|
201
|
+
* mirroring the Python side, which unwraps `Optional[Literal[...]]` before
|
|
202
|
+
* reading its members. Requiredness is compared separately.
|
|
203
|
+
*/
|
|
196
204
|
function literalValuesOf(type) {
|
|
197
205
|
if (type.isStringLiteralType()) {
|
|
198
206
|
return [type.value];
|
|
@@ -200,11 +208,13 @@ function literalValuesOf(type) {
|
|
|
200
208
|
if (type.isUnionType()) {
|
|
201
209
|
const values = [];
|
|
202
210
|
for (const member of type.getTypes()) {
|
|
211
|
+
if (member.flags & (TypeFlags.Undefined | TypeFlags.Null))
|
|
212
|
+
continue;
|
|
203
213
|
if (!member.isStringLiteralType())
|
|
204
214
|
return undefined;
|
|
205
215
|
values.push(member.value);
|
|
206
216
|
}
|
|
207
|
-
return values.sort();
|
|
217
|
+
return values.length > 0 ? values.sort() : undefined;
|
|
208
218
|
}
|
|
209
219
|
return undefined;
|
|
210
220
|
}
|