@cursor/july 0.1.8 → 0.1.10
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.js +9 -0
- package/dist/channels/slack/post-update-delivery.d.ts +85 -0
- package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.BP8mAVMW.js → app.CY2buaEE.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.OfDZ5CXg.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.1FXIZbz8.js → VPLocalSearchBox.Ix954SFJ.js} +1 -1
- package/dist/docs/assets/chunks/{theme.Dm1R02v9.js → theme.PDwuJbyi.js} +2 -2
- package/dist/docs/assets/{deployment.md.BtfEsc9S.js → deployment.md.DC5-ImcM.js} +1 -1
- package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.js → example-agents_approval-buddy.md.DFGBYLcc.js} +2 -2
- package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.lean.js → example-agents_approval-buddy.md.DFGBYLcc.lean.js} +1 -1
- package/dist/docs/assets/{example-agents_index.md.CwtWu5ix.js → example-agents_index.md.BPXjUtRB.js} +2 -2
- package/dist/docs/assets/{example-agents_index.md.CwtWu5ix.lean.js → example-agents_index.md.BPXjUtRB.lean.js} +1 -1
- package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.js → example-agents_security-reviewer.md.CE9v-aKm.js} +2 -2
- package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.lean.js → example-agents_security-reviewer.md.CE9v-aKm.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.D189RBCH.js → reference_cli.md.CjfiV2Q-.js} +2 -1
- package/dist/docs/assets/{reference_connections.md.zaEYCLHT.js → reference_connections.md.FbWupyzW.js} +1 -1
- package/dist/docs/assets/{storage.md.Cu3MwM2a.js → storage.md.DRTdnFvd.js} +7 -1
- package/dist/docs/assets/{storage.md.Cu3MwM2a.lean.js → storage.md.DRTdnFvd.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +5 -5
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +5 -5
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +5 -5
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +6 -6
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.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 +3 -3
- 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 +6 -5
- package/dist/docs/reference/connections.html +5 -5
- 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 +11 -5
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +7 -4
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +45 -18
- package/dist/internal/deploy-client.d.ts +7 -1
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +3 -0
- package/dist/internal/deploy-source.d.ts +3 -0
- package/dist/internal/deploy-source.d.ts.map +1 -1
- package/dist/internal/deploy-source.js +25 -7
- package/dist/internal/discovery.js +1 -1
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/hosting.d.ts +12 -2
- package/dist/internal/hosting.d.ts.map +1 -1
- package/dist/internal/hosting.js +21 -4
- package/dist/internal/json-dir-store.d.ts +32 -0
- package/dist/internal/json-dir-store.d.ts.map +1 -0
- package/dist/internal/logs-client.d.ts +16 -2
- package/dist/internal/logs-client.d.ts.map +1 -1
- package/dist/internal/logs-client.js +51 -16
- package/dist/internal/persistence-coordinator.d.ts +127 -0
- package/dist/internal/persistence-coordinator.d.ts.map +1 -0
- package/dist/internal/resolved-connections.d.ts +6 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -1
- package/dist/internal/resolved-connections.js +6 -2
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +11 -1
- package/dist/internal/session-engine.d.ts +23 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +100 -26
- package/dist/internal/sessions-client.d.ts +16 -2
- package/dist/internal/sessions-client.d.ts.map +1 -1
- package/dist/internal/sessions-client.js +28 -12
- package/dist/internal/storage-coordinator.d.ts +11 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +30 -0
- package/dist/internal/workspace.d.ts +19 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +47 -13
- package/dist/persistence.d.ts +184 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/playground/assets/index-C0-QEG4O.js +86 -0
- package/dist/playground/assets/index-z3JY9O2x.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +75 -0
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -0
- package/dist/storage-backends/cursor-hosted.js +217 -0
- package/dist/storage-backends/postgres-kv.d.ts +5 -0
- package/dist/storage-backends/postgres-kv.d.ts.map +1 -1
- package/dist/storage-backends/postgres-kv.js +5 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/deployment.md +7 -3
- package/docs/example-agents/approval-buddy.md +1 -1
- package/docs/example-agents/index.md +1 -1
- package/docs/example-agents/security-reviewer.md +5 -5
- package/docs/reference/cli.md +2 -0
- package/docs/reference/connections.md +4 -2
- package/docs/storage.md +18 -4
- package/package.json +8 -1
- package/src/bin/agent-serve.ts +10 -0
- package/src/internal/cli-ax.ts +10 -4
- package/src/internal/cli-deploy.ts +58 -20
- package/src/internal/deploy-client.ts +11 -0
- package/src/internal/deploy-source.ts +24 -6
- package/src/internal/discovery.ts +1 -1
- package/src/internal/distribution.ts +1 -0
- package/src/internal/hosting.ts +24 -4
- package/src/internal/logs-client.ts +67 -21
- package/src/internal/resolved-connections.ts +14 -4
- package/src/internal/server.ts +15 -1
- package/src/internal/session-engine.ts +105 -13
- package/src/internal/sessions-client.ts +49 -14
- package/src/internal/storage-coordinator.ts +36 -0
- package/src/internal/workspace.ts +48 -11
- package/src/storage-backends/cursor-hosted.ts +267 -0
- package/src/storage-backends/postgres-kv.ts +5 -0
- package/src/types.ts +16 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.DevOabKJ.js +0 -1
- package/dist/playground/assets/index-7GJIUFQj.css +0 -1
- package/dist/playground/assets/index-Bpr4Th4M.js +0 -319
- /package/dist/docs/assets/{deployment.md.BtfEsc9S.lean.js → deployment.md.DC5-ImcM.lean.js} +0 -0
- /package/dist/docs/assets/{reference_cli.md.D189RBCH.lean.js → reference_cli.md.CjfiV2Q-.lean.js} +0 -0
- /package/dist/docs/assets/{reference_connections.md.zaEYCLHT.lean.js → reference_connections.md.FbWupyzW.lean.js} +0 -0
package/docs/storage.md
CHANGED
|
@@ -113,13 +113,27 @@ propagate errors — unlike session mirrors, which are at-most-once.
|
|
|
113
113
|
|
|
114
114
|
Without `agent/storage.ts`, `host.kv` falls back to files under
|
|
115
115
|
`--state-root/kv`. That is fine for local dogfood; it does **not**
|
|
116
|
-
survive replacing the host. For
|
|
117
|
-
`@cursor/july/storage/
|
|
118
|
-
|
|
116
|
+
survive replacing the host. For Cursor-managed hosting, prefer
|
|
117
|
+
`@cursor/july/storage/cursor-hosted` so sessions and author KV share the
|
|
118
|
+
platform Bugbot tables through a control-plane HTTP proxy (authenticated
|
|
119
|
+
as the deployment pod credential — engines never receive a database URL).
|
|
120
|
+
For self-hosted Postgres you own, use `@cursor/july/storage/postgres-kv`
|
|
121
|
+
and supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` yourself.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// agent/storage.ts — Cursor-managed hosting
|
|
125
|
+
import { defineStorage } from "@cursor/july/storage";
|
|
126
|
+
import { cursorHostedStorage } from "@cursor/july/storage/cursor-hosted";
|
|
127
|
+
|
|
128
|
+
export default defineStorage({
|
|
129
|
+
...cursorHostedStorage(),
|
|
130
|
+
});
|
|
131
|
+
```
|
|
119
132
|
|
|
120
133
|
Built-in helpers:
|
|
121
134
|
|
|
122
135
|
| Import | Backend |
|
|
123
136
|
| --- | --- |
|
|
124
137
|
| `@cursor/july/storage/file-kv` | File-per-key under `.agent-serve/kv` |
|
|
125
|
-
| `@cursor/july/storage/
|
|
138
|
+
| `@cursor/july/storage/cursor-hosted` | Platform Bugbot `agent_serve_*` via control-plane proxy |
|
|
139
|
+
| `@cursor/july/storage/postgres-kv` | Same tables; you supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cursor/july",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
|
@@ -138,6 +138,13 @@
|
|
|
138
138
|
"import": "./dist/storage-backends/postgres-kv.js",
|
|
139
139
|
"default": "./dist/storage-backends/postgres-kv.js"
|
|
140
140
|
},
|
|
141
|
+
"./storage/cursor-hosted": {
|
|
142
|
+
"anysphere-source": "./src/storage-backends/cursor-hosted.ts",
|
|
143
|
+
"bun": "./src/storage-backends/cursor-hosted.ts",
|
|
144
|
+
"types": "./dist/storage-backends/cursor-hosted.d.ts",
|
|
145
|
+
"import": "./dist/storage-backends/cursor-hosted.js",
|
|
146
|
+
"default": "./dist/storage-backends/cursor-hosted.js"
|
|
147
|
+
},
|
|
141
148
|
"./package.json": {
|
|
142
149
|
"anysphere-source": "./package.json",
|
|
143
150
|
"bun": "./package.json",
|
package/src/bin/agent-serve.ts
CHANGED
|
@@ -247,6 +247,11 @@ Options:
|
|
|
247
247
|
githubChannel({ cursorAccount }). Admitted callers can drive
|
|
248
248
|
sessions holding the account's repo-scoped GitHub credential,
|
|
249
249
|
so use only behind a trusted boundary (e.g. an SSO proxy).
|
|
250
|
+
--allow-anonymous-cursor-account-mcp
|
|
251
|
+
serve: allow --allow-anonymous together with
|
|
252
|
+
defineConnection({ cursorAccount: true }). Admitted callers
|
|
253
|
+
can drive the account's MCP connectors, so use only behind
|
|
254
|
+
a trusted boundary (e.g. an SSO proxy or hosted alias token).
|
|
250
255
|
--public-url serve: base URL where this host is reachable from outside
|
|
251
256
|
(e.g. https://agent-serve.example.com). Required for peer
|
|
252
257
|
connections (defineConnection({ agent })) on cloud-runtime
|
|
@@ -372,6 +377,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
372
377
|
"bearer-token": { type: "string" },
|
|
373
378
|
"allow-anonymous": { type: "boolean", default: false },
|
|
374
379
|
"allow-anonymous-cursor-github": { type: "boolean", default: false },
|
|
380
|
+
"allow-anonymous-cursor-account-mcp": { type: "boolean", default: false },
|
|
375
381
|
"no-schedules": { type: "boolean", default: false },
|
|
376
382
|
"no-playground": { type: "boolean", default: false },
|
|
377
383
|
"no-docs": { type: "boolean", default: false },
|
|
@@ -976,6 +982,10 @@ async function main(argv: string[]): Promise<number> {
|
|
|
976
982
|
allowAnonymous: values["allow-anonymous"] === true ? true : undefined,
|
|
977
983
|
allowAnonymousCursorGithub:
|
|
978
984
|
values["allow-anonymous-cursor-github"] === true ? true : undefined,
|
|
985
|
+
allowAnonymousCursorAccountMcp:
|
|
986
|
+
values["allow-anonymous-cursor-account-mcp"] === true
|
|
987
|
+
? true
|
|
988
|
+
: undefined,
|
|
979
989
|
schedules: values["no-schedules"] === true ? false : undefined,
|
|
980
990
|
playground: values["no-playground"] === true ? false : undefined,
|
|
981
991
|
docs: values["no-docs"] === true ? false : undefined,
|
package/src/internal/cli-ax.ts
CHANGED
|
@@ -54,7 +54,7 @@ import { runSession } from "./run-client.js";
|
|
|
54
54
|
import {
|
|
55
55
|
fetchSessionEvents,
|
|
56
56
|
formatSessionsTable,
|
|
57
|
-
|
|
57
|
+
listSessionsWithSource,
|
|
58
58
|
resolvePreviousSessionId,
|
|
59
59
|
} from "./sessions-client.js";
|
|
60
60
|
import { createStreamProgress } from "./stream-progress.js";
|
|
@@ -1383,7 +1383,7 @@ export async function cmdSessions(options: AxCliOptions): Promise<number> {
|
|
|
1383
1383
|
return target;
|
|
1384
1384
|
}
|
|
1385
1385
|
try {
|
|
1386
|
-
const
|
|
1386
|
+
const listed = await listSessionsWithSource({
|
|
1387
1387
|
baseUrl: target.agentUrl,
|
|
1388
1388
|
auth: {
|
|
1389
1389
|
bearerToken: options.bearerToken,
|
|
@@ -1392,10 +1392,16 @@ export async function cmdSessions(options: AxCliOptions): Promise<number> {
|
|
|
1392
1392
|
fetchImpl: options.fetchImpl,
|
|
1393
1393
|
});
|
|
1394
1394
|
if (options.json === true) {
|
|
1395
|
-
|
|
1395
|
+
const payload =
|
|
1396
|
+
listed.source === undefined
|
|
1397
|
+
? { sessions: listed.sessions }
|
|
1398
|
+
: { sessions: listed.sessions, source: listed.source };
|
|
1399
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1396
1400
|
return 0;
|
|
1397
1401
|
}
|
|
1398
|
-
process.stdout.write(
|
|
1402
|
+
process.stdout.write(
|
|
1403
|
+
formatSessionsTable(listed.sessions, { source: listed.source })
|
|
1404
|
+
);
|
|
1399
1405
|
return 0;
|
|
1400
1406
|
} catch (error) {
|
|
1401
1407
|
process.stderr.write(
|
|
@@ -40,9 +40,11 @@ import {
|
|
|
40
40
|
} from "./discovery.js";
|
|
41
41
|
import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
|
|
42
42
|
import {
|
|
43
|
+
isReservedSecretName,
|
|
43
44
|
isValidCursorEventRepo,
|
|
44
45
|
validateSettableSecretName,
|
|
45
46
|
} from "./hosting.js";
|
|
47
|
+
import { deployProgressLabel, sanitizeCustomerError } from "./logs-client.js";
|
|
46
48
|
import { stderrPalette } from "./terminal-style.js";
|
|
47
49
|
|
|
48
50
|
const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
@@ -554,6 +556,24 @@ async function deployOne(
|
|
|
554
556
|
);
|
|
555
557
|
return 1;
|
|
556
558
|
}
|
|
559
|
+
// Repo-backed fields without a resolvable https origin become a static
|
|
560
|
+
// deploy body the control plane rejects. Fail here with a fix hint —
|
|
561
|
+
// common when origin is an unrecognized SSH shape (pass --repo).
|
|
562
|
+
if (
|
|
563
|
+
gitRepoUrl === undefined &&
|
|
564
|
+
(gitRef !== undefined ||
|
|
565
|
+
agentPath !== undefined ||
|
|
566
|
+
(options.cursorEventsRepos !== undefined &&
|
|
567
|
+
options.cursorEventsRepos.length > 0) ||
|
|
568
|
+
(options.allowDomains !== undefined && options.allowDomains.length > 0))
|
|
569
|
+
) {
|
|
570
|
+
ctx.err(
|
|
571
|
+
`Could not resolve an https:// git repository URL for this deploy.\n` +
|
|
572
|
+
` Pass --repo https://github.com/<owner>/<repo> (inferred from origin when possible).\n` +
|
|
573
|
+
` Or omit --ref/--path/--cursor-events-repo/--allow-domain for a static engine.\n`
|
|
574
|
+
);
|
|
575
|
+
return 1;
|
|
576
|
+
}
|
|
557
577
|
for (const repo of options.cursorEventsRepos ?? []) {
|
|
558
578
|
if (!isValidCursorEventRepo(repo)) {
|
|
559
579
|
ctx.err(`Invalid --cursor-events-repo: ${repo} (expected owner/name)\n`);
|
|
@@ -659,13 +679,11 @@ async function deployOne(
|
|
|
659
679
|
}
|
|
660
680
|
|
|
661
681
|
if (detail.status !== "running") {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}\n`
|
|
668
|
-
);
|
|
682
|
+
const errorDetail =
|
|
683
|
+
detail.lastError === null || detail.lastError === undefined
|
|
684
|
+
? ""
|
|
685
|
+
: `: ${sanitizeCustomerError(detail.lastError)}`;
|
|
686
|
+
ctx.err(`Deployment ${slug} ended ${detail.status}${errorDetail}\n`);
|
|
669
687
|
return 1;
|
|
670
688
|
}
|
|
671
689
|
ctx.out(
|
|
@@ -709,7 +727,11 @@ function warnMissingSecrets(
|
|
|
709
727
|
return;
|
|
710
728
|
}
|
|
711
729
|
const setNames = new Set(set ?? []);
|
|
712
|
-
|
|
730
|
+
// Platform-managed names (CURSOR_*, hosted storage) are injected by the
|
|
731
|
+
// control plane and never appear in secrets list — do not warn on them.
|
|
732
|
+
const missing = declared.filter(
|
|
733
|
+
(name) => !setNames.has(name) && !isReservedSecretName(name)
|
|
734
|
+
);
|
|
713
735
|
if (missing.length === 0) {
|
|
714
736
|
return;
|
|
715
737
|
}
|
|
@@ -793,11 +815,23 @@ export async function cmdDeploymentStatus(
|
|
|
793
815
|
const lines = [
|
|
794
816
|
`${detail.slug} (team ${detail.teamId})`,
|
|
795
817
|
` status: ${detail.status}`,
|
|
818
|
+
];
|
|
819
|
+
if (detail.progress !== undefined) {
|
|
820
|
+
lines.push(` progress: ${detail.progress}`);
|
|
821
|
+
}
|
|
822
|
+
if (
|
|
823
|
+
detail.statusMessage !== null &&
|
|
824
|
+
detail.statusMessage !== undefined &&
|
|
825
|
+
detail.statusMessage.trim() !== ""
|
|
826
|
+
) {
|
|
827
|
+
lines.push(` step: ${detail.statusMessage}`);
|
|
828
|
+
}
|
|
829
|
+
lines.push(
|
|
796
830
|
` generation: ${detail.generation}`,
|
|
797
831
|
` kind: ${detail.deploymentKind}`,
|
|
798
832
|
` alias: ${aliasUrl}`,
|
|
799
|
-
` playground: ${aliasUrl}/playground
|
|
800
|
-
|
|
833
|
+
` playground: ${aliasUrl}/playground`
|
|
834
|
+
);
|
|
801
835
|
if (detail.gitRepoUrl !== null && detail.gitRepoUrl !== undefined) {
|
|
802
836
|
lines.push(` repo: ${detail.gitRepoUrl} @ ${detail.gitRef ?? ""}`);
|
|
803
837
|
}
|
|
@@ -835,7 +869,7 @@ export async function cmdDeploymentStatus(
|
|
|
835
869
|
);
|
|
836
870
|
}
|
|
837
871
|
if (detail.lastError !== null && detail.lastError !== undefined) {
|
|
838
|
-
lines.push(` last error: ${detail.lastError}`);
|
|
872
|
+
lines.push(` last error: ${sanitizeCustomerError(detail.lastError)}`);
|
|
839
873
|
}
|
|
840
874
|
if (detail.desiredUpdatedAt !== undefined) {
|
|
841
875
|
lines.push(` updated: ${detail.desiredUpdatedAt}`);
|
|
@@ -1229,7 +1263,9 @@ function createLineReader(
|
|
|
1229
1263
|
/**
|
|
1230
1264
|
* Poll deployment status every `pollIntervalMs` until it lands in
|
|
1231
1265
|
* `terminal` (single-line progress on a TTY, one line per transition
|
|
1232
|
-
* otherwise).
|
|
1266
|
+
* otherwise). Prefers customer `statusMessage` / `progress` over raw
|
|
1267
|
+
* status so create → checkout → start is visible. Throws DeployApiError
|
|
1268
|
+
* on request failures or timeout.
|
|
1233
1269
|
*/
|
|
1234
1270
|
async function pollUntil(
|
|
1235
1271
|
ctx: DeployCliContext,
|
|
@@ -1238,21 +1274,22 @@ async function pollUntil(
|
|
|
1238
1274
|
): Promise<DeploymentDetail> {
|
|
1239
1275
|
const startedAt = Date.now();
|
|
1240
1276
|
const tty = process.stderr.isTTY === true;
|
|
1241
|
-
let
|
|
1277
|
+
let lastLabel: string | undefined;
|
|
1242
1278
|
let sawProgress = false;
|
|
1243
1279
|
|
|
1244
|
-
const report = (
|
|
1280
|
+
const report = (detail: DeploymentDetail): void => {
|
|
1245
1281
|
if (ctx.json) {
|
|
1246
1282
|
return;
|
|
1247
1283
|
}
|
|
1284
|
+
const label = deployProgressLabel(detail);
|
|
1248
1285
|
const elapsed = Math.round((Date.now() - startedAt) / 1000);
|
|
1249
1286
|
if (tty) {
|
|
1250
|
-
ctx.err(`\r\x1b[2K${slug}: ${
|
|
1287
|
+
ctx.err(`\r\x1b[2K${slug}: ${label} (${elapsed}s)`);
|
|
1251
1288
|
sawProgress = true;
|
|
1252
|
-
} else if (
|
|
1253
|
-
ctx.err(`${slug}: ${
|
|
1289
|
+
} else if (label !== lastLabel) {
|
|
1290
|
+
ctx.err(`${slug}: ${label}\n`);
|
|
1254
1291
|
}
|
|
1255
|
-
|
|
1292
|
+
lastLabel = label;
|
|
1256
1293
|
};
|
|
1257
1294
|
const finishProgressLine = (): void => {
|
|
1258
1295
|
if (tty && sawProgress) {
|
|
@@ -1267,15 +1304,16 @@ async function pollUntil(
|
|
|
1267
1304
|
finishProgressLine();
|
|
1268
1305
|
throw error;
|
|
1269
1306
|
});
|
|
1270
|
-
report(detail
|
|
1307
|
+
report(detail);
|
|
1271
1308
|
if (terminal.has(detail.status)) {
|
|
1272
1309
|
finishProgressLine();
|
|
1273
1310
|
return detail;
|
|
1274
1311
|
}
|
|
1275
1312
|
if (Date.now() - startedAt >= ctx.timeoutMs) {
|
|
1276
1313
|
finishProgressLine();
|
|
1314
|
+
const last = deployProgressLabel(detail);
|
|
1277
1315
|
throw new DeployApiError(
|
|
1278
|
-
`Timed out after ${Math.round(ctx.timeoutMs / 1000)}s waiting for ${slug} (last
|
|
1316
|
+
`Timed out after ${Math.round(ctx.timeoutMs / 1000)}s waiting for ${slug} (last: ${last}). It may still converge — check \`${CLI} deployment ${slug} --team ${ctx.teamId}\`.`,
|
|
1279
1317
|
0
|
|
1280
1318
|
);
|
|
1281
1319
|
}
|
|
@@ -31,7 +31,9 @@ export type DeploymentStatus =
|
|
|
31
31
|
export type DeploymentProgress =
|
|
32
32
|
| "queued"
|
|
33
33
|
| "preparing"
|
|
34
|
+
| "creating"
|
|
34
35
|
| "building"
|
|
36
|
+
| "starting"
|
|
35
37
|
| "finalizing"
|
|
36
38
|
| "running"
|
|
37
39
|
| "failed"
|
|
@@ -135,6 +137,12 @@ export interface Deployment {
|
|
|
135
137
|
* backends; older responses omit it.
|
|
136
138
|
*/
|
|
137
139
|
progress?: DeploymentProgress;
|
|
140
|
+
/**
|
|
141
|
+
* Live step while the deploy converges (e.g. "Checking out source…").
|
|
142
|
+
* Null when running / stopped or when only a failure is on the row.
|
|
143
|
+
* Present on newer backends; older responses omit it.
|
|
144
|
+
*/
|
|
145
|
+
statusMessage?: string | null;
|
|
138
146
|
generation: number;
|
|
139
147
|
observedGeneration?: number | null;
|
|
140
148
|
engineGeneration?: number | null;
|
|
@@ -218,7 +226,9 @@ const deploymentSchema = z.object({
|
|
|
218
226
|
.enum([
|
|
219
227
|
"queued",
|
|
220
228
|
"preparing",
|
|
229
|
+
"creating",
|
|
221
230
|
"building",
|
|
231
|
+
"starting",
|
|
222
232
|
"finalizing",
|
|
223
233
|
"running",
|
|
224
234
|
"failed",
|
|
@@ -227,6 +237,7 @@ const deploymentSchema = z.object({
|
|
|
227
237
|
"degraded",
|
|
228
238
|
])
|
|
229
239
|
.optional(),
|
|
240
|
+
statusMessage: z.string().nullable().optional(),
|
|
230
241
|
generation: z.number(),
|
|
231
242
|
observedGeneration: z.number().nullable().optional(),
|
|
232
243
|
engineGeneration: z.number().nullable().optional(),
|
|
@@ -39,17 +39,35 @@ const defaultGitRunner: GitRunner = async (args, cwd) => {
|
|
|
39
39
|
* Normalize a git remote URL to the https form the control plane accepts
|
|
40
40
|
* (`https://host/owner/repo`, no `.git` suffix). Returns undefined for
|
|
41
41
|
* unrecognized shapes.
|
|
42
|
+
*
|
|
43
|
+
* Accepts HTTPS, `git@host:path`, GitHub org-scoped SSH
|
|
44
|
+
* (`org-123@github.com:owner/repo.git`), and `ssh://user@host[:port]/path`.
|
|
42
45
|
*/
|
|
43
46
|
export function httpsGitRepoUrlFromRemote(remote: string): string | undefined {
|
|
44
47
|
let url = remote.trim();
|
|
45
48
|
if (url === "") {
|
|
46
49
|
return undefined;
|
|
47
50
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
|
|
52
|
+
// SCP-like SSH: user@host:owner/repo(.git) — includes git@ and GitHub
|
|
53
|
+
// org deploy keys (org-NNNN@github.com:…).
|
|
54
|
+
const scpMatch = /^([^@/\s]+)@([^:/\s]+):(.+)$/.exec(url);
|
|
55
|
+
if (scpMatch !== null) {
|
|
56
|
+
const host = scpMatch[2]!;
|
|
57
|
+
const path = scpMatch[3]!;
|
|
58
|
+
url = `https://${host}/${path}`;
|
|
59
|
+
} else if (/^ssh:\/\//i.test(url)) {
|
|
60
|
+
// ssh://user@host[:port]/owner/repo → https://host/owner/repo
|
|
61
|
+
try {
|
|
62
|
+
const ssh = new URL(url);
|
|
63
|
+
const path = ssh.pathname.replace(/^\/+/, "");
|
|
64
|
+
if (ssh.hostname === "" || path === "") {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
url = `https://${ssh.hostname}/${path}`;
|
|
68
|
+
} catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
53
71
|
} else if (!/^https?:\/\//i.test(url)) {
|
|
54
72
|
return undefined;
|
|
55
73
|
}
|
|
@@ -67,7 +85,7 @@ export function httpsGitRepoUrlFromRemote(remote: string): string | undefined {
|
|
|
67
85
|
if (path === "" || !path.includes("/")) {
|
|
68
86
|
return undefined;
|
|
69
87
|
}
|
|
70
|
-
// Use hostname (not host) so ssh
|
|
88
|
+
// Use hostname (not host) so ssh://…@github.com:22/… does not keep :22.
|
|
71
89
|
return `https://${parsed.hostname}/${path}`;
|
|
72
90
|
}
|
|
73
91
|
|
|
@@ -454,7 +454,7 @@ function resolveHosting(
|
|
|
454
454
|
warn(`hosting.secretNames: ${nameError}`);
|
|
455
455
|
} else if (isReservedSecretName(name)) {
|
|
456
456
|
warn(
|
|
457
|
-
`hosting.secretNames: ${JSON.stringify(name)} is platform-
|
|
457
|
+
`hosting.secretNames: ${JSON.stringify(name)} is platform-managed (CURSOR_* / Cursor-hosted storage) and injected automatically; omit it from secretNames.`
|
|
458
458
|
);
|
|
459
459
|
}
|
|
460
460
|
}
|
|
@@ -105,4 +105,5 @@ export const AUTHORING_ENTRY_FILES: ReadonlyArray<
|
|
|
105
105
|
["storage", "storage.js"],
|
|
106
106
|
["storage/file-kv", "storage-backends/file-kv.js"],
|
|
107
107
|
["storage/postgres-kv", "storage-backends/postgres-kv.js"],
|
|
108
|
+
["storage/cursor-hosted", "storage-backends/cursor-hosted.js"],
|
|
108
109
|
];
|
package/src/internal/hosting.ts
CHANGED
|
@@ -18,6 +18,17 @@ export const MAX_SECRET_NAME_LENGTH = 64;
|
|
|
18
18
|
/** Secret names the platform injects; user writes are rejected. */
|
|
19
19
|
export const RESERVED_SECRET_NAME_PREFIX = "CURSOR_";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Additional platform-managed env names for Cursor-hosted durable storage
|
|
23
|
+
* (Bugbot `agent_serve_*`). Mirrored from
|
|
24
|
+
* `@cursor/july/storage/cursor-hosted` so validate/secrets CLI stay in sync
|
|
25
|
+
* without importing the storage backend into the CLI graph.
|
|
26
|
+
*/
|
|
27
|
+
export const PLATFORM_HOSTED_STORAGE_SECRET_NAMES = [
|
|
28
|
+
"BUGBOTDB_URL",
|
|
29
|
+
"AGENT_SERVE_DEPLOYMENT_ID",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
21
32
|
const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
22
33
|
|
|
23
34
|
// Lowercase hostname with >=2 labels, an alphabetic TLD, and at most one
|
|
@@ -49,14 +60,23 @@ export function validateSecretName(name: string): string | undefined {
|
|
|
49
60
|
return undefined;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* True when the platform manages this name (`CURSOR_*` or hosted-storage
|
|
65
|
+
* env vars); user writes / deletes are rejected.
|
|
66
|
+
*/
|
|
53
67
|
export function isReservedSecretName(name: string): boolean {
|
|
54
|
-
|
|
68
|
+
if (name.startsWith(RESERVED_SECRET_NAME_PREFIX)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const upper = name.toUpperCase();
|
|
72
|
+
return (PLATFORM_HOSTED_STORAGE_SECRET_NAMES as readonly string[]).some(
|
|
73
|
+
(managed) => managed === upper
|
|
74
|
+
);
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
/**
|
|
58
78
|
* Human error for a secret name a user is trying to write or delete:
|
|
59
|
-
* grammar plus
|
|
79
|
+
* grammar plus platform-managed names (`CURSOR_*`, hosted storage).
|
|
60
80
|
*/
|
|
61
81
|
export function validateSettableSecretName(name: string): string | undefined {
|
|
62
82
|
const nameError = validateSecretName(name);
|
|
@@ -64,7 +84,7 @@ export function validateSettableSecretName(name: string): string | undefined {
|
|
|
64
84
|
return nameError;
|
|
65
85
|
}
|
|
66
86
|
if (isReservedSecretName(name)) {
|
|
67
|
-
return `Secret name ${JSON.stringify(name)} is platform-
|
|
87
|
+
return `Secret name ${JSON.stringify(name)} is platform-managed; the platform injects CURSOR_* and Cursor-hosted storage values automatically.`;
|
|
68
88
|
}
|
|
69
89
|
return undefined;
|
|
70
90
|
}
|
|
@@ -35,7 +35,9 @@ export type LogsProgress =
|
|
|
35
35
|
| DeploymentProgress
|
|
36
36
|
| "queued"
|
|
37
37
|
| "preparing"
|
|
38
|
+
| "creating"
|
|
38
39
|
| "building"
|
|
40
|
+
| "starting"
|
|
39
41
|
| "finalizing"
|
|
40
42
|
| "running"
|
|
41
43
|
| "failed"
|
|
@@ -46,7 +48,9 @@ export type LogsProgress =
|
|
|
46
48
|
const PROGRESS_MESSAGES: Readonly<Record<LogsProgress, string>> = {
|
|
47
49
|
queued: "Waiting to start…",
|
|
48
50
|
preparing: "Preparing runtime…",
|
|
49
|
-
|
|
51
|
+
creating: "Creating runtime…",
|
|
52
|
+
building: "Checking out source and installing dependencies…",
|
|
53
|
+
starting: "Starting agent and waiting for health…",
|
|
50
54
|
finalizing: "Finishing deployment…",
|
|
51
55
|
running: "Deployment running",
|
|
52
56
|
failed: "Deploy failed",
|
|
@@ -55,9 +59,15 @@ const PROGRESS_MESSAGES: Readonly<Record<LogsProgress, string>> = {
|
|
|
55
59
|
degraded: "Deployment degraded",
|
|
56
60
|
};
|
|
57
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Customer-facing line for the current deploy step. Prefer the live
|
|
64
|
+
* `statusMessage` from the API when present; otherwise the progress
|
|
65
|
+
* vocabulary. Failed/degraded always append sanitized `lastError`.
|
|
66
|
+
*/
|
|
58
67
|
export function progressMessage(
|
|
59
68
|
progress: LogsProgress,
|
|
60
|
-
lastError?: string | null
|
|
69
|
+
lastError?: string | null,
|
|
70
|
+
statusMessage?: string | null
|
|
61
71
|
): string {
|
|
62
72
|
if (progress === "failed") {
|
|
63
73
|
const detail =
|
|
@@ -73,6 +83,13 @@ export function progressMessage(
|
|
|
73
83
|
: `: ${sanitizeCustomerError(lastError)}`;
|
|
74
84
|
return `Deployment degraded${detail}`;
|
|
75
85
|
}
|
|
86
|
+
if (
|
|
87
|
+
statusMessage !== null &&
|
|
88
|
+
statusMessage !== undefined &&
|
|
89
|
+
statusMessage.trim() !== ""
|
|
90
|
+
) {
|
|
91
|
+
return statusMessage.trim();
|
|
92
|
+
}
|
|
76
93
|
return PROGRESS_MESSAGES[progress];
|
|
77
94
|
}
|
|
78
95
|
|
|
@@ -85,7 +102,38 @@ export function sanitizeCustomerError(message: string): string {
|
|
|
85
102
|
.replace(/\bcursorvm\.com\b/gi, "host")
|
|
86
103
|
.replace(/\bx-anyrun-[a-z0-9-]+\b/gi, "header")
|
|
87
104
|
.replace(/\bPROVISIONING\b/g, "building")
|
|
88
|
-
.replace(/\bPLANNED\b/g, "preparing")
|
|
105
|
+
.replace(/\bPLANNED\b/g, "preparing")
|
|
106
|
+
.replace(/\bgitCloneCheckout\b/gi, "git clone")
|
|
107
|
+
.replace(/\bgitFetchCheckout\b/gi, "git fetch")
|
|
108
|
+
.replace(/\bgitCheckout:clone\b/gi, "checkout")
|
|
109
|
+
.replace(/\bgitCheckout:fetch\b/gi, "checkout");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* One-line label for `agentkit deploy` polling (TTY spinner / non-TTY
|
|
114
|
+
* transitions). Uses statusMessage → progress → raw status.
|
|
115
|
+
*/
|
|
116
|
+
export function deployProgressLabel(detail: {
|
|
117
|
+
status: string;
|
|
118
|
+
progress?: DeploymentProgress;
|
|
119
|
+
statusMessage?: string | null;
|
|
120
|
+
}): string {
|
|
121
|
+
if (
|
|
122
|
+
detail.statusMessage !== null &&
|
|
123
|
+
detail.statusMessage !== undefined &&
|
|
124
|
+
detail.statusMessage.trim() !== ""
|
|
125
|
+
) {
|
|
126
|
+
return detail.statusMessage
|
|
127
|
+
.trim()
|
|
128
|
+
.replace(/…$/, "")
|
|
129
|
+
.replace(/\.\.\.$/, "");
|
|
130
|
+
}
|
|
131
|
+
if (detail.progress !== undefined) {
|
|
132
|
+
return PROGRESS_MESSAGES[detail.progress]
|
|
133
|
+
.replace(/…$/, "")
|
|
134
|
+
.replace(/\.\.\.$/, "");
|
|
135
|
+
}
|
|
136
|
+
return detail.status;
|
|
89
137
|
}
|
|
90
138
|
|
|
91
139
|
export async function fetchLogs(args: {
|
|
@@ -241,7 +289,7 @@ async function followProdLogs(args: {
|
|
|
241
289
|
);
|
|
242
290
|
});
|
|
243
291
|
|
|
244
|
-
let
|
|
292
|
+
let lastEmittedKey: string | undefined;
|
|
245
293
|
const slugHint = args.slug !== undefined && args.slug !== "" ? args.slug : "";
|
|
246
294
|
if (args.once !== true) {
|
|
247
295
|
const label = slugHint !== "" ? slugHint : "deployment";
|
|
@@ -260,33 +308,32 @@ async function followProdLogs(args: {
|
|
|
260
308
|
}
|
|
261
309
|
|
|
262
310
|
const progress = resolveProgress(detail);
|
|
311
|
+
const message = progressMessage(
|
|
312
|
+
progress,
|
|
313
|
+
detail.lastError,
|
|
314
|
+
detail.statusMessage
|
|
315
|
+
);
|
|
316
|
+
const emitKey = `${progress}\0${message}`;
|
|
263
317
|
|
|
264
318
|
if (progress === "failed") {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
lastProgress = progress;
|
|
319
|
+
if (emitKey !== lastEmittedKey) {
|
|
320
|
+
lastEmittedKey = emitKey;
|
|
268
321
|
emitProgress(args.write, args.json === true, progress, message);
|
|
269
322
|
}
|
|
270
323
|
throw new Error(`Deployment "${detail.slug}" failed.`);
|
|
271
324
|
}
|
|
272
325
|
if (progress === "stopped") {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
lastProgress = progress;
|
|
326
|
+
if (emitKey !== lastEmittedKey) {
|
|
327
|
+
lastEmittedKey = emitKey;
|
|
276
328
|
emitProgress(args.write, args.json === true, progress, message);
|
|
277
329
|
}
|
|
278
330
|
throw new Error(`Deployment "${detail.slug}" is stopped.`);
|
|
279
331
|
}
|
|
280
332
|
if (progress === "running" || progress === "degraded") {
|
|
281
333
|
// Hand off to runtime logs. Print degraded once; skip a "running" line.
|
|
282
|
-
if (progress === "degraded" &&
|
|
283
|
-
|
|
284
|
-
emitProgress(
|
|
285
|
-
args.write,
|
|
286
|
-
args.json === true,
|
|
287
|
-
progress,
|
|
288
|
-
progressMessage(progress, detail.lastError)
|
|
289
|
-
);
|
|
334
|
+
if (progress === "degraded" && emitKey !== lastEmittedKey) {
|
|
335
|
+
lastEmittedKey = emitKey;
|
|
336
|
+
emitProgress(args.write, args.json === true, progress, message);
|
|
290
337
|
}
|
|
291
338
|
const runtime = await resolveRuntime();
|
|
292
339
|
await followRuntimeLogs({
|
|
@@ -306,9 +353,8 @@ async function followProdLogs(args: {
|
|
|
306
353
|
return;
|
|
307
354
|
}
|
|
308
355
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
lastProgress = progress;
|
|
356
|
+
if (emitKey !== lastEmittedKey) {
|
|
357
|
+
lastEmittedKey = emitKey;
|
|
312
358
|
emitProgress(args.write, args.json === true, progress, message);
|
|
313
359
|
}
|
|
314
360
|
|
|
@@ -78,13 +78,23 @@ export function assertCloudCanHonorAccountServersFilters(args: {
|
|
|
78
78
|
/** Fail closed when anonymous HTTP admission would expose account connectors. */
|
|
79
79
|
export function assertAccountConnectionsNotAnonymous(args: {
|
|
80
80
|
allowAnonymous?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Opt in when every admitted caller is already trusted (e.g. behind an
|
|
83
|
+
* authenticating proxy / hosted alias token). Same trust model as
|
|
84
|
+
* `--allow-anonymous-cursor-github`.
|
|
85
|
+
*/
|
|
86
|
+
allowAnonymousCursorAccountMcp?: boolean;
|
|
81
87
|
hasAccountConnections: boolean;
|
|
82
88
|
}): void {
|
|
83
|
-
if (args.allowAnonymous
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
if (args.allowAnonymous !== true || !args.hasAccountConnections) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (args.allowAnonymousCursorAccountMcp === true) {
|
|
93
|
+
return;
|
|
87
94
|
}
|
|
95
|
+
throw new Error(
|
|
96
|
+
"[agentkit] --allow-anonymous cannot be used with Cursor account MCP connections (defineConnection({ cursorAccount: true })); use --bearer-token, or opt in with --allow-anonymous-cursor-account-mcp behind a trusted boundary."
|
|
97
|
+
);
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
export function resolveMountConnections(args: {
|
package/src/internal/server.ts
CHANGED
|
@@ -307,8 +307,18 @@ export async function startServer(
|
|
|
307
307
|
const accountRefsBySlug = collectCursorAccountConnections(mounts);
|
|
308
308
|
assertAccountConnectionsNotAnonymous({
|
|
309
309
|
allowAnonymous: options.allowAnonymous,
|
|
310
|
+
allowAnonymousCursorAccountMcp: options.allowAnonymousCursorAccountMcp,
|
|
310
311
|
hasAccountConnections: accountRefsBySlug.size > 0,
|
|
311
312
|
});
|
|
313
|
+
if (
|
|
314
|
+
options.allowAnonymous === true &&
|
|
315
|
+
options.allowAnonymousCursorAccountMcp === true &&
|
|
316
|
+
accountRefsBySlug.size > 0
|
|
317
|
+
) {
|
|
318
|
+
logger(
|
|
319
|
+
"[agentkit] warning: --allow-anonymous-cursor-account-mcp — every admitted HTTP caller can drive Cursor account MCP connectors; serve this only behind a trusted boundary (e.g. an SSO proxy or hosted alias token)"
|
|
320
|
+
);
|
|
321
|
+
}
|
|
312
322
|
let accountBridge: CursorAccountMcpBridge | undefined;
|
|
313
323
|
if (accountRefsBySlug.size > 0) {
|
|
314
324
|
if (accountKey === undefined) {
|
|
@@ -914,8 +924,12 @@ function mountAgentRoutes(
|
|
|
914
924
|
(relaxSessionReadAuthBeyondLoopback ||
|
|
915
925
|
(typeof ctx.requestIp === "string" &&
|
|
916
926
|
isLoopbackAddress(ctx.requestIp)));
|
|
927
|
+
const listed = await engine.listSessionsWithSource(ctx.auth, {
|
|
928
|
+
includeAll,
|
|
929
|
+
});
|
|
917
930
|
return jsonResponse({
|
|
918
|
-
sessions:
|
|
931
|
+
sessions: listed.sessions,
|
|
932
|
+
source: listed.source,
|
|
919
933
|
});
|
|
920
934
|
},
|
|
921
935
|
});
|