@cursor/july 0.1.100 → 0.1.101
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 +3 -2
- package/dist/channels/deployments/deployments-channel.d.ts +41 -5
- package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
- package/dist/channels/deployments/deployments-channel.js +88 -8
- package/dist/channels/deployments/types.d.ts +4 -3
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/channels/origin/defaults.d.ts +43 -0
- package/dist/channels/origin/defaults.d.ts.map +1 -0
- package/dist/channels/origin/defaults.js +111 -0
- package/dist/channels/origin/index.d.ts +2 -0
- package/dist/channels/origin/index.d.ts.map +1 -1
- package/dist/channels/origin/index.js +2 -0
- package/dist/channels/origin/origin-channel.d.ts +2 -1
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +24 -3
- package/dist/channels/origin/state.d.ts +32 -0
- package/dist/channels/origin/state.d.ts.map +1 -0
- package/dist/channels/origin/state.js +23 -0
- package/dist/channels/origin/types.d.ts +23 -1
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +2 -1
- package/dist/channels/slack/code-fences.d.ts +17 -0
- package/dist/channels/slack/code-fences.d.ts.map +1 -0
- package/dist/channels/slack/code-fences.js +168 -0
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +11 -4
- package/dist/connections.d.ts +18 -2
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +18 -2
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.DZ1e0Ycq.js → app.BASO9Kih.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DbFXINq2.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.l8omwc6D.js → VPLocalSearchBox.DwkqxoE0.js} +1 -1
- package/dist/docs/assets/chunks/{theme.JVcD6gel.js → theme.CCil_13e.js} +2 -2
- package/dist/docs/assets/{reference_connections.md.BiGoBAk2.js → reference_connections.md.Di6jJAXF.js} +12 -2
- package/dist/docs/assets/{reference_connections.md.BiGoBAk2.lean.js → reference_connections.md.Di6jJAXF.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.js → reference_http-api.md.oySXBO8o.js} +1 -1
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +4 -4
- package/dist/docs/evals.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/convert-automation.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +4 -4
- package/dist/docs/llms-full.txt +32 -1
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +4 -4
- package/dist/docs/reference/connections.html +16 -6
- package/dist/docs/reference/connections.md +31 -0
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/http-api.md +1 -1
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/skills/ab.html +4 -4
- package/dist/docs/skills/create-agent.html +4 -4
- package/dist/docs/skills/debug.html +4 -4
- package/dist/docs/skills/evals.html +4 -4
- package/dist/docs/skills/framework-map.html +4 -4
- package/dist/docs/skills/github.html +4 -4
- package/dist/docs/skills/hillclimb.html +4 -4
- package/dist/docs/skills/index.html +4 -4
- package/dist/docs/skills/mcp-auth.html +4 -4
- package/dist/docs/skills/otel.html +4 -4
- package/dist/docs/skills/setup-slack.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +4 -4
- package/dist/docs/templates/agents-md.html +4 -4
- package/dist/docs/templates/code-wiki.html +4 -4
- package/dist/docs/templates/demo.html +4 -4
- package/dist/docs/templates/pr-autofixer.html +4 -4
- package/dist/docs/templates/security-help.html +4 -4
- package/dist/docs/templates/security-reviewer.html +4 -4
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/extensions/cursor-cloud-agents/extension.d.ts +55 -0
- package/dist/extensions/cursor-cloud-agents/extension.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/extension.js +64 -0
- package/dist/extensions/cursor-cloud-agents/lib/cloud.d.ts +30 -0
- package/dist/extensions/cursor-cloud-agents/lib/cloud.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/lib/cloud.js +45 -0
- package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts +25 -0
- package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/lib/fleet.js +85 -0
- package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts +15 -0
- package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/lib/repos.js +55 -0
- package/dist/extensions/cursor-cloud-agents/skills/handoff.md +27 -0
- package/dist/extensions/cursor-cloud-agents/tools/follow_up.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/follow_up.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/follow_up.js +37 -0
- package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/launch.js +83 -0
- package/dist/extensions/cursor-cloud-agents/tools/list.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/list.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/list.js +36 -0
- package/dist/extensions/cursor-cloud-agents/tools/status.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/status.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/status.js +41 -0
- package/dist/extensions.d.ts +49 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +75 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/internal/advertise-tools.d.ts +4 -1
- package/dist/internal/advertise-tools.d.ts.map +1 -1
- package/dist/internal/advertise-tools.js +20 -1
- package/dist/internal/as-of.d.ts +18 -0
- package/dist/internal/as-of.d.ts.map +1 -1
- package/dist/internal/as-of.js +131 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +64 -19
- package/dist/internal/continuation-identity.d.ts.map +1 -1
- package/dist/internal/continuation-identity.js +3 -1
- package/dist/internal/cursor/hosted-deployment-secrets.d.ts +16 -0
- package/dist/internal/cursor/hosted-deployment-secrets.d.ts.map +1 -0
- package/dist/internal/cursor/hosted-deployment-secrets.js +61 -0
- package/dist/internal/cursor-deployment-relay.d.ts +15 -1
- package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
- package/dist/internal/cursor-deployment-relay.js +46 -7
- package/dist/internal/deploy-client.d.ts +23 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +54 -12
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +415 -120
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +5 -0
- package/dist/internal/framework-file-storage.d.ts +3 -3
- package/dist/internal/framework-file-storage.d.ts.map +1 -1
- package/dist/internal/framework-file-storage.js +34 -5
- package/dist/internal/hosted-delivery.d.ts +3 -1
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +19 -32
- package/dist/internal/hosted-execution-diag.d.ts +5 -0
- package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
- package/dist/internal/hosted-execution-diag.js +5 -0
- package/dist/internal/hosted-execution-flush.d.ts +46 -0
- package/dist/internal/hosted-execution-flush.d.ts.map +1 -0
- package/dist/internal/hosted-execution-flush.js +81 -0
- package/dist/internal/mcp-endpoint.js +1 -0
- package/dist/internal/mcp-host.d.ts +2 -1
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +35 -15
- package/dist/internal/review-comments.d.ts +1 -1
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +4 -4
- package/dist/internal/server.d.ts +3 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +79 -104
- package/dist/internal/session-engine.d.ts +9 -7
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +45 -34
- package/dist/internal/session-run-log.d.ts +2 -1
- package/dist/internal/session-run-log.d.ts.map +1 -1
- package/dist/internal/session-run-log.js +2 -1
- package/dist/internal/start-log-store.d.ts +60 -0
- package/dist/internal/start-log-store.d.ts.map +1 -0
- package/dist/internal/start-log-store.js +151 -0
- package/dist/internal/tool-policy.d.ts +8 -0
- package/dist/internal/tool-policy.d.ts.map +1 -1
- package/dist/internal/tool-policy.js +19 -7
- package/dist/playground/assets/{index-DVs98vPL.js → index-BnNN1-EK.js} +45 -45
- package/dist/playground/assets/index-DBQCwFp0.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +24 -0
- package/dist/storage-protocol.d.ts.map +1 -1
- package/dist/storage-protocol.js +33 -0
- package/dist/storage.d.ts +1 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1 -1
- package/dist/types.d.ts +118 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +29 -1
- package/docs/reference/connections.md +31 -0
- package/docs/reference/http-api.md +1 -1
- package/package.json +18 -4
- package/src/bin/agent-serve.ts +11 -2
- package/src/channels/deployments/deployments-channel.ts +117 -8
- package/src/channels/deployments/types.ts +4 -3
- package/src/channels/origin/defaults.ts +143 -0
- package/src/channels/origin/index.ts +2 -0
- package/src/channels/origin/origin-channel.ts +37 -1
- package/src/channels/origin/state.ts +54 -0
- package/src/channels/origin/types.ts +24 -0
- package/src/channels/slack/api.ts +2 -1
- package/src/channels/slack/code-fences.ts +183 -0
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/live-delivery.ts +16 -13
- package/src/connections.ts +18 -1
- package/src/extensions/cursor-cloud-agents/extension.ts +118 -0
- package/src/extensions/cursor-cloud-agents/lib/cloud.ts +79 -0
- package/src/extensions/cursor-cloud-agents/lib/fleet.ts +109 -0
- package/src/extensions/cursor-cloud-agents/lib/repos.ts +81 -0
- package/src/extensions/cursor-cloud-agents/skills/handoff.md +27 -0
- package/src/extensions/cursor-cloud-agents/tools/follow_up.ts +29 -0
- package/src/extensions/cursor-cloud-agents/tools/launch.ts +84 -0
- package/src/extensions/cursor-cloud-agents/tools/list.ts +31 -0
- package/src/extensions/cursor-cloud-agents/tools/status.ts +39 -0
- package/src/extensions.ts +98 -0
- package/src/index.ts +2 -0
- package/src/internal/advertise-tools.ts +37 -12
- package/src/internal/as-of.ts +163 -0
- package/src/internal/cli-deploy.ts +86 -23
- package/src/internal/continuation-identity.ts +3 -1
- package/src/internal/cursor/hosted-deployment-secrets.ts +72 -0
- package/src/internal/cursor-deployment-relay.ts +65 -8
- package/src/internal/deploy-client.ts +73 -18
- package/src/internal/discovery.ts +470 -113
- package/src/internal/distribution.ts +5 -0
- package/src/internal/framework-file-storage.ts +39 -5
- package/src/internal/hosted-delivery.ts +38 -63
- package/src/internal/hosted-execution-diag.ts +6 -0
- package/src/internal/hosted-execution-flush.ts +142 -0
- package/src/internal/mcp-endpoint.ts +1 -0
- package/src/internal/mcp-host.ts +40 -14
- package/src/internal/review-comments.ts +5 -2
- package/src/internal/server.ts +81 -100
- package/src/internal/session-engine.ts +56 -28
- package/src/internal/session-run-log.ts +2 -1
- package/src/internal/start-log-store.ts +177 -0
- package/src/internal/tool-policy.ts +39 -9
- package/src/storage-protocol.ts +37 -0
- package/src/storage.ts +1 -0
- package/src/types.ts +172 -14
- package/dist/docs/assets/chunks/@localSearchIndexroot.j70vvPL4.js +0 -1
- package/dist/playground/assets/index-BszoQDc6.css +0 -1
- /package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.lean.js → reference_http-api.md.oySXBO8o.lean.js} +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which repositories a launch may target. The model picks `repo` / `ref` /
|
|
3
|
+
* `prUrl` per call, but the author's `cloud.repos` are the allowlist.
|
|
4
|
+
* Model-chosen targets always require that allowlist: an unbound mount
|
|
5
|
+
* (no `cloud.repos`) must not accept an arbitrary URL under the host API
|
|
6
|
+
* key, with or without `envVars`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { parsePrUrl } from "../../../internal/scm/pr-url.js";
|
|
10
|
+
import type { AgentCloudOptions } from "../../../types.js";
|
|
11
|
+
|
|
12
|
+
export interface LaunchTarget {
|
|
13
|
+
repo?: string;
|
|
14
|
+
ref?: string;
|
|
15
|
+
prUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function launchRepos(
|
|
19
|
+
target: LaunchTarget,
|
|
20
|
+
cloud: AgentCloudOptions
|
|
21
|
+
): AgentCloudOptions["repos"] {
|
|
22
|
+
if (
|
|
23
|
+
target.repo === undefined &&
|
|
24
|
+
target.ref === undefined &&
|
|
25
|
+
target.prUrl === undefined
|
|
26
|
+
) {
|
|
27
|
+
return cloud.repos;
|
|
28
|
+
}
|
|
29
|
+
const pr = target.prUrl === undefined ? undefined : parsePrUrl(target.prUrl);
|
|
30
|
+
if (target.prUrl !== undefined && pr?.provider !== "github") {
|
|
31
|
+
throw new Error(`prUrl must be a GitHub pull request URL: ${target.prUrl}`);
|
|
32
|
+
}
|
|
33
|
+
const prRepo =
|
|
34
|
+
pr === undefined ? undefined : `https://github.com/${pr.owner}/${pr.repo}`;
|
|
35
|
+
// A lone `ref` needs exactly one configured repository to apply to.
|
|
36
|
+
const configured = cloud.repos ?? [];
|
|
37
|
+
const url =
|
|
38
|
+
target.repo ??
|
|
39
|
+
prRepo ??
|
|
40
|
+
(configured.length === 1 ? configured[0]?.url : undefined);
|
|
41
|
+
if (url === undefined) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
configured.length > 1
|
|
44
|
+
? "`repo` is required: this extension configures several repositories."
|
|
45
|
+
: "`repo` is required: this extension has no configured default repository."
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (prRepo !== undefined && repoKey(prRepo) !== repoKey(url)) {
|
|
49
|
+
throw new Error(`prUrl ${target.prUrl} is not a pull request of ${url}.`);
|
|
50
|
+
}
|
|
51
|
+
const allowlisted = cloud.repos?.some(
|
|
52
|
+
(configured) => repoKey(configured.url) === repoKey(url)
|
|
53
|
+
);
|
|
54
|
+
if (allowlisted !== true) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
cloud.repos === undefined
|
|
57
|
+
? `Configure cloud.repos before launching against a model-chosen repository (${url}).`
|
|
58
|
+
: `${url} is not one of this extension's configured repositories.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
// Keep the matching allowlist entry's branch / PR defaults when the model
|
|
62
|
+
// only names the repository (handoff always passes `repo`). An explicit
|
|
63
|
+
// `ref` clears an inherited `prUrl` so branch and PR targets do not mix.
|
|
64
|
+
const match = configured.find(
|
|
65
|
+
(configuredRepo) => repoKey(configuredRepo.url) === repoKey(url)
|
|
66
|
+
);
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
url,
|
|
70
|
+
startingRef: target.ref ?? match?.startingRef,
|
|
71
|
+
prUrl:
|
|
72
|
+
target.prUrl ?? (target.ref !== undefined ? undefined : match?.prUrl),
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function repoKey(url: string): string {
|
|
78
|
+
const parsed = new URL(url);
|
|
79
|
+
const path = parsed.pathname.replace(/\.git$/, "").replace(/\/+$/, "");
|
|
80
|
+
return `${parsed.hostname}${path}`.toLowerCase();
|
|
81
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Delegate coding work to Cursor cloud agents: when to hand off, how to write the prompt, and how to follow up and check status."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Cloud agent handoff
|
|
6
|
+
|
|
7
|
+
Hand work to a Cursor cloud agent when it needs a repository checkout, a long run, or an isolated machine: implementing a change, running a test suite, opening a pull request. Answer questions and small reads yourself.
|
|
8
|
+
|
|
9
|
+
The tools below are this extension's; they appear under its mount namespace, as `<namespace>__launch` and so on.
|
|
10
|
+
|
|
11
|
+
## Launch
|
|
12
|
+
|
|
13
|
+
Call `launch` with a self-contained prompt: the goal, the repository or pull request, constraints (branch, files to leave alone), and what done looks like (tests pass, PR opened). Pass `ref` or `prUrl` to pick a branch or pull request; `repo` must be one of the configured repositories.
|
|
14
|
+
|
|
15
|
+
The result carries `agentId` (`bc-...`) and a `url`. Report both in full. Never truncate the id.
|
|
16
|
+
|
|
17
|
+
## Follow up
|
|
18
|
+
|
|
19
|
+
Call `follow_up` with the `agentId` to steer an agent you launched or ask for changes on its branch. A busy agent rejects the follow-up: check status and retry once its run has finished. Do not launch a second agent for follow-up work on the same branch.
|
|
20
|
+
|
|
21
|
+
## Check status
|
|
22
|
+
|
|
23
|
+
Call `status` with the `agentId`. The latest run carries `status` (`running`, `finished`, `error`, or `cancelled`), the final `result` text, an `error` message, and any pushed `branches` or pull requests; the agent itself reports `archived`. A run takes minutes: check when asked or when you need the result, not in a tight loop.
|
|
24
|
+
|
|
25
|
+
## Fleet
|
|
26
|
+
|
|
27
|
+
`list` returns the agents launched in this session, newest first; pass `scope: "all"` to include your launches from other sessions. Launch one agent per independent task and give each a descriptive `name`. Agents launched by others are out of reach.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Agent } from "@cursor/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { defineTool } from "../../../tools.js";
|
|
4
|
+
import type { ToolDefinition } from "../../../types.js";
|
|
5
|
+
import extension from "../extension.js";
|
|
6
|
+
import { agentRef, apiKey } from "../lib/cloud.js";
|
|
7
|
+
import { assertOwned } from "../lib/fleet.js";
|
|
8
|
+
|
|
9
|
+
const follow_up: ToolDefinition = defineTool({
|
|
10
|
+
description:
|
|
11
|
+
"Send a follow-up prompt to a cloud agent you launched. The agent must be idle; a busy agent rejects the follow-up.",
|
|
12
|
+
effect: "write",
|
|
13
|
+
needsApproval: () => extension.config.needsApproval,
|
|
14
|
+
inputSchema: agentRef.extend({
|
|
15
|
+
prompt: z.string().min(1).describe("The follow-up instruction."),
|
|
16
|
+
}),
|
|
17
|
+
async execute(input, ctx) {
|
|
18
|
+
await assertOwned(ctx, input.agentId);
|
|
19
|
+
const agent = await Agent.resume(input.agentId, { apiKey: apiKey() });
|
|
20
|
+
try {
|
|
21
|
+
const run = await agent.send(input.prompt);
|
|
22
|
+
return { agentId: input.agentId, runId: run.id, status: run.status };
|
|
23
|
+
} finally {
|
|
24
|
+
agent.close();
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default follow_up;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Agent } from "@cursor/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { defineTool } from "../../../tools.js";
|
|
4
|
+
import type { ToolDefinition } from "../../../types.js";
|
|
5
|
+
import extension from "../extension.js";
|
|
6
|
+
import { agentUrl, agentView, apiKey, modelSelection } from "../lib/cloud.js";
|
|
7
|
+
import { recordLaunch } from "../lib/fleet.js";
|
|
8
|
+
import { launchRepos } from "../lib/repos.js";
|
|
9
|
+
|
|
10
|
+
const launch: ToolDefinition = defineTool({
|
|
11
|
+
description:
|
|
12
|
+
"Launch a Cursor cloud agent on a repository with a self-contained task. Returns the agent id and URL; check progress with the status tool.",
|
|
13
|
+
effect: "write",
|
|
14
|
+
needsApproval: () => extension.config.needsApproval,
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
prompt: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1)
|
|
19
|
+
.describe(
|
|
20
|
+
"Self-contained task: goal, constraints, and what done looks like."
|
|
21
|
+
),
|
|
22
|
+
repo: z
|
|
23
|
+
.string()
|
|
24
|
+
.url()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe(
|
|
27
|
+
"GitHub repository URL, one of the configured repositories. Defaults to the configured repository."
|
|
28
|
+
),
|
|
29
|
+
ref: z.string().optional().describe("Starting branch or commit."),
|
|
30
|
+
prUrl: z
|
|
31
|
+
.string()
|
|
32
|
+
.url()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Work on this pull request's branch instead of a new one."),
|
|
35
|
+
name: z
|
|
36
|
+
.string()
|
|
37
|
+
.max(100)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Display name; derived from the prompt when omitted."),
|
|
40
|
+
}),
|
|
41
|
+
async execute(input, ctx) {
|
|
42
|
+
const { cloud } = extension.config;
|
|
43
|
+
const key = apiKey();
|
|
44
|
+
const agent = await Agent.create({
|
|
45
|
+
apiKey: key,
|
|
46
|
+
model: modelSelection(),
|
|
47
|
+
name: input.name,
|
|
48
|
+
cloud: { ...cloud, repos: launchRepos(input, cloud) },
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
const run = await agent.send(input.prompt);
|
|
52
|
+
await recordLaunch(ctx, agent.agentId, input.name);
|
|
53
|
+
return {
|
|
54
|
+
agentId: agent.agentId,
|
|
55
|
+
runId: run.id,
|
|
56
|
+
status: run.status,
|
|
57
|
+
url: agentUrl(agent.agentId),
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// The API can fail after the agent exists (slow admission,
|
|
61
|
+
// post-create checks). The SDK seeded the id unless envVars made
|
|
62
|
+
// the server mint it, so read the agent back and keep the handle;
|
|
63
|
+
// envVars launches have no id to read back and surface the error.
|
|
64
|
+
const info =
|
|
65
|
+
cloud.envVars === undefined
|
|
66
|
+
? await Agent.get(agent.agentId, { apiKey: key }).catch(
|
|
67
|
+
() => undefined
|
|
68
|
+
)
|
|
69
|
+
: undefined;
|
|
70
|
+
if (info === undefined) {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
await recordLaunch(ctx, info.agentId, input.name);
|
|
74
|
+
return {
|
|
75
|
+
...agentView(info),
|
|
76
|
+
launchError: error instanceof Error ? error.message : String(error),
|
|
77
|
+
};
|
|
78
|
+
} finally {
|
|
79
|
+
agent.close();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export default launch;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineTool } from "../../../tools.js";
|
|
3
|
+
import type { ToolDefinition } from "../../../types.js";
|
|
4
|
+
import { agentUrl } from "../lib/cloud.js";
|
|
5
|
+
import { listFleet } from "../lib/fleet.js";
|
|
6
|
+
|
|
7
|
+
const list: ToolDefinition = defineTool({
|
|
8
|
+
description:
|
|
9
|
+
'List the cloud agents launched in this session, newest first. Pass scope "all" for every agent you launched, in any session.',
|
|
10
|
+
effect: "read",
|
|
11
|
+
inputSchema: z.object({
|
|
12
|
+
scope: z
|
|
13
|
+
.enum(["session", "all"])
|
|
14
|
+
.default("session")
|
|
15
|
+
.describe(
|
|
16
|
+
"session: agents this session launched; all: every agent launched by you, in any session."
|
|
17
|
+
),
|
|
18
|
+
limit: z.number().int().min(1).max(100).default(20),
|
|
19
|
+
}),
|
|
20
|
+
async execute(input, ctx) {
|
|
21
|
+
const fleet = await listFleet(ctx, input.scope);
|
|
22
|
+
return {
|
|
23
|
+
sessionId: ctx.session.id,
|
|
24
|
+
agents: fleet
|
|
25
|
+
.slice(0, input.limit)
|
|
26
|
+
.map((entry) => ({ ...entry, url: agentUrl(entry.agentId) })),
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export default list;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Agent } from "@cursor/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { defineTool } from "../../../tools.js";
|
|
4
|
+
import type { ToolDefinition } from "../../../types.js";
|
|
5
|
+
import { agentRef, agentView, apiKey, runView } from "../lib/cloud.js";
|
|
6
|
+
import { assertOwned } from "../lib/fleet.js";
|
|
7
|
+
|
|
8
|
+
const status: ToolDefinition = defineTool({
|
|
9
|
+
description:
|
|
10
|
+
"Read a cloud agent you launched and its latest run: state, final result text, error, and pushed branches or pull requests.",
|
|
11
|
+
effect: "read",
|
|
12
|
+
inputSchema: agentRef.extend({
|
|
13
|
+
runId: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("A specific run; defaults to the latest."),
|
|
17
|
+
}),
|
|
18
|
+
async execute(input, ctx) {
|
|
19
|
+
await assertOwned(ctx, input.agentId);
|
|
20
|
+
const key = apiKey();
|
|
21
|
+
const cloud = { runtime: "cloud", apiKey: key } as const;
|
|
22
|
+
const info = await Agent.get(input.agentId, { apiKey: key });
|
|
23
|
+
// The run list carries no result text; fetch the run itself.
|
|
24
|
+
const runId =
|
|
25
|
+
input.runId ??
|
|
26
|
+
(await Agent.listRuns(input.agentId, { ...cloud, limit: 1 })).items[0]
|
|
27
|
+
?.id;
|
|
28
|
+
const run =
|
|
29
|
+
runId === undefined
|
|
30
|
+
? undefined
|
|
31
|
+
: await Agent.getRun(runId, { ...cloud, agentId: input.agentId });
|
|
32
|
+
return {
|
|
33
|
+
...agentView(info),
|
|
34
|
+
run: run === undefined ? undefined : runView(run),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export default status;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extensions — agent-shaped trees an agent mounts under a namespace.
|
|
3
|
+
*
|
|
4
|
+
* An extension is a directory laid out like `agent/`: `extension.ts`
|
|
5
|
+
* declares it, `tools/<name>.ts` and `skills/<name>.md|ts` are its
|
|
6
|
+
* contributions, `lib/` holds shared code.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // extension.ts
|
|
10
|
+
* import { defineExtension } from "@cursor/july/extensions";
|
|
11
|
+
* import { z } from "zod";
|
|
12
|
+
*
|
|
13
|
+
* export default defineExtension({
|
|
14
|
+
* root: import.meta.dirname,
|
|
15
|
+
* description: "CRM lookups.",
|
|
16
|
+
* config: z.object({ apiKey: z.string() }),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // tools/search.ts
|
|
20
|
+
* import extension from "../extension.js";
|
|
21
|
+
*
|
|
22
|
+
* export default defineTool({
|
|
23
|
+
* description: "Search the CRM.",
|
|
24
|
+
* execute: (input) => search(extension.config.apiKey, input),
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* A consumer mounts it from `agent/extensions/<ns>.ts`; the filename is the
|
|
29
|
+
* namespace, and every contribution is exposed as `<ns>__<name>`
|
|
30
|
+
* (`crm__search`). `__` is reserved for that: an authored `agent/tools/` or
|
|
31
|
+
* `agent/skills/` name may not contain it.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* // agent/extensions/crm.ts
|
|
35
|
+
* import crm from "@acme/crm";
|
|
36
|
+
*
|
|
37
|
+
* export default crm({ apiKey: process.env.CRM_API_KEY! });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* To adjust a contribution, mount as a directory instead and overlay the
|
|
41
|
+
* slot: `agent/extensions/crm/extension.ts` holds the mount, and
|
|
42
|
+
* `agent/extensions/crm/tools/search.ts` replaces `crm__search` — or removes
|
|
43
|
+
* it with `export default disableTool()`.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { brandDefinition } from "./internal/brand.js";
|
|
47
|
+
import type {
|
|
48
|
+
DisabledToolDefinition,
|
|
49
|
+
Extension,
|
|
50
|
+
ExtensionMount,
|
|
51
|
+
ExtensionOptions,
|
|
52
|
+
} from "./types.js";
|
|
53
|
+
|
|
54
|
+
export type {
|
|
55
|
+
DisabledToolDefinition,
|
|
56
|
+
DiscoveredExtension,
|
|
57
|
+
Extension,
|
|
58
|
+
ExtensionDeclaration,
|
|
59
|
+
ExtensionMount,
|
|
60
|
+
ExtensionOptions,
|
|
61
|
+
} from "./types.js";
|
|
62
|
+
|
|
63
|
+
export function defineExtension<TConfig = undefined, TInput = TConfig>(
|
|
64
|
+
options: ExtensionOptions<TConfig, TInput>
|
|
65
|
+
): Extension<TConfig, TInput> {
|
|
66
|
+
let mounted: { config: TConfig } | undefined;
|
|
67
|
+
const mount = (config?: TInput): ExtensionMount => {
|
|
68
|
+
const parsed =
|
|
69
|
+
options.config === undefined
|
|
70
|
+
? (config as unknown as TConfig)
|
|
71
|
+
: options.config.parse(config);
|
|
72
|
+
mounted = { config: parsed };
|
|
73
|
+
return brandDefinition("extension", {
|
|
74
|
+
extension: handle,
|
|
75
|
+
config: parsed,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const handle = Object.defineProperties(mount, {
|
|
79
|
+
root: { value: options.root, enumerable: true },
|
|
80
|
+
description: { value: options.description, enumerable: true },
|
|
81
|
+
config: {
|
|
82
|
+
enumerable: true,
|
|
83
|
+
get(): TConfig {
|
|
84
|
+
if (mounted === undefined) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Extension at ${options.root} read its config before an agent mounted it. Mount it from agent/extensions/<ns>.ts; the mount runs before the tree loads.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return mounted.config;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}) as unknown as Extension<TConfig, TInput>;
|
|
93
|
+
return handle;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function disableTool(): DisabledToolDefinition {
|
|
97
|
+
return brandDefinition("disabled-tool", {});
|
|
98
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -78,6 +78,7 @@ export {
|
|
|
78
78
|
stampMailboxContinuation,
|
|
79
79
|
V2EngineError,
|
|
80
80
|
} from "./continuation.js";
|
|
81
|
+
export { defineExtension, disableTool } from "./extensions.js";
|
|
81
82
|
export { defineHook } from "./hooks.js";
|
|
82
83
|
export {
|
|
83
84
|
InvalidAsOfError,
|
|
@@ -189,6 +190,7 @@ export {
|
|
|
189
190
|
isToolEffect,
|
|
190
191
|
MAX_RUNNING_TURNS_LIMIT,
|
|
191
192
|
normalizeConnectionEffects,
|
|
193
|
+
normalizeConnectionTools,
|
|
192
194
|
normalizeCursorAccountServers,
|
|
193
195
|
supportsKvCas,
|
|
194
196
|
} from "./types.js";
|
|
@@ -27,21 +27,25 @@
|
|
|
27
27
|
* `defineConnection({ effects })` filters the listing before names are
|
|
28
28
|
* assigned: only tools whose MCP annotations classify as an intended
|
|
29
29
|
* effect are advertised. Unclassified tools are omitted, not guessed.
|
|
30
|
+
* `defineConnection({ tools: [...] })` keeps only the named tools.
|
|
31
|
+
* `disallowedTools` drops names. A name filter that matches nothing on
|
|
32
|
+
* the server fails the turn.
|
|
30
33
|
* Tools whose `annotations.title` is `do-not-call` are dropped as well
|
|
31
34
|
* (vendor host-UI / MCP Apps helpers that still carry `readOnlyHint`).
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
|
-
import
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
import {
|
|
38
|
+
connectionAllowsTool,
|
|
39
|
+
type DiscoveredConnection,
|
|
40
|
+
type DiscoveredTool,
|
|
41
|
+
type HostMcpCallResult,
|
|
42
|
+
type HostMcpToolInfo,
|
|
43
|
+
type JsonSchemaObject,
|
|
44
|
+
type McpToolAnnotations,
|
|
45
|
+
type ResolvedAgent,
|
|
46
|
+
type SessionInfo,
|
|
47
|
+
type ToolDefinition,
|
|
48
|
+
type ToolEffect,
|
|
45
49
|
} from "../types.js";
|
|
46
50
|
import { brandDefinition } from "./brand.js";
|
|
47
51
|
import type { HostMcpOneOff } from "./mcp-host.js";
|
|
@@ -136,7 +140,14 @@ export async function resolveAdvertisedTools(args: {
|
|
|
136
140
|
try {
|
|
137
141
|
const source = await sourceFor(connection, args.session, args.hostMcp);
|
|
138
142
|
const listed = await source.listTools();
|
|
139
|
-
return {
|
|
143
|
+
return {
|
|
144
|
+
connection,
|
|
145
|
+
ok: true,
|
|
146
|
+
source,
|
|
147
|
+
listed: listed.filter((info) =>
|
|
148
|
+
connectionAllowsTool(connection, info.name)
|
|
149
|
+
),
|
|
150
|
+
};
|
|
140
151
|
} catch (error) {
|
|
141
152
|
return { connection, ok: false, error };
|
|
142
153
|
}
|
|
@@ -157,6 +168,20 @@ export async function resolveAdvertisedTools(args: {
|
|
|
157
168
|
);
|
|
158
169
|
}
|
|
159
170
|
const { source, listed } = listing;
|
|
171
|
+
if (
|
|
172
|
+
(connection.tools !== undefined ||
|
|
173
|
+
connection.disallowedTools !== undefined) &&
|
|
174
|
+
listed.length === 0
|
|
175
|
+
) {
|
|
176
|
+
const error = new Error(
|
|
177
|
+
`Advertised connection "${connection.name}" tool filter matched no tools on the server.`
|
|
178
|
+
);
|
|
179
|
+
if (args.onConnectionError !== undefined) {
|
|
180
|
+
args.onConnectionError(connection.name, error);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
160
185
|
const intended = listed.filter(
|
|
161
186
|
(info) =>
|
|
162
187
|
!isDoNotCallTool(info.annotations) &&
|
package/src/internal/as-of.ts
CHANGED
|
@@ -105,3 +105,166 @@ export function timeArgViolation(
|
|
|
105
105
|
}
|
|
106
106
|
return undefined;
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
// Whole-string only: a query that merely contains "now" is not a bound.
|
|
110
|
+
// Duration-only strings (`30m`, `2h`) and last/this plus a non-window word
|
|
111
|
+
// (`last modified`) are not time bounds. Calendar-aligned `now/d` is.
|
|
112
|
+
const TIME_UNIT =
|
|
113
|
+
"(?:s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks|mo|month|months|y|yr|yrs|year|years)";
|
|
114
|
+
const WINDOW_WORD =
|
|
115
|
+
"(?:second|seconds|minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years|quarter|quarters|weekend|monday|tuesday|wednesday|thursday|friday|saturday|sunday|morning|afternoon|evening|night)";
|
|
116
|
+
const RELATIVE_TIME_EXPRESSION = new RegExp(
|
|
117
|
+
`^(?:now(?:\\s*[+-]\\s*\\d+\\s*${TIME_UNIT}\\b.*|/[a-zA-Z]+.*)?|[+-]\\d+\\s*${TIME_UNIT}|today|yesterday|tomorrow|(?:last|this)\\s+${WINDOW_WORD})$`,
|
|
118
|
+
"i"
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
function forwardTimeReason(
|
|
122
|
+
value: unknown,
|
|
123
|
+
asOf: string,
|
|
124
|
+
frozenMs: number
|
|
125
|
+
): string | undefined {
|
|
126
|
+
if (typeof value !== "string") {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const trimmed = value.trim();
|
|
130
|
+
if (trimmed === "") {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
if (RELATIVE_TIME_EXPRESSION.test(trimmed)) {
|
|
134
|
+
return (
|
|
135
|
+
`Argument ${JSON.stringify(value)} is a relative time expression, ` +
|
|
136
|
+
`which reads against the real present. This session reads the world ` +
|
|
137
|
+
`as of ${asOf}; supply an absolute ISO-8601 instant with a timezone ` +
|
|
138
|
+
`at or before that instant and retry.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const instant = parseInstant(trimmed);
|
|
142
|
+
if (instant !== undefined && instant.getTime() > frozenMs) {
|
|
143
|
+
return (
|
|
144
|
+
`Argument ${JSON.stringify(value)} is later than the instant this ` +
|
|
145
|
+
`session reads the world as of (${asOf}). Supply a bound at or before ` +
|
|
146
|
+
`that instant and retry.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Relative expressions or instants after `asOf`. Numeric epochs need `timeArgs`. */
|
|
153
|
+
export function forwardTimeArgument(
|
|
154
|
+
input: unknown,
|
|
155
|
+
asOf: string
|
|
156
|
+
): string | undefined {
|
|
157
|
+
const frozenMs = new Date(asOf).getTime();
|
|
158
|
+
const seen = new Set<unknown>();
|
|
159
|
+
const walk = (value: unknown, depth: number): string | undefined => {
|
|
160
|
+
if (depth > 8) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
const reason = forwardTimeReason(value, asOf, frozenMs);
|
|
164
|
+
if (reason !== undefined) {
|
|
165
|
+
return reason;
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
for (const item of value) {
|
|
169
|
+
const found = walk(item, depth + 1);
|
|
170
|
+
if (found !== undefined) {
|
|
171
|
+
return found;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
if (typeof value === "object" && value !== null) {
|
|
177
|
+
if (seen.has(value)) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
seen.add(value);
|
|
181
|
+
for (const item of Object.values(value as Record<string, unknown>)) {
|
|
182
|
+
const found = walk(item, depth + 1);
|
|
183
|
+
if (found !== undefined) {
|
|
184
|
+
return found;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
};
|
|
190
|
+
return walk(input, 0);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Property names that carry a query's time window when a vendor schema
|
|
195
|
+
* declares them. Deliberately narrower than the embedded-filter key list:
|
|
196
|
+
* these must be safe to *require*, so only unambiguous window names — never
|
|
197
|
+
* `timestamp`/`date`, which name data fields more often than bounds.
|
|
198
|
+
*/
|
|
199
|
+
const TIME_WINDOW_KEYS = new Set([
|
|
200
|
+
"from",
|
|
201
|
+
"to",
|
|
202
|
+
"since",
|
|
203
|
+
"until",
|
|
204
|
+
"start",
|
|
205
|
+
"end",
|
|
206
|
+
"start_time",
|
|
207
|
+
"end_time",
|
|
208
|
+
"starttime",
|
|
209
|
+
"endtime",
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The string-typed time-window properties a tool's input schema declares.
|
|
214
|
+
* String-typed only: a numeric `from` is a pagination offset more often than
|
|
215
|
+
* an instant, and requiring an ISO value there would break the call.
|
|
216
|
+
*/
|
|
217
|
+
export function schemaTimeWindowKeys(
|
|
218
|
+
inputSchema: { properties?: Record<string, unknown> } | undefined
|
|
219
|
+
): string[] {
|
|
220
|
+
const properties = inputSchema?.properties;
|
|
221
|
+
if (properties === undefined) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
const keys: string[] = [];
|
|
225
|
+
for (const [name, spec] of Object.entries(properties)) {
|
|
226
|
+
if (!TIME_WINDOW_KEYS.has(name.toLowerCase())) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const type =
|
|
230
|
+
typeof spec === "object" && spec !== null
|
|
231
|
+
? (spec as { type?: unknown }).type
|
|
232
|
+
: undefined;
|
|
233
|
+
const types = Array.isArray(type) ? type : [type];
|
|
234
|
+
if (types.includes("string")) {
|
|
235
|
+
keys.push(name);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return keys;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* An omitted time window on a vendor tool is not "no window" — the vendor
|
|
243
|
+
* fills it with a live default (Datadog: `now-1h`..`now`), which the walker
|
|
244
|
+
* cannot see because the value is never present. In a frozen session a call
|
|
245
|
+
* that omits a schema-declared window key is refused so the model supplies
|
|
246
|
+
* the bound explicitly, where {@link forwardTimeArgument} then checks it.
|
|
247
|
+
*/
|
|
248
|
+
export function missingTimeWindow(
|
|
249
|
+
input: unknown,
|
|
250
|
+
windowKeys: readonly string[],
|
|
251
|
+
asOf: string
|
|
252
|
+
): string | undefined {
|
|
253
|
+
if (windowKeys.length === 0) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
const record =
|
|
257
|
+
typeof input === "object" && input !== null
|
|
258
|
+
? (input as Record<string, unknown>)
|
|
259
|
+
: {};
|
|
260
|
+
const missing = windowKeys.filter((key) => record[key] === undefined);
|
|
261
|
+
if (missing.length === 0) {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
return (
|
|
265
|
+
`This tool fills omitted time bounds (${missing.join(", ")}) with a ` +
|
|
266
|
+
`live default window, which reads the real present. This session reads ` +
|
|
267
|
+
`the world as of ${asOf}; supply each bound explicitly as an absolute ` +
|
|
268
|
+
`ISO-8601 instant with a timezone at or before that instant and retry.`
|
|
269
|
+
);
|
|
270
|
+
}
|