@cursor/july 0.1.3 → 0.1.5
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/bin/agent-serve.d.ts +1 -1
- package/dist/bin/agent-serve.js +40 -16
- package/dist/channels/slack/log.d.ts +1 -1
- package/dist/channels/slack/log.d.ts.map +1 -1
- package/dist/channels/slack/log.js +4 -5
- package/dist/channels/slack/setup.js +1 -1
- package/dist/channels/slack/socket-mode.d.ts.map +1 -1
- package/dist/channels/slack/socket-mode.js +10 -9
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.BR0RbIdx.js → app.DabPG-io.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.QwK5BtEH.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.qjsVTneI.js → VPLocalSearchBox.jmyr0bU0.js} +1 -1
- package/dist/docs/assets/chunks/{theme.2Kg8jIp_.js → theme.DysN9-VN.js} +2 -2
- package/dist/docs/assets/{index.md.m81y7TY7.js → index.md.Cylk70gg.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +5 -5
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/internal/ab-collector.js +3 -3
- package/dist/internal/ab-fold.js +1 -1
- package/dist/internal/cli-ax.d.ts +13 -5
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +41 -15
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +5 -2
- package/dist/internal/cli-github.js +13 -13
- package/dist/internal/cursor/account-mcp.js +3 -3
- package/dist/internal/cursor/backend-client.js +1 -1
- package/dist/internal/cursor/credentials.d.ts +5 -3
- package/dist/internal/cursor/credentials.d.ts.map +1 -1
- package/dist/internal/cursor/credentials.js +9 -6
- package/dist/internal/cursor/github-credentials.js +3 -3
- package/dist/internal/cursor-event-relay.d.ts +4 -1
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +7 -1
- package/dist/internal/cursor-relay-core.d.ts +37 -2
- package/dist/internal/cursor-relay-core.d.ts.map +1 -1
- package/dist/internal/cursor-relay-core.js +91 -9
- package/dist/internal/cursor-slack-relay.d.ts +2 -2
- package/dist/internal/cursor-slack-relay.d.ts.map +1 -1
- package/dist/internal/cursor-slack-relay.js +5 -1
- package/dist/internal/distribution.d.ts +7 -0
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +7 -0
- package/dist/internal/docs-site.js +3 -3
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +8 -8
- package/dist/internal/github-fanout.js +2 -2
- package/dist/internal/host-platforms.js +5 -5
- package/dist/internal/mcp-endpoint.d.ts.map +1 -1
- package/dist/internal/mcp-endpoint.js +1 -1
- package/dist/internal/playground/toolchain.js +6 -6
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +11 -11
- package/dist/internal/resolve-prod-target.d.ts +2 -0
- package/dist/internal/resolve-prod-target.d.ts.map +1 -1
- package/dist/internal/resolve-prod-target.js +44 -21
- package/dist/internal/resolved-connections.js +5 -5
- package/dist/internal/schedule-runner.js +1 -1
- package/dist/internal/server.js +14 -14
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +13 -13
- package/dist/internal/sessions-client.d.ts +13 -0
- package/dist/internal/sessions-client.d.ts.map +1 -1
- package/dist/internal/sessions-client.js +25 -0
- package/dist/internal/storage-coordinator.js +6 -6
- package/dist/internal/update-check.d.ts +93 -0
- package/dist/internal/update-check.d.ts.map +1 -0
- package/dist/internal/update-check.js +431 -0
- package/dist/playground/assets/{index-D-vo2lV_.css → index-BEauYlII.css} +1 -1
- package/dist/playground/assets/index-BtM0wEGg.js +319 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +0 -1
- package/package.json +2 -1
- package/skills/setup-slack/SKILL.md +1 -1
- package/src/bin/agent-serve.ts +42 -16
- package/src/channels/slack/log.ts +5 -5
- package/src/channels/slack/setup.ts +1 -1
- package/src/channels/slack/socket-mode.ts +11 -9
- package/src/internal/ab-collector.ts +3 -3
- package/src/internal/ab-fold.ts +1 -1
- package/src/internal/cli-ax.ts +55 -16
- package/src/internal/cli-deploy.ts +5 -2
- package/src/internal/cli-github.ts +13 -15
- package/src/internal/cursor/account-mcp.ts +3 -3
- package/src/internal/cursor/backend-client.ts +1 -1
- package/src/internal/cursor/credentials.ts +12 -7
- package/src/internal/cursor/github-credentials.ts +3 -3
- package/src/internal/cursor-event-relay.ts +11 -2
- package/src/internal/cursor-relay-core.ts +119 -11
- package/src/internal/cursor-slack-relay.ts +7 -3
- package/src/internal/distribution.ts +8 -0
- package/src/internal/docs-site.ts +3 -3
- package/src/internal/eval-run-store.ts +8 -10
- package/src/internal/github-fanout.ts +2 -2
- package/src/internal/host-platforms.ts +5 -5
- package/src/internal/mcp-endpoint.ts +1 -3
- package/src/internal/playground/toolchain.ts +6 -6
- package/src/internal/reminder-runner.ts +11 -17
- package/src/internal/resolve-prod-target.ts +55 -23
- package/src/internal/resolved-connections.ts +5 -5
- package/src/internal/schedule-runner.ts +1 -1
- package/src/internal/server.ts +14 -14
- package/src/internal/session-engine.ts +13 -15
- package/src/internal/sessions-client.ts +34 -0
- package/src/internal/storage-coordinator.ts +6 -6
- package/src/internal/update-check.ts +552 -0
- package/src/types.ts +3 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DtIOQzGj.js +0 -1
- package/dist/playground/assets/index-x60b9q2j.js +0 -312
- /package/dist/docs/assets/{index.md.m81y7TY7.lean.js → index.md.Cylk70gg.lean.js} +0 -0
package/src/internal/cli-ax.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
fetchSessionEvents,
|
|
42
42
|
formatSessionsTable,
|
|
43
43
|
listSessions,
|
|
44
|
+
resolvePreviousSessionId,
|
|
44
45
|
} from "./sessions-client.js";
|
|
45
46
|
import { createStreamProgress } from "./stream-progress.js";
|
|
46
47
|
import {
|
|
@@ -118,6 +119,11 @@ export interface AxCliOptions {
|
|
|
118
119
|
* (CLI flag `--print`.)
|
|
119
120
|
*/
|
|
120
121
|
print?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* `chat --resume` / `resume` with no session id: reattach the most
|
|
124
|
+
* recently updated followable session from `/v1/sessions`.
|
|
125
|
+
*/
|
|
126
|
+
resume?: boolean;
|
|
121
127
|
/** Abort signal (tests / programmatic stop of `playground` proxy). */
|
|
122
128
|
signal?: AbortSignal;
|
|
123
129
|
}
|
|
@@ -310,26 +316,30 @@ export async function cmdInit(options: AxCliOptions): Promise<number> {
|
|
|
310
316
|
* prints a trajectory instead of the interactive UI.
|
|
311
317
|
*/
|
|
312
318
|
/**
|
|
313
|
-
* `resume
|
|
314
|
-
* session
|
|
315
|
-
*
|
|
316
|
-
*
|
|
319
|
+
* `resume [sessionId]` — reattach the interactive chat REPL to an existing
|
|
320
|
+
* session. When the id is omitted, picks the most recently updated
|
|
321
|
+
* followable session from `/v1/sessions` (same as `chat --resume`).
|
|
322
|
+
* Looks up the continuation token via `/v1/sessions` when
|
|
323
|
+
* `--continuation-token` is omitted. Same flags as `chat` (`--prod`,
|
|
324
|
+
* `--message`, `--json`, …). `--session` is accepted as a redundant
|
|
325
|
+
* alias for the positional id.
|
|
317
326
|
*/
|
|
318
327
|
export async function cmdResume(
|
|
319
328
|
sessionId: string | undefined,
|
|
320
329
|
options: AxCliOptions
|
|
321
330
|
): Promise<number> {
|
|
322
331
|
const resolved = trimNonEmpty(sessionId) ?? trimNonEmpty(options.sessionId);
|
|
323
|
-
if (resolved
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
332
|
+
if (resolved !== undefined) {
|
|
333
|
+
return cmdChat({
|
|
334
|
+
...options,
|
|
335
|
+
sessionId: resolved,
|
|
336
|
+
resume: false,
|
|
337
|
+
});
|
|
329
338
|
}
|
|
330
339
|
return cmdChat({
|
|
331
340
|
...options,
|
|
332
|
-
sessionId:
|
|
341
|
+
sessionId: undefined,
|
|
342
|
+
resume: true,
|
|
333
343
|
});
|
|
334
344
|
}
|
|
335
345
|
|
|
@@ -352,6 +362,35 @@ export async function cmdChat(options: AxCliOptions): Promise<number> {
|
|
|
352
362
|
: (options.url ?? "http://127.0.0.1:3000"),
|
|
353
363
|
});
|
|
354
364
|
|
|
365
|
+
let sessionId = trimNonEmpty(options.sessionId);
|
|
366
|
+
// `--resume` without an id → most recently updated followable session.
|
|
367
|
+
// An explicit `--session` / positional id always wins.
|
|
368
|
+
if (options.resume === true && sessionId === undefined) {
|
|
369
|
+
try {
|
|
370
|
+
const previous = await resolvePreviousSessionId({
|
|
371
|
+
baseUrl: target.agentUrl,
|
|
372
|
+
auth: {
|
|
373
|
+
bearerToken: options.bearerToken,
|
|
374
|
+
headers: target.headers,
|
|
375
|
+
},
|
|
376
|
+
fetchImpl: options.fetchImpl,
|
|
377
|
+
});
|
|
378
|
+
if (previous === undefined) {
|
|
379
|
+
process.stderr.write(
|
|
380
|
+
"No previous followable session found. Start one with " +
|
|
381
|
+
`\`${CLI_COMMAND_NAME} chat\` or pass a session id.\n`
|
|
382
|
+
);
|
|
383
|
+
return 1;
|
|
384
|
+
}
|
|
385
|
+
sessionId = previous;
|
|
386
|
+
} catch (error) {
|
|
387
|
+
process.stderr.write(
|
|
388
|
+
`${error instanceof Error ? error.message : String(error)}\n`
|
|
389
|
+
);
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
355
394
|
if (options.json) {
|
|
356
395
|
if (message === undefined) {
|
|
357
396
|
process.stderr.write("chat --json requires --message\n");
|
|
@@ -359,13 +398,13 @@ export async function cmdChat(options: AxCliOptions): Promise<number> {
|
|
|
359
398
|
}
|
|
360
399
|
let continuationToken = options.continuationToken;
|
|
361
400
|
if (
|
|
362
|
-
|
|
401
|
+
sessionId !== undefined &&
|
|
363
402
|
(continuationToken === undefined || continuationToken === "")
|
|
364
403
|
) {
|
|
365
404
|
try {
|
|
366
405
|
continuationToken = await lookupSessionContinuation({
|
|
367
406
|
baseUrl: target.agentUrl,
|
|
368
|
-
sessionId
|
|
407
|
+
sessionId,
|
|
369
408
|
auth: {
|
|
370
409
|
bearerToken: options.bearerToken,
|
|
371
410
|
headers: target.headers,
|
|
@@ -381,7 +420,7 @@ export async function cmdChat(options: AxCliOptions): Promise<number> {
|
|
|
381
420
|
const result = await runSession({
|
|
382
421
|
baseUrl: target.agentUrl,
|
|
383
422
|
messages: [message],
|
|
384
|
-
sessionId
|
|
423
|
+
sessionId,
|
|
385
424
|
continuationToken,
|
|
386
425
|
bearerToken: options.bearerToken,
|
|
387
426
|
headers: target.headers,
|
|
@@ -409,7 +448,7 @@ export async function cmdChat(options: AxCliOptions): Promise<number> {
|
|
|
409
448
|
await runChat({
|
|
410
449
|
baseUrl: target.agentUrl,
|
|
411
450
|
message,
|
|
412
|
-
sessionId
|
|
451
|
+
sessionId,
|
|
413
452
|
continuationToken: options.continuationToken,
|
|
414
453
|
bearerToken: options.bearerToken,
|
|
415
454
|
headers: target.headers,
|
|
@@ -892,7 +931,7 @@ export async function cmdSessions(options: AxCliOptions): Promise<number> {
|
|
|
892
931
|
/**
|
|
893
932
|
* `session <id>` — view a full session (trajectory text by default).
|
|
894
933
|
* `--json` → trajectory JSON; `--events` (`rawEvents`) → raw event list.
|
|
895
|
-
* Interactive reattach: `resume
|
|
934
|
+
* Interactive reattach: `resume [id]` / `chat --resume` (or `chat --session`).
|
|
896
935
|
*/
|
|
897
936
|
export async function cmdSession(
|
|
898
937
|
sessionId: string | undefined,
|
|
@@ -674,7 +674,8 @@ async function deployOne(
|
|
|
674
674
|
` generation: ${detail.generation}\n` +
|
|
675
675
|
` kind: ${detail.deploymentKind}\n` +
|
|
676
676
|
` alias: ${aliasUrl}\n` +
|
|
677
|
-
` playground: ${
|
|
677
|
+
` playground: ${aliasUrl}/playground (sign in with the alias token)\n` +
|
|
678
|
+
` or: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n`
|
|
678
679
|
);
|
|
679
680
|
return 0;
|
|
680
681
|
}
|
|
@@ -795,6 +796,7 @@ export async function cmdDeploymentStatus(
|
|
|
795
796
|
` generation: ${detail.generation}`,
|
|
796
797
|
` kind: ${detail.deploymentKind}`,
|
|
797
798
|
` alias: ${aliasUrl}`,
|
|
799
|
+
` playground: ${aliasUrl}/playground`,
|
|
798
800
|
];
|
|
799
801
|
if (detail.gitRepoUrl !== null && detail.gitRepoUrl !== undefined) {
|
|
800
802
|
lines.push(` repo: ${detail.gitRepoUrl} @ ${detail.gitRef ?? ""}`);
|
|
@@ -1285,7 +1287,8 @@ function aliasTokenBlock(aliasToken: string): string {
|
|
|
1285
1287
|
return (
|
|
1286
1288
|
`\n Alias token (shown ONCE — save it now, it cannot be retrieved again):\n\n` +
|
|
1287
1289
|
` ${aliasToken}\n\n` +
|
|
1288
|
-
` Send it as the X-Agent-Alias-Token header on requests to the alias URL
|
|
1290
|
+
` Send it as the X-Agent-Alias-Token header on requests to the alias URL,\n` +
|
|
1291
|
+
` or paste it into the hosted playground (<alias>/playground) sign-in gate.\n` +
|
|
1289
1292
|
` Lost tokens: \`${CLI} rotate-token <slug>\` mints a replacement.\n\n`
|
|
1290
1293
|
);
|
|
1291
1294
|
}
|
|
@@ -196,7 +196,7 @@ async function cmdGithubForward(options: GithubCliOptions): Promise<number> {
|
|
|
196
196
|
);
|
|
197
197
|
return 1;
|
|
198
198
|
}
|
|
199
|
-
process.stderr.write(`[
|
|
199
|
+
process.stderr.write(`[agentkit] inferred --repo ${repo}\n`);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
const secret = options.secret ?? process.env.GITHUB_WEBHOOK_SECRET;
|
|
@@ -219,30 +219,30 @@ async function cmdGithubForward(options: GithubCliOptions): Promise<number> {
|
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
process.stderr.write(
|
|
222
|
-
`[
|
|
222
|
+
`[agentkit] forwarding ${org !== undefined ? `org ${org}` : `repo ${repo}`} to ${targets.length} channel${targets.length === 1 ? "" : "s"}:\n`
|
|
223
223
|
);
|
|
224
224
|
for (const target of targets) {
|
|
225
225
|
process.stderr.write(
|
|
226
|
-
`[
|
|
226
|
+
`[agentkit] → ${targetLabel(target)} ${target.url} (${target.events.join(", ")})\n`
|
|
227
227
|
);
|
|
228
228
|
}
|
|
229
229
|
if (fanout) {
|
|
230
230
|
process.stderr.write(
|
|
231
|
-
`[
|
|
231
|
+
`[agentkit] fan-out proxy on ${forwardUrl} (union events: ${events.join(", ")})\n`
|
|
232
232
|
);
|
|
233
233
|
}
|
|
234
234
|
if (secret === undefined || secret === "") {
|
|
235
235
|
process.stderr.write(
|
|
236
|
-
"[
|
|
236
|
+
"[agentkit] no signing secret — deliveries are unsigned. A `serve --dev` github channel accepts them; set GITHUB_WEBHOOK_SECRET on both sides to verify signatures (required for a non-dev target).\n"
|
|
237
237
|
);
|
|
238
238
|
}
|
|
239
239
|
const envToken = detectGitHubEnvTokenOverride();
|
|
240
240
|
if (envToken !== undefined) {
|
|
241
241
|
process.stderr.write(
|
|
242
|
-
`[
|
|
242
|
+
`[agentkit] warning: ${githubEnvTokenHint(envToken)}\n`
|
|
243
243
|
);
|
|
244
244
|
}
|
|
245
|
-
process.stderr.write(`[
|
|
245
|
+
process.stderr.write(`[agentkit] running: gh ${redact(args)}\n`);
|
|
246
246
|
|
|
247
247
|
try {
|
|
248
248
|
return await spawnGh(args);
|
|
@@ -318,7 +318,7 @@ async function cmdGithubReplay(options: GithubCliOptions): Promise<number> {
|
|
|
318
318
|
);
|
|
319
319
|
if (unsupported.length > 0) {
|
|
320
320
|
process.stderr.write(
|
|
321
|
-
`[
|
|
321
|
+
`[agentkit] cannot synthesize (skipped): ${unsupported.join(", ")}\n`
|
|
322
322
|
);
|
|
323
323
|
}
|
|
324
324
|
if (eventsToReplay.length === 0) {
|
|
@@ -364,9 +364,7 @@ async function cmdGithubReplay(options: GithubCliOptions): Promise<number> {
|
|
|
364
364
|
check,
|
|
365
365
|
});
|
|
366
366
|
if ("error" in result) {
|
|
367
|
-
process.stderr.write(
|
|
368
|
-
`[agent-serve] skipping ${event}: ${result.error}\n`
|
|
369
|
-
);
|
|
367
|
+
process.stderr.write(`[agentkit] skipping ${event}: ${result.error}\n`);
|
|
370
368
|
continue;
|
|
371
369
|
}
|
|
372
370
|
synthesized.push(result);
|
|
@@ -378,7 +376,7 @@ async function cmdGithubReplay(options: GithubCliOptions): Promise<number> {
|
|
|
378
376
|
|
|
379
377
|
const secret = options.secret ?? process.env.GITHUB_WEBHOOK_SECRET;
|
|
380
378
|
process.stderr.write(
|
|
381
|
-
`[
|
|
379
|
+
`[agentkit] replaying ${ref.label} (${synthesized
|
|
382
380
|
.map((s) => s.eventName)
|
|
383
381
|
.join(
|
|
384
382
|
", "
|
|
@@ -401,7 +399,7 @@ async function cmdGithubReplay(options: GithubCliOptions): Promise<number> {
|
|
|
401
399
|
|
|
402
400
|
if (options.out !== undefined) {
|
|
403
401
|
const file = await writeReplayFixture(options.out, event);
|
|
404
|
-
process.stderr.write(`[
|
|
402
|
+
process.stderr.write(`[agentkit] wrote ${file}\n`);
|
|
405
403
|
}
|
|
406
404
|
|
|
407
405
|
const label = eventLabel(event);
|
|
@@ -436,7 +434,7 @@ async function cmdGithubReplay(options: GithubCliOptions): Promise<number> {
|
|
|
436
434
|
`${delivery.label} ${delivery.status || "ERR"}${delivery.ok ? "" : " (rejected)"}`
|
|
437
435
|
)
|
|
438
436
|
.join(", ") || "(no matching channel)";
|
|
439
|
-
process.stderr.write(`[
|
|
437
|
+
process.stderr.write(`[agentkit] ${label} → ${summary}\n`);
|
|
440
438
|
}
|
|
441
439
|
}
|
|
442
440
|
|
|
@@ -747,7 +745,7 @@ async function ensureWebhookExtension(
|
|
|
747
745
|
};
|
|
748
746
|
}
|
|
749
747
|
process.stderr.write(
|
|
750
|
-
`[
|
|
748
|
+
`[agentkit] installing ${GITHUB_WEBHOOK_EXTENSION}@${GITHUB_WEBHOOK_EXTENSION_PIN}…\n`
|
|
751
749
|
);
|
|
752
750
|
if (
|
|
753
751
|
(await tryExec({
|
|
@@ -231,7 +231,7 @@ export class CursorAccountMcpBridge {
|
|
|
231
231
|
)
|
|
232
232
|
.join(", ");
|
|
233
233
|
this.logger(
|
|
234
|
-
`[
|
|
234
|
+
`[agentkit] Cursor account MCP connection "${ref.name}": ${
|
|
235
235
|
catalog.servers.length === 0
|
|
236
236
|
? "no matching connectors on this account — connect servers in Cursor (dashboard → MCP) or fix the `servers` filter"
|
|
237
237
|
: summary
|
|
@@ -242,7 +242,7 @@ export class CursorAccountMcpBridge {
|
|
|
242
242
|
);
|
|
243
243
|
if (needsAuth.length > 0) {
|
|
244
244
|
this.logger(
|
|
245
|
-
`[
|
|
245
|
+
`[agentkit] Cursor account MCP connection "${ref.name}": ${needsAuth
|
|
246
246
|
.map((server) => server.serverIdentifier)
|
|
247
247
|
.join(
|
|
248
248
|
", "
|
|
@@ -252,7 +252,7 @@ export class CursorAccountMcpBridge {
|
|
|
252
252
|
})
|
|
253
253
|
.catch((error: unknown) => {
|
|
254
254
|
this.logger(
|
|
255
|
-
`[
|
|
255
|
+
`[agentkit] Cursor account MCP connection "${ref.name}" preflight failed: ${describeError(error)}`
|
|
256
256
|
);
|
|
257
257
|
});
|
|
258
258
|
}
|
|
@@ -445,7 +445,7 @@ export class CursorBackendClient {
|
|
|
445
445
|
value: body.data.accessToken,
|
|
446
446
|
expiresAtMs: jwtExpiryMs(body.data.accessToken),
|
|
447
447
|
};
|
|
448
|
-
this.logger("[
|
|
448
|
+
this.logger("[agentkit] cursor account: minted a fresh access token");
|
|
449
449
|
return body.data.accessToken;
|
|
450
450
|
}
|
|
451
451
|
}
|
|
@@ -73,18 +73,23 @@ const storedCredentialsSchema: z.ZodObject<
|
|
|
73
73
|
export type StoredCredentials = z.infer<typeof storedCredentialsSchema>;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* `~/.config/agent-serve
|
|
77
|
-
*
|
|
78
|
-
* (tests, containers).
|
|
76
|
+
* `~/.config/agent-serve/` (honoring `XDG_CONFIG_HOME`), the host-level
|
|
77
|
+
* config directory for credentials and the update-check cache.
|
|
78
|
+
* `AGENT_SERVE_CONFIG_DIR` pins the directory outright (tests, containers).
|
|
79
79
|
*/
|
|
80
|
-
export function
|
|
81
|
-
|
|
80
|
+
export function configDirPath(): string {
|
|
81
|
+
return (
|
|
82
82
|
process.env.AGENT_SERVE_CONFIG_DIR ??
|
|
83
83
|
join(
|
|
84
84
|
process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"),
|
|
85
85
|
"agent-serve"
|
|
86
|
-
)
|
|
87
|
-
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `<configDir>/credentials.json` (0600). */
|
|
91
|
+
export function credentialsFilePath(): string {
|
|
92
|
+
return join(configDirPath(), "credentials.json");
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
export async function readStoredCredentials(): Promise<
|
|
@@ -148,14 +148,14 @@ export class CursorGithubCredentialProvider {
|
|
|
148
148
|
this.scheduleRefresh();
|
|
149
149
|
}
|
|
150
150
|
this.logger(
|
|
151
|
-
`[
|
|
151
|
+
`[agentkit] GitHub auth: refreshed Cursor account credential for ${this.repos.length} repo(s)`
|
|
152
152
|
);
|
|
153
153
|
return result.token;
|
|
154
154
|
} catch (error) {
|
|
155
155
|
const message = error instanceof Error ? error.message : String(error);
|
|
156
156
|
if (this.cached !== undefined && this.now() < this.cached.expiresAtMs) {
|
|
157
157
|
this.logger(
|
|
158
|
-
`[
|
|
158
|
+
`[agentkit] GitHub auth: Cursor account credential refresh failed; using the current lease: ${message}`
|
|
159
159
|
);
|
|
160
160
|
this.cached.refreshAtMs = Math.min(
|
|
161
161
|
this.cached.expiresAtMs,
|
|
@@ -207,7 +207,7 @@ export class CursorGithubCredentialProvider {
|
|
|
207
207
|
this.refreshTimer = setTimeout(() => {
|
|
208
208
|
void this.getToken().catch((error: unknown) => {
|
|
209
209
|
this.logger(
|
|
210
|
-
`[
|
|
210
|
+
`[agentkit] GitHub auth: Cursor account credential refresh failed: ${
|
|
211
211
|
error instanceof Error ? error.message : String(error)
|
|
212
212
|
}`
|
|
213
213
|
);
|
|
@@ -68,7 +68,10 @@ export interface CursorEventRelayOptions {
|
|
|
68
68
|
repos: readonly string[];
|
|
69
69
|
events: readonly string[];
|
|
70
70
|
stateDir: string;
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Called for each matching event; failures retry a bounded number of
|
|
73
|
+
* times, then log and skip.
|
|
74
|
+
*/
|
|
72
75
|
dispatch: (event: CursorScmEvent) => Promise<void>;
|
|
73
76
|
logger: (line: string) => void;
|
|
74
77
|
/** Test seam. Defaults to global fetch. */
|
|
@@ -213,6 +216,12 @@ export class CursorEventRelay {
|
|
|
213
216
|
events: [...this.options.events],
|
|
214
217
|
})),
|
|
215
218
|
}),
|
|
219
|
+
// Empty subscriptions drop this consumer's registration on clean stop
|
|
220
|
+
// instead of leaving it to lapse by TTL.
|
|
221
|
+
buildDeregistrationBody: (consumerId) => ({
|
|
222
|
+
consumerId,
|
|
223
|
+
subscriptions: [],
|
|
224
|
+
}),
|
|
216
225
|
applyRegistrationResponse: (json) => {
|
|
217
226
|
const result = json as { repos?: SubscriptionStatus[] };
|
|
218
227
|
const statuses = result.repos ?? [];
|
|
@@ -263,7 +272,7 @@ export class CursorEventRelay {
|
|
|
263
272
|
if (!this.loggedSkippedRepos.has(event.repo)) {
|
|
264
273
|
this.loggedSkippedRepos.add(event.repo);
|
|
265
274
|
this.options.logger(
|
|
266
|
-
`[
|
|
275
|
+
`[agentkit] cursor-events: skipping events for ${event.repo}: not in this host's subscription (another consumer of this API key likely subscribed it)`
|
|
267
276
|
);
|
|
268
277
|
}
|
|
269
278
|
return false;
|
|
@@ -12,6 +12,8 @@ import { randomUUID } from "node:crypto";
|
|
|
12
12
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
|
|
15
|
+
import { LOG_PREFIX } from "./distribution.js";
|
|
16
|
+
|
|
15
17
|
const OFFSET_SNAPSHOT_VERSION = 1 as const;
|
|
16
18
|
const DEFAULT_POLL_WAIT_MS = 25_000;
|
|
17
19
|
const DEFAULT_POLL_LIMIT = 100;
|
|
@@ -21,6 +23,11 @@ const DEFAULT_IDLE_POLL_DELAY_MS = 250;
|
|
|
21
23
|
const MIN_BACKOFF_MS = 1_000;
|
|
22
24
|
const MAX_BACKOFF_MS = 30_000;
|
|
23
25
|
const AUTH_FAILURE_BACKOFF_MS = 60_000;
|
|
26
|
+
/** Attempts per event before the offset advances past it. */
|
|
27
|
+
const DISPATCH_ATTEMPTS = 3;
|
|
28
|
+
const DISPATCH_RETRY_DELAYS_MS = [250, 1_000];
|
|
29
|
+
/** Budget for the best-effort deregistration PUT during stop(). */
|
|
30
|
+
const DEREGISTER_TIMEOUT_MS = 1_500;
|
|
24
31
|
|
|
25
32
|
interface OffsetSnapshot {
|
|
26
33
|
version: typeof OFFSET_SNAPSHOT_VERSION;
|
|
@@ -98,7 +105,7 @@ interface PollResponse<TEvent> {
|
|
|
98
105
|
* anchor/poll/dedupe/offset loop, and backoff — is shared.
|
|
99
106
|
*/
|
|
100
107
|
export interface RelayAdapter<TEvent extends { offset: string }> {
|
|
101
|
-
/** Log fragment, e.g. `cursor-events` → `[
|
|
108
|
+
/** Log fragment, e.g. `cursor-events` → `[agentkit] cursor-events: …`. */
|
|
102
109
|
readonly logPrefix: string;
|
|
103
110
|
/** Subscription registration path, e.g. `/v0/scm-events/subscriptions`. */
|
|
104
111
|
readonly subscribePath: string;
|
|
@@ -106,6 +113,14 @@ export interface RelayAdapter<TEvent extends { offset: string }> {
|
|
|
106
113
|
readonly pollPath: string;
|
|
107
114
|
/** The registration request body for this consumer id. */
|
|
108
115
|
buildRegistrationBody(consumerId: string): unknown;
|
|
116
|
+
/**
|
|
117
|
+
* The empty-scope registration body used to deregister this consumer on a
|
|
118
|
+
* clean stop. When present, `stop()` best-effort PUTs it so the consumer's
|
|
119
|
+
* registration does not linger as a ghost until its server-side TTL (a
|
|
120
|
+
* ghost with this host's agent names makes name routing ambiguous for a
|
|
121
|
+
* redeployed sibling until it expires).
|
|
122
|
+
*/
|
|
123
|
+
buildDeregistrationBody?(consumerId: string): unknown;
|
|
109
124
|
/**
|
|
110
125
|
* Apply the (ok) registration response: update the accepted-scope filter
|
|
111
126
|
* and return the human-readable outcome lines to log. The core logs them
|
|
@@ -119,7 +134,10 @@ export interface RelayAdapter<TEvent extends { offset: string }> {
|
|
|
119
134
|
* the offset but are not dispatched.
|
|
120
135
|
*/
|
|
121
136
|
shouldDispatch(event: TEvent): boolean;
|
|
122
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Handle one matching event. Failures retry a bounded number of times
|
|
139
|
+
* ({@link DISPATCH_ATTEMPTS}), then log and skip.
|
|
140
|
+
*/
|
|
123
141
|
dispatch(event: TEvent): Promise<void>;
|
|
124
142
|
}
|
|
125
143
|
|
|
@@ -145,6 +163,14 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
|
|
|
145
163
|
private consumerId = "";
|
|
146
164
|
/** undefined until the first registration, so a clean first success logs. */
|
|
147
165
|
private lastRegistrationLog: string | undefined;
|
|
166
|
+
/** Whether any registration succeeded (gates the stop-time deregister). */
|
|
167
|
+
private registeredOnce = false;
|
|
168
|
+
/**
|
|
169
|
+
* The in-flight heartbeat re-registration, if any. `stop()` awaits it
|
|
170
|
+
* before deregistering so a just-sent heartbeat cannot land after the
|
|
171
|
+
* deregistration PUT and resurrect the registration for a full TTL.
|
|
172
|
+
*/
|
|
173
|
+
private heartbeatPromise: Promise<boolean> | undefined;
|
|
148
174
|
|
|
149
175
|
constructor(private readonly options: CursorRelayCoreOptions<TEvent>) {
|
|
150
176
|
this.offsetStore = new RelayOffsetStore(options.stateDir);
|
|
@@ -168,7 +194,7 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
|
|
|
168
194
|
const registered = await this.registerUntilSuccess();
|
|
169
195
|
if (!registered) return;
|
|
170
196
|
this.registerTimer = setInterval(() => {
|
|
171
|
-
|
|
197
|
+
this.heartbeatPromise = this.registerOnce();
|
|
172
198
|
}, DEFAULT_REGISTER_INTERVAL_MS);
|
|
173
199
|
this.registerTimer.unref?.();
|
|
174
200
|
await this.consumeLoop();
|
|
@@ -227,11 +253,61 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
|
|
|
227
253
|
timer.unref?.();
|
|
228
254
|
}),
|
|
229
255
|
]);
|
|
256
|
+
// A heartbeat re-registration may be in flight (its fetch was just
|
|
257
|
+
// aborted); let it settle so it cannot land after the deregistration.
|
|
258
|
+
await this.heartbeatPromise?.catch(() => {});
|
|
259
|
+
await this.deregister();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Best-effort empty-scope PUT on clean stop, so this consumer's
|
|
264
|
+
* registration is dropped immediately instead of lingering until its
|
|
265
|
+
* server-side TTL. A ghost registration keeps this host's agent names
|
|
266
|
+
* live and makes name routing ambiguous for a redeployed sibling with a
|
|
267
|
+
* fresh consumer id. Failures only log — the TTL still cleans up.
|
|
268
|
+
*/
|
|
269
|
+
private async deregister(): Promise<void> {
|
|
270
|
+
const adapter = this.options.adapter;
|
|
271
|
+
if (
|
|
272
|
+
adapter.buildDeregistrationBody === undefined ||
|
|
273
|
+
!this.registeredOnce ||
|
|
274
|
+
this.consumerId === ""
|
|
275
|
+
) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const url = `${this.options.baseUrl.replace(/\/+$/, "")}${adapter.subscribePath}`;
|
|
279
|
+
const controller = new AbortController();
|
|
280
|
+
const timer = setTimeout(() => controller.abort(), DEREGISTER_TIMEOUT_MS);
|
|
281
|
+
timer.unref?.();
|
|
282
|
+
try {
|
|
283
|
+
const response = await this.fetchImpl(url, {
|
|
284
|
+
method: "PUT",
|
|
285
|
+
headers: {
|
|
286
|
+
authorization: `Bearer ${this.options.apiKey}`,
|
|
287
|
+
"content-type": "application/json",
|
|
288
|
+
},
|
|
289
|
+
body: JSON.stringify(adapter.buildDeregistrationBody(this.consumerId)),
|
|
290
|
+
signal: controller.signal,
|
|
291
|
+
});
|
|
292
|
+
if (response.ok) {
|
|
293
|
+
this.log("deregistered on shutdown");
|
|
294
|
+
} else {
|
|
295
|
+
this.log(
|
|
296
|
+
`deregistration failed: ${response.status} (registration lapses by TTL)`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
this.log(
|
|
301
|
+
`deregistration failed: ${describeRelayError(error)} (registration lapses by TTL)`
|
|
302
|
+
);
|
|
303
|
+
} finally {
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
}
|
|
230
306
|
}
|
|
231
307
|
|
|
232
308
|
private log(line: string): void {
|
|
233
309
|
this.options.logger(
|
|
234
|
-
|
|
310
|
+
`${LOG_PREFIX} ${this.options.adapter.logPrefix}: ${line}`
|
|
235
311
|
);
|
|
236
312
|
}
|
|
237
313
|
|
|
@@ -289,6 +365,7 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
|
|
|
289
365
|
this.logRegistrationOutcome(
|
|
290
366
|
this.options.adapter.applyRegistrationResponse(json)
|
|
291
367
|
);
|
|
368
|
+
this.registeredOnce = true;
|
|
292
369
|
return true;
|
|
293
370
|
} catch (error) {
|
|
294
371
|
if (this.isAbort(error)) return false;
|
|
@@ -371,13 +448,7 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
|
|
|
371
448
|
// on replay after a crash between dispatch and save).
|
|
372
449
|
if (compareStreamOffsets(event.offset, offset) <= 0) continue;
|
|
373
450
|
if (this.options.adapter.shouldDispatch(event)) {
|
|
374
|
-
|
|
375
|
-
await this.options.adapter.dispatch(event);
|
|
376
|
-
} catch (error) {
|
|
377
|
-
this.log(
|
|
378
|
-
`dispatch failed at offset ${event.offset}: ${describeRelayError(error)}`
|
|
379
|
-
);
|
|
380
|
-
}
|
|
451
|
+
await this.dispatchWithRetry(event);
|
|
381
452
|
}
|
|
382
453
|
offset = event.offset;
|
|
383
454
|
}
|
|
@@ -401,6 +472,43 @@ export class CursorRelayCore<TEvent extends { offset: string }> {
|
|
|
401
472
|
}
|
|
402
473
|
}
|
|
403
474
|
|
|
475
|
+
/**
|
|
476
|
+
* Dispatch one event with a small bounded retry before the offset advances
|
|
477
|
+
* past it. Transient local failures (e.g. a connection reset while POSTing
|
|
478
|
+
* to the channel) used to drop the event outright; a poison event still
|
|
479
|
+
* only costs {@link DISPATCH_ATTEMPTS} tries and is then skipped, keeping
|
|
480
|
+
* at-least-once semantics without a retry loop.
|
|
481
|
+
*/
|
|
482
|
+
private async dispatchWithRetry(event: TEvent): Promise<void> {
|
|
483
|
+
for (let attempt = 1; attempt <= DISPATCH_ATTEMPTS; attempt++) {
|
|
484
|
+
if (attempt > 1 && !this.running) {
|
|
485
|
+
this.log(
|
|
486
|
+
`dispatch abandoned at offset ${event.offset}: relay stopping`
|
|
487
|
+
);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
await this.options.adapter.dispatch(event);
|
|
492
|
+
return;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if (attempt === DISPATCH_ATTEMPTS) {
|
|
495
|
+
this.log(
|
|
496
|
+
`dispatch failed at offset ${event.offset} after ${attempt} attempts: ${describeRelayError(error)}`
|
|
497
|
+
);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
this.log(
|
|
501
|
+
`dispatch attempt ${attempt} failed at offset ${event.offset}: ${describeRelayError(error)}; retrying`
|
|
502
|
+
);
|
|
503
|
+
await this.sleep(
|
|
504
|
+
DISPATCH_RETRY_DELAYS_MS[
|
|
505
|
+
Math.min(attempt - 1, DISPATCH_RETRY_DELAYS_MS.length - 1)
|
|
506
|
+
]
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
404
512
|
/** Log the failure, sleep, and return the backoff for the NEXT failure. */
|
|
405
513
|
private async backoffAfterHttpFailure(
|
|
406
514
|
response: Response,
|
|
@@ -44,8 +44,8 @@ export interface CursorSlackRelayOptions {
|
|
|
44
44
|
agents: readonly CursorSlackAgentRegistration[];
|
|
45
45
|
stateDir: string;
|
|
46
46
|
/**
|
|
47
|
-
* Called for each event addressed to this consumer's agents; failures
|
|
48
|
-
*
|
|
47
|
+
* Called for each event addressed to this consumer's agents; failures
|
|
48
|
+
* retry a bounded number of times, then log and skip.
|
|
49
49
|
*/
|
|
50
50
|
dispatch: (envelope: CursorSlackRelayEvent) => Promise<void>;
|
|
51
51
|
logger: (line: string) => void;
|
|
@@ -122,7 +122,7 @@ export function createCursorSlackApiCaller(args: {
|
|
|
122
122
|
};
|
|
123
123
|
} catch (error) {
|
|
124
124
|
args.logger?.(
|
|
125
|
-
`[
|
|
125
|
+
`[agentkit] cursor-slack: ${method} proxy call failed: ${describeRelayError(error)}`
|
|
126
126
|
);
|
|
127
127
|
return { ok: false, error: "cursor_backend_unreachable" };
|
|
128
128
|
}
|
|
@@ -163,6 +163,10 @@ export class CursorSlackRelay {
|
|
|
163
163
|
})),
|
|
164
164
|
};
|
|
165
165
|
},
|
|
166
|
+
// Empty agents drop this consumer's registration on clean stop, so a
|
|
167
|
+
// redeployed sibling's identical agent names never tie with this
|
|
168
|
+
// host's ghost until the server-side TTL lapses.
|
|
169
|
+
buildDeregistrationBody: (consumerId) => ({ consumerId, agents: [] }),
|
|
166
170
|
applyRegistrationResponse: (json) => {
|
|
167
171
|
const result = json as RegistrationResponse;
|
|
168
172
|
const agentStatuses = result.agents ?? [];
|
|
@@ -25,6 +25,14 @@ export const LEGACY_PACKAGE_NAME = "@anysphere/agent-serve";
|
|
|
25
25
|
*/
|
|
26
26
|
export const AGENTKIT_PACKAGE_NAME = "@cursor/agentkit";
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Bracketed product tag for stderr / ServeOptions.logger lines
|
|
30
|
+
* (`[agentkit] …`, `[agentkit/slack] …`).
|
|
31
|
+
*/
|
|
32
|
+
export const LOG_PRODUCT = "agentkit";
|
|
33
|
+
export const LOG_PREFIX: string = `[${LOG_PRODUCT}]`;
|
|
34
|
+
export const SLACK_LOG_PREFIX: string = `[${LOG_PRODUCT}/slack]`;
|
|
35
|
+
|
|
28
36
|
/**
|
|
29
37
|
* Command name this CLI was invoked as. Both bins point at the same entry;
|
|
30
38
|
* keep help text and hints copy-pasteable for whichever the user typed.
|