@vellumai/assistant 0.11.7 → 0.11.8-staging.1
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/ARCHITECTURE.md +10 -10
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +1 -1
- package/docs/error-handling.md +1 -2
- package/docs/guardian-request-flow.md +51 -5
- package/docs/trusted-contact-access.md +13 -12
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
- package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
- package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
- package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
- package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
- package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/openapi.yaml +481 -9
- package/package.json +3 -1
- package/scripts/sync-llm-catalog.ts +3 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
- package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +56 -0
- package/src/__tests__/approval-cascade.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +117 -6
- package/src/__tests__/attachments.test.ts +122 -0
- package/src/__tests__/bundled-asset.test.ts +22 -1
- package/src/__tests__/channel-approval.test.ts +14 -7
- package/src/__tests__/channel-approvals.test.ts +3 -3
- package/src/__tests__/channel-delivery-store.test.ts +58 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
- package/src/__tests__/channel-invite-transport.test.ts +1 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
- package/src/__tests__/compaction-events.test.ts +0 -12
- package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
- package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/contacts-write.test.ts +141 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop.test.ts +95 -13
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
- package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
- package/src/__tests__/conversation-media-retry.test.ts +33 -2
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -12
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
- package/src/__tests__/conversation-queue.test.ts +4 -13
- package/src/__tests__/conversation-retry-route.test.ts +5 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-sight-frames.test.ts +615 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -13
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
- package/src/__tests__/conversation-speed-override.test.ts +0 -12
- package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
- package/src/__tests__/conversation-summarize-route.test.ts +5 -0
- package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
- package/src/__tests__/credential-health-service.test.ts +45 -0
- package/src/__tests__/credential-record-retire.test.ts +256 -0
- package/src/__tests__/credential-record-write-through.test.ts +8 -4
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/delete-propagation.test.ts +121 -3
- package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
- package/src/__tests__/file-ops-service.test.ts +8 -3
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
- package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
- package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
- package/src/__tests__/guardian-gateway-sim.ts +56 -20
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
- package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
- package/src/__tests__/guardian-wait-controller.test.ts +6 -0
- package/src/__tests__/headless-browser-mode.test.ts +27 -0
- package/src/__tests__/helpers/mock-conversation.ts +2 -0
- package/src/__tests__/list-messages-system-card.test.ts +70 -0
- package/src/__tests__/llm-catalog-parity.test.ts +2 -0
- package/src/__tests__/managed-profile-guard.test.ts +2 -2
- package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
- package/src/__tests__/media-stream-server-integration.test.ts +6 -0
- package/src/__tests__/non-member-access-request.test.ts +12 -0
- package/src/__tests__/notification-discord-adapter.test.ts +76 -4
- package/src/__tests__/oauth-commands-routes.test.ts +21 -0
- package/src/__tests__/openai-provider.test.ts +63 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
- package/src/__tests__/persist-unsendable-image.test.ts +19 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -1
- package/src/__tests__/process-message-display-content.test.ts +1 -1
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
- package/src/__tests__/qdrant-manager.test.ts +6 -0
- package/src/__tests__/reaction-persistence.test.ts +58 -62
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
- package/src/__tests__/server-history-render.test.ts +22 -0
- package/src/__tests__/sight-frame-retention.test.ts +362 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
- package/src/__tests__/terminal-tools.test.ts +9 -9
- package/src/__tests__/tool-result-spool.test.ts +5 -8
- package/src/__tests__/usage-attribution.test.ts +26 -0
- package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
- package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
- package/src/acp/acp-auth-marker-store.ts +196 -0
- package/src/acp/acp-claude-oauth.ts +154 -4
- package/src/acp/acp-connect-card-state.ts +49 -8
- package/src/acp/agent-process.ts +1 -0
- package/src/acp/auto-install.ts +1 -0
- package/src/acp/client-handler.ts +1 -0
- package/src/acp/prepare-agent-env.ts +79 -1
- package/src/acp/resolve-agent.ts +12 -0
- package/src/acp/session-manager.test.ts +183 -0
- package/src/acp/session-manager.ts +110 -6
- package/src/acp/types.ts +17 -0
- package/src/agent/attachments.ts +7 -2
- package/src/agent/loop.ts +59 -1
- package/src/api/events/contact-form-closed.ts +32 -0
- package/src/api/events/contact-record-request.ts +63 -0
- package/src/api/events/contact-request.ts +10 -3
- package/src/api/index.ts +20 -0
- package/src/api/responses/conversation-message.ts +20 -0
- package/src/api/responses/home.ts +101 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +127 -30
- package/src/approvals/guardian-decision-primitive.ts +9 -0
- package/src/approvals/guardian-expiry-notifier.ts +22 -16
- package/src/approvals/guardian-request-status-sync.ts +98 -0
- package/src/avatar/svg-compositor.ts +1 -1
- package/src/avatar/traits-png-sync.ts +1 -1
- package/src/bundler/app-compiler.ts +1 -0
- package/src/bundler/compiler-tools.ts +1 -0
- package/src/bundler/package-resolver.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
- package/src/calls/guardian-action-sweep.ts +6 -1
- package/src/calls/voice-session-bridge.ts +46 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
- package/src/channels/gateway-guardian-requests.ts +37 -18
- package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
- package/src/cli/commands/__tests__/browser.test.ts +65 -1
- package/src/cli/commands/browser.ts +30 -10
- package/src/cli/commands/contacts.help.ts +173 -11
- package/src/cli/commands/contacts.ts +343 -4
- package/src/cli/commands/db/refresh.ts +1 -0
- package/src/cli/commands/oauth/index.help.ts +6 -3
- package/src/cli/commands/oauth/request.test.ts +37 -0
- package/src/cli/commands/oauth/request.ts +7 -6
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-github.ts +20 -19
- package/src/cli/lib/install-plugin-dependencies.ts +3 -5
- package/src/cli/lib/merge-plugin-tree.ts +1 -0
- package/src/cli/lib/publish-plugin.ts +4 -1
- package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
- package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
- package/src/config/balanced-model-experiment.ts +15 -9
- package/src/config/bundled-skills/contacts/SKILL.md +61 -18
- package/src/config/default-profile-catalog.ts +12 -11
- package/src/config/feature-flag-registry.json +22 -13
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
- package/src/config/schemas/live-voice.ts +7 -0
- package/src/config/schemas/sight.ts +45 -0
- package/src/config/skills.ts +2 -1
- package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
- package/src/contacts/contact-store.ts +47 -4
- package/src/contacts/contacts-write.ts +5 -2
- package/src/contacts/mirror-reconciler.ts +210 -0
- package/src/contacts/mirror-tombstones.ts +42 -0
- package/src/contacts/notify-contacts-changed.ts +11 -2
- package/src/context/compactor.ts +47 -19
- package/src/context/tool-result-spool.ts +21 -14
- package/src/conversations/message-consolidation.ts +2 -2
- package/src/credential-health/credential-health-service.ts +37 -6
- package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
- package/src/daemon/conversation-agent-loop.ts +100 -1
- package/src/daemon/conversation-media-retry.ts +11 -6
- package/src/daemon/conversation-messaging.ts +293 -30
- package/src/daemon/conversation-runtime-assembly.ts +67 -0
- package/src/daemon/conversation-sight-frames.ts +187 -0
- package/src/daemon/conversation-store.ts +18 -28
- package/src/daemon/conversation-surfaces.ts +12 -2
- package/src/daemon/conversation-tool-setup.ts +39 -10
- package/src/daemon/conversation.ts +159 -16
- package/src/daemon/daemon-control.ts +6 -17
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +10 -11
- package/src/daemon/install-assistant-command.ts +1 -0
- package/src/daemon/lifecycle.ts +10 -13
- package/src/daemon/message-types/sync.ts +4 -0
- package/src/daemon/process-message.ts +2 -17
- package/src/daemon/reaction-history-render.test.ts +162 -0
- package/src/daemon/reaction-history-render.ts +115 -0
- package/src/daemon/reaction-record.test.ts +162 -0
- package/src/daemon/reaction-record.ts +83 -0
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/summarize-boundary.ts +12 -0
- package/src/daemon/video-thumbnail.ts +1 -1
- package/src/home/feed-types.ts +4 -0
- package/src/home/feed-writer.ts +25 -0
- package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
- package/src/live-voice/live-voice-photo.ts +420 -65
- package/src/live-voice/live-voice-session.ts +311 -27
- package/src/live-voice/protocol.ts +147 -11
- package/src/messaging/provider-message-metadata.ts +7 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
- package/src/messaging/providers/channel-transport.ts +37 -0
- package/src/messaging/providers/discord/api.ts +4 -4
- package/src/messaging/providers/discord/send.test.ts +177 -2
- package/src/messaging/providers/discord/send.ts +197 -10
- package/src/messaging/providers/discord/transport.ts +10 -0
- package/src/messaging/providers/index.ts +42 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
- package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
- package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
- package/src/messaging/providers/slack/adapter.ts +112 -27
- package/src/messaging/providers/slack/api.test.ts +10 -0
- package/src/messaging/providers/slack/auth.ts +32 -6
- package/src/messaging/providers/slack/client.ts +0 -15
- package/src/messaging/providers/slack/message-content.ts +137 -0
- package/src/messaging/providers/slack/send.test.ts +43 -2
- package/src/messaging/providers/slack/send.ts +18 -5
- package/src/messaging/providers/slack/transport.ts +10 -0
- package/src/messaging/providers/slack/types.ts +21 -2
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
- package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
- package/src/messaging/providers/telegram-bot/send.ts +41 -1
- package/src/messaging/providers/telegram-bot/transport.ts +10 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
- package/src/messaging/reaction-envelopes.ts +79 -0
- package/src/monitoring/control.ts +2 -2
- package/src/monitoring/page-cache.ts +5 -1
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
- package/src/notifications/adapters/discord.ts +62 -9
- package/src/notifications/conversation-pairing.ts +31 -4
- package/src/notifications/emit-signal.ts +8 -5
- package/src/notifications/guardian-delivery-recorder.ts +47 -6
- package/src/notifications/guardian-feed-projection.ts +492 -0
- package/src/notifications/home-feed-side-effect.ts +59 -11
- package/src/oauth/byo-connection.test.ts +22 -0
- package/src/oauth/byo-connection.ts +11 -5
- package/src/oauth/connection.ts +3 -2
- package/src/oauth/platform-connection.test.ts +35 -0
- package/src/oauth/platform-connection.ts +18 -13
- package/src/oauth/scope-utils.ts +48 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/outbound-proxy/certs.ts +5 -4
- package/src/permissions/question-guardian-request.test.ts +7 -1
- package/src/persistence/attachments-store.ts +134 -31
- package/src/persistence/conversation-crud.ts +137 -4
- package/src/persistence/conversation-types.ts +109 -0
- package/src/persistence/db-connection.ts +3 -21
- package/src/persistence/delivery-crud.ts +42 -12
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
- package/src/persistence/embeddings/embedding-local.ts +7 -112
- package/src/persistence/embeddings/qdrant-manager.ts +4 -1
- package/src/persistence/llm-request-log-store.ts +1 -1
- package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
- package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
- package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
- package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
- package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
- package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
- package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
- package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
- package/src/persistence/schema/acp.ts +25 -0
- package/src/persistence/steps.ts +8 -0
- package/src/platform/platform-patch-queue.test.ts +43 -41
- package/src/plugin-api/index.ts +12 -0
- package/src/plugins/defaults/compaction/window-manager.ts +2 -0
- package/src/plugins/defaults/image-recovery/recover.ts +12 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
- package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
- package/src/plugins/defaults/memory/worker-control.ts +1 -2
- package/src/providers/content-block-schema.ts +1 -0
- package/src/providers/fireworks/client.ts +9 -1
- package/src/providers/gemini/client.ts +22 -15
- package/src/providers/media-resolve.ts +24 -10
- package/src/providers/model-catalog.ts +91 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -5
- package/src/providers/retry.ts +16 -11
- package/src/providers/types.ts +56 -0
- package/src/routes/__tests__/control.test.ts +4 -3
- package/src/routes/control.ts +1 -1
- package/src/routes/route-host-client.ts +1 -1
- package/src/runtime/AGENTS.md +30 -12
- package/src/runtime/__tests__/agent-wake.test.ts +71 -6
- package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
- package/src/runtime/access-request-helper.ts +15 -7
- package/src/runtime/agent-wake.ts +16 -4
- package/src/runtime/channel-approval-types.ts +8 -5
- package/src/runtime/channel-invite-transport.ts +2 -0
- package/src/runtime/channel-invite-transports/discord.ts +27 -0
- package/src/runtime/channel-readiness-service.ts +4 -4
- package/src/runtime/channel-reply-delivery.ts +72 -28
- package/src/runtime/guardian-action-service.ts +9 -5
- package/src/runtime/guardian-form-registry.ts +256 -0
- package/src/runtime/guardian-reply-router.ts +33 -14
- package/src/runtime/invite-instruction-generator.ts +14 -2
- package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
- package/src/runtime/no-response.ts +13 -74
- package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
- package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
- package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
- package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
- package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
- package/src/runtime/routes/acp-routes-list.test.ts +58 -0
- package/src/runtime/routes/acp-routes.ts +253 -47
- package/src/runtime/routes/app-management-routes.ts +80 -47
- package/src/runtime/routes/approval-routes.ts +9 -0
- package/src/runtime/routes/archive-utils.ts +6 -1
- package/src/runtime/routes/avatar-routes.ts +1 -1
- package/src/runtime/routes/channel-route-shared.ts +18 -20
- package/src/runtime/routes/contact-prompt-routes.ts +271 -105
- package/src/runtime/routes/conversation-routes.ts +41 -32
- package/src/runtime/routes/credential-prompt-routes.ts +10 -1
- package/src/runtime/routes/debug-bash-routes.ts +1 -0
- package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
- package/src/runtime/routes/guardian-form-routes.ts +115 -0
- package/src/runtime/routes/inbound-message-handler.ts +82 -29
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/channel.ts +39 -18
- package/src/runtime/routes/migration-routes.ts +1 -3
- package/src/runtime/routes/monitoring-routes.ts +0 -3
- package/src/runtime/routes/oauth-commands-routes.ts +18 -5
- package/src/runtime/routes/route-host-worker-routes.ts +1 -1
- package/src/runtime/routes/schedule-worker-routes.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/run-script.ts +1 -0
- package/src/schedule/scheduler.ts +1 -1
- package/src/schedule/worker-control.ts +2 -2
- package/src/security/oauth2.ts +2 -11
- package/src/security/secure-keys.ts +55 -8
- package/src/skills/catalog-install.ts +7 -4
- package/src/skills/clawhub.ts +1 -0
- package/src/skills/inline-command-runner.ts +1 -0
- package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
- package/src/stt/room-noise-floor.ts +140 -0
- package/src/subagent/notify.ts +49 -3
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/acp/spawn.ts +2 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
- package/src/tools/browser/browser-execution.ts +10 -43
- package/src/tools/browser/browser-manager.ts +1 -0
- package/src/tools/browser/browser-status-constants.ts +6 -5
- package/src/tools/browser/runtime-check.ts +2 -0
- package/src/tools/channel/react-to-message.test.ts +193 -0
- package/src/tools/channel/react-to-message.ts +159 -0
- package/src/tools/credentials/metadata-store.ts +115 -19
- package/src/tools/credentials/store.ts +0 -2
- package/src/tools/filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +11 -38
- package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
- package/src/tools/shared/shell-output.test.ts +104 -0
- package/src/tools/shared/shell-output.ts +112 -39
- package/src/tools/skills/sandbox-runner.ts +1 -0
- package/src/tools/system/request-permission.test.ts +34 -0
- package/src/tools/system/request-permission.ts +29 -5
- package/src/tools/terminal/shell.test.ts +63 -0
- package/src/tools/terminal/shell.ts +12 -44
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/usage/attribution.ts +12 -4
- package/src/util/__tests__/worker-ownership.test.ts +77 -0
- package/src/util/__tests__/worker-process-command.test.ts +246 -1
- package/src/util/bun-runtime.ts +44 -13
- package/src/util/bundled-asset.ts +9 -1
- package/src/util/disk-usage.ts +1 -0
- package/src/util/file-use.ts +4 -1
- package/src/util/guardian-form-timeouts.ts +50 -0
- package/src/util/image-conversion.ts +1 -0
- package/src/util/oauth-request-body.test.ts +59 -0
- package/src/util/oauth-request-body.ts +56 -5
- package/src/util/platform.ts +69 -73
- package/src/util/process-table.ts +16 -5
- package/src/util/read-stdin.ts +5 -0
- package/src/util/spawn.ts +1 -0
- package/src/util/sqlite3-runtime.ts +25 -5
- package/src/util/worker-ownership.ts +193 -0
- package/src/util/worker-process.ts +375 -37
- package/src/workspace/git-service.ts +4 -12
- package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import type { CliCommandHelp } from "../lib/cli-command-help.js";
|
|
4
4
|
|
|
5
|
+
/** Shared tail for the three commands that park on a form in the app. */
|
|
6
|
+
const FORM_NOTE = `The form opens in the guardian's desktop or web app. With no app connected
|
|
7
|
+
nobody can answer, and the command exits when the wait elapses.`;
|
|
8
|
+
|
|
5
9
|
export const contactsHelp: CliCommandHelp = {
|
|
6
10
|
name: "contacts",
|
|
7
11
|
description: "Manage and query the contact graph",
|
|
@@ -15,9 +19,21 @@ can be linked to external identifiers — phone numbers,
|
|
|
15
19
|
Telegram IDs, email addresses — via channel memberships. The contact graph
|
|
16
20
|
is the source of truth for identity resolution across all channels.
|
|
17
21
|
|
|
22
|
+
Writes are split in two, and both need the guardian at the app:
|
|
23
|
+
|
|
24
|
+
create/update/delete the record: who someone is (name, notes). Opens a
|
|
25
|
+
form in the guardian's app; nothing is written until
|
|
26
|
+
they submit it. A record has no channel, so it grants
|
|
27
|
+
no access on its own.
|
|
28
|
+
prompt the channel: an address the guardian types, bound to
|
|
29
|
+
a contact. This is what makes someone reachable.
|
|
30
|
+
|
|
31
|
+
Granting someone access to message the assistant is 'contacts invites create'.
|
|
32
|
+
|
|
18
33
|
Examples:
|
|
19
34
|
$ assistant contacts list
|
|
20
35
|
$ assistant contacts get abc-123
|
|
36
|
+
$ assistant contacts create --name "Alice"
|
|
21
37
|
$ assistant contacts invites list`,
|
|
22
38
|
subcommands: [
|
|
23
39
|
{
|
|
@@ -83,10 +99,139 @@ assistant metadata is included.
|
|
|
83
99
|
Examples:
|
|
84
100
|
$ assistant contacts get 7a3b1c2d-4e5f-6789-abcd-ef0123456789
|
|
85
101
|
$ assistant contacts get abc-123 --json`,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "create",
|
|
105
|
+
description: "Propose a new contact for the guardian to confirm",
|
|
106
|
+
options: [
|
|
107
|
+
{
|
|
108
|
+
flags: "--name <name>",
|
|
109
|
+
description: "Proposed display name, prefilled into the form",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
flags: "--notes <notes>",
|
|
113
|
+
description: "Proposed notes, prefilled into the form",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
flags: "--label <label>",
|
|
117
|
+
description: "Display label shown on the form",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
flags: "--description <description>",
|
|
121
|
+
description: "Longer description shown on the form",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
flags: "--timeout <ms>",
|
|
125
|
+
description:
|
|
126
|
+
"How long the form stays open (ms). The command waits for it to close.",
|
|
127
|
+
defaultValue: String(300_000),
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
helpText: `
|
|
131
|
+
Opens an add-contact form in the guardian's app, prefilled with --name and
|
|
132
|
+
--notes. The guardian can edit either field before submitting, and the values
|
|
133
|
+
they submit are what gets written. Nothing is written if they dismiss the form.
|
|
134
|
+
|
|
135
|
+
The new contact has no channels, so it cannot message the assistant and the
|
|
136
|
+
assistant cannot reach it. It is a name to hang notes on and to address an
|
|
137
|
+
invite to. Use 'contacts prompt' to bind an address, or
|
|
138
|
+
'contacts invites create' to let someone bind their own.
|
|
139
|
+
|
|
140
|
+
${FORM_NOTE}
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
$ assistant contacts create --name "Alice"
|
|
144
|
+
$ assistant contacts create --name "Alice" --notes "Dentist, referred by Bob"
|
|
145
|
+
$ assistant contacts create --name "Alice" --json`,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "update",
|
|
149
|
+
args: "<id>",
|
|
150
|
+
description: "Propose a contact edit for the guardian to confirm",
|
|
151
|
+
options: [
|
|
152
|
+
{
|
|
153
|
+
flags: "--name <name>",
|
|
154
|
+
description: "Proposed display name, prefilled into the form",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
flags: "--notes <notes>",
|
|
158
|
+
description: "Proposed notes, prefilled into the form",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
flags: "--label <label>",
|
|
162
|
+
description: "Display label shown on the form",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
flags: "--description <description>",
|
|
166
|
+
description: "Longer description shown on the form",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
flags: "--timeout <ms>",
|
|
170
|
+
description:
|
|
171
|
+
"How long the form stays open (ms). The command waits for it to close.",
|
|
172
|
+
defaultValue: String(300_000),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
helpText: `
|
|
176
|
+
Arguments:
|
|
177
|
+
id UUID of the contact to edit. Run 'assistant contacts list' to find IDs.
|
|
178
|
+
|
|
179
|
+
Opens an edit form in the guardian's app showing the contact's current name
|
|
180
|
+
alongside the proposed change. At least one of --name or --notes is required.
|
|
181
|
+
The guardian can edit either field before submitting; what they submit is what
|
|
182
|
+
gets written.
|
|
183
|
+
|
|
184
|
+
Edits the record only. Channel access is unaffected: use
|
|
185
|
+
'contacts channels update-status' to revoke or block a channel.
|
|
186
|
+
|
|
187
|
+
${FORM_NOTE}
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
$ assistant contacts update 7a3b1c2d-4e5f-6789-abcd-ef0123456789 --name "Alice Chen"
|
|
191
|
+
$ assistant contacts update abc-123 --notes "Moved to Berlin"`,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "delete",
|
|
195
|
+
args: "<id>",
|
|
196
|
+
description: "Propose deleting a contact for the guardian to confirm",
|
|
197
|
+
options: [
|
|
198
|
+
{
|
|
199
|
+
flags: "--label <label>",
|
|
200
|
+
description: "Display label shown on the confirmation",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
flags: "--description <description>",
|
|
204
|
+
description: "Longer description shown on the confirmation",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
flags: "--timeout <ms>",
|
|
208
|
+
description:
|
|
209
|
+
"How long the confirmation stays open (ms). The command waits for it to close.",
|
|
210
|
+
defaultValue: String(300_000),
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
helpText: `
|
|
214
|
+
Arguments:
|
|
215
|
+
id UUID of the contact to delete. Run 'assistant contacts list' to find IDs.
|
|
216
|
+
|
|
217
|
+
Opens a confirmation in the guardian's app showing the contact and its
|
|
218
|
+
channels. Nothing is deleted unless they confirm.
|
|
219
|
+
|
|
220
|
+
Deleting a contact deletes its channels with it, so anyone reaching the
|
|
221
|
+
assistant through those channels loses access. A guardian contact cannot be
|
|
222
|
+
deleted. To merge a duplicate instead of deleting it, use the contact_merge
|
|
223
|
+
tool, which keeps the surviving contact's channels.
|
|
224
|
+
|
|
225
|
+
${FORM_NOTE}
|
|
226
|
+
|
|
227
|
+
Examples:
|
|
228
|
+
$ assistant contacts delete 7a3b1c2d-4e5f-6789-abcd-ef0123456789
|
|
229
|
+
$ assistant contacts delete abc-123 --json`,
|
|
86
230
|
},
|
|
87
231
|
{
|
|
88
232
|
name: "prompt",
|
|
89
|
-
description:
|
|
233
|
+
description:
|
|
234
|
+
"Ask the guardian to enter a channel address and bind it to a contact",
|
|
90
235
|
options: [
|
|
91
236
|
{
|
|
92
237
|
flags: "--channel <channel>",
|
|
@@ -118,27 +263,44 @@ Examples:
|
|
|
118
263
|
{
|
|
119
264
|
flags: "--timeout <ms>",
|
|
120
265
|
description:
|
|
121
|
-
"How long
|
|
122
|
-
defaultValue: String(
|
|
266
|
+
"How long the form stays open (ms). The command waits for it to close.",
|
|
267
|
+
defaultValue: String(300_000),
|
|
123
268
|
},
|
|
124
269
|
{
|
|
125
270
|
flags: "--verify",
|
|
126
271
|
description:
|
|
127
|
-
"
|
|
272
|
+
"Pre-check the form's 'mark verified' box. The guardian decides: unchecked, the channel stays unverified.",
|
|
128
273
|
},
|
|
129
274
|
],
|
|
130
275
|
helpText: `
|
|
131
|
-
Opens
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
276
|
+
Opens an address form in the guardian's app. They enter a channel address
|
|
277
|
+
(phone number, email, Telegram ID, etc.) and it is bound as a channel.
|
|
278
|
+
|
|
279
|
+
Which contact it binds to depends on --role:
|
|
280
|
+
|
|
281
|
+
--role guardian Binds to the guardian contact. An address already bound
|
|
282
|
+
to a different contact is rejected as a conflict.
|
|
283
|
+
anything else Looks up the submitted (channel type, address). A match
|
|
284
|
+
reuses that contact and channel; no match creates a new
|
|
285
|
+
contact named after the address. Other --role values are
|
|
286
|
+
form hints only: the contact is created with role
|
|
287
|
+
"contact" either way.
|
|
288
|
+
|
|
289
|
+
This cannot add a channel to an arbitrary existing contact. To name a contact
|
|
290
|
+
first, run 'contacts create'; to let someone bind their own address, use
|
|
291
|
+
'contacts invites create'.
|
|
292
|
+
|
|
293
|
+
The channel is saved unverified unless the guardian leaves the form's "mark
|
|
294
|
+
verified" box checked, which attests it the same way Contacts Verify me does.
|
|
295
|
+
An attested channel is admitted, so --verify only proposes the box state.
|
|
296
|
+
|
|
297
|
+
${FORM_NOTE}
|
|
135
298
|
|
|
136
299
|
Examples:
|
|
137
300
|
$ assistant contacts prompt --channel phone --role guardian
|
|
301
|
+
$ assistant contacts prompt --channel email --label "Alice's email address"
|
|
138
302
|
$ assistant contacts prompt --channel imessage --role guardian --verify \\
|
|
139
|
-
--label "Your iMessage number" --placeholder "+
|
|
140
|
-
|
|
141
|
-
Run \`assistant contacts prompt --help\` for full option details.`,
|
|
303
|
+
--label "Your iMessage number" --placeholder "+15555550142"`,
|
|
142
304
|
},
|
|
143
305
|
{
|
|
144
306
|
name: "channels",
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
|
|
3
3
|
import { cliIpcCall, exitFromIpcResult } from "../../ipc/cli-client.js";
|
|
4
|
+
import {
|
|
5
|
+
GUARDIAN_FORM_DEFAULT_TIMEOUT_MS,
|
|
6
|
+
GUARDIAN_FORM_MAX_TIMEOUT_MS,
|
|
7
|
+
guardianFormCallBudgetMs,
|
|
8
|
+
} from "../../util/guardian-form-timeouts.js";
|
|
4
9
|
import { applyCommandHelp, subcommand } from "../lib/cli-command-help.js";
|
|
5
10
|
import { registerCommand } from "../lib/register-command.js";
|
|
6
11
|
import { shouldOutputJson, writeError, writeOutput } from "../output.js";
|
|
@@ -30,7 +35,9 @@ interface ContactWithChannels {
|
|
|
30
35
|
displayName: string;
|
|
31
36
|
role?: string;
|
|
32
37
|
contactType: string;
|
|
33
|
-
|
|
38
|
+
// Nullable on the wire (`contactSchema`, and the gateway's ContactRead):
|
|
39
|
+
// a contact with no notes reads as null, not as an absent field.
|
|
40
|
+
notes?: string | null;
|
|
34
41
|
principalId?: string;
|
|
35
42
|
createdAt: string | number;
|
|
36
43
|
updatedAt: string | number;
|
|
@@ -51,6 +58,8 @@ interface ContactPromptResult {
|
|
|
51
58
|
address?: string;
|
|
52
59
|
channelId?: string;
|
|
53
60
|
contactId?: string;
|
|
61
|
+
/** Whether the channel is attested, as the guardian's checkbox left it. */
|
|
62
|
+
verified?: boolean;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
// ---------------------------------------------------------------------------
|
|
@@ -156,6 +165,213 @@ function formatContactDetail(
|
|
|
156
165
|
return lines.join("\n");
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Parse `--timeout`, which bounds how long the form stays open. Returns null
|
|
170
|
+
* (after reporting) when the value is out of range, so a bad number fails here
|
|
171
|
+
* rather than as a schema rejection after the round trip.
|
|
172
|
+
*/
|
|
173
|
+
function parseFormTimeout(
|
|
174
|
+
raw: string | undefined,
|
|
175
|
+
cmd: Command,
|
|
176
|
+
): number | null {
|
|
177
|
+
if (raw === undefined) {
|
|
178
|
+
return GUARDIAN_FORM_DEFAULT_TIMEOUT_MS;
|
|
179
|
+
}
|
|
180
|
+
// The whole token has to be digits: parseInt takes a valid prefix, so
|
|
181
|
+
// "1e3" would pass as 1 and "100.5" as 100, quietly giving a form a
|
|
182
|
+
// fraction of the wait it was asked for.
|
|
183
|
+
const parsed = /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : Number.NaN;
|
|
184
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
185
|
+
writeError(
|
|
186
|
+
cmd,
|
|
187
|
+
`Invalid --timeout "${raw}": expected a positive whole number of milliseconds`,
|
|
188
|
+
);
|
|
189
|
+
process.exitCode = 1;
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (parsed > GUARDIAN_FORM_MAX_TIMEOUT_MS) {
|
|
193
|
+
writeError(
|
|
194
|
+
cmd,
|
|
195
|
+
`Invalid --timeout "${raw}": the longest a form stays open is ${GUARDIAN_FORM_MAX_TIMEOUT_MS}ms`,
|
|
196
|
+
);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return parsed;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface ContactRecordPromptBody {
|
|
204
|
+
operation: "create" | "update" | "delete";
|
|
205
|
+
contactId?: string;
|
|
206
|
+
currentDisplayName?: string;
|
|
207
|
+
currentNotes?: string;
|
|
208
|
+
channels?: Array<{ type: string; address: string }>;
|
|
209
|
+
displayName?: string;
|
|
210
|
+
notes?: string;
|
|
211
|
+
notesProposed?: boolean;
|
|
212
|
+
label?: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Read a contact before proposing a write against it, so a bad id fails here
|
|
218
|
+
* rather than after the guardian has answered a form about the wrong person.
|
|
219
|
+
* The read is the gateway-relayed one, so it reflects the source of truth.
|
|
220
|
+
*/
|
|
221
|
+
async function readContactForPrompt(
|
|
222
|
+
id: string,
|
|
223
|
+
cmd: Command,
|
|
224
|
+
/**
|
|
225
|
+
* Whether a contact the read cannot see should still reach the form. A
|
|
226
|
+
* dual-write gap can leave a contact in the assistant mirror that this
|
|
227
|
+
* gateway-backed read misses, and deleting one of those is supported, so a
|
|
228
|
+
* delete carries on with a bare record rather than refusing an id the
|
|
229
|
+
* guardian can see. An update cannot: it needs the stored values to know
|
|
230
|
+
* what the guardian actually changed.
|
|
231
|
+
*/
|
|
232
|
+
allowUnreadable = false,
|
|
233
|
+
): Promise<ContactWithChannels | null> {
|
|
234
|
+
const r = await cliIpcCall<{ ok: boolean; contact: ContactWithChannels }>(
|
|
235
|
+
"getContact",
|
|
236
|
+
{ pathParams: { id } },
|
|
237
|
+
);
|
|
238
|
+
if (r.ok) {
|
|
239
|
+
return r.result!.contact;
|
|
240
|
+
}
|
|
241
|
+
if (allowUnreadable && r.statusCode === 404) {
|
|
242
|
+
return {
|
|
243
|
+
id,
|
|
244
|
+
displayName: id,
|
|
245
|
+
contactType: "human",
|
|
246
|
+
createdAt: 0,
|
|
247
|
+
updatedAt: 0,
|
|
248
|
+
interactionCount: null,
|
|
249
|
+
channels: [],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
exitFromIpcResult(
|
|
254
|
+
r as { ok: false; error?: string; statusCode?: number },
|
|
255
|
+
cmd,
|
|
256
|
+
);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Park on the guardian's answer to a contact-record form, then report what was
|
|
262
|
+
* actually written. The submitted values can differ from the proposed ones, so
|
|
263
|
+
* the result is re-read rather than echoed back from the request.
|
|
264
|
+
*/
|
|
265
|
+
async function runRecordPrompt(
|
|
266
|
+
body: ContactRecordPromptBody,
|
|
267
|
+
timeout: string | undefined,
|
|
268
|
+
cmd: Command,
|
|
269
|
+
): Promise<void> {
|
|
270
|
+
const timeoutMs = parseFormTimeout(timeout, cmd);
|
|
271
|
+
if (timeoutMs === null) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const r = await cliIpcCall<{
|
|
275
|
+
ok: boolean;
|
|
276
|
+
error?: string;
|
|
277
|
+
contactId?: string;
|
|
278
|
+
notesSaved?: boolean;
|
|
279
|
+
nothingWritten?: boolean;
|
|
280
|
+
}>(
|
|
281
|
+
"contacts_record_prompt",
|
|
282
|
+
{ body: { ...body, timeoutMs } },
|
|
283
|
+
{ timeoutMs: guardianFormCallBudgetMs(timeoutMs) },
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (!r.ok) {
|
|
287
|
+
return exitFromIpcResult(
|
|
288
|
+
r as { ok: false; error?: string; statusCode?: number },
|
|
289
|
+
cmd,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!r.result?.ok) {
|
|
294
|
+
writeError(cmd, r.result?.error ?? "Contact form failed");
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const contactId = r.result.contactId;
|
|
300
|
+
// Notes are stored apart from the rest of the record, so they can be the one
|
|
301
|
+
// part that does not land. Whether that leaves nothing behind is the
|
|
302
|
+
// gateway's to say: the guardian submits only the fields they changed, so
|
|
303
|
+
// what this command proposed is no guide to what was written.
|
|
304
|
+
const notesLost = r.result.notesSaved === false;
|
|
305
|
+
const nothingWritten = r.result.nothingWritten === true;
|
|
306
|
+
|
|
307
|
+
if (body.operation === "delete") {
|
|
308
|
+
const deleted = body.currentDisplayName ?? contactId ?? body.contactId;
|
|
309
|
+
if (shouldOutputJson(cmd)) {
|
|
310
|
+
writeOutput(cmd, { ok: true, deleted: true, contactId });
|
|
311
|
+
} else {
|
|
312
|
+
process.stdout.write(`Deleted contact: ${deleted} (${body.contactId})\n`);
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!contactId) {
|
|
318
|
+
writeError(cmd, "Contact form returned no contact id");
|
|
319
|
+
process.exitCode = 1;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const verb = body.operation === "create" ? "Created" : "Updated";
|
|
324
|
+
|
|
325
|
+
// The write is already done and the guardian has already answered. This read
|
|
326
|
+
// is only for what to print, so a failure here reports the write with less
|
|
327
|
+
// detail rather than reporting the command as failed: an exit code that says
|
|
328
|
+
// otherwise invites a retry, and retrying a create makes a second contact.
|
|
329
|
+
const read = await cliIpcCall<{ ok: boolean; contact: ContactWithChannels }>(
|
|
330
|
+
"getContact",
|
|
331
|
+
{ pathParams: { id: contactId } },
|
|
332
|
+
);
|
|
333
|
+
const written = read.ok ? read.result?.contact : undefined;
|
|
334
|
+
|
|
335
|
+
if (nothingWritten) {
|
|
336
|
+
// Nothing the caller asked for landed, so this is a failure however it is
|
|
337
|
+
// read: one object, and a nonzero exit.
|
|
338
|
+
if (shouldOutputJson(cmd)) {
|
|
339
|
+
writeOutput(cmd, {
|
|
340
|
+
ok: false,
|
|
341
|
+
error: "The contact's notes could not be saved",
|
|
342
|
+
contactId,
|
|
343
|
+
notesSaved: false,
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
writeError(cmd, "The contact's notes could not be saved");
|
|
347
|
+
}
|
|
348
|
+
process.exitCode = 1;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (shouldOutputJson(cmd)) {
|
|
353
|
+
// One object, whatever happened: a second one saying otherwise reads as a
|
|
354
|
+
// failure to anything parsing this, and a retried create makes a duplicate.
|
|
355
|
+
writeOutput(cmd, {
|
|
356
|
+
ok: true,
|
|
357
|
+
...(written ? { contact: written } : { contactId }),
|
|
358
|
+
...(notesLost ? { notesSaved: false } : {}),
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (written) {
|
|
364
|
+
process.stdout.write(`${verb} contact:\n`);
|
|
365
|
+
process.stdout.write(formatContactDetail(written) + "\n");
|
|
366
|
+
} else {
|
|
367
|
+
process.stdout.write(`${verb} contact: ${contactId}\n`);
|
|
368
|
+
writeError(cmd, "Could not read the contact back to display it");
|
|
369
|
+
}
|
|
370
|
+
if (notesLost) {
|
|
371
|
+
writeError(cmd, "The contact was saved, but its notes were not");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
159
375
|
export function registerContactsCommand(program: Command): void {
|
|
160
376
|
registerCommand(program, {
|
|
161
377
|
name: contactsHelp.name,
|
|
@@ -250,6 +466,123 @@ export function registerContactsCommand(program: Command): void {
|
|
|
250
466
|
},
|
|
251
467
|
);
|
|
252
468
|
|
|
469
|
+
// -----------------------------------------------------------------------
|
|
470
|
+
// create / update / delete
|
|
471
|
+
//
|
|
472
|
+
// Each opens a form in the guardian's app and blocks on their answer.
|
|
473
|
+
// The daemon writes nothing: the guardian's client posts the confirmed
|
|
474
|
+
// record straight to the gateway, so an unattended assistant cannot
|
|
475
|
+
// change the contact graph.
|
|
476
|
+
// -----------------------------------------------------------------------
|
|
477
|
+
|
|
478
|
+
subcommand(contacts, "create").action(
|
|
479
|
+
async (
|
|
480
|
+
opts: {
|
|
481
|
+
name?: string;
|
|
482
|
+
notes?: string;
|
|
483
|
+
label?: string;
|
|
484
|
+
description?: string;
|
|
485
|
+
timeout?: string;
|
|
486
|
+
},
|
|
487
|
+
cmd: Command,
|
|
488
|
+
) => {
|
|
489
|
+
await runRecordPrompt(
|
|
490
|
+
{
|
|
491
|
+
operation: "create",
|
|
492
|
+
displayName: opts.name,
|
|
493
|
+
notes: opts.notes,
|
|
494
|
+
label: opts.label,
|
|
495
|
+
description: opts.description,
|
|
496
|
+
},
|
|
497
|
+
opts.timeout,
|
|
498
|
+
cmd,
|
|
499
|
+
);
|
|
500
|
+
},
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
subcommand(contacts, "update").action(
|
|
504
|
+
async (
|
|
505
|
+
id: string,
|
|
506
|
+
opts: {
|
|
507
|
+
name?: string;
|
|
508
|
+
notes?: string;
|
|
509
|
+
label?: string;
|
|
510
|
+
description?: string;
|
|
511
|
+
timeout?: string;
|
|
512
|
+
},
|
|
513
|
+
cmd: Command,
|
|
514
|
+
) => {
|
|
515
|
+
if (!opts.name && opts.notes === undefined) {
|
|
516
|
+
writeError(
|
|
517
|
+
cmd,
|
|
518
|
+
"At least one of --name or --notes must be provided",
|
|
519
|
+
);
|
|
520
|
+
process.exitCode = 1;
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const current = await readContactForPrompt(id, cmd);
|
|
524
|
+
if (!current) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
await runRecordPrompt(
|
|
528
|
+
{
|
|
529
|
+
operation: "update",
|
|
530
|
+
contactId: id,
|
|
531
|
+
currentDisplayName: current.displayName,
|
|
532
|
+
// What is stored, so the form can tell an accepted proposal from
|
|
533
|
+
// a field the guardian left alone.
|
|
534
|
+
currentNotes: current.notes ?? undefined,
|
|
535
|
+
displayName: opts.name,
|
|
536
|
+
// Seed the form's notes field with what the contact already has,
|
|
537
|
+
// so a name-only edit does not show an empty box the guardian
|
|
538
|
+
// then submits over their notes. `??` keeps an explicit
|
|
539
|
+
// `--notes ""` as the deliberate clear it is, and collapses the
|
|
540
|
+
// null a notes-less contact reads as into an omitted field.
|
|
541
|
+
notes: opts.notes ?? current.notes ?? undefined,
|
|
542
|
+
// An explicit --notes is a change the guardian confirms, even
|
|
543
|
+
// when it matches what is stored: comparison alone cannot tell
|
|
544
|
+
// that from stored notes the read could not see.
|
|
545
|
+
notesProposed: opts.notes !== undefined,
|
|
546
|
+
label: opts.label,
|
|
547
|
+
description: opts.description,
|
|
548
|
+
},
|
|
549
|
+
opts.timeout,
|
|
550
|
+
cmd,
|
|
551
|
+
);
|
|
552
|
+
},
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
subcommand(contacts, "delete").action(
|
|
556
|
+
async (
|
|
557
|
+
id: string,
|
|
558
|
+
opts: { label?: string; description?: string; timeout?: string },
|
|
559
|
+
cmd: Command,
|
|
560
|
+
) => {
|
|
561
|
+
const current = await readContactForPrompt(id, cmd, true);
|
|
562
|
+
if (!current) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
await runRecordPrompt(
|
|
566
|
+
{
|
|
567
|
+
operation: "delete",
|
|
568
|
+
contactId: id,
|
|
569
|
+
currentDisplayName: current.displayName,
|
|
570
|
+
// Two contacts can share a name, and deleting one takes its
|
|
571
|
+
// channels with it. The confirmation has to say which contact
|
|
572
|
+
// this is and what access is about to be lost.
|
|
573
|
+
channels: current.channels.map((ch) => ({
|
|
574
|
+
type: ch.type,
|
|
575
|
+
address: ch.address,
|
|
576
|
+
})),
|
|
577
|
+
label: opts.label,
|
|
578
|
+
description: opts.description,
|
|
579
|
+
},
|
|
580
|
+
opts.timeout,
|
|
581
|
+
cmd,
|
|
582
|
+
);
|
|
583
|
+
},
|
|
584
|
+
);
|
|
585
|
+
|
|
253
586
|
// -----------------------------------------------------------------------
|
|
254
587
|
// prompt
|
|
255
588
|
// -----------------------------------------------------------------------
|
|
@@ -268,7 +601,10 @@ export function registerContactsCommand(program: Command): void {
|
|
|
268
601
|
},
|
|
269
602
|
cmd: Command,
|
|
270
603
|
) => {
|
|
271
|
-
const timeoutMs =
|
|
604
|
+
const timeoutMs = parseFormTimeout(opts.timeout, cmd);
|
|
605
|
+
if (timeoutMs === null) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
272
608
|
const r = await cliIpcCall<ContactPromptResult>(
|
|
273
609
|
"contacts_prompt",
|
|
274
610
|
{
|
|
@@ -280,9 +616,10 @@ export function registerContactsCommand(program: Command): void {
|
|
|
280
616
|
label: opts.label,
|
|
281
617
|
description: opts.description,
|
|
282
618
|
verify: opts.verify === true,
|
|
619
|
+
timeoutMs,
|
|
283
620
|
},
|
|
284
621
|
},
|
|
285
|
-
{ timeoutMs },
|
|
622
|
+
{ timeoutMs: guardianFormCallBudgetMs(timeoutMs) },
|
|
286
623
|
);
|
|
287
624
|
|
|
288
625
|
if (!r.ok) {
|
|
@@ -306,7 +643,9 @@ export function registerContactsCommand(program: Command): void {
|
|
|
306
643
|
`Registered ${result.channelType} channel: ${result.address}\n` +
|
|
307
644
|
` Channel ID: ${result.channelId}\n` +
|
|
308
645
|
` Contact ID: ${result.contactId}\n` +
|
|
309
|
-
|
|
646
|
+
// The guardian's checkbox decides this, so report what the
|
|
647
|
+
// channel is rather than what the flag asked for.
|
|
648
|
+
` Status: ${result.verified ? "verified" : "unverified"}\n`,
|
|
310
649
|
);
|
|
311
650
|
}
|
|
312
651
|
},
|
|
@@ -778,8 +778,10 @@ form for any service other than the provider's default.
|
|
|
778
778
|
|
|
779
779
|
The body (-d) is JSON by default. Give a non-JSON Content-Type and the body
|
|
780
780
|
travels to the provider exactly as written, which is what multipart, XML, and
|
|
781
|
-
form-encoded payloads need. Body files (-d @<path>)
|
|
782
|
-
|
|
781
|
+
form-encoded payloads need. Body files (-d @<path>) and stdin (-d @-) are
|
|
782
|
+
read as raw bytes. Valid UTF-8 stays text (or JSON when the Content-Type is
|
|
783
|
+
JSON). Anything else, including PDFs and other binary files, is sent to the
|
|
784
|
+
provider byte for byte.
|
|
783
785
|
|
|
784
786
|
Note: The Authorization header is set automatically. User-supplied
|
|
785
787
|
-H "Authorization: ..." will be overridden by the OAuth bearer token.
|
|
@@ -790,7 +792,8 @@ Examples:
|
|
|
790
792
|
$ assistant oauth request --provider twitter -X POST -d '{"text":"Hello"}' https://api.x.com/2/tweets
|
|
791
793
|
$ assistant oauth request --provider google https://www.googleapis.com/calendar/v3/calendars/primary/events
|
|
792
794
|
$ assistant oauth request --provider slack -H "Content-Type: application/json" -d '{"channel":"C123"}' /api/chat.postMessage --json
|
|
793
|
-
$ assistant oauth request --provider google -X POST -H "Content-Type: multipart/related; boundary=b" -d @upload.txt "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
|
|
795
|
+
$ assistant oauth request --provider google -X POST -H "Content-Type: multipart/related; boundary=b" -d @upload.txt "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
|
|
796
|
+
$ assistant oauth request --provider google -X POST -H "Content-Type: application/pdf" -d @report.pdf "https://www.googleapis.com/upload/drive/v3/files?uploadType=media"`,
|
|
794
797
|
},
|
|
795
798
|
{
|
|
796
799
|
name: "disconnect",
|