@pellux/goodvibes-agent 1.1.6 → 1.2.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/CHANGELOG.md +176 -0
- package/README.md +46 -30
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
- package/dist/package/main.js +132662 -91399
- package/docs/README.md +14 -7
- package/docs/channels-remote-and-api.md +8 -5
- package/docs/connected-host.md +14 -12
- package/docs/getting-started.md +56 -30
- package/docs/knowledge-artifacts-and-multimodal.md +7 -5
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +11 -3
- package/docs/tools-and-commands.md +114 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +130 -5
- package/release/release-readiness.json +120 -90
- package/src/agent/assistant-cockpit.ts +247 -0
- package/src/agent/autonomy-schedule-format.ts +96 -0
- package/src/agent/autonomy-schedule.ts +514 -0
- package/src/agent/channel-delivery-receipts.ts +292 -0
- package/src/agent/competitive-feature-inventory.ts +296 -0
- package/src/agent/document-registry-types.ts +106 -0
- package/src/agent/document-registry.ts +734 -0
- package/src/agent/harness-control.ts +2 -2
- package/src/agent/memory-prompt.ts +2 -2
- package/src/agent/operator-actions.ts +50 -6
- package/src/agent/persona-registry.ts +8 -0
- package/src/agent/project-context-files.ts +273 -0
- package/src/agent/prompt-context-receipts.ts +502 -0
- package/src/agent/reminder-schedule-format.ts +16 -2
- package/src/agent/research-run-registry.ts +582 -0
- package/src/agent/research-source-registry.ts +441 -0
- package/src/agent/routine-registry.ts +19 -3
- package/src/agent/routine-schedule-format.ts +27 -2
- package/src/agent/runtime-profile-starters.ts +7 -0
- package/src/agent/runtime-profile.ts +106 -4
- package/src/agent/schedule-edit-format.ts +129 -0
- package/src/agent/schedule-edit.ts +496 -0
- package/src/agent/schedule-next-routes.ts +42 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +447 -0
- package/src/agent/skill-registry-types.ts +102 -0
- package/src/agent/skill-registry.ts +81 -107
- package/src/agent/vibe-confirmation-routes.ts +62 -0
- package/src/agent/vibe-file.ts +285 -0
- package/src/cli/agent-knowledge-command.ts +45 -2
- package/src/cli/help.ts +3 -0
- package/src/cli/profiles-command.ts +25 -11
- package/src/config/agent-settings-policy.ts +1 -18
- package/src/input/agent-workspace-activation.ts +53 -0
- package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
- package/src/input/agent-workspace-artifact-metadata.ts +17 -0
- package/src/input/agent-workspace-basic-command-editors.ts +5 -1
- package/src/input/agent-workspace-categories.ts +153 -155
- package/src/input/agent-workspace-channel-triage.ts +481 -0
- package/src/input/agent-workspace-channels.ts +263 -0
- package/src/input/agent-workspace-command-editor.ts +152 -0
- package/src/input/agent-workspace-context-snapshot.ts +349 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
- package/src/input/agent-workspace-document-editor.ts +502 -0
- package/src/input/agent-workspace-document-ops-editor.ts +523 -0
- package/src/input/agent-workspace-host-category.ts +1 -1
- package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
- package/src/input/agent-workspace-model-compare-editor.ts +376 -0
- package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
- package/src/input/agent-workspace-model-compare-types.ts +82 -0
- package/src/input/agent-workspace-model-compare-utils.ts +35 -0
- package/src/input/agent-workspace-onboarding-categories.ts +141 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
- package/src/input/agent-workspace-operations-command-editors.ts +22 -0
- package/src/input/agent-workspace-research-report-editor.ts +212 -0
- package/src/input/agent-workspace-research-run-editor.ts +144 -0
- package/src/input/agent-workspace-research-source-editor.ts +167 -0
- package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
- package/src/input/agent-workspace-review-packet-utils.ts +265 -0
- package/src/input/agent-workspace-settings.ts +262 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +198 -0
- package/src/input/agent-workspace-setup.ts +34 -1
- package/src/input/agent-workspace-snapshot.ts +162 -147
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +18 -0
- package/src/input/command-registry.ts +9 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
- package/src/input/commands/channels-runtime.ts +102 -114
- package/src/input/commands/delegation-runtime.ts +87 -24
- package/src/input/commands/knowledge-browser-flags.ts +12 -0
- package/src/input/commands/knowledge.ts +28 -14
- package/src/input/commands/operator-actions-runtime.ts +6 -3
- package/src/input/commands/schedule-runtime.ts +71 -14
- package/src/input/commands/session-content.ts +2 -2
- package/src/input/commands/shell-core.ts +2 -0
- package/src/input/commands/vibe-runtime.ts +140 -0
- package/src/input/commands.ts +2 -0
- package/src/input/connected-host-routes.ts +123 -0
- package/src/input/settings-modal-types.ts +14 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +686 -0
- package/src/renderer/agent-workspace.ts +30 -419
- package/src/renderer/settings-modal-helpers.ts +12 -0
- package/src/renderer/settings-modal.ts +12 -0
- package/src/runtime/bootstrap-command-context.ts +11 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -3
- package/src/runtime/bootstrap-core.ts +43 -1
- package/src/runtime/bootstrap-shell.ts +6 -0
- package/src/runtime/bootstrap.ts +113 -30
- package/src/runtime/connected-host-auth.ts +3 -2
- package/src/runtime/execution-ledger.ts +231 -0
- package/src/runtime/services.ts +88 -10
- package/src/runtime/tool-permission-safety.ts +287 -0
- package/src/shell/session-continuity-hints.ts +39 -0
- package/src/tools/agent-artifacts-tool.ts +606 -0
- package/src/tools/agent-audit-tool.ts +155 -0
- package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
- package/src/tools/agent-autonomy-tool.ts +140 -0
- package/src/tools/agent-channel-send-tool.ts +24 -2
- package/src/tools/agent-channels-tool.ts +140 -0
- package/src/tools/agent-computer-tool.ts +168 -0
- package/src/tools/agent-context-policy.ts +267 -10
- package/src/tools/agent-context-tool.ts +143 -0
- package/src/tools/agent-delegation-tool.ts +128 -0
- package/src/tools/agent-device-tool.ts +240 -0
- package/src/tools/agent-documents-tool.ts +684 -0
- package/src/tools/agent-execution-tool.ts +230 -0
- package/src/tools/agent-harness-agent-orchestration.ts +703 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +560 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +794 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
- package/src/tools/agent-harness-browser-control.ts +338 -0
- package/src/tools/agent-harness-channel-metadata.ts +168 -10
- package/src/tools/agent-harness-cli-command-policy.ts +110 -0
- package/src/tools/agent-harness-command-catalog.ts +4 -2
- package/src/tools/agent-harness-command-runner.ts +42 -0
- package/src/tools/agent-harness-connected-host-status.ts +7 -4
- package/src/tools/agent-harness-delegation-posture.ts +234 -19
- package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
- package/src/tools/agent-harness-document-ops-types.ts +72 -0
- package/src/tools/agent-harness-document-ops.ts +671 -0
- package/src/tools/agent-harness-execution-history.ts +489 -0
- package/src/tools/agent-harness-execution-posture.ts +382 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
- package/src/tools/agent-harness-learning-curator-common.ts +102 -0
- package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
- package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
- package/src/tools/agent-harness-learning-curator-types.ts +151 -0
- package/src/tools/agent-harness-learning-curator.ts +417 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
- package/src/tools/agent-harness-media-posture.ts +208 -2
- package/src/tools/agent-harness-memory-posture.ts +556 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +84 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +186 -0
- package/src/tools/agent-harness-model-readiness.ts +406 -0
- package/src/tools/agent-harness-model-routing-types.ts +266 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +137 -190
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +431 -18
- package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
- package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
- package/src/tools/agent-harness-personal-ops-records.ts +732 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +222 -0
- package/src/tools/agent-harness-personal-ops.ts +606 -0
- package/src/tools/agent-harness-project-context.ts +144 -0
- package/src/tools/agent-harness-prompt-context.ts +589 -0
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-release-evidence.ts +9 -7
- package/src/tools/agent-harness-release-readiness.ts +9 -7
- package/src/tools/agent-harness-research-briefing.ts +427 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +295 -0
- package/src/tools/agent-harness-research-workflow.ts +322 -0
- package/src/tools/agent-harness-security-posture.ts +2 -2
- package/src/tools/agent-harness-service-posture.ts +9 -9
- package/src/tools/agent-harness-session-metadata.ts +3 -3
- package/src/tools/agent-harness-setup-connected-host.ts +501 -0
- package/src/tools/agent-harness-setup-describe.ts +557 -0
- package/src/tools/agent-harness-setup-handoffs.ts +502 -0
- package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
- package/src/tools/agent-harness-setup-plan.ts +253 -0
- package/src/tools/agent-harness-setup-posture-types.ts +218 -0
- package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
- package/src/tools/agent-harness-setup-posture.ts +449 -221
- package/src/tools/agent-harness-setup-smoke.ts +508 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +120 -7
- package/src/tools/agent-harness-tool-types.ts +78 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +311 -370
- package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
- package/src/tools/agent-harness-ui-surface-types.ts +46 -0
- package/src/tools/agent-harness-vibe-health.ts +105 -0
- package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
- package/src/tools/agent-harness-workspace-actions.ts +114 -8
- package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
- package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
- package/src/tools/agent-host-tool.ts +159 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
- package/src/tools/agent-learning-consolidation-core.ts +327 -0
- package/src/tools/agent-learning-consolidation-tool.ts +758 -0
- package/src/tools/agent-memory-tool.ts +214 -0
- package/src/tools/agent-model-compare-export.ts +315 -0
- package/src/tools/agent-model-compare-handoff.ts +592 -0
- package/src/tools/agent-model-compare-judgment.ts +633 -0
- package/src/tools/agent-model-compare-run.ts +722 -0
- package/src/tools/agent-model-compare-tool.ts +698 -0
- package/src/tools/agent-model-compare-types.ts +191 -0
- package/src/tools/agent-model-compare-utils.ts +93 -0
- package/src/tools/agent-models-tool.ts +208 -0
- package/src/tools/agent-operator-action-tool.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +643 -0
- package/src/tools/agent-personal-ops-tool.ts +197 -0
- package/src/tools/agent-policy-explanation.ts +415 -0
- package/src/tools/agent-research-report-tool.ts +608 -0
- package/src/tools/agent-research-runs-tool.ts +434 -0
- package/src/tools/agent-research-sources-tool.ts +443 -0
- package/src/tools/agent-research-tool.ts +687 -0
- package/src/tools/agent-review-packet-presets-core.ts +757 -0
- package/src/tools/agent-review-packet-presets-tool.ts +466 -0
- package/src/tools/agent-review-packet-share-tool.ts +305 -0
- package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
- package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
- package/src/tools/agent-route-planner-candidates-work.ts +251 -0
- package/src/tools/agent-route-planner-helpers.ts +667 -0
- package/src/tools/agent-route-planner.ts +185 -0
- package/src/tools/agent-route-tool.ts +105 -0
- package/src/tools/agent-schedule-edit-tool.ts +210 -0
- package/src/tools/agent-schedule-tool.ts +282 -0
- package/src/tools/agent-security-tool.ts +145 -0
- package/src/tools/agent-sessions-tool.ts +122 -0
- package/src/tools/agent-settings-import-tool.ts +104 -0
- package/src/tools/agent-settings-tool.ts +176 -0
- package/src/tools/agent-setup-tool.ts +226 -0
- package/src/tools/agent-support-tool.ts +122 -0
- package/src/tools/agent-terminal-process-tools.ts +167 -0
- package/src/tools/agent-tool-policy-guard-types.ts +77 -0
- package/src/tools/agent-tool-policy-guard.ts +110 -89
- package/src/tools/agent-vibe-tool.ts +297 -0
- package/src/tools/agent-work-plan-tool.ts +265 -6
- package/src/tools/agent-workspace-tool.ts +305 -0
- package/src/tools/artifact-archive.ts +169 -0
- package/src/tools/tool-definition-compaction.ts +36 -0
- package/src/tools/tool-execution-safety.ts +41 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
2
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
3
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
4
|
+
import { readArtifactStore } from './agent-harness-model-catalog.ts';
|
|
5
|
+
import { localStackFor } from './agent-harness-local-model-endpoints.ts';
|
|
6
|
+
import type { LocalModelBenchmarkEvidence, LocalModelBenchmarkPlan, LocalModelBenchmarkWinner, LocalModelRecipe } from './agent-harness-model-routing-types.ts';
|
|
7
|
+
import { readRecord, readString } from './agent-harness-model-routing-utils.ts';
|
|
8
|
+
|
|
9
|
+
export function isLocalModelBenchmarkArtifact(artifact: ArtifactDescriptor): boolean {
|
|
10
|
+
const purpose = readString(artifact.metadata.purpose);
|
|
11
|
+
if (purpose !== 'agent-model-compare') return false;
|
|
12
|
+
if (readString(artifact.metadata.benchmarkKind) === 'local-model-route') return true;
|
|
13
|
+
const promptPreview = readString(artifact.metadata.promptPreview).toLowerCase();
|
|
14
|
+
return promptPreview.includes('local model benchmark') || promptPreview.includes('benchmark this local route');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isModelCompareJudgmentArtifact(artifact: ArtifactDescriptor): boolean {
|
|
18
|
+
return readString(artifact.metadata.purpose) === 'agent-model-compare-judgment';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function benchmarkCreatedAt(artifact: ArtifactDescriptor): string | null {
|
|
22
|
+
const timestamp = typeof artifact.createdAt === 'number' ? artifact.createdAt : null;
|
|
23
|
+
return timestamp == null ? null : new Date(timestamp).toISOString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function describeLocalBenchmarkArtifact(artifact: ArtifactDescriptor): Record<string, unknown> {
|
|
27
|
+
return {
|
|
28
|
+
artifactId: artifact.id,
|
|
29
|
+
...(artifact.filename ? { filename: artifact.filename } : {}),
|
|
30
|
+
createdAt: benchmarkCreatedAt(artifact),
|
|
31
|
+
comparisonId: readString(artifact.metadata.comparisonId) || null,
|
|
32
|
+
promptPreview: previewHarnessText(readString(artifact.metadata.promptPreview) || 'local model benchmark', 120),
|
|
33
|
+
candidateCount: artifact.metadata.candidateCount ?? null,
|
|
34
|
+
completedCandidates: artifact.metadata.completedCandidates ?? null,
|
|
35
|
+
benchmarkKind: readString(artifact.metadata.benchmarkKind) || 'local-model-route',
|
|
36
|
+
reviewRoute: `agent_model_compare review artifactId:"${artifact.id}"`,
|
|
37
|
+
revealRoute: `agent_model_compare reveal artifactId:"${artifact.id}"`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function describeLocalBenchmarkJudgment(artifact: ArtifactDescriptor): Record<string, unknown> {
|
|
42
|
+
const winnerModel = readString(artifact.metadata.winnerModel);
|
|
43
|
+
const sourceArtifactId = readString(artifact.metadata.sourceArtifactId);
|
|
44
|
+
return {
|
|
45
|
+
artifactId: artifact.id,
|
|
46
|
+
...(artifact.filename ? { filename: artifact.filename } : {}),
|
|
47
|
+
createdAt: benchmarkCreatedAt(artifact),
|
|
48
|
+
judgmentId: readString(artifact.metadata.judgmentId) || null,
|
|
49
|
+
comparisonId: readString(artifact.metadata.comparisonId) || null,
|
|
50
|
+
sourceArtifactId: sourceArtifactId || null,
|
|
51
|
+
winnerBlindId: readString(artifact.metadata.winnerBlindId) || null,
|
|
52
|
+
revealIncludedInJudgment: artifact.metadata.revealIncludedInJudgment === true,
|
|
53
|
+
winnerModel: winnerModel || null,
|
|
54
|
+
winnerStack: winnerModel ? localStackFor(winnerModel) : null,
|
|
55
|
+
promptPreview: previewHarnessText(readString(artifact.metadata.promptPreview) || 'local model benchmark judgment', 120),
|
|
56
|
+
analyticsRoute: 'agent_model_compare analytics benchmarkKind:"local-model-route" includeReasons:true',
|
|
57
|
+
exportRoute: `agent_model_compare export artifactId:"${artifact.id}" confirm:true explicitUserRequest:"Export this local benchmark judgment."`,
|
|
58
|
+
applyRoute: winnerModel
|
|
59
|
+
? `agent_model_compare apply artifactId:"${artifact.id}" confirm:true explicitUserRequest:"Apply this revealed local benchmark winner."`
|
|
60
|
+
: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function localBenchmarkEvidence(
|
|
65
|
+
comparisons: readonly ArtifactDescriptor[],
|
|
66
|
+
judgments: readonly ArtifactDescriptor[],
|
|
67
|
+
storeAvailable: boolean,
|
|
68
|
+
): LocalModelBenchmarkEvidence {
|
|
69
|
+
if (!storeAvailable) {
|
|
70
|
+
return {
|
|
71
|
+
status: 'unavailable',
|
|
72
|
+
comparisonCount: 0,
|
|
73
|
+
completedCandidateCount: 0,
|
|
74
|
+
revealedJudgmentCount: 0,
|
|
75
|
+
hiddenJudgmentCount: 0,
|
|
76
|
+
winnerStacks: [],
|
|
77
|
+
winnerModels: [],
|
|
78
|
+
summary: 'Artifact history is unavailable in this runtime.',
|
|
79
|
+
confidence: 'estimated',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const winnerModels: LocalModelBenchmarkWinner[] = [];
|
|
83
|
+
let hiddenJudgmentCount = 0;
|
|
84
|
+
for (const judgment of judgments) {
|
|
85
|
+
const winnerModel = readString(judgment.metadata.winnerModel);
|
|
86
|
+
if (!winnerModel) {
|
|
87
|
+
hiddenJudgmentCount += 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const sourceArtifactId = readString(judgment.metadata.sourceArtifactId);
|
|
91
|
+
winnerModels.push({
|
|
92
|
+
judgmentArtifactId: judgment.id,
|
|
93
|
+
sourceArtifactId: sourceArtifactId || null,
|
|
94
|
+
registryKey: winnerModel,
|
|
95
|
+
stack: localStackFor(winnerModel),
|
|
96
|
+
promptPreview: previewHarnessText(readString(judgment.metadata.promptPreview) || 'local model benchmark judgment', 120),
|
|
97
|
+
reviewRoute: sourceArtifactId
|
|
98
|
+
? `agent_model_compare review artifactId:"${sourceArtifactId}"`
|
|
99
|
+
: 'agent_model_compare review',
|
|
100
|
+
exportRoute: `agent_model_compare export artifactId:"${judgment.id}" confirm:true explicitUserRequest:"Export this local benchmark judgment."`,
|
|
101
|
+
applyRoute: `agent_model_compare apply artifactId:"${judgment.id}" confirm:true explicitUserRequest:"Apply this revealed local benchmark winner."`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const winnerStacks = [...new Set(winnerModels.map((winner) => winner.stack).filter((stack): stack is string => Boolean(stack)))].sort((a, b) => a.localeCompare(b));
|
|
105
|
+
const completedCandidateCount = comparisons.reduce((total, artifact) => {
|
|
106
|
+
const value = artifact.metadata.completedCandidates;
|
|
107
|
+
return total + (typeof value === 'number' && Number.isFinite(value) ? value : 0);
|
|
108
|
+
}, 0);
|
|
109
|
+
const status: LocalModelBenchmarkEvidence['status'] = winnerModels.length > 0
|
|
110
|
+
? 'reviewed-winner'
|
|
111
|
+
: comparisons.length > 0
|
|
112
|
+
? 'comparison-saved'
|
|
113
|
+
: 'unmeasured';
|
|
114
|
+
return {
|
|
115
|
+
status,
|
|
116
|
+
comparisonCount: comparisons.length,
|
|
117
|
+
completedCandidateCount,
|
|
118
|
+
revealedJudgmentCount: winnerModels.length,
|
|
119
|
+
hiddenJudgmentCount,
|
|
120
|
+
winnerStacks,
|
|
121
|
+
winnerModels,
|
|
122
|
+
summary: winnerModels.length > 0
|
|
123
|
+
? `Reviewed benchmark winner(s): ${winnerModels.map((winner) => winner.registryKey).join(', ')}.`
|
|
124
|
+
: comparisons.length > 0
|
|
125
|
+
? 'Saved local benchmark comparison exists; save a revealed judgment before route recommendations.'
|
|
126
|
+
: 'No saved local benchmark comparison has been recorded yet.',
|
|
127
|
+
confidence: winnerModels.length > 0 ? 'measured' : 'estimated',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function localModelBenchmarkHistory(context: CommandContext, includeParameters: boolean): Record<string, unknown> {
|
|
132
|
+
const store = readArtifactStore(context);
|
|
133
|
+
if (!store?.list) {
|
|
134
|
+
const evidence = localBenchmarkEvidence([], [], false);
|
|
135
|
+
return {
|
|
136
|
+
status: 'unavailable',
|
|
137
|
+
count: 0,
|
|
138
|
+
reason: 'Artifact history is unavailable in this runtime.',
|
|
139
|
+
saveRoute: 'agent_model_compare run benchmarkKind:"local-model-route" taskType:"local-model-route" confirm:true explicitUserRequest:"..."',
|
|
140
|
+
evidence,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const allArtifacts = store.list(100);
|
|
144
|
+
const artifacts = allArtifacts.filter(isLocalModelBenchmarkArtifact)
|
|
145
|
+
.slice(0, includeParameters ? 10 : 3);
|
|
146
|
+
const localComparisonIds = new Set(artifacts.map((artifact) => artifact.id));
|
|
147
|
+
const judgments = allArtifacts
|
|
148
|
+
.filter((artifact) => {
|
|
149
|
+
if (!isModelCompareJudgmentArtifact(artifact)) return false;
|
|
150
|
+
const sourceArtifactId = readString(artifact.metadata.sourceArtifactId);
|
|
151
|
+
if (sourceArtifactId && localComparisonIds.has(sourceArtifactId)) return true;
|
|
152
|
+
return readString(artifact.metadata.promptPreview).toLowerCase().includes('local model benchmark');
|
|
153
|
+
})
|
|
154
|
+
.slice(0, includeParameters ? 10 : 3);
|
|
155
|
+
const evidence = localBenchmarkEvidence(artifacts, judgments, true);
|
|
156
|
+
return {
|
|
157
|
+
status: artifacts.length > 0 ? 'history-found' : 'no-history',
|
|
158
|
+
count: artifacts.length,
|
|
159
|
+
artifacts: artifacts.map(describeLocalBenchmarkArtifact),
|
|
160
|
+
judgments: judgments.map(describeLocalBenchmarkJudgment),
|
|
161
|
+
evidence,
|
|
162
|
+
nextAction: artifacts.length > 0
|
|
163
|
+
? evidence.status === 'reviewed-winner'
|
|
164
|
+
? 'Use the revealed saved judgment as evidence only; apply/update still needs a separate confirmed user request.'
|
|
165
|
+
: `Review saved local benchmark ${artifacts[0]!.id}, then save a revealed judgment before recommending any default-model change.`
|
|
166
|
+
: 'Run the setupPlan benchmark prompt and save the comparison artifact before recommending any default-model change.',
|
|
167
|
+
analyticsRoute: 'agent_model_compare analytics benchmarkKind:"local-model-route" includeReasons:true',
|
|
168
|
+
saveRoute: 'agent_model_compare run benchmarkKind:"local-model-route" taskType:"local-model-route" confirm:true explicitUserRequest:"..."',
|
|
169
|
+
policy: 'Benchmark history is read-only evidence. Route changes still require a separate revealed judgment and confirmed apply/update action.',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function localModelBenchmarkPlan(recipe: LocalModelRecipe): LocalModelBenchmarkPlan {
|
|
174
|
+
return {
|
|
175
|
+
status: 'plan-ready',
|
|
176
|
+
prompt: [
|
|
177
|
+
'Benchmark this local route on one practical task:',
|
|
178
|
+
'1. summarize the current project goal in five bullets,',
|
|
179
|
+
'2. identify one likely setup risk,',
|
|
180
|
+
'3. propose one next action with a command or route.',
|
|
181
|
+
].join(' '),
|
|
182
|
+
measurements: [
|
|
183
|
+
'time to first useful token',
|
|
184
|
+
'total response latency',
|
|
185
|
+
'whether the answer followed the requested structure',
|
|
186
|
+
'whether the model handled project-specific nouns without hallucinating',
|
|
187
|
+
'whether the route supports the needed context window and tool workflow',
|
|
188
|
+
],
|
|
189
|
+
workspaceActionRoute: 'agent_harness mode:"run_workspace_action" actionId:"account-run-local-model-benchmark" confirm:true fields.confirm:"yes" fields.modelRefs:"<local-route>,<baseline-route>" explicitUserRequest:"Compare this local model route before making it default."',
|
|
190
|
+
compareRoute: `agent_model_compare run prompt:"local model benchmark: ${recipe.label}" benchmarkKind:"local-model-route" taskType:"local-model-route" confirm:true explicitUserRequest:"Compare this local model route before making it default."`,
|
|
191
|
+
refreshRoute: 'agent_harness mode:"run_command" command:"/refresh-models" confirm:true explicitUserRequest:"Refresh model catalog, benchmarks, and token limits after local model setup."',
|
|
192
|
+
notes: [
|
|
193
|
+
'Use the workspace action when the user wants a form with benchmark defaults; use compareRoute when the model already has exact modelRefs.',
|
|
194
|
+
'Run the same prompt against the local route and a baseline route before selecting a winner.',
|
|
195
|
+
'Keep benchmark notes in the saved comparison artifact before changing the default model.',
|
|
196
|
+
'Do not treat cached public benchmark scores as a substitute for this local latency and fit check.',
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
3
|
+
import { localModelDetection, localModelServerHealthMap } from './agent-harness-local-model-endpoints.ts';
|
|
4
|
+
import { localHardwareProfile, localRecipeReadinessScore, localRecipeStackId, scoreLocalModelRecipe } from './agent-harness-model-readiness.ts';
|
|
5
|
+
import { localModelBenchmarkHistory, localModelBenchmarkPlan } from './agent-harness-local-model-benchmarks.ts';
|
|
6
|
+
import type { LocalModelDetection, LocalModelHardwareProfile, LocalModelRecipe, LocalModelRecipeFit, LocalModelSetupPlan, LocalModelBenchmarkEvidence } from './agent-harness-model-routing-types.ts';
|
|
7
|
+
import { readRecord, readString } from './agent-harness-model-routing-utils.ts';
|
|
8
|
+
|
|
9
|
+
export function localModelDownloadGuidance(recipe: LocalModelRecipe, hardware: LocalModelHardwareProfile): readonly string[] {
|
|
10
|
+
if (recipe.id === 'ollama') {
|
|
11
|
+
const model = hardware.ramGb >= 32 ? 'qwen2.5-coder:14b' : 'qwen2.5-coder:7b';
|
|
12
|
+
return [
|
|
13
|
+
'Install Ollama from the vendor package or package manager.',
|
|
14
|
+
'Start the Ollama service before refreshing Agent models.',
|
|
15
|
+
`Suggested first pull: ollama pull ${model}`,
|
|
16
|
+
`Smoke test: ollama run ${model} "Say ready in one sentence."`,
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
if (recipe.id === 'llama-cpp') {
|
|
20
|
+
return [
|
|
21
|
+
'Choose a GGUF model that fits available RAM; prefer Q4/Q5 quantization on constrained systems.',
|
|
22
|
+
'Download the GGUF from the model publisher or a trusted mirror.',
|
|
23
|
+
'Start an OpenAI-compatible server: llama-server -m /path/to/model.gguf --host 127.0.0.1 --port 8080',
|
|
24
|
+
'Smoke test the /v1/models endpoint before adding the provider route.',
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
if (recipe.id === 'vllm') {
|
|
28
|
+
return [
|
|
29
|
+
'Verify CUDA driver, GPU memory, and Python environment before installing vLLM.',
|
|
30
|
+
'Install vLLM in an isolated environment.',
|
|
31
|
+
'Serve a small first model: python -m vllm.entrypoints.openai.api_server --model Qwen/Qwen2.5-Coder-7B-Instruct --host 127.0.0.1 --port 8000',
|
|
32
|
+
'Smoke test /v1/models before adding the provider route.',
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
return [
|
|
36
|
+
'Start the local OpenAI-compatible server in its own app or service.',
|
|
37
|
+
'Confirm the server exposes /v1/models and a private localhost or trusted LAN base URL.',
|
|
38
|
+
'Use the server app to download or load the model before adding it to Agent.',
|
|
39
|
+
'Keep LAN endpoints private unless the user explicitly intends shared access.',
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function localModelProviderRoutes(recipe: LocalModelRecipe): readonly string[] {
|
|
44
|
+
if (recipe.id === 'ollama') {
|
|
45
|
+
return [
|
|
46
|
+
'agent_harness mode:"open_ui_surface" surfaceId:"provider-picker" confirm:true explicitUserRequest:"Select the discovered Ollama route."',
|
|
47
|
+
'agent_harness mode:"run_command" command:"/refresh-models" confirm:true explicitUserRequest:"Refresh models after starting Ollama."',
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
if (recipe.id === 'llama-cpp') {
|
|
51
|
+
return [
|
|
52
|
+
'agent_harness mode:"run_command" command:"/provider add llama-cpp-local http://127.0.0.1:8080/v1 local --yes" confirm:true explicitUserRequest:"Add a local llama.cpp OpenAI-compatible provider."',
|
|
53
|
+
'agent_harness mode:"run_command" command:"/refresh-models" confirm:true explicitUserRequest:"Refresh models after starting llama.cpp."',
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
if (recipe.id === 'vllm') {
|
|
57
|
+
return [
|
|
58
|
+
'agent_harness mode:"run_command" command:"/provider add vllm-local http://127.0.0.1:8000/v1 local --yes" confirm:true explicitUserRequest:"Add a local vLLM OpenAI-compatible provider."',
|
|
59
|
+
'agent_harness mode:"run_command" command:"/refresh-models" confirm:true explicitUserRequest:"Refresh models after starting vLLM."',
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
'agent_harness mode:"run_command" command:"/provider add local-openai http://127.0.0.1:1234/v1 local --yes" confirm:true explicitUserRequest:"Add a local OpenAI-compatible provider."',
|
|
64
|
+
'agent_harness mode:"run_command" command:"/refresh-models" confirm:true explicitUserRequest:"Refresh models after starting the local server."',
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function localModelSetupPlan(
|
|
69
|
+
recipe: LocalModelRecipe,
|
|
70
|
+
hardware: LocalModelHardwareProfile,
|
|
71
|
+
detection: LocalModelDetection,
|
|
72
|
+
fit: LocalModelRecipeFit,
|
|
73
|
+
): LocalModelSetupPlan {
|
|
74
|
+
const stackId = localRecipeStackId(recipe);
|
|
75
|
+
const detected = detection.stacks.includes(stackId);
|
|
76
|
+
return {
|
|
77
|
+
status: detected ? 'detected' : fit.level === 'weak' ? 'needs-hardware-review' : 'ready-to-try',
|
|
78
|
+
priority: fit.score,
|
|
79
|
+
downloadGuidance: localModelDownloadGuidance(recipe, hardware),
|
|
80
|
+
providerRoutes: localModelProviderRoutes(recipe),
|
|
81
|
+
benchmarkPlan: localModelBenchmarkPlan(recipe),
|
|
82
|
+
confirmationBoundary: 'The plan is read-only guidance. Installs, downloads, server starts, provider edits, refreshes, comparisons, and route changes require explicit user action or confirmation.',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function localModelRecipes(): readonly LocalModelRecipe[] {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
id: 'ollama',
|
|
90
|
+
label: 'Ollama',
|
|
91
|
+
fit: 'Best first local route for most users.',
|
|
92
|
+
bestFor: 'Fast setup, chat, coding help, private everyday assistant work.',
|
|
93
|
+
hardware: 'Usable on modern CPU or Apple Silicon; 16 GB RAM is comfortable for 7B/8B quantized models, 32 GB+ for 14B/32B.',
|
|
94
|
+
setup: [
|
|
95
|
+
'Install Ollama and start the local service.',
|
|
96
|
+
'Pull one practical model, such as qwen2.5-coder:7b or llama3.1:8b.',
|
|
97
|
+
'Refresh models in GoodVibes Agent, then choose the discovered ollama:<model> route.',
|
|
98
|
+
],
|
|
99
|
+
modelExamples: ['qwen2.5-coder:7b', 'llama3.1:8b', 'mistral:7b'],
|
|
100
|
+
cautions: ['Large models may run slowly without enough memory.', 'Use vLLM instead when the goal is multi-user throughput.'],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'llama-cpp',
|
|
104
|
+
label: 'llama.cpp',
|
|
105
|
+
fit: 'Best low-dependency offline route.',
|
|
106
|
+
bestFor: 'CPU/Metal inference, GGUF files, portable offline use, constrained machines.',
|
|
107
|
+
hardware: 'Works without a GPU; use Q4/Q5 GGUF files for small systems and larger quantization only when memory allows.',
|
|
108
|
+
setup: [
|
|
109
|
+
'Download a GGUF model that fits memory.',
|
|
110
|
+
'Run llama-server with an OpenAI-compatible endpoint.',
|
|
111
|
+
'Add or refresh the local OpenAI-compatible provider, then select it in the model picker.',
|
|
112
|
+
],
|
|
113
|
+
modelExamples: ['Qwen2.5-Coder 7B Instruct GGUF', 'Llama 3.1 8B Instruct GGUF', 'Phi-3 Mini GGUF'],
|
|
114
|
+
cautions: ['Manual model-file choice matters more than with Ollama.', 'Throughput is lower than GPU serving.'],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'vllm',
|
|
118
|
+
label: 'vLLM',
|
|
119
|
+
fit: 'Best high-throughput local or LAN server route.',
|
|
120
|
+
bestFor: 'NVIDIA GPU serving, batching, OpenAI-compatible APIs, team/shared local models.',
|
|
121
|
+
hardware: 'Prefer CUDA GPUs; 16 GB VRAM can serve small quantized models, 24-48 GB+ is better for larger coder models.',
|
|
122
|
+
setup: [
|
|
123
|
+
'Install vLLM in a CUDA-ready Python environment.',
|
|
124
|
+
'Serve a model with the OpenAI-compatible API server.',
|
|
125
|
+
'Add the endpoint as a custom provider and select that route.',
|
|
126
|
+
],
|
|
127
|
+
modelExamples: ['Qwen2.5-Coder 7B Instruct', 'Llama 3.1 8B Instruct', 'DeepSeek Coder V2 Lite'],
|
|
128
|
+
cautions: ['Not the easiest first setup.', 'GPU drivers and model memory limits are the common failure points.'],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'openai-compatible-local',
|
|
132
|
+
label: 'Local OpenAI-compatible server',
|
|
133
|
+
fit: 'Best when the user already has LM Studio, LocalAI, TGI, or another local endpoint.',
|
|
134
|
+
bestFor: 'Reusing an existing localhost or LAN model server through one familiar API.',
|
|
135
|
+
hardware: 'Depends on the server backend; verify context window and memory in the serving app first.',
|
|
136
|
+
setup: [
|
|
137
|
+
'Start the local server and confirm its /v1/models endpoint works.',
|
|
138
|
+
'Add a custom provider with the local base URL.',
|
|
139
|
+
'Refresh models and select the discovered route.',
|
|
140
|
+
],
|
|
141
|
+
modelExamples: ['LM Studio loaded model', 'LocalAI model', 'TGI-served model'],
|
|
142
|
+
cautions: ['Some servers omit context-window metadata.', 'Keep LAN endpoints private unless explicitly intended.'],
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function describeLocalModelRecipe(
|
|
148
|
+
recipe: LocalModelRecipe,
|
|
149
|
+
detection: LocalModelDetection,
|
|
150
|
+
hardware: LocalModelHardwareProfile,
|
|
151
|
+
benchmarkEvidence: LocalModelBenchmarkEvidence,
|
|
152
|
+
includeParameters: boolean,
|
|
153
|
+
): Record<string, unknown> {
|
|
154
|
+
const stackId = localRecipeStackId(recipe);
|
|
155
|
+
const detected = detection.stacks.includes(stackId);
|
|
156
|
+
const fit = scoreLocalModelRecipe(recipe, hardware, detection);
|
|
157
|
+
const readiness = localRecipeReadinessScore(recipe, fit, detected, benchmarkEvidence);
|
|
158
|
+
return {
|
|
159
|
+
id: recipe.id,
|
|
160
|
+
label: recipe.label,
|
|
161
|
+
fit: recipe.fit,
|
|
162
|
+
fitScore: fit.score,
|
|
163
|
+
fitLevel: fit.level,
|
|
164
|
+
readinessScore: readiness.score,
|
|
165
|
+
readinessLevel: readiness.level,
|
|
166
|
+
readiness: includeParameters
|
|
167
|
+
? readiness
|
|
168
|
+
: {
|
|
169
|
+
score: readiness.score,
|
|
170
|
+
level: readiness.level,
|
|
171
|
+
confidence: readiness.confidence,
|
|
172
|
+
nextStep: readiness.nextStep,
|
|
173
|
+
},
|
|
174
|
+
bestFor: recipe.bestFor,
|
|
175
|
+
hardware: previewHarnessText(recipe.hardware, includeParameters ? 180 : 96),
|
|
176
|
+
hardwareMatched: fit.reasons.slice(0, includeParameters ? 6 : 3),
|
|
177
|
+
detected,
|
|
178
|
+
modelRoute: 'models action:"status" or agent_harness mode:"open_ui_surface"',
|
|
179
|
+
...(includeParameters ? {
|
|
180
|
+
setup: recipe.setup,
|
|
181
|
+
modelExamples: recipe.modelExamples,
|
|
182
|
+
cautions: recipe.cautions,
|
|
183
|
+
setupPlan: localModelSetupPlan(recipe, hardware, detection, fit),
|
|
184
|
+
} : {}),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function localModelCookbook(context: CommandContext, includeParameters: boolean): Record<string, unknown> {
|
|
189
|
+
const detection = localModelDetection(context);
|
|
190
|
+
const hardwareProfile = localHardwareProfile();
|
|
191
|
+
const benchmarkHistory = localModelBenchmarkHistory(context, includeParameters);
|
|
192
|
+
const benchmarkEvidence = readRecord(benchmarkHistory.evidence) as unknown as LocalModelBenchmarkEvidence;
|
|
193
|
+
const localServerHealth = localModelServerHealthMap(context, includeParameters);
|
|
194
|
+
const recipes = localModelRecipes()
|
|
195
|
+
.map((recipe) => describeLocalModelRecipe(recipe, detection, hardwareProfile, benchmarkEvidence, includeParameters))
|
|
196
|
+
.sort((left, right) => Number(readRecord(right).fitScore ?? 0) - Number(readRecord(left).fitScore ?? 0));
|
|
197
|
+
const topRecipe = readRecord(recipes[0]);
|
|
198
|
+
const topLabel = readString(topRecipe.label) || 'Ollama';
|
|
199
|
+
const nextActions = [
|
|
200
|
+
localServerHealth.endpointCount > 0
|
|
201
|
+
? `Smoke test detected local endpoint(s): ${localServerHealth.endpoints[0]?.modelsUrl ?? 'see localServerHealth.endpoints'}.`
|
|
202
|
+
: detection.stacks.length > 0
|
|
203
|
+
? `Inspect detected local route(s): ${detection.modelRoutes.join(', ') || detection.providerIds.join(', ')}.`
|
|
204
|
+
: `Start with ${topLabel}: inspect its setupPlan, then install/start the server outside Agent.`,
|
|
205
|
+
'Refresh the model catalog after the local server is running.',
|
|
206
|
+
'Run the local benchmark workspace action or saved model comparison before changing the default route.',
|
|
207
|
+
];
|
|
208
|
+
return {
|
|
209
|
+
status: detection.stacks.length > 0
|
|
210
|
+
? 'detected-local-route'
|
|
211
|
+
: localServerHealth.endpointCount > 0
|
|
212
|
+
? 'detected-local-server'
|
|
213
|
+
: 'recommendations-only',
|
|
214
|
+
recommendation: detection.stacks.includes('ollama')
|
|
215
|
+
? 'Use the discovered Ollama route first unless throughput requirements point to vLLM.'
|
|
216
|
+
: `Best current fit: ${topLabel}. Ollama remains the easiest first local route; use llama.cpp for offline GGUF files or vLLM for GPU throughput.`,
|
|
217
|
+
hardwareProfile,
|
|
218
|
+
detected: detection,
|
|
219
|
+
localServerHealth,
|
|
220
|
+
recipes,
|
|
221
|
+
benchmarkHistory,
|
|
222
|
+
readinessRubric: {
|
|
223
|
+
score: '0-100 estimated readiness for autonomous Agent work.',
|
|
224
|
+
confidence: 'estimated until a live route benchmark records latency and task fit on this machine',
|
|
225
|
+
dimensions: [
|
|
226
|
+
{ id: 'latency', weight: 20 },
|
|
227
|
+
{ id: 'context-window', weight: 20 },
|
|
228
|
+
{ id: 'tool-support', weight: 20 },
|
|
229
|
+
{ id: 'vision', weight: 10 },
|
|
230
|
+
{ id: 'cost', weight: 15 },
|
|
231
|
+
{ id: 'privacy', weight: 15 },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
nextActions,
|
|
235
|
+
modelRoute: 'models action:"local"',
|
|
236
|
+
policy: 'Read-only hardware-aware cookbook. Readiness scores are estimated until a live benchmark is recorded. Setup plans include download/start guidance and a confirmed benchmark action route, but installs, downloads, live benchmarks, provider edits, and route changes stay separate visible user actions.',
|
|
237
|
+
};
|
|
238
|
+
}
|