@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.
Files changed (256) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.md +46 -30
  3. package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
  4. package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
  5. package/dist/package/main.js +132662 -91399
  6. package/docs/README.md +14 -7
  7. package/docs/channels-remote-and-api.md +8 -5
  8. package/docs/connected-host.md +14 -12
  9. package/docs/getting-started.md +56 -30
  10. package/docs/knowledge-artifacts-and-multimodal.md +7 -5
  11. package/docs/project-planning.md +2 -2
  12. package/docs/providers-and-routing.md +11 -3
  13. package/docs/tools-and-commands.md +114 -45
  14. package/docs/voice-and-live-tts.md +3 -1
  15. package/package.json +3 -2
  16. package/release/release-notes.md +130 -5
  17. package/release/release-readiness.json +120 -90
  18. package/src/agent/assistant-cockpit.ts +247 -0
  19. package/src/agent/autonomy-schedule-format.ts +96 -0
  20. package/src/agent/autonomy-schedule.ts +514 -0
  21. package/src/agent/channel-delivery-receipts.ts +292 -0
  22. package/src/agent/competitive-feature-inventory.ts +296 -0
  23. package/src/agent/document-registry-types.ts +106 -0
  24. package/src/agent/document-registry.ts +734 -0
  25. package/src/agent/harness-control.ts +2 -2
  26. package/src/agent/memory-prompt.ts +2 -2
  27. package/src/agent/operator-actions.ts +50 -6
  28. package/src/agent/persona-registry.ts +8 -0
  29. package/src/agent/project-context-files.ts +273 -0
  30. package/src/agent/prompt-context-receipts.ts +502 -0
  31. package/src/agent/reminder-schedule-format.ts +16 -2
  32. package/src/agent/research-run-registry.ts +582 -0
  33. package/src/agent/research-source-registry.ts +441 -0
  34. package/src/agent/routine-registry.ts +19 -3
  35. package/src/agent/routine-schedule-format.ts +27 -2
  36. package/src/agent/runtime-profile-starters.ts +7 -0
  37. package/src/agent/runtime-profile.ts +106 -4
  38. package/src/agent/schedule-edit-format.ts +129 -0
  39. package/src/agent/schedule-edit.ts +496 -0
  40. package/src/agent/schedule-next-routes.ts +42 -0
  41. package/src/agent/setup-wizard-checkpoint.ts +140 -0
  42. package/src/agent/setup-wizard.ts +447 -0
  43. package/src/agent/skill-registry-types.ts +102 -0
  44. package/src/agent/skill-registry.ts +81 -107
  45. package/src/agent/vibe-confirmation-routes.ts +62 -0
  46. package/src/agent/vibe-file.ts +285 -0
  47. package/src/cli/agent-knowledge-command.ts +45 -2
  48. package/src/cli/help.ts +3 -0
  49. package/src/cli/profiles-command.ts +25 -11
  50. package/src/config/agent-settings-policy.ts +1 -18
  51. package/src/input/agent-workspace-activation.ts +53 -0
  52. package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
  53. package/src/input/agent-workspace-artifact-metadata.ts +17 -0
  54. package/src/input/agent-workspace-basic-command-editors.ts +5 -1
  55. package/src/input/agent-workspace-categories.ts +153 -155
  56. package/src/input/agent-workspace-channel-triage.ts +481 -0
  57. package/src/input/agent-workspace-channels.ts +263 -0
  58. package/src/input/agent-workspace-command-editor.ts +152 -0
  59. package/src/input/agent-workspace-context-snapshot.ts +349 -0
  60. package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
  61. package/src/input/agent-workspace-document-editor.ts +502 -0
  62. package/src/input/agent-workspace-document-ops-editor.ts +523 -0
  63. package/src/input/agent-workspace-host-category.ts +1 -1
  64. package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
  65. package/src/input/agent-workspace-model-compare-editor.ts +376 -0
  66. package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
  67. package/src/input/agent-workspace-model-compare-types.ts +82 -0
  68. package/src/input/agent-workspace-model-compare-utils.ts +35 -0
  69. package/src/input/agent-workspace-onboarding-categories.ts +141 -0
  70. package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
  71. package/src/input/agent-workspace-operations-command-editors.ts +22 -0
  72. package/src/input/agent-workspace-research-report-editor.ts +212 -0
  73. package/src/input/agent-workspace-research-run-editor.ts +144 -0
  74. package/src/input/agent-workspace-research-source-editor.ts +167 -0
  75. package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
  76. package/src/input/agent-workspace-review-packet-utils.ts +265 -0
  77. package/src/input/agent-workspace-settings.ts +262 -30
  78. package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
  79. package/src/input/agent-workspace-setup-snapshot.ts +198 -0
  80. package/src/input/agent-workspace-setup.ts +34 -1
  81. package/src/input/agent-workspace-snapshot.ts +162 -147
  82. package/src/input/agent-workspace-types.ts +287 -2
  83. package/src/input/agent-workspace.ts +18 -0
  84. package/src/input/command-registry.ts +9 -2
  85. package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
  86. package/src/input/commands/channels-runtime.ts +102 -114
  87. package/src/input/commands/delegation-runtime.ts +87 -24
  88. package/src/input/commands/knowledge-browser-flags.ts +12 -0
  89. package/src/input/commands/knowledge.ts +28 -14
  90. package/src/input/commands/operator-actions-runtime.ts +6 -3
  91. package/src/input/commands/schedule-runtime.ts +71 -14
  92. package/src/input/commands/session-content.ts +2 -2
  93. package/src/input/commands/shell-core.ts +2 -0
  94. package/src/input/commands/vibe-runtime.ts +140 -0
  95. package/src/input/commands.ts +2 -0
  96. package/src/input/connected-host-routes.ts +123 -0
  97. package/src/input/settings-modal-types.ts +14 -0
  98. package/src/main.ts +15 -15
  99. package/src/panels/qr-panel.ts +2 -2
  100. package/src/renderer/agent-workspace-context-lines.ts +686 -0
  101. package/src/renderer/agent-workspace.ts +30 -419
  102. package/src/renderer/settings-modal-helpers.ts +12 -0
  103. package/src/renderer/settings-modal.ts +12 -0
  104. package/src/runtime/bootstrap-command-context.ts +11 -1
  105. package/src/runtime/bootstrap-command-parts.ts +14 -3
  106. package/src/runtime/bootstrap-core.ts +43 -1
  107. package/src/runtime/bootstrap-shell.ts +6 -0
  108. package/src/runtime/bootstrap.ts +113 -30
  109. package/src/runtime/connected-host-auth.ts +3 -2
  110. package/src/runtime/execution-ledger.ts +231 -0
  111. package/src/runtime/services.ts +88 -10
  112. package/src/runtime/tool-permission-safety.ts +287 -0
  113. package/src/shell/session-continuity-hints.ts +39 -0
  114. package/src/tools/agent-artifacts-tool.ts +606 -0
  115. package/src/tools/agent-audit-tool.ts +155 -0
  116. package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
  117. package/src/tools/agent-autonomy-tool.ts +140 -0
  118. package/src/tools/agent-channel-send-tool.ts +24 -2
  119. package/src/tools/agent-channels-tool.ts +140 -0
  120. package/src/tools/agent-computer-tool.ts +168 -0
  121. package/src/tools/agent-context-policy.ts +267 -10
  122. package/src/tools/agent-context-tool.ts +143 -0
  123. package/src/tools/agent-delegation-tool.ts +128 -0
  124. package/src/tools/agent-device-tool.ts +240 -0
  125. package/src/tools/agent-documents-tool.ts +684 -0
  126. package/src/tools/agent-execution-tool.ts +230 -0
  127. package/src/tools/agent-harness-agent-orchestration.ts +703 -0
  128. package/src/tools/agent-harness-autonomy-intake.ts +785 -0
  129. package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
  130. package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
  131. package/src/tools/agent-harness-autonomy-queue.ts +560 -0
  132. package/src/tools/agent-harness-background-processes-types.ts +35 -0
  133. package/src/tools/agent-harness-background-processes.ts +794 -0
  134. package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
  135. package/src/tools/agent-harness-browser-control.ts +338 -0
  136. package/src/tools/agent-harness-channel-metadata.ts +168 -10
  137. package/src/tools/agent-harness-cli-command-policy.ts +110 -0
  138. package/src/tools/agent-harness-command-catalog.ts +4 -2
  139. package/src/tools/agent-harness-command-runner.ts +42 -0
  140. package/src/tools/agent-harness-connected-host-status.ts +7 -4
  141. package/src/tools/agent-harness-delegation-posture.ts +234 -19
  142. package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
  143. package/src/tools/agent-harness-document-ops-types.ts +72 -0
  144. package/src/tools/agent-harness-document-ops.ts +671 -0
  145. package/src/tools/agent-harness-execution-history.ts +489 -0
  146. package/src/tools/agent-harness-execution-posture.ts +382 -0
  147. package/src/tools/agent-harness-file-recovery.ts +135 -0
  148. package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
  149. package/src/tools/agent-harness-learning-curator-common.ts +102 -0
  150. package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
  151. package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
  152. package/src/tools/agent-harness-learning-curator-types.ts +151 -0
  153. package/src/tools/agent-harness-learning-curator.ts +417 -0
  154. package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
  155. package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
  156. package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
  157. package/src/tools/agent-harness-media-posture.ts +208 -2
  158. package/src/tools/agent-harness-memory-posture.ts +556 -0
  159. package/src/tools/agent-harness-metadata.ts +100 -168
  160. package/src/tools/agent-harness-mode-catalog.ts +84 -33
  161. package/src/tools/agent-harness-model-catalog.ts +162 -0
  162. package/src/tools/agent-harness-model-provider-health.ts +186 -0
  163. package/src/tools/agent-harness-model-readiness.ts +406 -0
  164. package/src/tools/agent-harness-model-routing-types.ts +266 -0
  165. package/src/tools/agent-harness-model-routing-utils.ts +30 -0
  166. package/src/tools/agent-harness-model-routing.ts +137 -190
  167. package/src/tools/agent-harness-operator-methods.ts +115 -133
  168. package/src/tools/agent-harness-pairing-posture.ts +431 -18
  169. package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
  170. package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
  171. package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
  172. package/src/tools/agent-harness-personal-ops-records.ts +732 -0
  173. package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
  174. package/src/tools/agent-harness-personal-ops-types.ts +222 -0
  175. package/src/tools/agent-harness-personal-ops.ts +606 -0
  176. package/src/tools/agent-harness-project-context.ts +144 -0
  177. package/src/tools/agent-harness-prompt-context.ts +589 -0
  178. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  179. package/src/tools/agent-harness-release-evidence.ts +9 -7
  180. package/src/tools/agent-harness-release-readiness.ts +9 -7
  181. package/src/tools/agent-harness-research-briefing.ts +427 -0
  182. package/src/tools/agent-harness-research-queue.ts +250 -0
  183. package/src/tools/agent-harness-research-runs.ts +295 -0
  184. package/src/tools/agent-harness-research-workflow.ts +322 -0
  185. package/src/tools/agent-harness-security-posture.ts +2 -2
  186. package/src/tools/agent-harness-service-posture.ts +9 -9
  187. package/src/tools/agent-harness-session-metadata.ts +3 -3
  188. package/src/tools/agent-harness-setup-connected-host.ts +501 -0
  189. package/src/tools/agent-harness-setup-describe.ts +557 -0
  190. package/src/tools/agent-harness-setup-handoffs.ts +502 -0
  191. package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
  192. package/src/tools/agent-harness-setup-plan.ts +253 -0
  193. package/src/tools/agent-harness-setup-posture-types.ts +218 -0
  194. package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
  195. package/src/tools/agent-harness-setup-posture.ts +449 -221
  196. package/src/tools/agent-harness-setup-smoke.ts +508 -0
  197. package/src/tools/agent-harness-sudo-posture.ts +114 -0
  198. package/src/tools/agent-harness-tool-schema.ts +120 -7
  199. package/src/tools/agent-harness-tool-types.ts +78 -0
  200. package/src/tools/agent-harness-tool-utils.ts +43 -0
  201. package/src/tools/agent-harness-tool.ts +311 -370
  202. package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
  203. package/src/tools/agent-harness-ui-surface-types.ts +46 -0
  204. package/src/tools/agent-harness-vibe-health.ts +105 -0
  205. package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
  206. package/src/tools/agent-harness-workspace-actions.ts +114 -8
  207. package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
  208. package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
  209. package/src/tools/agent-host-tool.ts +159 -0
  210. package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
  211. package/src/tools/agent-learning-consolidation-core.ts +327 -0
  212. package/src/tools/agent-learning-consolidation-tool.ts +758 -0
  213. package/src/tools/agent-memory-tool.ts +214 -0
  214. package/src/tools/agent-model-compare-export.ts +315 -0
  215. package/src/tools/agent-model-compare-handoff.ts +592 -0
  216. package/src/tools/agent-model-compare-judgment.ts +633 -0
  217. package/src/tools/agent-model-compare-run.ts +722 -0
  218. package/src/tools/agent-model-compare-tool.ts +698 -0
  219. package/src/tools/agent-model-compare-types.ts +191 -0
  220. package/src/tools/agent-model-compare-utils.ts +93 -0
  221. package/src/tools/agent-models-tool.ts +208 -0
  222. package/src/tools/agent-operator-action-tool.ts +1 -1
  223. package/src/tools/agent-operator-method-tool.ts +643 -0
  224. package/src/tools/agent-personal-ops-tool.ts +197 -0
  225. package/src/tools/agent-policy-explanation.ts +415 -0
  226. package/src/tools/agent-research-report-tool.ts +608 -0
  227. package/src/tools/agent-research-runs-tool.ts +434 -0
  228. package/src/tools/agent-research-sources-tool.ts +443 -0
  229. package/src/tools/agent-research-tool.ts +687 -0
  230. package/src/tools/agent-review-packet-presets-core.ts +757 -0
  231. package/src/tools/agent-review-packet-presets-tool.ts +466 -0
  232. package/src/tools/agent-review-packet-share-tool.ts +305 -0
  233. package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
  234. package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
  235. package/src/tools/agent-route-planner-candidates-work.ts +251 -0
  236. package/src/tools/agent-route-planner-helpers.ts +667 -0
  237. package/src/tools/agent-route-planner.ts +185 -0
  238. package/src/tools/agent-route-tool.ts +105 -0
  239. package/src/tools/agent-schedule-edit-tool.ts +210 -0
  240. package/src/tools/agent-schedule-tool.ts +282 -0
  241. package/src/tools/agent-security-tool.ts +145 -0
  242. package/src/tools/agent-sessions-tool.ts +122 -0
  243. package/src/tools/agent-settings-import-tool.ts +104 -0
  244. package/src/tools/agent-settings-tool.ts +176 -0
  245. package/src/tools/agent-setup-tool.ts +226 -0
  246. package/src/tools/agent-support-tool.ts +122 -0
  247. package/src/tools/agent-terminal-process-tools.ts +167 -0
  248. package/src/tools/agent-tool-policy-guard-types.ts +77 -0
  249. package/src/tools/agent-tool-policy-guard.ts +110 -89
  250. package/src/tools/agent-vibe-tool.ts +297 -0
  251. package/src/tools/agent-work-plan-tool.ts +265 -6
  252. package/src/tools/agent-workspace-tool.ts +305 -0
  253. package/src/tools/artifact-archive.ts +169 -0
  254. package/src/tools/tool-definition-compaction.ts +36 -0
  255. package/src/tools/tool-execution-safety.ts +41 -0
  256. 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,15 @@ function valuesMatch(left: unknown, right: unknown): boolean {
84
173
  return JSON.stringify(left) === JSON.stringify(right);
85
174
  }
