@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2
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 +9 -3
- package/ARCHITECTURE.md +32 -10
- package/docs/architecture/memory.md +43 -5
- package/docs/architecture/security.md +96 -152
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/browser-use-architecture-phase2.md +128 -56
- package/docs/flux-turn-detection-spike.md +248 -0
- package/docs/guardian-request-flow.md +35 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/knip.json +3 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +289 -113
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/scripts/write-plugin-api-shim.ts +10 -0
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/app-control-flow.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
- package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/compaction-events.test.ts +8 -10
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +32 -21
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +154 -4
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/conversation-slash-queue.test.ts +3 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/document-sync-tags.test.ts +0 -75
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -11
- package/src/__tests__/file-ops-service.test.ts +163 -30
- package/src/__tests__/filesystem-tools.test.ts +23 -24
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/host-file-read-tool.test.ts +16 -19
- package/src/__tests__/http-user-message-parity.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/media-generate-image.test.ts +131 -21
- package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +3 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/proxy-approval-callback.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +14 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
- package/src/__tests__/run-due-schedules.test.ts +21 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
- package/src/__tests__/schedule-routes.test.ts +23 -0
- package/src/__tests__/schedule-store.test.ts +17 -0
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -3
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
- package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
- package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
- package/src/__tests__/subagent-tools.test.ts +81 -101
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +68 -39
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +76 -3
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/events/host-file.ts +2 -2
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +12 -3
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
- package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
- package/src/calls/call-controller.ts +19 -3
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +115 -41
- package/src/calls/voice-triage-escalate.ts +105 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/bundled-modules.ts +29 -0
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/db/repair.ts +4 -8
- package/src/cli/commands/domain.ts +6 -3
- package/src/cli/commands/email.ts +6 -3
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +84 -28
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/plugins.ts +85 -36
- package/src/cli/commands/schedules.ts +35 -1
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/assistant-feature-flags.ts +36 -15
- package/src/config/balanced-model-experiment.ts +35 -0
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -12
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +11 -5
- package/src/config/default-profile-catalog.ts +179 -16
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/feature-flag-registry.json +12 -12
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/skills.ts +9 -2
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +31 -19
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +20 -10
- package/src/daemon/conversation-process.ts +20 -6
- package/src/daemon/conversation-runtime-assembly.ts +12 -6
- package/src/daemon/conversation-store.ts +14 -4
- package/src/daemon/conversation-surfaces.ts +75 -26
- package/src/daemon/conversation-tool-setup.ts +3 -6
- package/src/daemon/conversation.ts +154 -51
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -26
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/trust-context.ts +12 -7
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/daemon/windows-compiled-entry.ts +4 -0
- package/src/documents/document-store.ts +143 -240
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +103 -45
- package/src/hooks/types.ts +5 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/gateway-flag-listener.ts +17 -3
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-manager.ts +16 -3
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +598 -323
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/live-voice/windows-compiled-live-voice.ts +4 -0
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/control.ts +1 -0
- package/src/monitoring/db-integrity-sample.ts +4 -5
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/approval-card-data.ts +33 -0
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -5
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +182 -52
- package/src/persistence/delivery-crud.ts +150 -36
- package/src/persistence/embeddings/qdrant-manager.ts +84 -49
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/schema/documents.ts +4 -4
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/constants.ts +8 -0
- package/src/plugin-api/conversation-turn.ts +195 -5
- package/src/plugin-api/index.ts +38 -6
- package/src/plugin-api/resolve-credential.ts +3 -2
- package/src/plugin-api/store-credential.ts +146 -0
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +13 -11
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/main.ts +21 -7
- package/src/plugins/defaults/memory/AGENTS.md +15 -3
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/defaults/memory/v3/injector.ts +8 -0
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
- package/src/plugins/defaults/memory/worker-control.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/defaults/worker-entrypoints.ts +3 -0
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +37 -46
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/prompts/templates/system-sections.ts +0 -7
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -2
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +28 -8
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +133 -36
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/control.ts +1 -0
- package/src/routes/route-host-client.ts +1 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +48 -15
- package/src/runtime/AGENTS.md +16 -17
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +18 -15
- package/src/runtime/effective-capabilities.test.ts +43 -0
- package/src/runtime/effective-capabilities.ts +27 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
- package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +181 -23
- package/src/runtime/routes/conversation-management-routes.ts +17 -12
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/conversation-routes.ts +43 -29
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/credential-routes.ts +26 -82
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/documents-routes.ts +3 -222
- package/src/runtime/routes/inbound-message-handler.ts +21 -45
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -2
- package/src/runtime/routes/playground/reset-circuit.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +38 -8
- package/src/runtime/routes/schedule-routes.ts +94 -7
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +69 -15
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/workspace-routes.ts +0 -9
- package/src/runtime/routes/workspace-utils.ts +3 -13
- package/src/runtime/services/conversation-serializer.ts +7 -2
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
- package/src/schedule/plugin-schedule-availability.ts +58 -0
- package/src/schedule/plugin-schedule-declarations.ts +23 -27
- package/src/schedule/plugin-schedule-reconciler.ts +12 -3
- package/src/schedule/schedule-store.ts +5 -1
- package/src/schedule/scheduler.ts +9 -4
- package/src/schedule/worker-control.ts +1 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
- package/src/subagent/consult-context.ts +11 -11
- package/src/subagent/consult-prompt.ts +26 -35
- package/src/subagent/manager.ts +21 -40
- package/src/subagent/notify.ts +7 -1
- package/src/subagent/types.ts +15 -12
- package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +38 -4
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/executor.ts +53 -15
- package/src/tools/filesystem/read.ts +27 -10
- package/src/tools/host-filesystem/read.ts +27 -15
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/registry.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
- package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
- package/src/tools/shared/filesystem/types.ts +5 -5
- package/src/tools/skills/scaffold-managed.ts +25 -7
- package/src/tools/subagent/spawn.ts +28 -88
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
- package/src/tts/reasoning-tag-filter.ts +89 -0
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/util/worker-process.ts +37 -4
- package/src/windows-compiled-cli.ts +32 -0
- package/src/windows-compiled-entry.ts +4 -0
- package/src/windows-compiled-logger.ts +6 -0
- package/src/windows-compiled-worker-entry.ts +29 -0
- package/src/workspace/byok-default-profile-ensure.ts +76 -24
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/__tests__/document-workspace-file.test.ts +0 -467
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -45,7 +45,11 @@
|
|
|
45
45
|
* 6. Verify the run drained the buffer. `runResult.ok` only means the
|
|
46
46
|
* background run completed — the trim itself is delegated to the agent.
|
|
47
47
|
* A run that completes without shrinking the buffer is reported as
|
|
48
|
-
* `invoked` with `noProgress: true` and enqueues no follow-ups.
|
|
48
|
+
* `invoked` with `noProgress: true` and enqueues no follow-ups. The
|
|
49
|
+
* post-run page index is also read for `danglingLinks` (structural
|
|
50
|
+
* references with no target page): reported on the outcome and in the
|
|
51
|
+
* log, and fed into the NEXT pass's prompt as a repair step like
|
|
52
|
+
* `parseFailures`. They never gate follow-ups.
|
|
49
53
|
* 7. On progress, enqueue `memory_v2_reembed` (re-index any pages the agent
|
|
50
54
|
* touched). Tracking touched pages via mtime would be more precise but
|
|
51
55
|
* is fragile across filesystems; the embedder's content-hash cache makes
|
|
@@ -91,7 +95,10 @@ import {
|
|
|
91
95
|
type MemoryJobType,
|
|
92
96
|
} from "../../../../persistence/jobs-store.js";
|
|
93
97
|
import { runBackgroundJob } from "../../../../runtime/background-job-runner.js";
|
|
94
|
-
import {
|
|
98
|
+
import {
|
|
99
|
+
formatBufferTimestamp,
|
|
100
|
+
matchBufferEntryStart,
|
|
101
|
+
} from "../buffer-format.js";
|
|
95
102
|
import { getLogger } from "../logging.js";
|
|
96
103
|
import { getWorkspaceDir } from "../paths.js";
|
|
97
104
|
import {
|
|
@@ -101,6 +108,7 @@ import {
|
|
|
101
108
|
tryAcquireLock,
|
|
102
109
|
} from "./consolidation-lock.js";
|
|
103
110
|
import { getPageIndex, type PageParseFailure } from "./page-index.js";
|
|
111
|
+
import type { DanglingLink } from "./page-links.js";
|
|
104
112
|
import { resolveConsolidationPrompt } from "./prompts/consolidation.js";
|
|
105
113
|
import { resolveSubstrateTuning } from "./tuning.js";
|
|
106
114
|
|
|
@@ -293,8 +301,17 @@ export type ConsolidationOutcome =
|
|
|
293
301
|
* follow-ups were enqueued.
|
|
294
302
|
*/
|
|
295
303
|
noProgress: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Structural references (`links:`, `[[wikilinks]]`, `edges:`) in the
|
|
306
|
+
* post-run corpus whose target page does not exist; `null` when the
|
|
307
|
+
* post-run index could not be read.
|
|
308
|
+
*/
|
|
309
|
+
danglingLinks: number | null;
|
|
296
310
|
};
|
|
297
311
|
|
|
312
|
+
/** Dangling links named in the post-run warn line before the count takes over. */
|
|
313
|
+
const MAX_LOGGED_DANGLING_LINKS = 20;
|
|
314
|
+
|
|
298
315
|
export async function memoryV2ConsolidateJob(
|
|
299
316
|
_job: MemoryJob,
|
|
300
317
|
config: AssistantConfig,
|
|
@@ -416,16 +433,20 @@ export async function memoryV2ConsolidateJob(
|
|
|
416
433
|
// v2-only install must keep producing `summary:`-bearing fragment pages.
|
|
417
434
|
const memoryV3Live = isMemoryV3Live(config);
|
|
418
435
|
// Pages the index build dropped (malformed frontmatter) or degraded
|
|
419
|
-
// (unterminated fence)
|
|
420
|
-
//
|
|
421
|
-
// fail because the index
|
|
436
|
+
// (unterminated fence), and structural references with no target page,
|
|
437
|
+
// rendered into the prompt's repair steps so the agent fixes them this
|
|
438
|
+
// pass. Best-effort: prompt assembly must never fail because the index
|
|
439
|
+
// build did.
|
|
422
440
|
let parseFailures: PageParseFailure[] = [];
|
|
441
|
+
let danglingLinks: DanglingLink[] = [];
|
|
423
442
|
try {
|
|
424
|
-
|
|
443
|
+
const index = await getPageIndex(getWorkspaceDir());
|
|
444
|
+
parseFailures = index.parseFailures;
|
|
445
|
+
danglingLinks = index.danglingLinks;
|
|
425
446
|
} catch (err) {
|
|
426
447
|
log.warn(
|
|
427
448
|
{ err },
|
|
428
|
-
"consolidation: page-index read failed; omitting the repair
|
|
449
|
+
"consolidation: page-index read failed; omitting the repair sections",
|
|
429
450
|
);
|
|
430
451
|
}
|
|
431
452
|
const prompt = resolveConsolidationPrompt(
|
|
@@ -435,6 +456,7 @@ export async function memoryV2ConsolidateJob(
|
|
|
435
456
|
includeCorePagesSection: memoryV3Live,
|
|
436
457
|
articleShape: memoryV3Live ? "v3" : "v2",
|
|
437
458
|
parseFailures,
|
|
459
|
+
danglingLinks,
|
|
438
460
|
},
|
|
439
461
|
);
|
|
440
462
|
|
|
@@ -493,6 +515,13 @@ export async function memoryV2ConsolidateJob(
|
|
|
493
515
|
const bufferLinesAfter = countBufferLines(bufferPath);
|
|
494
516
|
const noProgress = bufferLinesAfter >= bufferLinesBefore;
|
|
495
517
|
|
|
518
|
+
// The agent's file-tool writes invalidate the page index, so this read
|
|
519
|
+
// sees the post-run corpus.
|
|
520
|
+
const danglingAfter = await readDanglingLinks(
|
|
521
|
+
runResult.conversationId,
|
|
522
|
+
danglingLinks.length,
|
|
523
|
+
);
|
|
524
|
+
|
|
496
525
|
// Failure-state bookkeeping. A skipped run (`skipReason`) never invoked
|
|
497
526
|
// the agent, so it neither clears nor records. A completed run that made
|
|
498
527
|
// no progress behaves like a failure for scheduling — the size trigger
|
|
@@ -523,6 +552,7 @@ export async function memoryV2ConsolidateJob(
|
|
|
523
552
|
deferredEntries,
|
|
524
553
|
followUpJobIds: [],
|
|
525
554
|
noProgress: true,
|
|
555
|
+
danglingLinks: danglingAfter,
|
|
526
556
|
};
|
|
527
557
|
}
|
|
528
558
|
|
|
@@ -574,12 +604,47 @@ export async function memoryV2ConsolidateJob(
|
|
|
574
604
|
deferredEntries,
|
|
575
605
|
followUpJobIds,
|
|
576
606
|
noProgress: false,
|
|
607
|
+
danglingLinks: danglingAfter,
|
|
577
608
|
};
|
|
578
609
|
} finally {
|
|
579
610
|
releaseLock(lockPath);
|
|
580
611
|
}
|
|
581
612
|
}
|
|
582
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Post-run dangling-link count; warns (with a capped sample and the pre-run
|
|
616
|
+
* count) when any remain. `null` when the index cannot be read.
|
|
617
|
+
*/
|
|
618
|
+
async function readDanglingLinks(
|
|
619
|
+
conversationId: string,
|
|
620
|
+
danglingBefore: number,
|
|
621
|
+
): Promise<number | null> {
|
|
622
|
+
let dangling: DanglingLink[];
|
|
623
|
+
try {
|
|
624
|
+
dangling = (await getPageIndex(getWorkspaceDir())).danglingLinks;
|
|
625
|
+
} catch (err) {
|
|
626
|
+
log.warn(
|
|
627
|
+
{ err, conversationId },
|
|
628
|
+
"consolidation: post-run page-index read failed; dangling links unknown",
|
|
629
|
+
);
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
if (dangling.length > 0) {
|
|
633
|
+
log.warn(
|
|
634
|
+
{
|
|
635
|
+
conversationId,
|
|
636
|
+
danglingBefore,
|
|
637
|
+
danglingAfter: dangling.length,
|
|
638
|
+
sample: dangling
|
|
639
|
+
.slice(0, MAX_LOGGED_DANGLING_LINKS)
|
|
640
|
+
.map((d) => `${d.from} -> ${d.to} (${d.kind})`),
|
|
641
|
+
},
|
|
642
|
+
"consolidation left structural links whose target page does not exist; the next pass renders them as a repair step",
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
return dangling.length;
|
|
646
|
+
}
|
|
647
|
+
|
|
583
648
|
/**
|
|
584
649
|
* Read `memory/buffer.md`. Missing file → empty string so the skip-on-empty
|
|
585
650
|
* branch doesn't have to distinguish "no file" from "blank file".
|
|
@@ -597,32 +662,36 @@ function readBufferContent(bufferPath: string): string {
|
|
|
597
662
|
|
|
598
663
|
/**
|
|
599
664
|
* Extract the bracketed timestamp from a `buffer.md` entry line
|
|
600
|
-
* (`- [Mon D, h:mm AM/PM] …`, see
|
|
601
|
-
* it can serve directly as a consolidation cutoff
|
|
602
|
-
* "timestamp
|
|
603
|
-
* shape.
|
|
665
|
+
* (`- [Mon D, h:mm AM/PM] …`, see {@link formatRememberEntry}). Returned
|
|
666
|
+
* verbatim so it can serve directly as a consolidation cutoff: both sides of
|
|
667
|
+
* the agent's "timestamp >= cutoff" comparison then share the exact
|
|
668
|
+
* {@link formatBufferTimestamp} shape.
|
|
604
669
|
*
|
|
605
|
-
*
|
|
606
|
-
* continuation lines
|
|
607
|
-
*
|
|
608
|
-
* would inflate the per-run budget
|
|
609
|
-
*
|
|
610
|
-
* timestamped entry start.
|
|
670
|
+
* Recognition is delegated to the shared matcher, so a remembered fact's
|
|
671
|
+
* continuation lines never register as entries. That matters here beyond
|
|
672
|
+
* tidiness: counting a fact's `- [ ] …` checklist lines or an indented
|
|
673
|
+
* entry-shaped body line as entries would inflate the per-run budget, or hand
|
|
674
|
+
* the agent a cutoff drawn from the middle of a fact.
|
|
611
675
|
*/
|
|
612
676
|
function extractBufferEntryTimestamp(line: string): string | null {
|
|
613
|
-
|
|
614
|
-
line,
|
|
615
|
-
);
|
|
616
|
-
return match ? match[1] : null;
|
|
677
|
+
return matchBufferEntryStart(line)?.timestamp ?? null;
|
|
617
678
|
}
|
|
618
679
|
|
|
619
680
|
/**
|
|
620
681
|
* Count non-empty lines in `memory/buffer.md`. Used by the scheduler to
|
|
621
682
|
* implement the size-based consolidation trigger. Missing file → 0.
|
|
622
683
|
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
684
|
+
* Lines, deliberately, not entries. A multiline `remember()` fact is one
|
|
685
|
+
* entry spread over several lines, so the two counts diverge and each answers
|
|
686
|
+
* a different question. This trigger and the injected-Buffer cap that reuses
|
|
687
|
+
* it (`capBufferSection` in `static-context.ts`) both care about how much
|
|
688
|
+
* context the buffer costs, which scales with lines. The per-run budget
|
|
689
|
+
* `consolidation_max_entries_per_run` cares about how many facts the agent
|
|
690
|
+
* must file, so it counts entry-start lines instead.
|
|
691
|
+
*
|
|
692
|
+
* Do not "fix" this to count entries: a single 200-line fact is a context
|
|
693
|
+
* problem the trigger should fire on, even though it is one entry. Blank
|
|
694
|
+
* lines and trailing newlines don't inflate the count.
|
|
626
695
|
*/
|
|
627
696
|
export function countBufferLines(bufferPath: string): number {
|
|
628
697
|
return countNonEmptyLines(readBufferContent(bufferPath));
|
|
@@ -166,37 +166,6 @@ export function getReachable(
|
|
|
166
166
|
return result;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
export interface EdgeValidationResult {
|
|
170
|
-
ok: boolean;
|
|
171
|
-
/**
|
|
172
|
-
* Outgoing-edge targets that don't correspond to any known slug. Sorted by
|
|
173
|
-
* `(from, to)` for deterministic output.
|
|
174
|
-
*/
|
|
175
|
-
missing: Array<{ from: string; to: string }>;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Validate every outgoing edge target against `knownSlugs`. Returns a sorted
|
|
180
|
-
* list of `(from, to)` pairs whose `to` slug has no corresponding concept
|
|
181
|
-
* page on disk.
|
|
182
|
-
*/
|
|
183
|
-
export function validateEdgeTargets(
|
|
184
|
-
index: EdgeIndex,
|
|
185
|
-
knownSlugs: ReadonlySet<string>,
|
|
186
|
-
): EdgeValidationResult {
|
|
187
|
-
const missing: Array<{ from: string; to: string }> = [];
|
|
188
|
-
const sources = [...index.outgoing.keys()].sort();
|
|
189
|
-
for (const from of sources) {
|
|
190
|
-
const targets = [...(index.outgoing.get(from) ?? [])].sort();
|
|
191
|
-
for (const to of targets) {
|
|
192
|
-
if (!knownSlugs.has(to)) {
|
|
193
|
-
missing.push({ from, to });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return { ok: missing.length === 0, missing };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
169
|
/**
|
|
201
170
|
* Total count of directed edges in the index — i.e. the sum of every page's
|
|
202
171
|
* outgoing-edge fanout. Each (from, to) pair counts once.
|
|
@@ -29,7 +29,9 @@ import {
|
|
|
29
29
|
tryAcquireLock,
|
|
30
30
|
} from "./consolidation-lock.js";
|
|
31
31
|
import { parseOriginDate } from "./page-index.js";
|
|
32
|
+
import { findDanglingLinks } from "./page-links.js";
|
|
32
33
|
import {
|
|
34
|
+
isValidSlug,
|
|
33
35
|
listPages,
|
|
34
36
|
parsePageContent,
|
|
35
37
|
validateSlug,
|
|
@@ -127,6 +129,14 @@ const REINDEX_JOB_TYPES: readonly MemoryJobType[] = [
|
|
|
127
129
|
* consolidation lock, so a page committed by another writer between
|
|
128
130
|
* validation and the write phase is still skipped rather than
|
|
129
131
|
* overwritten.
|
|
132
|
+
* 5. One more non-blocking warning, once the on-disk snapshot exists: a
|
|
133
|
+
* `links:`, `[[wikilink]]`, or `edges:` target that is neither on disk
|
|
134
|
+
* nor a valid page in this batch. Retrieval drops such a reference, so
|
|
135
|
+
* the caller either stages the missing page or turns the reference into
|
|
136
|
+
* prose. Warned, not rejected: a page is not worth losing over one link,
|
|
137
|
+
* and a multi-batch import legitimately references a later batch.
|
|
138
|
+
* Targets under the synthetic capability prefixes are not checked (their
|
|
139
|
+
* catalog is not on disk).
|
|
130
140
|
*
|
|
131
141
|
* With `dryRun` the summary is returned without touching disk or the lock;
|
|
132
142
|
* the dry-run collision snapshot is lock-free and therefore advisory.
|
|
@@ -250,6 +260,7 @@ export async function ingestPages(
|
|
|
250
260
|
// concurrent writer can change what a later real run would do.
|
|
251
261
|
const existingSlugs = new Set(await listPages(workspaceDir));
|
|
252
262
|
classifyCollisions(pending, existingSlugs, overwrite);
|
|
263
|
+
warnDanglingLinks(pending, existingSlugs);
|
|
253
264
|
return summarize(results, dryRun);
|
|
254
265
|
}
|
|
255
266
|
|
|
@@ -264,6 +275,7 @@ export async function ingestPages(
|
|
|
264
275
|
// writer after validation still classifies as a collision.
|
|
265
276
|
const existingSlugs = new Set(await listPages(workspaceDir));
|
|
266
277
|
const toWrite = classifyCollisions(pending, existingSlugs, overwrite);
|
|
278
|
+
warnDanglingLinks(pending, existingSlugs);
|
|
267
279
|
const summary = summarize(results, dryRun);
|
|
268
280
|
|
|
269
281
|
for (const page of toWrite) {
|
|
@@ -319,6 +331,53 @@ function classifyCollisions(
|
|
|
319
331
|
return toWrite;
|
|
320
332
|
}
|
|
321
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Append a dangling-link warning to every page about to be written whose
|
|
336
|
+
* structural references name a slug that is neither on disk nor a valid page
|
|
337
|
+
* in this batch. Runs after collision classification so pages the batch will
|
|
338
|
+
* not write (`skipped_exists`) are not warned about.
|
|
339
|
+
*/
|
|
340
|
+
function warnDanglingLinks(
|
|
341
|
+
pending: readonly PendingPage[],
|
|
342
|
+
existingSlugs: ReadonlySet<string>,
|
|
343
|
+
): void {
|
|
344
|
+
const knownSlugs = new Set(existingSlugs);
|
|
345
|
+
for (const { page } of pending) {
|
|
346
|
+
knownSlugs.add(page.slug);
|
|
347
|
+
}
|
|
348
|
+
const toWrite = pending.filter(({ result }) => result.action === "written");
|
|
349
|
+
const dangling = findDanglingLinks(
|
|
350
|
+
toWrite.map(({ page }) => page),
|
|
351
|
+
knownSlugs,
|
|
352
|
+
isValidSlug,
|
|
353
|
+
).filter(
|
|
354
|
+
(d) => !RESERVED_SLUG_PREFIXES.some((prefix) => d.to.startsWith(prefix)),
|
|
355
|
+
);
|
|
356
|
+
if (dangling.length === 0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const targetsBySlug = new Map<string, string[]>();
|
|
360
|
+
for (const { from, to } of dangling) {
|
|
361
|
+
const targets = targetsBySlug.get(from) ?? [];
|
|
362
|
+
if (!targets.includes(to)) {
|
|
363
|
+
targets.push(to);
|
|
364
|
+
}
|
|
365
|
+
targetsBySlug.set(from, targets);
|
|
366
|
+
}
|
|
367
|
+
for (const { page, result } of toWrite) {
|
|
368
|
+
const targets = targetsBySlug.get(page.slug);
|
|
369
|
+
if (targets === undefined) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
result.warnings.push(
|
|
373
|
+
`link target${targets.length === 1 ? "" : "s"} not on disk or in ` +
|
|
374
|
+
`this batch: ${targets.join(", ")}; stage the missing page or ` +
|
|
375
|
+
`turn the reference into prose (retrieval drops a link whose ` +
|
|
376
|
+
`target page does not exist)`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
322
381
|
function summarize(
|
|
323
382
|
results: IngestPageResult[],
|
|
324
383
|
dryRun: boolean,
|
|
@@ -20,7 +20,14 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { getLogger } from "../logging.js";
|
|
23
|
-
import {
|
|
23
|
+
import { type DanglingLink, findDanglingLinks } from "./page-links.js";
|
|
24
|
+
import {
|
|
25
|
+
getPageMtimeMs,
|
|
26
|
+
isValidSlug,
|
|
27
|
+
listPages,
|
|
28
|
+
readPage,
|
|
29
|
+
} from "./page-store.js";
|
|
30
|
+
import type { ConceptPage } from "./types.js";
|
|
24
31
|
|
|
25
32
|
// Dynamic import for `./skill-store.js` happens inside `getPageIndex` so that
|
|
26
33
|
// modules that only need `invalidatePageIndex` (page-store.ts,
|
|
@@ -103,8 +110,9 @@ export interface PageParseFailure {
|
|
|
103
110
|
* ASCII so IDs are deterministic across rebuilds with the same input. The
|
|
104
111
|
* `bySlug` and `byId` maps are convenience lookups; `rendered` is the prompt
|
|
105
112
|
* block consumed by the router. `parseFailures` lists the pages this build
|
|
106
|
-
* dropped or degraded
|
|
107
|
-
*
|
|
113
|
+
* dropped or degraded and `danglingLinks` the structural references whose
|
|
114
|
+
* target has no entry, so consumers (the consolidation prompt, the validate
|
|
115
|
+
* route) can surface them for repair.
|
|
108
116
|
*/
|
|
109
117
|
export interface PageIndex {
|
|
110
118
|
entries: PageIndexEntry[];
|
|
@@ -112,6 +120,8 @@ export interface PageIndex {
|
|
|
112
120
|
byId: Map<number, PageIndexEntry>;
|
|
113
121
|
rendered: string;
|
|
114
122
|
parseFailures: PageParseFailure[];
|
|
123
|
+
/** Resolved against every `bySlug` key, synthetic rows included. */
|
|
124
|
+
danglingLinks: DanglingLink[];
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
interface CachedIndex {
|
|
@@ -187,6 +197,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
|
|
|
187
197
|
|
|
188
198
|
const drafts: DraftEntry[] = [];
|
|
189
199
|
const parseFailures: PageParseFailure[] = [];
|
|
200
|
+
const linkSources: ConceptPage[] = [];
|
|
190
201
|
for (let i = 0; i < settled.length; i++) {
|
|
191
202
|
const result = settled[i];
|
|
192
203
|
const slug = slugs[i];
|
|
@@ -232,6 +243,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
|
|
|
232
243
|
modifiedAt: mtimeMs,
|
|
233
244
|
freshAt: resolveFreshAt(page.frontmatter.origin_date, mtimeMs),
|
|
234
245
|
});
|
|
246
|
+
linkSources.push(page);
|
|
235
247
|
}
|
|
236
248
|
|
|
237
249
|
for (const entry of skillEntries) {
|
|
@@ -276,8 +288,8 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
|
|
|
276
288
|
return entry;
|
|
277
289
|
});
|
|
278
290
|
|
|
279
|
-
// Edges whose target slug isn't in the index are
|
|
280
|
-
//
|
|
291
|
+
// Edges whose target slug isn't in the index are not resolved here; they are
|
|
292
|
+
// reported in `danglingLinks` below instead.
|
|
281
293
|
for (let i = 0; i < entries.length; i++) {
|
|
282
294
|
const draft = drafts[i];
|
|
283
295
|
const resolved: number[] = [];
|
|
@@ -297,8 +309,21 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
|
|
|
297
309
|
a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0,
|
|
298
310
|
);
|
|
299
311
|
|
|
312
|
+
const danglingLinks = findDanglingLinks(
|
|
313
|
+
linkSources,
|
|
314
|
+
new Set(bySlug.keys()),
|
|
315
|
+
isValidSlug,
|
|
316
|
+
);
|
|
317
|
+
|
|
300
318
|
const rendered = renderIndex(entries);
|
|
301
|
-
const index: PageIndex = {
|
|
319
|
+
const index: PageIndex = {
|
|
320
|
+
entries,
|
|
321
|
+
bySlug,
|
|
322
|
+
byId,
|
|
323
|
+
rendered,
|
|
324
|
+
parseFailures,
|
|
325
|
+
danglingLinks,
|
|
326
|
+
};
|
|
302
327
|
cache = { workspaceDir, index };
|
|
303
328
|
return index;
|
|
304
329
|
}
|
|
@@ -525,9 +550,10 @@ function buildLocalPageIndex(
|
|
|
525
550
|
bySlug: localBySlug,
|
|
526
551
|
byId: localById,
|
|
527
552
|
rendered: renderIndex(localEntries),
|
|
528
|
-
// Parse failures are
|
|
529
|
-
// batch
|
|
553
|
+
// Parse failures and dangling links are properties of the workspace
|
|
554
|
+
// build, not of a carved batch: the full index is the reporting surface.
|
|
530
555
|
parseFailures: [],
|
|
556
|
+
danglingLinks: [],
|
|
531
557
|
};
|
|
532
558
|
}
|
|
533
559
|
|
|
@@ -626,5 +652,6 @@ function emptyPageIndex(): PageIndex {
|
|
|
626
652
|
byId: new Map(),
|
|
627
653
|
rendered: "",
|
|
628
654
|
parseFailures: [],
|
|
655
|
+
danglingLinks: [],
|
|
629
656
|
};
|
|
630
657
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// SUBSTRATE (v2+v3).
|
|
2
|
+
/**
|
|
3
|
+
* Memory substrate: the single owner of concept-page link syntax.
|
|
4
|
+
*
|
|
5
|
+
* A `links:` frontmatter entry is `"<target-slug> <sep> <why>"` with the
|
|
6
|
+
* separator {@link LINK_SEPARATOR}, split on its first occurrence (no
|
|
7
|
+
* separator: bare slug). An inline `[[wikilink]]` may carry `|display` and/or
|
|
8
|
+
* `#anchor`, neither part of the target. `edges:` entries are bare slugs.
|
|
9
|
+
*
|
|
10
|
+
* Every reader of these shapes imports from here (the v3 edge lane, the
|
|
11
|
+
* pending-buffer hints, the page index, ingest); do not add a second wikilink
|
|
12
|
+
* matcher or `links:` splitter, for the same reason `buffer-format.ts` keeps
|
|
13
|
+
* one buffer-entry matcher.
|
|
14
|
+
*
|
|
15
|
+
* Leaf module: imports nothing from the rest of the substrate.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Space, U+2014 EM DASH, space. */
|
|
19
|
+
export const LINK_SEPARATOR = ` ${String.fromCodePoint(0x2014)} `;
|
|
20
|
+
|
|
21
|
+
/** Global; group 1 is the raw inner text. Pair with {@link wikilinkTarget}. */
|
|
22
|
+
export const WIKILINK_REGEX = /\[\[([^\]]+)\]\]/g;
|
|
23
|
+
|
|
24
|
+
export function parseLinkEntry(entry: string): {
|
|
25
|
+
target: string;
|
|
26
|
+
description: string | undefined;
|
|
27
|
+
} {
|
|
28
|
+
const sep = entry.indexOf(LINK_SEPARATOR);
|
|
29
|
+
if (sep === -1) {
|
|
30
|
+
return { target: entry.trim(), description: undefined };
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
target: entry.slice(0, sep).trim(),
|
|
34
|
+
description: entry.slice(sep + LINK_SEPARATOR.length).trim() || undefined,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The slug a wikilink's inner text names; empty for `[[|label]]` / `[[#anchor]]`. */
|
|
39
|
+
export function wikilinkTarget(inner: string): string {
|
|
40
|
+
let target = inner;
|
|
41
|
+
const pipe = target.indexOf("|");
|
|
42
|
+
if (pipe !== -1) {
|
|
43
|
+
target = target.slice(0, pipe);
|
|
44
|
+
}
|
|
45
|
+
const hash = target.indexOf("#");
|
|
46
|
+
if (hash !== -1) {
|
|
47
|
+
target = target.slice(0, hash);
|
|
48
|
+
}
|
|
49
|
+
return target.trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Every wikilink target in `text`, in order, empty targets dropped, duplicates kept. */
|
|
53
|
+
export function extractWikilinkTargets(text: string): string[] {
|
|
54
|
+
const targets: string[] = [];
|
|
55
|
+
for (const match of text.matchAll(WIKILINK_REGEX)) {
|
|
56
|
+
const target = wikilinkTarget(match[1]);
|
|
57
|
+
if (target) {
|
|
58
|
+
targets.push(target);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return targets;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type PageLinkKind = "links" | "wikilink" | "edges";
|
|
65
|
+
|
|
66
|
+
/** A structural reference from page `from` to slug `to` that has no page behind it. */
|
|
67
|
+
export interface DanglingLink {
|
|
68
|
+
from: string;
|
|
69
|
+
to: string;
|
|
70
|
+
kind: PageLinkKind;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The slice of a `ConceptPage` the finder reads (a parsed page satisfies it). */
|
|
74
|
+
export interface PageLinkSource {
|
|
75
|
+
slug: string;
|
|
76
|
+
frontmatter: {
|
|
77
|
+
links?: readonly string[] | undefined;
|
|
78
|
+
edges?: readonly string[] | undefined;
|
|
79
|
+
};
|
|
80
|
+
/** Frontmatter-stripped body. */
|
|
81
|
+
body: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Structural references on `pages` whose target is not in `knownSlugs`.
|
|
86
|
+
* Only slug-shaped targets (`isSlug`, the page store's rule) are references
|
|
87
|
+
* at all: `[[ -f foo ]]` in a shell snippet is not a link to a page. Self
|
|
88
|
+
* references are ignored, each `(from, to, kind)` is reported once, and the
|
|
89
|
+
* result is sorted by `(from, to, kind)` so renderings are byte-stable.
|
|
90
|
+
*/
|
|
91
|
+
export function findDanglingLinks(
|
|
92
|
+
pages: readonly PageLinkSource[],
|
|
93
|
+
knownSlugs: ReadonlySet<string>,
|
|
94
|
+
isSlug: (target: string) => boolean,
|
|
95
|
+
): DanglingLink[] {
|
|
96
|
+
const seen = new Set<string>();
|
|
97
|
+
const dangling: DanglingLink[] = [];
|
|
98
|
+
const report = (from: string, to: string, kind: PageLinkKind): void => {
|
|
99
|
+
if (to === from || knownSlugs.has(to) || !isSlug(to)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const key = `${kind} ${from} ${to}`;
|
|
103
|
+
if (seen.has(key)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
seen.add(key);
|
|
107
|
+
dangling.push({ from, to, kind });
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
for (const page of pages) {
|
|
111
|
+
for (const entry of page.frontmatter.links ?? []) {
|
|
112
|
+
report(page.slug, parseLinkEntry(entry).target, "links");
|
|
113
|
+
}
|
|
114
|
+
for (const target of extractWikilinkTargets(page.body)) {
|
|
115
|
+
report(page.slug, target, "wikilink");
|
|
116
|
+
}
|
|
117
|
+
for (const target of page.frontmatter.edges ?? []) {
|
|
118
|
+
report(page.slug, target.trim(), "edges");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
dangling.sort(
|
|
123
|
+
(a, b) =>
|
|
124
|
+
compareStrings(a.from, b.from) ||
|
|
125
|
+
compareStrings(a.to, b.to) ||
|
|
126
|
+
compareStrings(a.kind, b.kind),
|
|
127
|
+
);
|
|
128
|
+
return dangling;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function compareStrings(a: string, b: string): number {
|
|
132
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
133
|
+
}
|
|
@@ -153,6 +153,16 @@ export function validateSlug(slug: string): void {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/** Boolean form of {@link validateSlug}. */
|
|
157
|
+
export function isValidSlug(slug: string): boolean {
|
|
158
|
+
try {
|
|
159
|
+
validateSlug(slug);
|
|
160
|
+
return true;
|
|
161
|
+
} catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
156
166
|
// ---------------------------------------------------------------------------
|
|
157
167
|
// Path helpers
|
|
158
168
|
// ---------------------------------------------------------------------------
|