sneakoscope 8.3.2 → 8.4.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/README.md +1 -1
- package/config/architecture-map-policy.v1.json +168 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +19 -1
- package/dist/commands/doctor-profile.js +1 -1
- package/dist/commands/doctor.js +33 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/align/align-route.js +8 -2
- package/dist/core/align/code-navigation-align.js +88 -32
- package/dist/core/architecture-map-pipeline.js +172 -0
- package/dist/core/architecture-map-review.js +180 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +2 -1
- package/dist/core/codex-app/menubar/constants.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +25 -3
- package/dist/core/codex-lb/combined-catalog/builder.js +7 -2
- package/dist/core/codex-lb/combined-catalog/model-selection.js +71 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +20 -3
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +75 -4
- package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
- package/dist/core/commands/triwiki-atlas-command.js +372 -0
- package/dist/core/commands/triwiki-command.js +12 -1
- package/dist/core/fsx.js +14 -0
- package/dist/core/git-hygiene/git-policy.js +4 -0
- package/dist/core/git-hygiene/gitignore-writer.js +2 -0
- package/dist/core/gx-renderer.js +2 -1
- package/dist/core/managed-paths.js +1 -0
- package/dist/core/perf/release-latency-slo.js +57 -11
- package/dist/core/pipeline-internals/pipeline-stage-builder.js +181 -0
- package/dist/core/pipeline-internals/runtime-core.js +13 -172
- package/dist/core/pipeline-internals/runtime-gates.js +12 -1
- package/dist/core/release/content-digest-cache.js +70 -0
- package/dist/core/release/gate-manifest.js +9 -0
- package/dist/core/release/main-push-guard.js +4 -3
- package/dist/core/release/package-dist-snapshot.js +18 -2
- package/dist/core/release/physical-release-gates.js +26 -3
- package/dist/core/release/release-authorization-snapshot.js +4 -0
- package/dist/core/release/release-gate-cache-v2.js +27 -3
- package/dist/core/release/release-gate-contract.js +2 -0
- package/dist/core/release/release-gate-dag.js +3 -0
- package/dist/core/release/release-inspection-memo.js +69 -0
- package/dist/core/release/release-pack-content-scanner.js +5 -0
- package/dist/core/release/release-pack-receipt.js +28 -18
- package/dist/core/release/release-pack-tarball.js +10 -0
- package/dist/core/release/release-upgrade-baseline.js +4 -0
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/ssot-authority-inventory.js +114 -0
- package/dist/core/safety/ssot-guard.js +2 -44
- package/dist/core/triwiki/context-graph/architecture/baseline.js +81 -0
- package/dist/core/triwiki/context-graph/architecture/capsule.js +34 -0
- package/dist/core/triwiki/context-graph/architecture/contracts.js +67 -0
- package/dist/core/triwiki/context-graph/architecture/delta.js +149 -0
- package/dist/core/triwiki/context-graph/architecture/finding-factory.js +28 -0
- package/dist/core/triwiki/context-graph/architecture/findings.js +173 -0
- package/dist/core/triwiki/context-graph/architecture/fingerprint.js +75 -0
- package/dist/core/triwiki/context-graph/architecture/index.js +15 -0
- package/dist/core/triwiki/context-graph/architecture/input-bundle.js +66 -0
- package/dist/core/triwiki/context-graph/architecture/metrics.js +137 -0
- package/dist/core/triwiki/context-graph/architecture/policy.js +321 -0
- package/dist/core/triwiki/context-graph/architecture/slice.js +100 -0
- package/dist/core/triwiki/context-graph/architecture/ssot-analysis.js +59 -0
- package/dist/core/triwiki/context-graph/architecture/topology-overlay.js +106 -0
- package/dist/core/triwiki/context-graph/architecture/validation.js +145 -0
- package/dist/core/triwiki/context-graph/architecture/voxel-context.js +39 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +4 -0
- package/dist/core/triwiki/context-graph/projections/index.js +2 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/architecture-delta.js +68 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ast.js +49 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/change-impact.js +43 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/contracts.js +5 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/escape.js +45 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ids.js +73 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/index.js +113 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/manifest.js +37 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/module-dependency.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ownership-workstream.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/project-topology.js +13 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/public-surface.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/reduce.js +44 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/risk-domains.js +23 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/runtime-control.js +20 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/selection-ledger.js +94 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/serializer.js +81 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ssot-provenance.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/verification-coverage.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder-common.js +1 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder.js +173 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-specs.js +33 -0
- package/dist/core/triwiki/context-graph/store/architecture-map-store.js +71 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +26 -1
- package/dist/core/triwiki/triwiki-proof-bank.js +82 -2
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +2 -2
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +178 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +10 -1
- package/dist/scripts/architecture-map-check.js +299 -0
- package/dist/scripts/lib/ensure-dist-fresh.js +48 -1
- package/dist/scripts/release-metadata-check.js +2 -0
- package/package.json +6 -3
- package/release-gates.v2.json +204 -0
- package/schemas/architecture-baseline.v1.schema.json +47 -0
- package/schemas/architecture-map-manifest.v1.schema.json +40 -0
- package/schemas/architecture-map-policy.v1.schema.json +147 -0
- package/schemas/architecture-review.v1.schema.json +46 -0
- package/schemas/mermaid-projection.v1.schema.json +47 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exists, nowIso, readJson, writeJsonAtomic } from './fsx.js';
|
|
4
|
+
import { ARCHITECTURE_CAPSULE_ARTIFACT, ARCHITECTURE_MAP_BASELINE_ARTIFACT, ARCHITECTURE_MAP_MANIFEST_ARTIFACT, ARCHITECTURE_MAP_REVIEW_ARTIFACT, bindArchitectureMapBaseline, buildAfterReview, sealBaseline, validateArchitectureMapBaselineArtifact, validateArchitectureMapReviewArtifact } from './architecture-map-review.js';
|
|
5
|
+
import { readContextGraphSnapshot } from './triwiki/context-graph/store/snapshot-store.js';
|
|
6
|
+
export { ARCHITECTURE_MAP_REVIEW_ARTIFACT };
|
|
7
|
+
export const ARCHITECTURE_MAP_BASELINE_STAGE = 'architecture_map_baseline';
|
|
8
|
+
export const ARCHITECTURE_MAP_REVIEW_STAGE = 'architecture_map_review';
|
|
9
|
+
const ARCHITECTURE_MAP_EXEMPT_ROUTES = new Set([
|
|
10
|
+
'Answer',
|
|
11
|
+
'DFix',
|
|
12
|
+
'Help',
|
|
13
|
+
'Wiki',
|
|
14
|
+
'Goal',
|
|
15
|
+
'GX',
|
|
16
|
+
'DB'
|
|
17
|
+
]);
|
|
18
|
+
export function routeNeedsArchitectureMap(route) {
|
|
19
|
+
const id = String(route?.id || '');
|
|
20
|
+
if (!id)
|
|
21
|
+
return false;
|
|
22
|
+
if (ARCHITECTURE_MAP_EXEMPT_ROUTES.has(id))
|
|
23
|
+
return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
export function createArchitectureMapPlanBinding() {
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
baseline_artifact: ARCHITECTURE_MAP_BASELINE_ARTIFACT,
|
|
29
|
+
review_artifact: ARCHITECTURE_MAP_REVIEW_ARTIFACT,
|
|
30
|
+
capsule_artifact: ARCHITECTURE_CAPSULE_ARTIFACT,
|
|
31
|
+
manifest_artifact: ARCHITECTURE_MAP_MANIFEST_ARTIFACT,
|
|
32
|
+
required: true,
|
|
33
|
+
seeded_at: nowIso()
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function planStagesArchitectureMap(plan) {
|
|
37
|
+
return Array.isArray(plan?.stages)
|
|
38
|
+
&& plan.stages.some((stage) => String(stage?.id || '') === ARCHITECTURE_MAP_BASELINE_STAGE
|
|
39
|
+
&& !['skipped', 'not_applicable'].includes(String(stage?.status || '')));
|
|
40
|
+
}
|
|
41
|
+
export async function seedArchitectureMapPlanBinding(dir, plan) {
|
|
42
|
+
if (!planStagesArchitectureMap(plan))
|
|
43
|
+
return plan;
|
|
44
|
+
plan.architecture_map = createArchitectureMapPlanBinding();
|
|
45
|
+
await writeJsonAtomic(path.join(dir, 'pipeline-plan.json'), plan);
|
|
46
|
+
return plan;
|
|
47
|
+
}
|
|
48
|
+
export async function maybeSeedArchitectureMapForPlan(input) {
|
|
49
|
+
if (!input.plan?.architecture_map || !input.missionId)
|
|
50
|
+
return;
|
|
51
|
+
await seedArchitectureMapBaselineArtifacts({
|
|
52
|
+
root: input.root,
|
|
53
|
+
dir: input.dir,
|
|
54
|
+
missionId: String(input.missionId),
|
|
55
|
+
routeId: String(input.plan.route?.id || input.plan.route?.command || 'SKS'),
|
|
56
|
+
taskProfile: String(input.taskProfile || input.plan.task_profile || '')
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function seedArchitectureMapBaselineArtifacts(input) {
|
|
60
|
+
const baselinePath = path.join(input.dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT);
|
|
61
|
+
if (await exists(baselinePath))
|
|
62
|
+
return { ok: true, baselinePath };
|
|
63
|
+
const load = await readContextGraphSnapshot(input.root);
|
|
64
|
+
if (load.status !== 'ok' || !load.snapshot) {
|
|
65
|
+
return { ok: false, reason: load.blocker || 'architecture_map_graph_unavailable' };
|
|
66
|
+
}
|
|
67
|
+
const sealed = sealBaseline({
|
|
68
|
+
missionId: input.missionId,
|
|
69
|
+
routeId: input.routeId,
|
|
70
|
+
snapshot: load.snapshot,
|
|
71
|
+
policyRoot: input.root,
|
|
72
|
+
capturedAt: nowIso(),
|
|
73
|
+
...(input.taskProfile ? { taskProfile: input.taskProfile } : {})
|
|
74
|
+
});
|
|
75
|
+
await fsp.mkdir(input.dir, { recursive: true });
|
|
76
|
+
await writeJsonAtomic(baselinePath, sealed.baseline);
|
|
77
|
+
await writeJsonAtomic(path.join(input.dir, ARCHITECTURE_MAP_MANIFEST_ARTIFACT), sealed.manifest);
|
|
78
|
+
await writeArchitectureMapCapsule(input.dir, sealed.capsule.text);
|
|
79
|
+
return { ok: true, baselinePath };
|
|
80
|
+
}
|
|
81
|
+
export async function ensureArchitectureMapReviewArtifacts(input) {
|
|
82
|
+
const reviewPath = path.join(input.dir, ARCHITECTURE_MAP_REVIEW_ARTIFACT);
|
|
83
|
+
if (await exists(reviewPath))
|
|
84
|
+
return { ok: true, reviewPath };
|
|
85
|
+
const baselinePath = path.join(input.dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT);
|
|
86
|
+
if (!(await exists(baselinePath))) {
|
|
87
|
+
return { ok: false, reason: 'architecture_map_baseline_missing' };
|
|
88
|
+
}
|
|
89
|
+
const baseline = await readJson(baselinePath, null);
|
|
90
|
+
if (!baseline)
|
|
91
|
+
return { ok: false, reason: 'architecture_map_baseline_unreadable' };
|
|
92
|
+
const load = await readContextGraphSnapshot(input.root);
|
|
93
|
+
if (load.status !== 'ok' || !load.snapshot) {
|
|
94
|
+
return { ok: false, reason: load.blocker || 'architecture_map_graph_unavailable' };
|
|
95
|
+
}
|
|
96
|
+
const built = buildAfterReview({
|
|
97
|
+
missionId: input.missionId,
|
|
98
|
+
...(input.routeId ? { routeId: input.routeId } : {}),
|
|
99
|
+
baseline,
|
|
100
|
+
afterSnapshot: load.snapshot,
|
|
101
|
+
policyRoot: input.root,
|
|
102
|
+
generatedAt: nowIso()
|
|
103
|
+
});
|
|
104
|
+
await fsp.mkdir(input.dir, { recursive: true });
|
|
105
|
+
await writeJsonAtomic(reviewPath, built.review);
|
|
106
|
+
await writeJsonAtomic(path.join(input.dir, ARCHITECTURE_MAP_MANIFEST_ARTIFACT), built.manifest);
|
|
107
|
+
await writeArchitectureMapCapsule(input.dir, built.capsule.text);
|
|
108
|
+
await writeJsonAtomic(path.join(input.dir, 'architecture-map-delta.json'), built.delta);
|
|
109
|
+
return { ok: true, reviewPath };
|
|
110
|
+
}
|
|
111
|
+
export async function architectureMapGateStatus(root, state = {}) {
|
|
112
|
+
if (state?.architecture_map_required !== true)
|
|
113
|
+
return { ok: true, blockers: [] };
|
|
114
|
+
const missionId = state?.mission_id;
|
|
115
|
+
if (!missionId)
|
|
116
|
+
return { ok: false, blockers: ['architecture_map_mission_missing'] };
|
|
117
|
+
const dir = path.join(root, '.sneakoscope/missions', missionId);
|
|
118
|
+
const plan = await readJson(path.join(dir, 'pipeline-plan.json'), null);
|
|
119
|
+
if (!plan?.architecture_map)
|
|
120
|
+
return { ok: true, blockers: [], not_applicable: true };
|
|
121
|
+
if (await exists(path.join(dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT))) {
|
|
122
|
+
await ensureArchitectureMapReviewArtifacts({
|
|
123
|
+
root,
|
|
124
|
+
dir,
|
|
125
|
+
missionId,
|
|
126
|
+
routeId: String(plan?.route?.id || state?.route || '')
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const blockers = [];
|
|
130
|
+
const baselinePath = path.join(dir, ARCHITECTURE_MAP_BASELINE_ARTIFACT);
|
|
131
|
+
const reviewPath = path.join(dir, ARCHITECTURE_MAP_REVIEW_ARTIFACT);
|
|
132
|
+
if (!(await exists(baselinePath))) {
|
|
133
|
+
blockers.push('architecture_map_baseline_missing');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const baseline = await readJson(baselinePath, null);
|
|
137
|
+
const validation = validateArchitectureMapBaselineArtifact(baseline);
|
|
138
|
+
if (!validation.ok)
|
|
139
|
+
blockers.push('architecture_map_baseline_invalid', ...validation.blockers.map((b) => `baseline:${b}`));
|
|
140
|
+
else {
|
|
141
|
+
const binding = bindArchitectureMapBaseline({ baseline: baseline, missionId });
|
|
142
|
+
if (!binding.ok)
|
|
143
|
+
blockers.push(...binding.blockers.map((b) => `architecture_map_baseline_${b}`));
|
|
144
|
+
if (baseline && baseline.capturedBeforeMutation !== true)
|
|
145
|
+
blockers.push('architecture_map_baseline_late');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (!(await exists(reviewPath))) {
|
|
149
|
+
blockers.push('architecture_map_review_missing');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const review = await readJson(reviewPath, null);
|
|
153
|
+
const validation = validateArchitectureMapReviewArtifact(review);
|
|
154
|
+
if (!validation.ok)
|
|
155
|
+
blockers.push('architecture_map_review_invalid', ...validation.blockers.map((b) => `review:${b}`));
|
|
156
|
+
else if (review?.verdict !== 'pass') {
|
|
157
|
+
blockers.push('architecture_map_review_blocked');
|
|
158
|
+
if (Array.isArray(review?.unaccountedChangedPaths) && review.unaccountedChangedPaths.length) {
|
|
159
|
+
blockers.push('architecture_map_unaccounted_changed_file');
|
|
160
|
+
}
|
|
161
|
+
for (const id of review?.blockingFindingIds || [])
|
|
162
|
+
blockers.push(`architecture_map_finding:${id}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { ok: blockers.length === 0, blockers: [...new Set(blockers)] };
|
|
166
|
+
}
|
|
167
|
+
export async function writeArchitectureMapCapsule(dir, text) {
|
|
168
|
+
const file = path.join(dir, ARCHITECTURE_CAPSULE_ARTIFACT);
|
|
169
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
170
|
+
await fsp.writeFile(file, text.endsWith('\n') ? text : `${text}\n`, 'utf8');
|
|
171
|
+
return file;
|
|
172
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { sha256 } from './fsx.js';
|
|
2
|
+
import { ARCHITECTURE_BASELINE_SCHEMA, ARCHITECTURE_MAP_MANIFEST_SCHEMA, ARCHITECTURE_REVIEW_SCHEMA } from './triwiki/context-graph/architecture/contracts.js';
|
|
3
|
+
import { buildArchitectureBaseline, verifyArchitectureBaselineSeal } from './triwiki/context-graph/architecture/baseline.js';
|
|
4
|
+
import { renderArchitectureCapsule } from './triwiki/context-graph/architecture/capsule.js';
|
|
5
|
+
import { buildArchitectureDelta, buildArchitectureReview } from './triwiki/context-graph/architecture/delta.js';
|
|
6
|
+
import { buildArchitectureInputBundle, policyContentHash } from './triwiki/context-graph/architecture/input-bundle.js';
|
|
7
|
+
import { loadArchitectureMapPolicy } from './triwiki/context-graph/architecture/policy.js';
|
|
8
|
+
import { buildArchitectureScope } from './triwiki/context-graph/architecture/slice.js';
|
|
9
|
+
import { validateArchitectureBaseline, validateArchitectureInputBundle, validateArchitectureMapManifest, validateArchitectureReview } from './triwiki/context-graph/architecture/validation.js';
|
|
10
|
+
import { buildArchitectureMapManifest, buildArchitectureMapViews } from './triwiki/context-graph/projections/mermaid/index.js';
|
|
11
|
+
export const ARCHITECTURE_MAP_BASELINE_ARTIFACT = 'architecture-map-baseline.json';
|
|
12
|
+
export const ARCHITECTURE_MAP_REVIEW_ARTIFACT = 'architecture-map-review.json';
|
|
13
|
+
export const ARCHITECTURE_CAPSULE_ARTIFACT = 'architecture-capsule.txt';
|
|
14
|
+
export const ARCHITECTURE_MAP_MANIFEST_ARTIFACT = 'architecture-map-manifest.json';
|
|
15
|
+
export const ARCHITECTURE_MAP_REVIEW_SCHEMA = ARCHITECTURE_REVIEW_SCHEMA;
|
|
16
|
+
export const ARCHITECTURE_MAP_BASELINE_SCHEMA = ARCHITECTURE_BASELINE_SCHEMA;
|
|
17
|
+
export const ARCHITECTURE_MAP_MANIFEST_SCHEMA_ID = ARCHITECTURE_MAP_MANIFEST_SCHEMA;
|
|
18
|
+
export function resolveArchitectureMapPolicy(input) {
|
|
19
|
+
if (input.policy)
|
|
20
|
+
return input.policy;
|
|
21
|
+
if (input.policyRoot)
|
|
22
|
+
return loadArchitectureMapPolicy(input.policyRoot);
|
|
23
|
+
throw new Error('architecture_map_policy_required');
|
|
24
|
+
}
|
|
25
|
+
export function sealBaseline(input) {
|
|
26
|
+
const policy = resolveArchitectureMapPolicy(input);
|
|
27
|
+
const rootId = input.rootId ?? 'workspace';
|
|
28
|
+
const profile = input.profile ?? 'global';
|
|
29
|
+
const bundle = buildArchitectureInputBundle({
|
|
30
|
+
rootId,
|
|
31
|
+
graph: input.snapshot,
|
|
32
|
+
policy,
|
|
33
|
+
profile
|
|
34
|
+
});
|
|
35
|
+
const views = buildArchitectureMapViews(input.snapshot, policy, {
|
|
36
|
+
rootId,
|
|
37
|
+
missionId: input.missionId
|
|
38
|
+
});
|
|
39
|
+
const viewHashes = Object.freeze(Object.fromEntries(views.views.map((view) => [view.viewId, view.projection.contentHash])));
|
|
40
|
+
const scope = buildArchitectureScope({
|
|
41
|
+
profile,
|
|
42
|
+
...(input.seedNodeIds ? { seedNodeIds: input.seedNodeIds } : {}),
|
|
43
|
+
...(input.seedPaths ? { seedPaths: input.seedPaths } : {})
|
|
44
|
+
});
|
|
45
|
+
const baseline = buildArchitectureBaseline({
|
|
46
|
+
missionId: input.missionId,
|
|
47
|
+
routeId: input.routeId,
|
|
48
|
+
taskProfile: input.taskProfile ?? profile,
|
|
49
|
+
capturedAt: input.capturedAt,
|
|
50
|
+
bundle,
|
|
51
|
+
policy,
|
|
52
|
+
scope,
|
|
53
|
+
viewHashes,
|
|
54
|
+
findings: views.findings,
|
|
55
|
+
metrics: views.metrics
|
|
56
|
+
});
|
|
57
|
+
const capsule = renderArchitectureCapsule({
|
|
58
|
+
profile,
|
|
59
|
+
missionId: input.missionId,
|
|
60
|
+
routeId: input.routeId,
|
|
61
|
+
baseline,
|
|
62
|
+
metrics: views.metrics,
|
|
63
|
+
findings: views.findings,
|
|
64
|
+
...(input.seedPaths ? { seedPaths: input.seedPaths } : {}),
|
|
65
|
+
mapArtifact: ARCHITECTURE_MAP_MANIFEST_ARTIFACT
|
|
66
|
+
});
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
baseline,
|
|
69
|
+
bundle,
|
|
70
|
+
manifest: views.manifest,
|
|
71
|
+
capsule,
|
|
72
|
+
policy
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function buildAfterReview(input) {
|
|
76
|
+
const policy = resolveArchitectureMapPolicy(input);
|
|
77
|
+
const rootId = input.rootId ?? 'workspace';
|
|
78
|
+
const profile = input.profile ?? input.baseline.scope.profile;
|
|
79
|
+
const afterBundle = buildArchitectureInputBundle({
|
|
80
|
+
rootId,
|
|
81
|
+
graph: input.afterSnapshot,
|
|
82
|
+
policy,
|
|
83
|
+
profile,
|
|
84
|
+
policyHash: policyContentHash(policy)
|
|
85
|
+
});
|
|
86
|
+
const views = buildArchitectureMapViews(input.afterSnapshot, policy, {
|
|
87
|
+
rootId,
|
|
88
|
+
missionId: input.missionId
|
|
89
|
+
});
|
|
90
|
+
const projectionAccounting = Object.freeze(Object.fromEntries(views.views.map((view) => [
|
|
91
|
+
view.viewId,
|
|
92
|
+
{
|
|
93
|
+
selectedNodeCount: view.projection.accounting.selectedNodeIds.length,
|
|
94
|
+
emittedNodeCount: view.projection.accounting.emittedNodeIds.length,
|
|
95
|
+
omittedNodeCount: view.projection.accounting.omittedNodes.length,
|
|
96
|
+
selectedEdgeCount: view.projection.accounting.selectedEdgeIds.length,
|
|
97
|
+
emittedEdgeCount: view.projection.accounting.emittedEdgeIds.length,
|
|
98
|
+
omittedEdgeCount: view.projection.accounting.omittedEdges.length,
|
|
99
|
+
accountingRatio: view.projection.accounting.selectedNodeIds.length === 0
|
|
100
|
+
? 1
|
|
101
|
+
: view.projection.accounting.emittedNodeIds.length /
|
|
102
|
+
view.projection.accounting.selectedNodeIds.length
|
|
103
|
+
}
|
|
104
|
+
])));
|
|
105
|
+
const review = buildArchitectureReview({
|
|
106
|
+
missionId: input.missionId,
|
|
107
|
+
baseline: input.baseline,
|
|
108
|
+
afterBundle,
|
|
109
|
+
policy,
|
|
110
|
+
generatedAt: input.generatedAt,
|
|
111
|
+
projectionAccounting,
|
|
112
|
+
...(input.accountedChangedPaths
|
|
113
|
+
? { accountedChangedPaths: input.accountedChangedPaths }
|
|
114
|
+
: {})
|
|
115
|
+
});
|
|
116
|
+
const delta = buildArchitectureDelta({
|
|
117
|
+
missionId: input.missionId,
|
|
118
|
+
baseline: input.baseline,
|
|
119
|
+
afterBundle,
|
|
120
|
+
policy
|
|
121
|
+
});
|
|
122
|
+
const capsule = renderArchitectureCapsule({
|
|
123
|
+
profile,
|
|
124
|
+
missionId: input.missionId,
|
|
125
|
+
...(input.routeId ? { routeId: input.routeId } : {}),
|
|
126
|
+
baseline: input.baseline,
|
|
127
|
+
metrics: views.metrics,
|
|
128
|
+
findings: views.findings,
|
|
129
|
+
mapArtifact: 'architecture-map-delta.mmd'
|
|
130
|
+
});
|
|
131
|
+
return Object.freeze({
|
|
132
|
+
review,
|
|
133
|
+
delta,
|
|
134
|
+
afterBundle,
|
|
135
|
+
manifest: views.manifest,
|
|
136
|
+
capsule,
|
|
137
|
+
policy
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
export function validateArchitectureMapBaselineArtifact(value) {
|
|
141
|
+
return validateArchitectureBaseline(value);
|
|
142
|
+
}
|
|
143
|
+
export function validateArchitectureMapReviewArtifact(value) {
|
|
144
|
+
return validateArchitectureReview(value);
|
|
145
|
+
}
|
|
146
|
+
export function validateArchitectureMapManifestArtifact(value) {
|
|
147
|
+
return validateArchitectureMapManifest(value);
|
|
148
|
+
}
|
|
149
|
+
export function validateArchitectureMapInputBundleArtifact(value) {
|
|
150
|
+
return validateArchitectureInputBundle(value);
|
|
151
|
+
}
|
|
152
|
+
export function bindArchitectureMapBaseline(input) {
|
|
153
|
+
const validation = validateArchitectureBaseline(input.baseline);
|
|
154
|
+
const blockers = [...validation.blockers];
|
|
155
|
+
if (input.baseline.missionId !== input.missionId)
|
|
156
|
+
blockers.push('mission_mismatch');
|
|
157
|
+
if (!verifyArchitectureBaselineSeal(input.baseline))
|
|
158
|
+
blockers.push('seal_mismatch');
|
|
159
|
+
if (blockers.length)
|
|
160
|
+
return { ok: false, blockers, binding: null };
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
blockers: [],
|
|
164
|
+
binding: {
|
|
165
|
+
missionId: input.baseline.missionId,
|
|
166
|
+
seal: input.baseline.seal,
|
|
167
|
+
canonicalPayloadHash: input.baseline.canonicalPayloadHash,
|
|
168
|
+
policyHash: input.baseline.policyHash,
|
|
169
|
+
graphHash: input.baseline.graphHash,
|
|
170
|
+
worktreeFingerprintHash: input.baseline.worktreeFingerprintHash
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
export function architectureMapReviewDigest(review) {
|
|
175
|
+
return sha256(JSON.stringify(review ?? null));
|
|
176
|
+
}
|
|
177
|
+
export function architectureMapBaselineDigest(baseline) {
|
|
178
|
+
return sha256(JSON.stringify(baseline ?? null));
|
|
179
|
+
}
|
|
180
|
+
export { buildArchitectureMapManifest, buildArchitectureMapViews };
|
|
@@ -17,7 +17,8 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
17
17
|
const candidates = [
|
|
18
18
|
{ scope: 'project', file: path.join(resolvedRoot, '.codex', 'config.toml'), mode: 'project_forbidden_keys' },
|
|
19
19
|
{ scope: 'codex_home', file: path.join(home, 'config.toml'), mode: 'sks_caused_host_owned_keys' }
|
|
20
|
-
]
|
|
20
|
+
].filter((candidate) => candidate.mode !== 'project_forbidden_keys'
|
|
21
|
+
|| path.resolve(candidate.file) !== path.resolve(home, 'config.toml'));
|
|
21
22
|
const actions = [];
|
|
22
23
|
const detectedProjectLocalForbiddenKeys = [];
|
|
23
24
|
const unsafeReasons = [];
|
|
@@ -24,6 +24,7 @@ export const NATIVE_SOURCE_FILES = [
|
|
|
24
24
|
'ProvidersReliability.swift',
|
|
25
25
|
'ProvidersRoutingTruth.swift',
|
|
26
26
|
'ProvidersOpenRouter.swift',
|
|
27
|
+
'ProvidersModelExposure.swift',
|
|
27
28
|
'ProvidersBridgeCatalog.swift',
|
|
28
29
|
'RemoteCodingViewController.swift',
|
|
29
30
|
'DiagnosticsViewController.swift',
|
|
@@ -135,6 +135,7 @@ export async function readCodexLbModelCatalog(opts = {}) {
|
|
|
135
135
|
ok: false,
|
|
136
136
|
status: 'blocked',
|
|
137
137
|
models: [],
|
|
138
|
+
model_rows: [],
|
|
138
139
|
model_efforts: {},
|
|
139
140
|
http_status: null,
|
|
140
141
|
blockers: loaded.missing.length ? loaded.missing.map((item) => `codex_lb_missing:${item}`) : ['codex_lb_not_configured']
|
|
@@ -147,6 +148,7 @@ export async function readCodexLbModelCatalog(opts = {}) {
|
|
|
147
148
|
ok: false,
|
|
148
149
|
status: 'blocked',
|
|
149
150
|
models: [],
|
|
151
|
+
model_rows: [],
|
|
150
152
|
model_efforts: {},
|
|
151
153
|
http_status: null,
|
|
152
154
|
blockers: [transportBlocker]
|
|
@@ -167,6 +169,7 @@ export async function readCodexLbModelCatalog(opts = {}) {
|
|
|
167
169
|
ok: false,
|
|
168
170
|
status: 'blocked',
|
|
169
171
|
models: [],
|
|
172
|
+
model_rows: [],
|
|
170
173
|
model_efforts: {},
|
|
171
174
|
http_status: null,
|
|
172
175
|
blockers: [`codex_lb_${safeBridgeErrorCode(error).replace(/^bridge_/, '')}`]
|
|
@@ -175,15 +178,17 @@ export async function readCodexLbModelCatalog(opts = {}) {
|
|
|
175
178
|
try {
|
|
176
179
|
const timeoutMs = Math.max(250, Number(opts.timeoutMs || 5000));
|
|
177
180
|
const modelsUrl = new URL(`${loaded.base_url}/models`);
|
|
178
|
-
const
|
|
179
|
-
? await fetchImpl(
|
|
181
|
+
const request = async (url) => (opts.fetchImpl
|
|
182
|
+
? await fetchImpl(url, {
|
|
180
183
|
headers: gatewayHeaders,
|
|
181
184
|
redirect: 'error',
|
|
182
185
|
signal: AbortSignal.timeout(timeoutMs)
|
|
183
186
|
})
|
|
184
|
-
: await requestPinnedCodexLbCatalog(
|
|
187
|
+
: await requestPinnedCodexLbCatalog(url, remote, gatewayHeaders, timeoutMs));
|
|
188
|
+
const response = await request(modelsUrl);
|
|
185
189
|
const payload = await response.json().catch(() => null);
|
|
186
190
|
const models = normalizeCodexLbModelCatalogPayload(payload);
|
|
191
|
+
const modelRows = codexLbModelCatalogRows(payload);
|
|
187
192
|
const apiEfforts = normalizeCodexModelEffortCatalogPayload(payload);
|
|
188
193
|
const cachePath = path.join(String(process.env.CODEX_HOME || path.join(os.homedir(), '.codex')), 'models_cache.json');
|
|
189
194
|
const cachePayload = await readJson(cachePath, null).catch(() => null);
|
|
@@ -195,6 +200,7 @@ export async function readCodexLbModelCatalog(opts = {}) {
|
|
|
195
200
|
ok,
|
|
196
201
|
status: ok ? 'ready' : 'blocked',
|
|
197
202
|
models,
|
|
203
|
+
model_rows: modelRows,
|
|
198
204
|
model_efforts: modelEfforts,
|
|
199
205
|
http_status: response.status,
|
|
200
206
|
blockers: [
|
|
@@ -209,6 +215,7 @@ export async function readCodexLbModelCatalog(opts = {}) {
|
|
|
209
215
|
ok: false,
|
|
210
216
|
status: 'blocked',
|
|
211
217
|
models: [],
|
|
218
|
+
model_rows: [],
|
|
212
219
|
model_efforts: {},
|
|
213
220
|
http_status: null,
|
|
214
221
|
blockers: ['codex_lb_model_catalog_unavailable']
|
|
@@ -287,6 +294,21 @@ export function normalizeCodexLbModelCatalogPayload(payload) {
|
|
|
287
294
|
.filter(Boolean);
|
|
288
295
|
return [...new Set(models)];
|
|
289
296
|
}
|
|
297
|
+
export function codexLbModelCatalogRows(payload) {
|
|
298
|
+
const rows = Array.isArray(payload?.models) ? payload.models : Array.isArray(payload?.data) ? payload.data : [];
|
|
299
|
+
const seen = new Set();
|
|
300
|
+
const preserved = [];
|
|
301
|
+
for (const row of rows) {
|
|
302
|
+
if (!row || typeof row !== 'object' || Array.isArray(row))
|
|
303
|
+
continue;
|
|
304
|
+
const slug = String(row.id || row.model || row.slug || row.name || '').trim();
|
|
305
|
+
if (!slug || seen.has(slug))
|
|
306
|
+
continue;
|
|
307
|
+
seen.add(slug);
|
|
308
|
+
preserved.push({ ...row, slug });
|
|
309
|
+
}
|
|
310
|
+
return preserved;
|
|
311
|
+
}
|
|
290
312
|
export function normalizeCodexLbBaseUrl(input = '') {
|
|
291
313
|
let host = String(input || '').trim();
|
|
292
314
|
if (!host)
|
|
@@ -2,14 +2,18 @@ import { buildBridgeRouteIndex, sha256Stable } from '../route-index.js';
|
|
|
2
2
|
import { COMBINED_BRIDGE_CATALOG_SCHEMA } from './contracts.js';
|
|
3
3
|
import { normalizeProviderCatalog, providerCatalogStatus, routeProviderState } from './normalize.js';
|
|
4
4
|
import { compareModels, unique } from './shared.js';
|
|
5
|
+
import { applyBridgeModelSelection, availableModelRows, emptyBridgeModelSelection } from './model-selection.js';
|
|
5
6
|
export function buildCombinedBridgeCatalog(registry, options) {
|
|
6
7
|
const createdAt = options.created_at || new Date().toISOString();
|
|
7
8
|
const normalized = {
|
|
8
9
|
'codex-lb': normalizeProviderCatalog(options.catalogs['codex-lb']),
|
|
9
10
|
openrouter: normalizeProviderCatalog(options.catalogs.openrouter)
|
|
10
11
|
};
|
|
11
|
-
const
|
|
12
|
+
const selection = options.selection || emptyBridgeModelSelection(createdAt);
|
|
13
|
+
const allModels = [...normalized['codex-lb'].models, ...normalized.openrouter.models]
|
|
12
14
|
.sort(compareModels);
|
|
15
|
+
const availableOpenRouterModels = availableModelRows(allModels, selection);
|
|
16
|
+
const models = options.selection ? applyBridgeModelSelection(allModels, selection) : allModels;
|
|
13
17
|
const routeBuild = buildBridgeRouteIndex({
|
|
14
18
|
models,
|
|
15
19
|
providers: {
|
|
@@ -81,6 +85,7 @@ export function buildCombinedBridgeCatalog(registry, options) {
|
|
|
81
85
|
route_index: routeBuild.route_index,
|
|
82
86
|
status,
|
|
83
87
|
blockers,
|
|
84
|
-
warnings: status.warnings
|
|
88
|
+
warnings: status.warnings,
|
|
89
|
+
available_openrouter_models: availableOpenRouterModels
|
|
85
90
|
};
|
|
86
91
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeJsonAtomic } from '../../fsx.js';
|
|
4
|
+
export const BRIDGE_MODEL_SELECTION_SCHEMA = 'sks.bridge-model-selection.v1';
|
|
5
|
+
export const BRIDGE_MODEL_SELECTION_FILENAME = 'sks-bridge-model-selection.json';
|
|
6
|
+
export const BRIDGE_AVAILABLE_MODELS_FILENAME = 'sks-bridge-available-models.json';
|
|
7
|
+
export const MAX_SELECTED_OPENROUTER_MODELS = 64;
|
|
8
|
+
export function bridgeModelSelectionPath(home) {
|
|
9
|
+
return path.join(path.resolve(home), '.codex', 'sks', BRIDGE_MODEL_SELECTION_FILENAME);
|
|
10
|
+
}
|
|
11
|
+
export function bridgeAvailableModelsPath(home) {
|
|
12
|
+
return path.join(path.resolve(home), '.codex', 'sks', BRIDGE_AVAILABLE_MODELS_FILENAME);
|
|
13
|
+
}
|
|
14
|
+
export function emptyBridgeModelSelection(now) {
|
|
15
|
+
return { schema: BRIDGE_MODEL_SELECTION_SCHEMA, updated_at: now, openrouter: { mode: 'selected', public_ids: [] } };
|
|
16
|
+
}
|
|
17
|
+
export function normalizeBridgeModelSelection(value, now) {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
19
|
+
return emptyBridgeModelSelection(now);
|
|
20
|
+
const row = value;
|
|
21
|
+
if (row.schema !== BRIDGE_MODEL_SELECTION_SCHEMA)
|
|
22
|
+
return emptyBridgeModelSelection(now);
|
|
23
|
+
const openrouter = row.openrouter && typeof row.openrouter === 'object' && !Array.isArray(row.openrouter)
|
|
24
|
+
? row.openrouter
|
|
25
|
+
: {};
|
|
26
|
+
const ids = Array.isArray(openrouter.public_ids)
|
|
27
|
+
? openrouter.public_ids.map((id) => String(id || '').trim()).filter(Boolean)
|
|
28
|
+
: [];
|
|
29
|
+
return {
|
|
30
|
+
schema: BRIDGE_MODEL_SELECTION_SCHEMA,
|
|
31
|
+
updated_at: typeof row.updated_at === 'string' && row.updated_at ? row.updated_at : now,
|
|
32
|
+
openrouter: { mode: 'selected', public_ids: [...new Set(ids)].sort().slice(0, MAX_SELECTED_OPENROUTER_MODELS) }
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function readBridgeModelSelection(home, now = new Date().toISOString()) {
|
|
36
|
+
return (await readBridgeModelSelectionState(home, now)).selection;
|
|
37
|
+
}
|
|
38
|
+
export async function readBridgeModelSelectionState(home, now = new Date().toISOString()) {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await fsp.readFile(bridgeModelSelectionPath(home), 'utf8');
|
|
41
|
+
return { selection: normalizeBridgeModelSelection(JSON.parse(raw), now), stored: true };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { selection: emptyBridgeModelSelection(now), stored: false };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function writeBridgeModelSelection(home, selection) {
|
|
48
|
+
await writeJsonAtomic(bridgeModelSelectionPath(home), selection, { mode: 0o600 });
|
|
49
|
+
}
|
|
50
|
+
export function applyBridgeModelSelection(models, selection) {
|
|
51
|
+
const selected = new Set(selection.openrouter.public_ids);
|
|
52
|
+
return models.filter((model) => model.provider_id !== 'openrouter' || selected.has(model.public_id));
|
|
53
|
+
}
|
|
54
|
+
export function availableModelRows(models, selection) {
|
|
55
|
+
const selected = new Set(selection.openrouter.public_ids);
|
|
56
|
+
return models
|
|
57
|
+
.filter((model) => model.provider_id === 'openrouter')
|
|
58
|
+
.map((model) => ({
|
|
59
|
+
public_id: model.public_id,
|
|
60
|
+
display_name: model.display_name,
|
|
61
|
+
selected: selected.has(model.public_id)
|
|
62
|
+
}))
|
|
63
|
+
.sort((left, right) => left.display_name.localeCompare(right.display_name));
|
|
64
|
+
}
|
|
65
|
+
export function pruneSelection(selection, available) {
|
|
66
|
+
const known = new Set(available.map((row) => row.public_id));
|
|
67
|
+
const kept = selection.openrouter.public_ids.filter((id) => known.has(id));
|
|
68
|
+
if (kept.length === selection.openrouter.public_ids.length)
|
|
69
|
+
return selection;
|
|
70
|
+
return { ...selection, openrouter: { mode: 'selected', public_ids: kept } };
|
|
71
|
+
}
|
|
@@ -30,9 +30,10 @@ export function normalizeProviderCatalog(input) {
|
|
|
30
30
|
if (!publicId || !upstreamModel)
|
|
31
31
|
return null;
|
|
32
32
|
const features = row?.features && typeof row.features === 'object' ? row.features : {};
|
|
33
|
+
const reasoning = features.reasoning === true;
|
|
33
34
|
const capabilities = [
|
|
34
35
|
...(features.tools === true ? ['tools'] : []),
|
|
35
|
-
...(
|
|
36
|
+
...(reasoning ? ['reasoning'] : []),
|
|
36
37
|
...(features.vision === true ? ['vision'] : []),
|
|
37
38
|
...(features.audio === true ? ['audio'] : [])
|
|
38
39
|
];
|
|
@@ -45,7 +46,11 @@ export function normalizeProviderCatalog(input) {
|
|
|
45
46
|
capabilities,
|
|
46
47
|
source_catalog_generation: input.generation || 'unknown',
|
|
47
48
|
route_key: `openrouter:${publicId}`,
|
|
48
|
-
...
|
|
49
|
+
...(reasoning ? { default_reasoning_level: OPENROUTER_DEFAULT_REASONING_LEVEL } : {}),
|
|
50
|
+
...codexModelInfoFields(publicId, {
|
|
51
|
+
supported_reasoning_levels: reasoning ? openRouterReasoningLevels() : [],
|
|
52
|
+
supports_reasoning_summary_parameter: reasoning
|
|
53
|
+
}, { tools: capabilities.includes('tools') })
|
|
49
54
|
});
|
|
50
55
|
}).filter(isModel);
|
|
51
56
|
if (models.length === 0)
|
|
@@ -113,6 +118,7 @@ function normalizeCodexLbBridgeCatalogModels(payload, sourceCatalogGeneration) {
|
|
|
113
118
|
...(row.supports_audio === true || row.audio === true ? ['audio'] : [])
|
|
114
119
|
];
|
|
115
120
|
models.push({
|
|
121
|
+
...row,
|
|
116
122
|
public_id: publicId,
|
|
117
123
|
provider_id: 'codex-lb',
|
|
118
124
|
upstream_model: publicId,
|
|
@@ -151,6 +157,16 @@ function sanitizeCodexLbModelRow(row, index) {
|
|
|
151
157
|
function isPlainObject(value) {
|
|
152
158
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
153
159
|
}
|
|
160
|
+
export const OPENROUTER_REASONING_LEVELS = [
|
|
161
|
+
{ effort: 'low', description: 'Fast responses with lighter reasoning' },
|
|
162
|
+
{ effort: 'medium', description: 'Balances speed and reasoning depth for everyday tasks' },
|
|
163
|
+
{ effort: 'high', description: 'Greater reasoning depth for complex problems' },
|
|
164
|
+
{ effort: 'xhigh', description: 'Extra high reasoning depth for complex problems' }
|
|
165
|
+
];
|
|
166
|
+
export const OPENROUTER_DEFAULT_REASONING_LEVEL = 'medium';
|
|
167
|
+
function openRouterReasoningLevels() {
|
|
168
|
+
return OPENROUTER_REASONING_LEVELS.map((level) => ({ ...level }));
|
|
169
|
+
}
|
|
154
170
|
export function codexModelInfoFields(slug, row, fallback) {
|
|
155
171
|
const summaries = typeof row?.supports_reasoning_summary_parameter === 'boolean'
|
|
156
172
|
? row.supports_reasoning_summary_parameter
|
|
@@ -158,7 +174,7 @@ export function codexModelInfoFields(slug, row, fallback) {
|
|
|
158
174
|
return {
|
|
159
175
|
slug,
|
|
160
176
|
supported_reasoning_levels: Array.isArray(row?.supported_reasoning_levels)
|
|
161
|
-
? row.supported_reasoning_levels.map(String)
|
|
177
|
+
? row.supported_reasoning_levels.map((level) => (level && typeof level === 'object' ? level : String(level)))
|
|
162
178
|
: [],
|
|
163
179
|
shell_type: typeof row?.shell_type === 'string' && row.shell_type ? row.shell_type : 'shell_command',
|
|
164
180
|
visibility: typeof row?.visibility === 'string' && row.visibility ? row.visibility : 'list',
|
|
@@ -186,6 +202,7 @@ function canonicalModel(model) {
|
|
|
186
202
|
if (!publicId || !upstream)
|
|
187
203
|
return null;
|
|
188
204
|
return {
|
|
205
|
+
...model,
|
|
189
206
|
public_id: publicId,
|
|
190
207
|
provider_id: model.provider_id,
|
|
191
208
|
upstream_model: upstream,
|