@pellux/goodvibes-agent 1.1.7 → 1.3.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 +329 -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 +138903 -91477
- 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 +58 -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 +116 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +142 -5
- package/release/release-readiness.json +124 -94
- 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 +294 -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-artifact-receipts.ts +366 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +683 -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 +313 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +217 -0
- package/src/input/agent-workspace-setup.ts +61 -1
- package/src/input/agent-workspace-snapshot.ts +184 -149
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +23 -1
- 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/input/setup-wizard-live-receipts.ts +76 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +701 -0
- package/src/renderer/agent-workspace.ts +68 -421
- 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 +39 -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 +199 -5
- 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 +137 -7
- 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-policy.ts +75 -0
- package/src/tools/agent-harness-agent-orchestration.ts +791 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +571 -0
- package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +798 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
- package/src/tools/agent-harness-browser-control.ts +346 -0
- package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -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-device-live-read-models.ts +366 -0
- 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 +385 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-interactive-runtime-records.ts +421 -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 +269 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
- package/src/tools/agent-harness-local-model-smoke.ts +277 -0
- package/src/tools/agent-harness-local-model-url.ts +78 -0
- package/src/tools/agent-harness-media-posture.ts +223 -2
- package/src/tools/agent-harness-memory-external-providers.ts +796 -0
- package/src/tools/agent-harness-memory-posture.ts +672 -0
- package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
- package/src/tools/agent-harness-memory-refinement.ts +340 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +86 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +283 -0
- package/src/tools/agent-harness-model-readiness.ts +432 -0
- package/src/tools/agent-harness-model-routing-types.ts +327 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +165 -193
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +452 -18
- package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
- 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 +270 -0
- package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
- package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
- package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
- package/src/tools/agent-harness-personal-ops-records.ts +684 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +240 -0
- package/src/tools/agent-harness-personal-ops.ts +613 -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-remote-read-models.ts +541 -0
- package/src/tools/agent-harness-research-briefing.ts +446 -0
- package/src/tools/agent-harness-research-live-read-models.ts +500 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +307 -0
- package/src/tools/agent-harness-research-workflow.ts +395 -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 +102 -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 +533 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +142 -7
- package/src/tools/agent-harness-tool-types.ts +83 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +319 -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 +253 -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 +735 -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-runner.ts +367 -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 +515 -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 +106 -85
- 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/version.ts +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
2
|
+
import type { AgentWorkspaceRecentReviewerHandoffArtifact, AgentWorkspaceReviewPacketPresetLineage } from './agent-workspace-types.ts';
|
|
3
|
+
import { readArtifactMetadataNumber, readArtifactMetadataString, readArtifactMetadataStringList } from './agent-workspace-artifact-metadata.ts';
|
|
4
|
+
|
|
5
|
+
export function isoToEpoch(value: string): number {
|
|
6
|
+
const timestamp = Date.parse(value);
|
|
7
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function compactTimelineText(value: string, maxLength = 96): string {
|
|
11
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
12
|
+
if (normalized.length <= maxLength) return normalized;
|
|
13
|
+
return `${normalized.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function uniqueStrings(values: readonly (string | null | undefined)[], limit: number): readonly string[] {
|
|
17
|
+
const seen = new Set<string>();
|
|
18
|
+
const result: string[] = [];
|
|
19
|
+
for (const value of values) {
|
|
20
|
+
const trimmed = value?.trim();
|
|
21
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
22
|
+
seen.add(trimmed);
|
|
23
|
+
result.push(trimmed);
|
|
24
|
+
if (result.length >= limit) break;
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isReviewerHandoffArtifact(artifact: ArtifactDescriptor): boolean {
|
|
30
|
+
const purpose = readArtifactMetadataString(artifact.metadata, 'purpose');
|
|
31
|
+
if (purpose === 'agent-model-compare-handoff') return true;
|
|
32
|
+
if (purpose.length > 0) return false;
|
|
33
|
+
return (artifact.filename ?? '').startsWith('blind-model-comparison-handoff-');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isModelCompareArtifact(artifact: ArtifactDescriptor): boolean {
|
|
37
|
+
const purpose = readArtifactMetadataString(artifact.metadata, 'purpose');
|
|
38
|
+
if (purpose === 'agent-model-compare') return true;
|
|
39
|
+
if (purpose.length > 0) return false;
|
|
40
|
+
return (artifact.filename ?? '').startsWith('blind-model-comparison-cmp_');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isModelCompareJudgmentArtifact(artifact: ArtifactDescriptor): boolean {
|
|
44
|
+
const purpose = readArtifactMetadataString(artifact.metadata, 'purpose');
|
|
45
|
+
if (purpose === 'agent-model-compare-judgment') return true;
|
|
46
|
+
if (purpose.length > 0) return false;
|
|
47
|
+
return (artifact.filename ?? '').startsWith('blind-model-comparison-judgment-');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isDocumentExportArtifact(artifact: ArtifactDescriptor): boolean {
|
|
51
|
+
return readArtifactMetadataString(artifact.metadata, 'purpose') === 'agent-document-export';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isModelCompareExportArtifact(artifact: ArtifactDescriptor): boolean {
|
|
55
|
+
return readArtifactMetadataString(artifact.metadata, 'purpose') === 'agent-model-compare-export';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isReviewerHandoffArchiveArtifact(artifact: ArtifactDescriptor): boolean {
|
|
59
|
+
return readArtifactMetadataString(artifact.metadata, 'purpose') === 'agent-model-compare-handoff-archive';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isModelCompareRouteDecisionArtifact(artifact: ArtifactDescriptor): boolean {
|
|
63
|
+
return readArtifactMetadataString(artifact.metadata, 'purpose') === 'agent-model-compare-route-decision';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isReviewPacketPresetArtifact(artifact: ArtifactDescriptor): boolean {
|
|
67
|
+
return readArtifactMetadataString(artifact.metadata, 'purpose') === 'agent-review-packet-preset';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function artifactById(artifacts: readonly ArtifactDescriptor[], artifactId: string): ArtifactDescriptor | null {
|
|
71
|
+
return artifacts.find((artifact) => artifact.id === artifactId) ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function firstArtifactMetadataString(
|
|
75
|
+
artifacts: readonly ArtifactDescriptor[],
|
|
76
|
+
artifactIds: readonly string[],
|
|
77
|
+
keys: readonly string[],
|
|
78
|
+
): string {
|
|
79
|
+
for (const artifactId of artifactIds) {
|
|
80
|
+
const artifact = artifactById(artifacts, artifactId);
|
|
81
|
+
if (!artifact) continue;
|
|
82
|
+
for (const key of keys) {
|
|
83
|
+
const value = readArtifactMetadataString(artifact.metadata, key);
|
|
84
|
+
if (value) return value;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function latestMatchingArtifact(
|
|
91
|
+
artifacts: readonly ArtifactDescriptor[],
|
|
92
|
+
currentArtifactId: string,
|
|
93
|
+
predicate: (artifact: ArtifactDescriptor) => boolean,
|
|
94
|
+
): ArtifactDescriptor | null {
|
|
95
|
+
const current = artifactById(artifacts, currentArtifactId);
|
|
96
|
+
const currentCreatedAt = current?.createdAt ?? -1;
|
|
97
|
+
return artifacts
|
|
98
|
+
.filter((artifact) => artifact.id !== currentArtifactId && artifact.createdAt > currentCreatedAt && predicate(artifact))
|
|
99
|
+
.sort((left, right) => right.createdAt - left.createdAt || left.id.localeCompare(right.id))[0] ?? null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function reviewPacketPresetFreshness(
|
|
103
|
+
preset: ArtifactDescriptor,
|
|
104
|
+
artifacts: readonly ArtifactDescriptor[],
|
|
105
|
+
): { readonly missingCount: number; readonly newerCount: number; readonly summary: string; readonly status: 'info' | 'attention' } {
|
|
106
|
+
const metadata = preset.metadata;
|
|
107
|
+
const primaryIds = [
|
|
108
|
+
readArtifactMetadataString(metadata, 'documentExportArtifactId'),
|
|
109
|
+
readArtifactMetadataString(metadata, 'comparisonArtifactId'),
|
|
110
|
+
readArtifactMetadataString(metadata, 'judgmentArtifactId'),
|
|
111
|
+
readArtifactMetadataString(metadata, 'revealedJudgmentArtifactId'),
|
|
112
|
+
readArtifactMetadataString(metadata, 'routeDecisionArtifactId'),
|
|
113
|
+
readArtifactMetadataString(metadata, 'handoffArtifactId'),
|
|
114
|
+
readArtifactMetadataString(metadata, 'handoffArchiveArtifactId'),
|
|
115
|
+
].filter(Boolean);
|
|
116
|
+
const relatedIds = readArtifactMetadataStringList(metadata, 'relatedArtifactIds');
|
|
117
|
+
const referencedIds = uniqueStrings([...primaryIds, ...relatedIds], 40);
|
|
118
|
+
const missingCount = referencedIds.filter((artifactId) => !artifactById(artifacts, artifactId)).length;
|
|
119
|
+
const documentId = readArtifactMetadataString(metadata, 'documentId')
|
|
120
|
+
|| firstArtifactMetadataString(artifacts, primaryIds, ['documentId', 'sourceDocumentId']);
|
|
121
|
+
const comparisonId = firstArtifactMetadataString(artifacts, primaryIds, ['comparisonId']);
|
|
122
|
+
const sourceArtifactId = firstArtifactMetadataString(artifacts, primaryIds, ['sourceArtifactId'])
|
|
123
|
+
|| readArtifactMetadataString(metadata, 'documentExportArtifactId');
|
|
124
|
+
const handoffSourceArtifactId = readArtifactMetadataString(metadata, 'revealedJudgmentArtifactId')
|
|
125
|
+
|| readArtifactMetadataString(metadata, 'judgmentArtifactId')
|
|
126
|
+
|| readArtifactMetadataString(metadata, 'comparisonArtifactId')
|
|
127
|
+
|| readArtifactMetadataString(metadata, 'documentExportArtifactId');
|
|
128
|
+
const judgmentArtifactId = readArtifactMetadataString(metadata, 'revealedJudgmentArtifactId')
|
|
129
|
+
|| readArtifactMetadataString(metadata, 'judgmentArtifactId')
|
|
130
|
+
|| firstArtifactMetadataString(artifacts, [readArtifactMetadataString(metadata, 'routeDecisionArtifactId')], ['judgmentArtifactId']);
|
|
131
|
+
const handoffArtifactId = readArtifactMetadataString(metadata, 'handoffArtifactId')
|
|
132
|
+
|| firstArtifactMetadataString(artifacts, [readArtifactMetadataString(metadata, 'handoffArchiveArtifactId')], ['handoffArtifactId']);
|
|
133
|
+
const documentExportArtifactId = readArtifactMetadataString(metadata, 'documentExportArtifactId');
|
|
134
|
+
const comparisonArtifactId = readArtifactMetadataString(metadata, 'comparisonArtifactId');
|
|
135
|
+
const judgmentPresetArtifactId = readArtifactMetadataString(metadata, 'judgmentArtifactId');
|
|
136
|
+
const revealedJudgmentArtifactId = readArtifactMetadataString(metadata, 'revealedJudgmentArtifactId');
|
|
137
|
+
const routeDecisionArtifactId = readArtifactMetadataString(metadata, 'routeDecisionArtifactId');
|
|
138
|
+
const handoffPresetArtifactId = readArtifactMetadataString(metadata, 'handoffArtifactId');
|
|
139
|
+
const handoffArchiveArtifactId = readArtifactMetadataString(metadata, 'handoffArchiveArtifactId');
|
|
140
|
+
const freshnessChecks: Array<ArtifactDescriptor | null> = [
|
|
141
|
+
documentId && documentExportArtifactId
|
|
142
|
+
? latestMatchingArtifact(artifacts, documentExportArtifactId, (artifact) => (
|
|
143
|
+
isDocumentExportArtifact(artifact) && readArtifactMetadataString(artifact.metadata, 'documentId') === documentId
|
|
144
|
+
))
|
|
145
|
+
: null,
|
|
146
|
+
comparisonArtifactId && (documentId || sourceArtifactId)
|
|
147
|
+
? latestMatchingArtifact(artifacts, comparisonArtifactId, (artifact) => (
|
|
148
|
+
isModelCompareArtifact(artifact)
|
|
149
|
+
&& (
|
|
150
|
+
Boolean(documentId && readArtifactMetadataString(artifact.metadata, 'documentId') === documentId)
|
|
151
|
+
|| Boolean(sourceArtifactId && readArtifactMetadataString(artifact.metadata, 'sourceArtifactId') === sourceArtifactId)
|
|
152
|
+
)
|
|
153
|
+
))
|
|
154
|
+
: null,
|
|
155
|
+
judgmentPresetArtifactId && (comparisonId || sourceArtifactId || documentId)
|
|
156
|
+
? latestMatchingArtifact(artifacts, judgmentPresetArtifactId, (artifact) => (
|
|
157
|
+
isModelCompareJudgmentArtifact(artifact)
|
|
158
|
+
&& (
|
|
159
|
+
Boolean(comparisonId && readArtifactMetadataString(artifact.metadata, 'comparisonId') === comparisonId)
|
|
160
|
+
|| Boolean(documentId && readArtifactMetadataString(artifact.metadata, 'documentId') === documentId)
|
|
161
|
+
|| Boolean(sourceArtifactId && readArtifactMetadataString(artifact.metadata, 'sourceArtifactId') === sourceArtifactId)
|
|
162
|
+
)
|
|
163
|
+
))
|
|
164
|
+
: null,
|
|
165
|
+
revealedJudgmentArtifactId && (comparisonId || sourceArtifactId || documentId)
|
|
166
|
+
? latestMatchingArtifact(artifacts, revealedJudgmentArtifactId, (artifact) => (
|
|
167
|
+
isModelCompareJudgmentArtifact(artifact)
|
|
168
|
+
&& artifact.metadata.revealIncludedInJudgment === true
|
|
169
|
+
&& readArtifactMetadataString(artifact.metadata, 'winnerModel').length > 0
|
|
170
|
+
&& (
|
|
171
|
+
Boolean(comparisonId && readArtifactMetadataString(artifact.metadata, 'comparisonId') === comparisonId)
|
|
172
|
+
|| Boolean(documentId && readArtifactMetadataString(artifact.metadata, 'documentId') === documentId)
|
|
173
|
+
|| Boolean(sourceArtifactId && readArtifactMetadataString(artifact.metadata, 'sourceArtifactId') === sourceArtifactId)
|
|
174
|
+
)
|
|
175
|
+
))
|
|
176
|
+
: null,
|
|
177
|
+
routeDecisionArtifactId && (comparisonId || judgmentArtifactId)
|
|
178
|
+
? latestMatchingArtifact(artifacts, routeDecisionArtifactId, (artifact) => (
|
|
179
|
+
isModelCompareRouteDecisionArtifact(artifact)
|
|
180
|
+
&& (
|
|
181
|
+
Boolean(judgmentArtifactId && readArtifactMetadataString(artifact.metadata, 'judgmentArtifactId') === judgmentArtifactId)
|
|
182
|
+
|| Boolean(comparisonId && readArtifactMetadataString(artifact.metadata, 'comparisonId') === comparisonId)
|
|
183
|
+
)
|
|
184
|
+
))
|
|
185
|
+
: null,
|
|
186
|
+
handoffPresetArtifactId && (comparisonId || handoffSourceArtifactId)
|
|
187
|
+
? latestMatchingArtifact(artifacts, handoffPresetArtifactId, (artifact) => (
|
|
188
|
+
isReviewerHandoffArtifact(artifact)
|
|
189
|
+
&& (
|
|
190
|
+
Boolean(handoffSourceArtifactId && readArtifactMetadataString(artifact.metadata, 'sourceArtifactId') === handoffSourceArtifactId)
|
|
191
|
+
|| Boolean(comparisonId && readArtifactMetadataString(artifact.metadata, 'comparisonId') === comparisonId)
|
|
192
|
+
)
|
|
193
|
+
))
|
|
194
|
+
: null,
|
|
195
|
+
handoffArchiveArtifactId && (comparisonId || handoffArtifactId)
|
|
196
|
+
? latestMatchingArtifact(artifacts, handoffArchiveArtifactId, (artifact) => (
|
|
197
|
+
isReviewerHandoffArchiveArtifact(artifact)
|
|
198
|
+
&& (
|
|
199
|
+
Boolean(handoffArtifactId && readArtifactMetadataString(artifact.metadata, 'handoffArtifactId') === handoffArtifactId)
|
|
200
|
+
|| Boolean(comparisonId && readArtifactMetadataString(artifact.metadata, 'comparisonId') === comparisonId)
|
|
201
|
+
)
|
|
202
|
+
))
|
|
203
|
+
: null,
|
|
204
|
+
];
|
|
205
|
+
const newerCount = new Set(freshnessChecks.filter((artifact): artifact is ArtifactDescriptor => artifact !== null).map((artifact) => artifact.id)).size;
|
|
206
|
+
const needsReview = missingCount > 0 || newerCount > 0;
|
|
207
|
+
return {
|
|
208
|
+
missingCount,
|
|
209
|
+
newerCount,
|
|
210
|
+
summary: needsReview ? `freshness needs review: ${missingCount} missing, ${newerCount} newer` : 'freshness current',
|
|
211
|
+
status: needsReview ? 'attention' : 'info',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function buildReviewPacketPresetLineage(preset: ArtifactDescriptor | null): AgentWorkspaceReviewPacketPresetLineage | null {
|
|
216
|
+
if (!preset) return null;
|
|
217
|
+
const metadata = preset.metadata;
|
|
218
|
+
const refreshedFromArtifactId = readArtifactMetadataString(metadata, 'refreshOfArtifactId') || null;
|
|
219
|
+
const refreshedFromPresetId = readArtifactMetadataString(metadata, 'refreshOfPresetId') || null;
|
|
220
|
+
const freshnessMissingCount = readArtifactMetadataNumber(metadata, 'freshnessMissingCount');
|
|
221
|
+
const freshnessSupersededCount = readArtifactMetadataNumber(metadata, 'freshnessSupersededCount');
|
|
222
|
+
const freshnessUnresolvedCount = readArtifactMetadataNumber(metadata, 'freshnessUnresolvedCount');
|
|
223
|
+
const repairedCount = (freshnessMissingCount ?? 0) + (freshnessSupersededCount ?? 0);
|
|
224
|
+
const name = readArtifactMetadataString(metadata, 'name') || null;
|
|
225
|
+
const presetId = readArtifactMetadataString(metadata, 'presetId') || null;
|
|
226
|
+
const displayName = name ?? preset.filename ?? preset.id;
|
|
227
|
+
const summary = refreshedFromArtifactId
|
|
228
|
+
? [
|
|
229
|
+
`${displayName} refreshed from ${refreshedFromArtifactId}`,
|
|
230
|
+
refreshedFromPresetId ? `source preset ${refreshedFromPresetId}` : '',
|
|
231
|
+
`repaired ${repairedCount}`,
|
|
232
|
+
`unresolved ${freshnessUnresolvedCount ?? 0}`,
|
|
233
|
+
].filter(Boolean).join('; ')
|
|
234
|
+
: [
|
|
235
|
+
`${displayName} has no refresh lineage`,
|
|
236
|
+
presetId ? `preset ${presetId}` : '',
|
|
237
|
+
].filter(Boolean).join('; ');
|
|
238
|
+
return {
|
|
239
|
+
artifactId: preset.id,
|
|
240
|
+
presetId,
|
|
241
|
+
name,
|
|
242
|
+
refreshed: Boolean(refreshedFromArtifactId),
|
|
243
|
+
refreshedFromArtifactId,
|
|
244
|
+
refreshedFromPresetId,
|
|
245
|
+
freshnessMissingCount,
|
|
246
|
+
freshnessSupersededCount,
|
|
247
|
+
freshnessUnresolvedCount,
|
|
248
|
+
summary,
|
|
249
|
+
inspectRoute: `agent_review_packet_presets show artifactId:"${preset.id}"`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function summarizeReviewerHandoffArtifact(artifact: ArtifactDescriptor): AgentWorkspaceRecentReviewerHandoffArtifact {
|
|
254
|
+
const metadata = artifact.metadata;
|
|
255
|
+
return {
|
|
256
|
+
id: artifact.id,
|
|
257
|
+
filename: artifact.filename ?? '(unnamed handoff artifact)',
|
|
258
|
+
createdAt: artifact.createdAt,
|
|
259
|
+
handoffId: readArtifactMetadataString(metadata, 'handoffId') || 'unknown-handoff',
|
|
260
|
+
comparisonId: readArtifactMetadataString(metadata, 'comparisonId') || 'unknown-comparison',
|
|
261
|
+
sourceArtifactId: readArtifactMetadataString(metadata, 'sourceArtifactId') || '(missing source)',
|
|
262
|
+
sourceKind: readArtifactMetadataString(metadata, 'sourceKind') || 'unknown',
|
|
263
|
+
relatedArtifactCount: readArtifactMetadataStringList(metadata, 'relatedArtifactIds').length,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -56,6 +56,95 @@ export interface TuiSettingsImportOutcome extends SettingMutationOutcome {
|
|
|
56
56
|
readonly runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
type TuiImportStatus = 'would_import' | 'unchanged' | 'skipped';
|
|
60
|
+
|
|
61
|
+
interface TuiSettingsImportSource {
|
|
62
|
+
readonly id: string;
|
|
63
|
+
readonly label: string;
|
|
64
|
+
readonly path: string;
|
|
65
|
+
readonly sourcePackage: string;
|
|
66
|
+
readonly surfaceRoot: string;
|
|
67
|
+
readonly scope: 'user' | 'project';
|
|
68
|
+
readonly ownership: 'source-owned';
|
|
69
|
+
readonly mutatesSource: false;
|
|
70
|
+
readonly status: 'missing' | 'loaded' | 'invalid' | 'error';
|
|
71
|
+
readonly importableSettings: number;
|
|
72
|
+
readonly error?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface GoodVibesSettingsImportSourceCatalogEntry {
|
|
76
|
+
readonly packageId: string;
|
|
77
|
+
readonly surfaceRoot: string;
|
|
78
|
+
readonly scopes: readonly string[];
|
|
79
|
+
readonly status: 'implemented';
|
|
80
|
+
readonly ownership: 'source-owned';
|
|
81
|
+
readonly mutatesSource: false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface TuiSettingImportPlanEntry {
|
|
85
|
+
readonly key: string;
|
|
86
|
+
readonly source: string;
|
|
87
|
+
readonly value: unknown;
|
|
88
|
+
readonly status: TuiImportStatus;
|
|
89
|
+
readonly current: unknown;
|
|
90
|
+
readonly incoming: unknown;
|
|
91
|
+
readonly reason?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface TuiSubscriptionImportPlanEntry {
|
|
95
|
+
readonly provider?: string;
|
|
96
|
+
readonly kind: 'active' | 'pending';
|
|
97
|
+
readonly status: TuiImportStatus;
|
|
98
|
+
readonly reason?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface TuiSettingsImportPlan {
|
|
102
|
+
readonly sources: readonly TuiSettingsImportSource[];
|
|
103
|
+
readonly settings: readonly TuiSettingImportPlanEntry[];
|
|
104
|
+
readonly subscriptions: readonly TuiSubscriptionImportPlanEntry[];
|
|
105
|
+
readonly parseErrors: readonly string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface TuiSettingsImportPreview {
|
|
109
|
+
readonly summary: {
|
|
110
|
+
readonly settingsToImport: number;
|
|
111
|
+
readonly settingsUnchanged: number;
|
|
112
|
+
readonly settingsSkipped: number;
|
|
113
|
+
readonly activeSubscriptionsToImport: number;
|
|
114
|
+
readonly pendingSubscriptionsToImport: number;
|
|
115
|
+
readonly subscriptionsUnchanged: number;
|
|
116
|
+
readonly subscriptionsSkipped: number;
|
|
117
|
+
readonly parseErrors: number;
|
|
118
|
+
};
|
|
119
|
+
readonly sourceCatalog: readonly GoodVibesSettingsImportSourceCatalogEntry[];
|
|
120
|
+
readonly sources: readonly TuiSettingsImportSource[];
|
|
121
|
+
readonly settings: readonly {
|
|
122
|
+
readonly key: string;
|
|
123
|
+
readonly source: string;
|
|
124
|
+
readonly status: TuiImportStatus;
|
|
125
|
+
readonly current: unknown;
|
|
126
|
+
readonly incoming: unknown;
|
|
127
|
+
readonly reason?: string;
|
|
128
|
+
}[];
|
|
129
|
+
readonly subscriptions: readonly TuiSubscriptionImportPlanEntry[];
|
|
130
|
+
readonly policy: {
|
|
131
|
+
readonly effect: 'read-only' | 'state';
|
|
132
|
+
readonly confirmation: 'required-for-apply';
|
|
133
|
+
readonly boundary: string;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const GOODVIBES_SETTINGS_IMPORT_SOURCE_CATALOG: readonly GoodVibesSettingsImportSourceCatalogEntry[] = [
|
|
138
|
+
{
|
|
139
|
+
packageId: 'goodvibes-tui',
|
|
140
|
+
surfaceRoot: GOODVIBES_TUI_SURFACE_ROOT,
|
|
141
|
+
scopes: ['user-settings', 'project-settings', 'provider-subscriptions'],
|
|
142
|
+
status: 'implemented',
|
|
143
|
+
ownership: 'source-owned',
|
|
144
|
+
mutatesSource: false,
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
59
148
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
60
149
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
61
150
|
}
|
|
@@ -84,6 +173,36 @@ function valuesMatch(left: unknown, right: unknown): boolean {
|
|
|
84
173
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
85
174
|
}
|
|
86
175
|
|
|
176
|
+
function isSecretLikeSettingKey(key: string): boolean {
|
|
177
|
+
const normalized = key.replace(/([a-z0-9])([A-Z])/g, '$1.$2').toLowerCase();
|
|
178
|
+
if (normalized.endsWith('secret.policy')) return false;
|
|
179
|
+
return /(?:secret|token|password|api[-_.]?key|api\.key|signing)/i.test(normalized);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function compactSettingValue(key: string, value: unknown): string {
|
|
183
|
+
if (value === undefined || value === null) return '(unset)';
|
|
184
|
+
if (isSecretLikeSettingKey(key)) return value === '' ? '(empty)' : '(secret)';
|
|
185
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
186
|
+
if (typeof value === 'number') return Number.isFinite(value) ? String(value) : '(invalid)';
|
|
187
|
+
if (typeof value === 'string') {
|
|
188
|
+
if (value.length === 0) return '(empty)';
|
|
189
|
+
if (value.startsWith('goodvibes://secrets/')) return '(secret)';
|
|
190
|
+
return value.length > 36 ? `${value.slice(0, 33)}...` : value;
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(value)) return `[${value.length} item${value.length === 1 ? '' : 's'}]`;
|
|
193
|
+
if (isRecord(value)) return '{...}';
|
|
194
|
+
return String(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function redactImportValue(key: string, value: unknown): unknown {
|
|
198
|
+
if (typeof value !== 'string') return value;
|
|
199
|
+
if (!value) return value;
|
|
200
|
+
if (isSecretLikeSettingKey(key)) {
|
|
201
|
+
return value.startsWith('goodvibes://secrets/') ? '<secret-ref>' : '<redacted>';
|
|
202
|
+
}
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
|
|
87
206
|
function readRecordMap(record: Record<string, unknown>, key: string): readonly unknown[] {
|
|
88
207
|
const value = record[key];
|
|
89
208
|
return isRecord(value) ? Object.values(value) : [];
|
|
@@ -109,7 +228,45 @@ function isPendingSubscriptionLogin(value: unknown): value is PendingSubscriptio
|
|
|
109
228
|
&& typeof value.createdAt === 'number';
|
|
110
229
|
}
|
|
111
230
|
|
|
112
|
-
function
|
|
231
|
+
function planTuiSubscriptions(context: CommandContext, parseErrors: string[]): readonly TuiSubscriptionImportPlanEntry[] {
|
|
232
|
+
const shellPaths = context.workspace.shellPaths;
|
|
233
|
+
const manager = context.platform.subscriptionManager;
|
|
234
|
+
const result: TuiSubscriptionImportPlanEntry[] = [];
|
|
235
|
+
if (!shellPaths || !manager) return result;
|
|
236
|
+
|
|
237
|
+
const sourcePath = shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'subscriptions.json');
|
|
238
|
+
try {
|
|
239
|
+
const store = readJsonRecord(sourcePath);
|
|
240
|
+
if (!store) return result;
|
|
241
|
+
for (const entry of readRecordMap(store, 'subscriptions')) {
|
|
242
|
+
if (!isProviderSubscription(entry)) {
|
|
243
|
+
result.push({ kind: 'active', status: 'skipped', reason: 'active subscription with invalid shape' });
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (valuesMatch(manager.get(entry.provider), entry)) {
|
|
247
|
+
result.push({ provider: entry.provider, kind: 'active', status: 'unchanged' });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
result.push({ provider: entry.provider, kind: 'active', status: 'would_import' });
|
|
251
|
+
}
|
|
252
|
+
for (const entry of readRecordMap(store, 'pending')) {
|
|
253
|
+
if (!isPendingSubscriptionLogin(entry)) {
|
|
254
|
+
result.push({ kind: 'pending', status: 'skipped', reason: 'pending subscription with invalid shape' });
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (valuesMatch(manager.getPending(entry.provider), entry)) {
|
|
258
|
+
result.push({ provider: entry.provider, kind: 'pending', status: 'unchanged' });
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
result.push({ provider: entry.provider, kind: 'pending', status: 'would_import' });
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
parseErrors.push(`subscriptions: ${error instanceof Error ? error.message : String(error)}`);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function applyTuiSubscriptions(context: CommandContext, parseErrors: string[]): {
|
|
113
270
|
readonly importedActive: string[];
|
|
114
271
|
readonly importedPending: string[];
|
|
115
272
|
readonly unchangedActive: string[];
|
|
@@ -233,6 +390,36 @@ export function buildAgentWorkspaceSettingActionEffect(
|
|
|
233
390
|
};
|
|
234
391
|
}
|
|
235
392
|
|
|
393
|
+
export function buildAgentWorkspaceSettingActionPreview(
|
|
394
|
+
context: CommandContext | null,
|
|
395
|
+
action: AgentWorkspaceAction,
|
|
396
|
+
): string | null {
|
|
397
|
+
const settingKey = action.settingKey?.trim();
|
|
398
|
+
const configManager = context?.platform?.configManager;
|
|
399
|
+
if (!settingKey || !configManager) return null;
|
|
400
|
+
|
|
401
|
+
const setting = agentWorkspaceSettingSchema(context, settingKey);
|
|
402
|
+
const currentValue = configManager.get(settingKey as ConfigKey);
|
|
403
|
+
let proposedValue: unknown;
|
|
404
|
+
|
|
405
|
+
if (action.settingValueHint !== undefined) {
|
|
406
|
+
proposedValue = action.settingValueHint;
|
|
407
|
+
} else if (setting?.type === 'boolean') {
|
|
408
|
+
proposedValue = !Boolean(currentValue);
|
|
409
|
+
} else if (setting?.type === 'enum' && setting.enumValues && setting.enumValues.length > 0) {
|
|
410
|
+
const currentIndex = Math.max(0, setting.enumValues.indexOf(String(currentValue)));
|
|
411
|
+
proposedValue = setting.enumValues[(currentIndex + 1) % setting.enumValues.length]!;
|
|
412
|
+
} else if (setting?.type === 'number' || setting?.type === 'string') {
|
|
413
|
+
proposedValue = 'edit value';
|
|
414
|
+
} else if (typeof currentValue === 'boolean') {
|
|
415
|
+
proposedValue = !currentValue;
|
|
416
|
+
} else {
|
|
417
|
+
proposedValue = 'choose value';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return `${settingKey}: ${compactSettingValue(settingKey, currentValue)} -> ${compactSettingValue(settingKey, proposedValue)}`;
|
|
421
|
+
}
|
|
422
|
+
|
|
236
423
|
export async function applyAgentWorkspaceSettingValue(
|
|
237
424
|
context: CommandContext | null,
|
|
238
425
|
setting: ConfigSetting,
|
|
@@ -275,51 +462,147 @@ export async function applyAgentWorkspaceSettingValue(
|
|
|
275
462
|
}
|
|
276
463
|
}
|
|
277
464
|
|
|
278
|
-
|
|
465
|
+
function buildTuiSettingsImportPlan(context: CommandContext | null): TuiSettingsImportPlan | null {
|
|
279
466
|
const shellPaths = context?.workspace?.shellPaths;
|
|
280
467
|
const configManager = context?.platform?.configManager;
|
|
281
|
-
if (!context || !shellPaths || !configManager)
|
|
282
|
-
return {
|
|
283
|
-
status: 'GoodVibes TUI settings import is unavailable in this runtime.',
|
|
284
|
-
runtimeSnapshot: null,
|
|
285
|
-
result: {
|
|
286
|
-
kind: 'error',
|
|
287
|
-
title: 'Import unavailable',
|
|
288
|
-
detail: 'The workspace cannot locate shell paths or the Agent config manager.',
|
|
289
|
-
safety: 'safe',
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
}
|
|
468
|
+
if (!context || !shellPaths || !configManager) return null;
|
|
293
469
|
|
|
294
|
-
const
|
|
295
|
-
{
|
|
296
|
-
|
|
470
|
+
const sourceInputs: readonly Omit<TuiSettingsImportSource, 'status' | 'importableSettings' | 'error'>[] = [
|
|
471
|
+
{
|
|
472
|
+
id: 'goodvibes-tui:user-settings',
|
|
473
|
+
label: 'goodvibes-tui user settings',
|
|
474
|
+
path: shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json'),
|
|
475
|
+
sourcePackage: 'goodvibes-tui',
|
|
476
|
+
surfaceRoot: GOODVIBES_TUI_SURFACE_ROOT,
|
|
477
|
+
scope: 'user',
|
|
478
|
+
ownership: 'source-owned',
|
|
479
|
+
mutatesSource: false,
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: 'goodvibes-tui:project-settings',
|
|
483
|
+
label: 'goodvibes-tui project settings',
|
|
484
|
+
path: shellPaths.resolveProjectPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json'),
|
|
485
|
+
sourcePackage: 'goodvibes-tui',
|
|
486
|
+
surfaceRoot: GOODVIBES_TUI_SURFACE_ROOT,
|
|
487
|
+
scope: 'project',
|
|
488
|
+
ownership: 'source-owned',
|
|
489
|
+
mutatesSource: false,
|
|
490
|
+
},
|
|
297
491
|
];
|
|
298
492
|
const values = new Map<string, { readonly value: unknown; readonly source: string }>();
|
|
299
493
|
const parseErrors: string[] = [];
|
|
300
|
-
|
|
494
|
+
const sources: TuiSettingsImportSource[] = [];
|
|
495
|
+
for (const source of sourceInputs) {
|
|
496
|
+
if (!existsSync(source.path)) {
|
|
497
|
+
sources.push({ ...source, status: 'missing', importableSettings: 0 });
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
301
500
|
try {
|
|
302
501
|
const record = readJsonRecord(source.path);
|
|
303
|
-
if (!record)
|
|
502
|
+
if (!record) {
|
|
503
|
+
const message = `${source.label}: settings store is not an object`;
|
|
504
|
+
parseErrors.push(message);
|
|
505
|
+
sources.push({ ...source, status: 'invalid', importableSettings: 0, error: message });
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
let importableSettings = 0;
|
|
304
509
|
for (const setting of configManager.getSchema()) {
|
|
305
510
|
if (!canImportTuiSetting(setting.key)) continue;
|
|
306
511
|
const value = readNestedSettingValue(record, setting.key);
|
|
307
|
-
if (value
|
|
512
|
+
if (value === undefined) continue;
|
|
513
|
+
importableSettings += 1;
|
|
514
|
+
values.set(setting.key, { value, source: source.id });
|
|
308
515
|
}
|
|
516
|
+
sources.push({ ...source, status: 'loaded', importableSettings });
|
|
309
517
|
} catch (error) {
|
|
310
|
-
|
|
518
|
+
const message = `${source.label}: ${error instanceof Error ? error.message : String(error)}`;
|
|
519
|
+
parseErrors.push(message);
|
|
520
|
+
sources.push({ ...source, status: 'error', importableSettings: 0, error: message });
|
|
311
521
|
}
|
|
312
522
|
}
|
|
313
|
-
|
|
523
|
+
|
|
524
|
+
const settings: TuiSettingImportPlanEntry[] = [];
|
|
525
|
+
for (const [key, entry] of values) {
|
|
526
|
+
const setting = agentWorkspaceSettingSchema(context, key);
|
|
527
|
+
if (!setting) continue;
|
|
528
|
+
const current = configManager.get(setting.key as ConfigKey);
|
|
529
|
+
const status: TuiImportStatus = valuesMatch(current, entry.value) ? 'unchanged' : 'would_import';
|
|
530
|
+
settings.push({
|
|
531
|
+
key: setting.key,
|
|
532
|
+
source: entry.source,
|
|
533
|
+
value: entry.value,
|
|
534
|
+
status,
|
|
535
|
+
current: redactImportValue(setting.key, current),
|
|
536
|
+
incoming: redactImportValue(setting.key, entry.value),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
const subscriptions = planTuiSubscriptions(context, parseErrors);
|
|
540
|
+
return { sources, settings, subscriptions, parseErrors };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function previewAgentWorkspaceTuiSettingsImport(context: CommandContext | null): TuiSettingsImportPreview | null {
|
|
544
|
+
const plan = buildTuiSettingsImportPlan(context);
|
|
545
|
+
if (!plan) return null;
|
|
546
|
+
return {
|
|
547
|
+
summary: {
|
|
548
|
+
settingsToImport: plan.settings.filter((entry) => entry.status === 'would_import').length,
|
|
549
|
+
settingsUnchanged: plan.settings.filter((entry) => entry.status === 'unchanged').length,
|
|
550
|
+
settingsSkipped: plan.settings.filter((entry) => entry.status === 'skipped').length,
|
|
551
|
+
activeSubscriptionsToImport: plan.subscriptions.filter((entry) => entry.kind === 'active' && entry.status === 'would_import').length,
|
|
552
|
+
pendingSubscriptionsToImport: plan.subscriptions.filter((entry) => entry.kind === 'pending' && entry.status === 'would_import').length,
|
|
553
|
+
subscriptionsUnchanged: plan.subscriptions.filter((entry) => entry.status === 'unchanged').length,
|
|
554
|
+
subscriptionsSkipped: plan.subscriptions.filter((entry) => entry.status === 'skipped').length,
|
|
555
|
+
parseErrors: plan.parseErrors.length,
|
|
556
|
+
},
|
|
557
|
+
sourceCatalog: GOODVIBES_SETTINGS_IMPORT_SOURCE_CATALOG,
|
|
558
|
+
sources: plan.sources,
|
|
559
|
+
settings: plan.settings.map((entry) => ({
|
|
560
|
+
key: entry.key,
|
|
561
|
+
source: entry.source,
|
|
562
|
+
status: entry.status,
|
|
563
|
+
current: entry.current,
|
|
564
|
+
incoming: entry.incoming,
|
|
565
|
+
...(entry.reason ? { reason: entry.reason } : {}),
|
|
566
|
+
})),
|
|
567
|
+
subscriptions: plan.subscriptions,
|
|
568
|
+
policy: {
|
|
569
|
+
effect: 'read-only',
|
|
570
|
+
confirmation: 'required-for-apply',
|
|
571
|
+
boundary: 'Preview does not mutate state. Apply imports only Agent-owned settings and provider subscriptions from published GoodVibes platform sources.',
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export async function importAgentWorkspaceTuiSettings(context: CommandContext | null): Promise<TuiSettingsImportOutcome> {
|
|
577
|
+
const shellPaths = context?.workspace?.shellPaths;
|
|
578
|
+
const configManager = context?.platform?.configManager;
|
|
579
|
+
if (!context || !shellPaths || !configManager) {
|
|
580
|
+
return {
|
|
581
|
+
status: 'GoodVibes settings import is unavailable in this runtime.',
|
|
582
|
+
runtimeSnapshot: null,
|
|
583
|
+
result: {
|
|
584
|
+
kind: 'error',
|
|
585
|
+
title: 'Import unavailable',
|
|
586
|
+
detail: 'The workspace cannot locate shell paths or the Agent config manager.',
|
|
587
|
+
safety: 'safe',
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const plan = buildTuiSettingsImportPlan(context);
|
|
593
|
+
const values = plan?.settings ?? [];
|
|
594
|
+
const subscriptionParseErrors: string[] = [];
|
|
595
|
+
const subscriptions = applyTuiSubscriptions(context, subscriptionParseErrors);
|
|
596
|
+
const parseErrors: string[] = [...new Set([...(plan?.parseErrors ?? []), ...subscriptionParseErrors])];
|
|
314
597
|
const subscriptionImports = subscriptions.importedActive.length + subscriptions.importedPending.length;
|
|
315
598
|
const subscriptionUnchanged = subscriptions.unchangedActive.length + subscriptions.unchangedPending.length;
|
|
316
599
|
|
|
317
|
-
if (values.
|
|
600
|
+
if (values.length === 0 && subscriptionImports === 0 && subscriptionUnchanged === 0 && subscriptions.skipped.length === 0) {
|
|
318
601
|
const detail = parseErrors.length > 0
|
|
319
602
|
? `No importable settings or subscriptions found. ${parseErrors.join('; ')}`
|
|
320
|
-
: 'No GoodVibes
|
|
603
|
+
: 'No GoodVibes settings or subscription store with importable Agent-owned state was found.';
|
|
321
604
|
return {
|
|
322
|
-
status: 'No GoodVibes
|
|
605
|
+
status: 'No GoodVibes settings imported.',
|
|
323
606
|
runtimeSnapshot: null,
|
|
324
607
|
result: {
|
|
325
608
|
kind: parseErrors.length > 0 ? 'error' : 'guidance',
|
|
@@ -333,10 +616,10 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
|
|
|
333
616
|
const imported: string[] = [];
|
|
334
617
|
const unchanged: string[] = [];
|
|
335
618
|
const skipped: string[] = [];
|
|
336
|
-
for (const
|
|
337
|
-
const setting = agentWorkspaceSettingSchema(context, key);
|
|
619
|
+
for (const entry of values) {
|
|
620
|
+
const setting = agentWorkspaceSettingSchema(context, entry.key);
|
|
338
621
|
if (!setting) continue;
|
|
339
|
-
if (valuesMatch(configManager.get(setting.key as ConfigKey), entry.value)) {
|
|
622
|
+
if (entry.status === 'unchanged' || valuesMatch(configManager.get(setting.key as ConfigKey), entry.value)) {
|
|
340
623
|
unchanged.push(setting.key);
|
|
341
624
|
continue;
|
|
342
625
|
}
|
|
@@ -352,11 +635,11 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
|
|
|
352
635
|
const unchangedCount = unchanged.length + subscriptionUnchanged;
|
|
353
636
|
|
|
354
637
|
return {
|
|
355
|
-
status: changedCount > 0 ? `Imported ${changedCount} GoodVibes
|
|
638
|
+
status: changedCount > 0 ? `Imported ${changedCount} GoodVibes settings item(s).` : 'No GoodVibes settings changed.',
|
|
356
639
|
runtimeSnapshot: context ? buildAgentWorkspaceRuntimeSnapshot(context) : null,
|
|
357
640
|
result: {
|
|
358
641
|
kind: skipped.length > 0 && changedCount === 0 ? 'error' : changedCount > 0 ? 'refreshed' : 'guidance',
|
|
359
|
-
title: changedCount > 0 ? 'GoodVibes
|
|
642
|
+
title: changedCount > 0 ? 'GoodVibes settings imported' : 'No settings changed',
|
|
360
643
|
detail: [
|
|
361
644
|
imported.length > 0 ? `Imported: ${imported.slice(0, 10).join(', ')}${imported.length > 10 ? `, +${imported.length - 10} more` : ''}.` : '',
|
|
362
645
|
subscriptions.importedActive.length > 0 ? `Imported active subscription(s): ${subscriptions.importedActive.join(', ')}.` : '',
|