@cursor/july 0.1.26 → 0.1.27
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/dist/ab.d.ts +6 -0
- package/dist/ab.d.ts.map +1 -1
- package/dist/ab.js +1 -0
- package/dist/artifacts.d.ts +34 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +57 -0
- package/dist/bin/agent-serve.js +31 -9
- package/dist/channels/github/github-channel.d.ts +1 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +14 -1
- package/dist/channels/github/types.d.ts +6 -1
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/api.d.ts +2 -1
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +1 -0
- package/dist/channels/slack/cursor-account.d.ts +2 -1
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +1 -1
- package/dist/channels/slack/dispatch.d.ts +4 -1
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +3 -3
- package/dist/channels/slack/interactive.d.ts +2 -1
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +1 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -3
- package/dist/channels/slack/types.d.ts +6 -1
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +1 -1
- package/dist/docs/ab.html +2 -2
- package/dist/docs/assets/{app.DYcC9FY-.js → app.BICLoJYF.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.CQJytDwU.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.wN4Ko9dT.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.dnnTE-Co.js} +2 -2
- package/dist/docs/building-with-agents.html +2 -2
- package/dist/docs/concepts.html +2 -2
- package/dist/docs/deployment.html +2 -2
- package/dist/docs/evals.html +2 -2
- package/dist/docs/example-agents/approval-buddy.html +2 -2
- package/dist/docs/example-agents/benny.html +2 -2
- package/dist/docs/example-agents/bugbot.html +2 -2
- package/dist/docs/example-agents/codebase-wiki.html +2 -2
- package/dist/docs/example-agents/codeowners-review.html +2 -2
- package/dist/docs/example-agents/concierge.html +2 -2
- package/dist/docs/example-agents/fsd.html +2 -2
- package/dist/docs/example-agents/index.html +2 -2
- package/dist/docs/example-agents/knowledge-base.html +2 -2
- package/dist/docs/example-agents/oncall.html +2 -2
- package/dist/docs/example-agents/security-reviewer.html +2 -2
- package/dist/docs/example-agents/slack-agent.html +2 -2
- package/dist/docs/example-agents/weather-agent.html +2 -2
- package/dist/docs/guides/agent-to-agent.html +2 -2
- package/dist/docs/guides/cloud-runtime.html +2 -2
- package/dist/docs/guides/github.html +2 -2
- package/dist/docs/guides/human-in-the-loop.html +2 -2
- package/dist/docs/guides/mcp-oauth.html +2 -2
- package/dist/docs/guides/slack.html +2 -2
- package/dist/docs/guides/webhooks.html +2 -2
- package/dist/docs/hillclimbing.html +2 -2
- package/dist/docs/index.html +2 -2
- package/dist/docs/quickstart.html +2 -2
- package/dist/docs/reference/agent-config.html +2 -2
- package/dist/docs/reference/channels.html +2 -2
- package/dist/docs/reference/cli.html +2 -2
- package/dist/docs/reference/connections.html +2 -2
- package/dist/docs/reference/hooks.html +2 -2
- package/dist/docs/reference/http-api.html +2 -2
- package/dist/docs/reference/instructions.html +2 -2
- package/dist/docs/reference/playground.html +2 -2
- package/dist/docs/reference/project-layout.html +2 -2
- package/dist/docs/reference/prompt.html +2 -2
- package/dist/docs/reference/schedules.html +2 -2
- package/dist/docs/reference/sessions.html +2 -2
- package/dist/docs/reference/skills.html +2 -2
- package/dist/docs/reference/subagents.html +2 -2
- package/dist/docs/reference/tools.html +2 -2
- package/dist/docs/scaffolding-agents.html +2 -2
- package/dist/docs/storage.html +2 -2
- package/dist/docs/troubleshooting.html +2 -2
- package/dist/evals/assertions.d.ts +10 -1
- package/dist/evals/assertions.d.ts.map +1 -1
- package/dist/evals/assertions.js +12 -0
- package/dist/evals.d.ts +1 -1
- package/dist/evals.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/ab-fold.d.ts.map +1 -1
- package/dist/internal/ab-fold.js +4 -0
- package/dist/internal/ab-snapshot.d.ts.map +1 -1
- package/dist/internal/ab-snapshot.js +1 -0
- package/dist/internal/artifacts-store.d.ts +45 -0
- package/dist/internal/artifacts-store.d.ts.map +1 -0
- package/dist/internal/artifacts-store.js +250 -0
- package/dist/internal/builtin-tools/artifacts.d.ts +10 -0
- package/dist/internal/builtin-tools/artifacts.d.ts.map +1 -0
- package/dist/internal/builtin-tools/artifacts.js +73 -0
- package/dist/internal/builtin-tools/index.d.ts +8 -1
- package/dist/internal/builtin-tools/index.d.ts.map +1 -1
- package/dist/internal/builtin-tools/index.js +15 -0
- package/dist/internal/cli-ax.d.ts +11 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +80 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +14 -6
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +83 -2
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/model-pricing.d.ts +49 -0
- package/dist/internal/model-pricing.d.ts.map +1 -0
- package/dist/internal/model-pricing.js +377 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +1 -0
- package/dist/internal/schedule-runner.d.ts.map +1 -1
- package/dist/internal/schedule-runner.js +1 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +9 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +39 -0
- package/dist/internal/session-cost.d.ts +49 -0
- package/dist/internal/session-cost.d.ts.map +1 -0
- package/dist/internal/session-cost.js +175 -0
- package/dist/internal/session-engine.d.ts +35 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +128 -7
- package/dist/internal/storage-coordinator.d.ts +15 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +52 -0
- package/dist/internal/trajectory.d.ts +9 -1
- package/dist/internal/trajectory.d.ts.map +1 -1
- package/dist/internal/trajectory.js +32 -5
- package/dist/playground/assets/index-BDCeW4uc.js +86 -0
- package/dist/playground/assets/index-Bul1WfrB.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +4 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +4 -0
- package/dist/types.d.ts +164 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +26 -26
- package/src/ab.ts +7 -0
- package/src/bin/agent-serve.ts +30 -2
- package/src/internal/ab-fold.ts +4 -0
- package/src/internal/ab-snapshot.ts +1 -0
- package/src/internal/cli-ax.ts +95 -0
- package/src/internal/model-pricing.ts +426 -0
- package/src/internal/sdk-runner.ts +10 -2
- package/src/internal/session-cost.ts +231 -0
- package/src/internal/session-engine.ts +28 -0
- package/src/internal/trajectory.ts +45 -3
- package/src/types.ts +24 -1
- package/dist/channels/github/instrument.d.ts +0 -20
- package/dist/channels/github/instrument.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/json-dir-store.js +0 -100
- package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
- package/dist/playground/assets/index-DqXdAFGa.js +0 -85
- package/src/bin/agent-serve.version.test.ts +0 -64
- package/src/channels/github/api.test.ts +0 -64
- package/src/channels/github/auth.test.ts +0 -105
- package/src/channels/github/cursor-account.test.ts +0 -204
- package/src/channels/github/forward.test.ts +0 -457
- package/src/channels/github/github.test.ts +0 -985
- package/src/channels/github/replay.test.ts +0 -179
- package/src/channels/slack/api.post-message.test.ts +0 -148
- package/src/channels/slack/approvals.test.ts +0 -328
- package/src/channels/slack/block-actions.test.ts +0 -469
- package/src/channels/slack/bot-mentions.test.ts +0 -267
- package/src/channels/slack/channel-watch.test.ts +0 -363
- package/src/channels/slack/cursor-account.test.ts +0 -301
- package/src/channels/slack/defaults.final-post.test.ts +0 -182
- package/src/channels/slack/dispatch.test.ts +0 -875
- package/src/channels/slack/eval-directive.test.ts +0 -273
- package/src/channels/slack/message-body.test.ts +0 -54
- package/src/channels/slack/nudge-store.test.ts +0 -143
- package/src/channels/slack/slack.test.ts +0 -391
- package/src/channels/slack/stop.test.ts +0 -23
- package/src/channels/slack/thread-context.test.ts +0 -202
- package/src/evals/assertions.test.ts +0 -617
- package/src/evals/expect.test.ts +0 -144
- package/src/evals/judge.test.ts +0 -181
- package/src/evals/loaders.test.ts +0 -132
- package/src/evals/matchers.test.ts +0 -95
- package/src/evals/reporters.test.ts +0 -303
- package/src/evals/run-facts.test.ts +0 -259
- package/src/internal/ab-snapshot.test.ts +0 -325
- package/src/internal/approval-gate.test.ts +0 -49
- package/src/internal/approvals.integration.test.ts +0 -383
- package/src/internal/artifacts-store.test.ts +0 -283
- package/src/internal/authored-loaders.test.ts +0 -31
- package/src/internal/builtin-tools/artifacts.test.ts +0 -247
- package/src/internal/builtin-tools/reminders.test.ts +0 -201
- package/src/internal/channel-route-schema.test.ts +0 -294
- package/src/internal/chat-attach.test.ts +0 -262
- package/src/internal/cli-deploy.test.ts +0 -1991
- package/src/internal/cli-docs.test.ts +0 -161
- package/src/internal/cli-mcp.test.ts +0 -789
- package/src/internal/cli-skills.test.ts +0 -133
- package/src/internal/cli-slack.test.ts +0 -1654
- package/src/internal/cloud-merge.test.ts +0 -74
- package/src/internal/cron.test.ts +0 -22
- package/src/internal/cursor/account-mcp.test.ts +0 -807
- package/src/internal/cursor/backend-client.test.ts +0 -591
- package/src/internal/cursor/credentials.test.ts +0 -351
- package/src/internal/cursor/github-credentials.test.ts +0 -136
- package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
- package/src/internal/cursor-account.integration.test.ts +0 -441
- package/src/internal/cursor-event-relay.test.ts +0 -746
- package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
- package/src/internal/cursor-slack-relay.test.ts +0 -525
- package/src/internal/deploy-source.test.ts +0 -111
- package/src/internal/discovery.artifact-tool.test.ts +0 -136
- package/src/internal/discovery.artifacts.test.ts +0 -119
- package/src/internal/discovery.builtin-tools.test.ts +0 -94
- package/src/internal/discovery.concurrency.test.ts +0 -60
- package/src/internal/discovery.cursor-account.test.ts +0 -133
- package/src/internal/discovery.cwd.test.ts +0 -83
- package/src/internal/discovery.hosting.test.ts +0 -80
- package/src/internal/discovery.identity.test.ts +0 -44
- package/src/internal/docs-site.test.ts +0 -66
- package/src/internal/duration.test.ts +0 -29
- package/src/internal/eval-judge-model.test.ts +0 -187
- package/src/internal/eval-run-store.cancel.test.ts +0 -142
- package/src/internal/eval-run-store.storage.test.ts +0 -211
- package/src/internal/eval-runner.http.test.ts +0 -403
- package/src/internal/eval-runner.run.test.ts +0 -928
- package/src/internal/evals-client.test.ts +0 -307
- package/src/internal/event-mapper.test.ts +0 -243
- package/src/internal/github-fanout.test.ts +0 -213
- package/src/internal/handleAgentServeTrigger.test.ts +0 -185
- package/src/internal/host-kv.test.ts +0 -82
- package/src/internal/host-platforms.test.ts +0 -126
- package/src/internal/http-channel.test.ts +0 -402
- package/src/internal/init-project.test.ts +0 -270
- package/src/internal/install-cursor-skills.test.ts +0 -262
- package/src/internal/local-env.test.ts +0 -120
- package/src/internal/log-ring.test.ts +0 -31
- package/src/internal/logs-client.test.ts +0 -350
- package/src/internal/mcp-endpoint.test.ts +0 -436
- package/src/internal/mcp-host.test.ts +0 -298
- package/src/internal/mcp-oauth.test.ts +0 -148
- package/src/internal/net.test.ts +0 -17
- package/src/internal/peer-connections.test.ts +0 -128
- package/src/internal/peer-mcp.integration.test.ts +0 -289
- package/src/internal/playground/toolchain.test.ts +0 -53
- package/src/internal/playground-cli.test.ts +0 -187
- package/src/internal/playground-proxy.test.ts +0 -376
- package/src/internal/prompt-context.integration.test.ts +0 -232
- package/src/internal/prompt-context.test.ts +0 -127
- package/src/internal/reminder-runner.test.ts +0 -432
- package/src/internal/reminder-store.test.ts +0 -53
- package/src/internal/request-headers.test.ts +0 -27
- package/src/internal/resolve-prod-target.test.ts +0 -787
- package/src/internal/resolved-connections.test.ts +0 -295
- package/src/internal/router.test.ts +0 -57
- package/src/internal/sdk-runner.test.ts +0 -290
- package/src/internal/server.artifacts.test.ts +0 -334
- package/src/internal/session-engine.artifacts.test.ts +0 -293
- package/src/internal/session-engine.coalesce.test.ts +0 -169
- package/src/internal/session-engine.concurrency.test.ts +0 -250
- package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
- package/src/internal/session-engine.interrupt.test.ts +0 -577
- package/src/internal/session-engine.storage.test.ts +0 -547
- package/src/internal/session-urls.test.ts +0 -28
- package/src/internal/sessions-client.test.ts +0 -518
- package/src/internal/storage-coordinator.test.ts +0 -517
- package/src/internal/tool-call.test.ts +0 -458
- package/src/internal/tool-result.test.ts +0 -52
- package/src/internal/trajectory.approvals.test.ts +0 -83
- package/src/internal/trajectory.subagents.test.ts +0 -198
- package/src/internal/turn-governor.test.ts +0 -137
- package/src/internal/update-check.test.ts +0 -485
- package/src/internal/workspace.test.ts +0 -207
- package/src/storage-backends/cursor-hosted.test.ts +0 -121
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration: mock AgentRunner parks on needsApproval; resolve unblocks.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
9
|
-
import { httpChannel } from "../channels.js";
|
|
10
|
-
import { defineTool } from "../tools.js";
|
|
11
|
-
import type { AgentProject, RunnerTurnRequest, ToolContext } from "../types.js";
|
|
12
|
-
import type { AgentRunner } from "./sdk-runner.js";
|
|
13
|
-
import { SessionAuthError, SessionEngine } from "./session-engine.js";
|
|
14
|
-
|
|
15
|
-
function makeHitlProject(executeFlag: { called: boolean }): AgentProject {
|
|
16
|
-
const definition = defineTool({
|
|
17
|
-
description: "A risky tool that needs approval",
|
|
18
|
-
needsApproval: true,
|
|
19
|
-
async execute(input: { note?: string }, _ctx: ToolContext) {
|
|
20
|
-
executeFlag.called = true;
|
|
21
|
-
return { ok: true, note: input.note ?? "" };
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
rootDir: "/tmp/hitl-fixture",
|
|
27
|
-
agentDir: "/tmp/hitl-fixture/agent",
|
|
28
|
-
name: "hitl-fixture",
|
|
29
|
-
agent: {
|
|
30
|
-
name: "hitl-fixture",
|
|
31
|
-
runtime: "local",
|
|
32
|
-
tools: [
|
|
33
|
-
{
|
|
34
|
-
name: "risky_action",
|
|
35
|
-
description: definition.description,
|
|
36
|
-
execution: "server",
|
|
37
|
-
needsApproval: true,
|
|
38
|
-
inputSchema: {
|
|
39
|
-
type: "object",
|
|
40
|
-
properties: { note: { type: "string" } },
|
|
41
|
-
additionalProperties: false,
|
|
42
|
-
},
|
|
43
|
-
definition,
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
skills: [],
|
|
47
|
-
connections: [],
|
|
48
|
-
subagents: [],
|
|
49
|
-
seedFiles: [],
|
|
50
|
-
},
|
|
51
|
-
httpChannel: httpChannel(),
|
|
52
|
-
channels: [],
|
|
53
|
-
schedules: [],
|
|
54
|
-
hooks: [],
|
|
55
|
-
abs: [],
|
|
56
|
-
diagnostics: [],
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe("human-in-the-loop approvals (integration)", () => {
|
|
61
|
-
let stateRoot: string | undefined;
|
|
62
|
-
|
|
63
|
-
afterEach(async () => {
|
|
64
|
-
if (stateRoot !== undefined) {
|
|
65
|
-
await rm(stateRoot, { recursive: true, force: true });
|
|
66
|
-
stateRoot = undefined;
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("parks risky_action until approve, then finishes the turn", async () => {
|
|
71
|
-
stateRoot = await mkdtemp(join(tmpdir(), "agent-serve-hitl-"));
|
|
72
|
-
const executeFlag = { called: false };
|
|
73
|
-
const project = makeHitlProject(executeFlag);
|
|
74
|
-
|
|
75
|
-
const runner: AgentRunner = {
|
|
76
|
-
async runTurn(request: RunnerTurnRequest) {
|
|
77
|
-
const tool = request.customTools.risky_action;
|
|
78
|
-
if (tool === undefined) {
|
|
79
|
-
throw new Error("risky_action not registered");
|
|
80
|
-
}
|
|
81
|
-
const result = await tool.execute(
|
|
82
|
-
{ note: "ship it" },
|
|
83
|
-
{ toolCallId: "call_alert_1" }
|
|
84
|
-
);
|
|
85
|
-
expect(result).toMatchObject({ ok: true, note: "ship it" });
|
|
86
|
-
return {
|
|
87
|
-
status: "finished",
|
|
88
|
-
result: "done",
|
|
89
|
-
};
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const engine = new SessionEngine({
|
|
94
|
-
project,
|
|
95
|
-
stateRoot,
|
|
96
|
-
runner,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const auth = {
|
|
100
|
-
authenticator: "local-dev",
|
|
101
|
-
principalId: "loopback",
|
|
102
|
-
principalType: "user" as const,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const sessionPromise = engine.send("http", "please run risky action", {
|
|
106
|
-
auth,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
let callId: string | undefined;
|
|
110
|
-
let sessionId: string | undefined;
|
|
111
|
-
for (let i = 0; i < 100; i++) {
|
|
112
|
-
const sessions = await engine.sessions.list();
|
|
113
|
-
if (sessions.length > 0) {
|
|
114
|
-
sessionId = sessions[0]!.sessionId;
|
|
115
|
-
const pending = await engine.listPendingApprovals(sessionId);
|
|
116
|
-
if (pending.length > 0) {
|
|
117
|
-
callId = pending[0]!.callId;
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
expect(sessionId).toBeDefined();
|
|
125
|
-
expect(callId).toBe("call_alert_1");
|
|
126
|
-
expect(executeFlag.called).toBe(false);
|
|
127
|
-
|
|
128
|
-
await engine.resolveApproval(sessionId!, callId!, "approve", auth);
|
|
129
|
-
|
|
130
|
-
const session = await sessionPromise;
|
|
131
|
-
await session.waitForCompletion();
|
|
132
|
-
|
|
133
|
-
expect(executeFlag.called).toBe(true);
|
|
134
|
-
|
|
135
|
-
const events = await engine.listEvents(session.id);
|
|
136
|
-
const types = events.map((e) => e.type);
|
|
137
|
-
expect(types).toContain("action.approval_requested");
|
|
138
|
-
expect(types).toContain("action.approval_resolved");
|
|
139
|
-
expect(types).toContain("session.waiting");
|
|
140
|
-
|
|
141
|
-
await engine.close();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("rejects approval resolution from a different principal", async () => {
|
|
145
|
-
stateRoot = await mkdtemp(join(tmpdir(), "agent-serve-hitl-"));
|
|
146
|
-
const executeFlag = { called: false };
|
|
147
|
-
const project = makeHitlProject(executeFlag);
|
|
148
|
-
|
|
149
|
-
const runner: AgentRunner = {
|
|
150
|
-
async runTurn(request: RunnerTurnRequest) {
|
|
151
|
-
const tool = request.customTools.risky_action;
|
|
152
|
-
if (tool === undefined) {
|
|
153
|
-
throw new Error("risky_action not registered");
|
|
154
|
-
}
|
|
155
|
-
await tool.execute({ note: "ship it" }, { toolCallId: "call_alert_2" });
|
|
156
|
-
return { status: "finished", result: "done" };
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const engine = new SessionEngine({
|
|
161
|
-
project,
|
|
162
|
-
stateRoot,
|
|
163
|
-
runner,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const ownerAuth = {
|
|
167
|
-
authenticator: "local-dev",
|
|
168
|
-
principalId: "owner",
|
|
169
|
-
principalType: "user" as const,
|
|
170
|
-
};
|
|
171
|
-
const otherAuth = {
|
|
172
|
-
authenticator: "local-dev",
|
|
173
|
-
principalId: "other",
|
|
174
|
-
principalType: "user" as const,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
void engine.send("http", "please run risky action", { auth: ownerAuth });
|
|
178
|
-
|
|
179
|
-
let callId: string | undefined;
|
|
180
|
-
let sessionId: string | undefined;
|
|
181
|
-
for (let i = 0; i < 100; i++) {
|
|
182
|
-
const sessions = await engine.sessions.list();
|
|
183
|
-
if (sessions.length > 0) {
|
|
184
|
-
sessionId = sessions[0]!.sessionId;
|
|
185
|
-
const pending = await engine.listPendingApprovals(sessionId);
|
|
186
|
-
if (pending.length > 0) {
|
|
187
|
-
callId = pending[0]!.callId;
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
expect(sessionId).toBeDefined();
|
|
195
|
-
expect(callId).toBeDefined();
|
|
196
|
-
|
|
197
|
-
await expect(
|
|
198
|
-
engine.resolveApproval(sessionId!, callId!, "approve", otherAuth)
|
|
199
|
-
).rejects.toThrow(SessionAuthError);
|
|
200
|
-
|
|
201
|
-
// Deny as owner so the parked turn can finish before close().
|
|
202
|
-
await engine.resolveApproval(sessionId!, callId!, "deny", ownerAuth);
|
|
203
|
-
await engine.close();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it("asSessionOwner bridge lets a non-owner resolve Slack-owned approvals", async () => {
|
|
207
|
-
stateRoot = await mkdtemp(join(tmpdir(), "agent-serve-hitl-"));
|
|
208
|
-
const executeFlag = { called: false };
|
|
209
|
-
const project = makeHitlProject(executeFlag);
|
|
210
|
-
|
|
211
|
-
const runner: AgentRunner = {
|
|
212
|
-
async runTurn(request: RunnerTurnRequest) {
|
|
213
|
-
const tool = request.customTools.risky_action;
|
|
214
|
-
if (tool === undefined) {
|
|
215
|
-
throw new Error("risky_action not registered");
|
|
216
|
-
}
|
|
217
|
-
await tool.execute({ note: "bridge" }, { toolCallId: "call_bridge_1" });
|
|
218
|
-
return { status: "finished", result: "done" };
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const engine = new SessionEngine({
|
|
223
|
-
project,
|
|
224
|
-
stateRoot,
|
|
225
|
-
runner,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const slackAuth = {
|
|
229
|
-
authenticator: "slack",
|
|
230
|
-
principalId: "slack:T:C:123.456",
|
|
231
|
-
principalType: "user" as const,
|
|
232
|
-
attributes: {
|
|
233
|
-
channel_id: "C",
|
|
234
|
-
thread_ts: "123.456",
|
|
235
|
-
user_id: "U_OWNER",
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
const playgroundAuth = {
|
|
239
|
-
authenticator: "allow-all",
|
|
240
|
-
principalId: "anonymous",
|
|
241
|
-
principalType: "user" as const,
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const sessionPromise = engine.send("http", "please run risky action", {
|
|
245
|
-
auth: slackAuth,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
let callId: string | undefined;
|
|
249
|
-
let sessionId: string | undefined;
|
|
250
|
-
for (let i = 0; i < 100; i++) {
|
|
251
|
-
const sessions = await engine.sessions.list();
|
|
252
|
-
if (sessions.length > 0) {
|
|
253
|
-
sessionId = sessions[0]!.sessionId;
|
|
254
|
-
const pending = await engine.listPendingApprovals(sessionId);
|
|
255
|
-
if (pending.length > 0) {
|
|
256
|
-
callId = pending[0]!.callId;
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
expect(sessionId).toBeDefined();
|
|
264
|
-
expect(callId).toBeDefined();
|
|
265
|
-
|
|
266
|
-
await expect(
|
|
267
|
-
engine.resolveApproval(sessionId!, callId!, "approve", playgroundAuth)
|
|
268
|
-
).rejects.toThrow(SessionAuthError);
|
|
269
|
-
|
|
270
|
-
await engine.resolveApproval(
|
|
271
|
-
sessionId!,
|
|
272
|
-
callId!,
|
|
273
|
-
"approve",
|
|
274
|
-
playgroundAuth,
|
|
275
|
-
{ asSessionOwner: true }
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
const session = await sessionPromise;
|
|
279
|
-
await session.waitForCompletion();
|
|
280
|
-
|
|
281
|
-
expect(executeFlag.called).toBe(true);
|
|
282
|
-
|
|
283
|
-
const events = await engine.listEvents(session.id);
|
|
284
|
-
const resolved = events.find((e) => e.type === "action.approval_resolved");
|
|
285
|
-
expect(resolved?.type).toBe("action.approval_resolved");
|
|
286
|
-
if (resolved?.type === "action.approval_resolved") {
|
|
287
|
-
expect(resolved.data.by).toBe("anonymous");
|
|
288
|
-
expect(resolved.data.decision).toBe("approve");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
await engine.close();
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("parks when needsApproval predicate returns true (input-only)", async () => {
|
|
295
|
-
stateRoot = await mkdtemp(join(tmpdir(), "agent-serve-hitl-"));
|
|
296
|
-
const executeFlag = { called: false };
|
|
297
|
-
const definition = defineTool({
|
|
298
|
-
description: "Conditional risky tool",
|
|
299
|
-
needsApproval: (input: { urgent?: boolean }) => input.urgent === true,
|
|
300
|
-
async execute(_input: { urgent?: boolean }, _ctx: ToolContext) {
|
|
301
|
-
executeFlag.called = true;
|
|
302
|
-
return { ok: true };
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
const project: AgentProject = {
|
|
306
|
-
rootDir: "/tmp/hitl-fixture",
|
|
307
|
-
agentDir: "/tmp/hitl-fixture/agent",
|
|
308
|
-
name: "hitl-fixture",
|
|
309
|
-
agent: {
|
|
310
|
-
name: "hitl-fixture",
|
|
311
|
-
runtime: "local",
|
|
312
|
-
tools: [
|
|
313
|
-
{
|
|
314
|
-
name: "conditional_risky",
|
|
315
|
-
description: definition.description,
|
|
316
|
-
execution: "server",
|
|
317
|
-
needsApproval: true,
|
|
318
|
-
inputSchema: {
|
|
319
|
-
type: "object",
|
|
320
|
-
properties: { urgent: { type: "boolean" } },
|
|
321
|
-
additionalProperties: false,
|
|
322
|
-
},
|
|
323
|
-
definition,
|
|
324
|
-
},
|
|
325
|
-
],
|
|
326
|
-
skills: [],
|
|
327
|
-
connections: [],
|
|
328
|
-
subagents: [],
|
|
329
|
-
seedFiles: [],
|
|
330
|
-
},
|
|
331
|
-
httpChannel: httpChannel(),
|
|
332
|
-
channels: [],
|
|
333
|
-
schedules: [],
|
|
334
|
-
hooks: [],
|
|
335
|
-
abs: [],
|
|
336
|
-
diagnostics: [],
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
const runner: AgentRunner = {
|
|
340
|
-
async runTurn(request: RunnerTurnRequest) {
|
|
341
|
-
const tool = request.customTools.conditional_risky;
|
|
342
|
-
if (tool === undefined) {
|
|
343
|
-
throw new Error("conditional_risky not registered");
|
|
344
|
-
}
|
|
345
|
-
await tool.execute({ urgent: true }, { toolCallId: "call_pred_1" });
|
|
346
|
-
return { status: "finished", result: "done" };
|
|
347
|
-
},
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const engine = new SessionEngine({ project, stateRoot, runner });
|
|
351
|
-
const auth = {
|
|
352
|
-
authenticator: "local-dev",
|
|
353
|
-
principalId: "loopback",
|
|
354
|
-
principalType: "user" as const,
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
const sessionPromise = engine.send("http", "urgent action", { auth });
|
|
358
|
-
|
|
359
|
-
let callId: string | undefined;
|
|
360
|
-
let sessionId: string | undefined;
|
|
361
|
-
for (let i = 0; i < 100; i++) {
|
|
362
|
-
const sessions = await engine.sessions.list();
|
|
363
|
-
if (sessions.length > 0) {
|
|
364
|
-
sessionId = sessions[0]!.sessionId;
|
|
365
|
-
const pending = await engine.listPendingApprovals(sessionId);
|
|
366
|
-
if (pending.length > 0) {
|
|
367
|
-
callId = pending[0]!.callId;
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
expect(callId).toBe("call_pred_1");
|
|
375
|
-
expect(executeFlag.called).toBe(false);
|
|
376
|
-
|
|
377
|
-
await engine.resolveApproval(sessionId!, callId!, "approve", auth);
|
|
378
|
-
const session = await sessionPromise;
|
|
379
|
-
await session.waitForCompletion();
|
|
380
|
-
expect(executeFlag.called).toBe(true);
|
|
381
|
-
await engine.close();
|
|
382
|
-
});
|
|
383
|
-
});
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { defineArtifacts } from "../artifacts.js";
|
|
7
|
-
import { defineStorage, storageKeys } from "../storage.js";
|
|
8
|
-
import type { JsonValue } from "../types.js";
|
|
9
|
-
import { ArtifactsStore, artifactIdForKey } from "./artifacts-store.js";
|
|
10
|
-
import { StorageCoordinator } from "./storage-coordinator.js";
|
|
11
|
-
|
|
12
|
-
const AGENT = "artifacts-test";
|
|
13
|
-
|
|
14
|
-
const cleanups: Array<() => Promise<void>> = [];
|
|
15
|
-
|
|
16
|
-
afterEach(async () => {
|
|
17
|
-
while (cleanups.length > 0) {
|
|
18
|
-
await cleanups.pop()?.();
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
async function tempStateRoot(): Promise<string> {
|
|
23
|
-
const dir = await mkdtemp(join(tmpdir(), "agent-serve-artifacts-"));
|
|
24
|
-
cleanups.push(() => rm(dir, { recursive: true, force: true }));
|
|
25
|
-
return dir;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Distinct-millisecond updatedAt stamps for eviction/order assertions. */
|
|
29
|
-
function tick(): Promise<void> {
|
|
30
|
-
return new Promise((resolve) => setTimeout(resolve, 3));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe("ArtifactsStore", () => {
|
|
34
|
-
it("upserts by key: same key ⇒ same id, replaced row, preserved createdAt", async () => {
|
|
35
|
-
const store = new ArtifactsStore({
|
|
36
|
-
stateRoot: await tempStateRoot(),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const first = await store.tag({
|
|
40
|
-
kind: "report",
|
|
41
|
-
key: "weekly",
|
|
42
|
-
data: { rev: 1 },
|
|
43
|
-
});
|
|
44
|
-
await tick();
|
|
45
|
-
const second = await store.tag({
|
|
46
|
-
kind: "report",
|
|
47
|
-
key: "weekly",
|
|
48
|
-
data: { rev: 2 },
|
|
49
|
-
title: "Weekly report",
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(second.id).toBe(first.id);
|
|
53
|
-
expect(second.id).toBe(artifactIdForKey("weekly"));
|
|
54
|
-
expect(second.createdAt).toBe(first.createdAt);
|
|
55
|
-
expect(second.updatedAt > first.updatedAt).toBe(true);
|
|
56
|
-
|
|
57
|
-
const rows = await store.list();
|
|
58
|
-
expect(rows).toHaveLength(1);
|
|
59
|
-
expect(rows[0]?.data).toEqual({ rev: 2 });
|
|
60
|
-
expect(rows[0]?.title).toBe("Weekly report");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("gives keyless tags fresh random ids", async () => {
|
|
64
|
-
const store = new ArtifactsStore({
|
|
65
|
-
stateRoot: await tempStateRoot(),
|
|
66
|
-
});
|
|
67
|
-
const a = await store.tag({ data: 1 });
|
|
68
|
-
const b = await store.tag({ data: 2 });
|
|
69
|
-
expect(a.id).not.toBe(b.id);
|
|
70
|
-
expect(await store.list()).toHaveLength(2);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("validates data against the declared kind schema", async () => {
|
|
74
|
-
const store = new ArtifactsStore({
|
|
75
|
-
stateRoot: await tempStateRoot(),
|
|
76
|
-
config: defineArtifacts({
|
|
77
|
-
kinds: {
|
|
78
|
-
"reviewed-pr": {
|
|
79
|
-
description: "A reviewed PR.",
|
|
80
|
-
schema: z.object({ url: z.string() }),
|
|
81
|
-
},
|
|
82
|
-
note: { description: "Schemaless note." },
|
|
83
|
-
},
|
|
84
|
-
}),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const ok = await store.tag({
|
|
88
|
-
kind: "reviewed-pr",
|
|
89
|
-
data: { url: "https://github.com/org/repo/pull/1" },
|
|
90
|
-
});
|
|
91
|
-
expect(ok.kind).toBe("reviewed-pr");
|
|
92
|
-
|
|
93
|
-
await expect(
|
|
94
|
-
store.tag({ kind: "reviewed-pr", data: { nope: true } })
|
|
95
|
-
).rejects.toThrow(/does not match the "reviewed-pr" schema/);
|
|
96
|
-
|
|
97
|
-
// A kind without a schema accepts any payload.
|
|
98
|
-
await expect(
|
|
99
|
-
store.tag({ kind: "note", data: "free text" })
|
|
100
|
-
).resolves.toMatchObject({ kind: "note" });
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("persists the schema's parsed output (defaults applied)", async () => {
|
|
104
|
-
const store = new ArtifactsStore({
|
|
105
|
-
stateRoot: await tempStateRoot(),
|
|
106
|
-
config: defineArtifacts({
|
|
107
|
-
kinds: {
|
|
108
|
-
report: {
|
|
109
|
-
description: "Report.",
|
|
110
|
-
schema: z.object({
|
|
111
|
-
name: z.string(),
|
|
112
|
-
status: z.string().default("draft"),
|
|
113
|
-
}),
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
}),
|
|
117
|
-
});
|
|
118
|
-
const record = await store.tag({ kind: "report", data: { name: "q3" } });
|
|
119
|
-
expect(record.data).toEqual({ name: "q3", status: "draft" });
|
|
120
|
-
expect((await store.list())[0]?.data).toEqual({
|
|
121
|
-
name: "q3",
|
|
122
|
-
status: "draft",
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("rejects unknown kinds when kinds are declared", async () => {
|
|
127
|
-
const store = new ArtifactsStore({
|
|
128
|
-
stateRoot: await tempStateRoot(),
|
|
129
|
-
config: defineArtifacts({
|
|
130
|
-
kinds: { note: { description: "Note." } },
|
|
131
|
-
}),
|
|
132
|
-
});
|
|
133
|
-
await expect(store.tag({ kind: "mystery", data: 1 })).rejects.toThrow(
|
|
134
|
-
/unknown kind "mystery" \(declared: note\)/
|
|
135
|
-
);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('accepts freeform kinds (default "artifact") with zero config', async () => {
|
|
139
|
-
const store = new ArtifactsStore({
|
|
140
|
-
stateRoot: await tempStateRoot(),
|
|
141
|
-
});
|
|
142
|
-
const defaulted = await store.tag({ data: { anything: true } });
|
|
143
|
-
expect(defaulted.kind).toBe("artifact");
|
|
144
|
-
expect(defaulted.source).toBe("host");
|
|
145
|
-
const custom = await store.tag({ kind: "whatever", data: null });
|
|
146
|
-
expect(custom.kind).toBe("whatever");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("evicts the oldest-updated row past the max cap", async () => {
|
|
150
|
-
const store = new ArtifactsStore({
|
|
151
|
-
stateRoot: await tempStateRoot(),
|
|
152
|
-
config: defineArtifacts({ max: 2 }),
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
await store.tag({ key: "a", data: "a" });
|
|
156
|
-
await tick();
|
|
157
|
-
await store.tag({ key: "b", data: "b" });
|
|
158
|
-
await tick();
|
|
159
|
-
// Refreshing "a" makes "b" the oldest-updated row.
|
|
160
|
-
await store.tag({ key: "a", data: "a2" });
|
|
161
|
-
await tick();
|
|
162
|
-
await store.tag({ key: "c", data: "c" });
|
|
163
|
-
|
|
164
|
-
const keys = (await store.list()).map((row) => row.key);
|
|
165
|
-
expect(keys.sort()).toEqual(["a", "c"]);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("keeps exactly max rows under concurrent inserts", async () => {
|
|
169
|
-
const store = new ArtifactsStore({
|
|
170
|
-
stateRoot: await tempStateRoot(),
|
|
171
|
-
config: defineArtifacts({ max: 3 }),
|
|
172
|
-
});
|
|
173
|
-
await Promise.all(
|
|
174
|
-
Array.from({ length: 8 }, (_, i) => store.tag({ data: i }))
|
|
175
|
-
);
|
|
176
|
-
expect(await store.list()).toHaveLength(3);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("skips corrupt row files instead of failing list", async () => {
|
|
180
|
-
const stateRoot = await tempStateRoot();
|
|
181
|
-
const store = new ArtifactsStore({ stateRoot });
|
|
182
|
-
await store.tag({ key: "good", data: 1 });
|
|
183
|
-
await writeFile(
|
|
184
|
-
join(stateRoot, "artifacts", "corrupt.json"),
|
|
185
|
-
"{not json",
|
|
186
|
-
"utf8"
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const rows = await store.list();
|
|
190
|
-
expect(rows).toHaveLength(1);
|
|
191
|
-
expect(rows[0]?.key).toBe("good");
|
|
192
|
-
// Inserts (and their eviction scan) keep working too.
|
|
193
|
-
await expect(store.tag({ key: "more", data: 2 })).resolves.toBeDefined();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("filters list by kind and sessionId, newest-updated first", async () => {
|
|
197
|
-
const store = new ArtifactsStore({
|
|
198
|
-
stateRoot: await tempStateRoot(),
|
|
199
|
-
});
|
|
200
|
-
await store.tag({ kind: "report", data: 1, sessionId: "ses_1" });
|
|
201
|
-
await tick();
|
|
202
|
-
await store.tag({ kind: "report", data: 2, sessionId: "ses_2" });
|
|
203
|
-
await tick();
|
|
204
|
-
await store.tag({ kind: "note", data: 3, sessionId: "ses_1" });
|
|
205
|
-
|
|
206
|
-
expect(await store.list({ kind: "report" })).toHaveLength(2);
|
|
207
|
-
expect(await store.list({ sessionId: "ses_1" })).toHaveLength(2);
|
|
208
|
-
expect(
|
|
209
|
-
await store.list({ kind: "report", sessionId: "ses_1" })
|
|
210
|
-
).toHaveLength(1);
|
|
211
|
-
|
|
212
|
-
const all = await store.list();
|
|
213
|
-
expect(all.map((row) => row.data)).toEqual([3, 2, 1]);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("persists rows through defineStorage under storageKeys.artifact", async () => {
|
|
217
|
-
const backing = new Map<string, JsonValue>();
|
|
218
|
-
const coordinator = new StorageCoordinator({
|
|
219
|
-
definition: defineStorage({
|
|
220
|
-
put: (key, value) => {
|
|
221
|
-
backing.set(key, structuredClone(value));
|
|
222
|
-
},
|
|
223
|
-
get: (key) => backing.get(key),
|
|
224
|
-
delete: (key) => {
|
|
225
|
-
backing.delete(key);
|
|
226
|
-
},
|
|
227
|
-
list: (prefix) =>
|
|
228
|
-
[...backing.entries()]
|
|
229
|
-
.filter(([key]) => key.startsWith(prefix))
|
|
230
|
-
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
231
|
-
.map(([key, value]) => ({ key, value })),
|
|
232
|
-
}),
|
|
233
|
-
agentName: AGENT,
|
|
234
|
-
projectRoot: "/tmp",
|
|
235
|
-
logger: () => {},
|
|
236
|
-
});
|
|
237
|
-
const store = new ArtifactsStore({
|
|
238
|
-
stateRoot: await tempStateRoot(),
|
|
239
|
-
storage: coordinator,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const record = await store.tag({ key: "pr-1", data: { url: "u" } });
|
|
243
|
-
expect(backing.get(storageKeys.artifact(AGENT, record.id))).toMatchObject({
|
|
244
|
-
id: record.id,
|
|
245
|
-
key: "pr-1",
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
await tick();
|
|
249
|
-
await store.tag({ key: "pr-1", data: { url: "u2" } });
|
|
250
|
-
expect([...backing.keys()]).toHaveLength(1);
|
|
251
|
-
expect((await store.list())[0]?.data).toEqual({ url: "u2" });
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it("falls back to filesystem rows when the sink cannot delete", async () => {
|
|
255
|
-
const backing = new Map<string, JsonValue>();
|
|
256
|
-
// No `delete`: the sink could never evict past the cap, so artifact
|
|
257
|
-
// rows must not land there.
|
|
258
|
-
const coordinator = new StorageCoordinator({
|
|
259
|
-
definition: defineStorage({
|
|
260
|
-
put: (key, value) => {
|
|
261
|
-
backing.set(key, structuredClone(value));
|
|
262
|
-
},
|
|
263
|
-
get: (key) => backing.get(key),
|
|
264
|
-
list: (prefix) =>
|
|
265
|
-
[...backing.entries()]
|
|
266
|
-
.filter(([key]) => key.startsWith(prefix))
|
|
267
|
-
.map(([key, value]) => ({ key, value })),
|
|
268
|
-
}),
|
|
269
|
-
agentName: AGENT,
|
|
270
|
-
projectRoot: "/tmp",
|
|
271
|
-
logger: () => {},
|
|
272
|
-
});
|
|
273
|
-
expect(coordinator.supportsArtifacts).toBe(false);
|
|
274
|
-
|
|
275
|
-
const store = new ArtifactsStore({
|
|
276
|
-
stateRoot: await tempStateRoot(),
|
|
277
|
-
storage: coordinator,
|
|
278
|
-
});
|
|
279
|
-
await store.tag({ key: "local", data: 1 });
|
|
280
|
-
expect(backing.size).toBe(0);
|
|
281
|
-
expect(await store.list()).toHaveLength(1);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { AUTHORING_ENTRY_FILES } from "./distribution.js";
|
|
4
|
-
|
|
5
|
-
describe("AUTHORING_ENTRY_FILES", () => {
|
|
6
|
-
it("mirrors the package exports map (the runtime alias hooks derive their targets from it)", () => {
|
|
7
|
-
const pkg = JSON.parse(
|
|
8
|
-
readFileSync(new URL("../../package.json", import.meta.url), "utf8")
|
|
9
|
-
) as {
|
|
10
|
-
exports: Record<string, string | { import?: string }>;
|
|
11
|
-
};
|
|
12
|
-
const fromExports = Object.entries(pkg.exports)
|
|
13
|
-
.filter(
|
|
14
|
-
([exportPath, conditions]) =>
|
|
15
|
-
typeof conditions !== "string" && exportPath !== "./package.json"
|
|
16
|
-
)
|
|
17
|
-
.map(([exportPath, conditions]) => {
|
|
18
|
-
const subpath =
|
|
19
|
-
exportPath === "." ? "" : exportPath.replace(/^\.\//, "");
|
|
20
|
-
const importTarget = (conditions as { import?: string }).import;
|
|
21
|
-
expect(
|
|
22
|
-
importTarget,
|
|
23
|
-
`exports["${exportPath}"] must have an import condition`
|
|
24
|
-
).toBeDefined();
|
|
25
|
-
return [subpath, importTarget?.replace(/^\.\/dist\//, "")];
|
|
26
|
-
});
|
|
27
|
-
expect(AUTHORING_ENTRY_FILES.map((entry) => [...entry])).toEqual(
|
|
28
|
-
fromExports
|
|
29
|
-
);
|
|
30
|
-
});
|
|
31
|
-
});
|