@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
@@ -0,0 +1,421 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
3
+ import { readRecord, readString } from './agent-harness-model-routing-utils.ts';
4
+
5
+ export type AgentHarnessInteractiveRuntimeKind = 'process-output' | 'pty-session' | 'sudo-mediation' | 'browser-desktop-receipt';
6
+ export type AgentHarnessInteractiveRuntimeStatus = 'ready' | 'running' | 'completed' | 'blocked' | 'failed' | 'attention' | 'unknown';
7
+
8
+ export interface AgentHarnessInteractiveRuntimeCertification {
9
+ readonly schemaStatus: 'certified' | 'legacy';
10
+ readonly schemaVersion?: string;
11
+ readonly publicationGuarantee?: string;
12
+ readonly publisher?: string;
13
+ readonly provenance?: readonly string[];
14
+ readonly receiptId?: string;
15
+ readonly receiptRoute?: string;
16
+ readonly missingSignals: readonly string[];
17
+ readonly policy: string;
18
+ }
19
+
20
+ export interface AgentHarnessInteractiveRuntimeRoute {
21
+ readonly id: string;
22
+ readonly label: string;
23
+ readonly modelRoute: string;
24
+ readonly effect: 'read-only' | 'confirmed' | 'confirmed-admin';
25
+ }
26
+
27
+ export interface AgentHarnessInteractiveRuntimeRecord {
28
+ readonly id: string;
29
+ readonly kind: AgentHarnessInteractiveRuntimeKind;
30
+ readonly status: AgentHarnessInteractiveRuntimeStatus;
31
+ readonly source: string;
32
+ readonly label: string;
33
+ readonly summary: string;
34
+ readonly processId?: string;
35
+ readonly sessionId?: string;
36
+ readonly pid?: number;
37
+ readonly command?: string;
38
+ readonly surface?: string;
39
+ readonly action?: string;
40
+ readonly receiptId?: string;
41
+ readonly outputChunks?: readonly Record<string, unknown>[];
42
+ readonly routes: readonly AgentHarnessInteractiveRuntimeRoute[];
43
+ readonly updatedAt?: string;
44
+ readonly certification: AgentHarnessInteractiveRuntimeCertification;
45
+ }
46
+
47
+ const SECRET_PATTERNS: readonly [RegExp, string][] = [
48
+ [/secret:\/\/[^\s,'"}]+/gi, 'secret://<redacted>'],
49
+ [/("?\b(?:api[-_]?key|apikey|token|secret|password|passwd|credential|authorization)\b"?\s*:\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1"<redacted>"'],
50
+ [/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTHORIZATION|BEARER)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi, '$1=<redacted>'],
51
+ [/(\b(?:token|secret|password|passwd|api[-_]?key|apikey|authorization|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1<redacted>'],
52
+ [/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>'],
53
+ ];
54
+
55
+ function redactedPreview(value: unknown, limit = 180): string {
56
+ const raw = readString(value);
57
+ const redacted = SECRET_PATTERNS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), raw);
58
+ return previewHarnessText(redacted, limit);
59
+ }
60
+
61
+ function readBoolean(value: unknown): boolean | null {
62
+ if (typeof value === 'boolean') return value;
63
+ const raw = readString(value).toLowerCase();
64
+ if (['true', 'yes', '1', 'ready', 'available', 'published', 'certified'].includes(raw)) return true;
65
+ if (['false', 'no', '0', 'blocked', 'missing', 'unavailable'].includes(raw)) return false;
66
+ return null;
67
+ }
68
+
69
+ function readNumber(value: unknown): number | undefined {
70
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
71
+ return typeof parsed === 'number' && Number.isFinite(parsed) ? Math.trunc(parsed) : undefined;
72
+ }
73
+
74
+ function readTimestamp(value: unknown): string | undefined {
75
+ const raw = readString(value);
76
+ const parsed = raw ? Date.parse(raw) : typeof value === 'number' && Number.isFinite(value) ? value : NaN;
77
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : undefined;
78
+ }
79
+
80
+ function stringArray(value: unknown): readonly string[] {
81
+ if (Array.isArray(value)) return [...new Set(value.map((entry) => readString(entry)).filter(Boolean))].slice(0, 10);
82
+ const raw = readString(value);
83
+ return raw ? raw.split(',').map((entry) => entry.trim()).filter(Boolean).slice(0, 10) : [];
84
+ }
85
+
86
+ function firstString(records: readonly Readonly<Record<string, unknown>>[], keys: readonly string[]): string {
87
+ for (const record of records) {
88
+ for (const key of keys) {
89
+ const value = readString(record[key]);
90
+ if (value) return value;
91
+ }
92
+ }
93
+ return '';
94
+ }
95
+
96
+ function certificationRecords(record: Readonly<Record<string, unknown>>): readonly Readonly<Record<string, unknown>>[] {
97
+ return [
98
+ record,
99
+ readRecord(record.schema),
100
+ readRecord(record.contract),
101
+ readRecord(record.publication),
102
+ readRecord(record.receipt),
103
+ readRecord(record.certification),
104
+ ];
105
+ }
106
+
107
+ function schemaStatus(records: readonly Readonly<Record<string, unknown>>[]): AgentHarnessInteractiveRuntimeCertification['schemaStatus'] {
108
+ const explicit = firstString(records, ['schemaStatus', 'receiptSchemaStatus', 'certificationStatus']).toLowerCase().replace(/[_\s]+/g, '-');
109
+ if (['certified', 'valid', 'verified', 'schema-certified'].includes(explicit)) return 'certified';
110
+ return firstString(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion'])
111
+ && firstString(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'runtimePublicationGuarantee'])
112
+ && firstString(records, ['publisher', 'publisherId', 'daemonId', 'hostId', 'methodId', 'sourceTool'])
113
+ ? 'certified'
114
+ : 'legacy';
115
+ }
116
+
117
+ function routeValue(value: unknown): string {
118
+ const direct = readString(value);
119
+ if (direct) return direct;
120
+ const record = readRecord(value);
121
+ return readString(record.modelRoute) || readString(record.route) || readString(record.command) || readString(record.action) || readString(record.href) || readString(record.url);
122
+ }
123
+
124
+ function routeFrom(record: Readonly<Record<string, unknown>>, aliases: readonly string[]): string {
125
+ const containers = [record, readRecord(record.routes), readRecord(record.modelRoutes), readRecord(record.actions), readRecord(record.commands), readRecord(record.links)];
126
+ for (const alias of aliases) {
127
+ for (const container of containers) {
128
+ const route = routeValue(container[alias]) || routeValue(container[`${alias}Route`]) || routeValue(container[`${alias}Command`]) || routeValue(container[`${alias}Action`]);
129
+ if (route) return route;
130
+ }
131
+ }
132
+ return '';
133
+ }
134
+
135
+ function runtimeKind(record: Readonly<Record<string, unknown>>, fallback: AgentHarnessInteractiveRuntimeKind): AgentHarnessInteractiveRuntimeKind {
136
+ const raw = `${readString(record.kind) || readString(record.recordKind) || readString(record.type) || readString(record.capability)}`.toLowerCase();
137
+ if (/pty|terminal-session|interactive-session/.test(raw)) return 'pty-session';
138
+ if (/sudo|credential|privilege/.test(raw)) return 'sudo-mediation';
139
+ if (/browser|desktop|screenshot|screen|computer|control|receipt/.test(raw)) return 'browser-desktop-receipt';
140
+ if (/process|output|chunk|terminal/.test(raw)) return 'process-output';
141
+ return fallback;
142
+ }
143
+
144
+ function normalizeStatus(value: unknown): AgentHarnessInteractiveRuntimeStatus {
145
+ const raw = readString(value).toLowerCase().replace(/_/g, '-');
146
+ if (['ready', 'available', 'published', 'certified', 'ok', 'healthy'].includes(raw)) return 'ready';
147
+ if (['running', 'active', 'in-progress'].includes(raw)) return 'running';
148
+ if (['completed', 'complete', 'succeeded', 'success', 'done'].includes(raw)) return 'completed';
149
+ if (['blocked', 'denied', 'permission-denied'].includes(raw)) return 'blocked';
150
+ if (['failed', 'failure', 'error', 'errored'].includes(raw)) return 'failed';
151
+ if (['attention', 'review', 'stale'].includes(raw)) return 'attention';
152
+ return raw ? 'unknown' : 'unknown';
153
+ }
154
+
155
+ function readId(record: Readonly<Record<string, unknown>>, kind: AgentHarnessInteractiveRuntimeKind): string {
156
+ return readString(record.id)
157
+ || readString(record.recordId)
158
+ || readString(record.processId)
159
+ || readString(record.sessionId)
160
+ || readString(record.receiptId)
161
+ || readString(record.contractId)
162
+ || `${kind}:${readString(record.sourceId) || readString(record.action) || 'published'}`;
163
+ }
164
+
165
+ function routesFor(kind: AgentHarnessInteractiveRuntimeKind, record: Readonly<Record<string, unknown>>): readonly AgentHarnessInteractiveRuntimeRoute[] {
166
+ const routeSpecs: readonly [string, string, readonly string[], AgentHarnessInteractiveRuntimeRoute['effect']][] = [
167
+ ['inspect', 'Inspect record', ['inspect', 'status', 'show'], 'read-only'],
168
+ ['log', 'Read output', ['log', 'output', 'chunks', 'tail'], 'read-only'],
169
+ ['write', 'Write input', ['write', 'stdin', 'input', 'sendInput'], 'confirmed'],
170
+ ['resize', 'Resize session', ['resize'], 'confirmed'],
171
+ ['close', 'Close session', ['close', 'stop', 'kill', 'cancel'], 'confirmed'],
172
+ ['credential', 'Mediated credential prompt', ['credential', 'sudo', 'privilege', 'prompt'], 'confirmed-admin'],
173
+ ['execute', kind === 'browser-desktop-receipt' ? 'Execute trusted control' : 'Execute route', ['execute', 'command', 'control', 'run'], 'confirmed'],
174
+ ['receipt', 'Inspect receipt', ['receipt', 'receipts', 'history'], 'read-only'],
175
+ ];
176
+ return routeSpecs
177
+ .map(([id, label, aliases, effect]) => {
178
+ const modelRoute = routeFrom(record, aliases);
179
+ return modelRoute ? { id, label, modelRoute: redactedPreview(modelRoute, 220), effect } : null;
180
+ })
181
+ .filter((route): route is AgentHarnessInteractiveRuntimeRoute => route !== null);
182
+ }
183
+
184
+ function outputChunks(record: Readonly<Record<string, unknown>>): readonly Record<string, unknown>[] {
185
+ const output = readRecord(record.output);
186
+ const rawChunks = Array.isArray(record.outputChunks) ? record.outputChunks
187
+ : Array.isArray(record.chunks) ? record.chunks
188
+ : Array.isArray(output.chunks) ? output.chunks
189
+ : [];
190
+ const chunks = rawChunks.flatMap((entry, index): Record<string, unknown>[] => {
191
+ const chunk = readRecord(entry);
192
+ const text = redactedPreview(chunk.text ?? chunk.preview ?? chunk.data ?? chunk.content, 600);
193
+ return text ? [{
194
+ chunkId: readString(chunk.chunkId) || readString(chunk.id) || `${index + 1}`,
195
+ stream: readString(chunk.stream) || readString(chunk.channel) || 'stdout',
196
+ text,
197
+ chars: readNumber(chunk.chars) ?? text.length,
198
+ bytes: readNumber(chunk.bytes) ?? Buffer.byteLength(text),
199
+ truncated: readBoolean(chunk.truncated) ?? false,
200
+ createdAt: readTimestamp(chunk.createdAt ?? chunk.timestamp),
201
+ }] : [];
202
+ });
203
+ if (chunks.length > 0) return chunks.slice(0, 5);
204
+ return ['stdoutTail', 'stderrTail', 'stdout', 'stderr'].flatMap((key) => {
205
+ const text = redactedPreview(record[key] ?? output[key], 600);
206
+ return text ? [{ chunkId: key, stream: key.includes('stderr') ? 'stderr' : 'stdout', text, chars: text.length, bytes: Buffer.byteLength(text), truncated: false }] : [];
207
+ }).slice(0, 5);
208
+ }
209
+
210
+ function certification(input: {
211
+ readonly record: Readonly<Record<string, unknown>>;
212
+ readonly source: string;
213
+ readonly kind: AgentHarnessInteractiveRuntimeKind;
214
+ readonly id: string;
215
+ readonly routes: readonly AgentHarnessInteractiveRuntimeRoute[];
216
+ readonly chunks: readonly Record<string, unknown>[];
217
+ }): AgentHarnessInteractiveRuntimeCertification {
218
+ const records = certificationRecords(input.record);
219
+ const status = schemaStatus(records);
220
+ const publicationGuarantee = firstString(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'runtimePublicationGuarantee']);
221
+ const publisher = firstString(records, ['publisher', 'publisherId', 'daemonId', 'hostId']);
222
+ const provenance = [...new Set([
223
+ ...stringArray(input.record.provenance),
224
+ input.source ? `source ${input.source}` : '',
225
+ firstString(records, ['methodId']) ? `method ${firstString(records, ['methodId'])}` : '',
226
+ firstString(records, ['sourceTool']) ? `sourceTool ${firstString(records, ['sourceTool'])}` : '',
227
+ ].map((entry) => redactedPreview(entry, 180)).filter(Boolean))].slice(0, 8);
228
+ const receiptId = firstString(records, ['receiptId', 'operationReceiptId', 'commandReceiptId', 'sessionReceiptId']);
229
+ const hasRoute = (id: string) => input.routes.some((route) => route.id === id);
230
+ const rawSecretReturned = readBoolean(input.record.rawSecretReturned) === true || readBoolean(input.record.rawValueReturned) === true;
231
+ const missingSignals = [
232
+ ...(status === 'certified' ? [] : [`Certified ${input.kind} schema is not published.`]),
233
+ ...(input.id ? [] : [`Durable ${input.kind} id is not published.`]),
234
+ ...(publicationGuarantee ? [] : [`${input.kind} publication guarantee is not published.`]),
235
+ ...(publisher ? [] : [`${input.kind} publisher is not published.`]),
236
+ ...(provenance.length > 0 ? [] : [`${input.kind} provenance is not published.`]),
237
+ ...(input.kind === 'process-output' && input.chunks.length === 0 && !hasRoute('log') ? ['Live process output chunks or log route are not published.'] : []),
238
+ ...(input.kind === 'pty-session' && (!hasRoute('write') || (!hasRoute('log') && input.chunks.length === 0)) ? ['PTY input and output routes are not both published.'] : []),
239
+ ...(input.kind === 'sudo-mediation' && (!hasRoute('credential') || rawSecretReturned) ? ['Sudo credential mediation route is missing or raw secret exposure was reported.'] : []),
240
+ ...(input.kind === 'browser-desktop-receipt' && (!receiptId || !hasRoute('execute') || !hasRoute('receipt')) ? ['Browser/desktop command receipt, execute route, or receipt route is not published.'] : []),
241
+ ];
242
+ return {
243
+ schemaStatus: status,
244
+ ...(firstString(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']) ? { schemaVersion: redactedPreview(firstString(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']), 80) } : {}),
245
+ ...(publicationGuarantee ? { publicationGuarantee: redactedPreview(publicationGuarantee, 220) } : {}),
246
+ ...(publisher ? { publisher: redactedPreview(publisher, 80) } : {}),
247
+ ...(provenance.length > 0 ? { provenance } : {}),
248
+ ...(receiptId ? { receiptId: redactedPreview(receiptId, 96) } : {}),
249
+ ...(hasRoute('receipt') ? { receiptRoute: input.routes.find((route) => route.id === 'receipt')?.modelRoute } : {}),
250
+ missingSignals,
251
+ policy: 'Interactive runtime records certify release readiness only when the SDK or daemon publishes schema, durable id, publication guarantee, publisher/provenance, bounded output or receipt evidence, exact confirmed control routes, and no raw secret material.',
252
+ };
253
+ }
254
+
255
+ function normalizeRecord(value: unknown, source: string, fallbackKind: AgentHarnessInteractiveRuntimeKind): AgentHarnessInteractiveRuntimeRecord | null {
256
+ const record = readRecord(value);
257
+ if (Object.keys(record).length === 0) return null;
258
+ const kind = runtimeKind(record, fallbackKind);
259
+ const id = readId(record, kind);
260
+ if (!id) return null;
261
+ const routes = routesFor(kind, record);
262
+ const chunks = outputChunks(record);
263
+ const command = redactedPreview(record.command ?? record.cmd, 180);
264
+ const receiptId = readString(record.receiptId) || readString(record.operationReceiptId) || readString(record.commandReceiptId);
265
+ return {
266
+ id,
267
+ kind,
268
+ status: normalizeStatus(record.status ?? record.state ?? record.outcome),
269
+ source,
270
+ label: redactedPreview(record.label ?? record.title ?? id, 96),
271
+ summary: redactedPreview(record.summary ?? record.description ?? record.detail ?? command ?? id, 240),
272
+ ...(readString(record.processId) ? { processId: readString(record.processId) } : {}),
273
+ ...(readString(record.sessionId) ? { sessionId: readString(record.sessionId) } : {}),
274
+ ...(readNumber(record.pid) !== undefined ? { pid: readNumber(record.pid) } : {}),
275
+ ...(command ? { command } : {}),
276
+ ...(readString(record.surface) ? { surface: redactedPreview(record.surface, 80) } : {}),
277
+ ...(readString(record.action) ? { action: redactedPreview(record.action, 80) } : {}),
278
+ ...(receiptId ? { receiptId: redactedPreview(receiptId, 96) } : {}),
279
+ ...(chunks.length > 0 ? { outputChunks: chunks } : {}),
280
+ routes,
281
+ ...(readTimestamp(record.updatedAt ?? record.createdAt ?? record.timestamp) ? { updatedAt: readTimestamp(record.updatedAt ?? record.createdAt ?? record.timestamp) } : {}),
282
+ certification: certification({ record, source, kind, id, routes, chunks }),
283
+ };
284
+ }
285
+
286
+ function readSnapshot(source: unknown): unknown {
287
+ if (typeof source === 'function') {
288
+ const value = (source as () => unknown)();
289
+ return value && typeof (value as Promise<unknown>).then === 'function' ? undefined : value;
290
+ }
291
+ const record = readRecord(source);
292
+ for (const methodName of ['getSnapshot', 'snapshot', 'list', 'records']) {
293
+ const method = record[methodName];
294
+ if (typeof method !== 'function') continue;
295
+ const value = (method as () => unknown)();
296
+ return value && typeof (value as Promise<unknown>).then === 'function' ? undefined : value;
297
+ }
298
+ return source;
299
+ }
300
+
301
+ function recordsFromSnapshot(snapshot: unknown, fallbackKind: AgentHarnessInteractiveRuntimeKind): readonly { readonly kind: AgentHarnessInteractiveRuntimeKind; readonly value: unknown }[] {
302
+ if (Array.isArray(snapshot)) return snapshot.map((value) => ({ kind: fallbackKind, value }));
303
+ const record = readRecord(snapshot);
304
+ const keyed: Array<readonly [string, AgentHarnessInteractiveRuntimeKind]> = [
305
+ ['processes', 'process-output'],
306
+ ['liveProcesses', 'process-output'],
307
+ ['outputChunks', 'process-output'],
308
+ ['ptySessions', 'pty-session'],
309
+ ['sessions', 'pty-session'],
310
+ ['sudoMediation', 'sudo-mediation'],
311
+ ['credentials', 'sudo-mediation'],
312
+ ['browserDesktopReceipts', 'browser-desktop-receipt'],
313
+ ['controlReceipts', 'browser-desktop-receipt'],
314
+ ['receipts', 'browser-desktop-receipt'],
315
+ ['records', fallbackKind],
316
+ ['items', fallbackKind],
317
+ ];
318
+ const values: { kind: AgentHarnessInteractiveRuntimeKind; value: unknown }[] = [];
319
+ for (const [key, kind] of keyed) {
320
+ const candidate = record[key];
321
+ if (Array.isArray(candidate)) values.push(...candidate.map((value) => ({ kind, value })));
322
+ else {
323
+ const map = readRecord(candidate);
324
+ if (Object.keys(map).length > 0) values.push(...Object.entries(map).map(([id, value]) => ({ kind, value: { ...readRecord(value), id: readString(readRecord(value).id) || id } })));
325
+ }
326
+ }
327
+ return values.length > 0 ? values : Object.keys(record).length > 0 ? [{ kind: fallbackKind, value: record }] : [];
328
+ }
329
+
330
+ function runtimeSources(context: CommandContext): readonly { readonly path: string; readonly source: unknown; readonly kind: AgentHarnessInteractiveRuntimeKind }[] {
331
+ const platform = context.platform as unknown as Record<string, unknown>;
332
+ const clients = readRecord(context.clients);
333
+ const extensions = context.extensions as unknown as Record<string, unknown>;
334
+ const readModels = readRecord(platform.readModels);
335
+ const execution = readRecord(readModels.execution);
336
+ const computer = readRecord(readModels.computer);
337
+ const browser = readRecord(readModels.browser);
338
+ const operatorSdk = readRecord(clients.operatorSdk);
339
+ const daemonRuntime = readRecord(extensions.daemonRuntime);
340
+ return [
341
+ { path: 'context.platform.readModels.execution.interactiveRuntime', source: execution.interactiveRuntime, kind: 'process-output' },
342
+ { path: 'context.platform.readModels.execution.processRuntime', source: execution.processRuntime, kind: 'process-output' },
343
+ { path: 'context.platform.readModels.execution.ptySessions', source: execution.ptySessions, kind: 'pty-session' },
344
+ { path: 'context.platform.readModels.execution.sudoMediation', source: execution.sudoMediation, kind: 'sudo-mediation' },
345
+ { path: 'context.platform.readModels.computer.browserDesktopReceipts', source: computer.browserDesktopReceipts, kind: 'browser-desktop-receipt' },
346
+ { path: 'context.platform.readModels.browser.controlReceipts', source: browser.controlReceipts, kind: 'browser-desktop-receipt' },
347
+ { path: 'context.clients.operatorSdk.execution.interactiveRuntime', source: readRecord(operatorSdk.execution).interactiveRuntime, kind: 'process-output' },
348
+ { path: 'context.extensions.daemonRuntime.interactiveRuntime', source: daemonRuntime.interactiveRuntime, kind: 'process-output' },
349
+ ];
350
+ }
351
+
352
+ export function interactiveRuntimeRecords(context: CommandContext): readonly AgentHarnessInteractiveRuntimeRecord[] {
353
+ const records: AgentHarnessInteractiveRuntimeRecord[] = [];
354
+ for (const source of runtimeSources(context)) {
355
+ if (source.source === undefined || source.source === null) continue;
356
+ try {
357
+ const snapshot = readSnapshot(source.source);
358
+ records.push(...recordsFromSnapshot(snapshot, source.kind)
359
+ .map((entry) => normalizeRecord(entry.value, source.path, entry.kind))
360
+ .filter((entry): entry is AgentHarnessInteractiveRuntimeRecord => entry !== null));
361
+ } catch {
362
+ // Runtime read models are optional; a broken publisher should not hide local execution posture.
363
+ }
364
+ }
365
+ return records.sort((left, right) => {
366
+ const delta = (right.updatedAt ? Date.parse(right.updatedAt) : 0) - (left.updatedAt ? Date.parse(left.updatedAt) : 0);
367
+ return delta || left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id);
368
+ });
369
+ }
370
+
371
+ function complete(record: AgentHarnessInteractiveRuntimeRecord): boolean {
372
+ return record.certification.schemaStatus === 'certified' && record.certification.missingSignals.length === 0;
373
+ }
374
+
375
+ function kindSummary(records: readonly AgentHarnessInteractiveRuntimeRecord[], kind: AgentHarnessInteractiveRuntimeKind): Record<string, unknown> {
376
+ const matches = records.filter((record) => record.kind === kind);
377
+ const certified = matches.filter(complete);
378
+ return {
379
+ status: certified.length > 0 ? 'certified' : matches.length > 0 ? 'attention' : 'not-published',
380
+ recordCount: matches.length,
381
+ certifiedRecordCount: certified.length,
382
+ ...(matches[0] ? { latest: matches[0] } : {}),
383
+ };
384
+ }
385
+
386
+ export function interactiveRuntimeCapabilitySummary(context: CommandContext): Record<string, unknown> {
387
+ const records = interactiveRuntimeRecords(context);
388
+ const certified = records.filter(complete);
389
+ return {
390
+ status: certified.length > 0 ? 'certified-live-runtime' : records.length > 0 ? 'attention' : 'not-published',
391
+ recordCount: records.length,
392
+ certifiedRecordCount: certified.length,
393
+ liveProcessOutput: kindSummary(records, 'process-output'),
394
+ ptySessions: kindSummary(records, 'pty-session'),
395
+ sudoMediation: kindSummary(records, 'sudo-mediation'),
396
+ browserDesktopControl: kindSummary(records, 'browser-desktop-receipt'),
397
+ latestRecords: records.slice(0, 6),
398
+ policy: 'Certified interactive runtime records are read-only evidence until the selected published route is invoked with its own confirmation boundary.',
399
+ };
400
+ }
401
+
402
+ export function interactiveRuntimeParityStatus(context: CommandContext): {
403
+ readonly stdinWriteContract: boolean;
404
+ readonly ptyContract: boolean;
405
+ readonly sudoMediationContract: boolean;
406
+ readonly browserDesktopControlContract: boolean;
407
+ readonly browserDesktopRoute: string | null;
408
+ readonly browserDesktopRecords: readonly AgentHarnessInteractiveRuntimeRecord[];
409
+ } {
410
+ const certified = interactiveRuntimeRecords(context).filter(complete);
411
+ const hasRoute = (record: AgentHarnessInteractiveRuntimeRecord, id: string) => record.routes.some((route) => route.id === id);
412
+ const browserDesktopRecords = certified.filter((record) => record.kind === 'browser-desktop-receipt');
413
+ return {
414
+ stdinWriteContract: certified.some((record) => (record.kind === 'process-output' || record.kind === 'pty-session') && hasRoute(record, 'write')),
415
+ ptyContract: certified.some((record) => record.kind === 'pty-session'),
416
+ sudoMediationContract: certified.some((record) => record.kind === 'sudo-mediation'),
417
+ browserDesktopControlContract: browserDesktopRecords.length > 0,
418
+ browserDesktopRoute: browserDesktopRecords[0]?.routes.find((route) => route.id === 'execute')?.modelRoute ?? browserDesktopRecords[0]?.routes.find((route) => route.id === 'inspect')?.modelRoute ?? null,
419
+ browserDesktopRecords,
420
+ };
421
+ }
@@ -250,11 +250,11 @@ function resolveHarnessKeybinding(context: CommandContext, args: HarnessKeybindi
250
250
 
251
251
  function keybindingModelRoute(action: KeyAction): string {
252
252
  const route = keybindingOperationRoute(action);
253
- if (route.preferredMode === 'run_keybinding') return `agent_harness mode:"run_keybinding" actionId:"${action}"`;
254
- if (route.preferredMode === 'open_ui_surface' && route.surfaceId) return `agent_harness mode:"open_ui_surface" surfaceId:"${route.surfaceId}"`;
255
- if (route.preferredMode === 'open_ui_surface') return 'agent_harness mode:"open_ui_surface"';
256
- if (route.preferredMode === 'run_command' && route.command) return `agent_harness mode:"run_command" command:"${route.command}"`;
257
- if (route.preferredMode === 'run_command') return 'agent_harness mode:"run_command"';
253
+ if (route.preferredMode === 'run_keybinding') return `workspace action:"run_keybinding" actionId:"${action}"`;
254
+ if (route.preferredMode === 'open_ui_surface' && route.surfaceId) return `workspace action:"open" surfaceId:"${route.surfaceId}"`;
255
+ if (route.preferredMode === 'open_ui_surface') return 'workspace action:"open"';
256
+ if (route.preferredMode === 'run_command' && route.command) return `workspace action:"run_command" command:"${route.command}"`;
257
+ if (route.preferredMode === 'run_command') return 'workspace action:"run_command"';
258
258
  return 'direct-user-interaction';
259
259
  }
260
260
 
@@ -265,6 +265,10 @@ function keybindingModelAccess(action: KeyAction, operation: KeybindingOperation
265
265
  set: `agent_harness mode:"set_keybinding" actionId:"${action}" combo:{...} confirm:true explicitUserRequest:"..."`,
266
266
  reset: `agent_harness mode:"reset_keybinding" actionId:"${action}" confirm:true explicitUserRequest:"..."`,
267
267
  preferred: keybindingModelRoute(action),
268
+ directInspect: `workspace action:"keybinding" actionId:"${action}"`,
269
+ directRun: operation.supported ? `workspace action:"run_keybinding" actionId:"${action}" confirm:true explicitUserRequest:"..."` : 'not exposed',
270
+ directSet: `workspace action:"set_keybinding" actionId:"${action}" combo:{...} confirm:true explicitUserRequest:"..."`,
271
+ directReset: `workspace action:"reset_keybinding" actionId:"${action}" confirm:true explicitUserRequest:"..."`,
268
272
  };
269
273
  }
