@cursor/july 0.1.43 → 0.1.46
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 +5 -3
- package/README.md +3 -1
- package/dist/channels/github/api.d.ts +25 -0
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +48 -0
- package/dist/channels/github/cursor-account.d.ts +7 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +18 -0
- package/dist/channels/github/defaults.d.ts +1 -1
- package/dist/channels/github/defaults.js +1 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +15 -7
- package/dist/channels/github/progress.d.ts +6 -5
- package/dist/channels/github/progress.d.ts.map +1 -1
- package/dist/channels/github/progress.js +58 -16
- package/dist/channels/github/state.d.ts +11 -0
- package/dist/channels/github/state.d.ts.map +1 -1
- package/dist/channels/github/state.js +1 -1
- package/dist/channels/github/types.d.ts +11 -3
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels.d.ts +17 -0
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +31 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.DsTLo2j1.js → app.C2NFTb-h.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.B-FxvuoI.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DoVfutcV.js → VPLocalSearchBox.o1_FJGip.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DtuVtKkc.js → theme.BcGduE7g.js} +2 -2
- package/dist/docs/assets/{guides_github.md.DSg0j9qj.js → guides_github.md.B8QKtIpw.js} +7 -6
- package/dist/docs/assets/{guides_github.md.DSg0j9qj.lean.js → guides_github.md.B8QKtIpw.lean.js} +1 -1
- package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.js +27 -0
- package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.lean.js +1 -0
- package/dist/docs/assets/index.md.COeoSkcF.js +5 -0
- package/dist/docs/assets/index.md.COeoSkcF.lean.js +1 -0
- package/dist/docs/assets/{quickstart.md.rIbfahJM.js → quickstart.md.DZxBu44y.js} +18 -49
- package/dist/docs/assets/quickstart.md.DZxBu44y.lean.js +1 -0
- package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.js → reference_agent-config.md.DHkkvcEZ.js} +9 -4
- package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.lean.js → reference_agent-config.md.DHkkvcEZ.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.DqEqQpGn.js → reference_cli.md.BDmjPPAm.js} +2 -2
- package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.js → reference_hooks.md.DJE5DXcT.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.js → reference_tools.md.kJfJAP2m.js} +1 -1
- package/dist/docs/assets/{storage.md.DcGSnIxX.js → storage.md.CJYd5YmN.js} +2 -2
- package/dist/docs/assets/{storage.md.DcGSnIxX.lean.js → storage.md.CJYd5YmN.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 +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- 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 +3 -3
- 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 +3 -3
- 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 +10 -9
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +12 -5
- 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 +8 -28
- package/dist/docs/quickstart.html +24 -55
- package/dist/docs/reference/agent-config.html +11 -6
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +6 -6
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +5 -5
- 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/prompt.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 +5 -5
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +5 -5
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/auth.d.ts +4 -0
- package/dist/internal/auth.d.ts.map +1 -1
- package/dist/internal/auth.js +7 -0
- package/dist/internal/cli-ax.js +2 -2
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +38 -7
- package/dist/internal/deploy-client.d.ts +8 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +5 -2
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +46 -9
- package/dist/internal/durable-session-store.d.ts +35 -23
- package/dist/internal/durable-session-store.d.ts.map +1 -1
- package/dist/internal/durable-session-store.js +71 -26
- package/dist/internal/host-otel.d.ts +25 -0
- package/dist/internal/host-otel.d.ts.map +1 -0
- package/dist/internal/host-otel.js +162 -0
- package/dist/internal/init-project.js +10 -10
- package/dist/internal/ledger-serving.d.ts +135 -67
- package/dist/internal/ledger-serving.d.ts.map +1 -1
- package/dist/internal/ledger-serving.js +326 -107
- package/dist/internal/otel/collector.d.ts +5 -0
- package/dist/internal/otel/collector.d.ts.map +1 -1
- package/dist/internal/otel/collector.js +68 -27
- package/dist/internal/sdk-runner.d.ts +1 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +1 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +3 -2
- package/dist/internal/session-engine.d.ts +23 -18
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +83 -32
- package/dist/kv/memory-kv.d.ts +8 -0
- package/dist/kv/memory-kv.d.ts.map +1 -1
- package/dist/kv/memory-kv.js +8 -0
- package/dist/ledger/janitor.d.ts +60 -0
- package/dist/ledger/janitor.d.ts.map +1 -0
- package/dist/ledger/janitor.js +137 -0
- package/dist/ledger/session-ledger.d.ts +132 -43
- package/dist/ledger/session-ledger.d.ts.map +1 -1
- package/dist/ledger/session-ledger.js +282 -50
- package/dist/ledger.d.ts +9 -8
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +7 -7
- package/dist/playground/assets/{index-D-DIYQzw.js → index-6wW6SAc4.js} +19 -19
- package/dist/playground/assets/index-CSRYDAFw.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +5 -0
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +6 -0
- package/dist/types.d.ts +100 -10
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +33 -106
- package/docs/guides/github.md +23 -18
- package/docs/guides/opentelemetry.md +30 -0
- package/docs/quickstart.md +89 -227
- package/docs/reference/agent-config.md +47 -0
- package/docs/reference/cli.md +2 -2
- package/docs/reference/hooks.md +1 -1
- package/docs/reference/tools.md +5 -4
- package/docs/storage.md +26 -9
- package/package.json +2 -2
- package/skills/otel/SKILL.md +20 -2
- package/src/channels/github/api.ts +122 -0
- package/src/channels/github/cursor-account.ts +23 -0
- package/src/channels/github/defaults.ts +1 -1
- package/src/channels/github/github-channel.ts +14 -1
- package/src/channels/github/progress.ts +84 -19
- package/src/channels/github/state.ts +14 -0
- package/src/channels/github/types.ts +7 -3
- package/src/channels.ts +38 -0
- package/src/index.ts +2 -0
- package/src/internal/auth.ts +10 -0
- package/src/internal/cli-ax.ts +2 -2
- package/src/internal/cli-deploy.ts +58 -3
- package/src/internal/deploy-client.ts +11 -0
- package/src/internal/discovery.ts +49 -0
- package/src/internal/durable-session-store.ts +66 -27
- package/src/internal/host-otel.ts +252 -0
- package/src/internal/init-project.ts +10 -10
- package/src/internal/ledger-serving.ts +439 -148
- package/src/internal/otel/collector.ts +135 -50
- package/src/internal/sdk-runner.ts +6 -0
- package/src/internal/server.ts +7 -2
- package/src/internal/session-engine.ts +127 -39
- package/src/kv/memory-kv.ts +8 -0
- package/src/ledger/janitor.ts +193 -0
- package/src/ledger/session-ledger.ts +372 -68
- package/src/ledger.ts +21 -6
- package/src/storage-backends/cursor-hosted.ts +17 -2
- package/src/types.ts +108 -10
- package/dist/docs/assets/chunks/@localSearchIndexroot.BZx4pmZL.js +0 -1
- package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.js +0 -20
- package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.lean.js +0 -1
- package/dist/docs/assets/index.md.CMc5FP7E.js +0 -25
- package/dist/docs/assets/index.md.CMc5FP7E.lean.js +0 -1
- package/dist/docs/assets/quickstart.md.rIbfahJM.lean.js +0 -1
- package/dist/playground/assets/index-DQTH622f.css +0 -1
- /package/dist/docs/assets/{reference_cli.md.DqEqQpGn.lean.js → reference_cli.md.BDmjPPAm.lean.js} +0 -0
- /package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.lean.js → reference_hooks.md.DJE5DXcT.lean.js} +0 -0
- /package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.lean.js → reference_tools.md.kJfJAP2m.lean.js} +0 -0
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { basename, resolve } from "node:path";
|
|
13
|
+
import { isPublicEndpointPolicy } from "../channels.js";
|
|
13
14
|
import {
|
|
14
15
|
type AgentHostingOptions,
|
|
15
16
|
isCursorAccountTransport,
|
|
@@ -633,7 +634,8 @@ async function deployOne(
|
|
|
633
634
|
// truth for egress domains and expected secret names; flags override or
|
|
634
635
|
// augment it. Directories without an agent project (static placeholder
|
|
635
636
|
// deploys) simply have no manifest. The same load supplies the
|
|
636
|
-
// cursorAccount connection refs for the post-deploy connector auth
|
|
637
|
+
// cursorAccount connection refs for the post-deploy connector auth and
|
|
638
|
+
// the channels declared public via `publicEndpoint()`.
|
|
637
639
|
const manifest = isAgent ? await readDeployManifest(dir) : undefined;
|
|
638
640
|
const hosting = manifest?.hosting;
|
|
639
641
|
const egressAllowedDomains = [
|
|
@@ -642,6 +644,21 @@ async function deployOne(
|
|
|
642
644
|
...(options.allowDomains ?? []),
|
|
643
645
|
]),
|
|
644
646
|
];
|
|
647
|
+
const publicRoutePaths = (manifest?.publicChannelIds ?? []).map(
|
|
648
|
+
(channelId) => `/v1/channels/${channelId}`
|
|
649
|
+
);
|
|
650
|
+
if (!ctx.json && manifest?.httpChannelPublicEndpoint === true) {
|
|
651
|
+
ctx.err(
|
|
652
|
+
`warning: publicEndpoint() on the built-in HTTP channel has no effect on hosting — only custom channels (/v1/channels/…) can be public; the session API always requires the alias token.\n`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
if (!ctx.json && publicRoutePaths.length > 0) {
|
|
656
|
+
// Security-relevant surface change: say exactly what will be reachable
|
|
657
|
+
// without the alias token before the deploy is accepted.
|
|
658
|
+
ctx.err(
|
|
659
|
+
` public: ${publicRoutePaths.join(", ")} (served WITHOUT the alias token — these channels authenticate requests themselves)\n`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
645
662
|
|
|
646
663
|
// Today's only source shape: the control plane builds from a git ref (or
|
|
647
664
|
// serves the static placeholder). See DeploySource for the artifact seam.
|
|
@@ -655,6 +672,8 @@ async function deployOne(
|
|
|
655
672
|
? {}
|
|
656
673
|
: { cursorEventRepos: options.cursorEventsRepos }),
|
|
657
674
|
...(egressAllowedDomains.length === 0 ? {} : { egressAllowedDomains }),
|
|
675
|
+
publicRoutePaths:
|
|
676
|
+
publicRoutePaths.length === 0 ? undefined : publicRoutePaths,
|
|
658
677
|
};
|
|
659
678
|
|
|
660
679
|
let accepted: DeployAccepted;
|
|
@@ -716,6 +735,7 @@ async function deployOne(
|
|
|
716
735
|
ctx.err(`Deployment ${slug} ended ${detail.status}${errorDetail}\n`);
|
|
717
736
|
return 1;
|
|
718
737
|
}
|
|
738
|
+
const deployedPublicRoutes = detail.publicRoutePaths ?? [];
|
|
719
739
|
ctx.out(
|
|
720
740
|
`Deployed ${slug} (team ${ctx.teamId})\n` +
|
|
721
741
|
` status: running\n` +
|
|
@@ -723,7 +743,12 @@ async function deployOne(
|
|
|
723
743
|
` kind: ${detail.deploymentKind}\n` +
|
|
724
744
|
` alias: ${aliasUrl}\n` +
|
|
725
745
|
` playground: ${aliasUrl}/playground (sign in with the alias token)\n` +
|
|
726
|
-
` open: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n`
|
|
746
|
+
` open: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n` +
|
|
747
|
+
// Paste-ready webhook URLs: these subtrees skip the alias token, so
|
|
748
|
+
// the channel handler must verify deliveries (webhook signatures).
|
|
749
|
+
deployedPublicRoutes
|
|
750
|
+
.map((path) => ` public: ${aliasUrl}${path} (no alias token)\n`)
|
|
751
|
+
.join("")
|
|
727
752
|
);
|
|
728
753
|
|
|
729
754
|
await runPostDeployConnectorAuth(ctx, options, {
|
|
@@ -828,11 +853,23 @@ interface DeployManifest {
|
|
|
828
853
|
name: string;
|
|
829
854
|
servers: string[] | undefined;
|
|
830
855
|
}>;
|
|
856
|
+
/**
|
|
857
|
+
* Channels whose authored auth chain contains `publicEndpoint()`: their
|
|
858
|
+
* `/v1/channels/<id>` subtree is served through the stable alias WITHOUT
|
|
859
|
+
* `X-Agent-Alias-Token` (the channel authenticates deliveries itself).
|
|
860
|
+
*/
|
|
861
|
+
publicChannelIds: string[];
|
|
862
|
+
/**
|
|
863
|
+
* `publicEndpoint()` on the built-in HTTP channel — a no-op on hosting
|
|
864
|
+
* (only `/v1/channels/…` subtrees can be public); surfaced as a warning.
|
|
865
|
+
*/
|
|
866
|
+
httpChannelPublicEndpoint: boolean;
|
|
831
867
|
}
|
|
832
868
|
|
|
833
869
|
/**
|
|
834
870
|
* Read the deploy-relevant bits of the project manifest at `dir` in one
|
|
835
|
-
* load: the `hosting` block
|
|
871
|
+
* load: the `hosting` block, the agent's cursorAccount connections, and the
|
|
872
|
+
* channels declared public via `publicEndpoint()`.
|
|
836
873
|
*/
|
|
837
874
|
async function readDeployManifest(dir: string): Promise<DeployManifest> {
|
|
838
875
|
const project = await loadAgentProject(dir);
|
|
@@ -843,6 +880,14 @@ async function readDeployManifest(dir: string): Promise<DeployManifest> {
|
|
|
843
880
|
? [{ name: connection.name, servers: connection.transport.servers }]
|
|
844
881
|
: []
|
|
845
882
|
),
|
|
883
|
+
publicChannelIds: project.channels
|
|
884
|
+
.filter((channel) =>
|
|
885
|
+
(channel.definition.auth ?? []).some(isPublicEndpointPolicy)
|
|
886
|
+
)
|
|
887
|
+
.map((channel) => channel.id),
|
|
888
|
+
httpChannelPublicEndpoint: (project.httpChannel.auth ?? []).some(
|
|
889
|
+
isPublicEndpointPolicy
|
|
890
|
+
),
|
|
846
891
|
};
|
|
847
892
|
}
|
|
848
893
|
|
|
@@ -985,6 +1030,16 @@ export async function cmdDeploymentStatus(
|
|
|
985
1030
|
) {
|
|
986
1031
|
lines.push(` egress: ${detail.egressAllowedDomains.join(", ")}`);
|
|
987
1032
|
}
|
|
1033
|
+
if (
|
|
1034
|
+
detail.publicRoutePaths !== undefined &&
|
|
1035
|
+
detail.publicRoutePaths.length > 0
|
|
1036
|
+
) {
|
|
1037
|
+
// Served through the alias WITHOUT X-Agent-Alias-Token (the channel
|
|
1038
|
+
// authenticates deliveries itself).
|
|
1039
|
+
for (const path of detail.publicRoutePaths) {
|
|
1040
|
+
lines.push(` public: ${aliasUrl}${path} (no alias token)`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
988
1043
|
if (detail.secretNames !== undefined && detail.secretNames.length > 0) {
|
|
989
1044
|
lines.push(` secrets: ${detail.secretNames.join(", ")}`);
|
|
990
1045
|
}
|
|
@@ -69,6 +69,12 @@ export type DeploySource = {
|
|
|
69
69
|
* deploys — the backend 400s when set on a static deploy.
|
|
70
70
|
*/
|
|
71
71
|
egressAllowedDomains?: string[];
|
|
72
|
+
/**
|
|
73
|
+
* `/v1/channels/…` subtrees the stable alias serves WITHOUT
|
|
74
|
+
* `X-Agent-Alias-Token` (channels authored with `publicEndpoint()`).
|
|
75
|
+
* Only valid on repo-backed deploys.
|
|
76
|
+
*/
|
|
77
|
+
publicRoutePaths?: string[];
|
|
72
78
|
};
|
|
73
79
|
|
|
74
80
|
const SLUG_PATTERN = /^[a-z0-9_-]{1,64}$/;
|
|
@@ -158,6 +164,8 @@ export interface Deployment {
|
|
|
158
164
|
cursorEventRepos?: string[];
|
|
159
165
|
/** Normalized egress allowlist for the engine pod. */
|
|
160
166
|
egressAllowedDomains?: string[];
|
|
167
|
+
/** Channel subtrees the alias serves without `X-Agent-Alias-Token`. */
|
|
168
|
+
publicRoutePaths?: string[];
|
|
161
169
|
/** Service-account id (`sa_…`); null on legacy rows. */
|
|
162
170
|
serviceAccountId?: string | number | null;
|
|
163
171
|
/**
|
|
@@ -297,6 +305,7 @@ const deploymentSchema = z.object({
|
|
|
297
305
|
lastReconciledAt: z.string().nullable().optional(),
|
|
298
306
|
cursorEventRepos: z.array(z.string()).optional(),
|
|
299
307
|
egressAllowedDomains: z.array(z.string()).optional(),
|
|
308
|
+
publicRoutePaths: z.array(z.string()).optional(),
|
|
300
309
|
// String ids (`sa_…`) in prod; older mocks/fixtures used numbers. Null on
|
|
301
310
|
// legacy rows that predate service-account-backed deploys.
|
|
302
311
|
serviceAccountId: z.union([z.string(), z.number()]).nullable().optional(),
|
|
@@ -441,6 +450,8 @@ export class AgentServeDeployClient {
|
|
|
441
450
|
...(args.source.egressAllowedDomains === undefined
|
|
442
451
|
? {}
|
|
443
452
|
: { egressAllowedDomains: args.source.egressAllowedDomains }),
|
|
453
|
+
// JSON.stringify drops undefined keys.
|
|
454
|
+
publicRoutePaths: args.source.publicRoutePaths,
|
|
444
455
|
};
|
|
445
456
|
const raw = await this.request({
|
|
446
457
|
method: "POST",
|
|
@@ -376,6 +376,14 @@ async function loadAgent(
|
|
|
376
376
|
message: "builtinTools is not supported on subagents; ignoring.",
|
|
377
377
|
});
|
|
378
378
|
}
|
|
379
|
+
if (config?.tools !== undefined) {
|
|
380
|
+
ctx.diagnostics.push({
|
|
381
|
+
severity: "warning",
|
|
382
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
383
|
+
message:
|
|
384
|
+
"tools is not supported on subagents (they keep the harness task-tool toolset); ignoring.",
|
|
385
|
+
});
|
|
386
|
+
}
|
|
379
387
|
const artifactsPath = await findModuleFile(agentDir, "artifacts");
|
|
380
388
|
if (artifactsPath !== undefined) {
|
|
381
389
|
ctx.diagnostics.push({
|
|
@@ -415,6 +423,46 @@ async function loadAgent(
|
|
|
415
423
|
const localCwd = resolveAgentCwd(ctx, config?.local?.cwd);
|
|
416
424
|
const localWorkspaceDir = resolveAgentCwd(ctx, config?.local?.workspaceDir);
|
|
417
425
|
const localSandbox = config?.local?.sandbox === true;
|
|
426
|
+
// Built-in tool allowlist (top-level `tools`). `[]` is meaningful (no
|
|
427
|
+
// native built-in tools), so presence is keyed on the field, not on
|
|
428
|
+
// emptiness. "mcp" is always granted alongside a configured allowlist:
|
|
429
|
+
// it is transport, not capability — this kit's own server tools ride the
|
|
430
|
+
// SDK custom-tool MCP path, so omitting it silently breaks
|
|
431
|
+
// `agent/tools/`, and what MCP actually exposes stays controlled by the
|
|
432
|
+
// second layer (authored tools, mcp-connections, the harness cwd's
|
|
433
|
+
// ambient config).
|
|
434
|
+
const authoredAllowlist = config?.tools;
|
|
435
|
+
const allowedTools =
|
|
436
|
+
authoredAllowlist === undefined || authoredAllowlist.includes("mcp")
|
|
437
|
+
? authoredAllowlist
|
|
438
|
+
: [...authoredAllowlist, "mcp" as const];
|
|
439
|
+
// The allowlist states runtime-agnostic intent, but the SDK cannot
|
|
440
|
+
// enforce tool restrictions on cloud agents yet — a cloud-runtime agent
|
|
441
|
+
// that declares one must fail at serve start, never run unrestricted.
|
|
442
|
+
// (Hybrid per-send cloud sessions fail closed in the session engine.)
|
|
443
|
+
if (allowedTools !== undefined && runtime === "cloud") {
|
|
444
|
+
ctx.diagnostics.push({
|
|
445
|
+
severity: "error",
|
|
446
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
447
|
+
message:
|
|
448
|
+
'tools cannot be enforced on runtime: "cloud" yet (the Cursor SDK rejects tool restrictions on cloud agents). Remove `tools` or run the agent on the local runtime.',
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
// `execution: "agent"` tool scripts run through the shell tool. Shell is
|
|
452
|
+
// a real capability, so it is never injected — but an allowlist that
|
|
453
|
+
// strands authored scripts deserves a loud diagnostic, not silence.
|
|
454
|
+
if (
|
|
455
|
+
allowedTools !== undefined &&
|
|
456
|
+
!allowedTools.includes("shell") &&
|
|
457
|
+
tools.some((tool) => tool.execution === "agent")
|
|
458
|
+
) {
|
|
459
|
+
ctx.diagnostics.push({
|
|
460
|
+
severity: "warning",
|
|
461
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
462
|
+
message:
|
|
463
|
+
'`tools` omits "shell" but this agent has execution: "agent" tools, which run as shell scripts; the model will not be able to run them.',
|
|
464
|
+
});
|
|
465
|
+
}
|
|
418
466
|
if (localCwd !== undefined && localWorkspaceDir !== undefined) {
|
|
419
467
|
ctx.diagnostics.push({
|
|
420
468
|
severity: "warning",
|
|
@@ -455,6 +503,7 @@ async function loadAgent(
|
|
|
455
503
|
runtime,
|
|
456
504
|
cloud,
|
|
457
505
|
local,
|
|
506
|
+
...(allowedTools === undefined ? {} : { allowedTools }),
|
|
458
507
|
hosting,
|
|
459
508
|
concurrency,
|
|
460
509
|
serving,
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Two APIs, one backend: `defineStorage` is the **storage API** (agent
|
|
5
5
|
* state — session records, event chunks, author KV), and the session
|
|
6
|
-
* ledger is the **durable session API** (session identity,
|
|
7
|
-
* fenced commits). The ledger consumes {@link SessionLedgerKv} —
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* ledger is the **durable session API** (session identity, mailboxes,
|
|
7
|
+
* leases, fenced commits). The ledger consumes {@link SessionLedgerKv} —
|
|
8
|
+
* the full CAS-capable KV surface, since the inbox reads/sweeps mailbox
|
|
9
|
+
* payload blobs (`get`/`put`/`delete`) and scans by prefix (`listKeys`)
|
|
10
|
+
* on top of the CAS trio; this module adapts a storage backend's methods
|
|
11
|
+
* into that interface, so a deployment's one authored backend serves
|
|
12
|
+
* both APIs by default.
|
|
11
13
|
*
|
|
12
14
|
* Calls go straight at the config's methods — NOT through the
|
|
13
15
|
* StorageCoordinator: the coordinator's queue is bounded, serialized, and
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
type StorageConfig,
|
|
23
25
|
type StorageContext,
|
|
24
26
|
} from "../storage.js";
|
|
27
|
+
import type { JsonValue } from "../types.js";
|
|
25
28
|
|
|
26
29
|
export interface StorageCasKvArgs {
|
|
27
30
|
agentName: string;
|
|
@@ -29,10 +32,29 @@ export interface StorageCasKvArgs {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
35
|
+
* The one place the ledger's physical namespace is minted: the per-agent
|
|
36
|
+
* prefix for ledger state, following the {@link storageKeys} convention
|
|
37
|
+
* (`<root>/<agent>/<family>/…`) so a store shared by several ledger-mode
|
|
38
|
+
* agents (one fileKv dir, one hosted deployment) never mixes their
|
|
39
|
+
* sessions, leases, or mailboxes — and so each agent's janitor
|
|
40
|
+
* prefix-scan only ever sees its own ledgers. The extra
|
|
41
|
+
* `durable-session` segment keeps the ledger's `session/` family out of
|
|
42
|
+
* `storageKeys.sessionPrefix` scans (storage restore lists
|
|
43
|
+
* `<root>/<agent>/session/`). The ledger itself never sees this string:
|
|
44
|
+
* it speaks bare `session/…` / `ledger/…` / `mailbox/…` keys against a
|
|
45
|
+
* handle that {@link namespacedKv} has already scoped.
|
|
46
|
+
*/
|
|
47
|
+
export function ledgerKeyPrefix(agentName: string): string {
|
|
48
|
+
return `${STORAGE_KEY_ROOT}/${keySegment(agentName)}/durable-session/`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Adapt a storage backend into the ledger's KV interface. Throws when
|
|
53
|
+
* the backend does not implement the `cas` group, or omits `get` /
|
|
54
|
+
* `delete` (optional for resident-only sinks, but the ledger reads and
|
|
55
|
+
* sweeps mailbox payload blobs through the same store) — the caller
|
|
56
|
+
* turns that into the fail-fast startup error for `serving: "ledger"`.
|
|
57
|
+
* Every call carries a `reason: "coordination"` {@link StorageContext}.
|
|
36
58
|
*/
|
|
37
59
|
export function storageCasKv(
|
|
38
60
|
config: StorageConfig,
|
|
@@ -49,7 +71,23 @@ export function storageCasKv(
|
|
|
49
71
|
"storageCasKv: the storage backend does not implement the cas group (conditional writes)"
|
|
50
72
|
);
|
|
51
73
|
}
|
|
74
|
+
const get = config.get;
|
|
75
|
+
const remove = config.delete;
|
|
76
|
+
if (get === undefined || remove === undefined) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"storageCasKv: the storage backend must implement get and delete (the session ledger reads and sweeps payload blobs through the same store)"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
52
81
|
return {
|
|
82
|
+
async get(key): Promise<JsonValue | undefined> {
|
|
83
|
+
return (await get(key, ctx)) ?? undefined;
|
|
84
|
+
},
|
|
85
|
+
async put(key, value): Promise<void> {
|
|
86
|
+
await config.put(key, value, ctx);
|
|
87
|
+
},
|
|
88
|
+
async delete(key): Promise<void> {
|
|
89
|
+
await remove(key, ctx);
|
|
90
|
+
},
|
|
53
91
|
async getWithVersion(key) {
|
|
54
92
|
return await cas.getWithVersion(key, ctx);
|
|
55
93
|
},
|
|
@@ -59,37 +97,38 @@ export function storageCasKv(
|
|
|
59
97
|
async putIfVersion(key, value, expectedVersion) {
|
|
60
98
|
return await cas.putIfVersion(key, value, expectedVersion, ctx);
|
|
61
99
|
},
|
|
100
|
+
async listKeys(prefix) {
|
|
101
|
+
return await cas.listKeys(prefix, ctx);
|
|
102
|
+
},
|
|
62
103
|
};
|
|
63
104
|
}
|
|
64
105
|
|
|
65
106
|
/**
|
|
66
|
-
*
|
|
67
|
-
* `
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Scope a raw store handle under a namespace, prepending it to every
|
|
79
|
-
* key on the way in. Applied exactly once, where the engine constructs
|
|
80
|
-
* the ledger's store handle — the same conceptual boundary where
|
|
81
|
-
* `host.kv` gets its agent scoping. Internal: not part of the public kv
|
|
82
|
-
* interface, and callers (including the explicit `durableSessionStore`
|
|
83
|
-
* override) always supply the raw store — they never write prefixes.
|
|
107
|
+
* Scope a raw store handle under a namespace: every key going in gets it
|
|
108
|
+
* prepended, and `listKeys` results come back with it **stripped**, so a
|
|
109
|
+
* caller that lists and then reads/deletes the returned keys stays fully
|
|
110
|
+
* prefix-free (the janitor scan and the orphan sweep do exactly that).
|
|
111
|
+
* Applied exactly once, where the engine constructs the ledger's store
|
|
112
|
+
* handle — the same conceptual boundary where `host.kv` gets its agent
|
|
113
|
+
* scoping. Internal: not part of the public kv interface, and callers
|
|
114
|
+
* (including the explicit `durableSessionStore` override) always supply
|
|
115
|
+
* the raw store — they never write prefixes.
|
|
84
116
|
*/
|
|
85
117
|
export function namespacedKv(
|
|
86
118
|
store: SessionLedgerKv,
|
|
87
119
|
namespace: string
|
|
88
120
|
): SessionLedgerKv {
|
|
89
121
|
return {
|
|
122
|
+
get: (key) => store.get(namespace + key),
|
|
123
|
+
put: (key, value) => store.put(namespace + key, value),
|
|
124
|
+
delete: (key) => store.delete(namespace + key),
|
|
90
125
|
getWithVersion: (key) => store.getWithVersion(namespace + key),
|
|
91
126
|
putIfAbsent: (key, value) => store.putIfAbsent(namespace + key, value),
|
|
92
127
|
putIfVersion: (key, value, expectedVersion) =>
|
|
93
128
|
store.putIfVersion(namespace + key, value, expectedVersion),
|
|
129
|
+
listKeys: async (prefix) => {
|
|
130
|
+
const keys = await store.listKeys(namespace + prefix);
|
|
131
|
+
return keys.map((key) => key.slice(namespace.length));
|
|
132
|
+
},
|
|
94
133
|
};
|
|
95
134
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author-facing OpenTelemetry surface for {@link HostContext.otel}.
|
|
3
|
+
*
|
|
4
|
+
* Always present on `ctx.host`. Counters and histograms no-op when no meter
|
|
5
|
+
* is running. `setAttributes` still tags the open session when a collector
|
|
6
|
+
* is attached. `forSession` is engine-only so a tool cannot retarget another
|
|
7
|
+
* conversation's tags.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Counter, Histogram, Meter } from "@opentelemetry/api";
|
|
11
|
+
import type { HostOtelApi, HostOtelAttributes } from "../types.js";
|
|
12
|
+
import type { AttrValue } from "./otel/attrs.js";
|
|
13
|
+
import { attrs } from "./otel/attrs.js";
|
|
14
|
+
import type { OtelCollector } from "./otel/collector.js";
|
|
15
|
+
|
|
16
|
+
const METRIC_NAME = /^[A-Za-z][A-Za-z0-9_./-]*$/;
|
|
17
|
+
|
|
18
|
+
const RESERVED_METRICS = new Set([
|
|
19
|
+
"cursor.token.usage",
|
|
20
|
+
"cursor.tool.calls",
|
|
21
|
+
"cursor.cost.usage",
|
|
22
|
+
"agent_sdk.session.count",
|
|
23
|
+
"agent_sdk.turn.count",
|
|
24
|
+
"agent_sdk.subagent.count",
|
|
25
|
+
"agent_sdk.http.duration",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const RESERVED_ATTRIBUTES = new Set([
|
|
29
|
+
"cursor.conversation.id",
|
|
30
|
+
"agent_sdk.agent",
|
|
31
|
+
"agent_sdk.turn_id",
|
|
32
|
+
"agent_sdk.framework",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export interface CreateHostOtelApiOptions {
|
|
36
|
+
agentName: string;
|
|
37
|
+
meter?: Meter;
|
|
38
|
+
collector?: OtelCollector;
|
|
39
|
+
/** Session bound for join tags; prefer {@link HostOtelSessionApi.forSession}. */
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Engine-only binding. Not part of the public {@link HostOtelApi}. */
|
|
44
|
+
export interface HostOtelSessionApi extends HostOtelApi {
|
|
45
|
+
forSession(sessionId: string): HostOtelSessionApi;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface CachedInstrument {
|
|
49
|
+
kind: "counter" | "histogram";
|
|
50
|
+
instrument: Counter | Histogram;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface SharedState {
|
|
54
|
+
agentName: string;
|
|
55
|
+
meter: Meter | undefined;
|
|
56
|
+
collector: OtelCollector | undefined;
|
|
57
|
+
instruments: Map<string, CachedInstrument>;
|
|
58
|
+
/** Used when no collector is attached so bound instances still share tags. */
|
|
59
|
+
sessionTags: Map<string, Record<string, AttrValue>>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface BoundState {
|
|
63
|
+
shared: SharedState;
|
|
64
|
+
sessionId: string | undefined;
|
|
65
|
+
localTags: Record<string, AttrValue>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Build the host OpenTelemetry API for one agent process. */
|
|
69
|
+
export function createHostOtelApi(
|
|
70
|
+
options: CreateHostOtelApiOptions
|
|
71
|
+
): HostOtelSessionApi {
|
|
72
|
+
return createBoundHostOtelApi({
|
|
73
|
+
shared: {
|
|
74
|
+
agentName: options.agentName,
|
|
75
|
+
meter: options.meter,
|
|
76
|
+
collector: options.collector,
|
|
77
|
+
instruments: new Map(),
|
|
78
|
+
sessionTags: new Map(),
|
|
79
|
+
},
|
|
80
|
+
sessionId: options.sessionId,
|
|
81
|
+
localTags: {},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function createBoundHostOtelApi(state: BoundState): HostOtelSessionApi {
|
|
86
|
+
const api: HostOtelSessionApi = {
|
|
87
|
+
get enabled(): boolean {
|
|
88
|
+
return state.shared.meter !== undefined;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
setAttributes(attributes) {
|
|
92
|
+
const cleaned = validateAttributes(attributes);
|
|
93
|
+
if (state.sessionId === undefined) {
|
|
94
|
+
Object.assign(state.localTags, cleaned);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
mergeSessionTags(state, state.sessionId, cleaned);
|
|
98
|
+
state.shared.collector?.setSessionAttributes(state.sessionId, cleaned);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
increment(
|
|
102
|
+
name: string,
|
|
103
|
+
valueOrAttributes?: number | HostOtelAttributes,
|
|
104
|
+
maybeAttributes?: HostOtelAttributes
|
|
105
|
+
) {
|
|
106
|
+
const value =
|
|
107
|
+
typeof valueOrAttributes === "number" ? valueOrAttributes : 1;
|
|
108
|
+
const attributes =
|
|
109
|
+
typeof valueOrAttributes === "number"
|
|
110
|
+
? maybeAttributes
|
|
111
|
+
: valueOrAttributes;
|
|
112
|
+
counter(state.shared, name)?.add(
|
|
113
|
+
value,
|
|
114
|
+
emitAttributes(state, attributes)
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
record(name, value, attributes) {
|
|
119
|
+
histogram(state.shared, name)?.record(
|
|
120
|
+
value,
|
|
121
|
+
emitAttributes(state, attributes)
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
forSession(sessionId) {
|
|
126
|
+
const trimmed = sessionId.trim();
|
|
127
|
+
if (trimmed === "") {
|
|
128
|
+
throw new Error("host.otel.forSession requires a non-empty session id");
|
|
129
|
+
}
|
|
130
|
+
return createBoundHostOtelApi({
|
|
131
|
+
shared: state.shared,
|
|
132
|
+
sessionId: trimmed,
|
|
133
|
+
localTags: {},
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
return api;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function mergeSessionTags(
|
|
141
|
+
state: BoundState,
|
|
142
|
+
sessionId: string,
|
|
143
|
+
incoming: Record<string, AttrValue>
|
|
144
|
+
): void {
|
|
145
|
+
const bag = state.shared.sessionTags.get(sessionId) ?? {};
|
|
146
|
+
Object.assign(bag, incoming);
|
|
147
|
+
state.shared.sessionTags.set(sessionId, bag);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function emitAttributes(
|
|
151
|
+
state: BoundState,
|
|
152
|
+
callSite: HostOtelAttributes | undefined
|
|
153
|
+
): Record<string, AttrValue> {
|
|
154
|
+
return attrs({
|
|
155
|
+
...sessionBag(state),
|
|
156
|
+
...validateAttributes(callSite),
|
|
157
|
+
"agent_sdk.agent": state.shared.agentName,
|
|
158
|
+
...(state.sessionId === undefined
|
|
159
|
+
? {}
|
|
160
|
+
: { "cursor.conversation.id": state.sessionId }),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function sessionBag(state: BoundState): Record<string, AttrValue> {
|
|
165
|
+
if (state.sessionId === undefined) {
|
|
166
|
+
return state.localTags;
|
|
167
|
+
}
|
|
168
|
+
const fromCollector = state.shared.collector?.getSessionAttributes(
|
|
169
|
+
state.sessionId
|
|
170
|
+
);
|
|
171
|
+
const fromShared = state.shared.sessionTags.get(state.sessionId) ?? {};
|
|
172
|
+
return {
|
|
173
|
+
...fromShared,
|
|
174
|
+
...(fromCollector ?? {}),
|
|
175
|
+
...state.localTags,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function counter(shared: SharedState, name: string): Counter | undefined {
|
|
180
|
+
validateMetricName(name);
|
|
181
|
+
if (shared.meter === undefined) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const existing = shared.instruments.get(name);
|
|
185
|
+
if (existing !== undefined) {
|
|
186
|
+
if (existing.kind !== "counter") {
|
|
187
|
+
throw new Error(`host.otel metric "${name}" is already a histogram`);
|
|
188
|
+
}
|
|
189
|
+
return existing.instrument as Counter;
|
|
190
|
+
}
|
|
191
|
+
const instrument = shared.meter.createCounter(name);
|
|
192
|
+
shared.instruments.set(name, { kind: "counter", instrument });
|
|
193
|
+
return instrument;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function histogram(shared: SharedState, name: string): Histogram | undefined {
|
|
197
|
+
validateMetricName(name);
|
|
198
|
+
if (shared.meter === undefined) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
const existing = shared.instruments.get(name);
|
|
202
|
+
if (existing !== undefined) {
|
|
203
|
+
if (existing.kind !== "histogram") {
|
|
204
|
+
throw new Error(`host.otel metric "${name}" is already a counter`);
|
|
205
|
+
}
|
|
206
|
+
return existing.instrument as Histogram;
|
|
207
|
+
}
|
|
208
|
+
const instrument = shared.meter.createHistogram(name);
|
|
209
|
+
shared.instruments.set(name, { kind: "histogram", instrument });
|
|
210
|
+
return instrument;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function validateMetricName(name: string): void {
|
|
214
|
+
if (!METRIC_NAME.test(name)) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`host.otel metric name "${name}" must match [A-Za-z][A-Za-z0-9_./-]*`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (RESERVED_METRICS.has(name)) {
|
|
220
|
+
throw new Error(`host.otel metric name "${name}" is reserved`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function validateAttributes(
|
|
225
|
+
attributes: HostOtelAttributes | undefined
|
|
226
|
+
): Record<string, AttrValue> {
|
|
227
|
+
if (attributes === undefined) {
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
const out: Record<string, AttrValue> = {};
|
|
231
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
232
|
+
if (value === undefined) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (RESERVED_ATTRIBUTES.has(key)) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`host.otel attribute "${key}" is reserved; the runtime always sets it`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
typeof value !== "string" &&
|
|
242
|
+
typeof value !== "number" &&
|
|
243
|
+
typeof value !== "boolean"
|
|
244
|
+
) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`host.otel attribute "${key}" must be a string, number, or boolean`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
out[key] = value;
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
@@ -699,20 +699,20 @@ function initNextStepEntries(options: InitNextStepsOptions): Array<{
|
|
|
699
699
|
if (cd !== undefined) {
|
|
700
700
|
steps.push({ command: cd });
|
|
701
701
|
}
|
|
702
|
-
if (!options.cliOnPath) {
|
|
703
|
-
steps.push({
|
|
704
|
-
command: 'export PATH="$PWD/node_modules/.bin:$PATH"',
|
|
705
|
-
note: "use the project-local agent-sdk bin",
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
702
|
if (!options.signedIn) {
|
|
703
|
+
const login = options.cliOnPath
|
|
704
|
+
? `${CLI_COMMAND_NAME} login`
|
|
705
|
+
: `npx ${PACKAGE_NAME} login`;
|
|
709
706
|
steps.push({
|
|
710
|
-
command:
|
|
707
|
+
command: login,
|
|
711
708
|
note: "connect your Cursor account (powers model turns)",
|
|
712
709
|
});
|
|
713
710
|
}
|
|
711
|
+
const dev = options.cliOnPath
|
|
712
|
+
? `${CLI_COMMAND_NAME} dev`
|
|
713
|
+
: `npx ${PACKAGE_NAME} dev`;
|
|
714
714
|
steps.push({
|
|
715
|
-
command:
|
|
715
|
+
command: dev,
|
|
716
716
|
note: "start the local playground",
|
|
717
717
|
});
|
|
718
718
|
return steps;
|
|
@@ -739,9 +739,9 @@ export function printInitGuide(): void {
|
|
|
739
739
|
process.stdout.write(`${CLI_COMMAND_NAME} — coding agent setup
|
|
740
740
|
|
|
741
741
|
Scaffold a project, then open the playground:
|
|
742
|
-
npx ${PACKAGE_NAME} init ./my-agent # installs deps +
|
|
742
|
+
npx ${PACKAGE_NAME} init ./my-agent # installs deps + links agent-sdk onto PATH
|
|
743
743
|
npx ${PACKAGE_NAME} init ./my-triage --template triage # start from a template (agent-sdk templates: triage)
|
|
744
|
-
cd ./my-agent && ${
|
|
744
|
+
cd ./my-agent && agent-sdk dev # or: npx ${PACKAGE_NAME} dev
|
|
745
745
|
|
|
746
746
|
Recommended loop once the project exists (from the project directory):
|
|
747
747
|
1. Edit agent/ + tools under agent/tools/
|