@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,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for
|
|
2
|
+
* Tests for `GET /v1/acp/sessions`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
* `?
|
|
8
|
-
* (default 50, max 500).
|
|
9
|
-
*
|
|
10
|
-
* `POST /v1/acp/spawn`: when the adapter binary is missing, the handler
|
|
11
|
-
* silently auto-installs allowlisted adapter packages before failing with
|
|
12
|
-
* the install hint. `execFile` is stubbed via the shared
|
|
13
|
-
* `installExecFileStub` helper so tests can script `npm i -g` outcomes.
|
|
4
|
+
* The handler merges in-memory `AcpSessionManager.getStatus()` output with
|
|
5
|
+
* persisted `acp_session_history` rows, deduping by id (in-memory wins),
|
|
6
|
+
* filtering by `?conversationId`, sorting newest-first, and truncating to
|
|
7
|
+
* `?limit` (default 50, max 500).
|
|
14
8
|
*/
|
|
15
9
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
import { installAcpConfigStub } from "../../../acp/__tests__/helpers/acp-config-stub.js";
|
|
19
|
-
import { installExecFileStub } from "../../../acp/__tests__/helpers/exec-file-stub.js";
|
|
20
|
-
import { installWhichStub } from "../../../acp/__tests__/helpers/which-stub.js";
|
|
10
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
21
11
|
|
|
22
12
|
mock.module("../../../util/logger.js", () => ({
|
|
23
13
|
getLogger: () =>
|
|
@@ -26,12 +16,6 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
26
16
|
}),
|
|
27
17
|
}));
|
|
28
18
|
|
|
29
|
-
const {
|
|
30
|
-
execScripts,
|
|
31
|
-
execFileMock,
|
|
32
|
-
reset: resetExecFileStub,
|
|
33
|
-
} = installExecFileStub();
|
|
34
|
-
|
|
35
19
|
// ---------------------------------------------------------------------------
|
|
36
20
|
// Stub the ACP session manager so tests control the in-memory side without
|
|
37
21
|
// spawning real child processes. The route handler imports
|
|
@@ -53,61 +37,64 @@ interface FakeSessionState {
|
|
|
53
37
|
|
|
54
38
|
let fakeInMemorySessions: FakeSessionState[] = [];
|
|
55
39
|
|
|
56
|
-
const spawnMock = mock(async () => ({
|
|
57
|
-
acpSessionId: "acp-route-session",
|
|
58
|
-
protocolSessionId: "proto-route-session",
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
const defaultSteerOrResumeImpl = async (
|
|
62
|
-
_id: string,
|
|
63
|
-
_instruction: string,
|
|
64
|
-
): Promise<{ resumed: boolean }> => ({ resumed: false });
|
|
65
|
-
let steerOrResumeImpl: (
|
|
66
|
-
id: string,
|
|
67
|
-
instruction: string,
|
|
68
|
-
) => Promise<{ resumed: boolean }> = defaultSteerOrResumeImpl;
|
|
69
|
-
const steerOrResumeMock = mock(
|
|
70
|
-
(id: string, instruction: string, _send: unknown) =>
|
|
71
|
-
steerOrResumeImpl(id, instruction),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
40
|
mock.module("../../../acp/index.js", () => ({
|
|
75
41
|
getAcpSessionManager: () => ({
|
|
76
42
|
getStatus: () => fakeInMemorySessions,
|
|
77
|
-
spawn: spawnMock,
|
|
78
|
-
steerOrResume: steerOrResumeMock,
|
|
79
43
|
}),
|
|
80
44
|
}));
|
|
81
45
|
|
|
82
|
-
|
|
83
|
-
// its own suite; spawn tests here only care about the resolve/install flow.
|
|
84
|
-
mock.module("../../../acp/prepare-agent-env.js", () => ({
|
|
85
|
-
prepareAgentEnv: async (agentConfig: unknown) => agentConfig,
|
|
86
|
-
}));
|
|
87
|
-
|
|
88
|
-
const config = await installAcpConfigStub();
|
|
89
|
-
const which = installWhichStub();
|
|
90
|
-
|
|
91
|
-
import {
|
|
92
|
-
clearHistory,
|
|
93
|
-
insertHistoryRow,
|
|
94
|
-
} from "../../../acp/__tests__/helpers/acp-history-db.js";
|
|
95
|
-
import {
|
|
96
|
-
AcpResumeError,
|
|
97
|
-
AcpSessionNotFoundError,
|
|
98
|
-
} from "../../../acp/session-manager.js";
|
|
46
|
+
import { getSqlite } from "../../../memory/db-connection.js";
|
|
99
47
|
import { initializeDb } from "../../../memory/db-init.js";
|
|
100
|
-
import {
|
|
101
|
-
|
|
102
|
-
const { ROUTES } = await import("../acp-routes.js");
|
|
103
|
-
const { _resetAdapterInstallCacheForTests } =
|
|
104
|
-
await import("../../../acp/auto-install.js");
|
|
48
|
+
import { ROUTES } from "../acp-routes.js";
|
|
105
49
|
|
|
106
50
|
initializeDb();
|
|
107
51
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
52
|
+
function clearHistory(): void {
|
|
53
|
+
getSqlite().run("DELETE FROM acp_session_history");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function insertHistoryRow(row: {
|
|
57
|
+
id: string;
|
|
58
|
+
agentId: string;
|
|
59
|
+
acpSessionId: string;
|
|
60
|
+
parentConversationId: string;
|
|
61
|
+
startedAt: number;
|
|
62
|
+
completedAt?: number | null;
|
|
63
|
+
status: string;
|
|
64
|
+
stopReason?: string | null;
|
|
65
|
+
error?: string | null;
|
|
66
|
+
eventLogJson?: string;
|
|
67
|
+
}): void {
|
|
68
|
+
getSqlite()
|
|
69
|
+
.query(
|
|
70
|
+
/*sql*/ `
|
|
71
|
+
INSERT INTO acp_session_history (
|
|
72
|
+
id,
|
|
73
|
+
agent_id,
|
|
74
|
+
acp_session_id,
|
|
75
|
+
parent_conversation_id,
|
|
76
|
+
started_at,
|
|
77
|
+
completed_at,
|
|
78
|
+
status,
|
|
79
|
+
stop_reason,
|
|
80
|
+
error,
|
|
81
|
+
event_log_json
|
|
82
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
83
|
+
`,
|
|
84
|
+
)
|
|
85
|
+
.run(
|
|
86
|
+
row.id,
|
|
87
|
+
row.agentId,
|
|
88
|
+
row.acpSessionId,
|
|
89
|
+
row.parentConversationId,
|
|
90
|
+
row.startedAt,
|
|
91
|
+
row.completedAt ?? null,
|
|
92
|
+
row.status,
|
|
93
|
+
row.stopReason ?? null,
|
|
94
|
+
row.error ?? null,
|
|
95
|
+
row.eventLogJson ?? "[]",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
111
98
|
|
|
112
99
|
function getSessionsHandler() {
|
|
113
100
|
const route = ROUTES.find(
|
|
@@ -135,13 +122,6 @@ interface ResponseShape {
|
|
|
135
122
|
beforeEach(() => {
|
|
136
123
|
fakeInMemorySessions = [];
|
|
137
124
|
clearHistory();
|
|
138
|
-
resetExecFileStub();
|
|
139
|
-
spawnMock.mockClear();
|
|
140
|
-
steerOrResumeMock.mockClear();
|
|
141
|
-
steerOrResumeImpl = defaultSteerOrResumeImpl;
|
|
142
|
-
_resetAdapterInstallCacheForTests();
|
|
143
|
-
config.setConfig({});
|
|
144
|
-
which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
|
|
145
125
|
});
|
|
146
126
|
|
|
147
127
|
describe("GET /v1/acp/sessions — merged in-memory + history", () => {
|
|
@@ -326,9 +306,7 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
|
|
|
326
306
|
});
|
|
327
307
|
|
|
328
308
|
const handler = getSessionsHandler();
|
|
329
|
-
const body = (await handler({
|
|
330
|
-
queryParams: { limit: "2" },
|
|
331
|
-
})) as ResponseShape;
|
|
309
|
+
const body = (await handler({ queryParams: { limit: "2" } })) as ResponseShape;
|
|
332
310
|
expect(body.sessions).toHaveLength(2);
|
|
333
311
|
expect(body.sessions.map((s) => s.id)).toEqual(["live-newest", "hist-mid"]);
|
|
334
312
|
});
|
|
@@ -414,209 +392,3 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
|
|
|
414
392
|
expect(body.sessions).toHaveLength(3);
|
|
415
393
|
});
|
|
416
394
|
});
|
|
417
|
-
|
|
418
|
-
// ---------------------------------------------------------------------------
|
|
419
|
-
// POST /v1/acp/spawn: auto-install on missing adapter binary
|
|
420
|
-
// ---------------------------------------------------------------------------
|
|
421
|
-
|
|
422
|
-
function getSpawnHandler() {
|
|
423
|
-
const route = ROUTES.find(
|
|
424
|
-
(r) => r.endpoint === "acp/spawn" && r.method === "POST",
|
|
425
|
-
);
|
|
426
|
-
if (!route) throw new Error("acp/spawn POST route not found");
|
|
427
|
-
return route.handler;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const SPAWN_BODY = {
|
|
431
|
-
agent: "claude",
|
|
432
|
-
task: "do something",
|
|
433
|
-
conversationId: "conv-1",
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
describe("POST /v1/acp/spawn: auto-install on missing binary", () => {
|
|
437
|
-
test("binary missing + bun present: spawn proceeds via bunx without npm", async () => {
|
|
438
|
-
which.setWhich({ bun: "/usr/local/bin/bun" });
|
|
439
|
-
|
|
440
|
-
const handler = getSpawnHandler();
|
|
441
|
-
const body = (await handler({ body: SPAWN_BODY })) as Record<
|
|
442
|
-
string,
|
|
443
|
-
unknown
|
|
444
|
-
>;
|
|
445
|
-
|
|
446
|
-
expect(body).toEqual({
|
|
447
|
-
acpSessionId: "acp-route-session",
|
|
448
|
-
protocolSessionId: "proto-route-session",
|
|
449
|
-
agent: "claude",
|
|
450
|
-
});
|
|
451
|
-
expect(execFileMock).not.toHaveBeenCalled();
|
|
452
|
-
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
453
|
-
const agentConfigArg = (spawnMock.mock.calls[0] as unknown[])[1] as {
|
|
454
|
-
command: string;
|
|
455
|
-
args: string[];
|
|
456
|
-
adapterCommand?: string;
|
|
457
|
-
};
|
|
458
|
-
expect(agentConfigArg.command).toBe("bun");
|
|
459
|
-
expect(agentConfigArg.args).toEqual([
|
|
460
|
-
"x",
|
|
461
|
-
"--bun",
|
|
462
|
-
"@agentclientprotocol/claude-agent-acp",
|
|
463
|
-
]);
|
|
464
|
-
expect(agentConfigArg.adapterCommand).toBe("claude-agent-acp");
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
test("known command: installs the mapped package and spawn proceeds", async () => {
|
|
468
|
-
// Binary appears on PATH only after `npm i -g` runs, simulating a
|
|
469
|
-
// successful global install.
|
|
470
|
-
let binaryOnPath = false;
|
|
471
|
-
which.setWhich((cmd) => (binaryOnPath ? `/usr/local/bin/${cmd}` : null));
|
|
472
|
-
execScripts.set("npm i", {
|
|
473
|
-
stdout: "",
|
|
474
|
-
onCall: () => {
|
|
475
|
-
binaryOnPath = true;
|
|
476
|
-
},
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
const handler = getSpawnHandler();
|
|
480
|
-
const body = (await handler({ body: SPAWN_BODY })) as Record<
|
|
481
|
-
string,
|
|
482
|
-
unknown
|
|
483
|
-
>;
|
|
484
|
-
|
|
485
|
-
expect(body).toEqual({
|
|
486
|
-
acpSessionId: "acp-route-session",
|
|
487
|
-
protocolSessionId: "proto-route-session",
|
|
488
|
-
agent: "claude",
|
|
489
|
-
});
|
|
490
|
-
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
491
|
-
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
492
|
-
expect(execFileMock.mock.calls[0][1]).toEqual([
|
|
493
|
-
"i",
|
|
494
|
-
"-g",
|
|
495
|
-
"@agentclientprotocol/claude-agent-acp",
|
|
496
|
-
]);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
test("npm failure: FailedDependencyError carries hint and failure reason", async () => {
|
|
500
|
-
which.setWhich({});
|
|
501
|
-
execScripts.set("npm i", {
|
|
502
|
-
error: new Error("EACCES: permission denied"),
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
const handler = getSpawnHandler();
|
|
506
|
-
const promise = handler({ body: SPAWN_BODY });
|
|
507
|
-
await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
|
|
508
|
-
await expect(promise).rejects.toThrow(
|
|
509
|
-
/claude-agent-acp is not on PATH.*npm i -g @agentclientprotocol\/claude-agent-acp.*auto-install failed.*EACCES/,
|
|
510
|
-
);
|
|
511
|
-
expect(spawnMock).not.toHaveBeenCalled();
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
test("unknown command: plain hint maps to FailedDependencyError", async () => {
|
|
515
|
-
// The allowlist itself (no npm invocation for unmapped commands) is
|
|
516
|
-
// pinned in auto-install.test.ts and spawn.test.ts; this asserts only
|
|
517
|
-
// the route's transport mapping of the plain-hint failure.
|
|
518
|
-
config.setConfig({
|
|
519
|
-
agents: { custom: { command: "custom-bin", args: [] } },
|
|
520
|
-
});
|
|
521
|
-
which.setWhich({});
|
|
522
|
-
|
|
523
|
-
const handler = getSpawnHandler();
|
|
524
|
-
const promise = handler({ body: { ...SPAWN_BODY, agent: "custom" } });
|
|
525
|
-
await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
|
|
526
|
-
await expect(promise).rejects.toThrow(
|
|
527
|
-
"custom-bin is not on PATH. Install 'custom-bin' and ensure it is on PATH.",
|
|
528
|
-
);
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
// ---------------------------------------------------------------------------
|
|
533
|
-
// POST /v1/acp/:id/steer: transparent resume of sessions not in memory
|
|
534
|
-
// ---------------------------------------------------------------------------
|
|
535
|
-
|
|
536
|
-
function getSteerHandler() {
|
|
537
|
-
const route = ROUTES.find(
|
|
538
|
-
(r) => r.endpoint === "acp/:id/steer" && r.method === "POST",
|
|
539
|
-
);
|
|
540
|
-
if (!route) throw new Error("acp/:id/steer POST route not found");
|
|
541
|
-
return route.handler;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
describe("POST /v1/acp/:id/steer: resume fallback", () => {
|
|
545
|
-
test("in-memory session steers without a resume", async () => {
|
|
546
|
-
const handler = getSteerHandler();
|
|
547
|
-
const body = await handler({
|
|
548
|
-
pathParams: { id: "live-1" },
|
|
549
|
-
body: { instruction: "redirect" },
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
expect(body).toEqual({ acpSessionId: "live-1", steered: true });
|
|
553
|
-
expect(steerOrResumeMock).toHaveBeenCalledTimes(1);
|
|
554
|
-
expect(steerOrResumeMock.mock.calls[0][0]).toBe("live-1");
|
|
555
|
-
expect(steerOrResumeMock.mock.calls[0][1]).toBe("redirect");
|
|
556
|
-
expect(typeof steerOrResumeMock.mock.calls[0][2]).toBe("function");
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
test("resumed session reports the resumed flag", async () => {
|
|
560
|
-
steerOrResumeImpl = async () => ({ resumed: true });
|
|
561
|
-
|
|
562
|
-
const handler = getSteerHandler();
|
|
563
|
-
const body = await handler({
|
|
564
|
-
pathParams: { id: "gone-1" },
|
|
565
|
-
body: { instruction: "keep going" },
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
expect(body).toEqual({
|
|
569
|
-
acpSessionId: "gone-1",
|
|
570
|
-
steered: true,
|
|
571
|
-
resumed: true,
|
|
572
|
-
});
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
test("typed not-found (no session, no history row) maps to NotFoundError", async () => {
|
|
576
|
-
steerOrResumeImpl = async (id) => {
|
|
577
|
-
throw new AcpSessionNotFoundError(id);
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
const handler = getSteerHandler();
|
|
581
|
-
const promise = handler({
|
|
582
|
-
pathParams: { id: "missing-1" },
|
|
583
|
-
body: { instruction: "go" },
|
|
584
|
-
});
|
|
585
|
-
await expect(promise).rejects.toBeInstanceOf(NotFoundError);
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
test("resume failure surfaces as FailedDependencyError with the actionable hint", async () => {
|
|
589
|
-
steerOrResumeImpl = async (id) => {
|
|
590
|
-
throw new AcpResumeError(
|
|
591
|
-
new Error(
|
|
592
|
-
`ACP session "${id}" was recorded before resume support ` +
|
|
593
|
-
`(no working directory persisted) and cannot be resumed. ` +
|
|
594
|
-
`Spawn a new session instead.`,
|
|
595
|
-
),
|
|
596
|
-
);
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
const handler = getSteerHandler();
|
|
600
|
-
const promise = handler({
|
|
601
|
-
pathParams: { id: "legacy-1" },
|
|
602
|
-
body: { instruction: "go" },
|
|
603
|
-
});
|
|
604
|
-
await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
|
|
605
|
-
await expect(promise).rejects.toThrow(/recorded before resume support/);
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
test("plain steer errors map to NotFoundError", async () => {
|
|
609
|
-
steerOrResumeImpl = async (id) => {
|
|
610
|
-
throw new Error(
|
|
611
|
-
`ACP session "${id}" is not running (status: initializing)`,
|
|
612
|
-
);
|
|
613
|
-
};
|
|
614
|
-
|
|
615
|
-
const handler = getSteerHandler();
|
|
616
|
-
const promise = handler({
|
|
617
|
-
pathParams: { id: "init-1" },
|
|
618
|
-
body: { instruction: "go" },
|
|
619
|
-
});
|
|
620
|
-
await expect(promise).rejects.toBeInstanceOf(NotFoundError);
|
|
621
|
-
});
|
|
622
|
-
});
|
|
@@ -23,7 +23,7 @@ mock.module("../../assistant-event-hub.js", () => ({
|
|
|
23
23
|
broadcastMessage: () => {},
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
-
import { findConversation } from "../../../daemon/conversation-
|
|
26
|
+
import { findConversation } from "../../../daemon/conversation-store.js";
|
|
27
27
|
import { createConversation } from "../../../memory/conversation-crud.js";
|
|
28
28
|
import { getDb } from "../../../memory/db-connection.js";
|
|
29
29
|
import { initializeDb } from "../../../memory/db-init.js";
|
|
@@ -42,7 +42,7 @@ const findBySurfaceCalls: string[] = [];
|
|
|
42
42
|
const getOrCreateCalls: string[] = [];
|
|
43
43
|
const rawGetCalls: Array<{ sql: string; params: unknown[] }> = [];
|
|
44
44
|
|
|
45
|
-
mock.module("../../../daemon/conversation-
|
|
45
|
+
mock.module("../../../daemon/conversation-store.js", () => ({
|
|
46
46
|
findConversation: (id: string) => {
|
|
47
47
|
findConvCalls.push(id);
|
|
48
48
|
return memoryById ?? undefined;
|
|
@@ -51,9 +51,6 @@ mock.module("../../../daemon/conversation-registry.js", () => ({
|
|
|
51
51
|
findBySurfaceCalls.push(surfaceId);
|
|
52
52
|
return memoryBySurface ?? undefined;
|
|
53
53
|
},
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
mock.module("../../../daemon/conversation-store.js", () => ({
|
|
57
54
|
getOrCreateConversation: async (id: string) => {
|
|
58
55
|
getOrCreateCalls.push(id);
|
|
59
56
|
if (!rehydrated) {
|
|
@@ -186,7 +183,9 @@ describe("triggerSurfaceAction handler", () => {
|
|
|
186
183
|
// SQL must filter the messages table by ui_surface payload pattern.
|
|
187
184
|
expect(rawGetCalls[0]!.sql).toContain("FROM messages");
|
|
188
185
|
expect(rawGetCalls[0]!.sql).toContain("LIKE");
|
|
189
|
-
expect(rawGetCalls[0]!.params).toEqual([
|
|
186
|
+
expect(rawGetCalls[0]!.params).toEqual([
|
|
187
|
+
`%"surfaceId":"surf-evicted"%`,
|
|
188
|
+
]);
|
|
190
189
|
expect(getOrCreateCalls).toEqual(["conv-from-db"]);
|
|
191
190
|
expect(rehydrated.surfaceActionCalls).toEqual([
|
|
192
191
|
{ surfaceId: "surf-evicted", actionId: "act-3", data: undefined },
|
|
@@ -51,7 +51,7 @@ const findBySurfaceCalls: string[] = [];
|
|
|
51
51
|
const getOrCreateCalls: string[] = [];
|
|
52
52
|
const rawGetCalls: Array<{ sql: string; params: unknown[] }> = [];
|
|
53
53
|
|
|
54
|
-
mock.module("../../../daemon/conversation-
|
|
54
|
+
mock.module("../../../daemon/conversation-store.js", () => ({
|
|
55
55
|
findConversation: (id: string) => {
|
|
56
56
|
findConvCalls.push(id);
|
|
57
57
|
return memoryById ?? undefined;
|
|
@@ -60,9 +60,6 @@ mock.module("../../../daemon/conversation-registry.js", () => ({
|
|
|
60
60
|
findBySurfaceCalls.push(surfaceId);
|
|
61
61
|
return undefined;
|
|
62
62
|
},
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
mock.module("../../../daemon/conversation-store.js", () => ({
|
|
66
63
|
getOrCreateConversation: async (id: string) => {
|
|
67
64
|
getOrCreateCalls.push(id);
|
|
68
65
|
if (!rehydrated) {
|
|
@@ -2,28 +2,25 @@
|
|
|
2
2
|
* Tests for the ACP route handlers.
|
|
3
3
|
*
|
|
4
4
|
* Suites:
|
|
5
|
-
* - POST /v1/acp/spawn
|
|
6
|
-
*
|
|
7
|
-
* auto-install surface is covered in `__tests__/acp-routes.test.ts`.
|
|
5
|
+
* - POST /v1/acp/spawn — the three failure paths produced by
|
|
6
|
+
* `resolveAcpAgent` (acp_disabled, unknown_agent, binary_not_found).
|
|
8
7
|
* - POST /v1/acp/spawn (env injection) — CLAUDE_CODE_OAUTH_TOKEN is read
|
|
9
8
|
* from the credential broker (policy-gated + audited) and merged into
|
|
10
9
|
* `agentConfig.env` ONLY for the `claude` agent.
|
|
11
10
|
* - DELETE /v1/acp/sessions?status=completed — the bulk-clear route that
|
|
12
11
|
* wipes terminal-state rows (completed/failed/cancelled) from
|
|
13
|
-
* `acp_session_history` while leaving running/initializing rows
|
|
14
|
-
* rows with an in-flight resume intact.
|
|
12
|
+
* `acp_session_history` while leaving running/initializing rows intact.
|
|
15
13
|
* - DELETE /v1/acp/sessions/:id — single-row delete: completed → 200,
|
|
16
|
-
* running
|
|
17
|
-
* { deleted: false }.
|
|
14
|
+
* running → 409, unknown id → idempotent { deleted: false }.
|
|
18
15
|
*
|
|
19
16
|
* The spawn tests mirror the resolver's test setup using the shared
|
|
20
17
|
* `installAcpConfigStub` and `installWhichStub` helpers so the host
|
|
21
18
|
* environment doesn't influence the resolver's PATH preflight.
|
|
22
19
|
*
|
|
23
|
-
* The delete tests stub `getAcpSessionManager` so we can drive
|
|
24
|
-
* in-memory-status
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* The single-id delete tests stub `getAcpSessionManager` so we can drive
|
|
21
|
+
* the in-memory-status check without spawning real ACP child processes,
|
|
22
|
+
* and use the real DB (initialized via the test preload's per-file
|
|
23
|
+
* workspace) to verify the row is actually removed.
|
|
27
24
|
*/
|
|
28
25
|
|
|
29
26
|
import {
|
|
@@ -51,10 +48,8 @@ afterAll(() => {
|
|
|
51
48
|
// in-memory-status check without spawning real ACP processes, and so the
|
|
52
49
|
// env-injection spawn tests can capture the `agentConfig` arg without
|
|
53
50
|
// launching a real subprocess. Stored in mutable state so individual tests
|
|
54
|
-
// can plant arbitrary states / inspect capture.
|
|
55
|
-
// ids reserved by an in-flight resumeFromHistory (no SessionEntry yet).
|
|
51
|
+
// can plant arbitrary states / inspect capture.
|
|
56
52
|
const inMemoryStates = new Map<string, AcpSessionState>();
|
|
57
|
-
const pendingResumeIds = new Set<string>();
|
|
58
53
|
|
|
59
54
|
interface CapturedSpawn {
|
|
60
55
|
agent: string;
|
|
@@ -75,9 +70,6 @@ mock.module("../../acp/index.js", () => ({
|
|
|
75
70
|
if (!state) throw new Error(`ACP session "${id}" not found`);
|
|
76
71
|
return state;
|
|
77
72
|
},
|
|
78
|
-
getActiveAndPendingIds: () => [
|
|
79
|
-
...new Set([...inMemoryStates.keys(), ...pendingResumeIds]),
|
|
80
|
-
],
|
|
81
73
|
spawn: async (
|
|
82
74
|
agent: string,
|
|
83
75
|
agentConfig: { env?: Record<string, string> },
|
|
@@ -234,6 +226,22 @@ describe("POST /v1/acp/spawn", () => {
|
|
|
234
226
|
).rejects.toThrow('Unknown agent "nonexistent"');
|
|
235
227
|
});
|
|
236
228
|
|
|
229
|
+
test("throws FailedDependencyError when the agent binary is missing", async () => {
|
|
230
|
+
config.setConfig({ agents: {} });
|
|
231
|
+
which.setWhich({});
|
|
232
|
+
|
|
233
|
+
const handler = getSpawnHandler();
|
|
234
|
+
await expect(
|
|
235
|
+
handler({
|
|
236
|
+
body: {
|
|
237
|
+
agent: "codex",
|
|
238
|
+
task: "do a thing",
|
|
239
|
+
conversationId: "conv-1",
|
|
240
|
+
},
|
|
241
|
+
}),
|
|
242
|
+
).rejects.toThrow("codex-acp is not on PATH");
|
|
243
|
+
});
|
|
244
|
+
|
|
237
245
|
test("body-shape guard short-circuits before the resolver runs", async () => {
|
|
238
246
|
config.setConfig({ enabled: false });
|
|
239
247
|
|
|
@@ -449,8 +457,6 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
|
|
|
449
457
|
});
|
|
450
458
|
|
|
451
459
|
beforeEach(() => {
|
|
452
|
-
inMemoryStates.clear();
|
|
453
|
-
pendingResumeIds.clear();
|
|
454
460
|
getSqlite().run("DELETE FROM acp_session_history");
|
|
455
461
|
});
|
|
456
462
|
|
|
@@ -476,57 +482,6 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
|
|
|
476
482
|
]);
|
|
477
483
|
});
|
|
478
484
|
|
|
479
|
-
test("excludes terminal rows whose session is active in memory (resumed sessions)", async () => {
|
|
480
|
-
// A resumed session reuses its original id: the in-memory state is
|
|
481
|
-
// `running` while its history row still carries the old terminal
|
|
482
|
-
// status until the next terminal upsert. The bulk delete must not
|
|
483
|
-
// remove that row out from under the live session.
|
|
484
|
-
seedHistoryRow("row-resumed", "completed", 1000);
|
|
485
|
-
seedHistoryRow("row-completed", "completed", 2000);
|
|
486
|
-
inMemoryStates.set("row-resumed", {
|
|
487
|
-
id: "row-resumed",
|
|
488
|
-
agentId: "claude",
|
|
489
|
-
acpSessionId: "proto-resumed",
|
|
490
|
-
parentConversationId: "conv-test",
|
|
491
|
-
status: "running",
|
|
492
|
-
startedAt: 1000,
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
const handler = getBulkDeleteHandler();
|
|
496
|
-
const result = (await handler({
|
|
497
|
-
queryParams: { status: "completed" },
|
|
498
|
-
})) as {
|
|
499
|
-
deleted: number;
|
|
500
|
-
};
|
|
501
|
-
expect(result.deleted).toBe(1);
|
|
502
|
-
|
|
503
|
-
const remaining = listRows();
|
|
504
|
-
expect(remaining).toEqual([{ id: "row-resumed", status: "completed" }]);
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
test("excludes terminal rows whose session has a resume in flight", async () => {
|
|
508
|
-
// A resume that is still awaiting env preparation has no in-memory
|
|
509
|
-
// SessionEntry yet, but its history row (still terminal) must survive:
|
|
510
|
-
// deleting it mid-resume would let the later terminal upsert resurrect
|
|
511
|
-
// it as an orphan row.
|
|
512
|
-
seedHistoryRow("row-pending-resume", "completed", 1000);
|
|
513
|
-
seedHistoryRow("row-completed", "completed", 2000);
|
|
514
|
-
pendingResumeIds.add("row-pending-resume");
|
|
515
|
-
|
|
516
|
-
const handler = getBulkDeleteHandler();
|
|
517
|
-
const result = (await handler({
|
|
518
|
-
queryParams: { status: "completed" },
|
|
519
|
-
})) as {
|
|
520
|
-
deleted: number;
|
|
521
|
-
};
|
|
522
|
-
expect(result.deleted).toBe(1);
|
|
523
|
-
|
|
524
|
-
const remaining = listRows();
|
|
525
|
-
expect(remaining).toEqual([
|
|
526
|
-
{ id: "row-pending-resume", status: "completed" },
|
|
527
|
-
]);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
485
|
test("returns deleted=0 when no terminal rows are present", async () => {
|
|
531
486
|
seedHistoryRow("row-running", "running", 1000);
|
|
532
487
|
|
|
@@ -604,7 +559,6 @@ describe("DELETE /v1/acp/sessions/:id", () => {
|
|
|
604
559
|
|
|
605
560
|
beforeEach(() => {
|
|
606
561
|
inMemoryStates.clear();
|
|
607
|
-
pendingResumeIds.clear();
|
|
608
562
|
getDb().delete(acpSessionHistory).run();
|
|
609
563
|
});
|
|
610
564
|
|
|
@@ -656,24 +610,6 @@ describe("DELETE /v1/acp/sessions/:id", () => {
|
|
|
656
610
|
},
|
|
657
611
|
);
|
|
658
612
|
|
|
659
|
-
test("returns 409 when the session has a resume in flight (not yet in memory)", async () => {
|
|
660
|
-
pendingResumeIds.add("sess-resuming");
|
|
661
|
-
insertHistoryRow({ id: "sess-resuming", status: "completed" });
|
|
662
|
-
|
|
663
|
-
const handler = getDeleteSessionHandler();
|
|
664
|
-
expect(() => handler({ pathParams: { id: "sess-resuming" } })).toThrow(
|
|
665
|
-
"resume in flight",
|
|
666
|
-
);
|
|
667
|
-
|
|
668
|
-
// Row untouched.
|
|
669
|
-
const remaining = getDb()
|
|
670
|
-
.select()
|
|
671
|
-
.from(acpSessionHistory)
|
|
672
|
-
.where(eq(acpSessionHistory.id, "sess-resuming"))
|
|
673
|
-
.all();
|
|
674
|
-
expect(remaining).toHaveLength(1);
|
|
675
|
-
});
|
|
676
|
-
|
|
677
613
|
test("idempotent for unknown id — returns { deleted: false }", async () => {
|
|
678
614
|
const handler = getDeleteSessionHandler();
|
|
679
615
|
const result = (await handler({
|