270
274
 
@@ -435,12 +439,12 @@ export function totalHarnessShortcuts(context: CommandContext): number {
435
439
  }
436
440
 
437
441
  function fixedShortcutModelRoute(shortcut: Record<string, string>): string {
438
- if (shortcut.key.includes('/shortcuts')) return 'agent_harness mode:"shortcuts"';
439
- if (shortcut.key.includes('/keybindings')) return 'agent_harness mode:"keybindings"';
440
- if (shortcut.key.includes('?') || shortcut.key.includes('F1')) return 'agent_harness mode:"open_ui_surface" surfaceId:"help-overlay"';
441
- if (shortcut.key.includes('F2')) return 'agent_harness mode:"open_ui_surface" surfaceId:"process-monitor"';
442
+ if (shortcut.key.includes('/shortcuts')) return 'workspace action:"shortcuts"';
443
+ if (shortcut.key.includes('/keybindings')) return 'workspace action:"keybindings"';
444
+ if (shortcut.key.includes('?') || shortcut.key.includes('F1')) return 'workspace action:"open" surfaceId:"help-overlay"';
445
+ if (shortcut.key.includes('F2')) return 'workspace action:"open" surfaceId:"process-monitor"';
442
446
  if (shortcut.key.includes('Esc')) return 'direct-user-interaction';
443
- if (shortcut.key.includes('Tab')) return 'agent_harness mode:"commands"';
447
+ if (shortcut.key.includes('Tab')) return 'workspace action:"commands"';
444
448
  if (shortcut.key.includes('Enter')) return 'main conversation';
445
449
  return 'direct-user-interaction';
446
450
  }
