@pellux/goodvibes-agent 1.1.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/CHANGELOG.md +170 -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 +132577 -91483
  6. package/docs/README.md +14 -7
  7. package/docs/channels-remote-and-api.md +8 -5
  8. package/docs/connected-host.md +14 -12
  9. package/docs/getting-started.md +56 -30
  10. package/docs/knowledge-artifacts-and-multimodal.md +7 -5
  11. package/docs/project-planning.md +2 -2
  12. package/docs/providers-and-routing.md +11 -3
  13. package/docs/tools-and-commands.md +114 -45
  14. package/docs/voice-and-live-tts.md +3 -1
  15. package/package.json +3 -2
  16. package/release/release-notes.md +130 -5
  17. package/release/release-readiness.json +120 -90
  18. package/src/agent/assistant-cockpit.ts +247 -0
  19. package/src/agent/autonomy-schedule-format.ts +96 -0
  20. package/src/agent/autonomy-schedule.ts +514 -0
  21. package/src/agent/channel-delivery-receipts.ts +292 -0
  22. package/src/agent/competitive-feature-inventory.ts +296 -0
  23. package/src/agent/document-registry-types.ts +106 -0
  24. package/src/agent/document-registry.ts +734 -0
  25. package/src/agent/harness-control.ts +2 -2
  26. package/src/agent/memory-prompt.ts +2 -2
  27. package/src/agent/operator-actions.ts +50 -6
  28. package/src/agent/persona-registry.ts +8 -0
  29. package/src/agent/project-context-files.ts +273 -0
  30. package/src/agent/prompt-context-receipts.ts +502 -0
  31. package/src/agent/reminder-schedule-format.ts +16 -2
  32. package/src/agent/research-run-registry.ts +582 -0
  33. package/src/agent/research-source-registry.ts +441 -0
  34. package/src/agent/routine-registry.ts +19 -3
  35. package/src/agent/routine-schedule-format.ts +27 -2
  36. package/src/agent/runtime-profile-starters.ts +7 -0
  37. package/src/agent/runtime-profile.ts +106 -4
  38. package/src/agent/schedule-edit-format.ts +129 -0
  39. package/src/agent/schedule-edit.ts +496 -0
  40. package/src/agent/schedule-next-routes.ts +42 -0
  41. package/src/agent/setup-wizard-checkpoint.ts +140 -0
  42. package/src/agent/setup-wizard.ts +447 -0
  43. package/src/agent/skill-registry-types.ts +102 -0
  44. package/src/agent/skill-registry.ts +81 -107
  45. package/src/agent/vibe-confirmation-routes.ts +62 -0
  46. package/src/agent/vibe-file.ts +285 -0
  47. package/src/cli/agent-knowledge-command.ts +45 -2
  48. package/src/cli/help.ts +3 -0
  49. package/src/cli/profiles-command.ts +25 -11
  50. package/src/config/agent-settings-policy.ts +1 -18
  51. package/src/input/agent-workspace-activation.ts +53 -0
  52. package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
  53. package/src/input/agent-workspace-artifact-metadata.ts +17 -0
  54. package/src/input/agent-workspace-basic-command-editors.ts +5 -1
  55. package/src/input/agent-workspace-categories.ts +153 -155
  56. package/src/input/agent-workspace-channel-triage.ts +481 -0
  57. package/src/input/agent-workspace-channels.ts +263 -0
  58. package/src/input/agent-workspace-command-editor.ts +152 -0
  59. package/src/input/agent-workspace-context-snapshot.ts +349 -0
  60. package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
  61. package/src/input/agent-workspace-document-editor.ts +502 -0
  62. package/src/input/agent-workspace-document-ops-editor.ts +523 -0
  63. package/src/input/agent-workspace-host-category.ts +1 -1
  64. package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
  65. package/src/input/agent-workspace-model-compare-editor.ts +376 -0
  66. package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
  67. package/src/input/agent-workspace-model-compare-types.ts +82 -0
  68. package/src/input/agent-workspace-model-compare-utils.ts +35 -0
  69. package/src/input/agent-workspace-onboarding-categories.ts +141 -0
  70. package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
  71. package/src/input/agent-workspace-operations-command-editors.ts +22 -0
  72. package/src/input/agent-workspace-research-report-editor.ts +212 -0
  73. package/src/input/agent-workspace-research-run-editor.ts +144 -0
  74. package/src/input/agent-workspace-research-source-editor.ts +167 -0
  75. package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
  76. package/src/input/agent-workspace-review-packet-utils.ts +265 -0
  77. package/src/input/agent-workspace-settings.ts +262 -30
  78. package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
  79. package/src/input/agent-workspace-setup-snapshot.ts +198 -0
  80. package/src/input/agent-workspace-setup.ts +34 -1
  81. package/src/input/agent-workspace-snapshot.ts +162 -147
  82. package/src/input/agent-workspace-types.ts +287 -2
  83. package/src/input/agent-workspace.ts +18 -0
  84. package/src/input/command-registry.ts +9 -2
  85. package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
  86. package/src/input/commands/channels-runtime.ts +102 -114
  87. package/src/input/commands/delegation-runtime.ts +87 -24
  88. package/src/input/commands/knowledge-browser-flags.ts +12 -0
  89. package/src/input/commands/knowledge.ts +28 -14
  90. package/src/input/commands/operator-actions-runtime.ts +6 -3
  91. package/src/input/commands/schedule-runtime.ts +71 -14
  92. package/src/input/commands/session-content.ts +2 -2
  93. package/src/input/commands/shell-core.ts +2 -0
  94. package/src/input/commands/vibe-runtime.ts +140 -0
  95. package/src/input/commands.ts +2 -0
  96. package/src/input/connected-host-routes.ts +123 -0
  97. package/src/input/settings-modal-types.ts +14 -0
  98. package/src/main.ts +15 -15
  99. package/src/panels/qr-panel.ts +2 -2
  100. package/src/renderer/agent-workspace-context-lines.ts +686 -0
  101. package/src/renderer/agent-workspace.ts +30 -419
  102. package/src/renderer/settings-modal-helpers.ts +12 -0
  103. package/src/renderer/settings-modal.ts +12 -0
  104. package/src/runtime/bootstrap-command-context.ts +11 -1
  105. package/src/runtime/bootstrap-command-parts.ts +14 -3
  106. package/src/runtime/bootstrap-core.ts +39 -1
  107. package/src/runtime/bootstrap-shell.ts +6 -0
  108. package/src/runtime/bootstrap.ts +113 -30
  109. package/src/runtime/connected-host-auth.ts +3 -2
  110. package/src/runtime/execution-ledger.ts +231 -0
  111. package/src/runtime/services.ts +88 -10
  112. package/src/runtime/tool-permission-safety.ts +199 -5
  113. package/src/shell/session-continuity-hints.ts +39 -0
  114. package/src/tools/agent-artifacts-tool.ts +606 -0
  115. package/src/tools/agent-audit-tool.ts +155 -0
  116. package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
  117. package/src/tools/agent-autonomy-tool.ts +140 -0
  118. package/src/tools/agent-channel-send-tool.ts +24 -2
  119. package/src/tools/agent-channels-tool.ts +140 -0
  120. package/src/tools/agent-computer-tool.ts +168 -0
  121. package/src/tools/agent-context-policy.ts +137 -7
  122. package/src/tools/agent-context-tool.ts +143 -0
  123. package/src/tools/agent-delegation-tool.ts +128 -0
  124. package/src/tools/agent-device-tool.ts +240 -0
  125. package/src/tools/agent-documents-tool.ts +684 -0
  126. package/src/tools/agent-execution-tool.ts +230 -0
  127. package/src/tools/agent-harness-agent-orchestration.ts +703 -0
  128. package/src/tools/agent-harness-autonomy-intake.ts +785 -0
  129. package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
  130. package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
  131. package/src/tools/agent-harness-autonomy-queue.ts +560 -0
  132. package/src/tools/agent-harness-background-processes-types.ts +35 -0
  133. package/src/tools/agent-harness-background-processes.ts +794 -0
  134. package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
  135. package/src/tools/agent-harness-browser-control.ts +338 -0
  136. package/src/tools/agent-harness-channel-metadata.ts +168 -10
  137. package/src/tools/agent-harness-cli-command-policy.ts +110 -0
  138. package/src/tools/agent-harness-command-catalog.ts +4 -2
  139. package/src/tools/agent-harness-command-runner.ts +42 -0
  140. package/src/tools/agent-harness-connected-host-status.ts +7 -4
  141. package/src/tools/agent-harness-delegation-posture.ts +234 -19
  142. package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
  143. package/src/tools/agent-harness-document-ops-types.ts +72 -0
  144. package/src/tools/agent-harness-document-ops.ts +671 -0
  145. package/src/tools/agent-harness-execution-history.ts +489 -0
  146. package/src/tools/agent-harness-execution-posture.ts +382 -0
  147. package/src/tools/agent-harness-file-recovery.ts +135 -0
  148. package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
  149. package/src/tools/agent-harness-learning-curator-common.ts +102 -0
  150. package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
  151. package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
  152. package/src/tools/agent-harness-learning-curator-types.ts +151 -0
  153. package/src/tools/agent-harness-learning-curator.ts +417 -0
  154. package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
  155. package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
  156. package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
  157. package/src/tools/agent-harness-media-posture.ts +208 -2
  158. package/src/tools/agent-harness-memory-posture.ts +556 -0
  159. package/src/tools/agent-harness-metadata.ts +100 -168
  160. package/src/tools/agent-harness-mode-catalog.ts +84 -33
  161. package/src/tools/agent-harness-model-catalog.ts +162 -0
  162. package/src/tools/agent-harness-model-provider-health.ts +186 -0
  163. package/src/tools/agent-harness-model-readiness.ts +406 -0
  164. package/src/tools/agent-harness-model-routing-types.ts +266 -0
  165. package/src/tools/agent-harness-model-routing-utils.ts +30 -0
  166. package/src/tools/agent-harness-model-routing.ts +137 -190
  167. package/src/tools/agent-harness-operator-methods.ts +115 -133
  168. package/src/tools/agent-harness-pairing-posture.ts +431 -18
  169. package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
  170. package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
  171. package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
  172. package/src/tools/agent-harness-personal-ops-records.ts +732 -0
  173. package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
  174. package/src/tools/agent-harness-personal-ops-types.ts +222 -0
  175. package/src/tools/agent-harness-personal-ops.ts +606 -0
  176. package/src/tools/agent-harness-project-context.ts +144 -0
  177. package/src/tools/agent-harness-prompt-context.ts +589 -0
  178. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  179. package/src/tools/agent-harness-release-evidence.ts +9 -7
  180. package/src/tools/agent-harness-release-readiness.ts +9 -7
  181. package/src/tools/agent-harness-research-briefing.ts +427 -0
  182. package/src/tools/agent-harness-research-queue.ts +250 -0
  183. package/src/tools/agent-harness-research-runs.ts +295 -0
  184. package/src/tools/agent-harness-research-workflow.ts +322 -0
  185. package/src/tools/agent-harness-security-posture.ts +2 -2
  186. package/src/tools/agent-harness-service-posture.ts +9 -9
  187. package/src/tools/agent-harness-session-metadata.ts +3 -3
  188. package/src/tools/agent-harness-setup-connected-host.ts +501 -0
  189. package/src/tools/agent-harness-setup-describe.ts +557 -0
  190. package/src/tools/agent-harness-setup-handoffs.ts +502 -0
  191. package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
  192. package/src/tools/agent-harness-setup-plan.ts +253 -0
  193. package/src/tools/agent-harness-setup-posture-types.ts +218 -0
  194. package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
  195. package/src/tools/agent-harness-setup-posture.ts +449 -221
  196. package/src/tools/agent-harness-setup-smoke.ts +508 -0
  197. package/src/tools/agent-harness-sudo-posture.ts +114 -0
  198. package/src/tools/agent-harness-tool-schema.ts +120 -7
  199. package/src/tools/agent-harness-tool-types.ts +78 -0
  200. package/src/tools/agent-harness-tool-utils.ts +43 -0
  201. package/src/tools/agent-harness-tool.ts +311 -370
  202. package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
  203. package/src/tools/agent-harness-ui-surface-types.ts +46 -0
  204. package/src/tools/agent-harness-vibe-health.ts +105 -0
  205. package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
  206. package/src/tools/agent-harness-workspace-actions.ts +114 -8
  207. package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
  208. package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
  209. package/src/tools/agent-host-tool.ts +159 -0
  210. package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
  211. package/src/tools/agent-learning-consolidation-core.ts +327 -0
  212. package/src/tools/agent-learning-consolidation-tool.ts +758 -0
  213. package/src/tools/agent-memory-tool.ts +214 -0
  214. package/src/tools/agent-model-compare-export.ts +315 -0
  215. package/src/tools/agent-model-compare-handoff.ts +592 -0
  216. package/src/tools/agent-model-compare-judgment.ts +633 -0
  217. package/src/tools/agent-model-compare-run.ts +722 -0
  218. package/src/tools/agent-model-compare-tool.ts +698 -0
  219. package/src/tools/agent-model-compare-types.ts +191 -0
  220. package/src/tools/agent-model-compare-utils.ts +93 -0
  221. package/src/tools/agent-models-tool.ts +208 -0
  222. package/src/tools/agent-operator-action-tool.ts +1 -1
  223. package/src/tools/agent-operator-method-tool.ts +643 -0
  224. package/src/tools/agent-personal-ops-tool.ts +197 -0
  225. package/src/tools/agent-policy-explanation.ts +415 -0
  226. package/src/tools/agent-research-report-tool.ts +608 -0
  227. package/src/tools/agent-research-runs-tool.ts +434 -0
  228. package/src/tools/agent-research-sources-tool.ts +443 -0
  229. package/src/tools/agent-research-tool.ts +687 -0
  230. package/src/tools/agent-review-packet-presets-core.ts +757 -0
  231. package/src/tools/agent-review-packet-presets-tool.ts +466 -0
  232. package/src/tools/agent-review-packet-share-tool.ts +305 -0
  233. package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
  234. package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
  235. package/src/tools/agent-route-planner-candidates-work.ts +251 -0
  236. package/src/tools/agent-route-planner-helpers.ts +667 -0
  237. package/src/tools/agent-route-planner.ts +185 -0
  238. package/src/tools/agent-route-tool.ts +105 -0
  239. package/src/tools/agent-schedule-edit-tool.ts +210 -0
  240. package/src/tools/agent-schedule-tool.ts +282 -0
  241. package/src/tools/agent-security-tool.ts +145 -0
  242. package/src/tools/agent-sessions-tool.ts +122 -0
  243. package/src/tools/agent-settings-import-tool.ts +104 -0
  244. package/src/tools/agent-settings-tool.ts +176 -0
  245. package/src/tools/agent-setup-tool.ts +226 -0
  246. package/src/tools/agent-support-tool.ts +122 -0
  247. package/src/tools/agent-terminal-process-tools.ts +167 -0
  248. package/src/tools/agent-tool-policy-guard-types.ts +77 -0
  249. package/src/tools/agent-tool-policy-guard.ts +106 -85
  250. package/src/tools/agent-vibe-tool.ts +297 -0
  251. package/src/tools/agent-work-plan-tool.ts +265 -6
  252. package/src/tools/agent-workspace-tool.ts +305 -0
  253. package/src/tools/artifact-archive.ts +169 -0
  254. package/src/tools/tool-definition-compaction.ts +36 -0
  255. package/src/version.ts +1 -1
