@cursor/july 0.1.25 → 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.
Files changed (288) hide show
  1. package/dist/ab.d.ts +6 -0
  2. package/dist/ab.d.ts.map +1 -1
  3. package/dist/ab.js +1 -0
  4. package/dist/artifacts.d.ts +34 -0
  5. package/dist/artifacts.d.ts.map +1 -0
  6. package/dist/artifacts.js +57 -0
  7. package/dist/bin/agent-serve.js +31 -9
  8. package/dist/channels/github/github-channel.d.ts +1 -1
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +14 -1
  11. package/dist/channels/github/types.d.ts +6 -1
  12. package/dist/channels/github/types.d.ts.map +1 -1
  13. package/dist/channels/slack/api.d.ts +2 -1
  14. package/dist/channels/slack/api.d.ts.map +1 -1
  15. package/dist/channels/slack/api.js +1 -0
  16. package/dist/channels/slack/cursor-account.d.ts +2 -1
  17. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  18. package/dist/channels/slack/cursor-account.js +1 -1
  19. package/dist/channels/slack/dispatch.d.ts +4 -1
  20. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  21. package/dist/channels/slack/dispatch.js +3 -3
  22. package/dist/channels/slack/interactive.d.ts +2 -1
  23. package/dist/channels/slack/interactive.d.ts.map +1 -1
  24. package/dist/channels/slack/interactive.js +1 -0
  25. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  26. package/dist/channels/slack/slack-channel.js +4 -3
  27. package/dist/channels/slack/types.d.ts +6 -1
  28. package/dist/channels/slack/types.d.ts.map +1 -1
  29. package/dist/docs/404.html +1 -1
  30. package/dist/docs/ab.html +2 -2
  31. package/dist/docs/assets/{app.DYcC9FY-.js → app.BICLoJYF.js} +1 -1
  32. package/dist/docs/assets/chunks/@localSearchIndexroot.CQJytDwU.js +1 -0
  33. package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.wN4Ko9dT.js} +1 -1
  34. package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.dnnTE-Co.js} +2 -2
  35. package/dist/docs/building-with-agents.html +2 -2
  36. package/dist/docs/concepts.html +2 -2
  37. package/dist/docs/deployment.html +2 -2
  38. package/dist/docs/evals.html +2 -2
  39. package/dist/docs/example-agents/approval-buddy.html +2 -2
  40. package/dist/docs/example-agents/benny.html +2 -2
  41. package/dist/docs/example-agents/bugbot.html +2 -2
  42. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  43. package/dist/docs/example-agents/codeowners-review.html +2 -2
  44. package/dist/docs/example-agents/concierge.html +2 -2
  45. package/dist/docs/example-agents/fsd.html +2 -2
  46. package/dist/docs/example-agents/index.html +2 -2
  47. package/dist/docs/example-agents/knowledge-base.html +2 -2
  48. package/dist/docs/example-agents/oncall.html +2 -2
  49. package/dist/docs/example-agents/security-reviewer.html +2 -2
  50. package/dist/docs/example-agents/slack-agent.html +2 -2
  51. package/dist/docs/example-agents/weather-agent.html +2 -2
  52. package/dist/docs/guides/agent-to-agent.html +2 -2
  53. package/dist/docs/guides/cloud-runtime.html +2 -2
  54. package/dist/docs/guides/github.html +2 -2
  55. package/dist/docs/guides/human-in-the-loop.html +2 -2
  56. package/dist/docs/guides/mcp-oauth.html +2 -2
  57. package/dist/docs/guides/slack.html +2 -2
  58. package/dist/docs/guides/webhooks.html +2 -2
  59. package/dist/docs/hillclimbing.html +2 -2
  60. package/dist/docs/index.html +2 -2
  61. package/dist/docs/quickstart.html +2 -2
  62. package/dist/docs/reference/agent-config.html +2 -2
  63. package/dist/docs/reference/channels.html +2 -2
  64. package/dist/docs/reference/cli.html +2 -2
  65. package/dist/docs/reference/connections.html +2 -2
  66. package/dist/docs/reference/hooks.html +2 -2
  67. package/dist/docs/reference/http-api.html +2 -2
  68. package/dist/docs/reference/instructions.html +2 -2
  69. package/dist/docs/reference/playground.html +2 -2
  70. package/dist/docs/reference/project-layout.html +2 -2
  71. package/dist/docs/reference/prompt.html +2 -2
  72. package/dist/docs/reference/schedules.html +2 -2
  73. package/dist/docs/reference/sessions.html +2 -2
  74. package/dist/docs/reference/skills.html +2 -2
  75. package/dist/docs/reference/subagents.html +2 -2
  76. package/dist/docs/reference/tools.html +2 -2
  77. package/dist/docs/scaffolding-agents.html +2 -2
  78. package/dist/docs/storage.html +2 -2
  79. package/dist/docs/troubleshooting.html +2 -2
  80. package/dist/evals/assertions.d.ts +10 -1
  81. package/dist/evals/assertions.d.ts.map +1 -1
  82. package/dist/evals/assertions.js +12 -0
  83. package/dist/evals.d.ts +1 -1
  84. package/dist/evals.d.ts.map +1 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +1 -0
  88. package/dist/internal/ab-fold.d.ts.map +1 -1
  89. package/dist/internal/ab-fold.js +4 -0
  90. package/dist/internal/ab-snapshot.d.ts.map +1 -1
  91. package/dist/internal/ab-snapshot.js +1 -0
  92. package/dist/internal/artifacts-store.d.ts +45 -0
  93. package/dist/internal/artifacts-store.d.ts.map +1 -0
  94. package/dist/internal/artifacts-store.js +250 -0
  95. package/dist/internal/builtin-tools/artifacts.d.ts +10 -0
  96. package/dist/internal/builtin-tools/artifacts.d.ts.map +1 -0
  97. package/dist/internal/builtin-tools/artifacts.js +73 -0
  98. package/dist/internal/builtin-tools/index.d.ts +8 -1
  99. package/dist/internal/builtin-tools/index.d.ts.map +1 -1
  100. package/dist/internal/builtin-tools/index.js +15 -0
  101. package/dist/internal/cli-ax.d.ts +11 -0
  102. package/dist/internal/cli-ax.d.ts.map +1 -1
  103. package/dist/internal/cli-ax.js +80 -0
  104. package/dist/internal/cli-slack.d.ts.map +1 -1
  105. package/dist/internal/cli-slack.js +14 -6
  106. package/dist/internal/discovery.d.ts.map +1 -1
  107. package/dist/internal/discovery.js +83 -2
  108. package/dist/internal/distribution.d.ts.map +1 -1
  109. package/dist/internal/distribution.js +1 -0
  110. package/dist/internal/model-pricing.d.ts +49 -0
  111. package/dist/internal/model-pricing.d.ts.map +1 -0
  112. package/dist/internal/model-pricing.js +377 -0
  113. package/dist/internal/reminder-runner.d.ts.map +1 -1
  114. package/dist/internal/reminder-runner.js +1 -0
  115. package/dist/internal/schedule-runner.d.ts.map +1 -1
  116. package/dist/internal/schedule-runner.js +1 -0
  117. package/dist/internal/sdk-runner.d.ts.map +1 -1
  118. package/dist/internal/sdk-runner.js +9 -1
  119. package/dist/internal/server.d.ts.map +1 -1
  120. package/dist/internal/server.js +39 -0
  121. package/dist/internal/session-cost.d.ts +49 -0
  122. package/dist/internal/session-cost.d.ts.map +1 -0
  123. package/dist/internal/session-cost.js +175 -0
  124. package/dist/internal/session-engine.d.ts +35 -1
  125. package/dist/internal/session-engine.d.ts.map +1 -1
  126. package/dist/internal/session-engine.js +128 -7
  127. package/dist/internal/storage-coordinator.d.ts +15 -0
  128. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  129. package/dist/internal/storage-coordinator.js +52 -0
  130. package/dist/internal/trajectory.d.ts +9 -1
  131. package/dist/internal/trajectory.d.ts.map +1 -1
  132. package/dist/internal/trajectory.js +32 -5
  133. package/dist/playground/assets/index-BDCeW4uc.js +86 -0
  134. package/dist/playground/assets/index-Bul1WfrB.css +1 -0
  135. package/dist/playground/index.html +2 -2
  136. package/dist/storage.d.ts +4 -0
  137. package/dist/storage.d.ts.map +1 -1
  138. package/dist/storage.js +4 -0
  139. package/dist/types.d.ts +164 -1
  140. package/dist/types.d.ts.map +1 -1
  141. package/package.json +26 -26
  142. package/src/ab.ts +7 -0
  143. package/src/bin/agent-serve.ts +30 -2
  144. package/src/channels/github/github-channel.ts +19 -2
  145. package/src/channels/github/types.ts +6 -0
  146. package/src/channels/slack/api.ts +3 -1
  147. package/src/channels/slack/cursor-account.ts +3 -1
  148. package/src/channels/slack/dispatch.ts +12 -1
  149. package/src/channels/slack/interactive.ts +3 -0
  150. package/src/channels/slack/slack-channel.ts +6 -0
  151. package/src/channels/slack/types.ts +6 -0
  152. package/src/internal/ab-fold.ts +4 -0
  153. package/src/internal/ab-snapshot.ts +1 -0
  154. package/src/internal/cli-ax.ts +95 -0
  155. package/src/internal/model-pricing.ts +426 -0
  156. package/src/internal/reminder-runner.ts +1 -0
  157. package/src/internal/schedule-runner.ts +1 -0
  158. package/src/internal/sdk-runner.ts +10 -2
  159. package/src/internal/server.ts +2 -0
  160. package/src/internal/session-cost.ts +231 -0
  161. package/src/internal/session-engine.ts +32 -1
  162. package/src/internal/trajectory.ts +45 -3
  163. package/src/types.ts +42 -2
  164. package/dist/channels/github/instrument.d.ts +0 -20
  165. package/dist/channels/github/instrument.d.ts.map +0 -1
  166. package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
  167. package/dist/internal/json-dir-store.d.ts +0 -32
  168. package/dist/internal/json-dir-store.d.ts.map +0 -1
  169. package/dist/internal/json-dir-store.js +0 -100
  170. package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
  171. package/dist/playground/assets/index-DqXdAFGa.js +0 -85
  172. package/src/bin/agent-serve.version.test.ts +0 -64
  173. package/src/channels/github/api.test.ts +0 -64
  174. package/src/channels/github/auth.test.ts +0 -105
  175. package/src/channels/github/cursor-account.test.ts +0 -204
  176. package/src/channels/github/forward.test.ts +0 -457
  177. package/src/channels/github/github.test.ts +0 -943
  178. package/src/channels/github/replay.test.ts +0 -179
  179. package/src/channels/slack/api.post-message.test.ts +0 -148
  180. package/src/channels/slack/approvals.test.ts +0 -328
  181. package/src/channels/slack/block-actions.test.ts +0 -452
  182. package/src/channels/slack/bot-mentions.test.ts +0 -267
  183. package/src/channels/slack/channel-watch.test.ts +0 -363
  184. package/src/channels/slack/cursor-account.test.ts +0 -253
  185. package/src/channels/slack/defaults.final-post.test.ts +0 -182
  186. package/src/channels/slack/dispatch.test.ts +0 -795
  187. package/src/channels/slack/eval-directive.test.ts +0 -273
  188. package/src/channels/slack/message-body.test.ts +0 -54
  189. package/src/channels/slack/nudge-store.test.ts +0 -143
  190. package/src/channels/slack/slack.test.ts +0 -391
  191. package/src/channels/slack/stop.test.ts +0 -23
  192. package/src/channels/slack/thread-context.test.ts +0 -202
  193. package/src/evals/assertions.test.ts +0 -617
  194. package/src/evals/expect.test.ts +0 -144
  195. package/src/evals/judge.test.ts +0 -181
  196. package/src/evals/loaders.test.ts +0 -132
  197. package/src/evals/matchers.test.ts +0 -95
  198. package/src/evals/reporters.test.ts +0 -303
  199. package/src/evals/run-facts.test.ts +0 -259
  200. package/src/internal/ab-snapshot.test.ts +0 -325
  201. package/src/internal/approval-gate.test.ts +0 -49
  202. package/src/internal/approvals.integration.test.ts +0 -383
  203. package/src/internal/artifacts-store.test.ts +0 -283
  204. package/src/internal/authored-loaders.test.ts +0 -31
  205. package/src/internal/builtin-tools/artifacts.test.ts +0 -247
  206. package/src/internal/builtin-tools/reminders.test.ts +0 -201
  207. package/src/internal/channel-route-schema.test.ts +0 -294
  208. package/src/internal/chat-attach.test.ts +0 -262
  209. package/src/internal/cli-deploy.test.ts +0 -1991
  210. package/src/internal/cli-docs.test.ts +0 -161
  211. package/src/internal/cli-mcp.test.ts +0 -789
  212. package/src/internal/cli-skills.test.ts +0 -133
  213. package/src/internal/cli-slack.test.ts +0 -1654
  214. package/src/internal/cloud-merge.test.ts +0 -74
  215. package/src/internal/cron.test.ts +0 -22
  216. package/src/internal/cursor/account-mcp.test.ts +0 -807
  217. package/src/internal/cursor/backend-client.test.ts +0 -591
  218. package/src/internal/cursor/credentials.test.ts +0 -351
  219. package/src/internal/cursor/github-credentials.test.ts +0 -136
  220. package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
  221. package/src/internal/cursor-account.integration.test.ts +0 -441
  222. package/src/internal/cursor-event-relay.test.ts +0 -746
  223. package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
  224. package/src/internal/cursor-slack-relay.test.ts +0 -525
  225. package/src/internal/deploy-source.test.ts +0 -111
  226. package/src/internal/discovery.artifact-tool.test.ts +0 -136
  227. package/src/internal/discovery.artifacts.test.ts +0 -119
  228. package/src/internal/discovery.builtin-tools.test.ts +0 -94
  229. package/src/internal/discovery.concurrency.test.ts +0 -60
  230. package/src/internal/discovery.cursor-account.test.ts +0 -133
  231. package/src/internal/discovery.cwd.test.ts +0 -83
  232. package/src/internal/discovery.hosting.test.ts +0 -80
  233. package/src/internal/discovery.identity.test.ts +0 -44
  234. package/src/internal/docs-site.test.ts +0 -66
  235. package/src/internal/duration.test.ts +0 -29
  236. package/src/internal/eval-judge-model.test.ts +0 -187
  237. package/src/internal/eval-run-store.cancel.test.ts +0 -142
  238. package/src/internal/eval-run-store.storage.test.ts +0 -211
  239. package/src/internal/eval-runner.http.test.ts +0 -403
  240. package/src/internal/eval-runner.run.test.ts +0 -928
  241. package/src/internal/evals-client.test.ts +0 -307
  242. package/src/internal/event-mapper.test.ts +0 -243
  243. package/src/internal/github-fanout.test.ts +0 -213
  244. package/src/internal/handleAgentServeTrigger.test.ts +0 -185
  245. package/src/internal/host-kv.test.ts +0 -82
  246. package/src/internal/host-platforms.test.ts +0 -126
  247. package/src/internal/http-channel.test.ts +0 -402
  248. package/src/internal/init-project.test.ts +0 -270
  249. package/src/internal/install-cursor-skills.test.ts +0 -262
  250. package/src/internal/local-env.test.ts +0 -120
  251. package/src/internal/log-ring.test.ts +0 -31
  252. package/src/internal/logs-client.test.ts +0 -350
  253. package/src/internal/mcp-endpoint.test.ts +0 -436
  254. package/src/internal/mcp-host.test.ts +0 -298
  255. package/src/internal/mcp-oauth.test.ts +0 -148
  256. package/src/internal/net.test.ts +0 -17
  257. package/src/internal/peer-connections.test.ts +0 -128
  258. package/src/internal/peer-mcp.integration.test.ts +0 -289
  259. package/src/internal/playground/toolchain.test.ts +0 -53
  260. package/src/internal/playground-cli.test.ts +0 -187
  261. package/src/internal/playground-proxy.test.ts +0 -376
  262. package/src/internal/prompt-context.integration.test.ts +0 -232
  263. package/src/internal/prompt-context.test.ts +0 -127
  264. package/src/internal/reminder-runner.test.ts +0 -390
  265. package/src/internal/reminder-store.test.ts +0 -53
  266. package/src/internal/request-headers.test.ts +0 -27
  267. package/src/internal/resolve-prod-target.test.ts +0 -787
  268. package/src/internal/resolved-connections.test.ts +0 -295
  269. package/src/internal/router.test.ts +0 -57
  270. package/src/internal/sdk-runner.test.ts +0 -290
  271. package/src/internal/server.artifacts.test.ts +0 -303
  272. package/src/internal/session-engine.artifacts.test.ts +0 -243
  273. package/src/internal/session-engine.coalesce.test.ts +0 -169
  274. package/src/internal/session-engine.concurrency.test.ts +0 -250
  275. package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
  276. package/src/internal/session-engine.interrupt.test.ts +0 -577
  277. package/src/internal/session-engine.storage.test.ts +0 -547
  278. package/src/internal/session-urls.test.ts +0 -28
  279. package/src/internal/sessions-client.test.ts +0 -518
  280. package/src/internal/storage-coordinator.test.ts +0 -517
  281. package/src/internal/tool-call.test.ts +0 -458
  282. package/src/internal/tool-result.test.ts +0 -52
  283. package/src/internal/trajectory.approvals.test.ts +0 -83
  284. package/src/internal/trajectory.subagents.test.ts +0 -198
  285. package/src/internal/turn-governor.test.ts +0 -137
  286. package/src/internal/update-check.test.ts +0 -485
  287. package/src/internal/workspace.test.ts +0 -207
  288. 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
- });