@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,508 @@
|
|
|
1
|
+
import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
2
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
3
|
+
import { requireShellPaths } from '../input/commands/runtime-services.ts';
|
|
4
|
+
import { readOnboardingCompletionMarker } from '../runtime/onboarding/index.ts';
|
|
5
|
+
import { clearSetupWizardCheckpoint, readSetupWizardCheckpoint, saveSetupWizardCheckpoint } from '../agent/setup-wizard-checkpoint.ts';
|
|
6
|
+
import { DEFAULT_AGENT_SETUP_WIZARD_CLEAR_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_INSPECT_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_MARK_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_REVIEW_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_RERUN_SMOKE_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_SAVE_SMOKE_ROUTE, buildAgentSetupWizard, emptyAgentSetupSmokeHistory, emptyAgentSetupWizardCheckpoint, type AgentSetupWizard, type AgentSetupWizardCheckpoint, type AgentSetupWizardSmokeHistory, type AgentSetupWizardSourceItem } from '../agent/setup-wizard.ts';
|
|
7
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
8
|
+
import { readFieldMap, readString, safeIso, setupSmokeArtifactStore } from './agent-harness-setup-posture-utils.ts';
|
|
9
|
+
import { setupHandoffsForItem } from './agent-harness-setup-handoffs.ts';
|
|
10
|
+
import type { SetupConnectedHostAuthPosture, SetupInstallSmokeCheck, SetupInstallSmokePlan, SetupInstallSmokeRunSummary, SetupPlanItem, SetupServiceProbe, SetupSmokeEvidenceField } from './agent-harness-setup-posture-types.ts';
|
|
11
|
+
import type { OnboardingStep1CapabilityItem } from '../runtime/onboarding/index.ts';
|
|
12
|
+
|
|
13
|
+
export function installSmokeCheckStatus(ready: boolean): 'ready' | 'blocked' {
|
|
14
|
+
return ready ? 'ready' : 'blocked';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function installSmokePlan(
|
|
18
|
+
providerAccess: OnboardingStep1CapabilityItem,
|
|
19
|
+
serviceProbe: SetupServiceProbe,
|
|
20
|
+
authPosture: SetupConnectedHostAuthPosture,
|
|
21
|
+
): SetupInstallSmokePlan {
|
|
22
|
+
const hostReady = serviceProbe.status === 'reachable';
|
|
23
|
+
const authReady = authPosture.operatorToken.usable;
|
|
24
|
+
const modelReady = providerAccess.selected;
|
|
25
|
+
const checks: SetupInstallSmokeCheck[] = [
|
|
26
|
+
{
|
|
27
|
+
id: 'agent-binary',
|
|
28
|
+
label: 'Agent binary starts',
|
|
29
|
+
status: 'user-run',
|
|
30
|
+
evidence: 'The installed package binary should answer version/help/status without exposing secrets.',
|
|
31
|
+
route: 'goodvibes-agent --version && goodvibes-agent status --json',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'connected-host-status',
|
|
35
|
+
label: 'Connected host reachable',
|
|
36
|
+
status: installSmokeCheckStatus(hostReady),
|
|
37
|
+
evidence: `Runtime probe is ${serviceProbe.status} at ${serviceProbe.binding}.`,
|
|
38
|
+
route: 'host action:"status" includeParameters:true',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'connected-host-auth',
|
|
42
|
+
label: 'Connected-host operator auth usable',
|
|
43
|
+
status: installSmokeCheckStatus(authReady),
|
|
44
|
+
evidence: authReady
|
|
45
|
+
? `Operator token is usable (${authPosture.operatorToken.fingerprint ?? 'fingerprint unavailable'}).`
|
|
46
|
+
: `Operator token is ${authPosture.operatorToken.present ? 'present but not usable' : 'missing'} at ${authPosture.operatorToken.path}.`,
|
|
47
|
+
route: 'setup action:"item" setupItemId:"connected-host-auth"',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'provider-model',
|
|
51
|
+
label: 'Provider/model route selected',
|
|
52
|
+
status: installSmokeCheckStatus(modelReady),
|
|
53
|
+
evidence: modelReady ? 'Provider/model access is selected in onboarding state.' : 'Provider/model access is not selected yet.',
|
|
54
|
+
route: 'models action:"status" includeParameters:true',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'setup-posture',
|
|
58
|
+
label: 'Setup posture reviewed',
|
|
59
|
+
status: 'user-run',
|
|
60
|
+
evidence: 'Setup posture should show no unresolved autonomy blockers before ongoing work.',
|
|
61
|
+
route: DEFAULT_AGENT_SETUP_WIZARD_REVIEW_ROUTE,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'first-assistant-turn',
|
|
65
|
+
label: 'First assistant turn responds',
|
|
66
|
+
status: modelReady ? 'user-run' : 'blocked',
|
|
67
|
+
evidence: modelReady
|
|
68
|
+
? 'Ask the main assistant for a short ready response after model routing is selected.'
|
|
69
|
+
: 'A first assistant turn needs a provider/model route first.',
|
|
70
|
+
route: 'Ask the assistant: "Say ready in one sentence and list the active model route."',
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
return {
|
|
74
|
+
status: hostReady && authReady && modelReady ? 'ready-to-run' : 'blocked',
|
|
75
|
+
source: 'GoodVibes Agent installed package plus connected GoodVibes host',
|
|
76
|
+
checks,
|
|
77
|
+
successCriteria: [
|
|
78
|
+
'The Agent binary starts and reports status without printing connected-host tokens.',
|
|
79
|
+
'The connected GoodVibes host is reachable from Agent.',
|
|
80
|
+
'Connected-host operator auth is usable or the user has an explicit pairing handoff.',
|
|
81
|
+
'A provider/model route is selected for normal assistant turns.',
|
|
82
|
+
'The first assistant turn responds in the main conversation.',
|
|
83
|
+
],
|
|
84
|
+
policy: 'Install smoke is read-only, token-safe guidance. Agent does not run package, host, or shell smoke commands implicitly; use explicit user-run commands or confirmed routes only.',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function installSmokeSignals(plan: SetupInstallSmokePlan): readonly string[] {
|
|
89
|
+
return [
|
|
90
|
+
`install smoke: ${plan.status}`,
|
|
91
|
+
...plan.checks.map((check) => `${check.id}: ${check.status}`),
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const SETUP_SMOKE_EVIDENCE_FIELD_DEFINITIONS: readonly { readonly id: string; readonly label: string }[] = [
|
|
96
|
+
{ id: 'agentBinaryOutput', label: 'Agent binary output' },
|
|
97
|
+
{ id: 'statusJson', label: 'Agent status JSON' },
|
|
98
|
+
{ id: 'connectedHostStatusOutput', label: 'Connected-host status output' },
|
|
99
|
+
{ id: 'setupPostureOutput', label: 'Setup posture output' },
|
|
100
|
+
{ id: 'firstAssistantTurn', label: 'First assistant turn' },
|
|
101
|
+
{ id: 'notes', label: 'Operator notes' },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
export const MAX_SETUP_SMOKE_FIELD_CHARS = 8_000;
|
|
105
|
+
|
|
106
|
+
export function redactSetupSmokeEvidence(input: string): string {
|
|
107
|
+
const bounded = input.slice(0, MAX_SETUP_SMOKE_FIELD_CHARS);
|
|
108
|
+
return bounded
|
|
109
|
+
.replace(/(\bauthorization\s*[:=]\s*)(?:bearer\s+)?[^\s,;]+/gi, '$1<redacted>')
|
|
110
|
+
.replace(/("?\b(?:api[-_]?key|apikey|token|secret|password|credential)\b"?\s*:\s*)("[^"]*"|[^\s,}]+)/gi, '$1"<redacted>"')
|
|
111
|
+
.replace(/(\b(?:api[-_]?key|apikey|token|secret|password|credential)\b\s*=\s*)[^\s&]+/gi, '$1<redacted>')
|
|
112
|
+
.replace(/([?&](?:api[-_]?key|apikey|token|secret|password|credential)=)[^&\s]+/gi, '$1<redacted>');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function setupSmokeEvidenceFields(fields: unknown): readonly SetupSmokeEvidenceField[] {
|
|
116
|
+
const values = readFieldMap(fields);
|
|
117
|
+
return SETUP_SMOKE_EVIDENCE_FIELD_DEFINITIONS
|
|
118
|
+
.map((definition) => {
|
|
119
|
+
const raw = values[definition.id]?.trim() ?? '';
|
|
120
|
+
return raw ? { ...definition, value: redactSetupSmokeEvidence(raw) } : null;
|
|
121
|
+
})
|
|
122
|
+
.filter((entry): entry is SetupSmokeEvidenceField => entry !== null);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function safeSetupSmokeFilename(capturedAt: string): string {
|
|
126
|
+
return `setup-smoke-${capturedAt.replace(/[:.]/g, '-').replace(/[^a-zA-Z0-9-]+/g, '-')}.md`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function setupSmokeEvidenceMarkdown(input: {
|
|
130
|
+
readonly capturedAt: string;
|
|
131
|
+
readonly explicitUserRequest: string;
|
|
132
|
+
readonly smokePlan: SetupInstallSmokePlan;
|
|
133
|
+
readonly summary: SetupInstallSmokeRunSummary;
|
|
134
|
+
readonly blockedChecks: readonly string[];
|
|
135
|
+
readonly userRunChecks: readonly string[];
|
|
136
|
+
readonly evidenceFields: readonly SetupSmokeEvidenceField[];
|
|
137
|
+
}): string {
|
|
138
|
+
const checkLines = input.smokePlan.checks.map((check) => `- ${check.id}: ${check.status} - ${redactSetupSmokeEvidence(check.evidence)}`);
|
|
139
|
+
const evidenceSections = input.evidenceFields.map((field) => [
|
|
140
|
+
`## ${field.label}`,
|
|
141
|
+
'',
|
|
142
|
+
'```text',
|
|
143
|
+
field.value,
|
|
144
|
+
'```',
|
|
145
|
+
].join('\n'));
|
|
146
|
+
return [
|
|
147
|
+
'# GoodVibes Agent Setup Smoke Evidence',
|
|
148
|
+
'',
|
|
149
|
+
`Captured: ${input.capturedAt}`,
|
|
150
|
+
`Explicit user request: ${redactSetupSmokeEvidence(input.explicitUserRequest)}`,
|
|
151
|
+
`Smoke status: ${input.smokePlan.status}`,
|
|
152
|
+
`Ready checks: ${input.summary.ready}`,
|
|
153
|
+
`Blocked checks: ${input.blockedChecks.join(', ') || 'none'}`,
|
|
154
|
+
`User-run checks: ${input.userRunChecks.join(', ') || 'none'}`,
|
|
155
|
+
'',
|
|
156
|
+
'## Check Statuses',
|
|
157
|
+
'',
|
|
158
|
+
...checkLines,
|
|
159
|
+
'',
|
|
160
|
+
'## Success Criteria',
|
|
161
|
+
'',
|
|
162
|
+
...input.smokePlan.successCriteria.map((criterion) => `- ${criterion}`),
|
|
163
|
+
'',
|
|
164
|
+
...evidenceSections,
|
|
165
|
+
'',
|
|
166
|
+
'## Policy',
|
|
167
|
+
'',
|
|
168
|
+
input.smokePlan.policy,
|
|
169
|
+
'Evidence fields are redacted before storage. Agent does not run package, host, or shell smoke commands implicitly.',
|
|
170
|
+
].join('\n');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function saveSetupSmokeArtifact(input: {
|
|
174
|
+
readonly context: CommandContext;
|
|
175
|
+
readonly capturedAt: string;
|
|
176
|
+
readonly explicitUserRequest: string;
|
|
177
|
+
readonly smokePlan: SetupInstallSmokePlan;
|
|
178
|
+
readonly summary: SetupInstallSmokeRunSummary;
|
|
179
|
+
readonly blockedChecks: readonly string[];
|
|
180
|
+
readonly userRunChecks: readonly string[];
|
|
181
|
+
readonly evidenceFields: readonly SetupSmokeEvidenceField[];
|
|
182
|
+
}): Promise<Record<string, unknown>> {
|
|
183
|
+
if (input.evidenceFields.length === 0) {
|
|
184
|
+
return {
|
|
185
|
+
status: 'not_requested',
|
|
186
|
+
reason: 'Pass fields with user-run smoke output to save a redacted setup evidence artifact.',
|
|
187
|
+
supportedFields: SETUP_SMOKE_EVIDENCE_FIELD_DEFINITIONS.map((field) => field.id),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const artifactStore = setupSmokeArtifactStore(input.context);
|
|
191
|
+
if (!artifactStore?.create) {
|
|
192
|
+
return {
|
|
193
|
+
status: 'unavailable',
|
|
194
|
+
reason: 'This runtime did not provide an artifact store with create support.',
|
|
195
|
+
evidenceFields: input.evidenceFields.map((field) => field.id),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const descriptor = await artifactStore.create({
|
|
199
|
+
kind: 'document',
|
|
200
|
+
mimeType: 'text/markdown',
|
|
201
|
+
filename: safeSetupSmokeFilename(input.capturedAt),
|
|
202
|
+
text: setupSmokeEvidenceMarkdown(input),
|
|
203
|
+
metadata: {
|
|
204
|
+
purpose: 'agent-setup-smoke-evidence',
|
|
205
|
+
source: 'agent-harness-run-setup-smoke',
|
|
206
|
+
capturedAt: input.capturedAt,
|
|
207
|
+
smokeStatus: input.smokePlan.status,
|
|
208
|
+
result: input.blockedChecks.length > 0 ? 'blocked' : 'ready-for-user-run',
|
|
209
|
+
blockedChecks: input.blockedChecks,
|
|
210
|
+
userRunChecks: input.userRunChecks,
|
|
211
|
+
evidenceFields: input.evidenceFields.map((field) => field.id),
|
|
212
|
+
checkStatuses: Object.fromEntries(input.smokePlan.checks.map((check) => [check.id, check.status])),
|
|
213
|
+
explicitUserRequest: redactSetupSmokeEvidence(input.explicitUserRequest),
|
|
214
|
+
redaction: 'token, secret, password, credential, authorization, and api-key-like values redacted before storage',
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
status: 'saved',
|
|
219
|
+
artifactId: descriptor.id,
|
|
220
|
+
filename: descriptor.filename ?? null,
|
|
221
|
+
mimeType: descriptor.mimeType,
|
|
222
|
+
sizeBytes: descriptor.sizeBytes,
|
|
223
|
+
purpose: 'agent-setup-smoke-evidence',
|
|
224
|
+
evidenceFields: input.evidenceFields.map((field) => ({
|
|
225
|
+
id: field.id,
|
|
226
|
+
preview: previewHarnessText(field.value, 120),
|
|
227
|
+
})),
|
|
228
|
+
inspectRoute: `agent_artifacts show artifactId:"${descriptor.id}" includeContent:false`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function readArtifactMetadataString(artifact: ArtifactDescriptor, key: string): string {
|
|
233
|
+
const value = artifact.metadata[key];
|
|
234
|
+
return typeof value === 'string' ? value : '';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function readArtifactMetadataStringArray(artifact: ArtifactDescriptor, key: string): readonly string[] {
|
|
238
|
+
const value = artifact.metadata[key];
|
|
239
|
+
return Array.isArray(value) ? value.map((entry) => readString(entry)).filter(Boolean) : [];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function setupSmokeEvidenceArtifacts(context: CommandContext): { readonly status: 'available'; readonly artifacts: readonly ArtifactDescriptor[] } | { readonly status: 'unavailable'; readonly reason: string } {
|
|
243
|
+
const artifactStore = setupSmokeArtifactStore(context);
|
|
244
|
+
if (!artifactStore?.list) {
|
|
245
|
+
return {
|
|
246
|
+
status: 'unavailable',
|
|
247
|
+
reason: 'Artifact list support is unavailable in this runtime.',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const artifacts = artifactStore.list(100)
|
|
251
|
+
.filter((artifact) => readArtifactMetadataString(artifact, 'purpose') === 'agent-setup-smoke-evidence')
|
|
252
|
+
.sort((left, right) => (right.createdAt ?? 0) - (left.createdAt ?? 0));
|
|
253
|
+
return { status: 'available', artifacts };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function setupSmokeEvidenceScore(artifact: ArtifactDescriptor): number {
|
|
257
|
+
const result = readArtifactMetadataString(artifact, 'result');
|
|
258
|
+
if (result === 'ready-for-user-run') return 2;
|
|
259
|
+
if (result === 'blocked') return 0;
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function setupSmokeEvidenceTrend(artifacts: readonly ArtifactDescriptor[]): string {
|
|
264
|
+
if (artifacts.length === 0) return 'none';
|
|
265
|
+
if (artifacts.length === 1) return 'first-run';
|
|
266
|
+
const latest = setupSmokeEvidenceScore(artifacts[0]!);
|
|
267
|
+
const previous = setupSmokeEvidenceScore(artifacts[1]!);
|
|
268
|
+
if (latest > previous) return 'improving';
|
|
269
|
+
if (latest < previous) return 'regressing';
|
|
270
|
+
const result = readArtifactMetadataString(artifacts[0]!, 'result');
|
|
271
|
+
if (result === 'ready-for-user-run') return 'unchanged-ready';
|
|
272
|
+
if (result === 'blocked') return 'unchanged-blocked';
|
|
273
|
+
return 'unchanged';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function setupSmokeBlockedCheckFrequency(artifacts: readonly ArtifactDescriptor[]): readonly Record<string, unknown>[] {
|
|
277
|
+
const counts = new Map<string, number>();
|
|
278
|
+
for (const artifact of artifacts) {
|
|
279
|
+
for (const check of readArtifactMetadataStringArray(artifact, 'blockedChecks')) {
|
|
280
|
+
counts.set(check, (counts.get(check) ?? 0) + 1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return [...counts.entries()]
|
|
284
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
|
285
|
+
.slice(0, 8)
|
|
286
|
+
.map(([checkId, count]) => ({ checkId, count }));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function describeSetupSmokeEvidenceArtifact(artifact: ArtifactDescriptor): Record<string, unknown> {
|
|
290
|
+
return {
|
|
291
|
+
artifactId: artifact.id,
|
|
292
|
+
filename: artifact.filename ?? null,
|
|
293
|
+
capturedAt: readArtifactMetadataString(artifact, 'capturedAt') || safeIso(artifact.createdAt),
|
|
294
|
+
result: readArtifactMetadataString(artifact, 'result') || 'unknown',
|
|
295
|
+
smokeStatus: readArtifactMetadataString(artifact, 'smokeStatus') || 'unknown',
|
|
296
|
+
blockedChecks: readArtifactMetadataStringArray(artifact, 'blockedChecks'),
|
|
297
|
+
userRunChecks: readArtifactMetadataStringArray(artifact, 'userRunChecks'),
|
|
298
|
+
evidenceFields: readArtifactMetadataStringArray(artifact, 'evidenceFields'),
|
|
299
|
+
inspectRoute: `agent_artifacts show artifactId:"${artifact.id}" includeContent:false`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function latestSetupSmokeEvidence(context: CommandContext): Record<string, unknown> {
|
|
304
|
+
const listed = setupSmokeEvidenceArtifacts(context);
|
|
305
|
+
if (listed.status === 'unavailable') return listed;
|
|
306
|
+
const artifacts = listed.artifacts;
|
|
307
|
+
const latest = artifacts[0];
|
|
308
|
+
if (!latest) {
|
|
309
|
+
return {
|
|
310
|
+
status: 'none',
|
|
311
|
+
reason: 'No saved setup smoke evidence artifact found.',
|
|
312
|
+
saveRoute: DEFAULT_AGENT_SETUP_WIZARD_SAVE_SMOKE_ROUTE,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
status: 'saved',
|
|
317
|
+
...describeSetupSmokeEvidenceArtifact(latest),
|
|
318
|
+
rerunRoute: DEFAULT_AGENT_SETUP_WIZARD_RERUN_SMOKE_ROUTE,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function setupSmokeEvidenceHistory(context: CommandContext): Record<string, unknown> {
|
|
323
|
+
const listed = setupSmokeEvidenceArtifacts(context);
|
|
324
|
+
if (listed.status === 'unavailable') return listed;
|
|
325
|
+
const artifacts = listed.artifacts;
|
|
326
|
+
if (artifacts.length === 0) {
|
|
327
|
+
return {
|
|
328
|
+
status: 'none',
|
|
329
|
+
total: 0,
|
|
330
|
+
trend: 'none',
|
|
331
|
+
reason: 'No saved setup smoke evidence artifact found.',
|
|
332
|
+
saveRoute: DEFAULT_AGENT_SETUP_WIZARD_SAVE_SMOKE_ROUTE,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const resultCounts = artifacts.reduce<Record<string, number>>((counts, artifact) => {
|
|
336
|
+
const result = readArtifactMetadataString(artifact, 'result') || 'unknown';
|
|
337
|
+
counts[result] = (counts[result] ?? 0) + 1;
|
|
338
|
+
return counts;
|
|
339
|
+
}, {});
|
|
340
|
+
return {
|
|
341
|
+
status: 'available',
|
|
342
|
+
total: artifacts.length,
|
|
343
|
+
trend: setupSmokeEvidenceTrend(artifacts),
|
|
344
|
+
latestResult: readArtifactMetadataString(artifacts[0]!, 'result') || 'unknown',
|
|
345
|
+
previousResult: artifacts[1] ? readArtifactMetadataString(artifacts[1], 'result') || 'unknown' : null,
|
|
346
|
+
resultCounts,
|
|
347
|
+
blockedCheckFrequency: setupSmokeBlockedCheckFrequency(artifacts),
|
|
348
|
+
recent: artifacts.slice(0, 5).map(describeSetupSmokeEvidenceArtifact),
|
|
349
|
+
inspectLatestRoute: `agent_artifacts show artifactId:"${artifacts[0]!.id}" includeContent:false`,
|
|
350
|
+
rerunRoute: DEFAULT_AGENT_SETUP_WIZARD_RERUN_SMOKE_ROUTE,
|
|
351
|
+
saveRoute: DEFAULT_AGENT_SETUP_WIZARD_SAVE_SMOKE_ROUTE,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function setupWizardSmokeHistory(context: CommandContext): AgentSetupWizardSmokeHistory {
|
|
356
|
+
const listed = setupSmokeEvidenceArtifacts(context);
|
|
357
|
+
if (listed.status === 'unavailable') {
|
|
358
|
+
return {
|
|
359
|
+
...emptyAgentSetupSmokeHistory(listed.reason),
|
|
360
|
+
status: 'unavailable',
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const artifacts = listed.artifacts;
|
|
364
|
+
if (artifacts.length === 0) return emptyAgentSetupSmokeHistory();
|
|
365
|
+
const resultCounts = artifacts.reduce<Record<string, number>>((counts, artifact) => {
|
|
366
|
+
const result = readArtifactMetadataString(artifact, 'result') || 'unknown';
|
|
367
|
+
counts[result] = (counts[result] ?? 0) + 1;
|
|
368
|
+
return counts;
|
|
369
|
+
}, {});
|
|
370
|
+
return {
|
|
371
|
+
status: 'available',
|
|
372
|
+
total: artifacts.length,
|
|
373
|
+
trend: setupSmokeEvidenceTrend(artifacts),
|
|
374
|
+
latestResult: readArtifactMetadataString(artifacts[0]!, 'result') || 'unknown',
|
|
375
|
+
previousResult: artifacts[1] ? readArtifactMetadataString(artifacts[1], 'result') || 'unknown' : null,
|
|
376
|
+
resultCounts,
|
|
377
|
+
blockedCheckFrequency: setupSmokeBlockedCheckFrequency(artifacts).map((entry) => ({
|
|
378
|
+
checkId: readString(entry.checkId),
|
|
379
|
+
count: typeof entry.count === 'number' ? entry.count : 0,
|
|
380
|
+
})).filter((entry) => entry.checkId && entry.count > 0),
|
|
381
|
+
inspectLatestRoute: `agent_artifacts show artifactId:"${artifacts[0]!.id}" includeContent:false`,
|
|
382
|
+
rerunRoute: DEFAULT_AGENT_SETUP_WIZARD_RERUN_SMOKE_ROUTE,
|
|
383
|
+
saveRoute: DEFAULT_AGENT_SETUP_WIZARD_SAVE_SMOKE_ROUTE,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function setupWizardCheckpoint(context: CommandContext): AgentSetupWizardCheckpoint {
|
|
388
|
+
const routes = {
|
|
389
|
+
markCurrentRoute: DEFAULT_AGENT_SETUP_WIZARD_MARK_CHECKPOINT_ROUTE,
|
|
390
|
+
clearRoute: DEFAULT_AGENT_SETUP_WIZARD_CLEAR_CHECKPOINT_ROUTE,
|
|
391
|
+
inspectRoute: DEFAULT_AGENT_SETUP_WIZARD_INSPECT_CHECKPOINT_ROUTE,
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
const shellPaths = requireShellPaths(context);
|
|
395
|
+
const snapshot = readSetupWizardCheckpoint(shellPaths);
|
|
396
|
+
if (!snapshot.exists) {
|
|
397
|
+
return {
|
|
398
|
+
...emptyAgentSetupWizardCheckpoint(),
|
|
399
|
+
...routes,
|
|
400
|
+
path: snapshot.path,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (!snapshot.checkpoint) {
|
|
404
|
+
return {
|
|
405
|
+
...emptyAgentSetupWizardCheckpoint(snapshot.parseError ?? 'Saved setup wizard checkpoint could not be read.'),
|
|
406
|
+
...routes,
|
|
407
|
+
status: 'unavailable',
|
|
408
|
+
path: snapshot.path,
|
|
409
|
+
...(snapshot.parseError ? { parseError: snapshot.parseError } : {}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
status: 'available',
|
|
414
|
+
currentStepId: snapshot.checkpoint.currentStepId,
|
|
415
|
+
currentStepLabel: snapshot.checkpoint.currentStepLabel,
|
|
416
|
+
savedAt: snapshot.checkpoint.savedAt,
|
|
417
|
+
source: snapshot.checkpoint.source,
|
|
418
|
+
resumed: false,
|
|
419
|
+
summary: `Saved setup checkpoint for ${snapshot.checkpoint.currentStepLabel}.`,
|
|
420
|
+
path: snapshot.path,
|
|
421
|
+
...(snapshot.checkpoint.note ? { note: snapshot.checkpoint.note } : {}),
|
|
422
|
+
...routes,
|
|
423
|
+
};
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return {
|
|
426
|
+
...emptyAgentSetupWizardCheckpoint(previewHarnessText(error instanceof Error ? error.message : String(error), 160)),
|
|
427
|
+
...routes,
|
|
428
|
+
status: 'unavailable',
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function setupCompletionMarkerExists(context: CommandContext): boolean {
|
|
434
|
+
try {
|
|
435
|
+
return readOnboardingCompletionMarker(requireShellPaths(context), 'user').exists;
|
|
436
|
+
} catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export const SETUP_WIZARD_PLAN_BLOCKER_ALIASES: Readonly<Record<string, readonly string[]>> = {
|
|
442
|
+
'agent-binary': ['install-smoke', 'connected-host-readiness'],
|
|
443
|
+
'connected-host-status': ['connected-host-readiness'],
|
|
444
|
+
'connected-host-auth': ['connected-host-auth'],
|
|
445
|
+
'provider-model': ['provider-access'],
|
|
446
|
+
'setup-posture': ['install-smoke'],
|
|
447
|
+
'first-assistant-turn': ['install-smoke'],
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
export function setupPlanItemToWizardSource(item: SetupPlanItem): AgentSetupWizardSourceItem {
|
|
451
|
+
const primaryHandoff = setupHandoffsForItem(item)[0];
|
|
452
|
+
return {
|
|
453
|
+
id: item.id,
|
|
454
|
+
label: item.label,
|
|
455
|
+
status: item.status,
|
|
456
|
+
detail: item.nextAction || item.reason,
|
|
457
|
+
userRoute: primaryHandoff?.userRoute ?? item.userRoute,
|
|
458
|
+
modelRoute: primaryHandoff?.modelRoute ?? item.modelRoute,
|
|
459
|
+
actionId: primaryHandoff?.id ?? item.id,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function buildSetupWizard(plan: readonly SetupPlanItem[], context: CommandContext): AgentSetupWizard {
|
|
464
|
+
const markerItem = plan.find((item) => item.id === 'finish-onboarding');
|
|
465
|
+
return buildAgentSetupWizard({
|
|
466
|
+
items: plan.map(setupPlanItemToWizardSource),
|
|
467
|
+
smokeHistory: setupWizardSmokeHistory(context),
|
|
468
|
+
checkpoint: setupWizardCheckpoint(context),
|
|
469
|
+
closeoutCriticalStepIds: plan.filter((item) => item.blocksAutonomy).map((item) => item.id),
|
|
470
|
+
setupMarkerExists: markerItem?.status === 'ready' || setupCompletionMarkerExists(context),
|
|
471
|
+
repeatedBlockerAliases: SETUP_WIZARD_PLAN_BLOCKER_ALIASES,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function installSmokeRunSummary(plan: SetupInstallSmokePlan): SetupInstallSmokeRunSummary {
|
|
476
|
+
return {
|
|
477
|
+
ready: plan.checks.filter((check) => check.status === 'ready').length,
|
|
478
|
+
blocked: plan.checks.filter((check) => check.status === 'blocked').length,
|
|
479
|
+
userRun: plan.checks.filter((check) => check.status === 'user-run').length,
|
|
480
|
+
total: plan.checks.length,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function installSmokeRunResult(plan: SetupInstallSmokePlan): 'blocked' | 'ready-for-user-run' {
|
|
485
|
+
return plan.checks.some((check) => check.status === 'blocked') ? 'blocked' : 'ready-for-user-run';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function installSmokeNextAction(plan: SetupInstallSmokePlan): string {
|
|
489
|
+
const blocked = plan.checks.filter((check) => check.status === 'blocked').map((check) => check.id);
|
|
490
|
+
if (blocked.length > 0) return `Resolve blocked checks (${blocked.join(', ')}), then rerun setup action:"smoke".`;
|
|
491
|
+
const userRun = plan.checks.filter((check) => check.status === 'user-run').map((check) => check.id);
|
|
492
|
+
return `Run user-visible checks (${userRun.join(', ')}), then keep the redacted output with the setup evidence.`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function describeInstallSmokeCheck(check: SetupInstallSmokeCheck, includeParameters: boolean): Record<string, unknown> {
|
|
496
|
+
return {
|
|
497
|
+
id: check.id,
|
|
498
|
+
label: check.label,
|
|
499
|
+
status: check.status,
|
|
500
|
+
evidence: previewHarnessText(check.evidence, includeParameters ? 180 : 120),
|
|
501
|
+
route: previewHarnessText(check.route, includeParameters ? 180 : 120),
|
|
502
|
+
action: check.status === 'blocked'
|
|
503
|
+
? 'fix-before-smoke'
|
|
504
|
+
: check.status === 'user-run'
|
|
505
|
+
? 'user-visible-run'
|
|
506
|
+
: 'evidence-ready',
|
|
507
|
+
};
|
|
508
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
3
|
+
|
|
4
|
+
export interface AgentHarnessSudoPosture {
|
|
5
|
+
readonly capability: 'sudo';
|
|
6
|
+
readonly status: 'foreground-or-user-supervised-only';
|
|
7
|
+
readonly setupStatus: 'optional' | 'check';
|
|
8
|
+
readonly userOutcome: string;
|
|
9
|
+
readonly modelRoute: string;
|
|
10
|
+
readonly setupRoute: string;
|
|
11
|
+
readonly foregroundRoute: string;
|
|
12
|
+
readonly credentialSignal: {
|
|
13
|
+
readonly envPresent: boolean;
|
|
14
|
+
readonly checked: 'process.env.SUDO_PASSWORD only';
|
|
15
|
+
readonly envFilePath: string | null;
|
|
16
|
+
readonly rawValueReturned: false;
|
|
17
|
+
readonly valueUsableForBackgroundProcess: false;
|
|
18
|
+
readonly guidance: string;
|
|
19
|
+
};
|
|
20
|
+
readonly supportedRoutes: readonly Record<string, string>[];
|
|
21
|
+
readonly blockedRoutes: readonly Record<string, string>[];
|
|
22
|
+
readonly missingContracts: readonly string[];
|
|
23
|
+
readonly signals: readonly string[];
|
|
24
|
+
readonly nextAction: string;
|
|
25
|
+
readonly policy: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function homeDirectory(context?: CommandContext): string | null {
|
|
29
|
+
const shellPaths = context?.workspace.shellPaths as { readonly homeDirectory?: unknown } | undefined;
|
|
30
|
+
const fromShellPaths = typeof shellPaths?.homeDirectory === 'string' && shellPaths.homeDirectory.trim()
|
|
31
|
+
? shellPaths.homeDirectory.trim()
|
|
32
|
+
: '';
|
|
33
|
+
if (fromShellPaths) return fromShellPaths;
|
|
34
|
+
return typeof process.env.HOME === 'string' && process.env.HOME.trim() ? process.env.HOME.trim() : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function sudoExecutionPosture(context?: CommandContext): AgentHarnessSudoPosture {
|
|
38
|
+
const sudoPasswordPresent = Boolean(process.env.SUDO_PASSWORD);
|
|
39
|
+
const home = homeDirectory(context);
|
|
40
|
+
const envFilePath = home ? join(home, '.goodvibes', '.env') : null;
|
|
41
|
+
const setupRoute = 'setup action:"item" setupItemId:"sudo-execution-posture"';
|
|
42
|
+
const foregroundRoute = 'execution action:"route" id:"local-shell-command"';
|
|
43
|
+
const processCapabilitiesRoute = 'process action:"capabilities"';
|
|
44
|
+
return {
|
|
45
|
+
capability: 'sudo',
|
|
46
|
+
status: 'foreground-or-user-supervised-only',
|
|
47
|
+
setupStatus: sudoPasswordPresent ? 'check' : 'optional',
|
|
48
|
+
userOutcome: 'Privilege escalation stays explicit, visible, and user-supervised; hidden background sudo prompts are blocked.',
|
|
49
|
+
modelRoute: foregroundRoute,
|
|
50
|
+
setupRoute,
|
|
51
|
+
foregroundRoute,
|
|
52
|
+
credentialSignal: {
|
|
53
|
+
envPresent: sudoPasswordPresent,
|
|
54
|
+
checked: 'process.env.SUDO_PASSWORD only',
|
|
55
|
+
envFilePath,
|
|
56
|
+
rawValueReturned: false,
|
|
57
|
+
valueUsableForBackgroundProcess: false,
|
|
58
|
+
guidance: sudoPasswordPresent
|
|
59
|
+
? 'SUDO_PASSWORD is present in the current environment, but Agent only reports presence and does not read, print, or pass the value to hidden background sudo.'
|
|
60
|
+
: 'If future SDK/daemon mediation supports sudo credentials, GoodVibes can load SUDO_PASSWORD from ~/.goodvibes/.env; Agent does not write or display that value.',
|
|
61
|
+
},
|
|
62
|
+
supportedRoutes: [
|
|
63
|
+
{
|
|
64
|
+
id: 'foreground-supervised-shell',
|
|
65
|
+
route: foregroundRoute,
|
|
66
|
+
outcome: 'Use a visible foreground shell route for explicit user-requested escalation.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'process-capability-report',
|
|
70
|
+
route: processCapabilitiesRoute,
|
|
71
|
+
outcome: 'Inspect the process parity report before choosing background process behavior.',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'setup-posture',
|
|
75
|
+
route: setupRoute,
|
|
76
|
+
outcome: 'Show sudo setup posture, credential presence, blocked routes, and next steps without exposing secrets.',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
blockedRoutes: [
|
|
80
|
+
{
|
|
81
|
+
id: 'background-sudo-prompt',
|
|
82
|
+
route: 'terminal command:"sudo ..." background:true',
|
|
83
|
+
reason: 'Blocked because background password prompts can hang or hide privilege escalation.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'stdin-password-write',
|
|
87
|
+
route: 'process action:"write" data:"..."',
|
|
88
|
+
reason: 'Blocked until ProcessManager exposes a safe stdin/credential-prompt contract.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'raw-password-display',
|
|
92
|
+
route: 'setup/output/logs',
|
|
93
|
+
reason: 'Blocked because raw sudo passwords must never be printed, stored by Agent, or included in model-visible output.',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
missingContracts: [
|
|
97
|
+
'SDK ProcessManager stdin write',
|
|
98
|
+
'SDK ProcessManager PTY session',
|
|
99
|
+
'daemon credential-prompt mediation',
|
|
100
|
+
'model-visible credential-use receipt without secret value disclosure',
|
|
101
|
+
],
|
|
102
|
+
signals: [
|
|
103
|
+
`SUDO_PASSWORD env present: ${sudoPasswordPresent ? 'yes' : 'no'}`,
|
|
104
|
+
`credential check: process.env.SUDO_PASSWORD only`,
|
|
105
|
+
`env file guidance: ${envFilePath ?? '~/.goodvibes/.env'}`,
|
|
106
|
+
'background sudo prompt: blocked',
|
|
107
|
+
'raw sudo password returned: never',
|
|
108
|
+
],
|
|
109
|
+
nextAction: sudoPasswordPresent
|
|
110
|
+
? 'Review sudo posture before escalation; use only visible foreground sudo until the SDK/daemon publishes safe credential mediation.'
|
|
111
|
+
: 'Use visible foreground sudo for explicit escalation; configure SUDO_PASSWORD outside Agent only if a future safe credential contract requires it.',
|
|
112
|
+
policy: 'Agent reports only credential presence and safe routes. It never reads, stores, prints, or injects raw sudo passwords, and it blocks hidden background sudo prompts.',
|
|
113
|
+
};
|
|
114
|
+
}
|