86
175
 
176
+ function redactImportValue(key: string, value: unknown): unknown {
177
+ if (typeof value !== 'string') return value;
178
+ if (!value) return value;
179
+ if (/(?:secret|token|password|api[-_.]?key|signing)/i.test(key)) {
180
+ return value.startsWith('goodvibes://secrets/') ? '<secret-ref>' : '<redacted>';
181
+ }
182
+ return value;
183
+ }
184
+
87
185
  function readRecordMap(record: Record<string, unknown>, key: string): readonly unknown[] {
88
186
  const value = record[key];
89
187
  return isRecord(value) ? Object.values(value) : [];
@@ -109,7 +207,45 @@ function isPendingSubscriptionLogin(value: unknown): value is PendingSubscriptio
109
207
  && typeof value.createdAt === 'number';
110
208
  }
111
209
 
112
- function importTuiSubscriptions(context: CommandContext, parseErrors: string[]): {
210
+ function planTuiSubscriptions(context: CommandContext, parseErrors: string[]): readonly TuiSubscriptionImportPlanEntry[] {
211
+ const shellPaths = context.workspace.shellPaths;
212
+ const manager = context.platform.subscriptionManager;
213
+ const result: TuiSubscriptionImportPlanEntry[] = [];
214
+ if (!shellPaths || !manager) return result;
215
+
216
+ const sourcePath = shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'subscriptions.json');
217
+ try {
218
+ const store = readJsonRecord(sourcePath);
219
+ if (!store) return result;
220
+ for (const entry of readRecordMap(store, 'subscriptions')) {
221
+ if (!isProviderSubscription(entry)) {
222
+ result.push({ kind: 'active', status: 'skipped', reason: 'active subscription with invalid shape' });
223
+ continue;
224
+ }
225
+ if (valuesMatch(manager.get(entry.provider), entry)) {
226
+ result.push({ provider: entry.provider, kind: 'active', status: 'unchanged' });
227
+ continue;
228
+ }
229
+ result.push({ provider: entry.provider, kind: 'active', status: 'would_import' });
230
+ }
231
+ for (const entry of readRecordMap(store, 'pending')) {
232
+ if (!isPendingSubscriptionLogin(entry)) {
233
+ result.push({ kind: 'pending', status: 'skipped', reason: 'pending subscription with invalid shape' });
234
+ continue;
235
+ }
236
+ if (valuesMatch(manager.getPending(entry.provider), entry)) {
237
+ result.push({ provider: entry.provider, kind: 'pending', status: 'unchanged' });
238
+ continue;
239
+ }
240
+ result.push({ provider: entry.provider, kind: 'pending', status: 'would_import' });
241
+ }
242
+ } catch (error) {
243
+ parseErrors.push(`subscriptions: ${error instanceof Error ? error.message : String(error)}`);
244
+ }
245
+ return result;
246
+ }
247
+
248
+ function applyTuiSubscriptions(context: CommandContext, parseErrors: string[]): {
113
249
  readonly importedActive: string[];
114
250
  readonly importedPending: string[];
115
251
  readonly unchangedActive: string[];
@@ -275,51 +411,147 @@ export async function applyAgentWorkspaceSettingValue(
275
411
  }
276
412
  }
