@vellumai/assistant 0.5.16 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +1 -1
- package/Dockerfile +0 -3
- package/knip.json +2 -1
- package/openapi.yaml +660 -80
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +68 -0
- package/src/__tests__/agent-loop.test.ts +0 -32
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +57 -3
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/call-conversation-messages.test.ts +2 -6
- package/src/__tests__/call-domain.test.ts +2 -6
- package/src/__tests__/call-pointer-messages.test.ts +2 -14
- package/src/__tests__/call-recovery.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +2 -6
- package/src/__tests__/call-store.test.ts +2 -6
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
- package/src/__tests__/canonical-guardian-store.test.ts +2 -6
- package/src/__tests__/channel-delivery-store.test.ts +2 -6
- package/src/__tests__/channel-retry-sweep.test.ts +2 -6
- package/src/__tests__/checker.test.ts +25 -3
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
- package/src/__tests__/cli-memory.test.ts +74 -69
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
- package/src/__tests__/contacts-tools.test.ts +31 -0
- package/src/__tests__/context-overflow-reducer.test.ts +86 -0
- package/src/__tests__/context-token-estimator.test.ts +175 -10
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
- package/src/__tests__/conversation-agent-loop.test.ts +9 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -6
- package/src/__tests__/conversation-attention-store.test.ts +2 -6
- package/src/__tests__/conversation-clear-safety.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
- package/src/__tests__/conversation-disk-view.test.ts +2 -6
- package/src/__tests__/conversation-error.test.ts +33 -2
- package/src/__tests__/conversation-fork-crud.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
- package/src/__tests__/conversation-media-retry.test.ts +91 -0
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-usage.test.ts +2 -6
- package/src/__tests__/conversation-wipe.test.ts +11 -408
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +2 -0
- package/src/__tests__/followup-tools.test.ts +2 -6
- package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
- package/src/__tests__/guardian-action-store.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
- package/src/__tests__/guardian-dispatch.test.ts +2 -6
- package/src/__tests__/guardian-grant-minting.test.ts +2 -14
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
- package/src/__tests__/guardian-routing-state.test.ts +2 -6
- package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +154 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +292 -0
- package/src/__tests__/invite-redemption-service.test.ts +2 -6
- package/src/__tests__/invite-routes-http.test.ts +2 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/llm-context-route-provider.test.ts +2 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +2 -6
- package/src/__tests__/managed-store.test.ts +38 -11
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +2 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-guardian-path.test.ts +2 -6
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
- package/src/__tests__/outlook-attachments.test.ts +301 -0
- package/src/__tests__/outlook-automation-tools.test.ts +425 -0
- package/src/__tests__/outlook-categories.test.ts +212 -0
- package/src/__tests__/outlook-client-automation.test.ts +246 -0
- package/src/__tests__/outlook-compose-tools.test.ts +325 -0
- package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
- package/src/__tests__/outlook-email-watcher.test.ts +322 -0
- package/src/__tests__/outlook-follow-up.test.ts +196 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/platform-callback-registration.test.ts +4 -4
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/provider-error-scenarios.test.ts +21 -0
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/require-fresh-approval.test.ts +64 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/schedule-store.test.ts +2 -6
- package/src/__tests__/schedule-tools.test.ts +2 -6
- package/src/__tests__/scheduler-recurrence.test.ts +1 -5
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/search-skills-unified.test.ts +421 -0
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +2 -6
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
- package/src/__tests__/skill-memory.test.ts +140 -98
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/task-compiler.test.ts +2 -6
- package/src/__tests__/task-management-tools.test.ts +2 -6
- package/src/__tests__/task-memory-cleanup.test.ts +173 -229
- package/src/__tests__/task-runner.test.ts +2 -6
- package/src/__tests__/task-scheduler.test.ts +2 -6
- package/src/__tests__/test-preload.ts +3 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -6
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
- package/src/__tests__/trust-store.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +2 -6
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
- package/src/__tests__/usage-routes.test.ts +2 -6
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -6
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
- package/src/__tests__/voice-session-bridge.test.ts +2 -6
- package/src/__tests__/volume-security-guard.test.ts +2 -0
- package/src/__tests__/workspace-lifecycle.test.ts +29 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/agent/attachments.ts +7 -2
- package/src/agent/image-optimize.ts +165 -0
- package/src/agent/loop.ts +1 -15
- package/src/bundler/app-compiler.ts +179 -2
- package/src/bundler/package-resolver.ts +3 -5
- package/src/cli/__tests__/notifications.test.ts +1 -2
- package/src/cli/cli-memory.ts +67 -64
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/memory.ts +41 -55
- package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +11 -6
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
- package/src/cli/commands/platform/index.ts +16 -16
- package/src/cli/commands/skills.ts +88 -16
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/contacts/SKILL.md +0 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
- package/src/config/bundled-skills/gmail/SKILL.md +2 -10
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +10 -18
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
- package/src/config/bundled-skills/outlook/SKILL.md +189 -0
- package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
- package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
- package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
- package/src/config/bundled-skills/slack/SKILL.md +1 -7
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +15 -8
- package/src/config/feature-flag-registry.json +21 -124
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/config/skills.ts +18 -7
- package/src/context/token-estimator.ts +25 -18
- package/src/context/window-manager.ts +6 -2
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
- package/src/daemon/conversation-agent-loop.ts +96 -61
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-lifecycle.ts +33 -0
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-runtime-assembly.ts +5 -0
- package/src/daemon/conversation.ts +41 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/handlers/shared.ts +22 -12
- package/src/daemon/handlers/skills.ts +416 -202
- package/src/daemon/lifecycle.ts +40 -1
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +4 -1
- package/src/daemon/message-types/messages.ts +3 -1
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/providers-setup.ts +5 -0
- package/src/daemon/server.ts +11 -2
- package/src/daemon/tool-side-effects.ts +27 -5
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/runner.ts +15 -38
- package/src/inbound/platform-callback-registration.ts +14 -14
- package/src/memory/admin.ts +11 -45
- package/src/memory/conversation-bootstrap.ts +2 -0
- package/src/memory/conversation-crud.ts +242 -348
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +4 -2
- package/src/memory/db-init.ts +30 -3
- package/src/memory/embed.ts +73 -0
- package/src/memory/embedding-backend.ts +8 -14
- package/src/memory/embedding-runtime-manager.ts +12 -114
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/graph/bootstrap.ts +512 -0
- package/src/memory/graph/capability-seed.ts +297 -0
- package/src/memory/graph/consolidation.ts +691 -0
- package/src/memory/graph/conversation-graph-memory.ts +630 -0
- package/src/memory/graph/decay.test.ts +208 -0
- package/src/memory/graph/decay.ts +195 -0
- package/src/memory/graph/extraction-job.ts +69 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1254 -0
- package/src/memory/graph/graph-search.ts +266 -0
- package/src/memory/graph/image-ref-utils.ts +29 -0
- package/src/memory/graph/injection.test.ts +513 -0
- package/src/memory/graph/injection.ts +439 -0
- package/src/memory/graph/inspect.ts +534 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1008 -0
- package/src/memory/graph/scoring.test.ts +548 -0
- package/src/memory/graph/scoring.ts +232 -0
- package/src/memory/graph/serendipity.ts +65 -0
- package/src/memory/graph/store.test.ts +1050 -0
- package/src/memory/graph/store.ts +699 -0
- package/src/memory/graph/tool-handlers.ts +426 -0
- package/src/memory/graph/tools.ts +141 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +271 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +0 -53
- package/src/memory/job-handlers/conversation-starters.ts +91 -53
- package/src/memory/job-handlers/embedding.ts +5 -31
- package/src/memory/job-handlers/index-maintenance.ts +23 -11
- package/src/memory/job-handlers/summarization.ts +32 -17
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +50 -70
- package/src/memory/jobs-worker.ts +147 -112
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
- package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
- package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-graph.ts +139 -0
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/task-memory-cleanup.ts +28 -50
- package/src/messaging/providers/outlook/adapter.ts +8 -1
- package/src/messaging/providers/outlook/client.ts +299 -0
- package/src/messaging/providers/outlook/types.ts +118 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/copy-composer.ts +9 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/seed-providers.ts +2 -1
- package/src/permissions/checker.ts +24 -3
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/workspace-policy.ts +1 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +3 -29
- package/src/providers/anthropic/client.ts +47 -19
- package/src/providers/gemini/client.ts +1 -1
- package/src/providers/openai/client.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +19 -3
- package/src/runtime/actor-trust-resolver.ts +5 -1
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/guardian-reply-router.ts +5 -1
- package/src/runtime/http-server.ts +23 -3
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +8 -0
- package/src/runtime/routes/conversation-management-routes.ts +2 -0
- package/src/runtime/routes/conversation-starter-routes.ts +2 -2
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/global-search-routes.ts +21 -19
- package/src/runtime/routes/group-routes.ts +207 -0
- package/src/runtime/routes/guardian-action-routes.ts +21 -10
- package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
- package/src/runtime/routes/inbound-message-handler.ts +19 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
- package/src/runtime/routes/memory-item-routes.test.ts +2 -14
- package/src/runtime/routes/memory-item-routes.ts +341 -388
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/work-items-routes.test.ts +2 -6
- package/src/schedule/scheduler.ts +8 -1
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +4 -8
- package/src/shared/provider-env-vars.ts +19 -0
- package/src/skills/catalog-cache.ts +5 -0
- package/src/skills/catalog-install.ts +15 -14
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +27 -16
- package/src/skills/skill-memory.ts +152 -77
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/tasks/task-runner.ts +3 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +7 -1
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/tool-manifest.ts +3 -3
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
- package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
- package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/context-memory-e2e.test.ts +0 -415
- package/src/__tests__/journal-context.test.ts +0 -268
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
- package/src/__tests__/memory-query-builder.test.ts +0 -59
- package/src/__tests__/memory-recall-quality.test.ts +0 -1046
- package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
- package/src/__tests__/memory-regressions.test.ts +0 -3696
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
- package/src/daemon/conversation-memory.ts +0 -207
- package/src/memory/conversation-starters-cadence.ts +0 -74
- package/src/memory/items-extractor.ts +0 -860
- package/src/memory/job-handlers/batch-extraction.ts +0 -753
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
- package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
- package/src/memory/journal-memory.ts +0 -224
- package/src/memory/query-builder.ts +0 -47
- package/src/memory/query-expansion.ts +0 -83
- package/src/memory/retriever.test.ts +0 -1592
- package/src/memory/retriever.ts +0 -1331
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -139
- package/src/memory/search/ranking.ts +0 -15
- package/src/memory/search/staleness.ts +0 -40
- package/src/memory/search/tier-classifier.ts +0 -18
- package/src/memory/search/types.ts +0 -121
- package/src/prompts/journal-context.ts +0 -154
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -562
- package/src/tools/memory/handlers.ts +0 -434
package/src/skills/clawhub.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync,
|
|
3
|
-
readdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
statSync,
|
|
6
|
-
writeFileSync,
|
|
7
|
-
} from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
2
|
import { dirname, join } from "node:path";
|
|
9
3
|
|
|
10
4
|
import { getLogger } from "../util/logger.js";
|
|
11
5
|
import { getWorkspaceSkillsDir } from "../util/platform.js";
|
|
6
|
+
import {
|
|
7
|
+
computeSkillHash,
|
|
8
|
+
readInstallMeta,
|
|
9
|
+
writeInstallMeta,
|
|
10
|
+
} from "./install-meta.js";
|
|
12
11
|
|
|
13
12
|
const log = getLogger("clawhub");
|
|
14
13
|
|
|
@@ -54,61 +53,15 @@ export function loadIntegrityManifest(): IntegrityManifest {
|
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
function saveIntegrityManifest(manifest: IntegrityManifest): void {
|
|
58
|
-
writeFileSync(
|
|
59
|
-
getIntegrityPath(),
|
|
60
|
-
JSON.stringify(manifest, null, 2) + "\n",
|
|
61
|
-
"utf-8",
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Collect all file contents in a directory tree, sorted by relative path for determinism. */
|
|
66
|
-
function collectFileContents(
|
|
67
|
-
dir: string,
|
|
68
|
-
prefix = "",
|
|
69
|
-
): Array<{ relPath: string; content: Buffer }> {
|
|
70
|
-
const results: Array<{ relPath: string; content: Buffer }> = [];
|
|
71
|
-
if (!existsSync(dir)) return results;
|
|
72
|
-
|
|
73
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
74
|
-
for (const entry of entries) {
|
|
75
|
-
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
76
|
-
const fullPath = join(dir, entry.name);
|
|
77
|
-
if (entry.isDirectory()) {
|
|
78
|
-
results.push(...collectFileContents(fullPath, relPath));
|
|
79
|
-
} else if (entry.isFile()) {
|
|
80
|
-
results.push({ relPath, content: readFileSync(fullPath) });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return results.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Compute a SHA-256 hash over all files in a skill directory.
|
|
88
|
-
* Returns format: "v2:sha256hex" (version prefix added to support hash format evolution).
|
|
89
|
-
*/
|
|
90
|
-
function computeSkillHash(skillDir: string): string | null {
|
|
91
|
-
if (!existsSync(skillDir) || !statSync(skillDir).isDirectory()) return null;
|
|
92
|
-
|
|
93
|
-
const files = collectFileContents(skillDir);
|
|
94
|
-
if (files.length === 0) return null;
|
|
95
|
-
|
|
96
|
-
const hasher = new Bun.CryptoHasher("sha256");
|
|
97
|
-
for (const file of files) {
|
|
98
|
-
// Length-prefix each segment to prevent boundary ambiguity collisions
|
|
99
|
-
const pathBuf = Buffer.from(file.relPath, "utf-8");
|
|
100
|
-
hasher.update(`${pathBuf.length}:`);
|
|
101
|
-
hasher.update(pathBuf);
|
|
102
|
-
hasher.update(`${file.content.length}:`);
|
|
103
|
-
hasher.update(file.content);
|
|
104
|
-
}
|
|
105
|
-
return `v2:${hasher.digest("hex")}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
56
|
/**
|
|
109
57
|
* Record or verify the content hash of an installed skill.
|
|
110
58
|
* On first install: stores the hash (trust-on-first-use).
|
|
111
59
|
* On subsequent installs: compares with stored hash and warns on mismatch.
|
|
60
|
+
*
|
|
61
|
+
* Always writes to `install-meta.json`. For skills that don't have one yet,
|
|
62
|
+
* creates a new `install-meta.json` with minimal metadata. Reads from the
|
|
63
|
+
* legacy `.integrity.json` manifest as a read-only fallback for stored hashes
|
|
64
|
+
* from skills that haven't been migrated yet.
|
|
112
65
|
*/
|
|
113
66
|
export function verifyAndRecordSkillHash(slug: string): void {
|
|
114
67
|
const skillDir = join(getManagedSkillsDir(), slug);
|
|
@@ -118,17 +71,28 @@ export function verifyAndRecordSkillHash(slug: string): void {
|
|
|
118
71
|
return;
|
|
119
72
|
}
|
|
120
73
|
|
|
121
|
-
|
|
122
|
-
const
|
|
74
|
+
// Try install-meta.json first for stored hash
|
|
75
|
+
const installMeta = readInstallMeta(skillDir);
|
|
76
|
+
let storedHash: string | undefined;
|
|
123
77
|
|
|
124
|
-
if (
|
|
125
|
-
|
|
78
|
+
if (installMeta?.contentHash) {
|
|
79
|
+
storedHash = installMeta.contentHash;
|
|
80
|
+
} else {
|
|
81
|
+
// Fall back to legacy .integrity.json manifest
|
|
82
|
+
const manifest = loadIntegrityManifest();
|
|
83
|
+
const existing = manifest[slug];
|
|
84
|
+
if (existing) {
|
|
85
|
+
storedHash =
|
|
86
|
+
typeof existing.sha256 === "string" ? existing.sha256 : undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
126
89
|
|
|
127
|
-
|
|
90
|
+
if (storedHash) {
|
|
91
|
+
// Guard against corrupted entries where hash is not a string
|
|
128
92
|
if (typeof storedHash !== "string") {
|
|
129
93
|
log.warn(
|
|
130
94
|
{ slug },
|
|
131
|
-
"
|
|
95
|
+
"Stored hash has non-string value — re-recording hash",
|
|
132
96
|
);
|
|
133
97
|
} else if (!storedHash.startsWith("v2:")) {
|
|
134
98
|
// Unknown format (not v2: prefix) — warn about integrity mismatch
|
|
@@ -155,9 +119,18 @@ export function verifyAndRecordSkillHash(slug: string): void {
|
|
|
155
119
|
);
|
|
156
120
|
}
|
|
157
121
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
122
|
+
// Write to install-meta.json (preferred). If no install-meta exists yet,
|
|
123
|
+
// create one with minimal metadata.
|
|
124
|
+
if (installMeta) {
|
|
125
|
+
writeInstallMeta(skillDir, { ...installMeta, contentHash: hash });
|
|
126
|
+
} else {
|
|
127
|
+
writeInstallMeta(skillDir, {
|
|
128
|
+
origin: "clawhub",
|
|
129
|
+
installedAt: new Date().toISOString(),
|
|
130
|
+
slug,
|
|
131
|
+
contentHash: hash,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
161
134
|
}
|
|
162
135
|
|
|
163
136
|
interface ClawhubInstallResult {
|
|
@@ -248,7 +221,7 @@ async function runClawhub(
|
|
|
248
221
|
|
|
249
222
|
export async function clawhubInstall(
|
|
250
223
|
slug: string,
|
|
251
|
-
opts?: { version?: string },
|
|
224
|
+
opts?: { version?: string; contactId?: string },
|
|
252
225
|
): Promise<ClawhubInstallResult> {
|
|
253
226
|
if (!validateSlug(slug)) {
|
|
254
227
|
return { success: false, error: `Invalid skill slug: ${slug}` };
|
|
@@ -264,7 +237,19 @@ export async function clawhubInstall(
|
|
|
264
237
|
result.stderr.trim() || result.stdout.trim() || "Unknown error";
|
|
265
238
|
return { success: false, error };
|
|
266
239
|
}
|
|
267
|
-
|
|
240
|
+
|
|
241
|
+
// Write install-meta.json for the installed skill.
|
|
242
|
+
// contentHash is included here, so there's no need to call
|
|
243
|
+
// verifyAndRecordSkillHash() — it would just rewrite the same data.
|
|
244
|
+
const skillDir = join(getManagedSkillsDir(), slug);
|
|
245
|
+
writeInstallMeta(skillDir, {
|
|
246
|
+
origin: "clawhub",
|
|
247
|
+
slug,
|
|
248
|
+
installedAt: new Date().toISOString(),
|
|
249
|
+
...(opts?.contactId ? { installedBy: opts.contactId } : {}),
|
|
250
|
+
contentHash: computeSkillHash(skillDir) ?? undefined,
|
|
251
|
+
});
|
|
252
|
+
|
|
268
253
|
return { success: true, skillName: slug };
|
|
269
254
|
} catch (err) {
|
|
270
255
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -292,63 +277,63 @@ export async function clawhubUpdate(
|
|
|
292
277
|
|
|
293
278
|
export async function clawhubSearch(
|
|
294
279
|
query: string,
|
|
280
|
+
opts?: { limit?: number },
|
|
295
281
|
): Promise<ClawhubSearchResult> {
|
|
282
|
+
const limit = opts?.limit ?? 25;
|
|
283
|
+
|
|
296
284
|
// Empty query: use explore (browse trending) instead of search
|
|
297
285
|
if (!query.trim()) {
|
|
298
|
-
return clawhubExplore();
|
|
286
|
+
return clawhubExplore({ limit });
|
|
299
287
|
}
|
|
300
288
|
|
|
289
|
+
const result = await runClawhub(["search", query, "--limit", String(limit)]);
|
|
290
|
+
if (result.exitCode !== 0) {
|
|
291
|
+
const error =
|
|
292
|
+
result.stderr.trim() || result.stdout.trim() || "Unknown error";
|
|
293
|
+
throw new Error(`clawhub search failed: ${error}`);
|
|
294
|
+
}
|
|
295
|
+
// Try JSON first
|
|
301
296
|
try {
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
return {
|
|
297
|
+
const parsed = JSON.parse(result.stdout);
|
|
298
|
+
if (Array.isArray(parsed)) {
|
|
299
|
+
return {
|
|
300
|
+
skills: parsed.map((s: ClawhubSearchResultItem) => ({
|
|
301
|
+
...s,
|
|
302
|
+
source: s.source ?? ("clawhub" as const),
|
|
303
|
+
})),
|
|
304
|
+
};
|
|
305
305
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
source: s.source ?? ("clawhub" as const),
|
|
314
|
-
})),
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
if (parsed.skills && Array.isArray(parsed.skills)) {
|
|
318
|
-
return {
|
|
319
|
-
skills: parsed.skills.map((s: ClawhubSearchResultItem) => ({
|
|
320
|
-
...s,
|
|
321
|
-
source: s.source ?? ("clawhub" as const),
|
|
322
|
-
})),
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
} catch {
|
|
326
|
-
// CLI outputs text: "slug vVersion DisplayName (score)"
|
|
306
|
+
if (parsed.skills && Array.isArray(parsed.skills)) {
|
|
307
|
+
return {
|
|
308
|
+
skills: parsed.skills.map((s: ClawhubSearchResultItem) => ({
|
|
309
|
+
...s,
|
|
310
|
+
source: s.source ?? ("clawhub" as const),
|
|
311
|
+
})),
|
|
312
|
+
};
|
|
327
313
|
}
|
|
314
|
+
} catch {
|
|
315
|
+
// CLI outputs text: "slug vVersion DisplayName (score)"
|
|
316
|
+
}
|
|
328
317
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
318
|
+
// Parse text output lines: "slug vVersion Display Name (score)"
|
|
319
|
+
const skills: ClawhubSearchResultItem[] = [];
|
|
320
|
+
for (const line of result.stdout.split("\n")) {
|
|
321
|
+
const match = line.match(/^(\S+)\s+v(\S+)\s+(.+?)\s+\([\d.]+\)\s*$/);
|
|
322
|
+
if (match) {
|
|
323
|
+
skills.push({
|
|
324
|
+
slug: match[1],
|
|
325
|
+
version: match[2],
|
|
326
|
+
name: match[3].trim(),
|
|
327
|
+
description: "",
|
|
328
|
+
author: "",
|
|
329
|
+
stars: 0,
|
|
330
|
+
installs: 0,
|
|
331
|
+
createdAt: 0,
|
|
332
|
+
source: "clawhub",
|
|
333
|
+
});
|
|
346
334
|
}
|
|
347
|
-
return { skills };
|
|
348
|
-
} catch (err) {
|
|
349
|
-
log.warn({ err }, "clawhub search failed");
|
|
350
|
-
return { skills: [] };
|
|
351
335
|
}
|
|
336
|
+
return { skills };
|
|
352
337
|
}
|
|
353
338
|
|
|
354
339
|
export async function clawhubExplore(opts?: {
|
|
@@ -358,46 +343,41 @@ export async function clawhubExplore(opts?: {
|
|
|
358
343
|
const limit = String(opts?.limit ?? 25);
|
|
359
344
|
const sort = opts?.sort ?? "installsAllTime";
|
|
360
345
|
|
|
346
|
+
const result = await runClawhub([
|
|
347
|
+
"explore",
|
|
348
|
+
"--json",
|
|
349
|
+
"--limit",
|
|
350
|
+
limit,
|
|
351
|
+
"--sort",
|
|
352
|
+
sort,
|
|
353
|
+
]);
|
|
354
|
+
if (result.exitCode !== 0) {
|
|
355
|
+
const error =
|
|
356
|
+
result.stderr.trim() || result.stdout.trim() || "Unknown error";
|
|
357
|
+
throw new Error(`clawhub explore failed: ${error}`);
|
|
358
|
+
}
|
|
361
359
|
try {
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
description: (item.summary as string) ?? "",
|
|
384
|
-
author: (item.author as string) ?? "",
|
|
385
|
-
stars: (item.stats as Record<string, number>)?.stars ?? 0,
|
|
386
|
-
installs:
|
|
387
|
-
(item.stats as Record<string, number>)?.installsAllTime ?? 0,
|
|
388
|
-
version: (item.tags as Record<string, string>)?.latest ?? "",
|
|
389
|
-
createdAt: (item.createdAt as number) ?? 0,
|
|
390
|
-
source: "clawhub",
|
|
391
|
-
}),
|
|
392
|
-
);
|
|
393
|
-
return { skills };
|
|
394
|
-
} catch {
|
|
395
|
-
// parse failure
|
|
396
|
-
}
|
|
397
|
-
return { skills: [] };
|
|
398
|
-
} catch (err) {
|
|
399
|
-
log.warn({ err }, "clawhub explore failed");
|
|
400
|
-
return { skills: [] };
|
|
360
|
+
const parsed = JSON.parse(result.stdout);
|
|
361
|
+
const items = parsed.items ?? parsed;
|
|
362
|
+
if (!Array.isArray(items)) return { skills: [] };
|
|
363
|
+
|
|
364
|
+
// Normalize explore response to ClawhubSearchResultItem shape
|
|
365
|
+
const skills: ClawhubSearchResultItem[] = items.map(
|
|
366
|
+
(item: Record<string, unknown>) => ({
|
|
367
|
+
name: (item.displayName as string) ?? (item.slug as string) ?? "",
|
|
368
|
+
slug: (item.slug as string) ?? "",
|
|
369
|
+
description: (item.summary as string) ?? "",
|
|
370
|
+
author: (item.author as string) ?? "",
|
|
371
|
+
stars: (item.stats as Record<string, number>)?.stars ?? 0,
|
|
372
|
+
installs: (item.stats as Record<string, number>)?.installsAllTime ?? 0,
|
|
373
|
+
version: (item.tags as Record<string, string>)?.latest ?? "",
|
|
374
|
+
createdAt: (item.createdAt as number) ?? 0,
|
|
375
|
+
source: "clawhub",
|
|
376
|
+
}),
|
|
377
|
+
);
|
|
378
|
+
return { skills };
|
|
379
|
+
} catch {
|
|
380
|
+
throw new Error("Failed to parse clawhub explore output");
|
|
401
381
|
}
|
|
402
382
|
}
|
|
403
383
|
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
|
|
13
|
+
// ─── SkillInstallMeta type ──────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface SkillInstallMeta {
|
|
16
|
+
origin: "vellum" | "clawhub" | "skillssh" | "custom";
|
|
17
|
+
installedAt: string; // ISO 8601
|
|
18
|
+
installedBy?: string; // actorPrincipalId from auth context (identifies who initiated the install)
|
|
19
|
+
backfilledBy?: string; // set by migration that backfilled this file (e.g. "migration-026")
|
|
20
|
+
version?: string; // semver if known
|
|
21
|
+
slug?: string; // registry slug
|
|
22
|
+
sourceRepo?: string; // GitHub repo (e.g. "vercel-labs/agent-skills")
|
|
23
|
+
contentHash?: string; // SHA-256 content hash (v2:hex format)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Atomic write helper ────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function atomicWriteFile(filePath: string, content: string): void {
|
|
29
|
+
const dir = dirname(filePath);
|
|
30
|
+
mkdirSync(dir, { recursive: true });
|
|
31
|
+
const tmpPath = join(dir, `.tmp-${randomUUID()}`);
|
|
32
|
+
writeFileSync(tmpPath, content, "utf-8");
|
|
33
|
+
renameSync(tmpPath, filePath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Write install-meta.json ────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const INSTALL_META_FILENAME = "install-meta.json";
|
|
39
|
+
const LEGACY_VERSION_FILENAME = "version.json";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Atomically write `install-meta.json` inside the skill directory.
|
|
43
|
+
*/
|
|
44
|
+
export function writeInstallMeta(
|
|
45
|
+
skillDir: string,
|
|
46
|
+
meta: SkillInstallMeta,
|
|
47
|
+
): void {
|
|
48
|
+
const filePath = join(skillDir, INSTALL_META_FILENAME);
|
|
49
|
+
atomicWriteFile(filePath, JSON.stringify(meta, null, 2) + "\n");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Read install-meta.json (with legacy fallback) ──────────────────────────
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reads `install-meta.json` from the skill directory. If not found, falls
|
|
56
|
+
* back to reading legacy `version.json` and inferring the origin:
|
|
57
|
+
*
|
|
58
|
+
* - Has `origin: "skills.sh"` -> `origin: "skillssh"`, copies `source` as
|
|
59
|
+
* `sourceRepo` and `skillSlug` as `slug`.
|
|
60
|
+
* - Has `version` but no `origin` field -> `origin: "vellum"`.
|
|
61
|
+
* - Otherwise -> `origin: "custom"`.
|
|
62
|
+
*
|
|
63
|
+
* Legacy files never have `installedBy`, so it will be `undefined` for
|
|
64
|
+
* backfilled skills.
|
|
65
|
+
*
|
|
66
|
+
* If neither file exists, returns `null`.
|
|
67
|
+
*/
|
|
68
|
+
export function readInstallMeta(skillDir: string): SkillInstallMeta | null {
|
|
69
|
+
// Try install-meta.json first
|
|
70
|
+
const metaPath = join(skillDir, INSTALL_META_FILENAME);
|
|
71
|
+
if (existsSync(metaPath)) {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(readFileSync(metaPath, "utf-8")) as SkillInstallMeta;
|
|
74
|
+
} catch {
|
|
75
|
+
// Malformed install-meta.json (partial write, manual edit, etc.) —
|
|
76
|
+
// fall through to the legacy version.json path so we don't lose
|
|
77
|
+
// provenance info when a valid legacy file exists.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Fall back to legacy version.json
|
|
82
|
+
const legacyPath = join(skillDir, LEGACY_VERSION_FILENAME);
|
|
83
|
+
if (!existsSync(legacyPath)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const raw = JSON.parse(readFileSync(legacyPath, "utf-8")) as Record<
|
|
89
|
+
string,
|
|
90
|
+
unknown
|
|
91
|
+
>;
|
|
92
|
+
return inferFromLegacyVersionJson(raw);
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Infer a SkillInstallMeta from a legacy version.json object.
|
|
100
|
+
*/
|
|
101
|
+
function inferFromLegacyVersionJson(
|
|
102
|
+
raw: Record<string, unknown>,
|
|
103
|
+
): SkillInstallMeta {
|
|
104
|
+
// skills.sh origin: has `origin: "skills.sh"`
|
|
105
|
+
if (raw.origin === "skills.sh") {
|
|
106
|
+
return {
|
|
107
|
+
origin: "skillssh",
|
|
108
|
+
installedAt:
|
|
109
|
+
typeof raw.installedAt === "string"
|
|
110
|
+
? raw.installedAt
|
|
111
|
+
: new Date().toISOString(),
|
|
112
|
+
sourceRepo: typeof raw.source === "string" ? raw.source : undefined,
|
|
113
|
+
slug: typeof raw.skillSlug === "string" ? raw.skillSlug : undefined,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Vellum (first-party catalog) origin: has `version` but no `origin` field
|
|
118
|
+
if (typeof raw.version === "string" && !("origin" in raw)) {
|
|
119
|
+
return {
|
|
120
|
+
origin: "vellum",
|
|
121
|
+
installedAt:
|
|
122
|
+
typeof raw.installedAt === "string"
|
|
123
|
+
? raw.installedAt
|
|
124
|
+
: new Date().toISOString(),
|
|
125
|
+
version: raw.version,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Unknown format -> custom
|
|
130
|
+
return {
|
|
131
|
+
origin: "custom",
|
|
132
|
+
installedAt:
|
|
133
|
+
typeof raw.installedAt === "string"
|
|
134
|
+
? raw.installedAt
|
|
135
|
+
: new Date().toISOString(),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Content hash computation ───────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Metadata files excluded from content hashing. These are written by the
|
|
143
|
+
* installer and must not contribute to the content hash — otherwise the hash
|
|
144
|
+
* stored inside `install-meta.json` would change after writing the file.
|
|
145
|
+
*/
|
|
146
|
+
const METADATA_FILENAMES = new Set([
|
|
147
|
+
INSTALL_META_FILENAME, // install-meta.json
|
|
148
|
+
LEGACY_VERSION_FILENAME, // version.json
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Collect all file contents in a directory tree, sorted by relative path
|
|
153
|
+
* for determinism. Metadata files (`install-meta.json`, `version.json`) at
|
|
154
|
+
* the root level are excluded so the content hash covers only actual skill
|
|
155
|
+
* content.
|
|
156
|
+
*/
|
|
157
|
+
export function collectFileContents(
|
|
158
|
+
dir: string,
|
|
159
|
+
prefix = "",
|
|
160
|
+
): Array<{ relPath: string; content: Buffer }> {
|
|
161
|
+
const results: Array<{ relPath: string; content: Buffer }> = [];
|
|
162
|
+
if (!existsSync(dir)) return results;
|
|
163
|
+
|
|
164
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
// Exclude metadata files at the root level (prefix === "").
|
|
167
|
+
// Only exclude actual files — a directory with a metadata name should
|
|
168
|
+
// still be traversed so nested content contributes to the hash.
|
|
169
|
+
if (!prefix && entry.isFile() && METADATA_FILENAMES.has(entry.name))
|
|
170
|
+
continue;
|
|
171
|
+
|
|
172
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
173
|
+
const fullPath = join(dir, entry.name);
|
|
174
|
+
if (entry.isDirectory()) {
|
|
175
|
+
results.push(...collectFileContents(fullPath, relPath));
|
|
176
|
+
} else if (entry.isFile()) {
|
|
177
|
+
results.push({ relPath, content: readFileSync(fullPath) });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return results.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Compute a SHA-256 hash over all files in a skill directory.
|
|
185
|
+
* Returns format: "v2:sha256hex" (version prefix added to support hash format
|
|
186
|
+
* evolution).
|
|
187
|
+
*
|
|
188
|
+
* This is the content hash used by the integrity manifest (trust-on-first-use).
|
|
189
|
+
* It differs from `computeSkillVersionHash` in `version-hash.ts`, which uses a
|
|
190
|
+
* different hashing strategy (v1: prefix) for version identity.
|
|
191
|
+
*/
|
|
192
|
+
export function computeSkillHash(skillDir: string): string | null {
|
|
193
|
+
if (!existsSync(skillDir) || !statSync(skillDir).isDirectory()) return null;
|
|
194
|
+
|
|
195
|
+
const files = collectFileContents(skillDir);
|
|
196
|
+
if (files.length === 0) return null;
|
|
197
|
+
|
|
198
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
199
|
+
for (const file of files) {
|
|
200
|
+
// Length-prefix each segment to prevent boundary ambiguity collisions
|
|
201
|
+
const pathBuf = Buffer.from(file.relPath, "utf-8");
|
|
202
|
+
hasher.update(`${pathBuf.length}:`);
|
|
203
|
+
hasher.update(pathBuf);
|
|
204
|
+
hasher.update(`${file.content.length}:`);
|
|
205
|
+
hasher.update(file.content);
|
|
206
|
+
}
|
|
207
|
+
return `v2:${hasher.digest("hex")}`;
|
|
208
|
+
}
|
|
@@ -13,6 +13,7 @@ import { stringify as stringifyYaml } from "yaml";
|
|
|
13
13
|
|
|
14
14
|
import { getLogger } from "../util/logger.js";
|
|
15
15
|
import { getWorkspaceSkillsDir } from "../util/platform.js";
|
|
16
|
+
import { writeInstallMeta } from "./install-meta.js";
|
|
16
17
|
import { deleteSkillCapabilityMemory } from "./skill-memory.js";
|
|
17
18
|
|
|
18
19
|
const log = getLogger("managed-store");
|
|
@@ -165,15 +166,20 @@ function getVersionMetaPath(id: string): string {
|
|
|
165
166
|
return join(getManagedSkillDir(id), "version.json");
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
function writeVersionMeta(id: string, version: string): void {
|
|
169
|
-
const meta: SkillVersionMeta = {
|
|
170
|
-
version,
|
|
171
|
-
installedAt: new Date().toISOString(),
|
|
172
|
-
};
|
|
173
|
-
atomicWriteFile(getVersionMetaPath(id), JSON.stringify(meta, null, 2) + "\n");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
169
|
export function readSkillVersion(id: string): string | null {
|
|
170
|
+
// Try install-meta.json first (new format)
|
|
171
|
+
const installMetaPath = join(getManagedSkillDir(id), "install-meta.json");
|
|
172
|
+
if (existsSync(installMetaPath)) {
|
|
173
|
+
try {
|
|
174
|
+
const raw = readFileSync(installMetaPath, "utf-8");
|
|
175
|
+
const meta = JSON.parse(raw) as { version?: string };
|
|
176
|
+
if (meta.version) return meta.version;
|
|
177
|
+
} catch {
|
|
178
|
+
// Fall through to legacy path
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Fall back to legacy version.json
|
|
177
183
|
const metaPath = getVersionMetaPath(id);
|
|
178
184
|
if (!existsSync(metaPath)) return null;
|
|
179
185
|
try {
|
|
@@ -197,6 +203,7 @@ interface CreateManagedSkillParams {
|
|
|
197
203
|
addToIndex?: boolean;
|
|
198
204
|
includes?: string[];
|
|
199
205
|
version?: string;
|
|
206
|
+
contactId?: string;
|
|
200
207
|
}
|
|
201
208
|
|
|
202
209
|
interface CreateManagedSkillResult {
|
|
@@ -259,14 +266,18 @@ export function createManagedSkill(
|
|
|
259
266
|
mkdirSync(skillDir, { recursive: true });
|
|
260
267
|
atomicWriteFile(skillFilePath, content);
|
|
261
268
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
// Write install metadata
|
|
270
|
+
writeInstallMeta(skillDir, {
|
|
271
|
+
origin: "custom",
|
|
272
|
+
installedAt: new Date().toISOString(),
|
|
273
|
+
...(params.version ? { version: params.version } : {}),
|
|
274
|
+
...(params.contactId ? { installedBy: params.contactId } : {}),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Clean up legacy version.json if present (superseded by install-meta.json)
|
|
278
|
+
const metaPath = getVersionMetaPath(params.id);
|
|
279
|
+
if (existsSync(metaPath)) {
|
|
280
|
+
rmSync(metaPath);
|
|
270
281
|
}
|
|
271
282
|
|
|
272
283
|
log.info(
|