@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,643 @@
1
+ import { getOperatorContract } from '@pellux/goodvibes-sdk/contracts';
2
+ import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
3
+ import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
4
+ import type { ShellPathService } from '@/runtime/index.ts';
5
+ import type { AgentConnectedHostConfigReader } from '../agent/routine-schedule-promotion.ts';
6
+ import { resolveAgentConnectedHostConnection } from '../agent/routine-schedule-promotion.ts';
7
+
8
+ type JsonRecord = Record<string, unknown>;
9
+
10
+ interface OperatorContractMethod {
11
+ readonly id: string;
12
+ readonly title?: string;
13
+ readonly category?: string;
14
+ readonly access?: string;
15
+ readonly scopes?: readonly string[];
16
+ readonly http?: {
17
+ readonly method?: string;
18
+ readonly path?: string;
19
+ };
20
+ }
21
+
22
+ interface OperatorMethodToolArgs {
23
+ readonly methodId?: unknown;
24
+ readonly input?: unknown;
25
+ readonly confirm?: unknown;
26
+ readonly explicitUserRequest?: unknown;
27
+ readonly dryRun?: unknown;
28
+ }
29
+
30
+ interface PreparedOperatorRoute {
31
+ readonly method: OperatorContractMethod;
32
+ readonly httpMethod: string;
33
+ readonly path: string;
34
+ readonly payload: JsonRecord;
35
+ }
36
+
37
+ type ServiceMethodId = 'services.status' | 'services.install' | 'services.start' | 'services.restart';
38
+ type WatcherMethodId =
39
+ | 'watchers.create'
40
+ | 'watchers.patch'
41
+ | 'watchers.run'
42
+ | 'watchers.start'
43
+ | 'watchers.stop'
44
+ | 'watchers.delete';
45
+
46
+ const READ_ONLY_HTTP_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
47
+ const MAX_OUTPUT_CHARS = 18_000;
48
+ const SERVICE_METHOD_IDS = new Set<string>(['services.status', 'services.install', 'services.start', 'services.restart']);
49
+ const WATCHER_METHOD_IDS = new Set<string>([
50
+ 'watchers.create',
51
+ 'watchers.patch',
52
+ 'watchers.run',
53
+ 'watchers.start',
54
+ 'watchers.stop',
55
+ 'watchers.delete',
56
+ ]);
57
+
58
+ function isRecord(value: unknown): value is JsonRecord {
59
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
60
+ }
61
+
62
+ function readString(value: unknown): string {
63
+ return typeof value === 'string' ? value.trim() : '';
64
+ }
65
+
66
+ function readBoolean(value: unknown): boolean {
67
+ return value === true || value === 'true';
68
+ }
69
+
70
+ function operatorMethods(): readonly OperatorContractMethod[] {
71
+ const contract = getOperatorContract();
72
+ return Array.isArray(contract.operator?.methods)
73
+ ? contract.operator.methods as OperatorContractMethod[]
74
+ : [];
75
+ }
76
+
77
+ function findOperatorMethod(methodId: string): OperatorContractMethod | null {
78
+ const methods = operatorMethods();
79
+ return methods.find((method) => method.id === methodId)
80
+ ?? methods.find((method) => method.id.toLowerCase() === methodId.toLowerCase())
81
+ ?? null;
82
+ }
83
+
84
+ function methodIsReadOnly(method: OperatorContractMethod): boolean {
85
+ const httpMethod = method.http?.method?.toUpperCase();
86
+ if (httpMethod && READ_ONLY_HTTP_METHODS.has(httpMethod)) return true;
87
+ const scopes = method.scopes ?? [];
88
+ return scopes.length > 0 && scopes.every((scope) => scope.startsWith('read:'));
89
+ }
90
+
91
+ function methodRequiresConfirmation(method: OperatorContractMethod): boolean {
92
+ return !methodIsReadOnly(method);
93
+ }
94
+
95
+ function cloneInput(input: unknown): JsonRecord {
96
+ if (!isRecord(input)) return {};
97
+ return { ...input };
98
+ }
99
+
100
+ function encodePathValue(value: unknown, name: string): string {
101
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
102
+ return encodeURIComponent(String(value));
103
+ }
104
+ throw new Error(`operator method path parameter '${name}' must be a string, number, or boolean.`);
105
+ }
106
+
107
+ function substitutePath(pathTemplate: string, payload: JsonRecord): string {
108
+ let path = pathTemplate.replace(/\{([^}]+)\}/g, (_match, rawName: string) => {
109
+ const name = rawName.trim();
110
+ if (!(name in payload)) throw new Error(`operator method path parameter '${name}' is required.`);
111
+ const encoded = encodePathValue(payload[name], name);
112
+ delete payload[name];
113
+ return encoded;
114
+ });
115
+ path = path.replace(/:([A-Za-z][A-Za-z0-9_]*)/g, (match, name: string) => {
116
+ if (!(name in payload)) return match;
117
+ const encoded = encodePathValue(payload[name], name);
118
+ delete payload[name];
119
+ return encoded;
120
+ });
121
+ return path;
122
+ }
123
+
124
+ function prepareOperatorRoute(method: OperatorContractMethod, input: unknown): PreparedOperatorRoute {
125
+ const httpMethod = method.http?.method?.toUpperCase();
126
+ const pathTemplate = method.http?.path;
127
+ if (!httpMethod || !pathTemplate) {
128
+ throw new Error(`operator method '${method.id}' is not invokable over HTTP.`);
129
+ }
130
+ const payload = cloneInput(input);
131
+ const path = substitutePath(pathTemplate, payload);
132
+ return { method, httpMethod, path, payload };
133
+ }
134
+
135
+ function appendQuery(path: string, payload: JsonRecord): string {
136
+ const query = new URLSearchParams();
137
+ for (const [key, value] of Object.entries(payload)) {
138
+ if (value === undefined || value === null) continue;
139
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
140
+ query.set(key, String(value));
141
+ } else {
142
+ query.set(key, JSON.stringify(value));
143
+ }
144
+ }
145
+ const queryString = query.toString();
146
+ if (!queryString) return path;
147
+ return `${path}${path.includes('?') ? '&' : '?'}${queryString}`;
148
+ }
149
+
150
+ function isServiceMethodId(methodId: string): methodId is ServiceMethodId {
151
+ return SERVICE_METHOD_IDS.has(methodId);
152
+ }
153
+
154
+ function isWatcherMethodId(methodId: string): methodId is WatcherMethodId {
155
+ return WATCHER_METHOD_IDS.has(methodId);
156
+ }
157
+
158
+ function serviceSuccessCriteria(methodId: ServiceMethodId): readonly string[] {
159
+ if (methodId === 'services.status') {
160
+ return [
161
+ 'The daemon returns a service status receipt with installed, autostart, and running booleans.',
162
+ 'The receipt has no actionError.',
163
+ ];
164
+ }
165
+ if (methodId === 'services.install') {
166
+ return [
167
+ 'The daemon returns ok:true for services.install.',
168
+ 'The service status receipt reports installed:true.',
169
+ 'The receipt has no actionError.',
170
+ 'Verify with services.status before retrying install, start, or restart.',
171
+ ];
172
+ }
173
+ if (methodId === 'services.start') {
174
+ return [
175
+ 'The daemon returns ok:true for services.start.',
176
+ 'The service status receipt reports running:true.',
177
+ 'The receipt has no actionError.',
178
+ 'Verify with services.status before retrying start or escalating.',
179
+ ];
180
+ }
181
+ return [
182
+ 'The daemon returns ok:true for services.restart.',
183
+ 'The service status receipt reports running:true after restart.',
184
+ 'The receipt has no actionError.',
185
+ 'Verify with services.status before retrying restart or escalating.',
186
+ ];
187
+ }
188
+
189
+ function serviceExpectedOutcome(methodId: ServiceMethodId): JsonRecord {
190
+ const target = methodId === 'services.status'
191
+ ? 'read-current-posture'
192
+ : methodId === 'services.install'
193
+ ? 'installed-service'
194
+ : methodId === 'services.start'
195
+ ? 'running-service'
196
+ : 'restarted-running-service';
197
+ return {
198
+ target,
199
+ successCriteria: serviceSuccessCriteria(methodId),
200
+ evidenceFields: ['installed', 'autostart', 'running', 'pid', 'lastAction', 'actionError', 'network.controlPlane.ready'],
201
+ verificationRoute: 'agent_operator_method methodId:"services.status"',
202
+ recoveryRoute: 'setup action:"item" setupItemId:"connected-host-readiness" includeParameters:true',
203
+ };
204
+ }
205
+
206
+ function watcherSuccessCriteria(methodId: WatcherMethodId): readonly string[] {
207
+ if (methodId === 'watchers.delete') {
208
+ return [
209
+ 'The daemon returns ok:true for watchers.delete.',
210
+ 'The watcher receipt reports removed:true.',
211
+ 'Verify with watchers.list before recreating or assuming no trigger remains.',
212
+ ];
213
+ }
214
+ const action = methodId.split('.')[1] ?? 'watcher';
215
+ return [
216
+ `The daemon returns ok:true for ${methodId}.`,
217
+ 'The watcher receipt includes id, label, kind, state, source, and metadata.',
218
+ 'The watcher receipt has no lastError.',
219
+ `Verify with watchers.list before retrying ${action} or assuming the trigger is active.`,
220
+ ];
221
+ }
222
+
223
+ function watcherExpectedOutcome(methodId: WatcherMethodId): JsonRecord {
224
+ const target = methodId === 'watchers.create'
225
+ ? 'created-visible-watcher'
226
+ : methodId === 'watchers.patch'
227
+ ? 'updated-visible-watcher'
228
+ : methodId === 'watchers.run'
229
+ ? 'triggered-watcher-run'
230
+ : methodId === 'watchers.start'
231
+ ? 'started-watcher'
232
+ : methodId === 'watchers.stop'
233
+ ? 'stopped-watcher'
234
+ : 'deleted-watcher';
235
+ return {
236
+ target,
237
+ successCriteria: watcherSuccessCriteria(methodId),
238
+ evidenceFields: ['id', 'kind', 'label', 'state', 'source.kind', 'source.enabled', 'sourceStatus', 'lastCheckpoint', 'lastError'],
239
+ verificationRoute: 'agent_operator_method methodId:"watchers.list"',
240
+ recoveryRoute: 'autonomy action:"intake" query:"watcher trigger" includeParameters:true',
241
+ };
242
+ }
243
+
244
+ function summarizePreview(route: PreparedOperatorRoute): JsonRecord {
245
+ const readOnly = methodIsReadOnly(route.method);
246
+ const preview: JsonRecord = {
247
+ methodId: route.method.id,
248
+ title: route.method.title ?? route.method.id,
249
+ category: route.method.category ?? 'uncategorized',
250
+ access: route.method.access ?? 'authenticated',
251
+ scopes: route.method.scopes ?? [],
252
+ route: `${route.httpMethod} ${route.path}`,
253
+ effect: readOnly
254
+ ? 'read-only-network'
255
+ : route.method.access === 'admin'
256
+ ? 'confirmed-admin-connected-host-state'
257
+ : 'confirmed-connected-host-state',
258
+ confirmationRequired: !readOnly,
259
+ body: READ_ONLY_HTTP_METHODS.has(route.httpMethod) ? undefined : route.payload,
260
+ query: READ_ONLY_HTTP_METHODS.has(route.httpMethod) ? route.payload : undefined,
261
+ };
262
+ if (isServiceMethodId(route.method.id)) preview.expectedOutcome = serviceExpectedOutcome(route.method.id);
263
+ if (isWatcherMethodId(route.method.id)) preview.expectedOutcome = watcherExpectedOutcome(route.method.id);
264
+ return preview;
265
+ }
266
+
267
+ function redacted(value: unknown, depth = 0): unknown {
268
+ if (depth > 6) return '[truncated-depth]';
269
+ if (Array.isArray(value)) return value.slice(0, 100).map((entry) => redacted(entry, depth + 1));
270
+ if (!isRecord(value)) return value;
271
+ const output: JsonRecord = {};
272
+ for (const [key, entry] of Object.entries(value)) {
273
+ if (/(token|password|secret|apikey|api_key|authorization|cookie|credential)/i.test(key)) {
274
+ output[key] = '[redacted]';
275
+ } else {
276
+ output[key] = redacted(entry, depth + 1);
277
+ }
278
+ }
279
+ return output;
280
+ }
281
+
282
+ function stringifyOutput(value: unknown): string {
283
+ const text = JSON.stringify(redacted(value), null, 2);
284
+ if (text.length <= MAX_OUTPUT_CHARS) return text;
285
+ return `${text.slice(0, MAX_OUTPUT_CHARS)}\n...[truncated ${text.length - MAX_OUTPUT_CHARS} chars]`;
286
+ }
287
+
288
+ function summarizeError(error: unknown): string {
289
+ return error instanceof Error ? error.message : String(error);
290
+ }
291
+
292
+ function serviceReceiptEvidence(body: unknown): JsonRecord {
293
+ if (!isRecord(body)) return { receipt: 'not-json-object' };
294
+ const network = isRecord(body.network) ? body.network : {};
295
+ const controlPlane = isRecord(network.controlPlane) ? network.controlPlane : {};
296
+ return {
297
+ installed: typeof body.installed === 'boolean' ? body.installed : null,
298
+ autostart: typeof body.autostart === 'boolean' ? body.autostart : null,
299
+ running: typeof body.running === 'boolean' ? body.running : null,
300
+ pid: typeof body.pid === 'number' ? body.pid : null,
301
+ lastAction: typeof body.lastAction === 'string' ? body.lastAction : null,
302
+ actionError: typeof body.actionError === 'string' ? body.actionError : null,
303
+ controlPlaneReady: typeof controlPlane.ready === 'boolean' ? controlPlane.ready : null,
304
+ };
305
+ }
306
+
307
+ function serviceLifecycleRoute(methodId: ServiceMethodId): string {
308
+ return operatorMethodLikeRoute(methodId, methodId !== 'services.status');
309
+ }
310
+
311
+ function operatorMethodLikeRoute(methodId: string, confirmed: boolean): string {
312
+ return `agent_operator_method methodId:"${methodId}" input:{}${confirmed ? ' confirm:true explicitUserRequest:"..."' : ''}`;
313
+ }
314
+
315
+ function serviceLifecycleDecisionFromStatus(body: unknown): JsonRecord {
316
+ const evidence = serviceReceiptEvidence(body);
317
+ const common = {
318
+ evidence,
319
+ statusRoute: serviceLifecycleRoute('services.status'),
320
+ setupRoute: 'setup action:"item" setupItemId:"connected-host-readiness" includeParameters:true',
321
+ decisionRules: [
322
+ 'installed:false -> services.install',
323
+ 'installed:true and running:false -> services.start',
324
+ 'installed:true and running:true and network.controlPlane.ready:false -> services.restart',
325
+ 'installed:true and running:true with no failed control-plane evidence -> no lifecycle mutation',
326
+ ],
327
+ };
328
+ if (!isRecord(body)) {
329
+ return {
330
+ status: 'needs-follow-up',
331
+ action: 'read-status',
332
+ reason: 'The service status receipt was not a JSON object.',
333
+ modelRoute: serviceLifecycleRoute('services.status'),
334
+ ...common,
335
+ };
336
+ }
337
+ const actionError = typeof body.actionError === 'string' ? body.actionError.trim() : '';
338
+ if (actionError) {
339
+ return {
340
+ status: 'needs-diagnostics',
341
+ action: 'inspect-diagnostics',
342
+ reason: 'The service status receipt includes actionError; inspect diagnostics before any lifecycle mutation.',
343
+ modelRoute: 'host action:"services" includeParameters:true',
344
+ ...common,
345
+ };
346
+ }
347
+ if (typeof body.installed !== 'boolean' || typeof body.running !== 'boolean' || typeof body.autostart !== 'boolean') {
348
+ return {
349
+ status: 'needs-follow-up',
350
+ action: 'read-status',
351
+ reason: 'The service status receipt is missing installed, autostart, or running booleans.',
352
+ modelRoute: serviceLifecycleRoute('services.status'),
353
+ ...common,
354
+ };
355
+ }
356
+ if (body.installed === false) {
357
+ return {
358
+ status: 'action-recommended',
359
+ action: 'install-service',
360
+ methodId: 'services.install',
361
+ modelRoute: serviceLifecycleRoute('services.install'),
362
+ reason: 'services.status reports installed:false.',
363
+ ...common,
364
+ };
365
+ }
366
+ if (body.running === false) {
367
+ return {
368
+ status: 'action-recommended',
369
+ action: 'start-service',
370
+ methodId: 'services.start',
371
+ modelRoute: serviceLifecycleRoute('services.start'),
372
+ reason: 'services.status reports installed:true and running:false.',
373
+ ...common,
374
+ };
375
+ }
376
+ const network = isRecord(body.network) ? body.network : {};
377
+ const controlPlane = isRecord(network.controlPlane) ? network.controlPlane : {};
378
+ if (controlPlane.ready === false) {
379
+ return {
380
+ status: 'action-recommended',
381
+ action: 'restart-service',
382
+ methodId: 'services.restart',
383
+ modelRoute: serviceLifecycleRoute('services.restart'),
384
+ reason: 'services.status reports the service is installed and running, but network.controlPlane.ready:false.',
385
+ ...common,
386
+ };
387
+ }
388
+ return {
389
+ status: 'no-action-needed',
390
+ action: 'none',
391
+ reason: 'services.status reports the service is installed and running with no failed control-plane readiness evidence.',
392
+ modelRoute: 'host action:"status" includeParameters:true',
393
+ ...common,
394
+ };
395
+ }
396
+
397
+ function serviceLifecycleDecision(methodId: ServiceMethodId, responseOk: boolean, body: unknown): JsonRecord {
398
+ if (!responseOk) {
399
+ return {
400
+ status: 'needs-follow-up',
401
+ action: 'read-status',
402
+ reason: `The ${methodId} daemon call failed; do not retry a lifecycle mutation until services.status or host action:"services" explains the failure.`,
403
+ evidence: serviceReceiptEvidence(body),
404
+ statusRoute: serviceLifecycleRoute('services.status'),
405
+ setupRoute: 'setup action:"item" setupItemId:"connected-host-readiness" includeParameters:true',
406
+ modelRoute: 'host action:"services" includeParameters:true',
407
+ };
408
+ }
409
+ if (methodId === 'services.status') return serviceLifecycleDecisionFromStatus(body);
410
+ return {
411
+ status: 'verify-status',
412
+ action: 'read-status',
413
+ reason: `${methodId} returned a receipt; verify services.status before closing or advancing setup.`,
414
+ evidence: serviceReceiptEvidence(body),
415
+ statusRoute: serviceLifecycleRoute('services.status'),
416
+ setupRoute: 'setup action:"item" setupItemId:"connected-host-readiness" includeParameters:true',
417
+ modelRoute: serviceLifecycleRoute('services.status'),
418
+ };
419
+ }
420
+
421
+ function serviceOutcomeCertified(methodId: ServiceMethodId, responseOk: boolean, body: unknown): boolean {
422
+ if (!responseOk || !isRecord(body) || (typeof body.actionError === 'string' && body.actionError.trim())) return false;
423
+ if (methodId === 'services.status') {
424
+ return typeof body.installed === 'boolean'
425
+ && typeof body.autostart === 'boolean'
426
+ && typeof body.running === 'boolean';
427
+ }
428
+ if (methodId === 'services.install') return body.installed === true;
429
+ if (methodId === 'services.start') return body.running === true;
430
+ return body.running === true;
431
+ }
432
+
433
+ function serviceOutcome(methodId: string, responseOk: boolean, body: unknown): JsonRecord | undefined {
434
+ if (!isServiceMethodId(methodId)) return undefined;
435
+ const certified = serviceOutcomeCertified(methodId, responseOk, body);
436
+ return {
437
+ kind: 'service-repair-receipt',
438
+ status: !responseOk
439
+ ? 'failed'
440
+ : certified
441
+ ? 'certified'
442
+ : 'needs-follow-up',
443
+ certified,
444
+ methodId,
445
+ evidence: serviceReceiptEvidence(body),
446
+ expectedOutcome: serviceExpectedOutcome(methodId),
447
+ lifecycleDecision: serviceLifecycleDecision(methodId, responseOk, body),
448
+ nextStep: certified
449
+ ? 'Inspect services.status or connected-host status to confirm the setup wizard can advance.'
450
+ : 'Inspect services.status and connected-host setup posture before retrying a lifecycle mutation.',
451
+ };
452
+ }
453
+
454
+ function watcherReceiptEvidence(body: unknown): JsonRecord {
455
+ if (!isRecord(body)) return { receipt: 'not-json-object' };
456
+ const source = isRecord(body.source) ? body.source : {};
457
+ return {
458
+ id: typeof body.id === 'string' ? body.id : null,
459
+ kind: typeof body.kind === 'string' ? body.kind : null,
460
+ label: typeof body.label === 'string' ? body.label : null,
461
+ state: typeof body.state === 'string' ? body.state : null,
462
+ sourceKind: typeof source.kind === 'string' ? source.kind : null,
463
+ sourceEnabled: typeof source.enabled === 'boolean' ? source.enabled : null,
464
+ sourceStatus: typeof body.sourceStatus === 'string' ? body.sourceStatus : null,
465
+ lastCheckpoint: typeof body.lastCheckpoint === 'string' ? body.lastCheckpoint : null,
466
+ lastError: typeof body.lastError === 'string' ? body.lastError : null,
467
+ };
468
+ }
469
+
470
+ function watcherOutcomeCertified(methodId: WatcherMethodId, responseOk: boolean, body: unknown): boolean {
471
+ if (!responseOk || !isRecord(body)) return false;
472
+ if (methodId === 'watchers.delete') return body.removed === true;
473
+ if (typeof body.lastError === 'string' && body.lastError.trim()) return false;
474
+ return typeof body.id === 'string'
475
+ && typeof body.kind === 'string'
476
+ && typeof body.label === 'string'
477
+ && typeof body.state === 'string'
478
+ && isRecord(body.source);
479
+ }
480
+
481
+ function watcherOutcome(methodId: string, responseOk: boolean, body: unknown): JsonRecord | undefined {
482
+ if (!isWatcherMethodId(methodId)) return undefined;
483
+ const certified = watcherOutcomeCertified(methodId, responseOk, body);
484
+ return {
485
+ kind: 'watcher-receipt',
486
+ status: !responseOk
487
+ ? 'failed'
488
+ : certified
489
+ ? 'certified'
490
+ : 'needs-follow-up',
491
+ certified,
492
+ methodId,
493
+ evidence: watcherReceiptEvidence(body),
494
+ expectedOutcome: watcherExpectedOutcome(methodId),
495
+ nextStep: certified
496
+ ? 'Inspect watchers.list or autonomy action:"queue" to verify the trigger remains visible and scoped.'
497
+ : 'Inspect watchers.list and autonomy action:"intake" before retrying watcher setup or run control.',
498
+ };
499
+ }
500
+
501
+ function operatorOutcome(methodId: string, responseOk: boolean, body: unknown): JsonRecord | undefined {
502
+ return serviceOutcome(methodId, responseOk, body)
503
+ ?? watcherOutcome(methodId, responseOk, body);
504
+ }
505
+
506
+ async function readResponseBody(response: Response): Promise<unknown> {
507
+ const text = await response.text();
508
+ if (!text.trim()) return {};
509
+ try {
510
+ return JSON.parse(text) as unknown;
511
+ } catch {
512
+ return text;
513
+ }
514
+ }
515
+
516
+ export function createAgentOperatorMethodTool(
517
+ shellPaths: ShellPathService,
518
+ configManager: AgentConnectedHostConfigReader,
519
+ ): Tool {
520
+ return {
521
+ definition: {
522
+ name: 'agent_operator_method',
523
+ description: 'Invoke a GoodVibes daemon operator method with confirmation gates.',
524
+ parameters: {
525
+ type: 'object',
526
+ properties: {
527
+ methodId: {
528
+ type: 'string',
529
+ description: 'Exact operator method id, for example services.status.',
530
+ },
531
+ input: {
532
+ type: 'object',
533
+ description: 'Method input; path keys are removed before query/body.',
534
+ additionalProperties: true,
535
+ },
536
+ confirm: {
537
+ type: 'boolean',
538
+ description: 'Required true for write/admin operator methods.',
539
+ },
540
+ explicitUserRequest: {
541
+ type: 'string',
542
+ description: 'User request authorizing a write/admin method.',
543
+ },
544
+ dryRun: {
545
+ type: 'boolean',
546
+ description: 'Preview the route without calling the daemon.',
547
+ },
548
+ },
549
+ required: ['methodId'],
550
+ additionalProperties: false,
551
+ },
552
+ sideEffects: ['network', 'state'],
553
+ },
554
+ execute: async (rawArgs: unknown) => {
555
+ const args = isRecord(rawArgs) ? rawArgs as OperatorMethodToolArgs : {};
556
+ const methodId = readString(args.methodId);
557
+ if (!methodId) return { success: false, error: 'methodId is required.' };
558
+ const method = findOperatorMethod(methodId);
559
+ if (!method) {
560
+ return {
561
+ success: false,
562
+ error: `Unknown GoodVibes operator method '${methodId}'. Inspect host action:"methods" first.`,
563
+ };
564
+ }
565
+
566
+ let route: PreparedOperatorRoute;
567
+ try {
568
+ route = prepareOperatorRoute(method, args.input);
569
+ } catch (error) {
570
+ return { success: false, error: summarizeError(error) };
571
+ }
572
+
573
+ const preview = summarizePreview(route);
574
+ if (readBoolean(args.dryRun)) {
575
+ return { success: true, output: stringifyOutput({ preview, dryRun: true }) };
576
+ }
577
+
578
+ if (methodRequiresConfirmation(method)) {
579
+ const explicitUserRequest = readString(args.explicitUserRequest);
580
+ if (!readBoolean(args.confirm) || !explicitUserRequest) {
581
+ return {
582
+ success: false,
583
+ error: stringifyOutput({
584
+ preview,
585
+ required: {
586
+ confirm: true,
587
+ explicitUserRequest: 'Required for write/admin operator methods.',
588
+ },
589
+ }),
590
+ };
591
+ }
592
+ }
593
+
594
+ const connection = resolveAgentConnectedHostConnection(configManager, shellPaths.homeDirectory);
595
+ if (!connection.token) {
596
+ return {
597
+ success: false,
598
+ error: `auth_required: no connected-host operator token found at ${connection.tokenPath}`,
599
+ };
600
+ }
601
+
602
+ const requestPath = READ_ONLY_HTTP_METHODS.has(route.httpMethod)
603
+ ? appendQuery(route.path, route.payload)
604
+ : route.path;
605
+ try {
606
+ const response = await fetch(`${connection.baseUrl}${requestPath}`, {
607
+ method: route.httpMethod,
608
+ headers: {
609
+ authorization: `Bearer ${connection.token}`,
610
+ ...(READ_ONLY_HTTP_METHODS.has(route.httpMethod) ? {} : { 'content-type': 'application/json' }),
611
+ },
612
+ ...(READ_ONLY_HTTP_METHODS.has(route.httpMethod) ? {} : { body: JSON.stringify(route.payload) }),
613
+ });
614
+ const body = await readResponseBody(response);
615
+ const outcome = operatorOutcome(route.method.id, response.ok, body);
616
+ const output = {
617
+ methodId: route.method.id,
618
+ status: response.status,
619
+ ok: response.ok,
620
+ route: `${route.httpMethod} ${requestPath}`,
621
+ effect: preview.effect,
622
+ body,
623
+ ...(outcome ? { outcome } : {}),
624
+ };
625
+ if (!response.ok) return { success: false, error: stringifyOutput(output) };
626
+ return { success: true, output: stringifyOutput(output) };
627
+ } catch (error) {
628
+ return {
629
+ success: false,
630
+ error: `connected_host_unavailable: ${summarizeError(error)}`,
631
+ };
632
+ }
633
+ },
634
+ };
635
+ }
636
+
637
+ export function registerAgentOperatorMethodTool(
638
+ registry: ToolRegistry,
639
+ shellPaths: ShellPathService,
640
+ configManager: AgentConnectedHostConfigReader,
641
+ ): void {
642
+ registry.register(createAgentOperatorMethodTool(shellPaths, configManager));
643
+ }