277
413
 
278
- export async function importAgentWorkspaceTuiSettings(context: CommandContext | null): Promise<TuiSettingsImportOutcome> {
414
+ function buildTuiSettingsImportPlan(context: CommandContext | null): TuiSettingsImportPlan | null {
279
415
  const shellPaths = context?.workspace?.shellPaths;
280
416
  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
- }
417
+ if (!context || !shellPaths || !configManager) return null;
293
418
 
294
- const sources = [
295
- { label: 'user', path: shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json') },
296
- { label: 'project', path: shellPaths.resolveProjectPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json') },
419
+ const sourceInputs: readonly Omit<TuiSettingsImportSource, 'status' | 'importableSettings' | 'error'>[] = [
420
+ {
421
+ id: 'goodvibes-tui:user-settings',
422
+ label: 'goodvibes-tui user settings',
423
+ path: shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json'),
424
+ sourcePackage: 'goodvibes-tui',
425
+ surfaceRoot: GOODVIBES_TUI_SURFACE_ROOT,
426
+ scope: 'user',
427
+ ownership: 'source-owned',
428
+ mutatesSource: false,
429
+ },
430
+ {
431
+ id: 'goodvibes-tui:project-settings',
432
+ label: 'goodvibes-tui project settings',
433
+ path: shellPaths.resolveProjectPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json'),
434
+ sourcePackage: 'goodvibes-tui',
435
+ surfaceRoot: GOODVIBES_TUI_SURFACE_ROOT,
436
+ scope: 'project',
437
+ ownership: 'source-owned',
438
+ mutatesSource: false,
439
+ },
297
440
  ];
298
441
  const values = new Map<string, { readonly value: unknown; readonly source: string }>();
299
442
  const parseErrors: string[] = [];
300
- for (const source of sources) {
443
+ const sources: TuiSettingsImportSource[] = [];
444
+ for (const source of sourceInputs) {
445
+ if (!existsSync(source.path)) {
446
+ sources.push({ ...source, status: 'missing', importableSettings: 0 });
447
+ continue;
448
+ }
301
449
  try {
302
450
  const record = readJsonRecord(source.path);
303
- if (!record) continue;
451
+ if (!record) {
452
+ const message = `${source.label}: settings store is not an object`;
453
+ parseErrors.push(message);
454
+ sources.push({ ...source, status: 'invalid', importableSettings: 0, error: message });
455
+ continue;
456
+ }
457
+ let importableSettings = 0;
304
458
  for (const setting of configManager.getSchema()) {
305
459
  if (!canImportTuiSetting(setting.key)) continue;
306
460
  const value = readNestedSettingValue(record, setting.key);
307
- if (value !== undefined) values.set(setting.key, { value, source: source.label });
461
+ if (value === undefined) continue;
462
+ importableSettings += 1;
463
+ values.set(setting.key, { value, source: source.id });
308
464
  }
465
+ sources.push({ ...source, status: 'loaded', importableSettings });
309
466
  } catch (error) {
310
- parseErrors.push(`${source.label}: ${error instanceof Error ? error.message : String(error)}`);
467
+ const message = `${source.label}: ${error instanceof Error ? error.message : String(error)}`;
468
+ parseErrors.push(message);
469
+ sources.push({ ...source, status: 'error', importableSettings: 0, error: message });
311
470
  }
312
471
  }
313
- const subscriptions = importTuiSubscriptions(context, parseErrors);
472
+
473
+ const settings: TuiSettingImportPlanEntry[] = [];
474
+ for (const [key, entry] of values) {
475
+ const setting = agentWorkspaceSettingSchema(context, key);
476
+ if (!setting) continue;
477
+ const current = configManager.get(setting.key as ConfigKey);
478
+ const status: TuiImportStatus = valuesMatch(current, entry.value) ? 'unchanged' : 'would_import';
479
+ settings.push({
480
+ key: setting.key,
481
+ source: entry.source,
482
+ value: entry.value,
483
+ status,
484
+ current: redactImportValue(setting.key, current),
485
+ incoming: redactImportValue(setting.key, entry.value),
486
+ });
487
+ }
488
+ const subscriptions = planTuiSubscriptions(context, parseErrors);
489
+ return { sources, settings, subscriptions, parseErrors };
490
+ }
491
+
492
+ export function previewAgentWorkspaceTuiSettingsImport(context: CommandContext | null): TuiSettingsImportPreview | null {
493
+ const plan = buildTuiSettingsImportPlan(context);
494
+ if (!plan) return null;
495
+ return {
496
+ summary: {
497
+ settingsToImport: plan.settings.filter((entry) => entry.status === 'would_import').length,
498
+ settingsUnchanged: plan.settings.filter((entry) => entry.status === 'unchanged').length,
499
+ settingsSkipped: plan.settings.filter((entry) => entry.status === 'skipped').length,
500
+ activeSubscriptionsToImport: plan.subscriptions.filter((entry) => entry.kind === 'active' && entry.status === 'would_import').length,
501
+ pendingSubscriptionsToImport: plan.subscriptions.filter((entry) => entry.kind === 'pending' && entry.status === 'would_import').length,
502
+ subscriptionsUnchanged: plan.subscriptions.filter((entry) => entry.status === 'unchanged').length,
503
+ subscriptionsSkipped: plan.subscriptions.filter((entry) => entry.status === 'skipped').length,
504
+ parseErrors: plan.parseErrors.length,
505
+ },
506
+ sourceCatalog: GOODVIBES_SETTINGS_IMPORT_SOURCE_CATALOG,
507
+ sources: plan.sources,
508
+ settings: plan.settings.map((entry) => ({
509
+ key: entry.key,
510
+ source: entry.source,
511
+ status: entry.status,
512
+ current: entry.current,
513
+ incoming: entry.incoming,
514
+ ...(entry.reason ? { reason: entry.reason } : {}),
515
+ })),
516
+ subscriptions: plan.subscriptions,
517
+ policy: {
518
+ effect: 'read-only',
519
+ confirmation: 'required-for-apply',
520
+ boundary: 'Preview does not mutate state. Apply imports only Agent-owned settings and provider subscriptions from published GoodVibes platform sources.',
521
+ },
522
+ };
523
+ }
524
+
525
+ export async function importAgentWorkspaceTuiSettings(context: CommandContext | null): Promise<TuiSettingsImportOutcome> {
526
+ const shellPaths = context?.workspace?.shellPaths;
527
+ const configManager = context?.platform?.configManager;
528
+ if (!context || !shellPaths || !configManager) {
529
+ return {
530
+ status: 'GoodVibes settings import is unavailable in this runtime.',
531
+ runtimeSnapshot: null,
532
+ result: {
533
+ kind: 'error',
534
+ title: 'Import unavailable',
535
+ detail: 'The workspace cannot locate shell paths or the Agent config manager.',
536
+ safety: 'safe',
537
+ },
538
+ };
539
+ }
540
+
541
+ const plan = buildTuiSettingsImportPlan(context);
542
+ const values = plan?.settings ?? [];
543
+ const subscriptionParseErrors: string[] = [];
544
+ const subscriptions = applyTuiSubscriptions(context, subscriptionParseErrors);
545
+ const parseErrors: string[] = [...new Set([...(plan?.parseErrors ?? []), ...subscriptionParseErrors])];
314
546
  const subscriptionImports = subscriptions.importedActive.length + subscriptions.importedPending.length;
315
547
  const subscriptionUnchanged = subscriptions.unchangedActive.length + subscriptions.unchangedPending.length;
316
548
 
317
- if (values.size === 0 && subscriptionImports === 0 && subscriptionUnchanged === 0 && subscriptions.skipped.length === 0) {
549
+ if (values.length === 0 && subscriptionImports === 0 && subscriptionUnchanged === 0 && subscriptions.skipped.length === 0) {
318
550
  const detail = parseErrors.length > 0
319
551
  ? `No importable settings or subscriptions found. ${parseErrors.join('; ')}`
320
- : 'No GoodVibes TUI settings or subscription store with importable Agent-owned state was found.';
552
+ : 'No GoodVibes settings or subscription store with importable Agent-owned state was found.';
321
553
  return {
322
- status: 'No GoodVibes TUI settings imported.',
554
+ status: 'No GoodVibes settings imported.',
323
555
  runtimeSnapshot: null,
324
556
  result: {
325
557
  kind: parseErrors.length > 0 ? 'error' : 'guidance',
@@ -333,10 +565,10 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
333
565
  const imported: string[] = [];
334
566
  const unchanged: string[] = [];
335
567
  const skipped: string[] = [];
336
- for (const [key, entry] of values) {
337
- const setting = agentWorkspaceSettingSchema(context, key);
568
+ for (const entry of values) {
569
+ const setting = agentWorkspaceSettingSchema(context, entry.key);
338
570
  if (!setting) continue;
339
- if (valuesMatch(configManager.get(setting.key as ConfigKey), entry.value)) {
571
+ if (entry.status === 'unchanged' || valuesMatch(configManager.get(setting.key as ConfigKey), entry.value)) {
340
572
  unchanged.push(setting.key);
341
573
  continue;
342
574
  }
@@ -352,11 +584,11 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
352
584
  const unchangedCount = unchanged.length + subscriptionUnchanged;
353
585
 
354
586
  return {
355
- status: changedCount > 0 ? `Imported ${changedCount} GoodVibes TUI item(s).` : 'No GoodVibes TUI settings changed.',
587
+ status: changedCount > 0 ? `Imported ${changedCount} GoodVibes settings item(s).` : 'No GoodVibes settings changed.',
356
588
  runtimeSnapshot: context ? buildAgentWorkspaceRuntimeSnapshot(context) : null,
357
589
  result: {
358
590
  kind: skipped.length > 0 && changedCount === 0 ? 'error' : changedCount > 0 ? 'refreshed' : 'guidance',
359
- title: changedCount > 0 ? 'GoodVibes TUI settings imported' : 'No settings changed',
591
+ title: changedCount > 0 ? 'GoodVibes settings imported' : 'No settings changed',
360
592
  detail: [
361
593
  imported.length > 0 ? `Imported: ${imported.slice(0, 10).join(', ')}${imported.length > 10 ? `, +${imported.length - 10} more` : ''}.` : '',
362
594
  subscriptions.importedActive.length > 0 ? `Imported active subscription(s): ${subscriptions.importedActive.join(', ')}.` : '',