@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.
- package/CHANGELOG.md +329 -0
- package/README.md +46 -30
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
- package/dist/package/main.js +138903 -91477
- package/docs/README.md +14 -7
- package/docs/channels-remote-and-api.md +8 -5
- package/docs/connected-host.md +14 -12
- package/docs/getting-started.md +58 -30
- package/docs/knowledge-artifacts-and-multimodal.md +7 -5
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +11 -3
- package/docs/tools-and-commands.md +116 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +142 -5
- package/release/release-readiness.json +124 -94
- package/src/agent/assistant-cockpit.ts +247 -0
- package/src/agent/autonomy-schedule-format.ts +96 -0
- package/src/agent/autonomy-schedule.ts +514 -0
- package/src/agent/channel-delivery-receipts.ts +292 -0
- package/src/agent/competitive-feature-inventory.ts +294 -0
- package/src/agent/document-registry-types.ts +106 -0
- package/src/agent/document-registry.ts +734 -0
- package/src/agent/harness-control.ts +2 -2
- package/src/agent/memory-prompt.ts +2 -2
- package/src/agent/operator-actions.ts +50 -6
- package/src/agent/persona-registry.ts +8 -0
- package/src/agent/project-context-files.ts +273 -0
- package/src/agent/prompt-context-receipts.ts +502 -0
- package/src/agent/reminder-schedule-format.ts +16 -2
- package/src/agent/research-run-registry.ts +582 -0
- package/src/agent/research-source-registry.ts +441 -0
- package/src/agent/routine-registry.ts +19 -3
- package/src/agent/routine-schedule-format.ts +27 -2
- package/src/agent/runtime-profile-starters.ts +7 -0
- package/src/agent/runtime-profile.ts +106 -4
- package/src/agent/schedule-edit-format.ts +129 -0
- package/src/agent/schedule-edit.ts +496 -0
- package/src/agent/schedule-next-routes.ts +42 -0
- package/src/agent/setup-wizard-artifact-receipts.ts +366 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +683 -0
- package/src/agent/skill-registry-types.ts +102 -0
- package/src/agent/skill-registry.ts +81 -107
- package/src/agent/vibe-confirmation-routes.ts +62 -0
- package/src/agent/vibe-file.ts +285 -0
- package/src/cli/agent-knowledge-command.ts +45 -2
- package/src/cli/help.ts +3 -0
- package/src/cli/profiles-command.ts +25 -11
- package/src/config/agent-settings-policy.ts +1 -18
- package/src/input/agent-workspace-activation.ts +53 -0
- package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
- package/src/input/agent-workspace-artifact-metadata.ts +17 -0
- package/src/input/agent-workspace-basic-command-editors.ts +5 -1
- package/src/input/agent-workspace-categories.ts +153 -155
- package/src/input/agent-workspace-channel-triage.ts +481 -0
- package/src/input/agent-workspace-channels.ts +263 -0
- package/src/input/agent-workspace-command-editor.ts +152 -0
- package/src/input/agent-workspace-context-snapshot.ts +349 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
- package/src/input/agent-workspace-document-editor.ts +502 -0
- package/src/input/agent-workspace-document-ops-editor.ts +523 -0
- package/src/input/agent-workspace-host-category.ts +1 -1
- package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
- package/src/input/agent-workspace-model-compare-editor.ts +376 -0
- package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
- package/src/input/agent-workspace-model-compare-types.ts +82 -0
- package/src/input/agent-workspace-model-compare-utils.ts +35 -0
- package/src/input/agent-workspace-onboarding-categories.ts +141 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
- package/src/input/agent-workspace-operations-command-editors.ts +22 -0
- package/src/input/agent-workspace-research-report-editor.ts +212 -0
- package/src/input/agent-workspace-research-run-editor.ts +144 -0
- package/src/input/agent-workspace-research-source-editor.ts +167 -0
- package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
- package/src/input/agent-workspace-review-packet-utils.ts +265 -0
- package/src/input/agent-workspace-settings.ts +313 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +217 -0
- package/src/input/agent-workspace-setup.ts +61 -1
- package/src/input/agent-workspace-snapshot.ts +184 -149
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +23 -1
- package/src/input/command-registry.ts +9 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
- package/src/input/commands/channels-runtime.ts +102 -114
- package/src/input/commands/delegation-runtime.ts +87 -24
- package/src/input/commands/knowledge-browser-flags.ts +12 -0
- package/src/input/commands/knowledge.ts +28 -14
- package/src/input/commands/operator-actions-runtime.ts +6 -3
- package/src/input/commands/schedule-runtime.ts +71 -14
- package/src/input/commands/session-content.ts +2 -2
- package/src/input/commands/shell-core.ts +2 -0
- package/src/input/commands/vibe-runtime.ts +140 -0
- package/src/input/commands.ts +2 -0
- package/src/input/connected-host-routes.ts +123 -0
- package/src/input/settings-modal-types.ts +14 -0
- package/src/input/setup-wizard-live-receipts.ts +76 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +701 -0
- package/src/renderer/agent-workspace.ts +68 -421
- package/src/renderer/settings-modal-helpers.ts +12 -0
- package/src/renderer/settings-modal.ts +12 -0
- package/src/runtime/bootstrap-command-context.ts +11 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -3
- package/src/runtime/bootstrap-core.ts +39 -1
- package/src/runtime/bootstrap-shell.ts +6 -0
- package/src/runtime/bootstrap.ts +113 -30
- package/src/runtime/connected-host-auth.ts +3 -2
- package/src/runtime/execution-ledger.ts +231 -0
- package/src/runtime/services.ts +88 -10
- package/src/runtime/tool-permission-safety.ts +199 -5
- package/src/shell/session-continuity-hints.ts +39 -0
- package/src/tools/agent-artifacts-tool.ts +606 -0
- package/src/tools/agent-audit-tool.ts +155 -0
- package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
- package/src/tools/agent-autonomy-tool.ts +140 -0
- package/src/tools/agent-channel-send-tool.ts +24 -2
- package/src/tools/agent-channels-tool.ts +140 -0
- package/src/tools/agent-computer-tool.ts +168 -0
- package/src/tools/agent-context-policy.ts +137 -7
- package/src/tools/agent-context-tool.ts +143 -0
- package/src/tools/agent-delegation-tool.ts +128 -0
- package/src/tools/agent-device-tool.ts +240 -0
- package/src/tools/agent-documents-tool.ts +684 -0
- package/src/tools/agent-execution-tool.ts +230 -0
- package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
- package/src/tools/agent-harness-agent-orchestration.ts +791 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +571 -0
- package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +798 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
- package/src/tools/agent-harness-browser-control.ts +346 -0
- package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
- package/src/tools/agent-harness-channel-metadata.ts +168 -10
- package/src/tools/agent-harness-cli-command-policy.ts +110 -0
- package/src/tools/agent-harness-command-catalog.ts +4 -2
- package/src/tools/agent-harness-command-runner.ts +42 -0
- package/src/tools/agent-harness-connected-host-status.ts +7 -4
- package/src/tools/agent-harness-delegation-posture.ts +234 -19
- package/src/tools/agent-harness-device-live-read-models.ts +366 -0
- package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
- package/src/tools/agent-harness-document-ops-types.ts +72 -0
- package/src/tools/agent-harness-document-ops.ts +671 -0
- package/src/tools/agent-harness-execution-history.ts +489 -0
- package/src/tools/agent-harness-execution-posture.ts +385 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
- package/src/tools/agent-harness-learning-curator-common.ts +102 -0
- package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
- package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
- package/src/tools/agent-harness-learning-curator-types.ts +151 -0
- package/src/tools/agent-harness-learning-curator.ts +417 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +269 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
- package/src/tools/agent-harness-local-model-smoke.ts +277 -0
- package/src/tools/agent-harness-local-model-url.ts +78 -0
- package/src/tools/agent-harness-media-posture.ts +223 -2
- package/src/tools/agent-harness-memory-external-providers.ts +796 -0
- package/src/tools/agent-harness-memory-posture.ts +672 -0
- package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
- package/src/tools/agent-harness-memory-refinement.ts +340 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +86 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +283 -0
- package/src/tools/agent-harness-model-readiness.ts +432 -0
- package/src/tools/agent-harness-model-routing-types.ts +327 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +165 -193
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +452 -18
- package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
- package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
- package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +270 -0
- package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
- package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
- package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
- package/src/tools/agent-harness-personal-ops-records.ts +684 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +240 -0
- package/src/tools/agent-harness-personal-ops.ts +613 -0
- package/src/tools/agent-harness-project-context.ts +144 -0
- package/src/tools/agent-harness-prompt-context.ts +589 -0
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-release-evidence.ts +9 -7
- package/src/tools/agent-harness-release-readiness.ts +9 -7
- package/src/tools/agent-harness-remote-read-models.ts +541 -0
- package/src/tools/agent-harness-research-briefing.ts +446 -0
- package/src/tools/agent-harness-research-live-read-models.ts +500 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +307 -0
- package/src/tools/agent-harness-research-workflow.ts +395 -0
- package/src/tools/agent-harness-security-posture.ts +2 -2
- package/src/tools/agent-harness-service-posture.ts +9 -9
- package/src/tools/agent-harness-session-metadata.ts +3 -3
- package/src/tools/agent-harness-setup-connected-host.ts +501 -0
- package/src/tools/agent-harness-setup-describe.ts +557 -0
- package/src/tools/agent-harness-setup-handoffs.ts +502 -0
- package/src/tools/agent-harness-setup-model-helpers.ts +102 -0
- package/src/tools/agent-harness-setup-plan.ts +253 -0
- package/src/tools/agent-harness-setup-posture-types.ts +218 -0
- package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
- package/src/tools/agent-harness-setup-posture.ts +449 -221
- package/src/tools/agent-harness-setup-smoke.ts +533 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +142 -7
- package/src/tools/agent-harness-tool-types.ts +83 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +319 -370
- package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
- package/src/tools/agent-harness-ui-surface-types.ts +46 -0
- package/src/tools/agent-harness-vibe-health.ts +105 -0
- package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
- package/src/tools/agent-harness-workspace-actions.ts +114 -8
- package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
- package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
- package/src/tools/agent-host-tool.ts +159 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
- package/src/tools/agent-learning-consolidation-core.ts +327 -0
- package/src/tools/agent-learning-consolidation-tool.ts +758 -0
- package/src/tools/agent-memory-tool.ts +253 -0
- package/src/tools/agent-model-compare-export.ts +315 -0
- package/src/tools/agent-model-compare-handoff.ts +592 -0
- package/src/tools/agent-model-compare-judgment.ts +633 -0
- package/src/tools/agent-model-compare-run.ts +735 -0
- package/src/tools/agent-model-compare-tool.ts +698 -0
- package/src/tools/agent-model-compare-types.ts +191 -0
- package/src/tools/agent-model-compare-utils.ts +93 -0
- package/src/tools/agent-models-tool.ts +208 -0
- package/src/tools/agent-operator-action-tool.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +643 -0
- package/src/tools/agent-personal-ops-tool.ts +197 -0
- package/src/tools/agent-policy-explanation.ts +415 -0
- package/src/tools/agent-research-report-tool.ts +608 -0
- package/src/tools/agent-research-runner.ts +367 -0
- package/src/tools/agent-research-runs-tool.ts +434 -0
- package/src/tools/agent-research-sources-tool.ts +443 -0
- package/src/tools/agent-research-tool.ts +515 -0
- package/src/tools/agent-review-packet-presets-core.ts +757 -0
- package/src/tools/agent-review-packet-presets-tool.ts +466 -0
- package/src/tools/agent-review-packet-share-tool.ts +305 -0
- package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
- package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
- package/src/tools/agent-route-planner-candidates-work.ts +251 -0
- package/src/tools/agent-route-planner-helpers.ts +667 -0
- package/src/tools/agent-route-planner.ts +185 -0
- package/src/tools/agent-route-tool.ts +105 -0
- package/src/tools/agent-schedule-edit-tool.ts +210 -0
- package/src/tools/agent-schedule-tool.ts +282 -0
- package/src/tools/agent-security-tool.ts +145 -0
- package/src/tools/agent-sessions-tool.ts +122 -0
- package/src/tools/agent-settings-import-tool.ts +104 -0
- package/src/tools/agent-settings-tool.ts +176 -0
- package/src/tools/agent-setup-tool.ts +226 -0
- package/src/tools/agent-support-tool.ts +122 -0
- package/src/tools/agent-terminal-process-tools.ts +167 -0
- package/src/tools/agent-tool-policy-guard-types.ts +77 -0
- package/src/tools/agent-tool-policy-guard.ts +106 -85
- package/src/tools/agent-vibe-tool.ts +297 -0
- package/src/tools/agent-work-plan-tool.ts +265 -6
- package/src/tools/agent-workspace-tool.ts +305 -0
- package/src/tools/artifact-archive.ts +169 -0
- package/src/tools/tool-definition-compaction.ts +36 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { hasMethod } from './agent-harness-personal-ops-discovery.ts';
|
|
2
|
+
import type { PersonalOpsLiveRecord } from './agent-harness-personal-ops-types.ts';
|
|
3
|
+
|
|
4
|
+
export function taskOperationRecords(methodIds: readonly string[]): readonly PersonalOpsLiveRecord[] {
|
|
5
|
+
const records: PersonalOpsLiveRecord[] = [
|
|
6
|
+
{
|
|
7
|
+
id: 'workplan-list',
|
|
8
|
+
label: 'Review visible work plan',
|
|
9
|
+
status: 'ready',
|
|
10
|
+
summary: 'Read Agent-owned work-plan items before starting or switching multi-step work.',
|
|
11
|
+
userRoute: 'Agent Workspace -> Work -> Review work plan',
|
|
12
|
+
modelRoute: 'agent_work_plan action:"list"',
|
|
13
|
+
tags: ['work-plan', 'task-read'],
|
|
14
|
+
effect: 'read-only',
|
|
15
|
+
capability: 'task-read',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'workplan-add',
|
|
19
|
+
label: 'Add visible work item',
|
|
20
|
+
status: 'ready',
|
|
21
|
+
summary: 'Create one local Agent work-plan item instead of hiding task state in chat.',
|
|
22
|
+
userRoute: 'Agent Workspace -> Personal Ops -> Add work item',
|
|
23
|
+
modelRoute: 'agent_work_plan action:"create" title:"..."',
|
|
24
|
+
tags: ['work-plan', 'task-write'],
|
|
25
|
+
effect: 'confirmed-effect',
|
|
26
|
+
capability: 'task-write',
|
|
27
|
+
requiredFields: ['title'],
|
|
28
|
+
optionalFields: ['detail', 'priority', 'status'],
|
|
29
|
+
confirmationRequired: false,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'workplan-status',
|
|
33
|
+
label: 'Update work item status',
|
|
34
|
+
status: 'ready',
|
|
35
|
+
summary: 'Move one visible work item through pending, active, blocked, done, failed, or cancelled state.',
|
|
36
|
+
userRoute: 'Agent Workspace -> Work -> Update work item status',
|
|
37
|
+
modelRoute: 'agent_work_plan action:"set_status" id:"..." status:"..."',
|
|
38
|
+
tags: ['work-plan', 'task-write'],
|
|
39
|
+
effect: 'confirmed-effect',
|
|
40
|
+
capability: 'task-write',
|
|
41
|
+
requiredFields: ['id', 'status'],
|
|
42
|
+
confirmationRequired: false,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
if (hasMethod(methodIds, 'tasks.list')) {
|
|
46
|
+
records.push({
|
|
47
|
+
id: 'host-tasks-list',
|
|
48
|
+
label: 'List connected-host tasks',
|
|
49
|
+
status: 'ready',
|
|
50
|
+
summary: 'Inspect connected-host task state without creating, retrying, or mutating host tasks.',
|
|
51
|
+
userRoute: 'Agent Workspace -> Work -> Host tasks',
|
|
52
|
+
modelRoute: 'workspace action:"action" actionId:"tasks-list"',
|
|
53
|
+
tags: ['host-task', 'task-read'],
|
|
54
|
+
effect: 'read-only',
|
|
55
|
+
capability: 'host-task-read',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (hasMethod(methodIds, 'tasks.get') || hasMethod(methodIds, 'tasks.status')) {
|
|
59
|
+
records.push({
|
|
60
|
+
id: 'host-task-inspect',
|
|
61
|
+
label: 'Inspect connected-host task',
|
|
62
|
+
status: 'ready',
|
|
63
|
+
summary: 'Inspect one exact connected-host task id and output before considering controls.',
|
|
64
|
+
userRoute: 'Agent Workspace -> Work -> Inspect host task',
|
|
65
|
+
modelRoute: 'workspace action:"action" actionId:"task-show"',
|
|
66
|
+
tags: ['host-task', 'task-read'],
|
|
67
|
+
effect: 'read-only',
|
|
68
|
+
capability: 'host-task-read',
|
|
69
|
+
requiredFields: ['taskId'],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (hasMethod(methodIds, 'tasks.cancel')) {
|
|
73
|
+
records.push({
|
|
74
|
+
id: 'host-task-cancel',
|
|
75
|
+
label: 'Cancel connected-host task',
|
|
76
|
+
status: 'ready',
|
|
77
|
+
summary: 'Cancel one exact connected-host task id only when the user authorizes it.',
|
|
78
|
+
userRoute: 'Agent Workspace -> Work -> Host task controls',
|
|
79
|
+
modelRoute: 'agent_operator_method methodId:"tasks.cancel" input:{"taskId":"..."} confirm:true explicitUserRequest:"..."',
|
|
80
|
+
tags: ['host-task', 'task-write'],
|
|
81
|
+
effect: 'confirmed-effect',
|
|
82
|
+
capability: 'host-task-control',
|
|
83
|
+
requiredFields: ['taskId'],
|
|
84
|
+
confirmationRequired: true,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (hasMethod(methodIds, 'tasks.retry')) {
|
|
88
|
+
records.push({
|
|
89
|
+
id: 'host-task-retry',
|
|
90
|
+
label: 'Retry connected-host task',
|
|
91
|
+
status: 'ready',
|
|
92
|
+
summary: 'Retry one failed or cancelled connected-host task id only after inspection.',
|
|
93
|
+
userRoute: 'Agent Workspace -> Work -> Host task controls',
|
|
94
|
+
modelRoute: 'agent_operator_method methodId:"tasks.retry" input:{"taskId":"..."} confirm:true explicitUserRequest:"..."',
|
|
95
|
+
tags: ['host-task', 'task-write'],
|
|
96
|
+
effect: 'confirmed-effect',
|
|
97
|
+
capability: 'host-task-control',
|
|
98
|
+
requiredFields: ['taskId'],
|
|
99
|
+
confirmationRequired: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return records;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function reminderOperationRecords(methodIds: readonly string[], deliveryConfigured: boolean): readonly PersonalOpsLiveRecord[] {
|
|
106
|
+
const records: PersonalOpsLiveRecord[] = [
|
|
107
|
+
{
|
|
108
|
+
id: 'reminder-create',
|
|
109
|
+
label: 'Create confirmed reminder',
|
|
110
|
+
status: hasMethod(methodIds, 'schedules.create') ? deliveryConfigured ? 'ready' : 'attention' : 'needs-setup',
|
|
111
|
+
summary: deliveryConfigured
|
|
112
|
+
? 'Create one connected reminder schedule with real timing and a visible delivery path.'
|
|
113
|
+
: 'Create one reminder only after confirming timing and delivery scope; no configured delivery target was detected.',
|
|
114
|
+
userRoute: 'Agent Workspace -> Personal Ops -> Create reminder',
|
|
115
|
+
modelRoute: 'schedule action:"remind" message:"..." scheduleKind:"..." scheduleValue:"..." confirm:true explicitUserRequest:"..."',
|
|
116
|
+
tags: ['reminder', 'schedule-write'],
|
|
117
|
+
effect: 'confirmed-effect',
|
|
118
|
+
capability: 'reminder-create',
|
|
119
|
+
requiredFields: ['title', 'scheduleKind', 'scheduleValue'],
|
|
120
|
+
optionalFields: ['deliveryTargetId', 'timezone', 'message'],
|
|
121
|
+
confirmationRequired: true,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'autonomous-schedule-create',
|
|
125
|
+
label: 'Create autonomous schedule',
|
|
126
|
+
status: hasMethod(methodIds, 'schedules.create') ? 'ready' : 'needs-setup',
|
|
127
|
+
summary: 'Create one visible autonomous schedule only when task, cadence, success criteria, and user request provenance are explicit.',
|
|
128
|
+
userRoute: 'Agent Workspace -> Automation -> Create schedule',
|
|
129
|
+
modelRoute: 'schedule action:"create" task:"..." successCriteria:"..." scheduleKind:"..." scheduleValue:"..." confirm:true explicitUserRequest:"..."',
|
|
130
|
+
tags: ['autonomy', 'schedule-write'],
|
|
131
|
+
effect: 'confirmed-effect',
|
|
132
|
+
capability: 'schedule-create',
|
|
133
|
+
requiredFields: ['task', 'successCriteria', 'scheduleKind', 'scheduleValue'],
|
|
134
|
+
confirmationRequired: true,
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
if (hasMethod(methodIds, 'schedules.list')) {
|
|
138
|
+
records.push({
|
|
139
|
+
id: 'schedule-list',
|
|
140
|
+
label: 'List connected schedules',
|
|
141
|
+
status: 'ready',
|
|
142
|
+
summary: 'Inspect configured schedules and history before running or mutating one.',
|
|
143
|
+
userRoute: 'Agent Workspace -> Automation -> Schedules',
|
|
144
|
+
modelRoute: 'workspace action:"action" actionId:"schedule-list"',
|
|
145
|
+
tags: ['schedule', 'schedule-read'],
|
|
146
|
+
effect: 'read-only',
|
|
147
|
+
capability: 'schedule-read',
|
|
148
|
+
});
|
|
149
|
+
records.push({
|
|
150
|
+
id: 'schedule-edit',
|
|
151
|
+
label: 'Edit connected schedule',
|
|
152
|
+
status: 'ready',
|
|
153
|
+
summary: 'Preview and edit one exact connected schedule id with before/after diff context.',
|
|
154
|
+
userRoute: 'Agent Workspace -> Automation -> Edit schedule',
|
|
155
|
+
modelRoute: 'schedule action:"edit" scheduleId:"..." confirm:true explicitUserRequest:"..."',
|
|
156
|
+
tags: ['schedule', 'schedule-write'],
|
|
157
|
+
effect: 'confirmed-effect',
|
|
158
|
+
capability: 'schedule-control',
|
|
159
|
+
requiredFields: ['scheduleId'],
|
|
160
|
+
optionalFields: ['name', 'scheduleKind', 'scheduleValue', 'prompt'],
|
|
161
|
+
confirmationRequired: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (hasMethod(methodIds, 'schedules.run')) {
|
|
165
|
+
records.push({
|
|
166
|
+
id: 'schedule-run-now',
|
|
167
|
+
label: 'Run schedule now',
|
|
168
|
+
status: 'ready',
|
|
169
|
+
summary: 'Run one exact connected schedule id now after the user confirms.',
|
|
170
|
+
userRoute: 'Agent Workspace -> Automation -> Run job now',
|
|
171
|
+
modelRoute: 'schedule action:"run" scheduleId:"..." confirm:true explicitUserRequest:"..."',
|
|
172
|
+
tags: ['schedule', 'schedule-write'],
|
|
173
|
+
effect: 'confirmed-effect',
|
|
174
|
+
capability: 'schedule-control',
|
|
175
|
+
requiredFields: ['scheduleId'],
|
|
176
|
+
confirmationRequired: true,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (hasMethod(methodIds, 'schedules.disable')) {
|
|
180
|
+
records.push({
|
|
181
|
+
id: 'schedule-pause',
|
|
182
|
+
label: 'Pause connected schedule',
|
|
183
|
+
status: 'ready',
|
|
184
|
+
summary: 'Disable one exact connected schedule id after reviewing current state.',
|
|
185
|
+
userRoute: 'Agent Workspace -> Automation -> Schedule controls',
|
|
186
|
+
modelRoute: 'schedule action:"pause" scheduleId:"..." confirm:true explicitUserRequest:"..."',
|
|
187
|
+
tags: ['schedule', 'schedule-write'],
|
|
188
|
+
effect: 'confirmed-effect',
|
|
189
|
+
capability: 'schedule-control',
|
|
190
|
+
requiredFields: ['scheduleId'],
|
|
191
|
+
confirmationRequired: true,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (hasMethod(methodIds, 'schedules.enable')) {
|
|
195
|
+
records.push({
|
|
196
|
+
id: 'schedule-resume',
|
|
197
|
+
label: 'Resume connected schedule',
|
|
198
|
+
status: 'ready',
|
|
199
|
+
summary: 'Enable one exact connected schedule id after reviewing current state.',
|
|
200
|
+
userRoute: 'Agent Workspace -> Automation -> Schedule controls',
|
|
201
|
+
modelRoute: 'schedule action:"resume" scheduleId:"..." confirm:true explicitUserRequest:"..."',
|
|
202
|
+
tags: ['schedule', 'schedule-write'],
|
|
203
|
+
effect: 'confirmed-effect',
|
|
204
|
+
capability: 'schedule-control',
|
|
205
|
+
requiredFields: ['scheduleId'],
|
|
206
|
+
confirmationRequired: true,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (hasMethod(methodIds, 'schedules.delete')) {
|
|
210
|
+
records.push({
|
|
211
|
+
id: 'schedule-delete',
|
|
212
|
+
label: 'Delete connected schedule',
|
|
213
|
+
status: 'ready',
|
|
214
|
+
summary: 'Delete one exact connected schedule id only after explicit user confirmation.',
|
|
215
|
+
userRoute: 'Agent Workspace -> Automation -> Schedule controls',
|
|
216
|
+
modelRoute: 'schedule action:"delete" scheduleId:"..." confirm:true explicitUserRequest:"..."',
|
|
217
|
+
tags: ['schedule', 'schedule-write'],
|
|
218
|
+
effect: 'confirmed-effect',
|
|
219
|
+
capability: 'schedule-control',
|
|
220
|
+
requiredFields: ['scheduleId'],
|
|
221
|
+
confirmationRequired: true,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return records;
|
|
225
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
3
|
+
import { readRecord, readString } from './agent-harness-model-routing-utils.ts';
|
|
4
|
+
import { redactedPersonalOpsText } from './agent-harness-personal-ops-runner.ts';
|
|
5
|
+
import { personalOpsRecordCertification } from './agent-harness-personal-ops-certification.ts';
|
|
6
|
+
import { safeRecordIdPart } from './agent-harness-personal-ops-records.ts';
|
|
7
|
+
import type { PersonalOpsFollowUpRoute, PersonalOpsLaneId, PersonalOpsLiveRecord, PersonalOpsRecordFreshness } from './agent-harness-personal-ops-types.ts';
|
|
8
|
+
|
|
9
|
+
type ProviderBackedLaneId = Extract<PersonalOpsLaneId, 'inbox' | 'calendar'>;
|
|
10
|
+
type ProviderReadModelSourceKind = Extract<PersonalOpsRecordFreshness['source'], 'daemon-read-model' | 'sdk-read-model'>;
|
|
11
|
+
|
|
12
|
+
interface ProviderReadModelSource {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly source: unknown;
|
|
15
|
+
readonly kind: ProviderReadModelSourceKind;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const INBOX_ROUTE_KEYS = {
|
|
19
|
+
read: ['readRoute', 'refreshRoute', 'inspectRoute', 'threadRoute', 'messageRoute', 'getThreadRoute'],
|
|
20
|
+
reply: ['replyRoute', 'sendReplyRoute', 'draftReplyRoute'],
|
|
21
|
+
send: ['sendRoute', 'sendMessageRoute'],
|
|
22
|
+
archive: ['archiveRoute'],
|
|
23
|
+
label: ['labelRoute', 'applyLabelRoute'],
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
const CALENDAR_ROUTE_KEYS = {
|
|
27
|
+
read: ['readRoute', 'refreshRoute', 'inspectRoute', 'eventRoute', 'getEventRoute'],
|
|
28
|
+
edit: ['editRoute', 'updateRoute', 'rescheduleRoute'],
|
|
29
|
+
rsvp: ['rsvpRoute', 'respondRoute'],
|
|
30
|
+
delete: ['deleteRoute', 'cancelRoute'],
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
function firstString(record: Record<string, unknown>, keys: readonly string[]): string {
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
const direct = readString(record[key]);
|
|
36
|
+
if (direct) return direct;
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readBoolean(value: unknown): boolean | null {
|
|
42
|
+
if (typeof value === 'boolean') return value;
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
const normalized = value.trim().toLowerCase();
|
|
45
|
+
if (['true', 'yes', '1', 'unread', 'conflict', 'conflicted'].includes(normalized)) return true;
|
|
46
|
+
if (['false', 'no', '0', 'read', 'clear'].includes(normalized)) return false;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readStringArray(value: unknown): readonly string[] {
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return [...new Set(value.map((entry) => readString(entry)).filter(Boolean))].slice(0, 12);
|
|
54
|
+
}
|
|
55
|
+
const text = readString(value);
|
|
56
|
+
return text ? text.split(',').map((entry) => entry.trim()).filter(Boolean).slice(0, 12) : [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readIso(value: unknown): string {
|
|
60
|
+
if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString();
|
|
61
|
+
const text = readString(value);
|
|
62
|
+
if (!text) return '';
|
|
63
|
+
const timestamp = Date.parse(text);
|
|
64
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : previewHarnessText(redactedPersonalOpsText(text), 80);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readRoute(value: unknown): string {
|
|
68
|
+
const direct = readString(value);
|
|
69
|
+
if (direct) return direct;
|
|
70
|
+
const record = readRecord(value);
|
|
71
|
+
return firstString(record, ['modelRoute', 'route', 'command', 'action', 'href']);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function routeFromKeys(record: Record<string, unknown>, keys: readonly string[]): string {
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
const route = readRoute(record[key]);
|
|
77
|
+
if (route) return route;
|
|
78
|
+
}
|
|
79
|
+
const routes = readRecord(record.routes);
|
|
80
|
+
for (const key of keys) {
|
|
81
|
+
const route = readRoute(routes[key]);
|
|
82
|
+
if (route) return route;
|
|
83
|
+
}
|
|
84
|
+
const effectRoutes = readRecord(record.effectRoutes);
|
|
85
|
+
for (const key of keys) {
|
|
86
|
+
const route = readRoute(effectRoutes[key]);
|
|
87
|
+
if (route) return route;
|
|
88
|
+
}
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function providerReadModelSources(context: CommandContext, laneId: ProviderBackedLaneId): readonly ProviderReadModelSource[] {
|
|
93
|
+
const platform = context.platform as unknown as Record<string, unknown>;
|
|
94
|
+
const clients = readRecord(context.clients);
|
|
95
|
+
const readModels = readRecord(platform.readModels);
|
|
96
|
+
const personalOps = readRecord(readModels.personalOps);
|
|
97
|
+
const email = readRecord(readModels.email);
|
|
98
|
+
const mail = readRecord(readModels.mail);
|
|
99
|
+
const calendar = readRecord(readModels.calendar);
|
|
100
|
+
const platformPersonalOps = readRecord(platform.personalOps);
|
|
101
|
+
const operator = readRecord(clients.operator);
|
|
102
|
+
const operatorPersonalOps = readRecord(operator.personalOps);
|
|
103
|
+
if (laneId === 'inbox') {
|
|
104
|
+
return [
|
|
105
|
+
{ path: 'context.platform.readModels.personalOps.inboxThreads', source: personalOps.inboxThreads, kind: 'daemon-read-model' },
|
|
106
|
+
{ path: 'context.platform.readModels.personalOps.emailThreads', source: personalOps.emailThreads, kind: 'daemon-read-model' },
|
|
107
|
+
{ path: 'context.platform.readModels.inboxThreads', source: readModels.inboxThreads, kind: 'daemon-read-model' },
|
|
108
|
+
{ path: 'context.platform.readModels.emailThreads', source: readModels.emailThreads, kind: 'daemon-read-model' },
|
|
109
|
+
{ path: 'context.platform.readModels.email.threads', source: email.threads, kind: 'daemon-read-model' },
|
|
110
|
+
{ path: 'context.platform.readModels.email.messages', source: email.messages, kind: 'daemon-read-model' },
|
|
111
|
+
{ path: 'context.platform.readModels.mail.threads', source: mail.threads, kind: 'daemon-read-model' },
|
|
112
|
+
{ path: 'context.platform.personalOps.inboxThreads', source: platformPersonalOps.inboxThreads, kind: 'daemon-read-model' },
|
|
113
|
+
{ path: 'context.clients.operator.personalOps.inboxThreads', source: operatorPersonalOps.inboxThreads, kind: 'sdk-read-model' },
|
|
114
|
+
{ path: 'context.clients.operator.personalOps.emailThreads', source: operatorPersonalOps.emailThreads, kind: 'sdk-read-model' },
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
return [
|
|
118
|
+
{ path: 'context.platform.readModels.personalOps.calendarEvents', source: personalOps.calendarEvents, kind: 'daemon-read-model' },
|
|
119
|
+
{ path: 'context.platform.readModels.calendarEvents', source: readModels.calendarEvents, kind: 'daemon-read-model' },
|
|
120
|
+
{ path: 'context.platform.readModels.calendar.events', source: calendar.events, kind: 'daemon-read-model' },
|
|
121
|
+
{ path: 'context.platform.readModels.calendar.agenda', source: calendar.agenda, kind: 'daemon-read-model' },
|
|
122
|
+
{ path: 'context.platform.personalOps.calendarEvents', source: platformPersonalOps.calendarEvents, kind: 'daemon-read-model' },
|
|
123
|
+
{ path: 'context.clients.operator.personalOps.calendarEvents', source: operatorPersonalOps.calendarEvents, kind: 'sdk-read-model' },
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function readProviderSnapshot(source: unknown, laneId: ProviderBackedLaneId): unknown {
|
|
128
|
+
if (typeof source === 'function') {
|
|
129
|
+
try {
|
|
130
|
+
const result = (source as () => unknown)();
|
|
131
|
+
return result instanceof Promise ? undefined : result;
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const record = readRecord(source);
|
|
137
|
+
const methodNames = laneId === 'calendar'
|
|
138
|
+
? ['getSnapshot', 'snapshot', 'listEvents', 'getEvents', 'list']
|
|
139
|
+
: ['getSnapshot', 'snapshot', 'listThreads', 'getThreads', 'listMessages', 'getMessages', 'list'];
|
|
140
|
+
for (const methodName of methodNames) {
|
|
141
|
+
const method = record[methodName];
|
|
142
|
+
if (typeof method !== 'function') continue;
|
|
143
|
+
try {
|
|
144
|
+
const result = (method as () => unknown).call(source);
|
|
145
|
+
return result instanceof Promise ? undefined : result;
|
|
146
|
+
} catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return source;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function recordsFromSnapshot(snapshot: unknown, laneId: ProviderBackedLaneId, depth = 0): readonly unknown[] {
|
|
154
|
+
if (Array.isArray(snapshot)) return snapshot;
|
|
155
|
+
const record = readRecord(snapshot);
|
|
156
|
+
const keys = laneId === 'calendar'
|
|
157
|
+
? ['calendarEvents', 'events', 'agenda', 'items', 'records', 'entries', 'queue']
|
|
158
|
+
: ['inboxThreads', 'emailThreads', 'threads', 'messages', 'emails', 'items', 'records', 'entries', 'queue'];
|
|
159
|
+
for (const key of keys) {
|
|
160
|
+
const value = record[key];
|
|
161
|
+
if (Array.isArray(value)) return value;
|
|
162
|
+
const valueRecord = readRecord(value);
|
|
163
|
+
if (Object.keys(valueRecord).length > 0) {
|
|
164
|
+
return Object.entries(valueRecord).map(([providerRecordId, entry]) => {
|
|
165
|
+
const entryRecord = readRecord(entry);
|
|
166
|
+
return Object.keys(entryRecord).length > 0 && !firstString(entryRecord, ['id', 'threadId', 'messageId', 'eventId', 'uid'])
|
|
167
|
+
? { ...entryRecord, providerRecordId }
|
|
168
|
+
: entry;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (depth < 2) {
|
|
173
|
+
for (const key of ['snapshot', 'data', 'current', 'payload', 'result', 'state']) {
|
|
174
|
+
const nested = recordsFromSnapshot(record[key], laneId, depth + 1);
|
|
175
|
+
if (nested.length > 0) return nested;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return firstString(record, laneId === 'calendar'
|
|
179
|
+
? ['id', 'eventId', 'uid', 'providerRecordId']
|
|
180
|
+
: ['id', 'threadId', 'messageId', 'providerRecordId'])
|
|
181
|
+
? [record]
|
|
182
|
+
: [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function providerFreshness(options: {
|
|
186
|
+
readonly source: ProviderReadModelSource;
|
|
187
|
+
readonly readRoute: string;
|
|
188
|
+
readonly requiredFields: readonly string[];
|
|
189
|
+
readonly sampleInput: Readonly<Record<string, unknown>>;
|
|
190
|
+
readonly currentAt: string;
|
|
191
|
+
}): PersonalOpsRecordFreshness {
|
|
192
|
+
return {
|
|
193
|
+
status: options.readRoute ? 'fresh-provider-route-ready' : 'fresh-provider-record-current',
|
|
194
|
+
source: options.source.kind,
|
|
195
|
+
sourceTool: options.source.path,
|
|
196
|
+
...(options.currentAt ? { lastReviewedAt: options.currentAt } : {}),
|
|
197
|
+
...(options.readRoute ? { refreshRoute: options.readRoute } : {}),
|
|
198
|
+
...(options.requiredFields.length > 0 ? { requiredFields: options.requiredFields } : {}),
|
|
199
|
+
...(Object.keys(options.sampleInput).length > 0 ? { sampleInput: options.sampleInput } : {}),
|
|
200
|
+
policy: options.readRoute
|
|
201
|
+
? 'The provider published a current read-model record plus a bounded read or refresh route. Run the route only for a user-requested refresh; provider mutations remain separate confirmed follow-up routes.'
|
|
202
|
+
: 'The provider published current redacted queue state through the daemon or SDK read model. Inspect it as read-only; refreshes and provider mutations require explicit published routes.',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function followUp(
|
|
207
|
+
id: string,
|
|
208
|
+
label: string,
|
|
209
|
+
route: string,
|
|
210
|
+
effect: PersonalOpsFollowUpRoute['effect'],
|
|
211
|
+
policy: string,
|
|
212
|
+
): PersonalOpsFollowUpRoute | null {
|
|
213
|
+
if (!route) return null;
|
|
214
|
+
return {
|
|
215
|
+
id,
|
|
216
|
+
label,
|
|
217
|
+
effect,
|
|
218
|
+
modelRoute: route,
|
|
219
|
+
requiresConfirmation: effect === 'confirmed-effect',
|
|
220
|
+
policy,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function normalizeInboxRecord(source: ProviderReadModelSource, raw: unknown, index: number): PersonalOpsLiveRecord | null {
|
|
225
|
+
const record = readRecord(raw);
|
|
226
|
+
const providerId = firstString(record, ['providerId', 'provider', 'accountId', 'service', 'mailbox']) || 'provider';
|
|
227
|
+
const threadId = firstString(record, ['threadId', 'conversationId', 'messageThreadId', 'id', 'messageId', 'uid', 'providerRecordId']) || `${index + 1}`;
|
|
228
|
+
const subject = firstString(record, ['subject', 'title', 'summary', 'name']) || `Inbox thread ${index + 1}`;
|
|
229
|
+
const status = firstString(record, ['status', 'state', 'reviewState'])
|
|
230
|
+
|| (readBoolean(record.needsReply) === true ? 'needs-reply' : readBoolean(record.unread) === true ? 'unread' : 'current');
|
|
231
|
+
const sender = firstString(record, ['from', 'sender', 'author', 'participant']);
|
|
232
|
+
const receivedAt = readIso(record.receivedAt ?? record.updatedAt ?? record.timestamp ?? record.date);
|
|
233
|
+
const labels = readStringArray(record.labels ?? record.tags ?? record.mailboxLabels).slice(0, 6);
|
|
234
|
+
const snippet = firstString(record, ['snippet', 'preview', 'excerpt', 'abstract']);
|
|
235
|
+
const readRoute = routeFromKeys(record, INBOX_ROUTE_KEYS.read);
|
|
236
|
+
const requiredFields = readStringArray(record.requiredFields);
|
|
237
|
+
const sampleInput: Record<string, unknown> = threadId ? { threadId } : {};
|
|
238
|
+
const followUpRoutes = [
|
|
239
|
+
followUp('inspect-provider-thread', 'Inspect current thread record', readRoute || `personal_ops action:"lane" laneId:"inbox" query:"${safeRecordIdPart(threadId)}" includeParameters:true`, 'read-only', 'Inspecting the current provider-backed thread record is read-only and does not send, label, archive, move, or delete provider data.'),
|
|
240
|
+
followUp('reply-provider-thread', 'Reply to thread', routeFromKeys(record, INBOX_ROUTE_KEYS.reply), 'confirmed-effect', 'Replying sends or stages provider data only after the user reviews exact recipients and body and confirms the published route.'),
|
|
241
|
+
followUp('send-provider-message', 'Send provider message', routeFromKeys(record, INBOX_ROUTE_KEYS.send), 'confirmed-effect', 'Sending provider mail requires a separate published send route, exact body review, and explicit confirmation.'),
|
|
242
|
+
followUp('archive-provider-thread', 'Archive thread', routeFromKeys(record, INBOX_ROUTE_KEYS.archive), 'confirmed-effect', 'Archiving mutates provider state and requires an explicit user request plus confirmation.'),
|
|
243
|
+
followUp('label-provider-thread', 'Apply label', routeFromKeys(record, INBOX_ROUTE_KEYS.label), 'confirmed-effect', 'Labels mutate provider state and require an explicit user request plus confirmation.'),
|
|
244
|
+
].filter((route): route is PersonalOpsFollowUpRoute => route !== null);
|
|
245
|
+
const hasConfirmedEffectRoute = followUpRoutes.some((route) => route.effect === 'confirmed-effect');
|
|
246
|
+
const currentAt = readIso(record.currentAt ?? record.readAt ?? record.syncedAt ?? record.updatedAt ?? record.receivedAt);
|
|
247
|
+
return {
|
|
248
|
+
id: `provider-thread:${safeRecordIdPart(source.path)}:${safeRecordIdPart(threadId)}`,
|
|
249
|
+
label: `Fresh thread: ${previewHarnessText(redactedPersonalOpsText(subject), 96)}`,
|
|
250
|
+
status: previewHarnessText(redactedPersonalOpsText(status), 48),
|
|
251
|
+
summary: [
|
|
252
|
+
`Provider ${previewHarnessText(redactedPersonalOpsText(providerId), 64)} published current inbox thread ${previewHarnessText(redactedPersonalOpsText(threadId), 80)} from ${source.path}.`,
|
|
253
|
+
sender ? `From ${previewHarnessText(redactedPersonalOpsText(sender), 80)}.` : '',
|
|
254
|
+
labels.length > 0 ? `Labels ${labels.map((label) => previewHarnessText(redactedPersonalOpsText(label), 40)).join(', ')}.` : '',
|
|
255
|
+
receivedAt ? `Updated ${receivedAt}.` : '',
|
|
256
|
+
snippet ? `Preview ${previewHarnessText(redactedPersonalOpsText(snippet), 160)}.` : '',
|
|
257
|
+
followUpRoutes.some((route) => route.effect === 'confirmed-effect') ? 'Published provider effects are separated into confirmed follow-up routes.' : 'No provider mutation route is published for this thread.',
|
|
258
|
+
].filter(Boolean).join(' '),
|
|
259
|
+
userRoute: 'Agent Workspace -> Personal Ops -> Inbox provider queue',
|
|
260
|
+
modelRoute: readRoute || `personal_ops action:"lane" laneId:"inbox" query:"${safeRecordIdPart(threadId)}" includeParameters:true`,
|
|
261
|
+
tags: ['provider-backed', 'fresh-provider', 'inbox-thread', providerId, status, ...labels].filter(Boolean),
|
|
262
|
+
effect: 'read-only',
|
|
263
|
+
capability: 'inbox-provider-thread',
|
|
264
|
+
confirmationRequired: false,
|
|
265
|
+
sourceTool: source.path,
|
|
266
|
+
certification: personalOpsRecordCertification({
|
|
267
|
+
record,
|
|
268
|
+
sourcePath: source.path,
|
|
269
|
+
durableId: threadId,
|
|
270
|
+
recordKind: 'inbox thread',
|
|
271
|
+
hasConfirmedEffectRoute,
|
|
272
|
+
}),
|
|
273
|
+
freshness: providerFreshness({ source, readRoute, requiredFields, sampleInput, currentAt }),
|
|
274
|
+
followUpRoutes,
|
|
275
|
+
...(requiredFields.length > 0 ? { requiredFields } : {}),
|
|
276
|
+
...(Object.keys(sampleInput).length > 0 ? { sampleInput } : {}),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function normalizeCalendarRecord(source: ProviderReadModelSource, raw: unknown, index: number): PersonalOpsLiveRecord | null {
|
|
281
|
+
const record = readRecord(raw);
|
|
282
|
+
const providerId = firstString(record, ['providerId', 'provider', 'accountId', 'service', 'calendarProvider']) || 'provider';
|
|
283
|
+
const eventId = firstString(record, ['eventId', 'uid', 'id', 'providerRecordId']) || `${index + 1}`;
|
|
284
|
+
const calendarId = firstString(record, ['calendarId', 'calendar', 'calendarName']);
|
|
285
|
+
const title = firstString(record, ['title', 'summary', 'subject', 'name']) || `Calendar event ${index + 1}`;
|
|
286
|
+
const start = readIso(record.start ?? record.startAt ?? record.startTime ?? record.when);
|
|
287
|
+
const end = readIso(record.end ?? record.endAt ?? record.endTime);
|
|
288
|
+
const conflictCount = Array.isArray(record.conflicts) ? record.conflicts.length : readBoolean(record.conflict ?? record.hasConflict) === true ? 1 : 0;
|
|
289
|
+
const status = conflictCount > 0
|
|
290
|
+
? 'conflict'
|
|
291
|
+
: firstString(record, ['status', 'state', 'reviewState']) || 'current';
|
|
292
|
+
const location = firstString(record, ['location', 'room', 'venue']);
|
|
293
|
+
const attendeeCount = Array.isArray(record.attendees) ? record.attendees.length : null;
|
|
294
|
+
const snippet = firstString(record, ['snippet', 'preview', 'notes', 'descriptionSummary']);
|
|
295
|
+
const readRoute = routeFromKeys(record, CALENDAR_ROUTE_KEYS.read);
|
|
296
|
+
const requiredFields = readStringArray(record.requiredFields);
|
|
297
|
+
const sampleInput: Record<string, unknown> = { eventId };
|
|
298
|
+
if (calendarId) sampleInput.calendarId = calendarId;
|
|
299
|
+
const followUpRoutes = [
|
|
300
|
+
followUp('inspect-provider-event', 'Inspect current event record', readRoute || `personal_ops action:"lane" laneId:"calendar" query:"${safeRecordIdPart(eventId)}" includeParameters:true`, 'read-only', 'Inspecting the current provider-backed event record is read-only and does not edit, RSVP, delete, or reschedule provider data.'),
|
|
301
|
+
followUp('edit-provider-event', 'Edit or reschedule event', routeFromKeys(record, CALENDAR_ROUTE_KEYS.edit), 'confirmed-effect', 'Editing or rescheduling a provider event requires exact field review and explicit confirmation.'),
|
|
302
|
+
followUp('rsvp-provider-event', 'RSVP to event', routeFromKeys(record, CALENDAR_ROUTE_KEYS.rsvp), 'confirmed-effect', 'RSVP mutates provider state and requires an explicit user request plus confirmation.'),
|
|
303
|
+
followUp('delete-provider-event', 'Delete or cancel event', routeFromKeys(record, CALENDAR_ROUTE_KEYS.delete), 'confirmed-effect', 'Deleting or cancelling calendar data requires an explicit user request plus confirmation.'),
|
|
304
|
+
].filter((route): route is PersonalOpsFollowUpRoute => route !== null);
|
|
305
|
+
const hasConfirmedEffectRoute = followUpRoutes.some((route) => route.effect === 'confirmed-effect');
|
|
306
|
+
const currentAt = readIso(record.currentAt ?? record.readAt ?? record.syncedAt ?? record.updatedAt ?? record.start);
|
|
307
|
+
return {
|
|
308
|
+
id: `provider-event:${safeRecordIdPart(source.path)}:${safeRecordIdPart(eventId)}`,
|
|
309
|
+
label: `Fresh event: ${previewHarnessText(redactedPersonalOpsText(title), 96)}`,
|
|
310
|
+
status: previewHarnessText(redactedPersonalOpsText(status), 48),
|
|
311
|
+
summary: [
|
|
312
|
+
`Provider ${previewHarnessText(redactedPersonalOpsText(providerId), 64)} published current calendar event ${previewHarnessText(redactedPersonalOpsText(eventId), 80)} from ${source.path}.`,
|
|
313
|
+
calendarId ? `Calendar ${previewHarnessText(redactedPersonalOpsText(calendarId), 64)}.` : '',
|
|
314
|
+
start || end ? `Window ${[start, end].filter(Boolean).join(' -> ')}.` : '',
|
|
315
|
+
location ? `Location ${previewHarnessText(redactedPersonalOpsText(location), 80)}.` : '',
|
|
316
|
+
attendeeCount !== null ? `${attendeeCount} attendee(s).` : '',
|
|
317
|
+
conflictCount > 0 ? `${conflictCount} conflict signal(s).` : '',
|
|
318
|
+
snippet ? `Preview ${previewHarnessText(redactedPersonalOpsText(snippet), 160)}.` : '',
|
|
319
|
+
followUpRoutes.some((route) => route.effect === 'confirmed-effect') ? 'Published provider effects are separated into confirmed follow-up routes.' : 'No provider mutation route is published for this event.',
|
|
320
|
+
].filter(Boolean).join(' '),
|
|
321
|
+
userRoute: 'Agent Workspace -> Personal Ops -> Calendar provider queue',
|
|
322
|
+
modelRoute: readRoute || `personal_ops action:"lane" laneId:"calendar" query:"${safeRecordIdPart(eventId)}" includeParameters:true`,
|
|
323
|
+
tags: ['provider-backed', 'fresh-provider', 'calendar-event', providerId, status, conflictCount > 0 ? 'conflict' : '', calendarId].filter(Boolean),
|
|
324
|
+
effect: 'read-only',
|
|
325
|
+
capability: 'calendar-provider-event',
|
|
326
|
+
confirmationRequired: false,
|
|
327
|
+
sourceTool: source.path,
|
|
328
|
+
certification: personalOpsRecordCertification({
|
|
329
|
+
record,
|
|
330
|
+
sourcePath: source.path,
|
|
331
|
+
durableId: eventId,
|
|
332
|
+
recordKind: 'calendar event',
|
|
333
|
+
hasConfirmedEffectRoute,
|
|
334
|
+
}),
|
|
335
|
+
freshness: providerFreshness({ source, readRoute, requiredFields, sampleInput, currentAt }),
|
|
336
|
+
followUpRoutes,
|
|
337
|
+
...(requiredFields.length > 0 ? { requiredFields } : {}),
|
|
338
|
+
...(Object.keys(sampleInput).length > 0 ? { sampleInput } : {}),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function providerBackedQueueRecords(context: CommandContext, laneId: ProviderBackedLaneId): readonly PersonalOpsLiveRecord[] {
|
|
343
|
+
const records: PersonalOpsLiveRecord[] = [];
|
|
344
|
+
const seen = new Set<string>();
|
|
345
|
+
for (const source of providerReadModelSources(context, laneId)) {
|
|
346
|
+
const snapshot = readProviderSnapshot(source.source, laneId);
|
|
347
|
+
for (const [index, raw] of recordsFromSnapshot(snapshot, laneId).entries()) {
|
|
348
|
+
const record = laneId === 'calendar'
|
|
349
|
+
? normalizeCalendarRecord(source, raw, index)
|
|
350
|
+
: normalizeInboxRecord(source, raw, index);
|
|
351
|
+
if (!record || seen.has(record.id)) continue;
|
|
352
|
+
seen.add(record.id);
|
|
353
|
+
records.push(record);
|
|
354
|
+
if (records.length >= 10) return records;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return records;
|
|
358
|
+
}
|