@@ -452,8 +456,8 @@ function describeFixedShortcut(shortcut: Record<string, string>): Record<string,
452
456
  userEditable: false,
453
457
  modelRoute: fixedShortcutModelRoute(shortcut),
454
458
  modelAccess: {
455
- inspectShortcuts: 'agent_harness mode:"shortcuts"',
456
- inspectKeybindings: 'agent_harness mode:"keybindings"',
459
+ inspectShortcuts: 'workspace action:"shortcuts"',
460
+ inspectKeybindings: 'workspace action:"keybindings"',
457
461
  preferred: fixedShortcutModelRoute(shortcut),
458
462
  },
459
463
  };
@@ -0,0 +1,102 @@
1
+ import type { AgentWorkspaceLocalLibraryItem } from '../input/agent-workspace-types.ts';
2
+ import type { LearningCandidate, LearningCandidateStatus, LearningScores, LocalLearningCandidateDomain } from './agent-harness-learning-curator-types.ts';
3
+ export function clampScore(value: number): number {
4
+ return Math.max(0, Math.min(100, Math.round(value)));
5
+ }
6
+
7
+ export function isReviewed(item: AgentWorkspaceLocalLibraryItem): boolean {
8
+ return item.reviewState === 'reviewed';
9
+ }
10
+
11
+ export function missingRequirementCount(item: AgentWorkspaceLocalLibraryItem): number {
12
+ return item.missingRequirementCount ?? 0;
13
+ }
14
+
15
+ export function itemUsefulness(item: AgentWorkspaceLocalLibraryItem): number {
16
+ const triggerSignal = Math.min(20, item.triggers.length * 5);
17
+ const tagSignal = Math.min(10, item.tags.length * 2);
18
+ const usageSignal = (item.enabled ? 15 : 0) + (item.active ? 15 : 0) + Math.min(20, (item.startCount ?? 0) * 4);
19
+ const confidenceSignal = item.confidence === undefined ? 10 : Math.max(0, Math.min(20, item.confidence / 5));
20
+ return clampScore(35 + triggerSignal + tagSignal + usageSignal + confidenceSignal);
21
+ }
22
+
23
+ export function itemFreshness(item: AgentWorkspaceLocalLibraryItem): number {
24
+ if (item.reviewState === 'stale') return 15;
25
+ if (item.reviewState === 'fresh') return 55;
26
+ return missingRequirementCount(item) > 0 ? 70 : 92;
27
+ }
28
+
29
+ export function itemSourceQuality(item: AgentWorkspaceLocalLibraryItem): number {
30
+ const source = item.source.toLowerCase();
31
+ const base = source.includes('workspace') || source.includes('import') ? 72 : source.includes('agent') ? 68 : 60;
32
+ return clampScore(base + (isReviewed(item) ? 12 : 0) + Math.min(12, item.tags.length * 2));
33
+ }
34
+
35
+ export function itemRisk(item: AgentWorkspaceLocalLibraryItem): number {
36
+ const reviewRisk = item.reviewState === 'stale' ? 60 : item.reviewState === 'fresh' ? 35 : 10;
37
+ const injectionRisk = item.enabled || item.active ? 20 : 0;
38
+ const setupRisk = Math.min(30, missingRequirementCount(item) * 10);
39
+ const confidenceRisk = item.confidence === undefined ? 0 : Math.max(0, 70 - item.confidence);
40
+ return clampScore(reviewRisk + injectionRisk + setupRisk + confidenceRisk);
41
+ }
42
+
43
+ export function scoresForItem(item: AgentWorkspaceLocalLibraryItem): LearningScores {
44
+ return {
45
+ usefulness: itemUsefulness(item),
46
+ freshness: itemFreshness(item),
47
+ sourceQuality: itemSourceQuality(item),
48
+ risk: itemRisk(item),
49
+ };
50
+ }
51
+
52
+ export function routeDomain(domain: LocalLearningCandidateDomain): string {
53
+ return domain;
54
+ }
55
+
56
+ export function localRegistryRoute(domain: LocalLearningCandidateDomain, action: string, id: string): string {
57
+ return `agent_local_registry domain:"${routeDomain(domain)}" action:"${action}" id:"${id}"`;
58
+ }
59
+
60
+ export function localRegistryModelRoute(domain: LocalLearningCandidateDomain): string {
61
+ return `agent_local_registry domain:"${routeDomain(domain)}" action:"get"`;
62
+ }
63
+
64
+ export function routeValue(value: string): string {
65
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
66
+ }
67
+
68
+ export function routeList(values: readonly string[]): string {
69
+ return `[${values.map((value) => `"${routeValue(value)}"`).join(',')}]`;
70
+ }
71
+
72
+ export function candidateBase(
73
+ domain: LocalLearningCandidateDomain,
74
+ item: AgentWorkspaceLocalLibraryItem,
75
+ status: LearningCandidateStatus,
76
+ priority: number,
77
+ reason: string,
78
+ next: string,
79
+ ): LearningCandidate {
80
+ const missingRequirements = item.missingRequirements ?? [];
81
+ return {
82
+ id: `${domain}:${item.id}:${status}`,
83
+ label: item.name,
84
+ domain,
85
+ recordId: item.id,
86
+ status,
87
+ priority: clampScore(priority),
88
+ reason,
89
+ next,
90
+ scores: scoresForItem(item),
91
+ reviewState: item.reviewState,
92
+ ...(item.enabled === undefined ? {} : { enabled: item.enabled }),
93
+ ...(item.active === undefined ? {} : { active: item.active }),
94
+ ...(item.confidence === undefined ? {} : { confidence: item.confidence }),
95
+ ...(missingRequirements.length === 0 ? {} : { missingRequirements }),
96
+ inspectRoute: localRegistryRoute(domain, 'get', item.id),
97
+ modelRoute: localRegistryModelRoute(domain),
98
+ reviewRoute: localRegistryRoute(domain, 'review', item.id),
99
+ staleRoute: `${localRegistryRoute(domain, 'stale', item.id)} reason:"..."`,
100
+ deleteRoute: `${localRegistryRoute(domain, 'delete', item.id)} confirm:true explicitUserRequest:"..."`,
101
+ };
102
+ }