@cursor/july 0.1.19 → 0.1.21
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/AGENTS.md +10 -0
- package/dist/bin/agent-serve.js +0 -0
- package/dist/channels/github/instrument.d.ts +20 -0
- package/dist/channels/github/instrument.d.ts.map +1 -0
- package/dist/channels/slack/api.d.ts +2 -0
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +3 -0
- package/dist/channels/slack/bot-mentions.d.ts +106 -0
- package/dist/channels/slack/bot-mentions.d.ts.map +1 -0
- package/dist/channels/slack/bot-mentions.js +243 -0
- package/dist/channels/slack/dispatch.d.ts +6 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +24 -3
- package/dist/channels/slack/inbound.d.ts +10 -1
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +12 -4
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +61 -22
- package/dist/channels/slack/types.d.ts +58 -0
- 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.CrsWMchO.js → app.jDxLzWv4.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DoJHJjqF.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.D1JqzSh8.js → VPLocalSearchBox.Y6bDR1-a.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DaBvZYwl.js → theme.CLazCWlJ.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/internal/json-dir-store.d.ts +32 -0
- package/dist/internal/json-dir-store.d.ts.map +1 -0
- package/dist/internal/json-dir-store.js +100 -0
- package/dist/internal/resolved-connections.d.ts +1 -1
- package/dist/internal/resolved-connections.d.ts.map +1 -1
- package/dist/internal/resolved-connections.js +10 -0
- package/dist/internal/session-engine.d.ts +4 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +13 -2
- package/dist/playground/assets/index-CjOQ4hN9.css +1 -0
- package/dist/playground/assets/{index-C2SU2xV5.js → index-dshZQJCp.js} +46 -46
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -0
- package/package.json +25 -25
- package/skills/create-agent/SKILL.md +36 -4
- package/skills/evals/SKILL.md +3 -0
- package/skills/framework-map/SKILL.md +14 -0
- package/skills/github/SKILL.md +6 -1
- package/skills/hillclimb/SKILL.md +28 -7
- package/src/bin/agent-serve.version.test.ts +62 -0
- package/src/channels/github/api.test.ts +64 -0
- package/src/channels/github/auth.test.ts +105 -0
- package/src/channels/github/cursor-account.test.ts +204 -0
- package/src/channels/github/forward.test.ts +457 -0
- package/src/channels/github/github.test.ts +937 -0
- package/src/channels/github/replay.test.ts +179 -0
- package/src/channels/slack/api.post-message.test.ts +148 -0
- package/src/channels/slack/api.ts +5 -0
- package/src/channels/slack/approvals.test.ts +328 -0
- package/src/channels/slack/block-actions.test.ts +452 -0
- package/src/channels/slack/bot-mentions.test.ts +217 -0
- package/src/channels/slack/bot-mentions.ts +315 -0
- package/src/channels/slack/channel-watch.test.ts +363 -0
- package/src/channels/slack/cursor-account.test.ts +253 -0
- package/src/channels/slack/defaults.final-post.test.ts +182 -0
- package/src/channels/slack/dispatch.test.ts +795 -0
- package/src/channels/slack/dispatch.ts +27 -1
- package/src/channels/slack/eval-directive.test.ts +273 -0
- package/src/channels/slack/inbound.ts +25 -4
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/message-body.test.ts +54 -0
- package/src/channels/slack/nudge-store.test.ts +143 -0
- package/src/channels/slack/slack-channel.ts +66 -8
- package/src/channels/slack/slack.test.ts +391 -0
- package/src/channels/slack/stop.test.ts +23 -0
- package/src/channels/slack/thread-context.test.ts +202 -0
- package/src/channels/slack/types.ts +60 -0
- package/src/evals/assertions.test.ts +580 -0
- package/src/evals/expect.test.ts +144 -0
- package/src/evals/judge.test.ts +181 -0
- package/src/evals/loaders.test.ts +132 -0
- package/src/evals/matchers.test.ts +95 -0
- package/src/evals/reporters.test.ts +303 -0
- package/src/evals/run-facts.test.ts +259 -0
- package/src/internal/ab-snapshot.test.ts +325 -0
- package/src/internal/approval-gate.test.ts +49 -0
- package/src/internal/approvals.integration.test.ts +383 -0
- package/src/internal/authored-loaders.test.ts +31 -0
- package/src/internal/builtin-tools/reminders.test.ts +201 -0
- package/src/internal/channel-route-schema.test.ts +294 -0
- package/src/internal/chat-attach.test.ts +262 -0
- package/src/internal/cli-deploy.test.ts +1991 -0
- package/src/internal/cli-mcp.test.ts +789 -0
- package/src/internal/cli-skills.test.ts +133 -0
- package/src/internal/cli-slack.test.ts +1647 -0
- package/src/internal/cloud-merge.test.ts +74 -0
- package/src/internal/cron.test.ts +22 -0
- package/src/internal/cursor/account-mcp.test.ts +807 -0
- package/src/internal/cursor/backend-client.test.ts +591 -0
- package/src/internal/cursor/credentials.test.ts +351 -0
- package/src/internal/cursor/github-credentials.test.ts +136 -0
- package/src/internal/cursor-account-mcp-auth.test.ts +310 -0
- package/src/internal/cursor-account.integration.test.ts +441 -0
- package/src/internal/cursor-event-relay.test.ts +746 -0
- package/src/internal/cursor-github-credentials.integration.test.ts +271 -0
- package/src/internal/cursor-slack-relay.test.ts +525 -0
- package/src/internal/deploy-source.test.ts +111 -0
- package/src/internal/discovery.builtin-tools.test.ts +94 -0
- package/src/internal/discovery.concurrency.test.ts +60 -0
- package/src/internal/discovery.cursor-account.test.ts +133 -0
- package/src/internal/discovery.cwd.test.ts +83 -0
- package/src/internal/discovery.hosting.test.ts +80 -0
- package/src/internal/discovery.identity.test.ts +44 -0
- package/src/internal/docs-site.test.ts +66 -0
- package/src/internal/duration.test.ts +29 -0
- package/src/internal/eval-judge-model.test.ts +187 -0
- package/src/internal/eval-run-store.cancel.test.ts +142 -0
- package/src/internal/eval-run-store.storage.test.ts +211 -0
- package/src/internal/eval-runner.http.test.ts +403 -0
- package/src/internal/eval-runner.run.test.ts +928 -0
- package/src/internal/evals-client.test.ts +307 -0
- package/src/internal/event-mapper.test.ts +243 -0
- package/src/internal/github-fanout.test.ts +213 -0
- package/src/internal/handleAgentServeTrigger.test.ts +179 -0
- package/src/internal/host-kv.test.ts +82 -0
- package/src/internal/host-platforms.test.ts +126 -0
- package/src/internal/http-channel.test.ts +402 -0
- package/src/internal/init-project.test.ts +269 -0
- package/src/internal/install-cursor-skills.test.ts +262 -0
- package/src/internal/local-env.test.ts +100 -0
- package/src/internal/log-ring.test.ts +31 -0
- package/src/internal/logs-client.test.ts +350 -0
- package/src/internal/mcp-endpoint.test.ts +436 -0
- package/src/internal/mcp-host.test.ts +298 -0
- package/src/internal/mcp-oauth.test.ts +148 -0
- package/src/internal/net.test.ts +17 -0
- package/src/internal/peer-connections.test.ts +128 -0
- package/src/internal/peer-mcp.integration.test.ts +289 -0
- package/src/internal/playground/toolchain.test.ts +53 -0
- package/src/internal/playground-cli.test.ts +187 -0
- package/src/internal/playground-proxy.test.ts +376 -0
- package/src/internal/prompt-context.integration.test.ts +232 -0
- package/src/internal/prompt-context.test.ts +127 -0
- package/src/internal/reminder-runner.test.ts +390 -0
- package/src/internal/reminder-store.test.ts +53 -0
- package/src/internal/request-headers.test.ts +27 -0
- package/src/internal/resolve-prod-target.test.ts +787 -0
- package/src/internal/resolved-connections.test.ts +295 -0
- package/src/internal/resolved-connections.ts +18 -1
- package/src/internal/router.test.ts +57 -0
- package/src/internal/sdk-runner.test.ts +290 -0
- package/src/internal/session-engine.coalesce.test.ts +169 -0
- package/src/internal/session-engine.concurrency.test.ts +250 -0
- package/src/internal/session-engine.host-oauth-mcp.test.ts +110 -0
- package/src/internal/session-engine.interrupt.test.ts +577 -0
- package/src/internal/session-engine.storage.test.ts +547 -0
- package/src/internal/session-engine.ts +15 -1
- package/src/internal/session-urls.test.ts +28 -0
- package/src/internal/sessions-client.test.ts +518 -0
- package/src/internal/storage-coordinator.test.ts +517 -0
- package/src/internal/tool-call.test.ts +458 -0
- package/src/internal/tool-result.test.ts +52 -0
- package/src/internal/trajectory.approvals.test.ts +83 -0
- package/src/internal/trajectory.subagents.test.ts +198 -0
- package/src/internal/turn-governor.test.ts +137 -0
- package/src/internal/update-check.test.ts +485 -0
- package/src/internal/workspace.test.ts +81 -0
- package/src/storage-backends/cursor-hosted.test.ts +121 -0
- package/src/types.ts +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BnHRjfoe.js +0 -1
- package/dist/playground/assets/index-CidizGZv.css +0 -1
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { httpChannel } from "../channels.js";
|
|
3
|
+
import { defineTool } from "../tools.js";
|
|
4
|
+
import type {
|
|
5
|
+
AgentProject,
|
|
6
|
+
AuthContext,
|
|
7
|
+
SessionRecord,
|
|
8
|
+
ToolCallOutcome,
|
|
9
|
+
TurnOutcome,
|
|
10
|
+
} from "../types.js";
|
|
11
|
+
import {
|
|
12
|
+
createAgentMcpEndpoint,
|
|
13
|
+
MCP_CHANNEL_ID,
|
|
14
|
+
mcpEndpointToolNames,
|
|
15
|
+
} from "./mcp-endpoint.js";
|
|
16
|
+
import type { SessionEngine } from "./session-engine.js";
|
|
17
|
+
import { UnknownToolError } from "./session-engine.js";
|
|
18
|
+
|
|
19
|
+
const LOCAL_AUTH: AuthContext = {
|
|
20
|
+
authenticator: "local-dev-strict",
|
|
21
|
+
principalId: "local",
|
|
22
|
+
principalType: "user",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function makeProject(
|
|
26
|
+
overrides: { serverTools?: boolean; description?: string } = {}
|
|
27
|
+
): AgentProject {
|
|
28
|
+
const tools =
|
|
29
|
+
overrides.serverTools === true
|
|
30
|
+
? [
|
|
31
|
+
{
|
|
32
|
+
name: "get_number",
|
|
33
|
+
description: "Return a number.",
|
|
34
|
+
execution: "server" as const,
|
|
35
|
+
needsApproval: false,
|
|
36
|
+
inputSchema: { type: "object" },
|
|
37
|
+
definition: defineTool({
|
|
38
|
+
description: "Return a number.",
|
|
39
|
+
async execute() {
|
|
40
|
+
return { value: 42 };
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
: [];
|
|
46
|
+
return {
|
|
47
|
+
rootDir: "/tmp/mcp-endpoint-fixture",
|
|
48
|
+
agentDir: "/tmp/mcp-endpoint-fixture/agent",
|
|
49
|
+
name: "fixture-agent",
|
|
50
|
+
agent: {
|
|
51
|
+
name: "fixture-agent",
|
|
52
|
+
...(overrides.description === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { description: overrides.description }),
|
|
55
|
+
runtime: "local",
|
|
56
|
+
tools,
|
|
57
|
+
skills: [],
|
|
58
|
+
connections: [],
|
|
59
|
+
subagents: [],
|
|
60
|
+
seedFiles: [],
|
|
61
|
+
},
|
|
62
|
+
httpChannel: httpChannel(),
|
|
63
|
+
channels: [],
|
|
64
|
+
schedules: [],
|
|
65
|
+
hooks: [],
|
|
66
|
+
abs: [],
|
|
67
|
+
diagnostics: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface EngineStub {
|
|
72
|
+
project: AgentProject;
|
|
73
|
+
send: ReturnType<typeof vi.fn>;
|
|
74
|
+
sessions: { get: ReturnType<typeof vi.fn> };
|
|
75
|
+
waitForCompletion: ReturnType<typeof vi.fn>;
|
|
76
|
+
listEvents: ReturnType<typeof vi.fn>;
|
|
77
|
+
callTool: ReturnType<typeof vi.fn>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function makeEngine(
|
|
81
|
+
overrides: Partial<{
|
|
82
|
+
project: AgentProject;
|
|
83
|
+
record: SessionRecord | undefined;
|
|
84
|
+
outcome: TurnOutcome | Promise<TurnOutcome>;
|
|
85
|
+
}> = {}
|
|
86
|
+
): EngineStub {
|
|
87
|
+
return {
|
|
88
|
+
project: overrides.project ?? makeProject(),
|
|
89
|
+
send: vi.fn(async () => ({
|
|
90
|
+
id: "ses_new",
|
|
91
|
+
continuationToken: "cont",
|
|
92
|
+
isNew: true,
|
|
93
|
+
})),
|
|
94
|
+
sessions: { get: vi.fn(async () => overrides.record) },
|
|
95
|
+
waitForCompletion: vi.fn(
|
|
96
|
+
() =>
|
|
97
|
+
overrides.outcome ??
|
|
98
|
+
Promise.resolve({ status: "finished", result: "the reply" })
|
|
99
|
+
),
|
|
100
|
+
listEvents: vi.fn(async () => []),
|
|
101
|
+
callTool: vi.fn(async (): Promise<ToolCallOutcome> => {
|
|
102
|
+
throw new Error("not stubbed");
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sessionRecord(overrides: Partial<SessionRecord> = {}): SessionRecord {
|
|
108
|
+
const now = new Date().toISOString();
|
|
109
|
+
return {
|
|
110
|
+
sessionId: "ses_existing",
|
|
111
|
+
channelId: MCP_CHANNEL_ID,
|
|
112
|
+
mode: "chat",
|
|
113
|
+
continuationKey: "cont_key",
|
|
114
|
+
auth: LOCAL_AUTH,
|
|
115
|
+
turnCount: 1,
|
|
116
|
+
createdAt: now,
|
|
117
|
+
updatedAt: now,
|
|
118
|
+
...overrides,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function rpcRequest(body: unknown): Request {
|
|
123
|
+
return new Request("http://127.0.0.1:3000/v1/mcp", {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"content-type": "application/json",
|
|
127
|
+
accept: "application/json, text/event-stream",
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify(body),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function toolCallRequest(name: string, args: unknown, id = 1): Request {
|
|
134
|
+
return rpcRequest({
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
id,
|
|
137
|
+
method: "tools/call",
|
|
138
|
+
params: { name, arguments: args },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function rpcResult(response: Response): Promise<{
|
|
143
|
+
content: Array<{ type: string; text: string }>;
|
|
144
|
+
isError?: boolean;
|
|
145
|
+
}> {
|
|
146
|
+
expect(response.status).toBe(200);
|
|
147
|
+
const body = (await response.json()) as {
|
|
148
|
+
result?: {
|
|
149
|
+
content: Array<{ type: string; text: string }>;
|
|
150
|
+
isError?: boolean;
|
|
151
|
+
};
|
|
152
|
+
error?: { message: string };
|
|
153
|
+
};
|
|
154
|
+
expect(body.error).toBeUndefined();
|
|
155
|
+
return body.result!;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function payloadOf(result: { content: Array<{ text: string }> }): {
|
|
159
|
+
status: string;
|
|
160
|
+
sessionId: string;
|
|
161
|
+
reply?: string;
|
|
162
|
+
errorMessage?: string;
|
|
163
|
+
} {
|
|
164
|
+
return JSON.parse(result.content[0]!.text) as ReturnType<typeof payloadOf>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
describe("createAgentMcpEndpoint", () => {
|
|
168
|
+
it("responds to initialize with the agent identity and instructions", async () => {
|
|
169
|
+
const engine = makeEngine({
|
|
170
|
+
project: makeProject({ description: "Answers weather questions." }),
|
|
171
|
+
});
|
|
172
|
+
const endpoint = createAgentMcpEndpoint({
|
|
173
|
+
engine: engine as unknown as SessionEngine,
|
|
174
|
+
});
|
|
175
|
+
const response = await endpoint.handle(
|
|
176
|
+
rpcRequest({
|
|
177
|
+
jsonrpc: "2.0",
|
|
178
|
+
id: 1,
|
|
179
|
+
method: "initialize",
|
|
180
|
+
params: {
|
|
181
|
+
protocolVersion: "2025-03-26",
|
|
182
|
+
capabilities: {},
|
|
183
|
+
clientInfo: { name: "test", version: "0.0.0" },
|
|
184
|
+
},
|
|
185
|
+
}),
|
|
186
|
+
LOCAL_AUTH
|
|
187
|
+
);
|
|
188
|
+
expect(response.status).toBe(200);
|
|
189
|
+
const body = (await response.json()) as {
|
|
190
|
+
result: { serverInfo: { name: string }; instructions?: string };
|
|
191
|
+
};
|
|
192
|
+
expect(body.result.serverInfo.name).toBe("fixture-agent");
|
|
193
|
+
expect(body.result.instructions).toContain("Answers weather questions.");
|
|
194
|
+
expect(body.result.instructions).toContain("ask");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("lists ask/check, and call_tool only when server tools exist", async () => {
|
|
198
|
+
const bare = makeEngine();
|
|
199
|
+
const withTools = makeEngine({
|
|
200
|
+
project: makeProject({ serverTools: true }),
|
|
201
|
+
});
|
|
202
|
+
for (const [engine, expectCallTool] of [
|
|
203
|
+
[bare, false],
|
|
204
|
+
[withTools, true],
|
|
205
|
+
] as const) {
|
|
206
|
+
const endpoint = createAgentMcpEndpoint({
|
|
207
|
+
engine: engine as unknown as SessionEngine,
|
|
208
|
+
});
|
|
209
|
+
const response = await endpoint.handle(
|
|
210
|
+
rpcRequest({ jsonrpc: "2.0", id: 2, method: "tools/list" }),
|
|
211
|
+
LOCAL_AUTH
|
|
212
|
+
);
|
|
213
|
+
expect(response.status).toBe(200);
|
|
214
|
+
const body = (await response.json()) as {
|
|
215
|
+
result: { tools: Array<{ name: string }> };
|
|
216
|
+
};
|
|
217
|
+
const names = body.result.tools.map((tool) => tool.name).sort();
|
|
218
|
+
expect(names).toEqual(
|
|
219
|
+
expectCallTool ? ["ask", "call_tool", "check"] : ["ask", "check"]
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
expect(mcpEndpointToolNames(bare.project)).toEqual(["ask", "check"]);
|
|
223
|
+
expect(mcpEndpointToolNames(withTools.project)).toEqual([
|
|
224
|
+
"ask",
|
|
225
|
+
"check",
|
|
226
|
+
"call_tool",
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("ask starts a session on the mcp channel bound to the caller and returns the reply", async () => {
|
|
231
|
+
const engine = makeEngine();
|
|
232
|
+
const endpoint = createAgentMcpEndpoint({
|
|
233
|
+
engine: engine as unknown as SessionEngine,
|
|
234
|
+
});
|
|
235
|
+
const response = await endpoint.handle(
|
|
236
|
+
toolCallRequest("ask", { message: "hello" }),
|
|
237
|
+
LOCAL_AUTH
|
|
238
|
+
);
|
|
239
|
+
const result = await rpcResult(response);
|
|
240
|
+
const payload = payloadOf(result);
|
|
241
|
+
expect(payload).toMatchObject({
|
|
242
|
+
status: "completed",
|
|
243
|
+
sessionId: "ses_new",
|
|
244
|
+
reply: "the reply",
|
|
245
|
+
});
|
|
246
|
+
expect(engine.send).toHaveBeenCalledWith(MCP_CHANNEL_ID, "hello", {
|
|
247
|
+
auth: LOCAL_AUTH,
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("ask returns status running when the turn outlives waitSeconds", async () => {
|
|
252
|
+
const engine = makeEngine({
|
|
253
|
+
outcome: new Promise<TurnOutcome>(() => {}),
|
|
254
|
+
});
|
|
255
|
+
const endpoint = createAgentMcpEndpoint({
|
|
256
|
+
engine: engine as unknown as SessionEngine,
|
|
257
|
+
});
|
|
258
|
+
const response = await endpoint.handle(
|
|
259
|
+
toolCallRequest("ask", { message: "slow", waitSeconds: 0 }),
|
|
260
|
+
LOCAL_AUTH
|
|
261
|
+
);
|
|
262
|
+
const payload = payloadOf(await rpcResult(response));
|
|
263
|
+
expect(payload.status).toBe("running");
|
|
264
|
+
expect(payload.sessionId).toBe("ses_new");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("ask follows up on an owned session without rotating its continuation", async () => {
|
|
268
|
+
const record = sessionRecord();
|
|
269
|
+
const engine = makeEngine({ record });
|
|
270
|
+
const endpoint = createAgentMcpEndpoint({
|
|
271
|
+
engine: engine as unknown as SessionEngine,
|
|
272
|
+
});
|
|
273
|
+
const response = await endpoint.handle(
|
|
274
|
+
toolCallRequest("ask", { message: "more", sessionId: "ses_existing" }),
|
|
275
|
+
LOCAL_AUTH
|
|
276
|
+
);
|
|
277
|
+
const payload = payloadOf(await rpcResult(response));
|
|
278
|
+
expect(payload).toMatchObject({
|
|
279
|
+
status: "completed",
|
|
280
|
+
sessionId: "ses_existing",
|
|
281
|
+
});
|
|
282
|
+
expect(engine.send).toHaveBeenCalledWith(MCP_CHANNEL_ID, "more", {
|
|
283
|
+
auth: record.auth,
|
|
284
|
+
continuationToken: "cont_key",
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("ask rejects follow-ups from a different principal", async () => {
|
|
289
|
+
const engine = makeEngine({
|
|
290
|
+
record: sessionRecord({
|
|
291
|
+
auth: {
|
|
292
|
+
authenticator: "bearer",
|
|
293
|
+
principalId: "bearer",
|
|
294
|
+
principalType: "service",
|
|
295
|
+
},
|
|
296
|
+
}),
|
|
297
|
+
});
|
|
298
|
+
const endpoint = createAgentMcpEndpoint({
|
|
299
|
+
engine: engine as unknown as SessionEngine,
|
|
300
|
+
});
|
|
301
|
+
const response = await endpoint.handle(
|
|
302
|
+
toolCallRequest("ask", { message: "hi", sessionId: "ses_existing" }),
|
|
303
|
+
LOCAL_AUTH
|
|
304
|
+
);
|
|
305
|
+
const result = await rpcResult(response);
|
|
306
|
+
expect(result.isError).toBe(true);
|
|
307
|
+
expect(result.content[0]!.text).toContain("different principal");
|
|
308
|
+
expect(engine.send).not.toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("ask rejects follow-ups on task sessions", async () => {
|
|
312
|
+
const engine = makeEngine({
|
|
313
|
+
record: sessionRecord({ mode: "task" }),
|
|
314
|
+
});
|
|
315
|
+
const endpoint = createAgentMcpEndpoint({
|
|
316
|
+
engine: engine as unknown as SessionEngine,
|
|
317
|
+
});
|
|
318
|
+
const response = await endpoint.handle(
|
|
319
|
+
toolCallRequest("ask", { message: "hi", sessionId: "ses_existing" }),
|
|
320
|
+
LOCAL_AUTH
|
|
321
|
+
);
|
|
322
|
+
const result = await rpcResult(response);
|
|
323
|
+
expect(result.isError).toBe(true);
|
|
324
|
+
expect(result.content[0]!.text).toContain("task session");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("check waits for an owned session and reports errors as isError", async () => {
|
|
328
|
+
const engine = makeEngine({
|
|
329
|
+
record: sessionRecord(),
|
|
330
|
+
outcome: Promise.resolve({
|
|
331
|
+
status: "error",
|
|
332
|
+
errorMessage: "boom",
|
|
333
|
+
} satisfies TurnOutcome),
|
|
334
|
+
});
|
|
335
|
+
const endpoint = createAgentMcpEndpoint({
|
|
336
|
+
engine: engine as unknown as SessionEngine,
|
|
337
|
+
});
|
|
338
|
+
const response = await endpoint.handle(
|
|
339
|
+
toolCallRequest("check", { sessionId: "ses_existing" }),
|
|
340
|
+
LOCAL_AUTH
|
|
341
|
+
);
|
|
342
|
+
const result = await rpcResult(response);
|
|
343
|
+
expect(result.isError).toBe(true);
|
|
344
|
+
expect(payloadOf(result)).toMatchObject({
|
|
345
|
+
status: "error",
|
|
346
|
+
errorMessage: "boom",
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("check recovers the reply from the event log after a restart", async () => {
|
|
351
|
+
const engine = makeEngine({
|
|
352
|
+
record: sessionRecord(),
|
|
353
|
+
outcome: Promise.resolve({ status: "finished" } satisfies TurnOutcome),
|
|
354
|
+
});
|
|
355
|
+
engine.listEvents.mockResolvedValue([
|
|
356
|
+
{
|
|
357
|
+
type: "turn.completed",
|
|
358
|
+
index: 3,
|
|
359
|
+
sessionId: "ses_existing",
|
|
360
|
+
at: new Date().toISOString(),
|
|
361
|
+
data: { result: "recovered reply" },
|
|
362
|
+
},
|
|
363
|
+
]);
|
|
364
|
+
const endpoint = createAgentMcpEndpoint({
|
|
365
|
+
engine: engine as unknown as SessionEngine,
|
|
366
|
+
});
|
|
367
|
+
const response = await endpoint.handle(
|
|
368
|
+
toolCallRequest("check", { sessionId: "ses_existing" }),
|
|
369
|
+
LOCAL_AUTH
|
|
370
|
+
);
|
|
371
|
+
const payload = payloadOf(await rpcResult(response));
|
|
372
|
+
expect(payload).toMatchObject({
|
|
373
|
+
status: "completed",
|
|
374
|
+
reply: "recovered reply",
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("call_tool passes through to engine.callTool and surfaces unknown tools", async () => {
|
|
379
|
+
const engine = makeEngine({ project: makeProject({ serverTools: true }) });
|
|
380
|
+
engine.callTool.mockResolvedValue({
|
|
381
|
+
toolName: "get_number",
|
|
382
|
+
callId: "tool_get_number_1",
|
|
383
|
+
isError: false,
|
|
384
|
+
result: { value: 42 },
|
|
385
|
+
durationMs: 3,
|
|
386
|
+
} satisfies ToolCallOutcome);
|
|
387
|
+
const endpoint = createAgentMcpEndpoint({
|
|
388
|
+
engine: engine as unknown as SessionEngine,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const okResponse = await endpoint.handle(
|
|
392
|
+
toolCallRequest("call_tool", {
|
|
393
|
+
toolName: "get_number",
|
|
394
|
+
input: { seed: 1 },
|
|
395
|
+
}),
|
|
396
|
+
LOCAL_AUTH
|
|
397
|
+
);
|
|
398
|
+
const okResult = await rpcResult(okResponse);
|
|
399
|
+
expect(okResult.isError).toBeUndefined();
|
|
400
|
+
expect(JSON.parse(okResult.content[0]!.text)).toMatchObject({
|
|
401
|
+
toolName: "get_number",
|
|
402
|
+
result: { value: 42 },
|
|
403
|
+
});
|
|
404
|
+
expect(engine.callTool).toHaveBeenCalledWith(
|
|
405
|
+
"get_number",
|
|
406
|
+
{ seed: 1 },
|
|
407
|
+
{ auth: LOCAL_AUTH }
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
engine.callTool.mockRejectedValue(
|
|
411
|
+
new UnknownToolError("nope", ["get_number"])
|
|
412
|
+
);
|
|
413
|
+
const errResponse = await endpoint.handle(
|
|
414
|
+
toolCallRequest("call_tool", { toolName: "nope" }, 2),
|
|
415
|
+
LOCAL_AUTH
|
|
416
|
+
);
|
|
417
|
+
const errResult = await rpcResult(errResponse);
|
|
418
|
+
expect(errResult.isError).toBe(true);
|
|
419
|
+
expect(errResult.content[0]!.text).toContain("get_number");
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("rejects non-POST methods with 405", async () => {
|
|
423
|
+
const engine = makeEngine();
|
|
424
|
+
const endpoint = createAgentMcpEndpoint({
|
|
425
|
+
engine: engine as unknown as SessionEngine,
|
|
426
|
+
});
|
|
427
|
+
for (const method of ["GET", "DELETE"]) {
|
|
428
|
+
const response = await endpoint.handle(
|
|
429
|
+
new Request("http://127.0.0.1:3000/v1/mcp", { method }),
|
|
430
|
+
LOCAL_AUTH
|
|
431
|
+
);
|
|
432
|
+
expect(response.status).toBe(405);
|
|
433
|
+
expect(response.headers.get("allow")).toBe("POST");
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
});
|