@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
|
@@ -198,7 +198,6 @@ mock.module("../agent/loop.js", () => ({
|
|
|
198
198
|
}));
|
|
199
199
|
|
|
200
200
|
import { Conversation } from "../daemon/conversation.js";
|
|
201
|
-
import { refreshWorkspaceTopLevelContextIfNeeded } from "../daemon/conversation-workspace.js";
|
|
202
201
|
|
|
203
202
|
// ---------------------------------------------------------------------------
|
|
204
203
|
// Helpers
|
|
@@ -250,7 +249,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
250
249
|
});
|
|
251
250
|
|
|
252
251
|
test("refreshWorkspaceTopLevelContextIfNeeded populates context and clears dirty", () => {
|
|
253
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
252
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
254
253
|
|
|
255
254
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
256
255
|
expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
|
|
@@ -266,10 +265,10 @@ describe("Conversation workspace cache state", () => {
|
|
|
266
265
|
});
|
|
267
266
|
|
|
268
267
|
test("refreshWorkspaceTopLevelContextIfNeeded no-ops when not dirty and cache exists", () => {
|
|
269
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
268
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
270
269
|
const first = conversation.getWorkspaceTopLevelContext();
|
|
271
270
|
|
|
272
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
271
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
273
272
|
const second = conversation.getWorkspaceTopLevelContext();
|
|
274
273
|
|
|
275
274
|
// Same reference — no recomputation
|
|
@@ -277,7 +276,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
277
276
|
});
|
|
278
277
|
|
|
279
278
|
test("markWorkspaceTopLevelDirty sets dirty flag", () => {
|
|
280
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
279
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
281
280
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
282
281
|
|
|
283
282
|
conversation.markWorkspaceTopLevelDirty();
|
|
@@ -285,10 +284,10 @@ describe("Conversation workspace cache state", () => {
|
|
|
285
284
|
});
|
|
286
285
|
|
|
287
286
|
test("refresh after marking dirty produces fresh context", () => {
|
|
288
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
287
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
289
288
|
|
|
290
289
|
conversation.markWorkspaceTopLevelDirty();
|
|
291
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
290
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
292
291
|
|
|
293
292
|
expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
|
|
294
293
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
@@ -300,7 +299,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
300
299
|
test("renders client-reported host env when set on the conversation", () => {
|
|
301
300
|
conversation.hostHomeDir = "/Users/alice";
|
|
302
301
|
conversation.hostUsername = "alice";
|
|
303
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
302
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
304
303
|
|
|
305
304
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
306
305
|
expect(block).not.toBeNull();
|
|
@@ -310,7 +309,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
310
309
|
|
|
311
310
|
test("falls back to daemon os info when client host env is absent", async () => {
|
|
312
311
|
const { homedir, userInfo } = await import("node:os");
|
|
313
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
312
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
314
313
|
|
|
315
314
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
316
315
|
expect(block).not.toBeNull();
|
|
@@ -321,7 +320,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
321
320
|
test("re-renders with updated host env after marking dirty", () => {
|
|
322
321
|
conversation.hostHomeDir = "/Users/alice";
|
|
323
322
|
conversation.hostUsername = "alice";
|
|
324
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
323
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
325
324
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
326
325
|
"Host home directory: /Users/alice",
|
|
327
326
|
);
|
|
@@ -329,7 +328,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
329
328
|
conversation.hostHomeDir = "/Users/bob";
|
|
330
329
|
conversation.hostUsername = "bob";
|
|
331
330
|
conversation.markWorkspaceTopLevelDirty();
|
|
332
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
331
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
333
332
|
|
|
334
333
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
335
334
|
expect(block).not.toBeNull();
|
|
@@ -345,7 +344,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
345
344
|
// Simulate a macOS turn populating host env.
|
|
346
345
|
conversation.hostHomeDir = "/Users/alice";
|
|
347
346
|
conversation.hostUsername = "alice";
|
|
348
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
347
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
349
348
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
350
349
|
"Host home directory: /Users/alice",
|
|
351
350
|
);
|
|
@@ -356,7 +355,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
356
355
|
conversation.hostHomeDir = undefined;
|
|
357
356
|
conversation.hostUsername = undefined;
|
|
358
357
|
conversation.markWorkspaceTopLevelDirty();
|
|
359
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
358
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
360
359
|
|
|
361
360
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
362
361
|
expect(block).not.toBeNull();
|
|
@@ -382,7 +381,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
382
381
|
expect(conversation.hostUsername).toBe("alice");
|
|
383
382
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(true);
|
|
384
383
|
|
|
385
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
384
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
386
385
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
387
386
|
expect(block!).toContain("Host home directory: /Users/alice");
|
|
388
387
|
expect(block!).toContain("Host username: alice");
|
|
@@ -456,7 +455,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
456
455
|
hostUsername: "alice",
|
|
457
456
|
});
|
|
458
457
|
// Render once so the dirty flag clears.
|
|
459
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
458
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
460
459
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
461
460
|
|
|
462
461
|
// Re-apply the same values — dirty flag should remain false so we don't
|
|
@@ -477,7 +476,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
477
476
|
hostHomeDir: "/Users/alice",
|
|
478
477
|
hostUsername: "alice",
|
|
479
478
|
});
|
|
480
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
479
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
481
480
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
482
481
|
|
|
483
482
|
// New values — should mark dirty so the next render picks them up.
|
|
@@ -501,7 +500,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
501
500
|
|
|
502
501
|
try {
|
|
503
502
|
const tempConversation = makeConversation(workspaceRoot);
|
|
504
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
503
|
+
tempConversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
505
504
|
|
|
506
505
|
expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
|
|
507
506
|
`Current conversation attachments: conversations/${legacyDirName}/attachments/`,
|
|
@@ -295,14 +295,6 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
|
|
|
295
295
|
}));
|
|
296
296
|
|
|
297
297
|
import { Conversation } from "../daemon/conversation.js";
|
|
298
|
-
import {
|
|
299
|
-
clearConversations,
|
|
300
|
-
findConversation,
|
|
301
|
-
removeSubagentConversation,
|
|
302
|
-
setConversation,
|
|
303
|
-
setSubagentConversation,
|
|
304
|
-
} from "../daemon/conversation-registry.js";
|
|
305
|
-
import { resolveWorkspaceTopLevelContext } from "../daemon/conversation-workspace.js";
|
|
306
298
|
import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
|
|
307
299
|
|
|
308
300
|
function makeConversation(): Conversation {
|
|
@@ -317,7 +309,7 @@ function makeConversation(): Conversation {
|
|
|
317
309
|
};
|
|
318
310
|
},
|
|
319
311
|
};
|
|
320
|
-
|
|
312
|
+
return new Conversation(
|
|
321
313
|
"conv-1",
|
|
322
314
|
provider,
|
|
323
315
|
"system prompt",
|
|
@@ -325,10 +317,6 @@ function makeConversation(): Conversation {
|
|
|
325
317
|
"/tmp",
|
|
326
318
|
{ maxTokens: 4096 },
|
|
327
319
|
);
|
|
328
|
-
// Mirror production: top-level conversations are registered in the store, so
|
|
329
|
-
// the workspace injector can resolve them by id via the conversation registry.
|
|
330
|
-
setConversation(conversation.conversationId, conversation);
|
|
331
|
-
return conversation;
|
|
332
320
|
}
|
|
333
321
|
|
|
334
322
|
function messageText(message: Message): string {
|
|
@@ -347,7 +335,6 @@ describe("Conversation workspace injection", () => {
|
|
|
347
335
|
runCalls = [];
|
|
348
336
|
agentLoopScript = () => {};
|
|
349
337
|
scanCallCount = 0;
|
|
350
|
-
clearConversations();
|
|
351
338
|
resetPluginRegistryAndRegisterDefaults();
|
|
352
339
|
});
|
|
353
340
|
|
|
@@ -430,58 +417,11 @@ describe("Conversation workspace injection", () => {
|
|
|
430
417
|
});
|
|
431
418
|
});
|
|
432
419
|
|
|
433
|
-
describe("Conversation workspace injection — subagents", () => {
|
|
434
|
-
beforeEach(() => {
|
|
435
|
-
scanCallCount = 0;
|
|
436
|
-
clearConversations();
|
|
437
|
-
resetPluginRegistryAndRegisterDefaults();
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
test("workspace context resolves for subagent conversations not in the store", () => {
|
|
441
|
-
const provider = {
|
|
442
|
-
name: "mock",
|
|
443
|
-
async sendMessage(): Promise<ProviderResponse> {
|
|
444
|
-
return {
|
|
445
|
-
content: [],
|
|
446
|
-
model: "mock",
|
|
447
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
448
|
-
stopReason: "end_turn",
|
|
449
|
-
};
|
|
450
|
-
},
|
|
451
|
-
};
|
|
452
|
-
const subagentId = "subagent-conv-1";
|
|
453
|
-
const conversation = new Conversation(
|
|
454
|
-
subagentId,
|
|
455
|
-
provider,
|
|
456
|
-
"system prompt",
|
|
457
|
-
() => {},
|
|
458
|
-
"/tmp",
|
|
459
|
-
{ maxTokens: 4096 },
|
|
460
|
-
);
|
|
461
|
-
setSubagentConversation(subagentId, conversation);
|
|
462
|
-
|
|
463
|
-
try {
|
|
464
|
-
// Subagents live only in the manager's index, never in the
|
|
465
|
-
// eviction-managed store, so the store lookup must not see them.
|
|
466
|
-
expect(findConversation(subagentId)).toBeUndefined();
|
|
467
|
-
|
|
468
|
-
// The per-conversation workspace lookup still resolves them, so subagent
|
|
469
|
-
// turns retain workspace grounding.
|
|
470
|
-
const context = resolveWorkspaceTopLevelContext(subagentId);
|
|
471
|
-
expect(context).not.toBeNull();
|
|
472
|
-
expect(context).toContain("Root: /tmp");
|
|
473
|
-
} finally {
|
|
474
|
-
removeSubagentConversation(subagentId, conversation);
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
});
|
|
478
|
-
|
|
479
420
|
describe("Conversation workspace dirty-refresh E2E", () => {
|
|
480
421
|
beforeEach(() => {
|
|
481
422
|
runCalls = [];
|
|
482
423
|
agentLoopScript = () => {};
|
|
483
424
|
scanCallCount = 0;
|
|
484
|
-
clearConversations();
|
|
485
425
|
resetPluginRegistryAndRegisterDefaults();
|
|
486
426
|
});
|
|
487
427
|
|
|
@@ -273,7 +273,6 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
|
|
|
273
273
|
}));
|
|
274
274
|
|
|
275
275
|
import { Conversation } from "../daemon/conversation.js";
|
|
276
|
-
import { refreshWorkspaceTopLevelContextIfNeeded } from "../daemon/conversation-workspace.js";
|
|
277
276
|
|
|
278
277
|
function makeConversation(): Conversation {
|
|
279
278
|
const provider = {
|
|
@@ -311,7 +310,7 @@ describe("Conversation workspace dirty on file mutations", () => {
|
|
|
311
310
|
await conversation.loadFromDb();
|
|
312
311
|
|
|
313
312
|
// Prime the cache so dirty=false
|
|
314
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
313
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
315
314
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
316
315
|
|
|
317
316
|
agentLoopScript = (onEvent) => {
|
|
@@ -340,7 +339,7 @@ describe("Conversation workspace dirty on file mutations", () => {
|
|
|
340
339
|
const conversation = makeConversation();
|
|
341
340
|
await conversation.loadFromDb();
|
|
342
341
|
|
|
343
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
342
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
344
343
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
345
344
|
|
|
346
345
|
agentLoopScript = (onEvent) => {
|
|
@@ -371,7 +370,7 @@ describe("Conversation workspace dirty on file mutations", () => {
|
|
|
371
370
|
const conversation = makeConversation();
|
|
372
371
|
await conversation.loadFromDb();
|
|
373
372
|
|
|
374
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
373
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
375
374
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
376
375
|
|
|
377
376
|
agentLoopScript = (onEvent) => {
|
|
@@ -400,7 +399,7 @@ describe("Conversation workspace dirty on file mutations", () => {
|
|
|
400
399
|
const conversation = makeConversation();
|
|
401
400
|
await conversation.loadFromDb();
|
|
402
401
|
|
|
403
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
402
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
404
403
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
405
404
|
|
|
406
405
|
agentLoopScript = (onEvent) => {
|
|
@@ -429,7 +428,7 @@ describe("Conversation workspace dirty on file mutations", () => {
|
|
|
429
428
|
const conversation = makeConversation();
|
|
430
429
|
await conversation.loadFromDb();
|
|
431
430
|
|
|
432
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
431
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
433
432
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
434
433
|
|
|
435
434
|
agentLoopScript = (onEvent) => {
|
|
@@ -458,7 +457,7 @@ describe("Conversation workspace dirty on file mutations", () => {
|
|
|
458
457
|
const conversation = makeConversation();
|
|
459
458
|
await conversation.loadFromDb();
|
|
460
459
|
|
|
461
|
-
refreshWorkspaceTopLevelContextIfNeeded(
|
|
460
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
462
461
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
463
462
|
|
|
464
463
|
agentLoopScript = (onEvent) => {
|
|
@@ -5,7 +5,6 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
|
5
5
|
|
|
6
6
|
import { getSqliteFrom } from "../memory/db-connection.js";
|
|
7
7
|
import { migrate230AcpSessionHistory } from "../memory/migrations/230-acp-session-history.js";
|
|
8
|
-
import { migrateAcpSessionHistoryCwd } from "../memory/migrations/272-acp-session-history-cwd.js";
|
|
9
8
|
import * as schema from "../memory/schema.js";
|
|
10
9
|
|
|
11
10
|
function createTestDb() {
|
|
@@ -283,103 +282,3 @@ describe("acp_session_history migration", () => {
|
|
|
283
282
|
expect(row?.id).toBe("history-rerun");
|
|
284
283
|
});
|
|
285
284
|
});
|
|
286
|
-
|
|
287
|
-
describe("acp_session_history cwd migration (272)", () => {
|
|
288
|
-
function readColumns(raw: Database): Map<string, ColumnRow> {
|
|
289
|
-
const columns = raw
|
|
290
|
-
.query(`PRAGMA table_info(acp_session_history)`)
|
|
291
|
-
.all() as ColumnRow[];
|
|
292
|
-
return new Map(columns.map((c) => [c.name, c]));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
test("adds a nullable cwd TEXT column to an upgraded table", () => {
|
|
296
|
-
const db = createTestDb();
|
|
297
|
-
const raw = getSqliteFrom(db);
|
|
298
|
-
bootstrapCheckpointsTable(raw);
|
|
299
|
-
|
|
300
|
-
migrate230AcpSessionHistory(db);
|
|
301
|
-
expect(readColumns(raw).has("cwd")).toBe(false);
|
|
302
|
-
|
|
303
|
-
migrateAcpSessionHistoryCwd(db);
|
|
304
|
-
|
|
305
|
-
const cwd = readColumns(raw).get("cwd");
|
|
306
|
-
expect(cwd).toBeDefined();
|
|
307
|
-
expect(cwd?.type).toBe("TEXT");
|
|
308
|
-
expect(cwd?.notnull).toBe(0);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("re-running is a no-op, including with a cleared checkpoint", () => {
|
|
312
|
-
const db = createTestDb();
|
|
313
|
-
const raw = getSqliteFrom(db);
|
|
314
|
-
bootstrapCheckpointsTable(raw);
|
|
315
|
-
|
|
316
|
-
migrate230AcpSessionHistory(db);
|
|
317
|
-
migrateAcpSessionHistoryCwd(db);
|
|
318
|
-
|
|
319
|
-
// Second run short-circuits on the completed checkpoint.
|
|
320
|
-
expect(() => migrateAcpSessionHistoryCwd(db)).not.toThrow();
|
|
321
|
-
|
|
322
|
-
// Even with the checkpoint cleared (simulating crash recovery), the
|
|
323
|
-
// column-existence guard makes the ALTER a no-op.
|
|
324
|
-
raw
|
|
325
|
-
.query(`DELETE FROM memory_checkpoints WHERE key = ?`)
|
|
326
|
-
.run("migration_acp_session_history_cwd_v1");
|
|
327
|
-
expect(() => migrateAcpSessionHistoryCwd(db)).not.toThrow();
|
|
328
|
-
|
|
329
|
-
const columns = raw
|
|
330
|
-
.query(`PRAGMA table_info(acp_session_history)`)
|
|
331
|
-
.all() as ColumnRow[];
|
|
332
|
-
expect(columns.filter((c) => c.name === "cwd")).toHaveLength(1);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
test("legacy rows read back with cwd null; new rows round-trip cwd", () => {
|
|
336
|
-
const db = createTestDb();
|
|
337
|
-
const raw = getSqliteFrom(db);
|
|
338
|
-
bootstrapCheckpointsTable(raw);
|
|
339
|
-
|
|
340
|
-
migrate230AcpSessionHistory(db);
|
|
341
|
-
|
|
342
|
-
// Legacy row inserted before the cwd column existed.
|
|
343
|
-
raw
|
|
344
|
-
.query(
|
|
345
|
-
/*sql*/ `
|
|
346
|
-
INSERT INTO acp_session_history (
|
|
347
|
-
id, agent_id, acp_session_id, parent_conversation_id,
|
|
348
|
-
started_at, status
|
|
349
|
-
) VALUES (?, ?, ?, ?, ?, ?)
|
|
350
|
-
`,
|
|
351
|
-
)
|
|
352
|
-
.run("legacy-1", "agent-old", "acp-old", "conv-old", 100, "completed");
|
|
353
|
-
|
|
354
|
-
migrateAcpSessionHistoryCwd(db);
|
|
355
|
-
|
|
356
|
-
const legacy = raw
|
|
357
|
-
.query(`SELECT cwd FROM acp_session_history WHERE id = 'legacy-1'`)
|
|
358
|
-
.get() as { cwd: string | null } | null;
|
|
359
|
-
expect(legacy?.cwd).toBeNull();
|
|
360
|
-
|
|
361
|
-
raw
|
|
362
|
-
.query(
|
|
363
|
-
/*sql*/ `
|
|
364
|
-
INSERT INTO acp_session_history (
|
|
365
|
-
id, agent_id, acp_session_id, parent_conversation_id,
|
|
366
|
-
started_at, status, cwd
|
|
367
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
368
|
-
`,
|
|
369
|
-
)
|
|
370
|
-
.run(
|
|
371
|
-
"new-1",
|
|
372
|
-
"agent-new",
|
|
373
|
-
"acp-new",
|
|
374
|
-
"conv-new",
|
|
375
|
-
200,
|
|
376
|
-
"completed",
|
|
377
|
-
"/Users/me/project",
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
const fresh = raw
|
|
381
|
-
.query(`SELECT cwd FROM acp_session_history WHERE id = 'new-1'`)
|
|
382
|
-
.get() as { cwd: string | null } | null;
|
|
383
|
-
expect(fresh?.cwd).toBe("/Users/me/project");
|
|
384
|
-
});
|
|
385
|
-
});
|
|
@@ -94,37 +94,6 @@ describe("ui_show dynamic_page app substitute guard", () => {
|
|
|
94
94
|
expect(proxied).toBe(false);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
test("rejects dynamic_page with a clean title but substantial interactive html", async () => {
|
|
98
|
-
let proxied = false;
|
|
99
|
-
|
|
100
|
-
const result = await uiShowTool.execute(
|
|
101
|
-
{
|
|
102
|
-
surface_type: "dynamic_page",
|
|
103
|
-
title: "Labor Market Stats",
|
|
104
|
-
data: {
|
|
105
|
-
html:
|
|
106
|
-
"<div id='root'></div><script>" +
|
|
107
|
-
"const data=[/*...*/];".padEnd(2100, "/") +
|
|
108
|
-
"new Chart(document.getElementById('root'), {});</script>",
|
|
109
|
-
preview: { title: "Labor Market Stats" },
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
conversationId: "conversation-123",
|
|
114
|
-
workingDir: "/tmp",
|
|
115
|
-
trustClass: "guardian",
|
|
116
|
-
proxyToolResolver: async () => {
|
|
117
|
-
proxied = true;
|
|
118
|
-
return { content: "proxied", isError: false };
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
expect(result.isError).toBe(true);
|
|
124
|
-
expect(result.content).toContain('skill: "app-builder"');
|
|
125
|
-
expect(proxied).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
97
|
test("allows transient non-app dynamic_page surfaces", async () => {
|
|
129
98
|
let proxied = false;
|
|
130
99
|
|
|
@@ -260,66 +260,3 @@ describe("file_write tool PKB re-index hook", () => {
|
|
|
260
260
|
expect(existsSync(join(workingDir, "pkb", "oops.md"))).toBe(true);
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
|
-
|
|
264
|
-
describe("file_write artifact-HTML guard", () => {
|
|
265
|
-
test("rejects a self-contained interactive HTML visualization", async () => {
|
|
266
|
-
const dir = makeTempDir();
|
|
267
|
-
const html =
|
|
268
|
-
"<!doctype html><html><head><title>Food Market</title></head>" +
|
|
269
|
-
"<body><canvas id='c'></canvas><script>" +
|
|
270
|
-
("const data=[{x:1,y:2}];").padEnd(4000, "/") +
|
|
271
|
-
"new Chart(document.getElementById('c'), {data});</script></body></html>";
|
|
272
|
-
|
|
273
|
-
const result = await fileWriteTool.execute(
|
|
274
|
-
{ path: "food-market-stats.html", content: html },
|
|
275
|
-
makeContext(dir),
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
expect(result.isError).toBe(true);
|
|
279
|
-
expect(result.content).toContain('skill: "app-builder"');
|
|
280
|
-
expect(existsSync(join(dir, "food-market-stats.html"))).toBe(false);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
test("allows app-builder's thin shell index.html (external module script)", async () => {
|
|
284
|
-
const dir = makeTempDir();
|
|
285
|
-
const shell =
|
|
286
|
-
"<!doctype html><html><head>" +
|
|
287
|
-
"<link rel='stylesheet' href='/src/styles.css'>".padEnd(3200, " ") +
|
|
288
|
-
"</head><body><div id='app'></div>" +
|
|
289
|
-
"<script type='module' src='/src/main.tsx'></script></body></html>";
|
|
290
|
-
|
|
291
|
-
const result = await fileWriteTool.execute(
|
|
292
|
-
{ path: "src/index.html", content: shell },
|
|
293
|
-
makeContext(dir),
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
expect(result.isError).toBe(false);
|
|
297
|
-
expect(existsSync(join(dir, "src", "index.html"))).toBe(true);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test("allows a small/static HTML snippet", async () => {
|
|
301
|
-
const dir = makeTempDir();
|
|
302
|
-
const result = await fileWriteTool.execute(
|
|
303
|
-
{ path: "note.html", content: "<html><body><h1>Hi</h1></body></html>" },
|
|
304
|
-
makeContext(dir),
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
expect(result.isError).toBe(false);
|
|
308
|
-
expect(existsSync(join(dir, "note.html"))).toBe(true);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("allows a non-HTML file even with inline script-like content", async () => {
|
|
312
|
-
const dir = makeTempDir();
|
|
313
|
-
const result = await fileWriteTool.execute(
|
|
314
|
-
{
|
|
315
|
-
path: "notes.md",
|
|
316
|
-
content:
|
|
317
|
-
"<script>" + "x".padEnd(2000, "x") + "</script>" + "\n# notes\n",
|
|
318
|
-
},
|
|
319
|
-
makeContext(dir),
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
expect(result.isError).toBe(false);
|
|
323
|
-
expect(existsSync(join(dir, "notes.md"))).toBe(true);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
@@ -27,7 +27,6 @@ const ALLOWLIST = new Set([
|
|
|
27
27
|
"clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
|
|
28
28
|
"clients/macos/vellum-assistant/App/AppDelegate.swift",
|
|
29
29
|
"clients/macos/vellum-assistant/Features/Settings/SettingsConnectTab.swift",
|
|
30
|
-
"apps/macos/src/main/bundle-flow.ts", // Electron main calls the local gateway (gatewayPort) with a Guardian token to scan bundles
|
|
31
30
|
".claude/skills/update/SKILL.md", // daemon health check script
|
|
32
31
|
|
|
33
32
|
// --- Test fixtures that poll the daemon directly (gateway may require auth) ---
|
|
@@ -85,21 +84,12 @@ function isGatewayInternal(filePath: string): boolean {
|
|
|
85
84
|
return filePath.startsWith("gateway/");
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
/** Additional files allowed for the interpolated-port check only (use gateway port, not runtime). */
|
|
89
|
-
const INTERPOLATED_PORT_ALLOWLIST = new Set([
|
|
90
|
-
"apps/macos/src/main/bundle-flow.ts",
|
|
91
|
-
]);
|
|
92
|
-
|
|
93
87
|
/** Shared violation filter: exempt test files, gateway internals, and allowlisted paths. */
|
|
94
|
-
function filterViolations(
|
|
95
|
-
files: string[],
|
|
96
|
-
extraAllowlist?: Set<string>,
|
|
97
|
-
): string[] {
|
|
88
|
+
function filterViolations(files: string[]): string[] {
|
|
98
89
|
return files.filter((f) => {
|
|
99
90
|
if (isTestFile(f)) return false;
|
|
100
91
|
if (isGatewayInternal(f)) return false;
|
|
101
92
|
if (ALLOWLIST.has(f)) return false;
|
|
102
|
-
if (extraAllowlist?.has(f)) return false;
|
|
103
93
|
return true;
|
|
104
94
|
});
|
|
105
95
|
}
|
|
@@ -191,7 +181,7 @@ describe("gateway-only API consumption guard", () => {
|
|
|
191
181
|
}
|
|
192
182
|
|
|
193
183
|
const files = grepOutput.split("\n").filter((f) => f.length > 0);
|
|
194
|
-
const violations = filterViolations(files
|
|
184
|
+
const violations = filterViolations(files);
|
|
195
185
|
|
|
196
186
|
if (violations.length > 0) {
|
|
197
187
|
const message = [
|
|
@@ -20,7 +20,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
22
|
const _conversationMocks = new Map<string, unknown>();
|
|
23
|
-
mock.module("../daemon/conversation-
|
|
23
|
+
mock.module("../daemon/conversation-store.js", () => ({
|
|
24
24
|
findConversation: (id: string) => _conversationMocks.get(id),
|
|
25
25
|
}));
|
|
26
26
|
|
|
@@ -21,7 +21,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
21
21
|
mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
|
|
22
22
|
|
|
23
23
|
const _conversationMocks = new Map<string, unknown>();
|
|
24
|
-
mock.module("../daemon/conversation-
|
|
24
|
+
mock.module("../daemon/conversation-store.js", () => ({
|
|
25
25
|
findConversation: (id: string) => _conversationMocks.get(id),
|
|
26
26
|
}));
|
|
27
27
|
|
|
@@ -51,7 +51,9 @@ import {
|
|
|
51
51
|
routeGuardianReply,
|
|
52
52
|
} from "../runtime/guardian-reply-router.js";
|
|
53
53
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
54
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
listGuardianDecisionPrompts,
|
|
56
|
+
} from "../runtime/routes/guardian-action-routes.js";
|
|
55
57
|
|
|
56
58
|
initializeDb();
|
|
57
59
|
|
|
@@ -143,7 +143,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
143
143
|
import {
|
|
144
144
|
clearConversations,
|
|
145
145
|
setConversation,
|
|
146
|
-
} from "../daemon/conversation-
|
|
146
|
+
} from "../daemon/conversation-store.js";
|
|
147
147
|
import { handleConfirmationResponse } from "../daemon/handlers/conversations.js";
|
|
148
148
|
|
|
149
149
|
describe("handleConfirmationResponse canonical status sync", () => {
|
|
@@ -56,7 +56,6 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
|
|
|
56
56
|
markStaleRunsAsMissed: mockMarkStaleRunsAsMissed,
|
|
57
57
|
markStaleRunningAsError: mockMarkStaleRunningAsError,
|
|
58
58
|
countCompletedHeartbeatRuns: mock(() => 10),
|
|
59
|
-
countCompletedRunsToday: mock(() => 0),
|
|
60
59
|
countRecentConsecutiveRuns: mock(() => 0),
|
|
61
60
|
}));
|
|
62
61
|
|
|
@@ -32,7 +32,6 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
|
|
|
32
32
|
markStaleRunningAsError: mockMarkStaleRunningAsError,
|
|
33
33
|
listHeartbeatRuns: mockListHeartbeatRuns,
|
|
34
34
|
countCompletedHeartbeatRuns: mockCountCompletedHeartbeatRuns,
|
|
35
|
-
countCompletedRunsToday: mock(() => 0),
|
|
36
35
|
countRecentConsecutiveRuns: mock(() => 0),
|
|
37
36
|
}));
|
|
38
37
|
|
|
@@ -47,7 +47,7 @@ interface FakeConversation {
|
|
|
47
47
|
|
|
48
48
|
const conversations = new Map<string, FakeConversation>();
|
|
49
49
|
|
|
50
|
-
mock.module("../daemon/conversation-
|
|
50
|
+
mock.module("../daemon/conversation-store.js", () => ({
|
|
51
51
|
findConversation: (id: string) => conversations.get(id),
|
|
52
52
|
}));
|
|
53
53
|
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* 4. Untargeted (no targetClientId, no header) → 200 accepted (regression)
|
|
9
9
|
*
|
|
10
10
|
* Resolution goes through conversation.hostCuProxy?.resolve(...). The
|
|
11
|
-
* conversation
|
|
11
|
+
* conversation store is mocked to return a controlled conversation object.
|
|
12
12
|
*
|
|
13
|
-
* Note: host-cu-routes.ts has a deep import chain (conversation-
|
|
13
|
+
* Note: host-cu-routes.ts has a deep import chain (conversation-store →
|
|
14
14
|
* conversation.ts → ces-client → service-contracts) that requires mocking
|
|
15
15
|
* before the module loads. We use dynamic imports to ensure all mocks are
|
|
16
16
|
* registered before the route module is evaluated.
|
|
@@ -75,7 +75,7 @@ const conversationStore = new Map<
|
|
|
75
75
|
{ hostCuProxy?: { processObservation: (...args: unknown[]) => void } }
|
|
76
76
|
>();
|
|
77
77
|
|
|
78
|
-
mock.module("../daemon/conversation-
|
|
78
|
+
mock.module("../daemon/conversation-store.js", () => ({
|
|
79
79
|
findConversation: (conversationId: string) =>
|
|
80
80
|
conversationStore.get(conversationId),
|
|
81
81
|
}));
|
|
@@ -23,7 +23,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
23
23
|
import {
|
|
24
24
|
DEFAULT_INJECTOR_ORDER,
|
|
25
25
|
defaultInjectors,
|
|
26
|
-
} from "../plugins/defaults/
|
|
26
|
+
} from "../plugins/defaults/injectors/register.js";
|
|
27
27
|
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
28
28
|
|
|
29
29
|
function findInjector(name: string): Injector {
|