@vellumai/assistant 0.8.7-dev.202606052232.2ddc989 → 0.8.8
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/bun.lock +2 -2
- package/docs/plugins.md +832 -0
- package/examples/plugins/echo/README.md +60 -61
- package/examples/plugins/echo/package.json +2 -1
- package/examples/plugins/echo/register.ts +143 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
- package/openapi.yaml +5 -15
- package/package.json +2 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +1 -4
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
- package/src/__tests__/conversation-agent-loop.test.ts +39 -42
- package/src/__tests__/conversation-clean-command.test.ts +2 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
- package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
- package/src/__tests__/conversation-starter-routes.test.ts +6 -14
- package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
- package/src/__tests__/db-acp-history.test.ts +0 -101
- package/src/__tests__/dynamic-page-surface.test.ts +0 -31
- package/src/__tests__/file-write-tool.test.ts +0 -63
- package/src/__tests__/gateway-only-guard.test.ts +2 -12
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/injector-background-turn.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +6 -34
- package/src/__tests__/injector-disk-pressure.test.ts +34 -77
- package/src/__tests__/injector-document-comments.test.ts +1 -1
- package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
- package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
- package/src/__tests__/pipeline-runner.test.ts +554 -0
- package/src/__tests__/plugin-api-shim.test.ts +6 -3
- package/src/__tests__/plugin-bootstrap.test.ts +23 -12
- package/src/__tests__/plugin-registry.test.ts +49 -3
- package/src/__tests__/plugin-types.test.ts +70 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +1 -4
- package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +3 -1
- package/src/__tests__/subagent-notify-parent.test.ts +3 -1
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +286 -54
- package/src/acp/__tests__/client-handler.test.ts +0 -40
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
- package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
- package/src/acp/agent-process.ts +1 -61
- package/src/acp/client-handler.ts +0 -31
- package/src/acp/prepare-agent-env.ts +29 -83
- package/src/acp/resolve-agent.test.ts +7 -320
- package/src/acp/resolve-agent.ts +18 -182
- package/src/acp/session-manager.ts +73 -495
- package/src/acp/types.ts +0 -8
- package/src/agent/compaction-circuit.ts +102 -60
- package/src/agent/loop.ts +59 -32
- package/src/api/responses/conversation-message.ts +1 -7
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +0 -1
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +31 -83
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
- package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
- package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +0 -2
- package/src/config/feature-flag-registry.json +5 -14
- package/src/config/schemas/heartbeat.ts +0 -9
- package/src/context/strip-injections.ts +2 -8
- package/src/context/window-manager.ts +1 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
- package/src/daemon/conversation-agent-loop.ts +279 -62
- package/src/daemon/conversation-runtime-assembly.ts +69 -106
- package/src/daemon/conversation-store.ts +90 -9
- package/src/daemon/conversation-workspace.ts +0 -17
- package/src/daemon/conversation.ts +6 -0
- package/src/daemon/external-plugins-bootstrap.ts +11 -11
- package/src/daemon/handlers/conversations.ts +1 -3
- package/src/daemon/handlers/skills.ts +1 -4
- package/src/daemon/lifecycle.ts +0 -21
- package/src/daemon/server.ts +0 -2
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
- package/src/heartbeat/heartbeat-run-store.ts +1 -23
- package/src/heartbeat/heartbeat-service.ts +0 -26
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
- package/src/ipc/skill-routes/memory.ts +2 -4
- package/src/memory/conversation-starter-checkpoints.ts +0 -1
- package/src/memory/db-init.ts +0 -2
- package/src/memory/job-handlers/conversation-starters.ts +2 -13
- package/src/memory/jobs-worker.ts +1 -1
- package/src/memory/migrations/index.ts +0 -1
- package/src/memory/schema/acp.ts +0 -4
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +4 -13
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
- package/src/plugin-api/index.ts +5 -0
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
- package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
- package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +19 -8
- package/src/plugins/defaults/compaction/terminal.ts +73 -0
- package/src/plugins/defaults/index.ts +5 -3
- package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
- package/src/plugins/defaults/overflow-reduce/package.json +15 -0
- package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +293 -6
- package/src/plugins/registry.ts +37 -9
- package/src/plugins/types.ts +336 -32
- package/src/plugins/user-loader.ts +127 -30
- package/src/proactive-artifact/aux-message-injector.ts +1 -1
- package/src/proactive-artifact/job.test.ts +1 -1
- package/src/prompts/__tests__/system-prompt.test.ts +0 -6
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
- package/src/runtime/__tests__/agent-wake.test.ts +5 -5
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +3 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
- package/src/runtime/routes/acp-routes.test.ts +25 -89
- package/src/runtime/routes/acp-routes.ts +29 -81
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +10 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -15
- package/src/runtime/routes/conversation-starter-routes.ts +7 -13
- package/src/runtime/routes/conversations-import-routes.ts +7 -24
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -18
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/memory-v3-routes.ts +6 -16
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
- package/src/runtime/routes/work-items-routes.ts +4 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +4 -2
- package/src/subagent/manager.ts +5 -17
- package/src/tools/acp/list-agents.test.ts +1 -7
- package/src/tools/acp/spawn.test.ts +55 -158
- package/src/tools/acp/spawn.ts +72 -47
- package/src/tools/acp/steer.test.ts +8 -105
- package/src/tools/acp/steer.ts +17 -48
- package/src/tools/apps/executors.ts +8 -13
- package/src/tools/filesystem/write.ts +0 -34
- package/src/tools/subagent/spawn.ts +4 -2
- package/src/tools/ui-surface/definitions.ts +4 -25
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
- package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
- package/examples/plugins/echo/hooks/stop.ts +0 -16
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
- package/examples/plugins/echo/src/emit.ts +0 -19
- package/src/__tests__/compaction-circuit.test.ts +0 -258
- package/src/__tests__/compaction-direct.test.ts +0 -132
- package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
- package/src/acp/__tests__/agent-process.test.ts +0 -161
- package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
- package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
- package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
- package/src/acp/auto-install.test.ts +0 -196
- package/src/acp/auto-install.ts +0 -177
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/acp/resume-hint.ts +0 -25
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
- package/src/daemon/conversation-registry.ts +0 -159
- package/src/daemon/overflow-reduction-loop.ts +0 -230
- package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
- package/src/plugins/defaults/compaction/compact.ts +0 -59
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
- package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
- package/src/tools/acp/context.ts +0 -20
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* User plugin loader — discovers plugins under `<workspaceDir>/plugins/*`
|
|
3
|
-
*
|
|
2
|
+
* User plugin loader — discovers plugins under `<workspaceDir>/plugins/*` via
|
|
3
|
+
* one of two paths, gated by the contents of each candidate directory.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* delegates to {@link loadExternalPlugin},
|
|
7
|
-
* directory's interface dirs (`hooks/`,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
5
|
+
* **External plugin framework path** (`package.json` present **and** no
|
|
6
|
+
* `register.{ts,js}`): the harness delegates to {@link loadExternalPlugin},
|
|
7
|
+
* which builds a `Plugin` from the directory's interface dirs (`hooks/`,
|
|
8
|
+
* `tools/`) and registers it directly. This path is opt-in by the plugin
|
|
9
|
+
* author and currently experimental — see
|
|
10
|
+
* `assistant/src/plugins/external-plugin-loader.ts` for the full
|
|
11
|
+
* convention.
|
|
12
|
+
*
|
|
13
|
+
* **Legacy path** (`register.{ts,js}` present): the file is dynamic-imported
|
|
14
|
+
* and expected to call {@link registerPlugin} at import time as a side
|
|
15
|
+
* effect, populating the registry before {@link bootstrapPlugins} runs.
|
|
16
|
+
*
|
|
17
|
+
* The legacy path takes precedence when a directory contains both
|
|
18
|
+
* `package.json` and `register.{ts,js}` — a migration-friendly default
|
|
19
|
+
* that keeps existing plugins (including the in-repo `examples/plugins/echo`
|
|
20
|
+
* reference) working unchanged while we iterate the external-plugin
|
|
21
|
+
* convention. A directory matching neither path is skipped silently.
|
|
11
22
|
*
|
|
12
23
|
* The loader deliberately:
|
|
13
24
|
*
|
|
14
25
|
* - Uses `getWorkspaceDir()` so each instance loads its own plugin set
|
|
15
26
|
* when `VELLUM_WORKSPACE_DIR` is set.
|
|
16
|
-
* - Prefers `.js` over `.ts` per surface file (compiled-binary semantics)
|
|
17
|
-
* the rule
|
|
27
|
+
* - Prefers `.js` over `.ts` per surface file (compiled-binary semantics).
|
|
28
|
+
* The external loader applies the same rule per surface file; the
|
|
29
|
+
* legacy path picks between `register.js` and `register.ts`.
|
|
18
30
|
* - Treats any error from a plugin load as a per-plugin isolation
|
|
19
|
-
* boundary. {@link loadExternalPlugin} owns its own try/catch/timeout
|
|
20
|
-
*
|
|
31
|
+
* boundary. {@link loadExternalPlugin} owns its own try/catch/timeout;
|
|
32
|
+
* the legacy path is wrapped here. One bad user plugin must not crash
|
|
33
|
+
* the daemon.
|
|
34
|
+
* - Bounds each plugin load with {@link USER_PLUGIN_IMPORT_TIMEOUT_MS}
|
|
35
|
+
* so a plugin whose top-level `await` hangs or whose module evaluation
|
|
36
|
+
* never resolves cannot stall daemon startup.
|
|
21
37
|
*
|
|
22
38
|
* Call order relative to the rest of the plugin system:
|
|
23
39
|
*
|
|
@@ -30,6 +46,7 @@
|
|
|
30
46
|
|
|
31
47
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
32
48
|
import { join } from "node:path";
|
|
49
|
+
import { pathToFileURL } from "node:url";
|
|
33
50
|
|
|
34
51
|
import { getLogger } from "../util/logger.js";
|
|
35
52
|
import { getWorkspacePluginsDir } from "../util/platform.js";
|
|
@@ -40,24 +57,27 @@ import { closeRegistration } from "./registry.js";
|
|
|
40
57
|
const log = getLogger("user-plugin-loader");
|
|
41
58
|
|
|
42
59
|
/**
|
|
43
|
-
* Upper bound on how long a single user plugin's
|
|
44
|
-
* a hanging top-level `await` (or a never-resolving module
|
|
45
|
-
* otherwise block daemon startup indefinitely
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
60
|
+
* Upper bound on how long a single user plugin's dynamic `import()` may take.
|
|
61
|
+
* A plugin with a hanging top-level `await` (or a never-resolving module
|
|
62
|
+
* evaluation) would otherwise block daemon startup indefinitely, since a raw
|
|
63
|
+
* `try/catch` only isolates thrown errors — not hung promises. Ten seconds is
|
|
64
|
+
* generous relative to a typical side-effect registration (milliseconds) and
|
|
65
|
+
* matches the per-plugin isolation contract: slow plugins get skipped the
|
|
66
|
+
* same way thrown-error plugins do.
|
|
49
67
|
*/
|
|
50
68
|
const USER_PLUGIN_IMPORT_TIMEOUT_MS = 10_000;
|
|
51
69
|
|
|
52
70
|
/**
|
|
53
|
-
* Scan `getWorkspaceDir()/plugins/` for subdirectories
|
|
54
|
-
*
|
|
71
|
+
* Scan `getWorkspaceDir()/plugins/` for subdirectories, then dispatch each
|
|
72
|
+
* one to the external loader (if `package.json` is present and there is no
|
|
73
|
+
* `register.{ts,js}`) or the legacy side-effect importer (if
|
|
74
|
+
* `register.{ts,js}` is present).
|
|
55
75
|
*
|
|
56
76
|
* Invariants:
|
|
57
77
|
*
|
|
58
78
|
* - No-ops when `getWorkspaceDir()/plugins/` does not exist — a clean install with
|
|
59
79
|
* zero user plugins must not generate errors.
|
|
60
|
-
* - Per-plugin isolation: a failing
|
|
80
|
+
* - Per-plugin isolation: a failing import is logged and skipped. The
|
|
61
81
|
* function resolves normally even when every plugin fails to load.
|
|
62
82
|
* - Does not return plugin instances. The registry is the single source of
|
|
63
83
|
* truth for who got registered, and the caller inspects it directly.
|
|
@@ -66,7 +86,7 @@ const USER_PLUGIN_IMPORT_TIMEOUT_MS = 10_000;
|
|
|
66
86
|
*
|
|
67
87
|
* - Must be invoked exactly once during daemon startup, before
|
|
68
88
|
* `bootstrapPlugins()` walks the registry.
|
|
69
|
-
* - Holds no locks during the
|
|
89
|
+
* - Holds no locks during the import — bun's dynamic `import()` resolution
|
|
70
90
|
* is concurrency-safe.
|
|
71
91
|
*/
|
|
72
92
|
export async function loadUserPlugins(
|
|
@@ -131,19 +151,96 @@ export async function loadUserPlugins(
|
|
|
131
151
|
}
|
|
132
152
|
if (!stats.isDirectory()) continue;
|
|
133
153
|
|
|
134
|
-
|
|
135
|
-
|
|
154
|
+
// Path selection: the legacy side-effect path takes precedence when
|
|
155
|
+
// both a `register.{ts,js}` and a `package.json` are present.
|
|
156
|
+
// Migration-friendly: any plugin in the wild today that happens to
|
|
157
|
+
// ship a `package.json` keeps loading via its existing register entry.
|
|
158
|
+
// The external-plugin path only fires when the directory is
|
|
159
|
+
// unambiguously the new convention.
|
|
160
|
+
const jsPath = join(pluginDir, "register.js");
|
|
161
|
+
const tsPath = join(pluginDir, "register.ts");
|
|
162
|
+
let registerPath: string | undefined;
|
|
163
|
+
if (existsSync(jsPath)) {
|
|
164
|
+
registerPath = jsPath;
|
|
165
|
+
} else if (existsSync(tsPath)) {
|
|
166
|
+
registerPath = tsPath;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (registerPath === undefined) {
|
|
170
|
+
// External plugin framework path. `loadExternalPlugin` owns its own
|
|
171
|
+
// try/catch + timeout, so a `continue` is the entire branch here.
|
|
172
|
+
if (existsSync(join(pluginDir, "package.json"))) {
|
|
173
|
+
await loadExternalPlugin(pluginDir, { importTimeoutMs });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
log.debug(
|
|
177
|
+
{ pluginDir },
|
|
178
|
+
"loadUserPlugins: no register.{ts,js} or package.json — skipping",
|
|
179
|
+
);
|
|
136
180
|
continue;
|
|
137
181
|
}
|
|
138
182
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
183
|
+
// Legacy side-effect import path. `import()` with a `file://` URL
|
|
184
|
+
// works identically under Node and bun and sidesteps platform-specific
|
|
185
|
+
// absolute-path quirks on Windows.
|
|
186
|
+
const moduleUrl = pathToFileURL(registerPath).href;
|
|
187
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
188
|
+
try {
|
|
189
|
+
// Race the import against a timeout so a plugin with a hanging top-level
|
|
190
|
+
// await or never-resolving module evaluation cannot stall daemon startup.
|
|
191
|
+
// The per-plugin try/catch already handles thrown errors; this extends
|
|
192
|
+
// the isolation boundary to cover hung promises as well.
|
|
193
|
+
const timeoutSentinel = Symbol("user-plugin-import-timeout");
|
|
194
|
+
const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
|
|
195
|
+
timeoutHandle = setTimeout(
|
|
196
|
+
() => resolve(timeoutSentinel),
|
|
197
|
+
importTimeoutMs,
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
// Retain the import promise so we can attach a terminal `.catch` on the
|
|
201
|
+
// timeout branch. `Promise.race` does not cancel the losing promise —
|
|
202
|
+
// the module evaluation keeps running in the background even after we
|
|
203
|
+
// stop awaiting it, and if it eventually throws (either from the
|
|
204
|
+
// module body or from the late `registerPlugin()` hitting a closed
|
|
205
|
+
// registry) an unhandled rejection would crash the daemon.
|
|
206
|
+
const importPromise = import(moduleUrl);
|
|
207
|
+
const result = await Promise.race([importPromise, timeoutPromise]);
|
|
208
|
+
if (result === timeoutSentinel) {
|
|
209
|
+
importPromise.catch(() => {
|
|
210
|
+
// Abandoned import completed (or threw) after the timeout. The
|
|
211
|
+
// closed-registration latch in registry.ts guarantees any late
|
|
212
|
+
// `registerPlugin()` call is rejected, so swallowing the outcome
|
|
213
|
+
// here is the safe default.
|
|
214
|
+
});
|
|
215
|
+
log.warn(
|
|
216
|
+
{ pluginDir, registerPath, timeoutMs: importTimeoutMs },
|
|
217
|
+
`Timed out loading user plugin ${pluginDir} after ${importTimeoutMs}ms — skipping`,
|
|
218
|
+
);
|
|
219
|
+
} else {
|
|
220
|
+
log.info(
|
|
221
|
+
{ pluginDir, registerPath },
|
|
222
|
+
"loaded user plugin (side-effect import completed)",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
// One plugin's failure must never prevent other plugins from loading
|
|
227
|
+
// or crash the daemon. Log with the directory name so operators can
|
|
228
|
+
// find the broken plugin quickly.
|
|
229
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
230
|
+
log.error(
|
|
231
|
+
{ err, pluginDir },
|
|
232
|
+
`Failed to load user plugin ${pluginDir}: ${message}`,
|
|
233
|
+
);
|
|
234
|
+
} finally {
|
|
235
|
+
if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
|
|
236
|
+
}
|
|
142
237
|
}
|
|
143
238
|
|
|
144
239
|
// Close the registration window once every candidate plugin has been
|
|
145
|
-
// awaited (or timed out). The per-plugin try/catch
|
|
146
|
-
//
|
|
147
|
-
//
|
|
240
|
+
// awaited (or timed out). The per-plugin try/catch guarantees no throw
|
|
241
|
+
// escapes the loop, so this line always runs. Any abandoned import that
|
|
242
|
+
// later resolves and reaches `registerPlugin()` is rejected by the latch,
|
|
243
|
+
// preserving the `bootstrapPlugins()` invariant that the registry is
|
|
244
|
+
// fully populated before it is walked.
|
|
148
245
|
closeRegistration();
|
|
149
246
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { createAssistantMessage } from "../agent/message-types.js";
|
|
10
|
-
import { findConversation } from "../daemon/conversation-
|
|
10
|
+
import { findConversation } from "../daemon/conversation-store.js";
|
|
11
11
|
import {
|
|
12
12
|
conversationMessagesSyncTag,
|
|
13
13
|
SYNC_TAGS,
|
|
@@ -196,7 +196,7 @@ type MockConversation = {
|
|
|
196
196
|
};
|
|
197
197
|
let mockConversations: Map<string, MockConversation> = new Map();
|
|
198
198
|
|
|
199
|
-
mock.module("../daemon/conversation-
|
|
199
|
+
mock.module("../daemon/conversation-store.js", () => ({
|
|
200
200
|
findConversation: (id: string) => mockConversations.get(id),
|
|
201
201
|
}));
|
|
202
202
|
|
|
@@ -148,11 +148,5 @@ describe("maybeReseedBootstrap — activation rail template", () => {
|
|
|
148
148
|
// Propose: the extract-shape vs infer-shape example block.
|
|
149
149
|
expect(content).toContain("extract-shape");
|
|
150
150
|
expect(content).toContain("infer-shape");
|
|
151
|
-
|
|
152
|
-
// Port: prompt-writing guidance (JARVIS-1124).
|
|
153
|
-
expect(content).toContain("portable context brief, not a self-summary");
|
|
154
|
-
expect(content).toContain("load-bearing work in the next month");
|
|
155
|
-
expect(content).toContain("what to help with first");
|
|
156
|
-
expect(content).toContain("another tool or collaborator");
|
|
157
151
|
});
|
|
158
152
|
});
|
|
@@ -8,11 +8,9 @@ The user just finished pre-chat. You know their name and vibe; maybe their Googl
|
|
|
8
8
|
|
|
9
9
|
Four moves. Goals, not steps.
|
|
10
10
|
|
|
11
|
-
**Port.** Pull their existing assistant context with two pastes
|
|
11
|
+
**Port.** Pull their existing assistant context with two pastes — about a minute, no upload, no export. You write a prompt, they paste it into Claude or ChatGPT, they paste the response back. Cheap signal, real signal.
|
|
12
12
|
|
|
13
|
-
The prompt
|
|
14
|
-
|
|
15
|
-
The prompt itself must be one-click copyable. Inline paragraph text the user has to select isn't. Neither is a custom-built widget with a fake copy button. If the affordance needs you to build an app or a new surface to render, you've over-built the move. Use what chat already gives you.
|
|
13
|
+
The prompt should be one-click copyable. Inline paragraph text the user has to select isn't. Neither is a custom-built widget with a fake copy button. If the affordance needs you to build an app or a new surface to render, you've over-built the move. Use what chat already gives you.
|
|
16
14
|
|
|
17
15
|
**Propose.** Don't organize what they already told you — infer what they didn't. Name the unstated thing sitting in their context and say *why* you think it: point at the specific surface that made you say it. "You didn't say this, but —". Then recommend, and lean one way; the recommendation IS the click, not a neutral menu of equally-weighted options.
|
|
18
16
|
|
|
@@ -432,7 +432,7 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
432
432
|
expect(target.runCalls).toHaveLength(0);
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
-
test("
|
|
435
|
+
test("threads cleanup-mode injection context for explicit local-owner wakes", async () => {
|
|
436
436
|
mockDiskPressureStatus = {
|
|
437
437
|
enabled: true,
|
|
438
438
|
state: "critical",
|
|
@@ -463,11 +463,11 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
463
463
|
|
|
464
464
|
expect(result).toEqual({ invoked: true, producedToolCalls: false });
|
|
465
465
|
expect(target.runCalls).toHaveLength(1);
|
|
466
|
-
expect(target.runCalls[0]!.turnContext).
|
|
467
|
-
requestId: "wake:local-cleanup",
|
|
466
|
+
expect(target.runCalls[0]!.turnContext).toMatchObject({
|
|
468
467
|
conversationId: target.conversationId,
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
injectionInputs: {
|
|
469
|
+
diskPressureContext: { cleanupModeActive: true },
|
|
470
|
+
},
|
|
471
471
|
});
|
|
472
472
|
});
|
|
473
473
|
|
|
@@ -50,7 +50,7 @@ let surfaceCalls: Array<{
|
|
|
50
50
|
surfaceId: string;
|
|
51
51
|
}> = [];
|
|
52
52
|
|
|
53
|
-
mock.module("../../daemon/conversation-
|
|
53
|
+
mock.module("../../daemon/conversation-store.js", () => ({
|
|
54
54
|
findConversation: (_conversationId: string) => {
|
|
55
55
|
return mockConversation ?? undefined;
|
|
56
56
|
},
|
|
@@ -708,7 +708,7 @@ async function createCanonicalRequestForConfirmation(
|
|
|
708
708
|
{ DAEMON_INTERNAL_ASSISTANT_ID },
|
|
709
709
|
{ bridgeConfirmationRequestToGuardian },
|
|
710
710
|
] = await Promise.all([
|
|
711
|
-
import("../daemon/conversation-
|
|
711
|
+
import("../daemon/conversation-store.js"),
|
|
712
712
|
import("../memory/canonical-guardian-store.js"),
|
|
713
713
|
import("../security/secret-scanner.js"),
|
|
714
714
|
import("../tools/tool-input-summary.js"),
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 3. Consume user decisions and apply them to the underlying session
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { findConversation } from "../daemon/conversation-
|
|
12
|
+
import { findConversation } from "../daemon/conversation-store.js";
|
|
13
13
|
import type { UserDecision } from "../permissions/types.js";
|
|
14
14
|
import { composeApprovalMessage } from "./approval-message-composer.js";
|
|
15
15
|
import type {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* `"timed_out"`.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import { findConversation } from "../daemon/conversation-
|
|
33
|
+
import { findConversation } from "../daemon/conversation-store.js";
|
|
34
34
|
import { showStandaloneSurface } from "../daemon/conversation-surfaces.js";
|
|
35
35
|
import { getLogger } from "../util/logger.js";
|
|
36
36
|
import { mintDecisionToken } from "./decision-token.js";
|