@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.
Files changed (274) hide show
  1. package/CHANGELOG.md +329 -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 +138903 -91477
  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 +58 -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 +116 -45
  14. package/docs/voice-and-live-tts.md +3 -1
  15. package/package.json +3 -2
  16. package/release/release-notes.md +142 -5
  17. package/release/release-readiness.json +124 -94
  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 +294 -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-artifact-receipts.ts +366 -0
  42. package/src/agent/setup-wizard-checkpoint.ts +140 -0
  43. package/src/agent/setup-wizard.ts +683 -0
  44. package/src/agent/skill-registry-types.ts +102 -0
  45. package/src/agent/skill-registry.ts +81 -107
  46. package/src/agent/vibe-confirmation-routes.ts +62 -0
  47. package/src/agent/vibe-file.ts +285 -0
  48. package/src/cli/agent-knowledge-command.ts +45 -2
  49. package/src/cli/help.ts +3 -0
  50. package/src/cli/profiles-command.ts +25 -11
  51. package/src/config/agent-settings-policy.ts +1 -18
  52. package/src/input/agent-workspace-activation.ts +53 -0
  53. package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
  54. package/src/input/agent-workspace-artifact-metadata.ts +17 -0
  55. package/src/input/agent-workspace-basic-command-editors.ts +5 -1
  56. package/src/input/agent-workspace-categories.ts +153 -155
  57. package/src/input/agent-workspace-channel-triage.ts +481 -0
  58. package/src/input/agent-workspace-channels.ts +263 -0
  59. package/src/input/agent-workspace-command-editor.ts +152 -0
  60. package/src/input/agent-workspace-context-snapshot.ts +349 -0
  61. package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
  62. package/src/input/agent-workspace-document-editor.ts +502 -0
  63. package/src/input/agent-workspace-document-ops-editor.ts +523 -0
  64. package/src/input/agent-workspace-host-category.ts +1 -1
  65. package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
  66. package/src/input/agent-workspace-model-compare-editor.ts +376 -0
  67. package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
  68. package/src/input/agent-workspace-model-compare-types.ts +82 -0
  69. package/src/input/agent-workspace-model-compare-utils.ts +35 -0
  70. package/src/input/agent-workspace-onboarding-categories.ts +141 -0
  71. package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
  72. package/src/input/agent-workspace-operations-command-editors.ts +22 -0
  73. package/src/input/agent-workspace-research-report-editor.ts +212 -0
  74. package/src/input/agent-workspace-research-run-editor.ts +144 -0
  75. package/src/input/agent-workspace-research-source-editor.ts +167 -0
  76. package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
  77. package/src/input/agent-workspace-review-packet-utils.ts +265 -0
  78. package/src/input/agent-workspace-settings.ts +313 -30
  79. package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
  80. package/src/input/agent-workspace-setup-snapshot.ts +217 -0
  81. package/src/input/agent-workspace-setup.ts +61 -1
  82. package/src/input/agent-workspace-snapshot.ts +184 -149
  83. package/src/input/agent-workspace-types.ts +287 -2
  84. package/src/input/agent-workspace.ts +23 -1
  85. package/src/input/command-registry.ts +9 -2
  86. package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
  87. package/src/input/commands/channels-runtime.ts +102 -114
  88. package/src/input/commands/delegation-runtime.ts +87 -24
  89. package/src/input/commands/knowledge-browser-flags.ts +12 -0
  90. package/src/input/commands/knowledge.ts +28 -14
  91. package/src/input/commands/operator-actions-runtime.ts +6 -3
  92. package/src/input/commands/schedule-runtime.ts +71 -14
  93. package/src/input/commands/session-content.ts +2 -2
  94. package/src/input/commands/shell-core.ts +2 -0
  95. package/src/input/commands/vibe-runtime.ts +140 -0
  96. package/src/input/commands.ts +2 -0
  97. package/src/input/connected-host-routes.ts +123 -0
  98. package/src/input/settings-modal-types.ts +14 -0
  99. package/src/input/setup-wizard-live-receipts.ts +76 -0
  100. package/src/main.ts +15 -15
  101. package/src/panels/qr-panel.ts +2 -2
  102. package/src/renderer/agent-workspace-context-lines.ts +701 -0
  103. package/src/renderer/agent-workspace.ts +68 -421
  104. package/src/renderer/settings-modal-helpers.ts +12 -0
  105. package/src/renderer/settings-modal.ts +12 -0
  106. package/src/runtime/bootstrap-command-context.ts +11 -1
  107. package/src/runtime/bootstrap-command-parts.ts +14 -3
  108. package/src/runtime/bootstrap-core.ts +39 -1
  109. package/src/runtime/bootstrap-shell.ts +6 -0
  110. package/src/runtime/bootstrap.ts +113 -30
  111. package/src/runtime/connected-host-auth.ts +3 -2
  112. package/src/runtime/execution-ledger.ts +231 -0
  113. package/src/runtime/services.ts +88 -10
  114. package/src/runtime/tool-permission-safety.ts +199 -5
  115. package/src/shell/session-continuity-hints.ts +39 -0
  116. package/src/tools/agent-artifacts-tool.ts +606 -0
  117. package/src/tools/agent-audit-tool.ts +155 -0
  118. package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
  119. package/src/tools/agent-autonomy-tool.ts +140 -0
  120. package/src/tools/agent-channel-send-tool.ts +24 -2
  121. package/src/tools/agent-channels-tool.ts +140 -0
  122. package/src/tools/agent-computer-tool.ts +168 -0
  123. package/src/tools/agent-context-policy.ts +137 -7
  124. package/src/tools/agent-context-tool.ts +143 -0
  125. package/src/tools/agent-delegation-tool.ts +128 -0
  126. package/src/tools/agent-device-tool.ts +240 -0
  127. package/src/tools/agent-documents-tool.ts +684 -0
  128. package/src/tools/agent-execution-tool.ts +230 -0
  129. package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
  130. package/src/tools/agent-harness-agent-orchestration.ts +791 -0
  131. package/src/tools/agent-harness-autonomy-intake.ts +785 -0
  132. package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
  133. package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
  134. package/src/tools/agent-harness-autonomy-queue.ts +571 -0
  135. package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
  136. package/src/tools/agent-harness-background-processes-types.ts +35 -0
  137. package/src/tools/agent-harness-background-processes.ts +798 -0
  138. package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
  139. package/src/tools/agent-harness-browser-control.ts +346 -0
  140. package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
  141. package/src/tools/agent-harness-channel-metadata.ts +168 -10
  142. package/src/tools/agent-harness-cli-command-policy.ts +110 -0
  143. package/src/tools/agent-harness-command-catalog.ts +4 -2
  144. package/src/tools/agent-harness-command-runner.ts +42 -0
  145. package/src/tools/agent-harness-connected-host-status.ts +7 -4
  146. package/src/tools/agent-harness-delegation-posture.ts +234 -19
  147. package/src/tools/agent-harness-device-live-read-models.ts +366 -0
  148. package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
  149. package/src/tools/agent-harness-document-ops-types.ts +72 -0
  150. package/src/tools/agent-harness-document-ops.ts +671 -0
  151. package/src/tools/agent-harness-execution-history.ts +489 -0
  152. package/src/tools/agent-harness-execution-posture.ts +385 -0
  153. package/src/tools/agent-harness-file-recovery.ts +135 -0
  154. package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
  155. package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
  156. package/src/tools/agent-harness-learning-curator-common.ts +102 -0
  157. package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
  158. package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
  159. package/src/tools/agent-harness-learning-curator-types.ts +151 -0
  160. package/src/tools/agent-harness-learning-curator.ts +417 -0
  161. package/src/tools/agent-harness-local-model-benchmarks.ts +269 -0
  162. package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
  163. package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
  164. package/src/tools/agent-harness-local-model-smoke.ts +277 -0
  165. package/src/tools/agent-harness-local-model-url.ts +78 -0
  166. package/src/tools/agent-harness-media-posture.ts +223 -2
  167. package/src/tools/agent-harness-memory-external-providers.ts +796 -0
  168. package/src/tools/agent-harness-memory-posture.ts +672 -0
  169. package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
  170. package/src/tools/agent-harness-memory-refinement.ts +340 -0
  171. package/src/tools/agent-harness-metadata.ts +100 -168
  172. package/src/tools/agent-harness-mode-catalog.ts +86 -33
  173. package/src/tools/agent-harness-model-catalog.ts +162 -0
  174. package/src/tools/agent-harness-model-provider-health.ts +283 -0
  175. package/src/tools/agent-harness-model-readiness.ts +432 -0
  176. package/src/tools/agent-harness-model-routing-types.ts +327 -0
  177. package/src/tools/agent-harness-model-routing-utils.ts +30 -0
  178. package/src/tools/agent-harness-model-routing.ts +165 -193
  179. package/src/tools/agent-harness-operator-methods.ts +115 -133
  180. package/src/tools/agent-harness-pairing-posture.ts +452 -18
  181. package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
  182. package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
  183. package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
  184. package/src/tools/agent-harness-personal-ops-lanes.ts +270 -0
  185. package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
  186. package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
  187. package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
  188. package/src/tools/agent-harness-personal-ops-records.ts +684 -0
  189. package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
  190. package/src/tools/agent-harness-personal-ops-types.ts +240 -0
  191. package/src/tools/agent-harness-personal-ops.ts +613 -0
  192. package/src/tools/agent-harness-project-context.ts +144 -0
  193. package/src/tools/agent-harness-prompt-context.ts +589 -0
  194. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  195. package/src/tools/agent-harness-release-evidence.ts +9 -7
  196. package/src/tools/agent-harness-release-readiness.ts +9 -7
  197. package/src/tools/agent-harness-remote-read-models.ts +541 -0
  198. package/src/tools/agent-harness-research-briefing.ts +446 -0
  199. package/src/tools/agent-harness-research-live-read-models.ts +500 -0
  200. package/src/tools/agent-harness-research-queue.ts +250 -0
  201. package/src/tools/agent-harness-research-runs.ts +307 -0
  202. package/src/tools/agent-harness-research-workflow.ts +395 -0
  203. package/src/tools/agent-harness-security-posture.ts +2 -2
  204. package/src/tools/agent-harness-service-posture.ts +9 -9
  205. package/src/tools/agent-harness-session-metadata.ts +3 -3
  206. package/src/tools/agent-harness-setup-connected-host.ts +501 -0
  207. package/src/tools/agent-harness-setup-describe.ts +557 -0
  208. package/src/tools/agent-harness-setup-handoffs.ts +502 -0
  209. package/src/tools/agent-harness-setup-model-helpers.ts +102 -0
  210. package/src/tools/agent-harness-setup-plan.ts +253 -0
  211. package/src/tools/agent-harness-setup-posture-types.ts +218 -0
  212. package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
  213. package/src/tools/agent-harness-setup-posture.ts +449 -221
  214. package/src/tools/agent-harness-setup-smoke.ts +533 -0
  215. package/src/tools/agent-harness-sudo-posture.ts +114 -0
  216. package/src/tools/agent-harness-tool-schema.ts +142 -7
  217. package/src/tools/agent-harness-tool-types.ts +83 -0
  218. package/src/tools/agent-harness-tool-utils.ts +43 -0
  219. package/src/tools/agent-harness-tool.ts +319 -370
  220. package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
  221. package/src/tools/agent-harness-ui-surface-types.ts +46 -0
  222. package/src/tools/agent-harness-vibe-health.ts +105 -0
  223. package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
  224. package/src/tools/agent-harness-workspace-actions.ts +114 -8
  225. package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
  226. package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
  227. package/src/tools/agent-host-tool.ts +159 -0
  228. package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
  229. package/src/tools/agent-learning-consolidation-core.ts +327 -0
  230. package/src/tools/agent-learning-consolidation-tool.ts +758 -0
  231. package/src/tools/agent-memory-tool.ts +253 -0
  232. package/src/tools/agent-model-compare-export.ts +315 -0
  233. package/src/tools/agent-model-compare-handoff.ts +592 -0
  234. package/src/tools/agent-model-compare-judgment.ts +633 -0
  235. package/src/tools/agent-model-compare-run.ts +735 -0
  236. package/src/tools/agent-model-compare-tool.ts +698 -0
  237. package/src/tools/agent-model-compare-types.ts +191 -0
  238. package/src/tools/agent-model-compare-utils.ts +93 -0
  239. package/src/tools/agent-models-tool.ts +208 -0
  240. package/src/tools/agent-operator-action-tool.ts +1 -1
  241. package/src/tools/agent-operator-method-tool.ts +643 -0
  242. package/src/tools/agent-personal-ops-tool.ts +197 -0
  243. package/src/tools/agent-policy-explanation.ts +415 -0
  244. package/src/tools/agent-research-report-tool.ts +608 -0
  245. package/src/tools/agent-research-runner.ts +367 -0
  246. package/src/tools/agent-research-runs-tool.ts +434 -0
  247. package/src/tools/agent-research-sources-tool.ts +443 -0
  248. package/src/tools/agent-research-tool.ts +515 -0
  249. package/src/tools/agent-review-packet-presets-core.ts +757 -0
  250. package/src/tools/agent-review-packet-presets-tool.ts +466 -0
  251. package/src/tools/agent-review-packet-share-tool.ts +305 -0
  252. package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
  253. package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
  254. package/src/tools/agent-route-planner-candidates-work.ts +251 -0
  255. package/src/tools/agent-route-planner-helpers.ts +667 -0
  256. package/src/tools/agent-route-planner.ts +185 -0
  257. package/src/tools/agent-route-tool.ts +105 -0
  258. package/src/tools/agent-schedule-edit-tool.ts +210 -0
  259. package/src/tools/agent-schedule-tool.ts +282 -0
  260. package/src/tools/agent-security-tool.ts +145 -0
  261. package/src/tools/agent-sessions-tool.ts +122 -0
  262. package/src/tools/agent-settings-import-tool.ts +104 -0
  263. package/src/tools/agent-settings-tool.ts +176 -0
  264. package/src/tools/agent-setup-tool.ts +226 -0
  265. package/src/tools/agent-support-tool.ts +122 -0
  266. package/src/tools/agent-terminal-process-tools.ts +167 -0
  267. package/src/tools/agent-tool-policy-guard-types.ts +77 -0
  268. package/src/tools/agent-tool-policy-guard.ts +106 -85
  269. package/src/tools/agent-vibe-tool.ts +297 -0
  270. package/src/tools/agent-work-plan-tool.ts +265 -6
  271. package/src/tools/agent-workspace-tool.ts +305 -0
  272. package/src/tools/artifact-archive.ts +169 -0
  273. package/src/tools/tool-definition-compaction.ts +36 -0
  274. package/src/version.ts +1 -1
