@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,637 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
3
|
+
import { PERSONAL_OPS_READ_CONTROL_FIELDS, type PersonalOpsConnectorSignal, type PersonalOpsConnectorTool, type PersonalOpsExecutionStep, type PersonalOpsIntakeCandidate, type PersonalOpsLane, type PersonalOpsLaneId, type PersonalOpsLiveRecord, type PersonalOpsReadRunResult, type PersonalOpsRoutePacket, type PersonalOpsWorkflow, type PersonalOpsWorkflowStatus } from './agent-harness-personal-ops-types.ts';
|
|
4
|
+
|
|
5
|
+
export function laneById(lanes: readonly PersonalOpsLane[], laneId: PersonalOpsLaneId): PersonalOpsLane {
|
|
6
|
+
return lanes.find((lane) => lane.id === laneId)!;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function workflowById(lane: PersonalOpsLane, workflowId: string): PersonalOpsWorkflow | null {
|
|
10
|
+
return lane.workflows?.find((workflow) => workflow.id === workflowId) ?? null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function liveRecordById(lane: PersonalOpsLane, recordId: string): PersonalOpsLiveRecord | null {
|
|
14
|
+
return lane.liveRecords?.find((record) => record.id === recordId) ?? null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function liveRecordSearchText(record: PersonalOpsLiveRecord): string {
|
|
18
|
+
return [
|
|
19
|
+
record.id,
|
|
20
|
+
record.label,
|
|
21
|
+
record.status,
|
|
22
|
+
record.summary,
|
|
23
|
+
record.modelRoute,
|
|
24
|
+
record.qualifiedName ?? '',
|
|
25
|
+
record.capability ?? '',
|
|
26
|
+
record.artifactId ?? '',
|
|
27
|
+
record.reviewLabels?.join('\n') ?? '',
|
|
28
|
+
record.sourceTool ?? '',
|
|
29
|
+
record.followUpRoutes?.map((route) => `${route.id} ${route.label} ${route.effect} ${route.modelRoute} ${route.policy}`).join('\n') ?? '',
|
|
30
|
+
record.tags?.join('\n') ?? '',
|
|
31
|
+
].join('\n').toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function workflowMissingFields(lane: PersonalOpsLane, workflow: PersonalOpsWorkflow, operation?: PersonalOpsConnectorTool): readonly string[] | undefined {
|
|
35
|
+
if (workflow.status === 'needs-setup') return [`configured ${lane.id === 'inbox' ? 'email' : lane.id} connector or daemon method`];
|
|
36
|
+
if (workflow.status === 'attention') return ['connector trust/schema freshness'];
|
|
37
|
+
if (operation?.requiredFields && operation.requiredFields.length > 0) return operation.requiredFields;
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function operationSummary(
|
|
42
|
+
tool: PersonalOpsConnectorTool | undefined,
|
|
43
|
+
signal: PersonalOpsConnectorSignal | undefined,
|
|
44
|
+
includeParameters: boolean,
|
|
45
|
+
): Record<string, unknown> | undefined {
|
|
46
|
+
if (!tool) return undefined;
|
|
47
|
+
return {
|
|
48
|
+
name: tool.name,
|
|
49
|
+
effect: tool.effect,
|
|
50
|
+
capability: tool.capability,
|
|
51
|
+
...(tool.qualifiedName ? { qualifiedName: tool.qualifiedName } : {}),
|
|
52
|
+
...(signal ? { connectorId: signal.id, connectorStatus: signal.status } : {}),
|
|
53
|
+
...(tool.schemaRoute ? { schemaRoute: tool.schemaRoute } : {}),
|
|
54
|
+
...(includeParameters && tool.requiredFields ? { requiredFields: tool.requiredFields } : {}),
|
|
55
|
+
...(includeParameters && tool.optionalFields ? { optionalFields: tool.optionalFields.slice(0, 12) } : {}),
|
|
56
|
+
...(includeParameters && tool.sampleInput ? { sampleInput: tool.sampleInput } : {}),
|
|
57
|
+
...(tool.effect === 'confirmed-effect' ? { confirmationRequired: true } : { confirmationRequired: false }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function toolModelRoute(tool: PersonalOpsConnectorTool | undefined): string {
|
|
62
|
+
if (!tool) return 'personal_ops action:"status"';
|
|
63
|
+
return tool.schemaRoute ?? `agent_harness mode:"mcp_servers" query:"${tool.qualifiedName ?? tool.name}"`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function connectorStep(options: {
|
|
67
|
+
readonly id: string;
|
|
68
|
+
readonly label: string;
|
|
69
|
+
readonly routeKind: PersonalOpsExecutionStep['routeKind'];
|
|
70
|
+
readonly status: PersonalOpsWorkflowStatus;
|
|
71
|
+
readonly tool: PersonalOpsConnectorTool;
|
|
72
|
+
readonly signal?: PersonalOpsConnectorSignal;
|
|
73
|
+
readonly includeParameters: boolean;
|
|
74
|
+
readonly policy: string;
|
|
75
|
+
}): PersonalOpsExecutionStep {
|
|
76
|
+
return {
|
|
77
|
+
id: options.id,
|
|
78
|
+
label: options.label,
|
|
79
|
+
routeKind: options.routeKind,
|
|
80
|
+
effect: options.tool.effect,
|
|
81
|
+
requiresConfirmation: options.tool.effect === 'confirmed-effect',
|
|
82
|
+
modelRoute: toolModelRoute(options.tool),
|
|
83
|
+
status: options.status,
|
|
84
|
+
policy: options.policy,
|
|
85
|
+
...(options.signal ? { connectorId: options.signal.id, connectorStatus: options.signal.status } : {}),
|
|
86
|
+
...(options.tool.qualifiedName ? { qualifiedName: options.tool.qualifiedName } : {}),
|
|
87
|
+
...(options.tool.schemaRoute ? { schemaRoute: options.tool.schemaRoute } : {}),
|
|
88
|
+
...(options.includeParameters && options.tool.requiredFields ? { requiredFields: options.tool.requiredFields } : {}),
|
|
89
|
+
...(options.includeParameters && options.tool.sampleInput ? { sampleInput: options.tool.sampleInput } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function workflowExecutionPlan(options: {
|
|
94
|
+
readonly lane: PersonalOpsLane;
|
|
95
|
+
readonly workflow: PersonalOpsWorkflow;
|
|
96
|
+
readonly operation?: { readonly signal: PersonalOpsConnectorSignal; readonly tool: PersonalOpsConnectorTool };
|
|
97
|
+
readonly followUpOperation?: { readonly signal: PersonalOpsConnectorSignal; readonly tool: PersonalOpsConnectorTool };
|
|
98
|
+
readonly includeParameters: boolean;
|
|
99
|
+
readonly readOnlyNext: string;
|
|
100
|
+
readonly mutationBoundary: string;
|
|
101
|
+
}): readonly PersonalOpsExecutionStep[] {
|
|
102
|
+
if (options.workflow.status === 'needs-setup') {
|
|
103
|
+
return [{
|
|
104
|
+
id: 'setup-connector',
|
|
105
|
+
label: `Set up a ${options.lane.id === 'inbox' ? 'mail' : options.lane.id} connector`,
|
|
106
|
+
routeKind: 'setup',
|
|
107
|
+
effect: 'setup',
|
|
108
|
+
requiresConfirmation: false,
|
|
109
|
+
modelRoute: options.workflow.modelRoute,
|
|
110
|
+
status: 'needs-setup',
|
|
111
|
+
policy: 'Do not read personal data or create effects until a connector or daemon method is configured and reviewed.',
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
if (options.workflow.status === 'attention') {
|
|
115
|
+
return [{
|
|
116
|
+
id: 'repair-connector-readiness',
|
|
117
|
+
label: 'Repair connector trust or schema freshness',
|
|
118
|
+
routeKind: 'setup',
|
|
119
|
+
effect: 'setup',
|
|
120
|
+
requiresConfirmation: false,
|
|
121
|
+
modelRoute: options.operation?.signal.modelRoute ?? options.workflow.modelRoute,
|
|
122
|
+
status: 'attention',
|
|
123
|
+
policy: 'Review connector trust, connection, and schema freshness before using live personal data.',
|
|
124
|
+
}];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const steps: PersonalOpsExecutionStep[] = [];
|
|
128
|
+
if (options.operation) {
|
|
129
|
+
steps.push(connectorStep({
|
|
130
|
+
id: 'read-live-context',
|
|
131
|
+
label: options.readOnlyNext,
|
|
132
|
+
routeKind: 'connector-read',
|
|
133
|
+
status: options.workflow.status,
|
|
134
|
+
tool: options.operation.tool,
|
|
135
|
+
signal: options.operation.signal,
|
|
136
|
+
includeParameters: options.includeParameters,
|
|
137
|
+
policy: 'Run only the selected bounded read route, then summarize or draft in the Agent transcript without mutating the provider.',
|
|
138
|
+
}));
|
|
139
|
+
} else {
|
|
140
|
+
steps.push({
|
|
141
|
+
id: 'inspect-read-route',
|
|
142
|
+
label: 'Inspect the exact read route before using live personal data.',
|
|
143
|
+
routeKind: 'setup',
|
|
144
|
+
effect: 'setup',
|
|
145
|
+
requiresConfirmation: false,
|
|
146
|
+
modelRoute: options.workflow.modelRoute,
|
|
147
|
+
status: options.workflow.status,
|
|
148
|
+
policy: 'No concrete connector tool is selected yet; inspect the lane before using personal data.',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
steps.push({
|
|
153
|
+
id: 'compose-local-result',
|
|
154
|
+
label: options.lane.id === 'calendar' ? 'Summarize agenda or conflict findings locally.' : 'Summarize or draft locally without sending.',
|
|
155
|
+
routeKind: 'local-compose',
|
|
156
|
+
effect: 'local-only',
|
|
157
|
+
requiresConfirmation: false,
|
|
158
|
+
modelRoute: 'main Agent response',
|
|
159
|
+
status: options.workflow.status,
|
|
160
|
+
policy: 'Local composition does not send, edit, archive, label, or write provider records.',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (options.followUpOperation) {
|
|
164
|
+
steps.push(connectorStep({
|
|
165
|
+
id: 'confirmed-follow-up-effect',
|
|
166
|
+
label: options.mutationBoundary,
|
|
167
|
+
routeKind: 'connector-confirmed-effect',
|
|
168
|
+
status: options.workflow.status,
|
|
169
|
+
tool: options.followUpOperation.tool,
|
|
170
|
+
signal: options.followUpOperation.signal,
|
|
171
|
+
includeParameters: options.includeParameters,
|
|
172
|
+
policy: 'Only run this follow-up after the user reviews the draft/change and explicitly confirms the exact provider effect.',
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
return steps;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function recordOperationSummary(record: PersonalOpsLiveRecord | null, includeParameters: boolean): Record<string, unknown> | undefined {
|
|
179
|
+
if (!record) return undefined;
|
|
180
|
+
return {
|
|
181
|
+
id: record.id,
|
|
182
|
+
label: record.label,
|
|
183
|
+
status: record.status,
|
|
184
|
+
modelRoute: record.modelRoute,
|
|
185
|
+
effect: record.effect ?? 'read-only',
|
|
186
|
+
capability: record.capability,
|
|
187
|
+
...(includeParameters && record.requiredFields ? { requiredFields: record.requiredFields } : {}),
|
|
188
|
+
...(includeParameters && record.optionalFields ? { optionalFields: record.optionalFields.slice(0, 12) } : {}),
|
|
189
|
+
...(includeParameters && record.sampleInput ? { sampleInput: record.sampleInput } : {}),
|
|
190
|
+
confirmationRequired: record.confirmationRequired === true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function redactedPersonalOpsText(value: string): string {
|
|
195
|
+
return value
|
|
196
|
+
.replace(/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>')
|
|
197
|
+
.replace(/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTHORIZATION|BEARER)[A-Z0-9_]*)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;}]+)/gi, '$1=<redacted>')
|
|
198
|
+
.replace(/(\b(?:api[-_]?key|token|secret|password|passwd|authorization|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;}]+)/gi, '$1<redacted>')
|
|
199
|
+
.replace(/\b(xox[baprs]-[A-Za-z0-9-]{8,})\b/g, '<redacted-token>');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function stringifyForPreview(value: unknown): string {
|
|
203
|
+
if (typeof value === 'string') return value;
|
|
204
|
+
try {
|
|
205
|
+
return JSON.stringify(value, null, 2);
|
|
206
|
+
} catch {
|
|
207
|
+
return String(value);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function boundedPersonalOpsResult(value: unknown, includeParameters: boolean): Record<string, unknown> {
|
|
212
|
+
const text = redactedPersonalOpsText(stringifyForPreview(value));
|
|
213
|
+
const max = includeParameters ? 6000 : 1600;
|
|
214
|
+
return {
|
|
215
|
+
format: typeof value,
|
|
216
|
+
characters: text.length,
|
|
217
|
+
truncated: text.length > max,
|
|
218
|
+
preview: text.length <= max ? text : `${text.slice(0, max).trimEnd()}...`,
|
|
219
|
+
redaction: 'Secret-looking tokens, API keys, credentials, passwords, and bearer values are redacted before model display.',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function recordObject(value: unknown): Record<string, unknown> | null {
|
|
224
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function lowerKeyEntries(record: Record<string, unknown>): readonly [string, unknown][] {
|
|
228
|
+
return Object.entries(record).map(([key, value]) => [key.toLowerCase(), value] as [string, unknown]);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function stringField(record: Record<string, unknown>, names: readonly string[]): string {
|
|
232
|
+
const lowered = lowerKeyEntries(record);
|
|
233
|
+
for (const name of names) {
|
|
234
|
+
const exact = lowered.find(([key]) => key === name);
|
|
235
|
+
const value = exact?.[1];
|
|
236
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
237
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
238
|
+
}
|
|
239
|
+
for (const name of names) {
|
|
240
|
+
const fuzzy = lowered.find(([key]) => key.includes(name));
|
|
241
|
+
const value = fuzzy?.[1];
|
|
242
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
243
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
244
|
+
}
|
|
245
|
+
return '';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function candidateResultItems(value: unknown, depth = 0): readonly unknown[] {
|
|
249
|
+
if (depth > 4) return [];
|
|
250
|
+
if (Array.isArray(value)) return value;
|
|
251
|
+
const object = recordObject(value);
|
|
252
|
+
if (!object) return [];
|
|
253
|
+
for (const key of ['messages', 'threads', 'emails', 'mail', 'events', 'items', 'results', 'data']) {
|
|
254
|
+
const entry = object[key];
|
|
255
|
+
if (Array.isArray(entry)) return entry;
|
|
256
|
+
const nested = candidateResultItems(entry, depth + 1);
|
|
257
|
+
if (nested.length > 0) return nested;
|
|
258
|
+
}
|
|
259
|
+
for (const key of ['structuredContent', 'structured_content', 'result', 'payload', 'output']) {
|
|
260
|
+
const nested = candidateResultItems(object[key], depth + 1);
|
|
261
|
+
if (nested.length > 0) return nested;
|
|
262
|
+
}
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function reviewCardFromObject(
|
|
267
|
+
lane: PersonalOpsLane,
|
|
268
|
+
sourceRecord: PersonalOpsLiveRecord,
|
|
269
|
+
item: Record<string, unknown>,
|
|
270
|
+
index: number,
|
|
271
|
+
includeParameters: boolean,
|
|
272
|
+
): Record<string, unknown> {
|
|
273
|
+
const calendar = lane.id === 'calendar';
|
|
274
|
+
const id = stringField(item, calendar ? ['id', 'eventid', 'uid'] : ['id', 'messageid', 'threadid', 'emailid']) || `${sourceRecord.id}:item-${index + 1}`;
|
|
275
|
+
const subject = stringField(item, calendar
|
|
276
|
+
? ['summary', 'title', 'subject', 'name']
|
|
277
|
+
: ['subject', 'title', 'summary']);
|
|
278
|
+
const actor = stringField(item, calendar
|
|
279
|
+
? ['calendar', 'organizer', 'attendee', 'location']
|
|
280
|
+
: ['from', 'sender', 'author']);
|
|
281
|
+
const time = stringField(item, calendar
|
|
282
|
+
? ['start', 'starttime', 'time', 'when', 'date']
|
|
283
|
+
: ['date', 'receivedat', 'timestamp', 'time']);
|
|
284
|
+
const body = stringField(item, calendar
|
|
285
|
+
? ['description', 'notes', 'body', 'snippet']
|
|
286
|
+
: ['snippet', 'preview', 'body', 'text', 'content']);
|
|
287
|
+
const label = subject || actor || `${calendar ? 'Calendar event' : 'Inbox item'} ${index + 1}`;
|
|
288
|
+
const summaryParts = [
|
|
289
|
+
actor ? `${calendar ? 'source' : 'from'} ${actor}` : '',
|
|
290
|
+
time ? `time ${time}` : '',
|
|
291
|
+
body,
|
|
292
|
+
].filter(Boolean);
|
|
293
|
+
const summary = redactedPersonalOpsText(summaryParts.join('; ') || stringifyForPreview(item));
|
|
294
|
+
return {
|
|
295
|
+
id,
|
|
296
|
+
kind: calendar ? 'calendar-event' : 'inbox-message',
|
|
297
|
+
label: previewHarnessText(redactedPersonalOpsText(label), includeParameters ? 160 : 96),
|
|
298
|
+
summary: previewHarnessText(summary, includeParameters ? 420 : 180),
|
|
299
|
+
sourceRecordId: sourceRecord.id,
|
|
300
|
+
sourceTool: sourceRecord.qualifiedName,
|
|
301
|
+
confidence: 'normalized',
|
|
302
|
+
followUpBoundary: calendar
|
|
303
|
+
? 'Calendar create, edit, delete, RSVP, and reschedule actions require a separate confirmed route.'
|
|
304
|
+
: 'Reply, send, label, archive, move, and delete actions require a separate confirmed route.',
|
|
305
|
+
...(includeParameters ? { rawKeys: Object.keys(item).slice(0, 16) } : {}),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function personalOpsReadReviewRecords(
|
|
310
|
+
lane: PersonalOpsLane,
|
|
311
|
+
sourceRecord: PersonalOpsLiveRecord,
|
|
312
|
+
result: unknown,
|
|
313
|
+
includeParameters: boolean,
|
|
314
|
+
): readonly Record<string, unknown>[] {
|
|
315
|
+
const items = candidateResultItems(result);
|
|
316
|
+
const objectCards = items
|
|
317
|
+
.map((item, index) => {
|
|
318
|
+
const object = recordObject(item);
|
|
319
|
+
if (!object) return null;
|
|
320
|
+
return reviewCardFromObject(lane, sourceRecord, object, index, includeParameters);
|
|
321
|
+
})
|
|
322
|
+
.filter((item): item is Record<string, unknown> => item !== null)
|
|
323
|
+
.slice(0, 8);
|
|
324
|
+
if (objectCards.length > 0) return objectCards;
|
|
325
|
+
const preview = boundedPersonalOpsResult(result, includeParameters).preview;
|
|
326
|
+
return [{
|
|
327
|
+
id: `${sourceRecord.id}:raw-preview`,
|
|
328
|
+
kind: lane.id === 'calendar' ? 'calendar-read-preview' : 'inbox-read-preview',
|
|
329
|
+
label: `${lane.label} read output preview`,
|
|
330
|
+
summary: previewHarnessText(String(preview), includeParameters ? 420 : 180),
|
|
331
|
+
sourceRecordId: sourceRecord.id,
|
|
332
|
+
sourceTool: sourceRecord.qualifiedName,
|
|
333
|
+
confidence: 'raw-preview',
|
|
334
|
+
followUpBoundary: 'Use this preview to summarize locally; any provider mutation requires a separate confirmed route.',
|
|
335
|
+
}];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function reviewRecordFieldValues(records: readonly Record<string, unknown>[], names: readonly string[], limit: number): readonly string[] {
|
|
339
|
+
const values: string[] = [];
|
|
340
|
+
const seen = new Set<string>();
|
|
341
|
+
for (const record of records) {
|
|
342
|
+
const value = previewHarnessText(redactedPersonalOpsText(stringField(record, names)), 120);
|
|
343
|
+
if (!value || seen.has(value.toLowerCase())) continue;
|
|
344
|
+
seen.add(value.toLowerCase());
|
|
345
|
+
values.push(value);
|
|
346
|
+
if (values.length >= limit) break;
|
|
347
|
+
}
|
|
348
|
+
return values;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export async function savePersonalOpsReviewArtifact(options: {
|
|
352
|
+
readonly context: CommandContext;
|
|
353
|
+
readonly lane: PersonalOpsLane;
|
|
354
|
+
readonly sourceRecord: PersonalOpsLiveRecord;
|
|
355
|
+
readonly inputFields: Readonly<Record<string, unknown>>;
|
|
356
|
+
readonly reviewRecords: readonly Record<string, unknown>[];
|
|
357
|
+
readonly output: Record<string, unknown>;
|
|
358
|
+
readonly title?: string;
|
|
359
|
+
}): Promise<Record<string, unknown>> {
|
|
360
|
+
const store = options.context.platform.artifactStore;
|
|
361
|
+
if (!store?.create) {
|
|
362
|
+
return {
|
|
363
|
+
status: 'unavailable',
|
|
364
|
+
reason: 'artifact_store_unavailable',
|
|
365
|
+
policy: 'Review-card persistence requires the Agent artifact store; raw connector output was not written.',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const createdAt = new Date().toISOString();
|
|
369
|
+
const safeTitle = previewHarnessText(options.title || `${options.lane.label} review cards`, 96)
|
|
370
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
371
|
+
.replace(/^-+|-+$/g, '') || 'personal-ops-review';
|
|
372
|
+
const reviewLabels = reviewRecordFieldValues(options.reviewRecords, ['label', 'subject', 'title', 'summary'], 5);
|
|
373
|
+
const reviewKinds = reviewRecordFieldValues(options.reviewRecords, ['kind'], 5);
|
|
374
|
+
const reviewRecordIds = reviewRecordFieldValues(options.reviewRecords, ['id'], 8);
|
|
375
|
+
const payload = {
|
|
376
|
+
version: 1,
|
|
377
|
+
createdAt,
|
|
378
|
+
laneId: options.lane.id,
|
|
379
|
+
sourceRecordId: options.sourceRecord.id,
|
|
380
|
+
sourceTool: options.sourceRecord.qualifiedName,
|
|
381
|
+
reviewRecords: options.reviewRecords,
|
|
382
|
+
outputPreview: options.output.preview,
|
|
383
|
+
outputTruncated: options.output.truncated,
|
|
384
|
+
inputFieldKeys: Object.keys(options.inputFields).sort((left, right) => left.localeCompare(right)),
|
|
385
|
+
policy: 'Saved Personal Ops review-card artifacts contain redacted review cards and bounded previews only; full raw connector output and full input values are not stored.',
|
|
386
|
+
};
|
|
387
|
+
const descriptor = await store.create({
|
|
388
|
+
kind: 'data',
|
|
389
|
+
mimeType: 'application/json',
|
|
390
|
+
filename: `${safeTitle}-${Date.now()}.json`,
|
|
391
|
+
text: `${redactedPersonalOpsText(JSON.stringify(payload, null, 2))}\n`,
|
|
392
|
+
acquisitionMode: 'inline-data',
|
|
393
|
+
fetchMode: 'not-applicable',
|
|
394
|
+
metadata: {
|
|
395
|
+
purpose: 'personal-ops-review-cards',
|
|
396
|
+
laneId: options.lane.id,
|
|
397
|
+
sourceRecordId: options.sourceRecord.id,
|
|
398
|
+
sourceTool: options.sourceRecord.qualifiedName ?? '',
|
|
399
|
+
reviewRecordCount: options.reviewRecords.length,
|
|
400
|
+
reviewLabels,
|
|
401
|
+
reviewKinds,
|
|
402
|
+
reviewRecordIds,
|
|
403
|
+
fullRawConnectorOutputStored: false,
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
return {
|
|
407
|
+
status: 'saved',
|
|
408
|
+
artifactId: descriptor.id,
|
|
409
|
+
filename: descriptor.filename ?? null,
|
|
410
|
+
mimeType: descriptor.mimeType,
|
|
411
|
+
sizeBytes: descriptor.sizeBytes,
|
|
412
|
+
modelRoute: `agent_artifacts show artifactId:"${descriptor.id}"`,
|
|
413
|
+
policy: 'Artifact contains redacted review cards for later user review; provider mutations still require separate confirmation.',
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function fieldInputValue(value: string, sample: unknown): unknown {
|
|
418
|
+
if (typeof sample === 'number') {
|
|
419
|
+
const parsed = Number(value);
|
|
420
|
+
return Number.isFinite(parsed) ? parsed : value;
|
|
421
|
+
}
|
|
422
|
+
if (typeof sample === 'boolean') return /^(1|true|yes|y)$/i.test(value.trim());
|
|
423
|
+
if (Array.isArray(sample)) return value.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function readInputFields(value: unknown, sampleInput?: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>> {
|
|
428
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
429
|
+
const fields: Record<string, unknown> = {};
|
|
430
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
431
|
+
if (PERSONAL_OPS_READ_CONTROL_FIELDS.has(key)) continue;
|
|
432
|
+
if (entry === undefined || entry === null) continue;
|
|
433
|
+
const text = typeof entry === 'string' ? entry.trim() : String(entry).trim();
|
|
434
|
+
if (!text) continue;
|
|
435
|
+
fields[key] = fieldInputValue(text, sampleInput?.[key]);
|
|
436
|
+
}
|
|
437
|
+
return fields;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function readRunControlString(fields: unknown, key: string): string {
|
|
441
|
+
const object = recordObject(fields);
|
|
442
|
+
const value = object?.[key];
|
|
443
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export function readRunControlBoolean(fields: unknown, keys: readonly string[]): boolean {
|
|
447
|
+
const object = recordObject(fields);
|
|
448
|
+
if (!object) return false;
|
|
449
|
+
return keys.some((key) => {
|
|
450
|
+
const value = object[key];
|
|
451
|
+
if (value === true) return true;
|
|
452
|
+
if (typeof value !== 'string') return false;
|
|
453
|
+
return /^(1|true|yes|y|on)$/i.test(value.trim());
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function missingRequiredInputFields(record: PersonalOpsLiveRecord, fields: Readonly<Record<string, unknown>>): readonly string[] {
|
|
458
|
+
return (record.requiredFields ?? []).filter((field) => !(field in fields) || fields[field] === '');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function personalOpsReadRunRoute(laneId: PersonalOpsLaneId, recordId: string): string {
|
|
462
|
+
return `personal_ops action:"read" laneId:"${laneId}" recordId:"${recordId}" fields:{...} confirm:true explicitUserRequest:"..."`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function executionRouteForRecord(record: PersonalOpsLiveRecord, laneId: PersonalOpsLaneId): string {
|
|
466
|
+
return personalOpsReadRunRoute(laneId, record.id);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function personalOpsRoutePacket(options: {
|
|
470
|
+
readonly id: string;
|
|
471
|
+
readonly label: string;
|
|
472
|
+
readonly effect: PersonalOpsRoutePacket['effect'];
|
|
473
|
+
readonly modelRoute: string;
|
|
474
|
+
readonly requiresConfirmation: boolean;
|
|
475
|
+
readonly policy: string;
|
|
476
|
+
}): PersonalOpsRoutePacket {
|
|
477
|
+
return {
|
|
478
|
+
id: options.id,
|
|
479
|
+
label: options.label,
|
|
480
|
+
effect: options.effect,
|
|
481
|
+
modelRoute: options.modelRoute,
|
|
482
|
+
requiresConfirmation: options.requiresConfirmation,
|
|
483
|
+
policy: options.policy,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export function savedReviewArtifactId(savedReviewArtifact: Record<string, unknown> | null): string {
|
|
488
|
+
const value = savedReviewArtifact?.artifactId;
|
|
489
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function personalOpsReadNextRoutes(options: {
|
|
493
|
+
readonly lane: PersonalOpsLane;
|
|
494
|
+
readonly runRoute: string;
|
|
495
|
+
readonly savedReviewArtifact: Record<string, unknown> | null;
|
|
496
|
+
}): Record<string, PersonalOpsRoutePacket> {
|
|
497
|
+
const artifactId = savedReviewArtifactId(options.savedReviewArtifact);
|
|
498
|
+
const artifactRoute = artifactId ? `agent_artifacts show artifactId:"${artifactId}" includeContent:true` : '';
|
|
499
|
+
const localReviewRoute = artifactRoute || 'main Agent response';
|
|
500
|
+
const routes: Record<string, PersonalOpsRoutePacket> = {
|
|
501
|
+
lane: personalOpsRoutePacket({
|
|
502
|
+
id: 'inspect-personal-ops-lane',
|
|
503
|
+
label: `Inspect ${options.lane.label} lane`,
|
|
504
|
+
effect: 'read-only',
|
|
505
|
+
modelRoute: `personal_ops action:"lane" laneId:"${options.lane.id}" includeParameters:true`,
|
|
506
|
+
requiresConfirmation: false,
|
|
507
|
+
policy: 'Reopen the lane to inspect available records, connector readiness, and workflow cards before the next personal-data action.',
|
|
508
|
+
}),
|
|
509
|
+
queue: personalOpsRoutePacket({
|
|
510
|
+
id: 'inspect-personal-ops-queue',
|
|
511
|
+
label: `Inspect ${options.lane.label} review queue`,
|
|
512
|
+
effect: 'read-only',
|
|
513
|
+
modelRoute: `personal_ops action:"queue" query:"${options.lane.id}" includeParameters:true`,
|
|
514
|
+
requiresConfirmation: false,
|
|
515
|
+
policy: 'The queue is read-only; it lists saved review artifacts, fresh read routes, and follow-up boundaries without executing connectors.',
|
|
516
|
+
}),
|
|
517
|
+
refresh: personalOpsRoutePacket({
|
|
518
|
+
id: 'refresh-live-provider-read',
|
|
519
|
+
label: 'Refresh this provider read',
|
|
520
|
+
effect: 'read-only',
|
|
521
|
+
modelRoute: options.runRoute,
|
|
522
|
+
requiresConfirmation: true,
|
|
523
|
+
policy: 'Refreshing reads current provider data only after the user supplies current fields and confirms the bounded read; prior input values are not stored.',
|
|
524
|
+
}),
|
|
525
|
+
};
|
|
526
|
+
if (artifactRoute) {
|
|
527
|
+
routes.artifact = personalOpsRoutePacket({
|
|
528
|
+
id: 'reopen-saved-review-artifact',
|
|
529
|
+
label: 'Reopen saved review cards',
|
|
530
|
+
effect: 'read-only',
|
|
531
|
+
modelRoute: artifactRoute,
|
|
532
|
+
requiresConfirmation: false,
|
|
533
|
+
policy: 'Saved artifacts contain redacted review cards and bounded previews only; inspect them before local drafting, reminders, or any separate confirmed effect.',
|
|
534
|
+
});
|
|
535
|
+
routes.savedQueue = personalOpsRoutePacket({
|
|
536
|
+
id: 'find-saved-review-queue-records',
|
|
537
|
+
label: 'Find saved review queue records',
|
|
538
|
+
effect: 'read-only',
|
|
539
|
+
modelRoute: `personal_ops action:"queue" query:"${artifactId}" includeParameters:true`,
|
|
540
|
+
requiresConfirmation: false,
|
|
541
|
+
policy: 'Filter the read-only queue to the newly saved artifact so the next action starts from durable redacted cards instead of raw provider output.',
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (options.lane.id === 'calendar') {
|
|
545
|
+
routes.localReminderDraft = personalOpsRoutePacket({
|
|
546
|
+
id: 'draft-local-reminder-from-review',
|
|
547
|
+
label: 'Draft reminder locally from review cards',
|
|
548
|
+
effect: 'local-only',
|
|
549
|
+
modelRoute: localReviewRoute,
|
|
550
|
+
requiresConfirmation: false,
|
|
551
|
+
policy: 'Draft timing and message text in the Agent transcript first; creating a reminder is a separate confirmed schedule route.',
|
|
552
|
+
});
|
|
553
|
+
routes.createReminderBoundary = personalOpsRoutePacket({
|
|
554
|
+
id: 'create-reminder-confirmed-boundary',
|
|
555
|
+
label: 'Create reminder after review',
|
|
556
|
+
effect: 'confirmed-effect',
|
|
557
|
+
modelRoute: 'schedule action:"remind" message:"..." at:"..." confirm:true explicitUserRequest:"..."',
|
|
558
|
+
requiresConfirmation: true,
|
|
559
|
+
policy: 'Create one reminder only after the user reviews exact message text and timing.',
|
|
560
|
+
});
|
|
561
|
+
routes.calendarEditBoundary = personalOpsRoutePacket({
|
|
562
|
+
id: 'inspect-calendar-edit-boundary',
|
|
563
|
+
label: 'Inspect calendar edit route',
|
|
564
|
+
effect: 'confirmed-effect',
|
|
565
|
+
modelRoute: 'personal_ops action:"intake" query:"edit saved calendar event" includeParameters:true',
|
|
566
|
+
requiresConfirmation: true,
|
|
567
|
+
policy: 'Calendar edits, RSVP, reschedule, and deletes require a separate inspected connector route and explicit confirmation.',
|
|
568
|
+
});
|
|
569
|
+
} else {
|
|
570
|
+
routes.localDraft = personalOpsRoutePacket({
|
|
571
|
+
id: 'draft-local-reply-from-review',
|
|
572
|
+
label: 'Draft reply locally from review cards',
|
|
573
|
+
effect: 'local-only',
|
|
574
|
+
modelRoute: localReviewRoute,
|
|
575
|
+
requiresConfirmation: false,
|
|
576
|
+
policy: 'Drafting stays local in the Agent transcript and does not send, label, archive, move, or delete provider records.',
|
|
577
|
+
});
|
|
578
|
+
routes.sendBoundary = personalOpsRoutePacket({
|
|
579
|
+
id: 'inspect-send-boundary',
|
|
580
|
+
label: 'Inspect send route after review',
|
|
581
|
+
effect: 'confirmed-effect',
|
|
582
|
+
modelRoute: 'personal_ops action:"intake" query:"send reviewed reply from saved inbox review" includeParameters:true',
|
|
583
|
+
requiresConfirmation: true,
|
|
584
|
+
policy: 'Sending requires a write-like inbox connector route plus explicit confirmation of exact recipients and body.',
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
return routes;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function summarizeRunRecord(record: PersonalOpsLiveRecord, lane: PersonalOpsLane, includeParameters: boolean): Record<string, unknown> {
|
|
591
|
+
return {
|
|
592
|
+
laneId: lane.id,
|
|
593
|
+
recordId: record.id,
|
|
594
|
+
label: record.label,
|
|
595
|
+
status: record.status,
|
|
596
|
+
effect: record.effect ?? 'read-only',
|
|
597
|
+
capability: record.capability,
|
|
598
|
+
modelRoute: record.modelRoute,
|
|
599
|
+
...(record.qualifiedName ? { qualifiedName: record.qualifiedName } : {}),
|
|
600
|
+
...(includeParameters && record.requiredFields ? { requiredFields: record.requiredFields } : {}),
|
|
601
|
+
...(includeParameters && record.optionalFields ? { optionalFields: record.optionalFields.slice(0, 12) } : {}),
|
|
602
|
+
...(includeParameters && record.sampleInput ? { sampleInput: record.sampleInput } : {}),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export function resolveRunRecord(
|
|
607
|
+
lanes: readonly PersonalOpsLane[],
|
|
608
|
+
options: { readonly laneId: string; readonly recordId: string; readonly target: string; readonly query: string },
|
|
609
|
+
): { readonly lane: PersonalOpsLane; readonly record: PersonalOpsLiveRecord } | null | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] } {
|
|
610
|
+
const scopedLanes = options.laneId ? lanes.filter((lane) => lane.id === options.laneId) : lanes;
|
|
611
|
+
const lookup = options.recordId || options.target || options.query;
|
|
612
|
+
if (!lookup) return null;
|
|
613
|
+
const normalized = lookup.toLowerCase();
|
|
614
|
+
const exact = scopedLanes.flatMap((lane) => (lane.liveRecords ?? [])
|
|
615
|
+
.filter((record) => record.id === lookup || record.qualifiedName === lookup)
|
|
616
|
+
.map((record) => ({ lane, record })));
|
|
617
|
+
if (exact.length === 1) return exact[0]!;
|
|
618
|
+
if (exact.length > 1) {
|
|
619
|
+
return {
|
|
620
|
+
status: 'ambiguous',
|
|
621
|
+
input: lookup,
|
|
622
|
+
candidates: exact.map(({ lane, record }) => summarizeRunRecord(record, lane, false)),
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
const matches = scopedLanes.flatMap((lane) => (lane.liveRecords ?? [])
|
|
626
|
+
.filter((record) => liveRecordSearchText(record).includes(normalized))
|
|
627
|
+
.map((record) => ({ lane, record })));
|
|
628
|
+
if (matches.length === 1) return matches[0]!;
|
|
629
|
+
if (matches.length > 1) {
|
|
630
|
+
return {
|
|
631
|
+
status: 'ambiguous',
|
|
632
|
+
input: lookup,
|
|
633
|
+
candidates: matches.slice(0, 8).map(({ lane, record }) => summarizeRunRecord(record, lane, false)),
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
}
|