@vellumai/assistant 0.8.12 → 0.9.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/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +219 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/mock-gateway-ipc.ts +23 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/resolve-app-id.test.ts +56 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/bundler/package-resolver.ts +0 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +167 -3
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +28 -11
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +152 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +91 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -41,11 +41,18 @@ function manifestWith(name: string, ref: string): unknown {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Build a `fetch` that serves the marketplace manifest
|
|
45
|
-
* undefined` answers 404 (empty catalog); `fail: true`
|
|
46
|
-
*
|
|
44
|
+
* Build a `fetch` that serves the marketplace manifest and the GitHub commit
|
|
45
|
+
* API. `marketplace: undefined` answers 404 (empty catalog); `fail: true`
|
|
46
|
+
* rejects the marketplace request with a network error. `remoteCommitDate`
|
|
47
|
+
* seeds the committer date the GitHub commit endpoint returns (its absence
|
|
48
|
+
* 404s, so the remote timestamp degrades to `null`). Anything else surfaces a
|
|
49
|
+
* 500 so test bugs are loud.
|
|
47
50
|
*/
|
|
48
|
-
function makeFetch(opts: {
|
|
51
|
+
function makeFetch(opts: {
|
|
52
|
+
marketplace?: unknown;
|
|
53
|
+
fail?: boolean;
|
|
54
|
+
remoteCommitDate?: string;
|
|
55
|
+
}): FetchLike {
|
|
49
56
|
return (async (input: RequestInfo | URL) => {
|
|
50
57
|
const url = typeof input === "string" ? input : input.toString();
|
|
51
58
|
if (url.includes("marketplace.json")) {
|
|
@@ -55,6 +62,17 @@ function makeFetch(opts: { marketplace?: unknown; fail?: boolean }): FetchLike {
|
|
|
55
62
|
}
|
|
56
63
|
return new Response(JSON.stringify(opts.marketplace), { status: 200 });
|
|
57
64
|
}
|
|
65
|
+
if (url.includes("api.github.com") && url.includes("/commits/")) {
|
|
66
|
+
if (opts.remoteCommitDate === undefined) {
|
|
67
|
+
return new Response("not found", { status: 404 });
|
|
68
|
+
}
|
|
69
|
+
return new Response(
|
|
70
|
+
JSON.stringify({
|
|
71
|
+
commit: { committer: { date: opts.remoteCommitDate } },
|
|
72
|
+
}),
|
|
73
|
+
{ status: 200 },
|
|
74
|
+
);
|
|
75
|
+
}
|
|
58
76
|
return new Response("unexpected url: " + url, { status: 500 });
|
|
59
77
|
}) as FetchLike;
|
|
60
78
|
}
|
|
@@ -65,7 +83,11 @@ function installPlugin(
|
|
|
65
83
|
name: string,
|
|
66
84
|
opts: {
|
|
67
85
|
version?: string;
|
|
68
|
-
sidecar?: {
|
|
86
|
+
sidecar?: {
|
|
87
|
+
commit?: string | null;
|
|
88
|
+
ref?: string;
|
|
89
|
+
committedAt?: string;
|
|
90
|
+
} | null;
|
|
69
91
|
/** Embed a content fingerprint of the materialized tree in the sidecar. */
|
|
70
92
|
fingerprint?: boolean;
|
|
71
93
|
} = {},
|
|
@@ -99,6 +121,7 @@ function installPlugin(
|
|
|
99
121
|
ref: sidecar.ref ?? sidecar.commit ?? SHA_A,
|
|
100
122
|
},
|
|
101
123
|
commit: sidecar.commit ?? undefined,
|
|
124
|
+
committedAt: sidecar.committedAt,
|
|
102
125
|
installedAt: "2026-06-10T12:00:00.000Z",
|
|
103
126
|
fingerprint,
|
|
104
127
|
}),
|
|
@@ -139,6 +162,47 @@ describe("inspectPlugin", () => {
|
|
|
139
162
|
expect(result.remote?.license).toBe("MIT");
|
|
140
163
|
});
|
|
141
164
|
|
|
165
|
+
test("surfaces commit timestamps as the human-readable version on both sides", async () => {
|
|
166
|
+
// GIVEN an installed copy with a recorded commit timestamp and a
|
|
167
|
+
// marketplace whose pinned commit GitHub dates a few days later
|
|
168
|
+
installPlugin(workspace, "level-up", {
|
|
169
|
+
sidecar: { commit: SHA_A, committedAt: "2026-06-01T12:34:56.000Z" },
|
|
170
|
+
});
|
|
171
|
+
const fetch = makeFetch({
|
|
172
|
+
marketplace: manifestWith("level-up", SHA_B),
|
|
173
|
+
remoteCommitDate: "2026-06-05T08:12:24.000Z",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// WHEN it is inspected
|
|
177
|
+
const result = await inspectPlugin(
|
|
178
|
+
{ name: "level-up" },
|
|
179
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// THEN the installed timestamp comes from the sidecar and the remote one
|
|
183
|
+
// is resolved from GitHub — both as ISO-8601 UTC strings
|
|
184
|
+
expect(result.local?.committedAt).toBe("2026-06-01T12:34:56.000Z");
|
|
185
|
+
expect(result.remote?.committedAt).toBe("2026-06-05T08:12:24.000Z");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("leaves the remote timestamp null when the commit date cannot be fetched", async () => {
|
|
189
|
+
// GIVEN a marketplace entry but a GitHub commit endpoint that 404s
|
|
190
|
+
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
191
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_B) });
|
|
192
|
+
|
|
193
|
+
// WHEN it is inspected
|
|
194
|
+
const result = await inspectPlugin(
|
|
195
|
+
{ name: "level-up" },
|
|
196
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// THEN the SHA is still reported while the timestamp degrades to null
|
|
200
|
+
expect(result.remote?.commit).toBe(SHA_B);
|
|
201
|
+
expect(result.remote?.committedAt).toBeNull();
|
|
202
|
+
// AND an older install without a recorded commit date reports null too
|
|
203
|
+
expect(result.local?.committedAt).toBeNull();
|
|
204
|
+
});
|
|
205
|
+
|
|
142
206
|
test("reports update-available when the marketplace pin has advanced", async () => {
|
|
143
207
|
// GIVEN an installed plugin whose commit differs from the marketplace pin
|
|
144
208
|
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
@@ -146,6 +146,8 @@ function makeContentsFetch(opts: {
|
|
|
146
146
|
function fakeGitRunner(opts: {
|
|
147
147
|
tree: Record<string, string | null>;
|
|
148
148
|
commit?: string;
|
|
149
|
+
/** UNIX committer seconds reported by `git show -s --format=%ct HEAD`. */
|
|
150
|
+
committedAtSeconds?: number;
|
|
149
151
|
calls?: string[][];
|
|
150
152
|
fetchError?: Error;
|
|
151
153
|
}): GitRunner {
|
|
@@ -170,6 +172,13 @@ function fakeGitRunner(opts: {
|
|
|
170
172
|
}
|
|
171
173
|
case "rev-parse":
|
|
172
174
|
return { stdout: `${commit}\n` };
|
|
175
|
+
case "show":
|
|
176
|
+
return {
|
|
177
|
+
stdout:
|
|
178
|
+
opts.committedAtSeconds === undefined
|
|
179
|
+
? ""
|
|
180
|
+
: `${opts.committedAtSeconds}\n`,
|
|
181
|
+
};
|
|
173
182
|
default:
|
|
174
183
|
return { stdout: "" };
|
|
175
184
|
}
|
|
@@ -338,6 +347,10 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
338
347
|
".claude-plugin/plugin.json": "{}",
|
|
339
348
|
},
|
|
340
349
|
commit: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
350
|
+
// 2026-06-05T08:12:24Z as UNIX seconds, so the install captures it.
|
|
351
|
+
committedAtSeconds: Math.floor(
|
|
352
|
+
Date.parse("2026-06-05T08:12:24.000Z") / 1000,
|
|
353
|
+
),
|
|
341
354
|
calls,
|
|
342
355
|
});
|
|
343
356
|
|
|
@@ -354,6 +367,8 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
354
367
|
expect(result.target).toBe(target);
|
|
355
368
|
expect(result.ref).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
356
369
|
expect(result.commit).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
370
|
+
// AND the commit's committer date is captured as an ISO-8601 UTC string
|
|
371
|
+
expect(result.committedAt).toBe("2026-06-05T08:12:24.000Z");
|
|
357
372
|
expect(result.fileCount).toBe(3);
|
|
358
373
|
expect(readFileSync(join(target, "package.json"), "utf-8")).toBe(
|
|
359
374
|
'{"name":"caveman"}',
|
|
@@ -61,6 +61,7 @@ function manifestWith(name: string, ref: string): unknown {
|
|
|
61
61
|
function makeFetch(opts: {
|
|
62
62
|
manifest?: unknown;
|
|
63
63
|
manifestStatus?: number;
|
|
64
|
+
remoteCommitDate?: string;
|
|
64
65
|
}): FetchLike {
|
|
65
66
|
return (async (input: RequestInfo | URL) => {
|
|
66
67
|
const url = typeof input === "string" ? input : input.toString();
|
|
@@ -75,6 +76,19 @@ function makeFetch(opts: {
|
|
|
75
76
|
}
|
|
76
77
|
return new Response(JSON.stringify(opts.manifest), { status: 200 });
|
|
77
78
|
}
|
|
79
|
+
// The remote commit date `inspect` resolves to surface the human-readable
|
|
80
|
+
// "to" timestamp; absent it 404s and the timestamp degrades to null.
|
|
81
|
+
if (url.includes("api.github.com") && url.includes("/commits/")) {
|
|
82
|
+
if (opts.remoteCommitDate === undefined) {
|
|
83
|
+
return new Response("not found", { status: 404 });
|
|
84
|
+
}
|
|
85
|
+
return new Response(
|
|
86
|
+
JSON.stringify({
|
|
87
|
+
commit: { committer: { date: opts.remoteCommitDate } },
|
|
88
|
+
}),
|
|
89
|
+
{ status: 200 },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
78
92
|
// No adapter stub: the Contents API listing for plugins/<name> is empty.
|
|
79
93
|
if (url.startsWith(CONTENTS)) {
|
|
80
94
|
return new Response("not found", { status: 404 });
|
|
@@ -83,10 +97,17 @@ function makeFetch(opts: {
|
|
|
83
97
|
}) as FetchLike;
|
|
84
98
|
}
|
|
85
99
|
|
|
86
|
-
/**
|
|
87
|
-
|
|
100
|
+
/**
|
|
101
|
+
* A fake clone that materializes one file and reports `commit` at HEAD. When
|
|
102
|
+
* `committedAtSeconds` is given, `git show -s --format=%ct HEAD` reports that
|
|
103
|
+
* UNIX committer time, mirroring how install captures the commit timestamp.
|
|
104
|
+
*/
|
|
105
|
+
function fakeGitRunner(
|
|
106
|
+
commit: string,
|
|
107
|
+
opts: { calls?: string[][]; committedAtSeconds?: number } = {},
|
|
108
|
+
): GitRunner {
|
|
88
109
|
return async (args, { cwd }) => {
|
|
89
|
-
calls?.push([...args]);
|
|
110
|
+
opts.calls?.push([...args]);
|
|
90
111
|
switch (args[0]) {
|
|
91
112
|
case "fetch": {
|
|
92
113
|
mkdirSync(join(cwd, ".git"), { recursive: true });
|
|
@@ -96,6 +117,13 @@ function fakeGitRunner(commit: string, calls?: string[][]): GitRunner {
|
|
|
96
117
|
}
|
|
97
118
|
case "rev-parse":
|
|
98
119
|
return { stdout: `${commit}\n` };
|
|
120
|
+
case "show":
|
|
121
|
+
return {
|
|
122
|
+
stdout:
|
|
123
|
+
opts.committedAtSeconds === undefined
|
|
124
|
+
? ""
|
|
125
|
+
: `${opts.committedAtSeconds}\n`,
|
|
126
|
+
};
|
|
99
127
|
default:
|
|
100
128
|
return { stdout: "" };
|
|
101
129
|
}
|
|
@@ -111,7 +139,7 @@ const unusedGitRunner: GitRunner = async (args) => {
|
|
|
111
139
|
function installCopy(
|
|
112
140
|
pluginsDir: string,
|
|
113
141
|
name: string,
|
|
114
|
-
sidecar: { commit: string } | null,
|
|
142
|
+
sidecar: { commit: string; committedAt?: string } | null,
|
|
115
143
|
): void {
|
|
116
144
|
const dir = join(pluginsDir, name);
|
|
117
145
|
mkdirSync(dir, { recursive: true });
|
|
@@ -132,6 +160,7 @@ function installCopy(
|
|
|
132
160
|
ref: sidecar.commit,
|
|
133
161
|
},
|
|
134
162
|
commit: sidecar.commit,
|
|
163
|
+
committedAt: sidecar.committedAt,
|
|
135
164
|
installedAt: "2026-06-10T12:00:00.000Z",
|
|
136
165
|
}),
|
|
137
166
|
);
|
|
@@ -181,6 +210,54 @@ describe("upgradePlugin", () => {
|
|
|
181
210
|
expect(sidecarCommit(pluginsDir, "level-up")).toBe(SHA_B);
|
|
182
211
|
});
|
|
183
212
|
|
|
213
|
+
test("threads commit timestamps through the move as the human-readable version", async () => {
|
|
214
|
+
// GIVEN an installed copy with a recorded commit timestamp
|
|
215
|
+
installCopy(pluginsDir, "level-up", {
|
|
216
|
+
commit: SHA_A,
|
|
217
|
+
committedAt: "2026-06-01T12:34:56.000Z",
|
|
218
|
+
});
|
|
219
|
+
// AND the marketplace pins a newer commit whose date the clone reports
|
|
220
|
+
const fetch = makeFetch({ manifest: manifestWith("level-up", SHA_B) });
|
|
221
|
+
// 2026-06-05T08:12:24Z expressed as UNIX seconds for `git show %ct`.
|
|
222
|
+
const runGit = fakeGitRunner(SHA_B, {
|
|
223
|
+
committedAtSeconds: Math.floor(
|
|
224
|
+
Date.parse("2026-06-05T08:12:24.000Z") / 1000,
|
|
225
|
+
),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// WHEN the plugin is upgraded
|
|
229
|
+
const result = await upgradePlugin(
|
|
230
|
+
{ name: "level-up" },
|
|
231
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// THEN both ends of the move carry their commit timestamps
|
|
235
|
+
expect(result.fromTimestamp).toBe("2026-06-01T12:34:56.000Z");
|
|
236
|
+
expect(result.toTimestamp).toBe("2026-06-05T08:12:24.000Z");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("a dry run resolves the target timestamp from the marketplace pin", async () => {
|
|
240
|
+
// GIVEN an installed copy and a marketplace pin whose commit GitHub dates
|
|
241
|
+
installCopy(pluginsDir, "level-up", {
|
|
242
|
+
commit: SHA_A,
|
|
243
|
+
committedAt: "2026-06-01T12:34:56.000Z",
|
|
244
|
+
});
|
|
245
|
+
const fetch = makeFetch({
|
|
246
|
+
manifest: manifestWith("level-up", SHA_B),
|
|
247
|
+
remoteCommitDate: "2026-06-05T08:12:24.000Z",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// WHEN a dry-run upgrade is performed (git must never run)
|
|
251
|
+
const result = await upgradePlugin(
|
|
252
|
+
{ name: "level-up", dryRun: true },
|
|
253
|
+
{ fetch, runGit: unusedGitRunner, workspacePluginsDir: pluginsDir },
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// THEN the preview shows the timestamp move resolved without a clone
|
|
257
|
+
expect(result.fromTimestamp).toBe("2026-06-01T12:34:56.000Z");
|
|
258
|
+
expect(result.toTimestamp).toBe("2026-06-05T08:12:24.000Z");
|
|
259
|
+
});
|
|
260
|
+
|
|
184
261
|
test("is a no-op when the installed commit already equals the pin", async () => {
|
|
185
262
|
// GIVEN an installed copy already pinned to SHA_A
|
|
186
263
|
installCopy(pluginsDir, "level-up", { commit: SHA_A });
|
|
@@ -70,6 +70,13 @@ export interface PluginLocalInfo {
|
|
|
70
70
|
readonly target: string;
|
|
71
71
|
/** Resolved commit the copy was installed at; `null` when no provenance was recorded. */
|
|
72
72
|
readonly commit: string | null;
|
|
73
|
+
/**
|
|
74
|
+
* ISO-8601 committer timestamp of {@link PluginLocalInfo.commit} (UTC), the
|
|
75
|
+
* human-readable version `plugins inspect` shows. `null` for older installs
|
|
76
|
+
* written before commit timestamps were recorded. Distinct from
|
|
77
|
+
* {@link PluginLocalInfo.installedAt} (when this machine ran the install).
|
|
78
|
+
*/
|
|
79
|
+
readonly committedAt: string | null;
|
|
73
80
|
/** `package.json` `version`, when present. */
|
|
74
81
|
readonly version: string | null;
|
|
75
82
|
/** `package.json` `description`, when present. */
|
|
@@ -96,6 +103,12 @@ export interface PluginRemoteInfo {
|
|
|
96
103
|
readonly path: string;
|
|
97
104
|
/** Pinned commit SHA the marketplace currently resolves installs to. */
|
|
98
105
|
readonly commit: string;
|
|
106
|
+
/**
|
|
107
|
+
* ISO-8601 committer timestamp of {@link PluginRemoteInfo.commit} (UTC),
|
|
108
|
+
* resolved from GitHub. `null` when the commit metadata could not be fetched
|
|
109
|
+
* (network / rate-limit); the SHA is still reported.
|
|
110
|
+
*/
|
|
111
|
+
readonly committedAt: string | null;
|
|
99
112
|
readonly description: string | null;
|
|
100
113
|
readonly homepage: string | null;
|
|
101
114
|
readonly license: string | null;
|
|
@@ -167,6 +180,7 @@ function readLocal(
|
|
|
167
180
|
return {
|
|
168
181
|
target: entry.target,
|
|
169
182
|
commit,
|
|
183
|
+
committedAt: manifest?.committedAt ?? null,
|
|
170
184
|
version: entry.packageJson?.version ?? null,
|
|
171
185
|
description: entry.packageJson?.description ?? null,
|
|
172
186
|
installedAt: manifest?.installedAt || null,
|
|
@@ -179,11 +193,13 @@ function readLocal(
|
|
|
179
193
|
function readRemote(
|
|
180
194
|
entry: MarketplaceEntry,
|
|
181
195
|
marketplaceRef: string,
|
|
196
|
+
committedAt: string | null,
|
|
182
197
|
): PluginRemoteInfo {
|
|
183
198
|
return {
|
|
184
199
|
repo: entry.source.repo,
|
|
185
200
|
path: entry.source.path ?? "",
|
|
186
201
|
commit: entry.source.ref,
|
|
202
|
+
committedAt,
|
|
187
203
|
description: entry.description ?? null,
|
|
188
204
|
homepage: entry.homepage ?? null,
|
|
189
205
|
license: entry.license ?? null,
|
|
@@ -192,6 +208,44 @@ function readRemote(
|
|
|
192
208
|
};
|
|
193
209
|
}
|
|
194
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Resolve the committer date of a pinned commit from GitHub, normalized to a
|
|
213
|
+
* UTC ISO-8601 string so it is directly comparable with the install-time
|
|
214
|
+
* {@link PluginLocalInfo.committedAt}.
|
|
215
|
+
*
|
|
216
|
+
* Best-effort: any failure (network, rate-limit, unexpected shape) yields
|
|
217
|
+
* `null` so inspection still reports the remote pin's SHA without its date,
|
|
218
|
+
* mirroring how the local side degrades to `unknown` when no date was recorded.
|
|
219
|
+
*/
|
|
220
|
+
async function fetchCommitDate(
|
|
221
|
+
repo: string,
|
|
222
|
+
sha: string,
|
|
223
|
+
fetch: FetchLike,
|
|
224
|
+
): Promise<string | null> {
|
|
225
|
+
const url = `https://api.github.com/repos/${repo}/commits/${encodeURIComponent(sha)}`;
|
|
226
|
+
try {
|
|
227
|
+
const res = await fetch(url, {
|
|
228
|
+
headers: {
|
|
229
|
+
Accept: "application/vnd.github+json",
|
|
230
|
+
"User-Agent": "vellum-assistant-cli",
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
if (!res.ok) return null;
|
|
234
|
+
const json: unknown = JSON.parse(await res.text());
|
|
235
|
+
if (typeof json !== "object" || json === null) return null;
|
|
236
|
+
const commit = (json as Record<string, unknown>).commit;
|
|
237
|
+
if (typeof commit !== "object" || commit === null) return null;
|
|
238
|
+
const committer = (commit as Record<string, unknown>).committer;
|
|
239
|
+
if (typeof committer !== "object" || committer === null) return null;
|
|
240
|
+
const date = (committer as Record<string, unknown>).date;
|
|
241
|
+
if (typeof date !== "string") return null;
|
|
242
|
+
const ms = Date.parse(date);
|
|
243
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
195
249
|
/**
|
|
196
250
|
* Resolve the local-vs-remote inspection for a single plugin.
|
|
197
251
|
*
|
|
@@ -221,7 +275,14 @@ export async function inspectPlugin(
|
|
|
221
275
|
{ ref: marketplaceRef },
|
|
222
276
|
);
|
|
223
277
|
const match = entries.find((e) => e.name === name);
|
|
224
|
-
if (match)
|
|
278
|
+
if (match) {
|
|
279
|
+
const committedAt = await fetchCommitDate(
|
|
280
|
+
match.source.repo,
|
|
281
|
+
match.source.ref,
|
|
282
|
+
deps.fetch,
|
|
283
|
+
);
|
|
284
|
+
remote = readRemote(match, marketplaceRef, committedAt);
|
|
285
|
+
}
|
|
225
286
|
} catch (err) {
|
|
226
287
|
remoteError = err instanceof Error ? err.message : String(err);
|
|
227
288
|
}
|
|
@@ -135,6 +135,11 @@ export interface InstallPluginResult {
|
|
|
135
135
|
readonly ref: string;
|
|
136
136
|
/** Resolved commit SHA the external source was cloned at; null when it could not be read. */
|
|
137
137
|
readonly commit: string | null;
|
|
138
|
+
/**
|
|
139
|
+
* ISO-8601 committer timestamp of {@link InstallPluginResult.commit} (UTC);
|
|
140
|
+
* null when the commit or its date could not be read.
|
|
141
|
+
*/
|
|
142
|
+
readonly committedAt: string | null;
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
/** Plugin name failed sanitization. */
|
|
@@ -369,6 +374,7 @@ export async function installPlugin(
|
|
|
369
374
|
|
|
370
375
|
let fileCount: number;
|
|
371
376
|
let commit: string | null = null;
|
|
377
|
+
let committedAt: string | null = null;
|
|
372
378
|
try {
|
|
373
379
|
const cloned = await copyExternalViaGit(
|
|
374
380
|
source,
|
|
@@ -377,6 +383,7 @@ export async function installPlugin(
|
|
|
377
383
|
);
|
|
378
384
|
fileCount = cloned.fileCount;
|
|
379
385
|
commit = cloned.commit;
|
|
386
|
+
committedAt = cloned.committedAt;
|
|
380
387
|
// An external clone is often a foreign-ecosystem plugin (e.g. a Claude
|
|
381
388
|
// Code plugin) that the Vellum loader can't run as-is. When we curate an
|
|
382
389
|
// adapter stub for it, overlay the stub and run its transform so the
|
|
@@ -411,6 +418,7 @@ export async function installPlugin(
|
|
|
411
418
|
source,
|
|
412
419
|
ref,
|
|
413
420
|
commit,
|
|
421
|
+
committedAt,
|
|
414
422
|
fingerprint,
|
|
415
423
|
contentHash,
|
|
416
424
|
});
|
|
@@ -426,7 +434,7 @@ export async function installPlugin(
|
|
|
426
434
|
}
|
|
427
435
|
renameSync(stagingDir, target);
|
|
428
436
|
|
|
429
|
-
return { name, target, fileCount, ref, commit };
|
|
437
|
+
return { name, target, fileCount, ref, commit, committedAt };
|
|
430
438
|
}
|
|
431
439
|
|
|
432
440
|
/** Cap on any single git invocation; a shallow fetch is well under this. */
|
|
@@ -497,6 +505,16 @@ export interface InstallMeta {
|
|
|
497
505
|
readonly source: InstallMetaSource;
|
|
498
506
|
/** Resolved commit SHA the source was cloned at; `null` when it could not be read at install time. */
|
|
499
507
|
readonly commit: string | null;
|
|
508
|
+
/**
|
|
509
|
+
* ISO-8601 committer timestamp of {@link InstallMeta.commit}, in UTC
|
|
510
|
+
* (e.g. `2026-06-01T12:34:56.000Z`). This is a property of the commit
|
|
511
|
+
* itself, distinct from {@link InstallMeta.installedAt} (when the local
|
|
512
|
+
* machine ran the install). `null` for older installs written before commit
|
|
513
|
+
* timestamps were recorded, or when the date could not be read. Reserved for
|
|
514
|
+
* the human-readable "version" surfaced by `plugins inspect`; the optional
|
|
515
|
+
* {@link InstallMeta.version} field stays free for future tag/semver support.
|
|
516
|
+
*/
|
|
517
|
+
readonly committedAt?: string | null;
|
|
500
518
|
/**
|
|
501
519
|
* Per-file content digest of the materialized tree, captured at install
|
|
502
520
|
* time. `null` for older installs written before fingerprinting; callers
|
|
@@ -525,7 +543,11 @@ async function copyExternalViaGit(
|
|
|
525
543
|
source: PluginFetchSource,
|
|
526
544
|
destDir: string,
|
|
527
545
|
runGit: GitRunner,
|
|
528
|
-
): Promise<{
|
|
546
|
+
): Promise<{
|
|
547
|
+
fileCount: number;
|
|
548
|
+
commit: string | null;
|
|
549
|
+
committedAt: string | null;
|
|
550
|
+
}> {
|
|
529
551
|
const cloneDir = `${destDir}.gitclone`;
|
|
530
552
|
rmSync(cloneDir, { recursive: true, force: true });
|
|
531
553
|
mkdirSync(cloneDir, { recursive: true });
|
|
@@ -543,7 +565,8 @@ async function copyExternalViaGit(
|
|
|
543
565
|
// A missing repo/ref (or a private one we can't reach) is a hard
|
|
544
566
|
// not-found, surfaced as zero files. Anything else — network loss, a
|
|
545
567
|
// transient GitHub outage — is retryable, so map it to a 503.
|
|
546
|
-
if (isGitRefNotFound(err))
|
|
568
|
+
if (isGitRefNotFound(err))
|
|
569
|
+
return { fileCount: 0, commit: null, committedAt: null };
|
|
547
570
|
throw new PluginSourceUnavailableError(
|
|
548
571
|
`git clone failed for ${sourceLabel(source)} @ ${source.ref}: ${subprocessErrorText(err)}`,
|
|
549
572
|
503,
|
|
@@ -553,6 +576,7 @@ async function copyExternalViaGit(
|
|
|
553
576
|
await runGit(["checkout", "--quiet", "FETCH_HEAD"], { cwd: cloneDir });
|
|
554
577
|
|
|
555
578
|
let commit: string | null = null;
|
|
579
|
+
let committedAt: string | null = null;
|
|
556
580
|
try {
|
|
557
581
|
const { stdout } = await runGit(["rev-parse", "HEAD"], { cwd: cloneDir });
|
|
558
582
|
commit = stdout.trim() || null;
|
|
@@ -560,6 +584,24 @@ async function copyExternalViaGit(
|
|
|
560
584
|
// Provenance is best-effort; a missing commit must not fail the install.
|
|
561
585
|
commit = null;
|
|
562
586
|
}
|
|
587
|
+
if (commit) {
|
|
588
|
+
// The committer date (`%ct`, UNIX seconds) is the version timestamp
|
|
589
|
+
// `plugins inspect` shows. Normalize to a UTC ISO-8601 string so installs
|
|
590
|
+
// made in different local timezones remain directly comparable.
|
|
591
|
+
try {
|
|
592
|
+
const { stdout } = await runGit(
|
|
593
|
+
["show", "-s", "--format=%ct", "HEAD"],
|
|
594
|
+
{ cwd: cloneDir },
|
|
595
|
+
);
|
|
596
|
+
const seconds = Number.parseInt(stdout.trim(), 10);
|
|
597
|
+
if (Number.isFinite(seconds)) {
|
|
598
|
+
committedAt = new Date(seconds * 1000).toISOString();
|
|
599
|
+
}
|
|
600
|
+
} catch {
|
|
601
|
+
// Best-effort: a missing date must not fail the install.
|
|
602
|
+
committedAt = null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
563
605
|
|
|
564
606
|
// Defense in depth: external marketplace refs are full commit SHAs (the
|
|
565
607
|
// manifest schema rejects mutable tags/branches), so the checked-out
|
|
@@ -577,11 +619,11 @@ async function copyExternalViaGit(
|
|
|
577
619
|
? join(cloneDir, source.rootPath)
|
|
578
620
|
: cloneDir;
|
|
579
621
|
if (!existsSync(srcRoot) || !statSync(srcRoot).isDirectory()) {
|
|
580
|
-
return { fileCount: 0, commit };
|
|
622
|
+
return { fileCount: 0, commit, committedAt };
|
|
581
623
|
}
|
|
582
624
|
|
|
583
625
|
const fileCount = copyTreeSkippingGit(srcRoot, destDir);
|
|
584
|
-
return { fileCount, commit };
|
|
626
|
+
return { fileCount, commit, committedAt };
|
|
585
627
|
} finally {
|
|
586
628
|
rmSync(cloneDir, { recursive: true, force: true });
|
|
587
629
|
}
|
|
@@ -948,6 +990,8 @@ interface WriteInstallMetaParams {
|
|
|
948
990
|
readonly source: PluginFetchSource;
|
|
949
991
|
readonly ref: string;
|
|
950
992
|
readonly commit: string | null;
|
|
993
|
+
/** ISO-8601 committer timestamp of {@link WriteInstallMetaParams.commit} (UTC); null when unknown. */
|
|
994
|
+
readonly committedAt: string | null;
|
|
951
995
|
readonly fingerprint: Fingerprint;
|
|
952
996
|
readonly contentHash: string;
|
|
953
997
|
}
|
|
@@ -985,6 +1029,7 @@ function writeInstallMeta(
|
|
|
985
1029
|
source,
|
|
986
1030
|
ref,
|
|
987
1031
|
commit,
|
|
1032
|
+
committedAt,
|
|
988
1033
|
fingerprint,
|
|
989
1034
|
contentHash,
|
|
990
1035
|
}: WriteInstallMetaParams,
|
|
@@ -1004,6 +1049,7 @@ function writeInstallMeta(
|
|
|
1004
1049
|
ref,
|
|
1005
1050
|
},
|
|
1006
1051
|
commit,
|
|
1052
|
+
committedAt,
|
|
1007
1053
|
fingerprint,
|
|
1008
1054
|
};
|
|
1009
1055
|
writeFileSync(
|
|
@@ -1072,6 +1118,7 @@ export function readInstallMeta(pluginDir: string): InstallMeta | null {
|
|
|
1072
1118
|
ref: source.ref,
|
|
1073
1119
|
},
|
|
1074
1120
|
commit: typeof obj.commit === "string" ? obj.commit : null,
|
|
1121
|
+
committedAt: typeof obj.committedAt === "string" ? obj.committedAt : null,
|
|
1075
1122
|
fingerprint: parseFingerprint(obj.fingerprint),
|
|
1076
1123
|
};
|
|
1077
1124
|
}
|
|
@@ -81,8 +81,18 @@ export interface PluginUpgradeResult {
|
|
|
81
81
|
readonly outcome: PluginUpgradeOutcome;
|
|
82
82
|
/** Installed commit before the upgrade; `null` when no provenance was recorded. */
|
|
83
83
|
readonly fromCommit: string | null;
|
|
84
|
+
/**
|
|
85
|
+
* ISO-8601 committer timestamp (UTC) of {@link PluginUpgradeResult.fromCommit},
|
|
86
|
+
* the human-readable version moved from; `null` when it was not recorded.
|
|
87
|
+
*/
|
|
88
|
+
readonly fromTimestamp: string | null;
|
|
84
89
|
/** Marketplace-pinned commit the install was (or would be) moved to. */
|
|
85
90
|
readonly toCommit: string;
|
|
91
|
+
/**
|
|
92
|
+
* ISO-8601 committer timestamp (UTC) of {@link PluginUpgradeResult.toCommit},
|
|
93
|
+
* the human-readable version moved to; `null` when it could not be resolved.
|
|
94
|
+
*/
|
|
95
|
+
readonly toTimestamp: string | null;
|
|
86
96
|
/** Absolute path to the installed plugin directory. */
|
|
87
97
|
readonly target: string;
|
|
88
98
|
/** Files materialized by the upgrade; `null` for a no-op or dry run. */
|
|
@@ -173,7 +183,9 @@ export async function upgradePlugin(
|
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
const fromCommit = local.commit;
|
|
186
|
+
const fromTimestamp = local.committedAt;
|
|
176
187
|
const toCommit = remote.commit;
|
|
188
|
+
const toTimestamp = remote.committedAt;
|
|
177
189
|
const provenanceWasUnknown = inspection.status === "unknown-provenance";
|
|
178
190
|
|
|
179
191
|
if (inspection.status === "up-to-date") {
|
|
@@ -181,7 +193,9 @@ export async function upgradePlugin(
|
|
|
181
193
|
name,
|
|
182
194
|
outcome: "already-up-to-date",
|
|
183
195
|
fromCommit,
|
|
196
|
+
fromTimestamp,
|
|
184
197
|
toCommit,
|
|
198
|
+
toTimestamp,
|
|
185
199
|
target: local.target,
|
|
186
200
|
fileCount: null,
|
|
187
201
|
dryRun,
|
|
@@ -194,7 +208,9 @@ export async function upgradePlugin(
|
|
|
194
208
|
name,
|
|
195
209
|
outcome: "would-upgrade",
|
|
196
210
|
fromCommit,
|
|
211
|
+
fromTimestamp,
|
|
197
212
|
toCommit,
|
|
213
|
+
toTimestamp,
|
|
198
214
|
target: local.target,
|
|
199
215
|
fileCount: null,
|
|
200
216
|
dryRun: true,
|
|
@@ -216,7 +232,9 @@ export async function upgradePlugin(
|
|
|
216
232
|
name,
|
|
217
233
|
outcome: "upgraded",
|
|
218
234
|
fromCommit,
|
|
235
|
+
fromTimestamp,
|
|
219
236
|
toCommit: result.commit ?? toCommit,
|
|
237
|
+
toTimestamp: result.committedAt ?? toTimestamp,
|
|
220
238
|
target: result.target,
|
|
221
239
|
fileCount: result.fileCount,
|
|
222
240
|
dryRun: false,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { AssistantConfigSchema } from "../schema.js";
|
|
4
|
+
import { WorkflowsConfigSchema } from "../schemas/workflows.js";
|
|
5
|
+
|
|
6
|
+
describe("WorkflowsConfigSchema", () => {
|
|
7
|
+
test("empty object parses to full defaults", () => {
|
|
8
|
+
const parsed = WorkflowsConfigSchema.parse({});
|
|
9
|
+
expect(parsed).toEqual({
|
|
10
|
+
maxAgentsPerRun: 500,
|
|
11
|
+
maxConcurrentLeaves: 6,
|
|
12
|
+
maxConcurrentRuns: 3,
|
|
13
|
+
journalRetentionDays: 30,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("custom values round-trip", () => {
|
|
18
|
+
const parsed = WorkflowsConfigSchema.parse({
|
|
19
|
+
maxAgentsPerRun: 100,
|
|
20
|
+
maxConcurrentLeaves: 12,
|
|
21
|
+
maxConcurrentRuns: 1,
|
|
22
|
+
journalRetentionDays: 7,
|
|
23
|
+
});
|
|
24
|
+
expect(parsed).toEqual({
|
|
25
|
+
maxAgentsPerRun: 100,
|
|
26
|
+
maxConcurrentLeaves: 12,
|
|
27
|
+
maxConcurrentRuns: 1,
|
|
28
|
+
journalRetentionDays: 7,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("rejects non-positive and non-integer values", () => {
|
|
33
|
+
expect(
|
|
34
|
+
WorkflowsConfigSchema.safeParse({ maxAgentsPerRun: 0 }).success,
|
|
35
|
+
).toBe(false);
|
|
36
|
+
expect(
|
|
37
|
+
WorkflowsConfigSchema.safeParse({ maxConcurrentLeaves: -1 }).success,
|
|
38
|
+
).toBe(false);
|
|
39
|
+
expect(
|
|
40
|
+
WorkflowsConfigSchema.safeParse({ journalRetentionDays: 1.5 }).success,
|
|
41
|
+
).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("root config resolves workflow defaults when section is absent", () => {
|
|
45
|
+
const config = AssistantConfigSchema.parse({});
|
|
46
|
+
expect(config.workflows.maxAgentsPerRun).toBe(500);
|
|
47
|
+
expect(config.workflows.maxConcurrentLeaves).toBe(6);
|
|
48
|
+
expect(config.workflows.maxConcurrentRuns).toBe(3);
|
|
49
|
+
expect(config.workflows.journalRetentionDays).toBe(30);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("root config applies workflow overrides", () => {
|
|
53
|
+
const config = AssistantConfigSchema.parse({
|
|
54
|
+
workflows: { maxConcurrentLeaves: 10 },
|
|
55
|
+
});
|
|
56
|
+
expect(config.workflows.maxConcurrentLeaves).toBe(10);
|
|
57
|
+
// Unspecified leaves still fall back to defaults.
|
|
58
|
+
expect(config.workflows.maxAgentsPerRun).toBe(500);
|
|
59
|
+
});
|
|
60
|
+
});
|