@@ -0,0 +1,606 @@
1
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
2
+ import type { CommandContext } from '../input/command-registry.ts';
3
+ import { previewHarnessText } from './agent-harness-text.ts';
4
+ import { LANE_IDS, QUEUE_CAPABILITIES, type AgentHarnessPersonalOpsArgs, type McpToolSchema, type PersonalOpsBriefingStatus, type PersonalOpsLane, type PersonalOpsLaneId, type PersonalOpsLaneResolution, type PersonalOpsLiveRecord, type PersonalOpsReadRunResult, type PersonalOpsStatus, type PersonalOpsWorkflowStatus } from './agent-harness-personal-ops-types.ts';
5
+ import { mcpToolCaller, mcpToolRecords, mcpToolSchemas, readLimit, readString, toolsByServer } from './agent-harness-personal-ops-discovery.ts';
6
+ import { buildLanes } from './agent-harness-personal-ops-lanes.ts';
7
+ import { describeLane, laneStatusRank, searchText } from './agent-harness-personal-ops-records.ts';
8
+ import { boundedPersonalOpsResult, executionRouteForRecord, liveRecordSearchText, missingRequiredInputFields, personalOpsReadNextRoutes, personalOpsReadReviewRecords, readInputFields, readRunControlBoolean, readRunControlString, redactedPersonalOpsText, resolveRunRecord, savePersonalOpsReviewArtifact, summarizeRunRecord } from './agent-harness-personal-ops-runner.ts';
9
+ import { buildPersonalOpsIntakeCandidates, nextActions } from './agent-harness-personal-ops-intake.ts';
10
+
11
+ export function personalOpsCatalogStatus(context: CommandContext): Record<string, unknown> {
12
+ const lanes = buildLanes(context);
13
+ const workflows = lanes.flatMap((lane) => lane.workflows ?? []);
14
+ const counts = lanes.reduce<Record<PersonalOpsStatus, number>>((acc, lane) => {
15
+ acc[lane.status] += 1;
16
+ return acc;
17
+ }, { ready: 0, partial: 0, 'needs-setup': 0, gap: 0 });
18
+ return {
19
+ modes: ['personal_ops_briefing', 'personal_ops', 'personal_ops_queue', 'personal_ops_lane', 'personal_ops_intake', 'run_personal_ops_read'],
20
+ lanes: lanes.length,
21
+ ...counts,
22
+ workflows: workflows.length,
23
+ readyWorkflows: workflows.filter((workflow) => workflow.status === 'ready').length,
24
+ attentionWorkflows: workflows.filter((workflow) => workflow.status === 'attention').length,
25
+ setupWorkflows: workflows.filter((workflow) => workflow.status === 'needs-setup').length,
26
+ bestReadyStatus: lanes.reduce((best, lane) => Math.max(best, laneStatusRank(lane.status)), 0),
27
+ };
28
+ }
29
+
30
+ function briefingStatusForLane(lane: PersonalOpsLane): PersonalOpsBriefingStatus {
31
+ if (lane.status === 'ready') return 'ready';
32
+ if (lane.status === 'partial') return 'attention';
33
+ return 'needs-setup';
34
+ }
35
+
36
+ function briefingStatusRank(status: PersonalOpsBriefingStatus): number {
37
+ if (status === 'ready') return 3;
38
+ if (status === 'attention') return 2;
39
+ return 1;
40
+ }
41
+
42
+ function briefingStatusFromSteps(steps: readonly Record<string, unknown>[]): PersonalOpsBriefingStatus {
43
+ const statuses = steps
44
+ .map((step) => typeof step.status === 'string' ? step.status : '')
45
+ .filter((status): status is PersonalOpsBriefingStatus => status === 'ready' || status === 'attention' || status === 'needs-setup');
46
+ if (statuses.length === 0) return 'needs-setup';
47
+ if (statuses.some((status) => status === 'needs-setup')) return statuses.some((status) => status !== 'needs-setup') ? 'attention' : 'needs-setup';
48
+ return statuses.some((status) => status === 'attention') ? 'attention' : 'ready';
49
+ }
50
+
51
+ function recordCount(lane: PersonalOpsLane, predicate: (record: PersonalOpsLiveRecord) => boolean): number {
52
+ return (lane.liveRecords ?? []).filter(predicate).length;
53
+ }
54
+
55
+ function workflowCount(lane: PersonalOpsLane, status: PersonalOpsWorkflowStatus): number {
56
+ return (lane.workflows ?? []).filter((workflow) => workflow.status === status).length;
57
+ }
58
+
59
+ function briefingPurpose(lane: PersonalOpsLane): string {
60
+ if (lane.id === 'inbox') return 'Find inbox items that need attention while keeping replies, labels, archive, and sends as separate confirmed effects.';
61
+ if (lane.id === 'calendar') return 'Brief the agenda, conflicts, prep gaps, and reminder opportunities before any event edit or RSVP.';
62
+ if (lane.id === 'tasks') return 'Make ongoing work visible through work plans and connected-host task inspection.';
63
+ if (lane.id === 'reminders') return 'Capture follow-ups as confirmed reminders or schedules with an explicit delivery path.';
64
+ if (lane.id === 'routines') return 'Reuse reviewed routines and only promote schedules when cadence and scope are explicit.';
65
+ if (lane.id === 'delivery') return 'Check whether summaries, reminders, and follow-ups can reach the user through configured channels.';
66
+ return 'Capture useful working context locally without promoting it to durable memory until reviewed.';
67
+ }
68
+
69
+ function briefingNext(lane: PersonalOpsLane): string {
70
+ const freshProviderReads = recordCount(lane, (record) => record.freshness?.status === 'fresh-provider-route-ready');
71
+ const refreshableSavedRecords = recordCount(lane, (record) => record.freshness?.status === 'saved-review-refreshable');
72
+ const savedReviewRecords = recordCount(lane, (record) => record.freshness?.source === 'saved-review-artifact' || typeof record.reviewRecordCount === 'number');
73
+ const attentionWorkflows = workflowCount(lane, 'attention');
74
+ if ((lane.id === 'inbox' || lane.id === 'calendar') && freshProviderReads > 0) {
75
+ return 'Pick one bounded read-only record and run it with personal_ops action:"read" only when the user asks for live provider data.';
76
+ }
77
+ if ((lane.id === 'inbox' || lane.id === 'calendar') && refreshableSavedRecords > 0) {
78
+ return 'Recap the saved redacted queue first; refresh a single record only through the returned confirmed read route.';
79
+ }
80
+ if ((lane.id === 'inbox' || lane.id === 'calendar') && savedReviewRecords > 0) {
81
+ return 'Use saved redacted review records for today, then repair a provider connector before promising fresh queue state.';
82
+ }
83
+ if ((lane.id === 'inbox' || lane.id === 'calendar') && attentionWorkflows > 0) {
84
+ return 'Repair connector trust, connection, or schema freshness before reading live personal data.';
85
+ }
86
+ return lane.next;
87
+ }
88
+
89
+ function briefingStepForLane(lane: PersonalOpsLane, includeParameters: boolean): Record<string, unknown> {
90
+ const records = lane.liveRecords ?? [];
91
+ const workflows = lane.workflows ?? [];
92
+ const freshProviderReads = recordCount(lane, (record) => record.freshness?.status === 'fresh-provider-route-ready');
93
+ const refreshableSavedRecords = recordCount(lane, (record) => record.freshness?.status === 'saved-review-refreshable');
94
+ const savedReviewRecords = recordCount(lane, (record) => record.freshness?.source === 'saved-review-artifact' || typeof record.reviewRecordCount === 'number');
95
+ const readOnlyRecords = recordCount(lane, (record) => record.effect === 'read-only');
96
+ const confirmedEffectRecords = recordCount(lane, (record) => record.effect === 'confirmed-effect' || record.confirmationRequired === true);
97
+ const laneRoute = `personal_ops action:"lane" laneId:"${lane.id}"`;
98
+ const intakeRoute = `personal_ops action:"intake" query:"${lane.label.toLowerCase()} daily briefing"`;
99
+ const workflowRoutes = workflows
100
+ .map((workflow) => workflow.modelRoute)
101
+ .filter((route, index, routes) => route && routes.indexOf(route) === index)
102
+ .slice(0, includeParameters ? 4 : 2);
103
+ const evidence = [
104
+ ...lane.signals.slice(0, includeParameters ? 8 : 4),
105
+ `${records.length} live/operation record(s)`,
106
+ `${workflowCount(lane, 'ready')} ready workflow(s)`,
107
+ `${workflowCount(lane, 'attention')} attention workflow(s)`,
108
+ `${freshProviderReads} fresh provider read route(s)`,
109
+ `${refreshableSavedRecords} refreshable saved review record(s)`,
110
+ `${savedReviewRecords} saved review record(s)`,
111
+ ];
112
+ return {
113
+ id: lane.id,
114
+ label: lane.label,
115
+ status: briefingStatusForLane(lane),
116
+ purpose: briefingPurpose(lane),
117
+ next: previewHarnessText(briefingNext(lane), includeParameters ? 220 : 140),
118
+ modelRoute: laneRoute,
119
+ inspectRoutes: [laneRoute, intakeRoute, ...workflowRoutes].slice(0, includeParameters ? 8 : 4),
120
+ evidence,
121
+ sourceCounts: {
122
+ records: records.length,
123
+ readOnlyRecords,
124
+ confirmedEffectRecords,
125
+ workflows: workflows.length,
126
+ readyWorkflows: workflowCount(lane, 'ready'),
127
+ attentionWorkflows: workflowCount(lane, 'attention'),
128
+ freshProviderReads,
129
+ refreshableSavedRecords,
130
+ savedReviewRecords,
131
+ connectorSignals: lane.connectorSignals?.length ?? 0,
132
+ },
133
+ confirmationBoundary: lane.id === 'inbox' || lane.id === 'calendar'
134
+ ? 'Live provider reads stay bounded and selected; sends, labels, archives, moves, event edits, RSVP, and deletes require a separate confirmed route.'
135
+ : 'Writes, sends, schedules, connected-host controls, and operator effects require an explicit user request and the owning confirmed tool.',
136
+ };
137
+ }
138
+
139
+ function autonomyBriefingStep(): Record<string, unknown> {
140
+ return {
141
+ id: 'autonomy-queue',
142
+ label: 'Autonomy Queue',
143
+ status: 'ready' satisfies PersonalOpsBriefingStatus,
144
+ purpose: 'Review visible ongoing work, owners, status, tails, receipts, and cancel or recovery routes before starting more autonomous work.',
145
+ next: 'Inspect the autonomy queue and resolve running, blocked, or ownerless work before adding new background jobs.',
146
+ modelRoute: 'autonomy action:"queue"',
147
+ inspectRoutes: [
148
+ 'autonomy action:"queue"',
149
+ 'autonomy action:"intake" query:"daily operations follow-up"',
150
+ ],
151
+ evidence: [
152
+ 'Visible autonomy queue has a first-class harness mode',
153
+ 'Queue item inspection exposes cancel and recovery routes',
154
+ 'New ongoing work should enter through autonomy action:"intake" or a visible schedule/work-plan route',
155
+ ],
156
+ sourceCounts: {
157
+ records: 0,
158
+ workflows: 1,
159
+ readyWorkflows: 1,
160
+ attentionWorkflows: 0,
161
+ freshProviderReads: 0,
162
+ refreshableSavedRecords: 0,
163
+ savedReviewRecords: 0,
164
+ connectorSignals: 0,
165
+ },
166
+ confirmationBoundary: 'Starting, cancelling, retrying, scheduling, or mutating autonomous work remains a separate confirmed route.',
167
+ };
168
+ }
169
+
170
+ export async function personalOpsBriefingSummary(context: CommandContext, args: AgentHarnessPersonalOpsArgs): Promise<Record<string, unknown>> {
171
+ const includeParameters = args.includeParameters === true;
172
+ const request = readString(args.query) || readString(args.target) || 'daily personal operations';
173
+ const tools = await mcpToolRecords(context);
174
+ const lanes = buildLanes(context, {
175
+ toolsByServer: toolsByServer(tools),
176
+ schemasByQualifiedName: includeParameters ? await mcpToolSchemas(context, tools) : new Map<string, McpToolSchema>(),
177
+ });
178
+ const orderedLaneIds: readonly PersonalOpsLaneId[] = ['inbox', 'calendar', 'tasks', 'reminders', 'routines', 'delivery', 'notes'];
179
+ const allSteps = [
180
+ ...orderedLaneIds
181
+ .map((laneId) => lanes.find((lane) => lane.id === laneId))
182
+ .filter((lane): lane is PersonalOpsLane => lane !== undefined)
183
+ .map((lane) => briefingStepForLane(lane, includeParameters)),
184
+ autonomyBriefingStep(),
185
+ ];
186
+ const limit = readLimit(args.limit, includeParameters ? 8 : 6);
187
+ const steps = allSteps.slice(0, limit);
188
+ const status = briefingStatusFromSteps(steps);
189
+ const readiness = steps.reduce<Record<PersonalOpsBriefingStatus, number>>((acc, step) => {
190
+ const stepStatus = typeof step.status === 'string' ? step.status : 'needs-setup';
191
+ if (stepStatus === 'ready' || stepStatus === 'attention' || stepStatus === 'needs-setup') acc[stepStatus] += 1;
192
+ return acc;
193
+ }, { ready: 0, attention: 0, 'needs-setup': 0 });
194
+ const liveRecords = lanes.flatMap((lane) => lane.liveRecords ?? []);
195
+ const workflows = lanes.flatMap((lane) => lane.workflows ?? []);
196
+ const missingContracts = lanes
197
+ .filter((lane) => (lane.id === 'inbox' || lane.id === 'calendar') && lane.status !== 'ready')
198
+ .map((lane) => `${lane.label}: fresh provider-backed queues need a ready connector or daemon record before GoodVibes can promise current live data.`);
199
+ return {
200
+ status,
201
+ title: 'Daily Personal Ops briefing plan',
202
+ request: previewHarnessText(request, includeParameters ? 220 : 120),
203
+ generatedFrom: 'Current Agent-local workspace state, GoodVibes daemon contract metadata, MCP connector posture, saved review queues, and schema-derived Personal Ops records.',
204
+ returned: steps.length,
205
+ total: allSteps.length,
206
+ steps,
207
+ readiness,
208
+ recommendedOrder: steps
209
+ .slice()
210
+ .sort((left, right) => briefingStatusRank((right.status as PersonalOpsBriefingStatus) ?? 'needs-setup') - briefingStatusRank((left.status as PersonalOpsBriefingStatus) ?? 'needs-setup'))
211
+ .map((step) => step.id),
212
+ sourceSummary: {
213
+ lanes: lanes.length,
214
+ liveRecords: liveRecords.length,
215
+ workflows: workflows.length,
216
+ readyWorkflows: workflows.filter((workflow) => workflow.status === 'ready').length,
217
+ attentionWorkflows: workflows.filter((workflow) => workflow.status === 'attention').length,
218
+ freshProviderReads: liveRecords.filter((record) => record.freshness?.status === 'fresh-provider-route-ready').length,
219
+ refreshableSavedRecords: liveRecords.filter((record) => record.freshness?.status === 'saved-review-refreshable').length,
220
+ savedReviewRecords: liveRecords.filter((record) => record.freshness?.source === 'saved-review-artifact' || typeof record.reviewRecordCount === 'number').length,
221
+ connectorSignals: lanes.reduce((sum, lane) => sum + (lane.connectorSignals?.length ?? 0), 0),
222
+ },
223
+ routes: {
224
+ personalOps: 'personal_ops action:"status"',
225
+ intake: 'personal_ops action:"intake" query:"..."',
226
+ laneTemplate: 'personal_ops action:"lane" laneId:"inbox|calendar|notes|tasks|reminders|routines|delivery"',
227
+ liveReadTemplate: 'personal_ops action:"read" laneId:"inbox|calendar" recordId:"..." fields:{...} confirm:true explicitUserRequest:"..."',
228
+ autonomyQueue: 'autonomy action:"queue"',
229
+ workspace: 'workspace action:"actions" categoryId:"personal-ops"',
230
+ },
231
+ nextActions: [
232
+ 'Start with ready steps, then resolve attention or setup steps before promising live provider state.',
233
+ 'Use saved redacted inbox/calendar review queues for recap when fresh connectors are not ready.',
234
+ 'Run exactly one live personal-data read at a time through personal_ops action:"read", then summarize before any follow-up effect.',
235
+ ...nextActions(lanes).slice(0, 3),
236
+ ].slice(0, includeParameters ? 8 : 5),
237
+ missingContracts,
238
+ policy: 'This briefing is read-only. It plans the user-facing daily operations flow across Personal Ops lanes and the autonomy queue; live personal-data reads, sends, edits, schedule creation, connected-host controls, and external delivery remain separate explicit confirmed actions.',
239
+ };
240
+ }
241
+
242
+ export async function personalOpsSummary(context: CommandContext, args: AgentHarnessPersonalOpsArgs): Promise<Record<string, unknown>> {
243
+ const includeParameters = args.includeParameters === true;
244
+ const tools = includeParameters ? await mcpToolRecords(context) : [];
245
+ const schemasByQualifiedName = includeParameters ? await mcpToolSchemas(context, tools) : new Map<string, McpToolSchema>();
246
+ const lanes = buildLanes(context, {
247
+ toolsByServer: toolsByServer(tools),
248
+ schemasByQualifiedName,
249
+ });
250
+ const workflows = lanes.flatMap((lane) => lane.workflows ?? []);
251
+ return {
252
+ lanes: lanes.map((lane) => describeLane(lane, includeParameters)),
253
+ returned: lanes.length,
254
+ total: lanes.length,
255
+ workflowSummary: {
256
+ workflows: workflows.length,
257
+ ready: workflows.filter((workflow) => workflow.status === 'ready').length,
258
+ attention: workflows.filter((workflow) => workflow.status === 'attention').length,
259
+ needsSetup: workflows.filter((workflow) => workflow.status === 'needs-setup').length,
260
+ },
261
+ policy: 'Personal Ops unifies inbox, agenda, notes, tasks, reminders, routines, and delivery. Lanes include live records when Agent owns them and schema-derived connector operation records when MCP schemas are available. Missing email/calendar connectors, messages, and events are reported as setup/data gaps, not faked.',
262
+ nextActions: nextActions(lanes),
263
+ };
264
+ }
265
+
266
+ function queueRecordType(record: PersonalOpsLiveRecord): string {
267
+ if (record.capability === 'inbox-thread-review') return 'saved-inbox-thread';
268
+ if (record.capability === 'calendar-event-review') return 'saved-calendar-event';
269
+ if (record.capability === 'inbox-review-artifact') return 'saved-inbox-review';
270
+ if (record.capability === 'calendar-review-artifact') return 'saved-calendar-review';
271
+ if (record.freshness?.status === 'fresh-provider-route-ready') return 'fresh-provider-read';
272
+ if (record.freshness?.status === 'connector-attention') return 'provider-read-attention';
273
+ return record.capability ?? 'personal-ops-record';
274
+ }
275
+
276
+ function queueStatusRank(record: PersonalOpsLiveRecord): number {
277
+ if (record.freshness?.status === 'saved-review-refreshable') return 100;
278
+ if (record.capability === 'inbox-thread-review' || record.capability === 'calendar-event-review') return 90;
279
+ if (record.freshness?.status === 'fresh-provider-route-ready') return 80;
280
+ if (record.capability === 'inbox-review-artifact' || record.capability === 'calendar-review-artifact') return 70;
281
+ if (record.freshness?.status === 'connector-attention') return 50;
282
+ if (record.freshness?.status === 'provider-contract-missing') return 35;
283
+ if (record.freshness?.status === 'source-tool-missing') return 25;
284
+ return 10;
285
+ }
286
+
287
+ function isPersonalOpsQueueRecord(record: PersonalOpsLiveRecord): boolean {
288
+ if (record.freshness?.source === 'saved-review-artifact') return true;
289
+ if (record.freshness?.status === 'fresh-provider-route-ready' || record.freshness?.status === 'connector-attention') return true;
290
+ return record.capability ? QUEUE_CAPABILITIES.has(record.capability) : false;
291
+ }
292
+
293
+ function describeQueueItem(lane: PersonalOpsLane, record: PersonalOpsLiveRecord, includeParameters: boolean): Record<string, unknown> {
294
+ const refreshRoute = record.freshness?.refreshRoute;
295
+ const followUpRoutes = (record.followUpRoutes ?? []).slice(0, includeParameters ? 8 : 3);
296
+ return {
297
+ queueItemId: `${lane.id}:${record.id}`,
298
+ laneId: lane.id,
299
+ laneLabel: lane.label,
300
+ type: queueRecordType(record),
301
+ label: record.label,
302
+ status: record.status,
303
+ summary: previewHarnessText(record.summary, includeParameters ? 220 : 120),
304
+ modelRoute: previewHarnessText(record.modelRoute, includeParameters ? 180 : 120),
305
+ inspectRoute: record.modelRoute,
306
+ effect: record.effect ?? 'read-only',
307
+ capability: record.capability,
308
+ confirmationRequired: Boolean(record.confirmationRequired),
309
+ ...(record.freshness ? {
310
+ freshness: {
311
+ status: record.freshness.status,
312
+ source: record.freshness.source,
313
+ ...(record.freshness.sourceTool ? { sourceTool: record.freshness.sourceTool } : {}),
314
+ ...(record.freshness.lastReviewedAt ? { lastReviewedAt: record.freshness.lastReviewedAt } : {}),
315
+ ...(refreshRoute ? { refreshRoute, refreshRequiresConfirmation: true } : {}),
316
+ policy: previewHarnessText(record.freshness.policy, includeParameters ? 220 : 120),
317
+ },
318
+ } : {}),
319
+ routes: {
320
+ lane: `personal_ops action:"lane" laneId:"${lane.id}"`,
321
+ inspect: record.modelRoute,
322
+ ...(refreshRoute ? { refresh: refreshRoute } : {}),
323
+ ...(record.artifactId ? { artifact: `agent_artifacts show artifactId:"${record.artifactId}" includeContent:true` } : {}),
324
+ },
325
+ followUpRoutes,
326
+ ...(includeParameters && record.tags && record.tags.length > 0 ? { tags: record.tags } : {}),
327
+ ...(includeParameters && record.requiredFields ? { requiredFields: record.requiredFields } : {}),
328
+ ...(includeParameters && record.sampleInput ? { sampleInput: record.sampleInput } : {}),
329
+ ...(includeParameters && record.artifactId ? { artifactId: record.artifactId } : {}),
330
+ ...(includeParameters && record.reviewLabels ? { reviewLabels: record.reviewLabels } : {}),
331
+ ...(includeParameters && record.sourceTool ? { sourceTool: record.sourceTool } : {}),
332
+ };
333
+ }
334
+
335
+ function queueSearchText(item: { readonly lane: PersonalOpsLane; readonly record: PersonalOpsLiveRecord }): string {
336
+ return [
337
+ item.lane.id,
338
+ item.lane.label,
339
+ liveRecordSearchText(item.record),
340
+ ].join('\n').toLowerCase();
341
+ }
342
+
343
+ export async function personalOpsQueueSummary(context: CommandContext, args: AgentHarnessPersonalOpsArgs): Promise<Record<string, unknown>> {
344
+ const includeParameters = args.includeParameters === true;
345
+ const query = readString(args.query) || readString(args.target);
346
+ const tools = await mcpToolRecords(context);
347
+ const lanes = buildLanes(context, {
348
+ toolsByServer: toolsByServer(tools),
349
+ schemasByQualifiedName: includeParameters ? await mcpToolSchemas(context, tools) : new Map<string, McpToolSchema>(),
350
+ });
351
+ const queueLanes = lanes.filter((lane) => lane.id === 'inbox' || lane.id === 'calendar');
352
+ const allItems = queueLanes
353
+ .flatMap((lane) => (lane.liveRecords ?? [])
354
+ .filter(isPersonalOpsQueueRecord)
355
+ .map((record) => ({ lane, record })))
356
+ .filter((item) => !query || queueSearchText(item).includes(query.toLowerCase()))
357
+ .sort((left, right) => queueStatusRank(right.record) - queueStatusRank(left.record) || left.lane.id.localeCompare(right.lane.id) || left.record.label.localeCompare(right.record.label));
358
+ const limit = readLimit(args.limit, includeParameters ? 20 : 8);
359
+ const items = allItems.slice(0, limit).map((item) => describeQueueItem(item.lane, item.record, includeParameters));
360
+ const readRecords = allItems.filter((item) => item.record.effect === 'read-only');
361
+ const confirmedFollowUps = allItems.reduce((total, item) => total + (item.record.followUpRoutes ?? []).filter((route) => route.requiresConfirmation).length, 0);
362
+ const freshProviderReads = allItems.filter((item) => item.record.freshness?.status === 'fresh-provider-route-ready').length;
363
+ const refreshableSavedRecords = allItems.filter((item) => item.record.freshness?.status === 'saved-review-refreshable').length;
364
+ const savedReviewRecords = allItems.filter((item) => item.record.freshness?.source === 'saved-review-artifact' || typeof item.record.reviewRecordCount === 'number').length;
365
+ const attentionRecords = allItems.filter((item) => item.record.freshness?.status === 'connector-attention' || item.lane.status === 'gap' || item.lane.status === 'needs-setup').length;
366
+ return {
367
+ status: allItems.length > 0 ? attentionRecords > 0 ? 'attention' : 'ready' : 'empty',
368
+ queue: items,
369
+ returned: items.length,
370
+ total: allItems.length,
371
+ summary: {
372
+ inbox: allItems.filter((item) => item.lane.id === 'inbox').length,
373
+ calendar: allItems.filter((item) => item.lane.id === 'calendar').length,
374
+ readOnlyRecords: readRecords.length,
375
+ freshProviderReads,
376
+ refreshableSavedRecords,
377
+ savedReviewRecords,
378
+ attentionRecords,
379
+ confirmedFollowUps,
380
+ },
381
+ routes: {
382
+ status: 'personal_ops action:"status"',
383
+ briefing: 'personal_ops action:"briefing"',
384
+ intake: 'personal_ops action:"intake" query:"..."',
385
+ inboxLane: 'personal_ops action:"lane" laneId:"inbox"',
386
+ calendarLane: 'personal_ops action:"lane" laneId:"calendar"',
387
+ liveReadTemplate: 'personal_ops action:"read" laneId:"inbox|calendar" recordId:"..." fields:{...} confirm:true explicitUserRequest:"..."',
388
+ },
389
+ nextActions: allItems.length > 0
390
+ ? [
391
+ refreshableSavedRecords > 0 ? 'Refresh one saved queue item only through its returned confirmed read route when the user asks for current provider state.' : '',
392
+ freshProviderReads > 0 ? 'Run one fresh provider read at a time, summarize it, and save review cards when the user wants a durable queue.' : '',
393
+ 'Use saved redacted queue artifacts for recap or local drafts before any external send, label, archive, edit, RSVP, or delete.',
394
+ ].filter(Boolean).slice(0, includeParameters ? 5 : 3)
395
+ : [
396
+ 'Run personal_ops action:"intake" for the user request to find a safe connector route.',
397
+ 'Set up an inbox or calendar connector before promising fresh queue state.',
398
+ ],
399
+ policy: 'Personal Ops queue is read-only. It aggregates existing saved review artifacts and connector read-route records; it does not execute MCP tools, read live provider data, send messages, edit calendar events, create reminders, or mutate artifacts.',
400
+ };
401
+ }
402
+
403
+ export async function personalOpsIntakeSummary(context: CommandContext, args: AgentHarnessPersonalOpsArgs): Promise<Record<string, unknown>> {
404
+ const request = readString(args.query) || readString(args.target);
405
+ if (!request) {
406
+ return {
407
+ status: 'missing_request',
408
+ usage: 'Use personal_ops action:"intake" with query:"<personal operations request>". This action is read-only and returns the safest lane, route, missing fields, and confirmation boundary.',
409
+ examples: [
410
+ 'Triage my unread inbox.',
411
+ 'Draft a reply to this email thread.',
412
+ 'Brief my calendar for today.',
413
+ 'Remind me tomorrow at 9am to send the report.',
414
+ ],
415
+ personalOpsRoute: 'personal_ops action:"status"',
416
+ };
417
+ }
418
+ const includeParameters = args.includeParameters === true;
419
+ const tools = await mcpToolRecords(context);
420
+ const lanes = buildLanes(context, {
421
+ toolsByServer: toolsByServer(tools),
422
+ schemasByQualifiedName: await mcpToolSchemas(context, tools),
423
+ });
424
+ const limit = readLimit(args.limit, includeParameters ? 8 : 4);
425
+ const candidates = buildPersonalOpsIntakeCandidates(request, lanes, includeParameters).slice(0, limit);
426
+ const preferred = candidates[0]!;
427
+ return {
428
+ status: 'ready',
429
+ request: previewHarnessText(request, includeParameters ? 220 : 120),
430
+ preferred,
431
+ candidates,
432
+ personalOpsRoute: 'personal_ops action:"status"',
433
+ laneRoute: `personal_ops action:"lane" laneId:"${preferred.laneId}"`,
434
+ policy: 'Personal Ops intake is read-only. It chooses the safest visible lane and route; live personal-data reads must use reviewed connector or daemon routes, and every send, edit, schedule, or external effect still requires explicit confirmation.',
435
+ };
436
+ }
437
+
438
+ export async function runPersonalOpsRead(context: CommandContext, args: AgentHarnessPersonalOpsArgs): Promise<PersonalOpsReadRunResult> {
439
+ const includeParameters = args.includeParameters === true;
440
+ const tools = await mcpToolRecords(context);
441
+ const lanes = buildLanes(context, {
442
+ toolsByServer: toolsByServer(tools),
443
+ schemasByQualifiedName: await mcpToolSchemas(context, tools),
444
+ });
445
+ const resolved = resolveRunRecord(lanes, {
446
+ laneId: readString(args.laneId),
447
+ recordId: readString(args.recordId),
448
+ target: readString(args.target),
449
+ query: readString(args.query),
450
+ });
451
+ if (!resolved) {
452
+ return {
453
+ status: 'missing_lookup',
454
+ usage: 'personal_ops action:"read" requires laneId plus recordId, target, or query for one read-only inbox/calendar connector operation.',
455
+ examples: [
456
+ 'personal_ops action:"intake" query:"Triage my unread email." includeParameters:true',
457
+ 'personal_ops action:"read" laneId:"inbox" recordId:"mcp:gmail-inbox:gmail.search_messages" fields:{"query":"is:unread newer_than:7d"} confirm:true explicitUserRequest:"Triage my unread inbox."',
458
+ ],
459
+ };
460
+ }
461
+ if (!('lane' in resolved)) return resolved;
462
+
463
+ const { lane, record } = resolved;
464
+ const runRoute = executionRouteForRecord(record, lane.id);
465
+ const fields = readInputFields(args.fields, record.sampleInput);
466
+ const recordSummary = summarizeRunRecord(record, lane, includeParameters);
467
+
468
+ if (record.status !== 'ready') {
469
+ return {
470
+ status: 'blocked',
471
+ reason: 'connector_not_ready',
472
+ record: recordSummary,
473
+ next: 'Repair connector trust, connection, or schema freshness before reading live personal data.',
474
+ inspectRoutes: [record.modelRoute, `personal_ops action:"lane" laneId:"${lane.id}"`],
475
+ policy: 'Personal Ops never reads personal provider data from attention or setup-needed connectors.',
476
+ };
477
+ }
478
+ if (record.effect !== 'read-only') {
479
+ return {
480
+ status: 'blocked',
481
+ reason: 'not_read_only',
482
+ record: recordSummary,
483
+ next: 'Use the returned confirmed-effect route only after the user reviews the intended provider mutation.',
484
+ policy: 'personal_ops action:"read" refuses send, edit, label, archive, delete, move, create, RSVP, and reschedule operations.',
485
+ };
486
+ }
487
+ if (!record.qualifiedName) {
488
+ return {
489
+ status: 'blocked',
490
+ reason: 'missing_qualified_tool',
491
+ record: recordSummary,
492
+ next: 'Inspect the connector schema or lane operation records until a qualified MCP tool name is available.',
493
+ };
494
+ }
495
+ const missingFields = missingRequiredInputFields(record, fields);
496
+ if (missingFields.length > 0) {
497
+ return {
498
+ status: 'missing_fields',
499
+ record: recordSummary,
500
+ missingFields,
501
+ sampleInput: record.sampleInput ?? {},
502
+ runRoute,
503
+ policy: 'Required fields must be supplied by the user or an explicit intake plan; placeholder sample values are never executed silently.',
504
+ };
505
+ }
506
+ if (!readString(args.explicitUserRequest) || args.confirm !== true) {
507
+ return {
508
+ status: 'needs_confirmation',
509
+ record: recordSummary,
510
+ inputPreview: fields,
511
+ runRoute,
512
+ policy: 'Reading live personal inbox/calendar data requires confirm:true and explicitUserRequest. Write-like provider effects are not supported by this route.',
513
+ };
514
+ }
515
+ const callTool = mcpToolCaller(context);
516
+ if (!callTool) {
517
+ return {
518
+ status: 'unavailable',
519
+ reason: 'mcp_call_tool_unavailable',
520
+ record: recordSummary,
521
+ next: 'Use MCP tool discovery and connector setup until the runtime publishes callTool for this Agent session.',
522
+ };
523
+ }
524
+
525
+ try {
526
+ const result = await callTool(record.qualifiedName, fields);
527
+ const reviewRecords = personalOpsReadReviewRecords(lane, record, result, includeParameters);
528
+ const output = boundedPersonalOpsResult(result, includeParameters);
529
+ const saveRequested = readRunControlBoolean(args.fields, ['saveReviewCards', 'saveReview']);
530
+ const savedReviewArtifact = saveRequested
531
+ ? await savePersonalOpsReviewArtifact({
532
+ context,
533
+ lane,
534
+ sourceRecord: record,
535
+ inputFields: fields,
536
+ reviewRecords,
537
+ output,
538
+ title: readRunControlString(args.fields, 'artifactTitle'),
539
+ })
540
+ : null;
541
+ const nextRoutes = personalOpsReadNextRoutes({ lane, runRoute, savedReviewArtifact });
542
+ return {
543
+ status: 'executed',
544
+ record: recordSummary,
545
+ input: fields,
546
+ output,
547
+ reviewSummary: {
548
+ kind: lane.id === 'calendar' ? 'calendar' : 'inbox',
549
+ records: reviewRecords.length,
550
+ source: record.qualifiedName,
551
+ },
552
+ reviewRecords,
553
+ ...(savedReviewArtifact ? { savedReviewArtifact } : {}),
554
+ nextRoutes,
555
+ followUp: [
556
+ 'Summarize or draft only in the Agent transcript.',
557
+ 'Ask for explicit confirmation before any send, label, archive, delete, calendar edit, RSVP, or reschedule route.',
558
+ ],
559
+ policy: 'This route executed one selected read-only MCP connector tool and returned bounded, redacted output for review.',
560
+ };
561
+ } catch (error) {
562
+ return {
563
+ status: 'failed',
564
+ record: recordSummary,
565
+ input: fields,
566
+ error: previewHarnessText(redactedPersonalOpsText(summarizeError(error)), 320),
567
+ policy: 'Connector failures are reported as reviewable errors; no fallback personal data is fabricated.',
568
+ };
569
+ }
570
+ }
571
+
572
+ export async function describePersonalOpsLane(context: CommandContext, args: AgentHarnessPersonalOpsArgs): Promise<PersonalOpsLaneResolution> {
573
+ const laneId = readString(args.laneId);
574
+ const target = readString(args.target);
575
+ const query = readString(args.query);
576
+ const input = laneId || target || query;
577
+ if (!input) {
578
+ return {
579
+ status: 'missing_lookup',
580
+ usage: `personal_ops action:"lane" requires laneId, target, or query. Lane ids: ${LANE_IDS.join(', ')}.`,
581
+ };
582
+ }
583
+ const normalized = input.toLowerCase();
584
+ const tools = await mcpToolRecords(context);
585
+ const lanes = buildLanes(context, { toolsByServer: toolsByServer(tools), schemasByQualifiedName: await mcpToolSchemas(context, tools) });
586
+ const exact = lanes.find((lane) => lane.id === normalized);
587
+ if (exact) return { status: 'found', lane: describeLane(exact, true) };
588
+ const matches = lanes.filter((lane) => searchText(lane).includes(normalized));
589
+ if (matches.length === 1) return { status: 'found', lane: describeLane(matches[0]!, true) };
590
+ if (matches.length > 1) {
591
+ return {
592
+ status: 'ambiguous',
593
+ input,
594
+ candidates: matches.map((lane) => ({
595
+ laneId: lane.id,
596
+ label: lane.label,
597
+ status: lane.status,
598
+ modelRoute: lane.modelRoute,
599
+ })),
600
+ };
601
+ }
602
+ return {
603
+ status: 'missing_lookup',
604
+ usage: `Unknown Personal Ops lane ${input}. Lane ids: ${LANE_IDS.join(', ')}.`,
605
+ };
606
+ }