@vellumai/assistant 0.9.1-staging.1 → 0.10.0-staging.2
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/docs/activation-funnel-telemetry.md +24 -18
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
- package/openapi.yaml +852 -15
- package/package.json +1 -1
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
- package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +174 -30
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-prompt-route.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -0
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +51 -0
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +30 -1
- package/src/__tests__/llm-resolver.test.ts +21 -0
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +38 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +5 -0
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +150 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-verification.test.ts +2 -4
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +0 -1
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +33 -33
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +26 -0
- package/src/api/responses/home.ts +26 -0
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +181 -78
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +57 -5
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/merge-plugin-tree.ts +149 -49
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +64 -36
- package/src/cli/program.ts +2 -4
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/workflows/SKILL.md +14 -7
- package/src/config/call-site-defaults.ts +3 -0
- package/src/config/feature-flag-registry.json +49 -18
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +94 -34
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +2 -10
- package/src/contacts/contacts-write.ts +1 -2
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +86 -52
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +100 -19
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +16 -11
- package/src/daemon/conversation.ts +64 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +5 -10
- package/src/daemon/handlers/config-slack-channel.ts +20 -0
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -20
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +10 -0
- package/src/memory/embedding-backend.ts +15 -1
- package/src/memory/jobs-worker.ts +2 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +9 -0
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -1
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/slack.ts +55 -73
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +6 -2
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +3 -13
- package/src/notifications/notification-utils.ts +2 -1
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -128
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/index.ts +27 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +35 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +12 -2
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +49 -21
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +5 -1
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +1 -2
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +6 -22
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +37 -12
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +40 -7
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +225 -1
- package/src/runtime/routes/workflow-routes.ts +131 -1
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +11 -4
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +29 -18
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +3 -2
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +4 -3
- package/src/tools/schedule/update.ts +2 -1
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/skills/execute.ts +1 -2
- package/src/tools/subagent/spawn.ts +37 -13
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +17 -10
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +25 -2
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +8 -1
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -3
- package/src/workflows/run-manager.ts +64 -0
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for {@link detectPluginSurfaces}.
|
|
3
|
+
*
|
|
4
|
+
* Surface detection is a pure walk of an installed plugin's on-disk tree, so
|
|
5
|
+
* the fixtures materialize the `hooks/`, `tools/`, and `skills/` directory
|
|
6
|
+
* conventions in a real temp dir and assert the derived listing matches what
|
|
7
|
+
* the runtime loader / skills catalog would discover.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import { detectPluginSurfaces } from "../plugin-surfaces.js";
|
|
16
|
+
|
|
17
|
+
let pluginDir: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
pluginDir = mkdtempSync(join(tmpdir(), "plugin-surfaces-"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
rmSync(pluginDir, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/** Create `<pluginDir>/<rel>` with empty contents, making parents as needed. */
|
|
28
|
+
function touch(rel: string): void {
|
|
29
|
+
const path = join(pluginDir, rel);
|
|
30
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
31
|
+
writeFileSync(path, "");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("detectPluginSurfaces", () => {
|
|
35
|
+
test("lists hooks, tools, and skills from the directory conventions", () => {
|
|
36
|
+
// GIVEN a plugin shipping two hooks, one tool, and two skills
|
|
37
|
+
touch("hooks/post-model-call.ts");
|
|
38
|
+
touch("hooks/init.ts");
|
|
39
|
+
touch("tools/summarize.ts");
|
|
40
|
+
touch("skills/first-skill/SKILL.md");
|
|
41
|
+
touch("skills/second-skill/SKILL.md");
|
|
42
|
+
|
|
43
|
+
// WHEN its surfaces are detected
|
|
44
|
+
const surfaces = detectPluginSurfaces(pluginDir);
|
|
45
|
+
|
|
46
|
+
// THEN each surface type lists its items, sorted
|
|
47
|
+
expect(surfaces.hooks).toEqual(["init", "post-model-call"]);
|
|
48
|
+
expect(surfaces.tools).toEqual(["summarize"]);
|
|
49
|
+
expect(surfaces.skills).toEqual(["first-skill", "second-skill"]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("reports tools under their registered name, not the raw filename", () => {
|
|
53
|
+
// GIVEN a tool whose filename is not already a valid tool-name segment
|
|
54
|
+
touch("tools/create-issue.ts");
|
|
55
|
+
|
|
56
|
+
// WHEN its surfaces are detected
|
|
57
|
+
const surfaces = detectPluginSurfaces(pluginDir);
|
|
58
|
+
|
|
59
|
+
// THEN the derived (registered) name is reported, matching the loader
|
|
60
|
+
expect(surfaces.tools).toEqual(["create_issue"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("omits surface types the plugin does not contribute", () => {
|
|
64
|
+
// GIVEN a plugin that ships only a hook
|
|
65
|
+
touch("hooks/post-model-call.ts");
|
|
66
|
+
|
|
67
|
+
// WHEN its surfaces are detected
|
|
68
|
+
const surfaces = detectPluginSurfaces(pluginDir);
|
|
69
|
+
|
|
70
|
+
// THEN the contributed type is listed and the others are empty
|
|
71
|
+
expect(surfaces.hooks).toEqual(["post-model-call"]);
|
|
72
|
+
expect(surfaces.tools).toEqual([]);
|
|
73
|
+
expect(surfaces.skills).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("prefers .js over .ts for the same basename and skips .d.ts declarations", () => {
|
|
77
|
+
// GIVEN a compiled hook shipping both .ts and .js plus a .d.ts declaration
|
|
78
|
+
touch("hooks/post-model-call.ts");
|
|
79
|
+
touch("hooks/post-model-call.js");
|
|
80
|
+
touch("hooks/post-model-call.d.ts");
|
|
81
|
+
|
|
82
|
+
// WHEN its surfaces are detected
|
|
83
|
+
const surfaces = detectPluginSurfaces(pluginDir);
|
|
84
|
+
|
|
85
|
+
// THEN the basename appears once and the declaration file is ignored
|
|
86
|
+
expect(surfaces.hooks).toEqual(["post-model-call"]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("ignores skill directories without a SKILL.md", () => {
|
|
90
|
+
// GIVEN a skills dir with one real skill and one stray subdirectory
|
|
91
|
+
touch("skills/real-skill/SKILL.md");
|
|
92
|
+
touch("skills/not-a-skill/README.md");
|
|
93
|
+
|
|
94
|
+
// WHEN its surfaces are detected
|
|
95
|
+
const surfaces = detectPluginSurfaces(pluginDir);
|
|
96
|
+
|
|
97
|
+
// THEN only the directory carrying a SKILL.md is reported
|
|
98
|
+
expect(surfaces.skills).toEqual(["real-skill"]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("returns empty surfaces for a plugin with no surface directories", () => {
|
|
102
|
+
// GIVEN a plugin tree with only a package.json
|
|
103
|
+
touch("package.json");
|
|
104
|
+
|
|
105
|
+
// WHEN its surfaces are detected
|
|
106
|
+
const surfaces = detectPluginSurfaces(pluginDir);
|
|
107
|
+
|
|
108
|
+
// THEN every surface type is empty
|
|
109
|
+
expect(surfaces).toEqual({ skills: [], hooks: [], tools: [] });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -31,7 +31,6 @@ import { PluginNotInstalledError } from "../uninstall-plugin.js";
|
|
|
31
31
|
import {
|
|
32
32
|
PluginMergeBaselineError,
|
|
33
33
|
PluginNotUpgradableError,
|
|
34
|
-
PluginUpgradeStrategyUnsupportedError,
|
|
35
34
|
upgradePlugin,
|
|
36
35
|
} from "../upgrade-plugin.js";
|
|
37
36
|
|
|
@@ -582,19 +581,62 @@ describe("upgradePlugin --strategy", () => {
|
|
|
582
581
|
expect(installedFile("level-up", "local-only.txt")).toBeNull();
|
|
583
582
|
});
|
|
584
583
|
|
|
585
|
-
test("
|
|
586
|
-
// GIVEN an
|
|
584
|
+
test("--strategy assistant writes conflict markers and reports the conflicted path", async () => {
|
|
585
|
+
// GIVEN an install at SHA_A with local edits, and a pin at SHA_B
|
|
587
586
|
installMergeCopy("level-up", OURS, SHA_A, BASE);
|
|
588
587
|
const fetch = makeFetch({ manifest: manifestWith("level-up", SHA_B) });
|
|
588
|
+
const runGit = treeGitRunner({ [SHA_A]: BASE, [SHA_B]: THEIRS });
|
|
589
589
|
|
|
590
|
-
// WHEN
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
).
|
|
590
|
+
// WHEN the plugin is upgraded with the `assistant` strategy
|
|
591
|
+
const result = await upgradePlugin(
|
|
592
|
+
{ name: "level-up", strategy: "assistant" },
|
|
593
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
// THEN it moves to the pin and records the strategy
|
|
597
|
+
expect(result.outcome).toBe("upgraded");
|
|
598
|
+
expect(result.toCommit).toBe(SHA_B);
|
|
599
|
+
expect(result.strategy).toBe("assistant");
|
|
600
|
+
// AND non-conflicting edits from both sides still auto-merge
|
|
601
|
+
expect(installedFile("level-up", "common.txt")).toBe("A\nb\nC\n");
|
|
602
|
+
expect(installedFile("level-up", "local-only.txt")).toBe("added locally\n");
|
|
603
|
+
expect(installedFile("level-up", "remote-only.txt")).toBe(
|
|
604
|
+
"added upstream\n",
|
|
605
|
+
);
|
|
606
|
+
// AND the true conflict carries git markers naming both commits
|
|
607
|
+
const conflict = installedFile("level-up", "conflict.txt") ?? "";
|
|
608
|
+
expect(conflict).toContain("<<<<<<<");
|
|
609
|
+
expect(conflict).toContain(SHA_A.slice(0, 7));
|
|
610
|
+
expect(conflict).toContain(SHA_B.slice(0, 7));
|
|
611
|
+
expect(conflict).toContain("ours\n");
|
|
612
|
+
expect(conflict).toContain("theirs\n");
|
|
613
|
+
// AND the conflicted path is surfaced for the assistant to resolve
|
|
614
|
+
expect(result.conflicts).toEqual(["conflict.txt"]);
|
|
615
|
+
expect(result.binaryConflicts).toEqual([]);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("--strategy assistant reports no conflicts on a clean three-way merge", async () => {
|
|
619
|
+
// GIVEN an install whose only divergence from the pin auto-merges cleanly
|
|
620
|
+
const cleanOurs: Tree = { "common.txt": "A\nb\nc\n" };
|
|
621
|
+
const cleanBase: Tree = { "common.txt": "a\nb\nc\n" };
|
|
622
|
+
const cleanTheirs: Tree = { "common.txt": "a\nb\nC\n" };
|
|
623
|
+
installMergeCopy("level-up", cleanOurs, SHA_A, cleanBase);
|
|
624
|
+
const fetch = makeFetch({ manifest: manifestWith("level-up", SHA_B) });
|
|
625
|
+
const runGit = treeGitRunner({ [SHA_A]: cleanBase, [SHA_B]: cleanTheirs });
|
|
626
|
+
|
|
627
|
+
// WHEN the plugin is upgraded with the `assistant` strategy
|
|
628
|
+
const result = await upgradePlugin(
|
|
629
|
+
{ name: "level-up", strategy: "assistant" },
|
|
630
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
// THEN both edits merge with no markers and nothing needs resolution
|
|
634
|
+
expect(result.strategy).toBe("assistant");
|
|
635
|
+
expect(result.conflicts).toEqual([]);
|
|
636
|
+
expect(result.binaryConflicts).toEqual([]);
|
|
637
|
+
const merged = installedFile("level-up", "common.txt") ?? "";
|
|
638
|
+
expect(merged).toBe("A\nb\nC\n");
|
|
639
|
+
expect(merged).not.toContain("<<<<<<<");
|
|
598
640
|
});
|
|
599
641
|
|
|
600
642
|
test("throws PluginMergeBaselineError when no fingerprint was recorded", async () => {
|
|
@@ -36,6 +36,10 @@ import {
|
|
|
36
36
|
fetchMarketplaceEntries,
|
|
37
37
|
type MarketplaceEntry,
|
|
38
38
|
} from "./plugin-marketplace.js";
|
|
39
|
+
import {
|
|
40
|
+
detectPluginSurfaces,
|
|
41
|
+
type PluginSurfaces,
|
|
42
|
+
} from "./plugin-surfaces.js";
|
|
39
43
|
|
|
40
44
|
/** Full commit SHA (40 hex SHA-1 or 64 hex SHA-256). */
|
|
41
45
|
const FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
|
|
@@ -131,6 +135,12 @@ export interface PluginInspection {
|
|
|
131
135
|
readonly remote: PluginRemoteInfo | null;
|
|
132
136
|
/** Marketplace fetch error message, when the catalog could not be read. */
|
|
133
137
|
readonly remoteError: string | null;
|
|
138
|
+
/**
|
|
139
|
+
* Surfaces the installed copy contributes (skills, hooks, tools), read from
|
|
140
|
+
* its on-disk tree. `null` when the plugin is not installed — there is no
|
|
141
|
+
* tree to inspect, and the marketplace metadata does not enumerate surfaces.
|
|
142
|
+
*/
|
|
143
|
+
readonly surfaces: PluginSurfaces | null;
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
/** Neither an installed copy nor a marketplace entry claims the name. */
|
|
@@ -266,6 +276,7 @@ export async function inspectPlugin(
|
|
|
266
276
|
});
|
|
267
277
|
const installed = entry !== null;
|
|
268
278
|
const local = entry ? readLocal(entry, readInstallMeta(entry.target)) : null;
|
|
279
|
+
const surfaces = entry ? detectPluginSurfaces(entry.target) : null;
|
|
269
280
|
|
|
270
281
|
let remote: PluginRemoteInfo | null = null;
|
|
271
282
|
let remoteError: string | null = null;
|
|
@@ -294,7 +305,7 @@ export async function inspectPlugin(
|
|
|
294
305
|
}
|
|
295
306
|
|
|
296
307
|
const status = classify(installed, local, remote, remoteError);
|
|
297
|
-
return { name, installed, status, local, remote, remoteError };
|
|
308
|
+
return { name, installed, status, local, remote, remoteError, surfaces };
|
|
298
309
|
}
|
|
299
310
|
|
|
300
311
|
function classify(
|
|
@@ -11,18 +11,25 @@
|
|
|
11
11
|
* Per file the merge is delegated to `git merge-file`, the same line-level
|
|
12
12
|
* three-way algorithm git itself uses: a hunk edited on only one side is taken
|
|
13
13
|
* from that side, so non-conflicting edits from *both* sides survive. Only a
|
|
14
|
-
* hunk edited differently on both sides is a true conflict, resolved
|
|
15
|
-
* caller's strategy
|
|
14
|
+
* hunk edited differently on both sides is a true conflict, resolved per the
|
|
15
|
+
* caller's strategy:
|
|
16
|
+
* - `ours` keeps the local hunk (`git merge-file --ours`),
|
|
17
|
+
* - `theirs` keeps the pinned hunk (`git merge-file --theirs`),
|
|
18
|
+
* - `assistant` writes standard git conflict markers into the file (no resolve
|
|
19
|
+
* flag) and reports the path, leaving the conflict for the assistant to
|
|
20
|
+
* resolve — exactly the mid-merge working-tree UX.
|
|
21
|
+
*
|
|
16
22
|
* File-level add/delete divergence (a file added, removed, or modified on only
|
|
17
23
|
* one side) is resolved here before any line merge, since `git merge-file`
|
|
18
24
|
* operates on three existing blobs.
|
|
19
25
|
*
|
|
20
26
|
* Binary files cannot be line-merged, so a binary file that diverged on both
|
|
21
|
-
* sides is resolved whole-file by the strategy
|
|
22
|
-
*
|
|
27
|
+
* sides is resolved whole-file by the strategy (`ours`/`theirs`); under
|
|
28
|
+
* `assistant` the local copy is kept and the path is reported as a binary
|
|
29
|
+
* conflict, since markers cannot be written into binary content.
|
|
23
30
|
*
|
|
24
31
|
* The `overwrite` strategy never reaches here — it discards local edits and is
|
|
25
|
-
* a plain re-install at the pin.
|
|
32
|
+
* a plain re-install at the pin.
|
|
26
33
|
*/
|
|
27
34
|
|
|
28
35
|
import { execFile } from "node:child_process";
|
|
@@ -46,11 +53,25 @@ const execFileAsync = promisify(execFile);
|
|
|
46
53
|
const MERGE_TIMEOUT_MS = 30_000;
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
|
-
*
|
|
50
|
-
* auto-resolve
|
|
51
|
-
*
|
|
56
|
+
* How hunks edited differently on both sides are reconciled. `ours`/`theirs`
|
|
57
|
+
* auto-resolve toward that side; `assistant` writes conflict markers and
|
|
58
|
+
* reports the conflict for later resolution. `overwrite` is not a merge
|
|
59
|
+
* strategy — the caller re-installs the pin wholesale instead of merging.
|
|
52
60
|
*/
|
|
53
|
-
export type
|
|
61
|
+
export type PluginMergeStrategy = "ours" | "theirs" | "assistant";
|
|
62
|
+
|
|
63
|
+
/** Human-readable labels for the conflict markers `assistant` writes. */
|
|
64
|
+
export interface ConflictLabels {
|
|
65
|
+
readonly ours: string;
|
|
66
|
+
readonly base: string;
|
|
67
|
+
readonly theirs: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const DEFAULT_CONFLICT_LABELS: ConflictLabels = {
|
|
71
|
+
ours: "local edits",
|
|
72
|
+
base: "install baseline",
|
|
73
|
+
theirs: "upgrade pin",
|
|
74
|
+
};
|
|
54
75
|
|
|
55
76
|
/** Inputs for a three-way plugin-tree merge. */
|
|
56
77
|
export interface MergePluginTreeOptions {
|
|
@@ -63,7 +84,36 @@ export interface MergePluginTreeOptions {
|
|
|
63
84
|
/** Empty directory the merged tree is written into. */
|
|
64
85
|
readonly destDir: string;
|
|
65
86
|
/** How to resolve hunks edited differently on both sides. */
|
|
66
|
-
readonly strategy:
|
|
87
|
+
readonly strategy: PluginMergeStrategy;
|
|
88
|
+
/** Marker labels for the `assistant` strategy. Defaults are used when omitted. */
|
|
89
|
+
readonly conflictLabels?: ConflictLabels;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Outcome of a three-way plugin-tree merge. */
|
|
93
|
+
export interface PluginMergeResult {
|
|
94
|
+
/** Number of files written into the destination tree. */
|
|
95
|
+
readonly fileCount: number;
|
|
96
|
+
/**
|
|
97
|
+
* Paths (relative to the tree root) left for the assistant to resolve.
|
|
98
|
+
* Text files carry git conflict markers; modify/delete divergences keep the
|
|
99
|
+
* surviving content. Empty for `ours`/`theirs`, which auto-resolve.
|
|
100
|
+
*/
|
|
101
|
+
readonly conflicts: readonly string[];
|
|
102
|
+
/**
|
|
103
|
+
* Paths of binary files that diverged on both sides. The local copy is kept
|
|
104
|
+
* (markers cannot be written into binary content), so the assistant must
|
|
105
|
+
* choose a version rather than edit markers. Empty for `ours`/`theirs`.
|
|
106
|
+
*/
|
|
107
|
+
readonly binaryConflicts: readonly string[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Result of merging a single file. */
|
|
111
|
+
interface MergedFile {
|
|
112
|
+
readonly content: Buffer;
|
|
113
|
+
/** Conflict markers were written into `content` (text, `assistant` only). */
|
|
114
|
+
readonly conflicted: boolean;
|
|
115
|
+
/** A binary file conflicted; `content` is the kept local copy (`assistant`). */
|
|
116
|
+
readonly binaryConflicted: boolean;
|
|
67
117
|
}
|
|
68
118
|
|
|
69
119
|
/** A NUL byte in the leading bytes is the heuristic git uses to flag a blob as binary. */
|
|
@@ -83,23 +133,36 @@ function writeInto(destDir: string, rel: string, content: Buffer): void {
|
|
|
83
133
|
}
|
|
84
134
|
|
|
85
135
|
/**
|
|
86
|
-
* Line-merge three blobs with `git merge-file
|
|
87
|
-
*
|
|
136
|
+
* Line-merge three blobs with `git merge-file`. Non-conflicting hunks from both
|
|
137
|
+
* sides are always kept. `ours`/`theirs` auto-resolve conflicting hunks toward
|
|
138
|
+
* that side (no markers); `assistant` writes git conflict markers and flags the
|
|
139
|
+
* file as conflicted.
|
|
140
|
+
*
|
|
88
141
|
* Binary input cannot be line-merged: a side that matches the base did not
|
|
89
|
-
* change, so the other side's edit is taken
|
|
90
|
-
* sides is a true conflict resolved whole-file by `
|
|
91
|
-
*
|
|
142
|
+
* change, so the other side's edit is taken. A binary blob changed on *both*
|
|
143
|
+
* sides is a true conflict — resolved whole-file by `ours`/`theirs`, or kept as
|
|
144
|
+
* the local copy and flagged under `assistant` (no markers possible).
|
|
92
145
|
*/
|
|
93
146
|
async function threeWayMergeFile(
|
|
94
147
|
ours: Buffer,
|
|
95
148
|
base: Buffer,
|
|
96
149
|
theirs: Buffer,
|
|
97
|
-
strategy:
|
|
98
|
-
|
|
150
|
+
strategy: PluginMergeStrategy,
|
|
151
|
+
labels: ConflictLabels,
|
|
152
|
+
): Promise<MergedFile> {
|
|
99
153
|
if (isBinary(ours) || isBinary(base) || isBinary(theirs)) {
|
|
100
|
-
if (ours.equals(base))
|
|
101
|
-
|
|
102
|
-
|
|
154
|
+
if (ours.equals(base))
|
|
155
|
+
return { content: theirs, conflicted: false, binaryConflicted: false };
|
|
156
|
+
if (theirs.equals(base))
|
|
157
|
+
return { content: ours, conflicted: false, binaryConflicted: false };
|
|
158
|
+
if (strategy === "assistant") {
|
|
159
|
+
return { content: ours, conflicted: false, binaryConflicted: true };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
content: strategy === "ours" ? ours : theirs,
|
|
163
|
+
conflicted: false,
|
|
164
|
+
binaryConflicted: false,
|
|
165
|
+
};
|
|
103
166
|
}
|
|
104
167
|
|
|
105
168
|
const scratch = mkdtempSync(join(tmpdir(), "plugin-merge-file-"));
|
|
@@ -112,18 +175,16 @@ async function threeWayMergeFile(
|
|
|
112
175
|
writeFileSync(theirsPath, theirs);
|
|
113
176
|
|
|
114
177
|
// `-p` prints the merged result to stdout instead of editing `ours` in
|
|
115
|
-
// place
|
|
116
|
-
// side
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
theirsPath,
|
|
126
|
-
];
|
|
178
|
+
// place. `--ours`/`--theirs` auto-resolve conflicting hunks toward that
|
|
179
|
+
// side; with neither (the `assistant` strategy) git writes conflict
|
|
180
|
+
// markers, labelled via `-L` so the assistant can tell the sides apart.
|
|
181
|
+
const args = ["merge-file", "-p"];
|
|
182
|
+
if (strategy === "ours" || strategy === "theirs") {
|
|
183
|
+
args.push(`--${strategy}`);
|
|
184
|
+
} else {
|
|
185
|
+
args.push("-L", labels.ours, "-L", labels.base, "-L", labels.theirs);
|
|
186
|
+
}
|
|
187
|
+
args.push(oursPath, basePath, theirsPath);
|
|
127
188
|
try {
|
|
128
189
|
const { stdout } = await execFileAsync("git", args, {
|
|
129
190
|
cwd: scratch,
|
|
@@ -131,10 +192,31 @@ async function threeWayMergeFile(
|
|
|
131
192
|
timeout: MERGE_TIMEOUT_MS,
|
|
132
193
|
maxBuffer: 64 * 1024 * 1024,
|
|
133
194
|
});
|
|
134
|
-
return stdout;
|
|
195
|
+
return { content: stdout, conflicted: false, binaryConflicted: false };
|
|
135
196
|
} catch (err) {
|
|
136
|
-
|
|
137
|
-
|
|
197
|
+
// `git merge-file` exits with the (positive) conflict count when markers
|
|
198
|
+
// were left, still printing the full merged bytes to stdout — that is the
|
|
199
|
+
// only rejection we may install. A killed process (timeout) or a
|
|
200
|
+
// `maxBuffer` overflow also rejects here, but with truncated/partial
|
|
201
|
+
// stdout we must NOT install it, or a large conflicted file would be
|
|
202
|
+
// silently corrupted; surface those as errors. A resolving flag never
|
|
203
|
+
// leaves conflicts, so any non-zero exit there is likewise a real
|
|
204
|
+
// failure.
|
|
205
|
+
const e = err as {
|
|
206
|
+
stdout?: Buffer;
|
|
207
|
+
code?: unknown;
|
|
208
|
+
killed?: boolean;
|
|
209
|
+
signal?: string | null;
|
|
210
|
+
};
|
|
211
|
+
const isConflictExit =
|
|
212
|
+
strategy === "assistant" &&
|
|
213
|
+
e.killed !== true &&
|
|
214
|
+
e.signal == null &&
|
|
215
|
+
typeof e.code === "number" &&
|
|
216
|
+
e.code > 0;
|
|
217
|
+
if (isConflictExit && Buffer.isBuffer(e.stdout)) {
|
|
218
|
+
return { content: e.stdout, conflicted: true, binaryConflicted: false };
|
|
219
|
+
}
|
|
138
220
|
throw err;
|
|
139
221
|
}
|
|
140
222
|
} finally {
|
|
@@ -143,12 +225,14 @@ async function threeWayMergeFile(
|
|
|
143
225
|
}
|
|
144
226
|
|
|
145
227
|
/**
|
|
146
|
-
* Three-way merge `oursDir`/`theirsDir` against `baseDir` into `destDir
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
228
|
+
* Three-way merge `oursDir`/`theirsDir` against `baseDir` into `destDir` per
|
|
229
|
+
* `strategy`. The provenance sidecar is excluded on every side — it is
|
|
230
|
+
* rewritten by the caller after the swap and must not be carried through the
|
|
231
|
+
* merge.
|
|
150
232
|
*
|
|
151
|
-
* Returns the
|
|
233
|
+
* Returns the file count plus, for the `assistant` strategy, the paths left for
|
|
234
|
+
* the assistant to resolve (text files with conflict markers and modify/delete
|
|
235
|
+
* divergences) and the binary files that conflicted.
|
|
152
236
|
*/
|
|
153
237
|
export async function mergePluginTree({
|
|
154
238
|
baseDir,
|
|
@@ -156,7 +240,9 @@ export async function mergePluginTree({
|
|
|
156
240
|
theirsDir,
|
|
157
241
|
destDir,
|
|
158
242
|
strategy,
|
|
159
|
-
|
|
243
|
+
conflictLabels,
|
|
244
|
+
}: MergePluginTreeOptions): Promise<PluginMergeResult> {
|
|
245
|
+
const labels = conflictLabels ?? DEFAULT_CONFLICT_LABELS;
|
|
160
246
|
const exclude = [INSTALL_META_FILENAME];
|
|
161
247
|
const base = computeFingerprint(baseDir, exclude).files;
|
|
162
248
|
const ours = computeFingerprint(oursDir, exclude).files;
|
|
@@ -173,6 +259,8 @@ export async function mergePluginTree({
|
|
|
173
259
|
const readTheirs = (rel: string) => readFileSync(join(theirsDir, rel));
|
|
174
260
|
|
|
175
261
|
let fileCount = 0;
|
|
262
|
+
const conflicts: string[] = [];
|
|
263
|
+
const binaryConflicts: string[] = [];
|
|
176
264
|
const keep = (rel: string, content: Buffer): void => {
|
|
177
265
|
writeInto(destDir, rel, content);
|
|
178
266
|
fileCount++;
|
|
@@ -194,18 +282,26 @@ export async function mergePluginTree({
|
|
|
194
282
|
b !== undefined ? readBase(rel) : Buffer.alloc(0),
|
|
195
283
|
readTheirs(rel),
|
|
196
284
|
strategy,
|
|
285
|
+
labels,
|
|
197
286
|
);
|
|
198
|
-
keep(rel, merged);
|
|
287
|
+
keep(rel, merged.content);
|
|
288
|
+
if (merged.conflicted) conflicts.push(rel);
|
|
289
|
+
if (merged.binaryConflicted) binaryConflicts.push(rel);
|
|
199
290
|
}
|
|
200
291
|
continue;
|
|
201
292
|
}
|
|
202
293
|
|
|
203
294
|
if (o !== undefined) {
|
|
204
295
|
// Present only locally. A local-only addition (no base) always survives.
|
|
205
|
-
// A file deleted upstream is a delete: drop it when unchanged locally
|
|
206
|
-
//
|
|
207
|
-
|
|
296
|
+
// A file deleted upstream is a delete: drop it when unchanged locally.
|
|
297
|
+
// On a modify/delete conflict `ours` keeps the edit and `theirs` honors
|
|
298
|
+
// the deletion; `assistant` keeps the edit and flags it (a whole-file
|
|
299
|
+
// conflict markers can't express).
|
|
300
|
+
if (b === undefined || (b !== o && strategy !== "theirs")) {
|
|
208
301
|
keep(rel, readOurs(rel));
|
|
302
|
+
if (b !== undefined && b !== o && strategy === "assistant") {
|
|
303
|
+
conflicts.push(rel);
|
|
304
|
+
}
|
|
209
305
|
}
|
|
210
306
|
continue;
|
|
211
307
|
}
|
|
@@ -213,10 +309,14 @@ export async function mergePluginTree({
|
|
|
213
309
|
if (t !== undefined) {
|
|
214
310
|
// Present only at the pin. A remote-only addition (no base) always lands.
|
|
215
311
|
// A file deleted locally is a delete: keep upstream's removal when the
|
|
216
|
-
// pin left it unchanged
|
|
217
|
-
//
|
|
218
|
-
|
|
312
|
+
// pin left it unchanged. On a delete/modify conflict `theirs` keeps the
|
|
313
|
+
// pin's edit and `ours` honors the local deletion; `assistant` keeps the
|
|
314
|
+
// pin's edit and flags it.
|
|
315
|
+
if (b === undefined || (b !== t && strategy !== "ours")) {
|
|
219
316
|
keep(rel, readTheirs(rel));
|
|
317
|
+
if (b !== undefined && b !== t && strategy === "assistant") {
|
|
318
|
+
conflicts.push(rel);
|
|
319
|
+
}
|
|
220
320
|
}
|
|
221
321
|
continue;
|
|
222
322
|
}
|
|
@@ -224,5 +324,5 @@ export async function mergePluginTree({
|
|
|
224
324
|
// Present only in the base: removed on both sides, so it stays removed.
|
|
225
325
|
}
|
|
226
326
|
|
|
227
|
-
return fileCount;
|
|
327
|
+
return { fileCount, conflicts, binaryConflicts };
|
|
228
328
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect the surfaces an installed plugin contributes to the running assistant,
|
|
3
|
+
* read directly from its on-disk tree.
|
|
4
|
+
*
|
|
5
|
+
* The host discovers a plugin's contributions from fixed directory conventions:
|
|
6
|
+
*
|
|
7
|
+
* - `hooks/<name>.{ts,js}` → a lifecycle hook keyed by the file basename
|
|
8
|
+
* (see the external plugin loader's `loadHooks`).
|
|
9
|
+
* - `tools/<name>.{ts,js}` → a tool, also keyed by the file basename
|
|
10
|
+
* (see the external plugin loader's tool walk).
|
|
11
|
+
* - `skills/<id>/SKILL.md` → a skill owned by the plugin (see the skills
|
|
12
|
+
* catalog's `discoverPluginResidentSkills`).
|
|
13
|
+
*
|
|
14
|
+
* This module re-derives those same sets so `plugins inspect` can report exactly
|
|
15
|
+
* what a plugin contributes. Detection is intentionally a self-contained walk of
|
|
16
|
+
* the install tree — `cli/lib` does not reach into the daemon-internal loader or
|
|
17
|
+
* skills catalog — but it mirrors their conventions so inspect agrees with what
|
|
18
|
+
* the runtime actually loads.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
|
|
24
|
+
/** The surfaces an installed plugin contributes, each sorted and de-duplicated. */
|
|
25
|
+
export interface PluginSurfaces {
|
|
26
|
+
/** Skill ids shipped at `skills/<id>/SKILL.md`. */
|
|
27
|
+
readonly skills: readonly string[];
|
|
28
|
+
/** Lifecycle hook names from `hooks/<name>.{ts,js}` (e.g. `pre-model-call`). */
|
|
29
|
+
readonly hooks: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* Registered tool names from `tools/<name>.{ts,js}`. The loader derives a
|
|
32
|
+
* tool's name from its filename via {@link deriveToolName} (e.g.
|
|
33
|
+
* `create-issue.ts` registers as `create_issue`), so the derived form is
|
|
34
|
+
* reported rather than the raw basename. A tool module that overrides its own
|
|
35
|
+
* name via an exported `name` is not reflected here: that would require
|
|
36
|
+
* importing and executing untrusted plugin code, which inspection avoids.
|
|
37
|
+
*/
|
|
38
|
+
readonly tools: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Derive a tool's registered name from its file basename, mirroring the
|
|
43
|
+
* external plugin loader's `deriveToolName`: non-alphanumeric runs collapse to
|
|
44
|
+
* `_`, leading/trailing `_` are trimmed, and an empty result falls back to
|
|
45
|
+
* `tool`. Keeps the inspected tool name aligned with the callable tool name.
|
|
46
|
+
*/
|
|
47
|
+
function deriveToolName(basename: string): string {
|
|
48
|
+
return (
|
|
49
|
+
basename.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "tool"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* List the basenames of every `.ts`/`.js` module directly under `dir`,
|
|
55
|
+
* preferring `.js` over `.ts` for the same basename (compiled-binary semantics)
|
|
56
|
+
* and skipping `.d.ts` declaration files. Returns names sorted for a
|
|
57
|
+
* deterministic listing. A missing or non-directory path yields `[]`.
|
|
58
|
+
*
|
|
59
|
+
* Mirrors the external plugin loader's `listSurfaceDir`, the gate it uses to
|
|
60
|
+
* turn a `hooks/`/`tools/` directory into loadable surfaces.
|
|
61
|
+
*/
|
|
62
|
+
function listModuleBasenames(dir: string): string[] {
|
|
63
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];
|
|
64
|
+
const bases = new Set<string>();
|
|
65
|
+
for (const entry of readdirSync(dir)) {
|
|
66
|
+
if (entry.endsWith(".d.ts")) continue;
|
|
67
|
+
if (!entry.endsWith(".ts") && !entry.endsWith(".js")) continue;
|
|
68
|
+
bases.add(entry.slice(0, -3));
|
|
69
|
+
}
|
|
70
|
+
return [...bases].sort();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* List the skill ids a plugin ships: each subdirectory of `skills/` that
|
|
75
|
+
* contains a `SKILL.md`. Mirrors the skills catalog's plugin-resident skill
|
|
76
|
+
* discovery so inspect reports the same set the runtime would surface.
|
|
77
|
+
*/
|
|
78
|
+
function listSkillIds(skillsDir: string): string[] {
|
|
79
|
+
if (!existsSync(skillsDir) || !statSync(skillsDir).isDirectory()) return [];
|
|
80
|
+
const ids: string[] = [];
|
|
81
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
82
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
83
|
+
if (existsSync(join(skillsDir, entry.name, "SKILL.md"))) {
|
|
84
|
+
ids.push(entry.name);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return ids.sort();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Detect the {@link PluginSurfaces} an installed plugin contributes by walking
|
|
92
|
+
* its install tree at `pluginDir`. Surface types with no contributions come
|
|
93
|
+
* back as empty arrays; callers omit empty types from the rendered output.
|
|
94
|
+
*/
|
|
95
|
+
export function detectPluginSurfaces(pluginDir: string): PluginSurfaces {
|
|
96
|
+
const toolNames = listModuleBasenames(join(pluginDir, "tools")).map(
|
|
97
|
+
deriveToolName,
|
|
98
|
+
);
|
|
99
|
+
return {
|
|
100
|
+
skills: listSkillIds(join(pluginDir, "skills")),
|
|
101
|
+
hooks: listModuleBasenames(join(pluginDir, "hooks")),
|
|
102
|
+
tools: [...new Set(toolNames)].sort(),
|
|
103
|
+
};
|
|
104
|
+
}
|