@@ -1,175 +1,19 @@
1
+ import { arch, cpus, freemem, platform, totalmem } from 'node:os';
2
+ import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
1
3
  import type { CommandContext } from '../input/command-registry.ts';
2
4
  import { requireProviderApi } from '../input/commands/runtime-services.ts';
3
5
  import { previewHarnessText } from './agent-harness-text.ts';
4
-
5
- export interface AgentHarnessModelRoutingArgs {
6
- readonly modelRouteId?: unknown;
7
- readonly target?: unknown;
8
- readonly query?: unknown;
9
- readonly includeParameters?: unknown;
10
- readonly limit?: unknown;
11
- }
12
-
13
- type ModelRouteResolution =
14
- | { readonly status: 'found'; readonly route: Record<string, unknown> }
15
- | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
16
- | { readonly status: 'missing_lookup'; readonly usage: string };
17
-
18
- type ModelRouteLookupSource = 'modelRouteId' | 'target' | 'query';
19
-
20
- interface ModelCandidate {
21
- readonly kind: 'model';
22
- readonly id: string;
23
- readonly registryKey: string;
24
- readonly modelId: string;
25
- readonly providerId: string;
26
- readonly displayName: string;
27
- readonly current: boolean;
28
- readonly contextWindow: number | null;
29
- readonly reasoningEffort: readonly string[];
30
- readonly capabilities: unknown;
31
- readonly pinned: boolean;
32
- }
33
-
34
- interface RouteCandidate {
35
- readonly kind: 'route';
36
- readonly id: string;
37
- readonly label: string;
38
- readonly detail: string;
39
- readonly currentValue: unknown;
40
- readonly settingKeys: readonly string[];
41
- readonly commands: readonly string[];
42
- readonly uiSurfaces: readonly string[];
43
- }
44
-
45
- interface ProviderApiLike {
46
- readonly getFavorites: () => Promise<unknown>;
47
- readonly getCurrentModel: () => Promise<unknown>;
48
- readonly listModels: (options?: { readonly selectableOnly?: boolean }) => Promise<readonly unknown[]>;
49
- readonly listProviderIds: () => readonly string[];
50
- }
51
-
52
- function readString(value: unknown): string {
53
- return typeof value === 'string' ? value.trim() : '';
54
- }
55
-
56
- function readLimit(value: unknown, fallback: number): number {
57
- const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
58
- if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
59
- return Math.max(1, Math.min(500, Math.trunc(parsed)));
60
- }
61
-
62
- function readRecord(value: unknown): Record<string, unknown> {
63
- return value && typeof value === 'object' ? value as Record<string, unknown> : {};
64
- }
65
-
66
- function readStringArray(value: unknown): readonly string[] {
67
- return Array.isArray(value) ? value.map((entry) => readString(entry)).filter(Boolean) : [];
68
- }
69
-
70
- function readConfig(context: CommandContext, key: string): unknown {
71
- try {
72
- return (context.platform.configManager as { get(settingKey: string): unknown }).get(key);
73
- } catch {
74
- return undefined;
75
- }
76
- }
77
-
78
- function contextWindowFor(context: CommandContext, model: unknown): number | null {
79
- try {
80
- const registry = context.provider.providerRegistry as { getContextWindowForModel(candidate: unknown): number };
81
- const value = registry.getContextWindowForModel(model);
82
- return typeof value === 'number' && Number.isFinite(value) ? value : null;
83
- } catch {
84
- return null;
85
- }
86
- }
87
-
88
- function modelRegistryKey(model: unknown): string {
89
- const record = readRecord(model);
90
- return readString(record.registryKey) || readString(record.id) || readString(record.modelId);
91
- }
92
-
93
- function modelProviderId(model: unknown): string {
94
- const record = readRecord(model);
95
- return readString(record.providerId) || readString(record.provider);
96
- }
97
-
98
- function modelModelId(model: unknown): string {
99
- const record = readRecord(model);
100
- return readString(record.modelId) || readString(record.id) || modelRegistryKey(model);
101
- }
102
-
103
- function modelDisplayName(model: unknown): string {
104
- const record = readRecord(model);
105
- return readString(record.displayName) || readString(record.name) || modelRegistryKey(model);
106
- }
107
-
108
- function modelCurrent(model: unknown): boolean {
109
- const record = readRecord(model);
110
- return record.current === true;
111
- }
112
-
113
- function modelReasoning(model: unknown): readonly string[] {
114
- const record = readRecord(model);
115
- return readStringArray(record.reasoningEffort);
116
- }
117
-
118
- function modelCapabilities(model: unknown): unknown {
119
- return readRecord(model).capabilities ?? null;
120
- }
121
-
122
- function readProviderApi(context: CommandContext): ProviderApiLike | null {
123
- try {
124
- return requireProviderApi(context) as ProviderApiLike;
125
- } catch {
126
- return null;
127
- }
128
- }
129
-
130
- async function loadPinnedModelIds(context: CommandContext): Promise<ReadonlySet<string>> {
131
- const providerApi = readProviderApi(context);
132
- if (!providerApi) return new Set();
133
- try {
134
- const favorites = await providerApi.getFavorites();
135
- const pinned = readRecord(favorites).pinned;
136
- if (!Array.isArray(pinned)) return new Set();
137
- return new Set(pinned.flatMap((entry) => {
138
- const record = readRecord(entry);
139
- return [readString(record.registryKey), readString(record.modelId)].filter(Boolean);
140
- }));
141
- } catch {
142
- return new Set();
143
- }
144
- }
145
-
146
- async function loadModels(context: CommandContext): Promise<readonly ModelCandidate[]> {
147
- const providerApi = readProviderApi(context);
148
- if (!providerApi) return [];
149
- const pinned = await loadPinnedModelIds(context);
150
- const models = await providerApi.listModels({ selectableOnly: true });
151
- return models.map((model) => {
152
- const registryKey = modelRegistryKey(model);
153
- const modelId = modelModelId(model);
154
- return {
155
- kind: 'model',
156
- id: registryKey,
157
- registryKey,
158
- modelId,
159
- providerId: modelProviderId(model),
160
- displayName: modelDisplayName(model),
161
- current: modelCurrent(model) || registryKey === context.session.runtime.model,
162
- contextWindow: contextWindowFor(context, model),
163
- reasoningEffort: modelReasoning(model),
164
- capabilities: modelCapabilities(model),
165
- pinned: pinned.has(registryKey) || pinned.has(modelId),
166
- };
167
- });
168
- }
169
-
170
- function listProviderIds(context: CommandContext): readonly string[] {
171
- return readProviderApi(context)?.listProviderIds() ?? [];
172
- }
6
+ import { localModelCookbook } from './agent-harness-local-model-cookbook.ts';
7
+ import { localBenchmarkRouteLatencyMap } from './agent-harness-local-model-benchmarks.ts';
8
+ import { localModelDetection, localModelServerEndpoints, localModelServerHealthMap } from './agent-harness-local-model-endpoints.ts';
9
+ import { runLocalModelServerSmoke } from './agent-harness-local-model-smoke.ts';
10
+ import { localHardwareProfile, modelReadinessScore } from './agent-harness-model-readiness.ts';
11
+ import { contextWindowFor, loadModels, listProviderIds, modelBenchmarkCompositeScore, modelBenchmarkQualityTier, modelCapabilities, modelCurrent, modelDisplayName, modelModelId, modelProviderId, modelReasoning, modelRegistryKey, modelTier, readConfig, readProviderApi } from './agent-harness-model-catalog.ts';
12
+ import type { AgentHarnessModelRoutingArgs, LocalModelServerEndpoint, ModelCandidate, ModelProviderHealthSignal, ModelRouteLookupSource, ModelRouteResolution, RouteCandidate } from './agent-harness-model-routing-types.ts';
13
+ import { readLimit, readString } from './agent-harness-model-routing-utils.ts';
14
+ export type { AgentHarnessModelRoutingArgs } from './agent-harness-model-routing-types.ts';
15
+ export { localModelCookbook } from './agent-harness-local-model-cookbook.ts';
16
+ export { runLocalModelServerSmoke } from './agent-harness-local-model-smoke.ts';
173
17
 
