@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
|
@@ -23,13 +23,35 @@ import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
|
23
23
|
import type { AcpSessionUpdate } from "../../daemon/message-types/acp.js";
|
|
24
24
|
import { getSqlite } from "../../memory/db-connection.js";
|
|
25
25
|
import { initializeDb } from "../../memory/db-init.js";
|
|
26
|
-
import {
|
|
27
|
-
clearHistory,
|
|
28
|
-
insertHistoryRow,
|
|
29
|
-
readHistoryRow,
|
|
30
|
-
} from "./helpers/acp-history-db.js";
|
|
31
26
|
initializeDb();
|
|
32
27
|
|
|
28
|
+
function clearHistory() {
|
|
29
|
+
getSqlite().run("DELETE FROM acp_session_history");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface HistoryRow {
|
|
33
|
+
id: string;
|
|
34
|
+
agent_id: string;
|
|
35
|
+
acp_session_id: string;
|
|
36
|
+
parent_conversation_id: string;
|
|
37
|
+
started_at: number;
|
|
38
|
+
completed_at: number | null;
|
|
39
|
+
status: string;
|
|
40
|
+
stop_reason: string | null;
|
|
41
|
+
error: string | null;
|
|
42
|
+
event_log_json: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readHistoryRow(id: string): HistoryRow | null {
|
|
46
|
+
return getSqlite()
|
|
47
|
+
.query(
|
|
48
|
+
`SELECT id, agent_id, acp_session_id, parent_conversation_id,
|
|
49
|
+
started_at, completed_at, status, stop_reason, error, event_log_json
|
|
50
|
+
FROM acp_session_history WHERE id = ?`,
|
|
51
|
+
)
|
|
52
|
+
.get(id) as HistoryRow | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
/**
|
|
34
56
|
* Builds a manager with a fake session pre-injected and returns the handles
|
|
35
57
|
* needed to drive terminal transitions in tests.
|
|
@@ -39,7 +61,6 @@ function buildSessionWithFakeProcess(opts: {
|
|
|
39
61
|
agentId: string;
|
|
40
62
|
protocolSessionId: string;
|
|
41
63
|
parentConversationId: string;
|
|
42
|
-
cwd?: string;
|
|
43
64
|
}): {
|
|
44
65
|
manager: AcpSessionManager;
|
|
45
66
|
resolvePrompt: (v: { stopReason: string }) => void;
|
|
@@ -105,7 +126,7 @@ function buildSessionWithFakeProcess(opts: {
|
|
|
105
126
|
sendToVellum: wrappedSend,
|
|
106
127
|
currentPrompt: null as Promise<unknown> | null,
|
|
107
128
|
parentConversationId: opts.parentConversationId,
|
|
108
|
-
cwd:
|
|
129
|
+
cwd: "/tmp",
|
|
109
130
|
command: "claude-agent-acp",
|
|
110
131
|
};
|
|
111
132
|
sessions.set(opts.id, entry);
|
|
@@ -324,94 +345,6 @@ describe("AcpSessionManager — terminal persistence", () => {
|
|
|
324
345
|
expect(row!.event_log_json.length).toBeLessThan(256 * 1024 + 1024);
|
|
325
346
|
});
|
|
326
347
|
|
|
327
|
-
test("persists the spawn cwd on terminal transition", async () => {
|
|
328
|
-
const id = "session-cwd-1";
|
|
329
|
-
const handles = buildSessionWithFakeProcess({
|
|
330
|
-
id,
|
|
331
|
-
agentId: "agent-cwd",
|
|
332
|
-
protocolSessionId: "proto-cwd",
|
|
333
|
-
parentConversationId: "conv-cwd",
|
|
334
|
-
cwd: "/Users/me/projects/widget",
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
handles.resolvePrompt({ stopReason: "end_turn" });
|
|
338
|
-
await Promise.resolve();
|
|
339
|
-
await Promise.resolve();
|
|
340
|
-
|
|
341
|
-
const row = readHistoryRow(id);
|
|
342
|
-
expect(row).not.toBeNull();
|
|
343
|
-
expect(row!.cwd).toBe("/Users/me/projects/widget");
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("legacy rows without a cwd read back as null", () => {
|
|
347
|
-
insertHistoryRow({
|
|
348
|
-
id: "legacy-row-1",
|
|
349
|
-
agentId: "agent-legacy",
|
|
350
|
-
acpSessionId: "proto-legacy",
|
|
351
|
-
parentConversationId: "conv-legacy",
|
|
352
|
-
startedAt: 1000,
|
|
353
|
-
completedAt: null,
|
|
354
|
-
status: "completed",
|
|
355
|
-
stopReason: null,
|
|
356
|
-
cwd: null,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const row = readHistoryRow("legacy-row-1");
|
|
360
|
-
expect(row).not.toBeNull();
|
|
361
|
-
expect(row!.cwd).toBeNull();
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
test("upserts over a pre-existing row with the same id (resumed runs)", async () => {
|
|
365
|
-
const id = "session-upsert-1";
|
|
366
|
-
// Simulate the row left behind by the original run that a resumed run
|
|
367
|
-
// reuses the id of.
|
|
368
|
-
insertHistoryRow({
|
|
369
|
-
id,
|
|
370
|
-
agentId: "agent-up",
|
|
371
|
-
acpSessionId: "proto-up",
|
|
372
|
-
parentConversationId: "conv-up",
|
|
373
|
-
startedAt: 1000,
|
|
374
|
-
completedAt: 2000,
|
|
375
|
-
status: "cancelled",
|
|
376
|
-
stopReason: "daemon_restarted",
|
|
377
|
-
eventLogJson: '[{"old":true}]',
|
|
378
|
-
cwd: "/old/cwd",
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
const handles = buildSessionWithFakeProcess({
|
|
382
|
-
id,
|
|
383
|
-
agentId: "agent-up",
|
|
384
|
-
protocolSessionId: "proto-up",
|
|
385
|
-
parentConversationId: "conv-up",
|
|
386
|
-
cwd: "/new/cwd",
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
handles.emitUpdate({
|
|
390
|
-
type: "acp_session_update",
|
|
391
|
-
acpSessionId: id,
|
|
392
|
-
updateType: "agent_message_chunk",
|
|
393
|
-
content: "from the resumed run",
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
handles.resolvePrompt({ stopReason: "end_turn" });
|
|
397
|
-
await Promise.resolve();
|
|
398
|
-
await Promise.resolve();
|
|
399
|
-
|
|
400
|
-
const count = getSqlite()
|
|
401
|
-
.query(`SELECT COUNT(*) AS n FROM acp_session_history WHERE id = ?`)
|
|
402
|
-
.get(id) as { n: number };
|
|
403
|
-
expect(count.n).toBe(1);
|
|
404
|
-
|
|
405
|
-
const row = readHistoryRow(id);
|
|
406
|
-
expect(row).not.toBeNull();
|
|
407
|
-
expect(row!.status).toBe("completed");
|
|
408
|
-
expect(row!.stop_reason).toBe("end_turn");
|
|
409
|
-
expect(row!.cwd).toBe("/new/cwd");
|
|
410
|
-
const log = JSON.parse(row!.event_log_json) as AcpSessionUpdate[];
|
|
411
|
-
expect(log).toHaveLength(1);
|
|
412
|
-
expect(log[0]).toMatchObject({ content: "from the resumed run" });
|
|
413
|
-
});
|
|
414
|
-
|
|
415
348
|
test("persists empty event log when no updates were emitted", async () => {
|
|
416
349
|
const id = "session-empty-1";
|
|
417
350
|
const handles = buildSessionWithFakeProcess({
|
package/src/acp/agent-process.ts
CHANGED
|
@@ -34,7 +34,6 @@ export type AcpClientFactory = (agent: Agent) => Client;
|
|
|
34
34
|
export class AcpAgentProcess {
|
|
35
35
|
private proc: ChildProcess | null = null;
|
|
36
36
|
private connection: acp.ClientSideConnection | null = null;
|
|
37
|
-
private initializeResponse: InitializeResponse | null = null;
|
|
38
37
|
|
|
39
38
|
constructor(
|
|
40
39
|
public readonly agentId: string,
|
|
@@ -100,7 +99,7 @@ export class AcpAgentProcess {
|
|
|
100
99
|
|
|
101
100
|
log.info({ agentId: this.agentId }, "Initializing ACP connection");
|
|
102
101
|
|
|
103
|
-
|
|
102
|
+
return this.connection.initialize({
|
|
104
103
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
105
104
|
clientInfo: { name: "vellum", version: "1.0.0" },
|
|
106
105
|
clientCapabilities: {
|
|
@@ -108,28 +107,6 @@ export class AcpAgentProcess {
|
|
|
108
107
|
terminal: true,
|
|
109
108
|
},
|
|
110
109
|
});
|
|
111
|
-
|
|
112
|
-
this.initializeResponse = response;
|
|
113
|
-
return response;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Whether the agent advertised support for `session/load` at initialize.
|
|
118
|
-
* Returns false before initialize() resolves.
|
|
119
|
-
*/
|
|
120
|
-
get supportsLoadSession(): boolean {
|
|
121
|
-
return this.initializeResponse?.agentCapabilities?.loadSession === true;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Whether the agent advertised support for `session/resume` at initialize.
|
|
126
|
-
* Returns false before initialize() resolves.
|
|
127
|
-
*/
|
|
128
|
-
get supportsSessionResume(): boolean {
|
|
129
|
-
return (
|
|
130
|
-
this.initializeResponse?.agentCapabilities?.sessionCapabilities?.resume !=
|
|
131
|
-
null
|
|
132
|
-
);
|
|
133
110
|
}
|
|
134
111
|
|
|
135
112
|
/**
|
|
@@ -151,41 +128,6 @@ export class AcpAgentProcess {
|
|
|
151
128
|
return result.sessionId;
|
|
152
129
|
}
|
|
153
130
|
|
|
154
|
-
/**
|
|
155
|
-
* Loads a previously persisted ACP session via `session/load`.
|
|
156
|
-
*
|
|
157
|
-
* Per the ACP spec, the agent replays the session's conversation history
|
|
158
|
-
* as `session/update` notifications before the load response resolves;
|
|
159
|
-
* callers should suppress forwarding of those replayed updates (see
|
|
160
|
-
* VellumAcpClientHandler.beginReplaySuppression).
|
|
161
|
-
*/
|
|
162
|
-
async loadSession(sessionId: string, cwd: string): Promise<void> {
|
|
163
|
-
if (!this.connection) {
|
|
164
|
-
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
log.info({ agentId: this.agentId, sessionId, cwd }, "Loading ACP session");
|
|
168
|
-
|
|
169
|
-
await this.connection.loadSession({ sessionId, cwd, mcpServers: [] });
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Resumes a previously persisted ACP session via `session/resume`.
|
|
174
|
-
*
|
|
175
|
-
* Unlike `session/load`, resume performs no history replay, so it is
|
|
176
|
-
* preferred when the agent advertises the capability
|
|
177
|
-
* (see supportsSessionResume).
|
|
178
|
-
*/
|
|
179
|
-
async resumeSession(sessionId: string, cwd: string): Promise<void> {
|
|
180
|
-
if (!this.connection) {
|
|
181
|
-
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
log.info({ agentId: this.agentId, sessionId, cwd }, "Resuming ACP session");
|
|
185
|
-
|
|
186
|
-
await this.connection.resumeSession({ sessionId, cwd, mcpServers: [] });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
131
|
/**
|
|
190
132
|
* Sends a prompt to an existing ACP session.
|
|
191
133
|
* Returns the prompt response (includes stopReason).
|
|
@@ -233,7 +175,6 @@ export class AcpAgentProcess {
|
|
|
233
175
|
this.proc = null;
|
|
234
176
|
}
|
|
235
177
|
this.connection = null;
|
|
236
|
-
this.initializeResponse = null;
|
|
237
178
|
}
|
|
238
179
|
|
|
239
180
|
/**
|
|
@@ -264,6 +205,5 @@ export class AcpAgentProcess {
|
|
|
264
205
|
|
|
265
206
|
this.proc = null;
|
|
266
207
|
this.connection = null;
|
|
267
|
-
this.initializeResponse = null;
|
|
268
208
|
}
|
|
269
209
|
}
|
|
@@ -51,7 +51,6 @@ interface TerminalState {
|
|
|
51
51
|
export class VellumAcpClientHandler implements Client {
|
|
52
52
|
private terminals = new Map<string, TerminalState>();
|
|
53
53
|
private accumulatedText = "";
|
|
54
|
-
private suppressForwarding = false;
|
|
55
54
|
/** Tracks pending ACP permission requestIds for cleanup on session close. */
|
|
56
55
|
readonly pendingRequestIds = new Set<string>();
|
|
57
56
|
|
|
@@ -66,38 +65,8 @@ export class VellumAcpClientHandler implements Client {
|
|
|
66
65
|
private readonly parentConversationId: string,
|
|
67
66
|
) {}
|
|
68
67
|
|
|
69
|
-
/**
|
|
70
|
-
* Begins suppressing session updates from being forwarded to Vellum.
|
|
71
|
-
*
|
|
72
|
-
* Per the ACP spec, `session/load` replays the entire conversation history
|
|
73
|
-
* as `session/update` notifications before the load response resolves. The
|
|
74
|
-
* parent conversation already received those events during the original
|
|
75
|
-
* run, so re-forwarding them would duplicate them into the conversation and
|
|
76
|
-
* the ring buffer. Callers wrap `loadSession` in
|
|
77
|
-
* beginReplaySuppression()/endReplaySuppression() to drop the replay.
|
|
78
|
-
* `session/resume` performs no replay, which is why it is preferred when
|
|
79
|
-
* the agent supports it.
|
|
80
|
-
*/
|
|
81
|
-
beginReplaySuppression(): void {
|
|
82
|
-
this.suppressForwarding = true;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Ends replay suppression; subsequent updates flow normally. */
|
|
86
|
-
endReplaySuppression(): void {
|
|
87
|
-
this.suppressForwarding = false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
68
|
async sessionUpdate(params: SessionNotification): Promise<void> {
|
|
91
69
|
const update = params.update;
|
|
92
|
-
|
|
93
|
-
if (this.suppressForwarding) {
|
|
94
|
-
log.debug(
|
|
95
|
-
{ acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
|
|
96
|
-
"Dropping replayed session update during suppression",
|
|
97
|
-
);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
70
|
log.debug(
|
|
102
71
|
{ acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
|
|
103
72
|
"ACP session update received",
|
|
@@ -20,91 +20,56 @@
|
|
|
20
20
|
* owns the plaintext read boundary.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { basename } from "node:path";
|
|
24
|
+
|
|
23
25
|
import { FailedDependencyError } from "../runtime/routes/errors.js";
|
|
24
26
|
import { credentialBroker } from "../tools/credentials/broker.js";
|
|
25
27
|
import {
|
|
26
28
|
getCredentialMetadata,
|
|
27
29
|
upsertCredentialMetadata,
|
|
28
30
|
} from "../tools/credentials/metadata-store.js";
|
|
29
|
-
import { getLogger } from "../util/logger.js";
|
|
30
|
-
import { adapterCommandOf } from "./resolve-agent.js";
|
|
31
31
|
import type { AcpAgentConfig } from "./types.js";
|
|
32
32
|
|
|
33
|
-
const log = getLogger("acp:prepare-agent-env");
|
|
34
|
-
|
|
35
33
|
const ACP_SPAWN_TOOL = "acp_spawn";
|
|
36
|
-
const ACP_SERVICE = "acp";
|
|
37
34
|
|
|
38
35
|
/**
|
|
39
|
-
* Ensure
|
|
40
|
-
* `acp_spawn` tool to read it, but only for legacy/unmanaged cases:
|
|
36
|
+
* Ensure the `acp/claude_oauth_token` credential has metadata that allows
|
|
37
|
+
* the `acp_spawn` tool to read it, but only for legacy/unmanaged cases:
|
|
41
38
|
*
|
|
42
|
-
* - No metadata at all
|
|
43
|
-
* - Metadata exists with an empty `allowedTools
|
|
39
|
+
* - No metadata at all → create with `allowedTools: ["acp_spawn"]`.
|
|
40
|
+
* - Metadata exists with an empty `allowedTools` → default provisioning
|
|
44
41
|
* path (user ran `credentials set` without `--allowed-tools`), add it.
|
|
45
|
-
* - Metadata exists with a non-empty `allowedTools
|
|
46
|
-
* by the user/admin. Respect it even if `acp_spawn` is absent
|
|
47
|
-
* broker will deny the read and the
|
|
42
|
+
* - Metadata exists with a non-empty `allowedTools` → explicit policy set
|
|
43
|
+
* by the user/admin. Respect it even if `acp_spawn` is absent — the
|
|
44
|
+
* broker will deny the read and the preflight will throw.
|
|
48
45
|
*/
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
usageDescription: string,
|
|
52
|
-
): void {
|
|
53
|
-
const meta = getCredentialMetadata(ACP_SERVICE, field);
|
|
46
|
+
function ensureAcpTokenPolicy(): void {
|
|
47
|
+
const meta = getCredentialMetadata("acp", "claude_oauth_token");
|
|
54
48
|
if (!meta) {
|
|
55
|
-
upsertCredentialMetadata(
|
|
49
|
+
upsertCredentialMetadata("acp", "claude_oauth_token", {
|
|
56
50
|
allowedTools: [ACP_SPAWN_TOOL],
|
|
57
|
-
usageDescription
|
|
51
|
+
usageDescription:
|
|
52
|
+
"Claude OAuth token for ACP agent authentication",
|
|
58
53
|
});
|
|
59
54
|
return;
|
|
60
55
|
}
|
|
61
56
|
const tools = meta.allowedTools ?? [];
|
|
62
57
|
if (tools.length === 0) {
|
|
63
|
-
upsertCredentialMetadata(
|
|
58
|
+
upsertCredentialMetadata("acp", "claude_oauth_token", {
|
|
64
59
|
allowedTools: [ACP_SPAWN_TOOL],
|
|
65
60
|
});
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
/**
|
|
70
|
-
* Read an `acp/<field>` credential through the broker and inject it into
|
|
71
|
-
* `env` under `envVar`. Returns the broker's failure reason when the value
|
|
72
|
-
* was not injected (missing credential, denied policy, no stored value),
|
|
73
|
-
* or undefined on success. Never throws: `serverUse` signals every failure
|
|
74
|
-
* mode, including a simply-absent credential, as `{ success: false,
|
|
75
|
-
* reason }`, so callers choose whether a miss is fatal.
|
|
76
|
-
*/
|
|
77
|
-
async function injectCredential(
|
|
78
|
-
env: Record<string, string>,
|
|
79
|
-
field: string,
|
|
80
|
-
envVar: string,
|
|
81
|
-
usageDescription: string,
|
|
82
|
-
): Promise<string | undefined> {
|
|
83
|
-
ensureAcpCredentialPolicy(field, usageDescription);
|
|
84
|
-
const result = await credentialBroker.serverUse<void>({
|
|
85
|
-
service: ACP_SERVICE,
|
|
86
|
-
field,
|
|
87
|
-
toolName: ACP_SPAWN_TOOL,
|
|
88
|
-
execute: async (value) => {
|
|
89
|
-
env[envVar] = value;
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
return result.success ? undefined : result.reason;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
64
|
/**
|
|
96
65
|
* Returns a NEW config with any required credentials merged into `env`.
|
|
97
66
|
* Does NOT mutate the input. Throws `FailedDependencyError` if a required
|
|
98
67
|
* credential is missing from both the user-supplied env override and the
|
|
99
68
|
* secure store.
|
|
100
69
|
*
|
|
101
|
-
* Gating is keyed off the
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* "claude-agent-acp", ... }` alias still gets the env it needs, and so does
|
|
105
|
-
* the bunx-rewritten claude adapter (whose `command` is "bun"). Without
|
|
106
|
-
* the adapterCommand gate, bunx-resolved spawns would start with no auth
|
|
107
|
-
* and die as zombies on the first prompt.
|
|
70
|
+
* Gating is keyed off the resolved agent COMMAND (basename), not the
|
|
71
|
+
* user-facing agent id, so a custom `acp.agents.my-claude = { command:
|
|
72
|
+
* "claude-agent-acp", ... }` alias still gets the env it needs.
|
|
108
73
|
*
|
|
109
74
|
* For `claude-agent-acp` the only required env var is
|
|
110
75
|
* `CLAUDE_CODE_OAUTH_TOKEN`. Two provisioning routes converge on it, with
|
|
@@ -119,11 +84,6 @@ async function injectCredential(
|
|
|
119
84
|
* before spawning. The "fail-fast" throw is symmetric with the existing
|
|
120
85
|
* `binary_not_found` preflight in `resolveAcpAgent` and strictly better
|
|
121
86
|
* than a `warn` + zombie subprocess 10 seconds later.
|
|
122
|
-
*
|
|
123
|
-
* For `gemini` the env var is `GEMINI_API_KEY`, provisioned the same two
|
|
124
|
-
* ways (config.json override wins, vault field `gemini_api_key` second),
|
|
125
|
-
* but it is OPTIONAL: the Gemini CLI supports its own OAuth login, so a
|
|
126
|
-
* vault miss proceeds without the key instead of failing the spawn.
|
|
127
87
|
*/
|
|
128
88
|
export async function prepareAgentEnv(
|
|
129
89
|
agentConfig: AcpAgentConfig,
|
|
@@ -132,16 +92,19 @@ export async function prepareAgentEnv(
|
|
|
132
92
|
// agent reference. The local `env` binding sidesteps TS narrowing
|
|
133
93
|
// limitations on the optional `AcpAgentConfig.env` field.
|
|
134
94
|
const env: Record<string, string> = { ...(agentConfig.env ?? {}) };
|
|
135
|
-
const
|
|
95
|
+
const commandBasename = basename(agentConfig.command);
|
|
136
96
|
|
|
137
|
-
if (
|
|
97
|
+
if (commandBasename === "claude-agent-acp") {
|
|
138
98
|
if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
|
|
99
|
+
ensureAcpTokenPolicy();
|
|
100
|
+
await credentialBroker.serverUse<void>({
|
|
101
|
+
service: "acp",
|
|
102
|
+
field: "claude_oauth_token",
|
|
103
|
+
toolName: ACP_SPAWN_TOOL,
|
|
104
|
+
execute: async (token) => {
|
|
105
|
+
env.CLAUDE_CODE_OAUTH_TOKEN = token;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
145
108
|
}
|
|
146
109
|
if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
147
110
|
throw new FailedDependencyError(
|
|
@@ -150,23 +113,6 @@ export async function prepareAgentEnv(
|
|
|
150
113
|
"(or set it under acp.agents.<id>.env in config.json).",
|
|
151
114
|
);
|
|
152
115
|
}
|
|
153
|
-
} else if (adapterCommand === "gemini") {
|
|
154
|
-
if (!env.GEMINI_API_KEY) {
|
|
155
|
-
const missReason = await injectCredential(
|
|
156
|
-
env,
|
|
157
|
-
"gemini_api_key",
|
|
158
|
-
"GEMINI_API_KEY",
|
|
159
|
-
"Gemini API key for ACP agent authentication",
|
|
160
|
-
);
|
|
161
|
-
if (missReason !== undefined) {
|
|
162
|
-
// Optional credential: Gemini CLI can authenticate via its own
|
|
163
|
-
// OAuth login, so a vault miss must not fail the spawn.
|
|
164
|
-
log.debug(
|
|
165
|
-
{ reason: missReason },
|
|
166
|
-
"Gemini API key unavailable from the vault; spawning without GEMINI_API_KEY",
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
116
|
}
|
|
171
117
|
|
|
172
118
|
return { ...agentConfig, env };
|