174
18
  async function readCurrentModel(context: CommandContext): Promise<unknown> {
175
19
  try {
@@ -179,6 +23,15 @@ async function readCurrentModel(context: CommandContext): Promise<unknown> {
179
23
  }
180
24
  }
181
25
 
26
+ function attachLocalBenchmarkLatencies(context: CommandContext, models: readonly ModelCandidate[]): readonly ModelCandidate[] {
27
+ const latencies = localBenchmarkRouteLatencyMap(context);
28
+ if (latencies.size === 0) return models;
29
+ return models.map((model) => ({
30
+ ...model,
31
+ localBenchmarkLatency: latencies.get(model.registryKey) ?? null,
32
+ }));
33
+ }
34
+
182
35
  function routeCandidates(context: CommandContext): readonly RouteCandidate[] {
183
36
  return [
184
37
  {
@@ -253,6 +106,20 @@ function routeCandidates(context: CommandContext): readonly RouteCandidate[] {
253
106
  commands: ['/refresh-models'],
254
107
  uiSurfaces: ['model-picker'],
255
108
  },
109
+ {
110
+ kind: 'route',
111
+ id: 'local-model-cookbook',
112
+ label: 'Local model cookbook',
113
+ detail: 'Hardware-aware recommendations for Ollama, llama.cpp, vLLM, and local OpenAI-compatible servers.',
114
+ currentValue: {
115
+ detected: localModelDetection(context),
116
+ hardwareProfile: localHardwareProfile(),
117
+ localServerHealth: localModelServerHealthMap(context, false),
118
+ },
119
+ settingKeys: [],
120
+ commands: ['/provider add <name> <baseURL> [apiKey] --yes', '/refresh-models', '/model'],
121
+ uiSurfaces: ['provider-picker', 'model-picker', 'settings'],
122
+ },
256
123
  {
257
124
  kind: 'route',
258
125
  id: 'pinned-models',
@@ -310,7 +177,39 @@ function modelSearchText(model: ModelCandidate): string {
310
177
  ].join('\n').toLowerCase();
311
178
  }
312
179
 
313
- function describeRoute(route: RouteCandidate, options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {}): Record<string, unknown> {
180
+ function localEndpointSearchText(endpoint: LocalModelServerEndpoint): string {
181
+ return [
182
+ endpoint.id,
183
+ endpoint.providerId ?? '',
184
+ endpoint.stack ?? '',
185
+ endpoint.baseUrl,
186
+ endpoint.modelsUrl,
187
+ endpoint.diagnosticStatus,
188
+ ...endpoint.sources,
189
+ ...endpoint.sourceDetails,
190
+ ...endpoint.modelRoutes,
191
+ ...endpoint.notes,
192
+ ].join('\n').toLowerCase();
193
+ }
194
+
195
+ function describeLocalServerEndpointRoute(endpoint: LocalModelServerEndpoint, lookup?: Record<string, unknown>): Record<string, unknown> {
196
+ return {
197
+ ...endpoint,
198
+ ...(lookup ? { lookup } : {}),
199
+ modelRouteId: endpoint.id,
200
+ label: `Local model server ${endpoint.baseUrl}`,
201
+ modelRoute: endpoint.inspectRoute,
202
+ modelAccess: {
203
+ smoke: endpoint.smokeRoute,
204
+ refresh: endpoint.refreshRoute,
205
+ addProvider: endpoint.addProviderRoute,
206
+ cookbook: 'models action:"local" includeParameters:true',
207
+ },
208
+ policy: endpoint.diagnostics?.policy ?? 'Read-only local model endpoint inspection. The smoke test, refresh, provider add, benchmark, and route changes remain separate visible confirmed actions.',
209
+ };
210
+ }
211
+
212
+ function describeRoute(route: RouteCandidate, options: { readonly context: CommandContext; readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> }): Record<string, unknown> {
314
213
  return {
315
214
  kind: 'route',
316
215
  modelRouteId: route.id,
@@ -324,6 +223,7 @@ function describeRoute(route: RouteCandidate, options: { readonly includeParamet
324
223
  uiSurfaces: route.uiSurfaces,
325
224
  } : {}),
326
225
  ...(options.lookup ? { lookup: options.lookup } : {}),
226
+ ...(route.id === 'local-model-cookbook' && options.includeParameters === true ? { localCookbook: localModelCookbook(options.context, true) } : {}),
327
227
  ...(options.includeParameters ? {
328
228
  policy: {
329
229
  effect: 'read-only',
@@ -331,10 +231,10 @@ function describeRoute(route: RouteCandidate, options: { readonly includeParamet
331
231
  mutation: 'Model/provider selection, catalog refresh, favorites, custom provider edits, and route setting changes stay explicit user-facing picker, settings, workspace, or slash-command flows.',
332
232
  },
333
233
  modelAccess: {
334
- inspectRouting: 'agent_harness mode:"model_routing"',
234
+ inspectRouting: 'models action:"status"',
335
235
  inspectRoute: 'agent_harness mode:"model_route"',
336
- settingRead: 'agent_harness mode:"get_setting"',
337
- settingMutation: 'agent_harness mode:"set_setting" confirm:true explicitUserRequest:"..."',
236
+ settingRead: 'settings action:"get"',
237
+ settingMutation: 'settings action:"set" confirm:true explicitUserRequest:"..."',
338
238
  openModelPicker: 'agent_harness mode:"open_ui_surface" surfaceId:"model-picker" confirm:true explicitUserRequest:"..."',
339
239
  openProviderPicker: 'agent_harness mode:"open_ui_surface" surfaceId:"provider-picker" confirm:true explicitUserRequest:"..."',
340
240
  },
@@ -342,7 +242,26 @@ function describeRoute(route: RouteCandidate, options: { readonly includeParamet
342
242
  };
343
243
  }
344
244
 
345
- function describeModel(model: ModelCandidate, options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {}): Record<string, unknown> {
245
+ function compactProviderHealthSignal(signal: ModelProviderHealthSignal): Record<string, unknown> {
246
+ return {
247
+ status: signal.status,
248
+ sdkContract: signal.sdkContract,
249
+ daemonPublication: signal.daemonPublication,
250
+ agentConsumption: signal.agentConsumption,
251
+ ...(signal.healthStatus ? { healthStatus: signal.healthStatus } : {}),
252
+ ...(signal.modelRouteId ? { modelRouteId: signal.modelRouteId } : {}),
253
+ ...(signal.sourceRecordId ? { sourceRecordId: signal.sourceRecordId } : {}),
254
+ ...(signal.avgLatencyMs !== undefined ? { avgLatencyMs: signal.avgLatencyMs } : {}),
255
+ ...(signal.rateLimitRemaining !== undefined ? { rateLimitRemaining: signal.rateLimitRemaining } : {}),
256
+ ...(signal.rateLimitResetAt ? { rateLimitResetAt: signal.rateLimitResetAt } : {}),
257
+ ...(signal.lastErrorMessage ? { lastErrorMessage: signal.lastErrorMessage } : {}),
258
+ missingSignals: signal.missingSignals,
259
+ policy: signal.policy,
260
+ };
261
+ }
262
+
263
+ function describeModel(model: ModelCandidate, options: { readonly context: CommandContext; readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> }): Record<string, unknown> {
264
+ const readiness = modelReadinessScore(options.context, model);
346
265
  return {
347
266
  kind: 'model',
348
267
  modelRouteId: model.registryKey,
@@ -354,6 +273,21 @@ function describeModel(model: ModelCandidate, options: { readonly includeParamet
354
273
  pinned: model.pinned,
355
274
  contextWindow: model.contextWindow,
356
275
  reasoningEffort: model.reasoningEffort,
276
+ tier: model.tier ?? null,
277
+ benchmarkCompositeScore: model.benchmarkCompositeScore ?? null,
278
+ benchmarkQualityTier: model.benchmarkQualityTier ?? null,
279
+ localBenchmarkLatency: model.localBenchmarkLatency ?? null,
280
+ readinessScore: readiness.score,
281
+ readinessLevel: readiness.level,
282
+ readiness: options.includeParameters
283
+ ? readiness
284
+ : {
285
+ score: readiness.score,
286
+ level: readiness.level,
287
+ confidence: readiness.confidence,
288
+ providerHealth: compactProviderHealthSignal(readiness.providerHealth),
289
+ nextStep: readiness.nextStep,
290
+ },
357
291
  modelRoute: modelCandidateModelRoute(),
358
292
  ...(options.lookup ? { lookup: options.lookup } : {}),
359
293
  ...(options.includeParameters ? {
@@ -367,7 +301,7 @@ function describeModel(model: ModelCandidate, options: { readonly includeParamet
367
301
  selectProviderCommand: `/provider ${model.providerId}`,
368
302
  pinCommand: `/pin ${model.registryKey}`,
369
303
  unpinCommand: `/unpin ${model.registryKey}`,
370
- setMainModel: `agent_harness mode:"set_setting" key:"provider.model" value:"${model.registryKey}" confirm:true explicitUserRequest:"..."`,
304
+ setMainModel: `settings action:"set" key:"provider.model" value:"${model.registryKey}" confirm:true explicitUserRequest:"..."`,
371
305
  },
372
306
  } : {}),
373
307
  };
@@ -393,8 +327,19 @@ function describeCandidate(entry: RouteCandidate | ModelCandidate): Record<strin
393
327
  };
394
328
  }
395
329
 
330
+ function describeEndpointCandidate(endpoint: LocalModelServerEndpoint): Record<string, unknown> {
331
+ return {
332
+ kind: endpoint.kind,
333
+ modelRouteId: endpoint.id,
334
+ baseUrl: endpoint.baseUrl,
335
+ stack: endpoint.stack,
336
+ diagnosticStatus: endpoint.diagnosticStatus,
337
+ modelRoute: endpoint.inspectRoute,
338
+ };
339
+ }
340
+
396
341
  function modelRoutingModelRoute(): string {
397
- return 'agent_harness mode:"model_route" or mode:"run_command"';
342
+ return 'models action:"route" or action:"smoke"';
398
343
  }
399
344
 
400
345
  function modelCandidateModelRoute(): string {
@@ -402,10 +347,11 @@ function modelCandidateModelRoute(): string {
402
347
  }
403
348
 
404
349
  export async function modelRoutingCatalogStatus(context: CommandContext): Promise<Record<string, unknown>> {
405
- const [models, providerIds] = await Promise.all([
350
+ const [rawModels, providerIds] = await Promise.all([
406
351
  loadModels(context),
407
352
  Promise.resolve(listProviderIds(context)),
408
353
  ]);
354
+ const models = attachLocalBenchmarkLatencies(context, rawModels);
409
355
  return {
410
356
  modes: ['model_routing', 'model_route'],
411
357
  status: readProviderApi(context) ? 'available' : 'degraded',
@@ -421,15 +367,20 @@ export async function modelRoutingCatalogStatus(context: CommandContext): Promis
421
367
  export async function modelRoutingSummary(context: CommandContext, args: AgentHarnessModelRoutingArgs): Promise<Record<string, unknown>> {
422
368
  const query = readString(args.query).toLowerCase();
423
369
  const includeParameters = args.includeParameters === true;
424
- const [models, providerIds, currentModel] = await Promise.all([
370
+ const [rawModels, providerIds, currentModel] = await Promise.all([
425
371
  loadModels(context),
426
372
  Promise.resolve(listProviderIds(context)),
427
373
  readCurrentModel(context),
428
374
  ]);
375
+ const models = attachLocalBenchmarkLatencies(context, rawModels);
429
376
  const routes = routeCandidates(context);
430
377
  const filteredRoutes = routes.filter((route) => !query || routeSearchText(route).includes(query));
431
378
  const filteredModels = models.filter((model) => !query || modelSearchText(model).includes(query));
432
379
  const limit = readLimit(args.limit, 100);
380
+ const currentRegistryKey = currentModel ? modelRegistryKey(currentModel) : '';
381
+ const loadedCurrentModel = currentRegistryKey
382
+ ? models.find((model) => model.registryKey === currentRegistryKey || model.modelId === modelModelId(currentModel))
383
+ : null;
433
384
  return {
434
385
  status: readProviderApi(context) ? 'available' : 'degraded',
435
386
  current: {
@@ -447,12 +398,17 @@ export async function modelRoutingSummary(context: CommandContext, args: AgentHa
447
398
  contextWindow: contextWindowFor(context, currentModel),
448
399
  reasoningEffort: modelReasoning(currentModel),
449
400
  capabilities: modelCapabilities(currentModel),
401
+ tier: modelTier(currentModel),
402
+ benchmarkCompositeScore: modelBenchmarkCompositeScore(currentModel),
403
+ benchmarkQualityTier: modelBenchmarkQualityTier(currentModel),
404
+ localBenchmarkLatency: loadedCurrentModel?.localBenchmarkLatency ?? null,
450
405
  pinned: false,
451
- }, { includeParameters }) : null,
406
+ }, { context, includeParameters }) : null,
452
407
  },
453
408
  providers: providerIds,
454
- routes: filteredRoutes.slice(0, limit).map((route) => describeRoute(route, { includeParameters })),
455
- models: filteredModels.slice(0, limit).map((model) => describeModel(model, { includeParameters })),
409
+ localCookbook: localModelCookbook(context, includeParameters),
410
+ routes: filteredRoutes.slice(0, limit).map((route) => describeRoute(route, { context, includeParameters })),
411
+ models: filteredModels.slice(0, limit).map((model) => describeModel(model, { context, includeParameters })),
456
412
  returned: {
457
413
  routes: Math.min(filteredRoutes.length, limit),
458
414
  models: Math.min(filteredModels.length, limit),
@@ -475,42 +431,58 @@ export async function describeHarnessModelRoute(context: CommandContext, args: A
475
431
  if (!lookup) {
476
432
  return {
477
433
  status: 'missing_lookup',
478
- usage: 'model_route requires modelRouteId, target, or query. Use mode:"model_routing" to inspect route and model ids.',
434
+ usage: 'model_route requires modelRouteId, target, or query. Prefer models action:"status" to inspect route, model, and local endpoint ids.',
479
435
  };
480
436
  }
481
- const [models] = await Promise.all([loadModels(context)]);
437
+ const [rawModels] = await Promise.all([loadModels(context)]);
438
+ const models = attachLocalBenchmarkLatencies(context, rawModels);
482
439
  const routes = routeCandidates(context);
440
+ const endpoints = localModelServerEndpoints(context, true);
483
441
  const normalized = lookup.input.toLowerCase();
484
442
  const exactRoute = routes.find((route) => route.id === lookup.input);
485
- if (exactRoute) return { status: 'found', route: describeRoute(exactRoute, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'route-id' } }) };
443
+ if (exactRoute) return { status: 'found', route: describeRoute(exactRoute, { context, includeParameters: true, lookup: { ...lookup, resolvedBy: 'route-id' } }) };
486
444
  const exactModel = models.find((model) => model.registryKey === lookup.input || model.modelId === lookup.input);
487
- if (exactModel) return { status: 'found', route: describeModel(exactModel, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'model-id' } }) };
445
+ if (exactModel) return { status: 'found', route: describeModel(exactModel, { context, includeParameters: true, lookup: { ...lookup, resolvedBy: 'model-id' } }) };
446
+ const exactEndpoint = endpoints.find((endpoint) => endpoint.id === lookup.input || endpoint.baseUrl === lookup.input || endpoint.modelsUrl === lookup.input);
447
+ if (exactEndpoint) return { status: 'found', route: describeLocalServerEndpointRoute(exactEndpoint, { ...lookup, resolvedBy: 'local-endpoint-id' }) };
488
448
  const insensitiveRoute = routes.find((route) => route.id.toLowerCase() === normalized);
489
- if (insensitiveRoute) return { status: 'found', route: describeRoute(insensitiveRoute, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-route-id' } }) };
449
+ if (insensitiveRoute) return { status: 'found', route: describeRoute(insensitiveRoute, { context, includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-route-id' } }) };
490
450
  const insensitiveModel = models.find((model) => model.registryKey.toLowerCase() === normalized || model.modelId.toLowerCase() === normalized);
491
- if (insensitiveModel) return { status: 'found', route: describeModel(insensitiveModel, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-model-id' } }) };
451
+ if (insensitiveModel) return { status: 'found', route: describeModel(insensitiveModel, { context, includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-model-id' } }) };
452
+ const insensitiveEndpoint = endpoints.find((endpoint) => endpoint.id.toLowerCase() === normalized || endpoint.baseUrl.toLowerCase() === normalized || endpoint.modelsUrl.toLowerCase() === normalized);
453
+ if (insensitiveEndpoint) return { status: 'found', route: describeLocalServerEndpointRoute(insensitiveEndpoint, { ...lookup, resolvedBy: 'case-insensitive-local-endpoint-id' }) };
492
454
  const searched = [
493
455
  ...routes.filter((route) => routeSearchText(route).includes(normalized)),
494
456
  ...models.filter((model) => modelSearchText(model).includes(normalized)),
495
457
  ];
496
- if (searched.length === 1) {
458
+ const searchedEndpoints = endpoints.filter((endpoint) => localEndpointSearchText(endpoint).includes(normalized));
459
+ if (searched.length === 0 && searchedEndpoints.length === 1) {
460
+ return {
461
+ status: 'found',
462
+ route: describeLocalServerEndpointRoute(searchedEndpoints[0]!, { ...lookup, resolvedBy: 'local-endpoint-search' }),
463
+ };
464
+ }
465
+ if (searched.length === 1 && searchedEndpoints.length === 0) {
497
466
  const found = searched[0]!;
498
467
  return {
499
468
  status: 'found',
500
469
  route: found.kind === 'route'
501
- ? describeRoute(found, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } })
502
- : describeModel(found, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }),
470
+ ? describeRoute(found, { context, includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } })
471
+ : describeModel(found, { context, includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }),
503
472
  };
504
473
  }
505
- if (searched.length > 1) {
474
+ if (searched.length + searchedEndpoints.length > 1) {
506
475
  return {
507
476
  status: 'ambiguous',
508
477
  input: lookup.input,
509
- candidates: searched.slice(0, 8).map(describeCandidate),
478
+ candidates: [
479
+ ...searched.slice(0, 8).map(describeCandidate),
480
+ ...searchedEndpoints.slice(0, Math.max(0, 8 - searched.length)).map(describeEndpointCandidate),
481
+ ],
510
482
  };
511
483
  }
512
484
  return {
513
485
  status: 'missing_lookup',
514
- usage: `Unknown model route ${lookup.input}. Use mode:"model_routing" to inspect route and model ids.`,
486
+ usage: `Unknown model route ${lookup.input}. Prefer models action:"status" to inspect route, model, and local endpoint ids.`,
515
487
  };
516
488
  }