@cursor/july 0.1.33 → 0.1.35
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/README.md +1 -1
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +0 -5
- package/dist/bin/agent-serve.d.ts +1 -0
- package/dist/bin/agent-serve.d.ts.map +1 -1
- package/dist/bin/agent-serve.js +32 -2
- package/dist/channels/github/types.d.ts +10 -7
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.CgZqLyWL.js → app.D5Mv1T0U.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.Cu7b6o1D.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.C4KigoQn.js → VPLocalSearchBox.CMq_BQce.js} +1 -1
- package/dist/docs/assets/chunks/{theme.C_Ty4yT7.js → theme.C6D9UPLK.js} +2 -2
- package/dist/docs/assets/{example-agents_weather-agent.md.Dmrcphhl.js → example-agents_weather-agent.md.BADkPqxQ.js} +6 -4
- package/dist/docs/assets/example-agents_weather-agent.md.BADkPqxQ.lean.js +1 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.js +9 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.lean.js +1 -0
- 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 +10 -8
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.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 +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/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 +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/files-backends/agent-store-presigned-url.d.ts +3 -11
- package/dist/files.d.ts +20 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +20 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/artifacts-store.d.ts +14 -6
- package/dist/internal/artifacts-store.d.ts.map +1 -1
- package/dist/internal/artifacts-store.js +160 -16
- package/dist/internal/cli-convert-automation.d.ts +39 -0
- package/dist/internal/cli-convert-automation.d.ts.map +1 -0
- package/dist/internal/cli-convert-automation.js +280 -0
- package/dist/internal/cli-deploy.d.ts +2 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +1 -1
- package/dist/internal/cli-mcp-oauth.d.ts +23 -0
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
- package/dist/internal/cli-mcp-oauth.js +57 -33
- package/dist/internal/convert-automation/convert-workflow.d.ts +10 -0
- package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -0
- package/dist/internal/convert-automation/convert-workflow.js +998 -0
- package/dist/internal/convert-automation/index.d.ts +3 -0
- package/dist/internal/convert-automation/index.d.ts.map +1 -0
- package/dist/internal/convert-automation/index.js +1 -0
- package/dist/internal/convert-automation/slug.d.ts +11 -0
- package/dist/internal/convert-automation/slug.d.ts.map +1 -0
- package/dist/internal/convert-automation/slug.js +36 -0
- package/dist/internal/convert-automation/types.d.ts +191 -0
- package/dist/internal/convert-automation/types.d.ts.map +1 -0
- package/dist/internal/convert-automation/types.js +13 -0
- package/dist/internal/deploy-client.d.ts +31 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +48 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +3 -32
- package/dist/internal/host-kv.d.ts +6 -2
- package/dist/internal/host-kv.d.ts.map +1 -1
- package/dist/internal/init-project.d.ts +1 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +1 -1
- package/dist/internal/mcp-endpoint.d.ts +6 -0
- package/dist/internal/mcp-endpoint.d.ts.map +1 -1
- package/dist/internal/mcp-endpoint.js +168 -0
- package/dist/internal/resolved-connections.d.ts +15 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -1
- package/dist/internal/resolved-connections.js +12 -5
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +75 -7
- package/dist/internal/session-engine.d.ts +56 -3
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +262 -84
- package/dist/internal/storage-coordinator.d.ts +9 -16
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +7 -52
- package/dist/internal/storage-roles.d.ts +78 -0
- package/dist/internal/storage-roles.d.ts.map +1 -0
- package/dist/internal/storage-roles.js +24 -0
- package/dist/internal/workspace.d.ts +36 -5
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +66 -0
- package/dist/memory.d.ts +22 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +100 -8
- package/dist/playground/assets/{index-CVlCAcUx.js → index-D7OV8B_H.js} +50 -50
- package/dist/playground/assets/index-DOb96C0M.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +0 -4
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +0 -4
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/example-agents/weather-agent.md +29 -11
- package/docs/guides/cloud-runtime.md +6 -4
- package/package.json +3 -1
- package/src/artifacts.ts +0 -6
- package/src/bin/agent-serve.ts +36 -2
- package/src/channels/github/types.ts +10 -7
- package/src/files.ts +27 -0
- package/src/index.ts +6 -1
- package/src/internal/artifacts-store.ts +185 -22
- package/src/internal/cli-convert-automation.ts +352 -0
- package/src/internal/cli-deploy.ts +1 -1
- package/src/internal/cli-mcp-oauth.ts +69 -31
- package/src/internal/convert-automation/convert-workflow.ts +1422 -0
- package/src/internal/convert-automation/index.ts +10 -0
- package/src/internal/convert-automation/slug.ts +38 -0
- package/src/internal/convert-automation/types.ts +199 -0
- package/src/internal/deploy-client.ts +78 -0
- package/src/internal/discovery.ts +3 -37
- package/src/internal/host-kv.ts +6 -2
- package/src/internal/init-project.ts +1 -1
- package/src/internal/mcp-endpoint.ts +243 -0
- package/src/internal/resolved-connections.ts +32 -6
- package/src/internal/server.ts +99 -1
- package/src/internal/session-engine.ts +345 -103
- package/src/internal/storage-coordinator.ts +15 -63
- package/src/internal/storage-roles.ts +86 -0
- package/src/internal/workspace.ts +81 -6
- package/src/memory.ts +126 -10
- package/src/storage.ts +0 -6
- package/src/types.ts +15 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.B1uHGxBZ.js +0 -1
- package/dist/docs/assets/example-agents_weather-agent.md.Dmrcphhl.lean.js +0 -1
- package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.js +0 -9
- package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.lean.js +0 -1
- package/dist/playground/assets/index-BRhBbnd4.css +0 -1
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
* and A/B metrics go to their dedicated tables (`evals` / `abs`).
|
|
15
15
|
* Reminder documents live on the control plane. Everything shares one
|
|
16
16
|
* bounded, serialized queue.
|
|
17
|
+
*
|
|
18
|
+
* It does three unrelated jobs, declared separately as
|
|
19
|
+
* {@link FrameworkSessionStore}, {@link AuthorKvStore}, and
|
|
20
|
+
* {@link EvalAbStore} — see `storage-roles.ts` for why they differ and where
|
|
21
|
+
* each is headed. The queue stays shared: it is one bounded, serialized chain
|
|
22
|
+
* per agent, and splitting it would change backpressure behavior, which this
|
|
23
|
+
* seam deliberately does not touch.
|
|
17
24
|
*/
|
|
18
25
|
|
|
19
26
|
import type { ABMetricSample } from "../ab.js";
|
|
@@ -30,6 +37,11 @@ import type { JsonValue, SessionEvent, SessionRecord } from "../types.js";
|
|
|
30
37
|
import type { ABSnapshot } from "./ab-snapshot.js";
|
|
31
38
|
import { describeError } from "./describe-error.js";
|
|
32
39
|
import type { EvalRunStorage } from "./eval-run-store.js";
|
|
40
|
+
import type {
|
|
41
|
+
AuthorKvStore,
|
|
42
|
+
EvalAbStore,
|
|
43
|
+
FrameworkSessionStore,
|
|
44
|
+
} from "./storage-roles.js";
|
|
33
45
|
|
|
34
46
|
/** Event types that close a unit of work — flush point for turn-end batching. */
|
|
35
47
|
const TURN_BOUNDARY_EVENTS: ReadonlySet<SessionEvent["type"]> = new Set([
|
|
@@ -85,7 +97,9 @@ interface SessionBuffer {
|
|
|
85
97
|
debounceTimer?: ReturnType<typeof setTimeout>;
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
export class StorageCoordinator
|
|
100
|
+
export class StorageCoordinator
|
|
101
|
+
implements FrameworkSessionStore, AuthorKvStore, EvalAbStore
|
|
102
|
+
{
|
|
89
103
|
readonly policy: ResolvedStoragePolicy;
|
|
90
104
|
|
|
91
105
|
private readonly definition: StorageDefinition;
|
|
@@ -458,68 +472,6 @@ export class StorageCoordinator {
|
|
|
458
472
|
await this.definition.delete?.(key, this.context("policy"));
|
|
459
473
|
}
|
|
460
474
|
|
|
461
|
-
// ==========================================================================
|
|
462
|
-
// Artifacts (`ctx.artifacts`)
|
|
463
|
-
// ==========================================================================
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Whether the sink can serve awaited artifact rows: `get` (upsert
|
|
467
|
-
* read-back), `list` (listing + eviction), and `delete` (`max` cap
|
|
468
|
-
* eviction) are all required — without `delete` the retention cap
|
|
469
|
-
* could never evict, so rows fall back to the filesystem store.
|
|
470
|
-
*/
|
|
471
|
-
get supportsArtifacts(): boolean {
|
|
472
|
-
return (
|
|
473
|
-
this.definition.get !== undefined &&
|
|
474
|
-
this.definition.list !== undefined &&
|
|
475
|
-
this.definition.delete !== undefined
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/** Awaited artifact-row read. Errors propagate (read-after-write). */
|
|
480
|
-
async artifactGet(id: string): Promise<JsonValue | undefined> {
|
|
481
|
-
const get = this.definition.get;
|
|
482
|
-
if (get === undefined) {
|
|
483
|
-
throw new Error("storage get is not configured");
|
|
484
|
-
}
|
|
485
|
-
const value = await get(
|
|
486
|
-
storageKeys.artifact(this.agentName, id),
|
|
487
|
-
this.context("restore")
|
|
488
|
-
);
|
|
489
|
-
return value ?? undefined;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/** Awaited artifact-row write (straight to the sink, like author KV). */
|
|
493
|
-
async artifactPut(id: string, value: JsonValue): Promise<void> {
|
|
494
|
-
await this.definition.put(
|
|
495
|
-
storageKeys.artifact(this.agentName, id),
|
|
496
|
-
value,
|
|
497
|
-
this.context("policy")
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/** Awaited artifact-row delete. Errors propagate (cap eviction). */
|
|
502
|
-
async artifactDelete(id: string): Promise<void> {
|
|
503
|
-
const del = this.definition.delete;
|
|
504
|
-
if (del === undefined) {
|
|
505
|
-
throw new Error("storage delete is not configured");
|
|
506
|
-
}
|
|
507
|
-
await del(storageKeys.artifact(this.agentName, id), this.context("policy"));
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/** All artifact-row values for this agent. Errors propagate. */
|
|
511
|
-
async artifactList(): Promise<JsonValue[]> {
|
|
512
|
-
const list = this.definition.list;
|
|
513
|
-
if (list === undefined) {
|
|
514
|
-
throw new Error("storage list is not configured");
|
|
515
|
-
}
|
|
516
|
-
const entries = await list(
|
|
517
|
-
storageKeys.artifactPrefix(this.agentName),
|
|
518
|
-
this.context("restore")
|
|
519
|
-
);
|
|
520
|
-
return Array.isArray(entries) ? entries.map((entry) => entry.value) : [];
|
|
521
|
-
}
|
|
522
|
-
|
|
523
475
|
// ==========================================================================
|
|
524
476
|
// Evals (dedicated `evals` table)
|
|
525
477
|
// ==========================================================================
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three unrelated jobs `defineStorage` currently does, named separately.
|
|
3
|
+
*
|
|
4
|
+
* {@link import("./storage-coordinator.js").StorageCoordinator} implements all
|
|
5
|
+
* three today over one sink and one delivery queue. Splitting the *types* is
|
|
6
|
+
* the seam that lets each job change independently:
|
|
7
|
+
*
|
|
8
|
+
* - {@link FrameworkSessionStore} is framework state. Session records, event
|
|
9
|
+
* chunks, and the continuation index are how a session survives the process,
|
|
10
|
+
* so they belong to Agent Serve, not to the agent. Today they persist only
|
|
11
|
+
* when the author writes `agent/storage.ts` — the defect this seam exists to
|
|
12
|
+
* let us fix, by giving the framework its own provider that is automatic and
|
|
13
|
+
* not author-configurable.
|
|
14
|
+
* - {@link AuthorKvStore} is author state: whatever `ctx.host.kv` was handed.
|
|
15
|
+
* It converges onto the author file plane, which is a different destination
|
|
16
|
+
* from the one above.
|
|
17
|
+
* - {@link EvalAbStore} is playground/experiment bookkeeping with its own
|
|
18
|
+
* dedicated tables, and follows neither.
|
|
19
|
+
*
|
|
20
|
+
* Consumers should depend on the narrowest role they use. That is what keeps a
|
|
21
|
+
* later change to framework persistence from silently reaching author KV, and
|
|
22
|
+
* it is why these are interfaces rather than a comment on one class.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { ABMetricSample } from "../ab.js";
|
|
26
|
+
import type { StorageContext } from "../storage.js";
|
|
27
|
+
import type { JsonValue, SessionEvent, SessionRecord } from "../types.js";
|
|
28
|
+
import type { ABSnapshot } from "./ab-snapshot.js";
|
|
29
|
+
import type { EvalRunStorage } from "./eval-run-store.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Framework-owned session durability: what has to be true for a session to
|
|
33
|
+
* outlive the process that served it.
|
|
34
|
+
*
|
|
35
|
+
* Writes are fire-and-forget by design — a throwing backend is logged and its
|
|
36
|
+
* write dropped, because storage must never stall or fail a turn. That is
|
|
37
|
+
* at-most-once, and it is the property Track F later has to tighten, since a
|
|
38
|
+
* dropped record is a session that cannot be restored.
|
|
39
|
+
*/
|
|
40
|
+
export interface FrameworkSessionStore {
|
|
41
|
+
/** Durable session-record update; coalesced until the next flush. */
|
|
42
|
+
sessionRecord(record: SessionRecord): void;
|
|
43
|
+
/** One appended session event; batched per turn or debounce window. */
|
|
44
|
+
event(event: SessionEvent): void;
|
|
45
|
+
/** Marks an event delivered, for backends that track dispatch. */
|
|
46
|
+
eventDispatched(event: SessionEvent): void;
|
|
47
|
+
/** Flush everything buffered for one session (ordered: events, record). */
|
|
48
|
+
flushSession(sessionId: string, reason?: StorageContext["reason"]): void;
|
|
49
|
+
|
|
50
|
+
/** True when the backend can list every session in one bulk read. */
|
|
51
|
+
readonly canBulkRestore: boolean;
|
|
52
|
+
/** True when the backend is the Cursor-hosted one rather than an authored sink. */
|
|
53
|
+
readonly isCursorHosted: boolean;
|
|
54
|
+
|
|
55
|
+
listSessions(): Promise<SessionRecord[]>;
|
|
56
|
+
listSessionsFromSink(): Promise<SessionRecord[]>;
|
|
57
|
+
listSessionEvents(sessionId: string): Promise<SessionEvent[]>;
|
|
58
|
+
getSessionById(sessionId: string): Promise<SessionRecord | undefined>;
|
|
59
|
+
getSessionByContinuation(
|
|
60
|
+
channelId: string,
|
|
61
|
+
continuationKey: string
|
|
62
|
+
): Promise<SessionRecord | undefined>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Author-facing durable JSON behind `ctx.host.kv`.
|
|
67
|
+
*
|
|
68
|
+
* Deployment-wide on purpose: the point is state an author saves in one
|
|
69
|
+
* session and reads back in a later one, so unlike `ctx.host.files` there is
|
|
70
|
+
* no session scope to default to.
|
|
71
|
+
*/
|
|
72
|
+
export interface AuthorKvStore {
|
|
73
|
+
/** False when no backend is configured; callers fall back to local disk. */
|
|
74
|
+
readonly supportsAuthorKv: boolean;
|
|
75
|
+
kvGet(authorKey: string): Promise<JsonValue | undefined>;
|
|
76
|
+
kvPut(authorKey: string, value: JsonValue): Promise<void>;
|
|
77
|
+
kvDelete(authorKey: string): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Playground eval batches and live A/B metrics, on their own tables. */
|
|
81
|
+
export interface EvalAbStore {
|
|
82
|
+
evalRuns(): EvalRunStorage | undefined;
|
|
83
|
+
abSample(sample: ABMetricSample): void;
|
|
84
|
+
abSnapshot(snapshot: ABSnapshot): void;
|
|
85
|
+
getLatestAbSnapshot(): Promise<ABSnapshot | undefined>;
|
|
86
|
+
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
writeFile,
|
|
20
20
|
} from "node:fs/promises";
|
|
21
21
|
import { dirname, join, parse, relative, resolve, sep } from "node:path";
|
|
22
|
+
import { agentStoreKeys, DEPLOYMENT_STORE_MOUNT_PATH } from "../files.js";
|
|
22
23
|
import { MEMORY_DIR_NAME } from "../memory.js";
|
|
23
24
|
import type {
|
|
24
25
|
DiscoveredSkill,
|
|
@@ -38,20 +39,41 @@ export interface MaterializeWorkspaceOptions {
|
|
|
38
39
|
seed: boolean;
|
|
39
40
|
/**
|
|
40
41
|
* Agent state root. `<stateRoot>/memory/` (the durable cross-session
|
|
41
|
-
* directory the memory hook journals into
|
|
42
|
-
* the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* directory the memory hook journals into — on hosting a local mirror of
|
|
43
|
+
* the Agent Store journal) is created and symlinked into the workspace as
|
|
44
|
+
* `memory` so the agent reads it with plain file tools. Cloud VMs have no
|
|
45
|
+
* materialized workspace; they read the store mount directly (see
|
|
46
|
+
* {@link buildCloudMemorySection}).
|
|
46
47
|
*/
|
|
47
48
|
stateRoot: string;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* First-turn preamble section for cloud-runtime sessions on hosted
|
|
53
|
+
* deployments: cloud VMs read the memory journal from the store mount.
|
|
54
|
+
*/
|
|
55
|
+
export function buildCloudMemorySection(agentName: string): string {
|
|
56
|
+
const dir = `${DEPLOYMENT_STORE_MOUNT_PATH}/${agentStoreKeys.memoryDir(agentName)}`;
|
|
57
|
+
return [
|
|
58
|
+
"## Shared session memory",
|
|
59
|
+
"",
|
|
60
|
+
`The cross-session memory journal (the \`memory/\` directory your instructions may reference) lives at \`${dir}\` on this machine — read \`${dir}/journal.jsonl\` with your normal file tools.`,
|
|
61
|
+
].join("\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
/** Relative path of an agent-side tool script inside the session workspace. */
|
|
51
65
|
export function agentToolScriptPath(toolName: string): string {
|
|
52
66
|
return `.agent-serve/tools/${toolName}.sh`;
|
|
53
67
|
}
|
|
54
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Reserved MCP connection name under which authored server tools are served
|
|
71
|
+
* to cloud turns (the session-bound tool MCP endpoint). Shared between the
|
|
72
|
+
* turn's MCP config and the first-prompt server-tool catalog so the model is
|
|
73
|
+
* told exactly where those tools live.
|
|
74
|
+
*/
|
|
75
|
+
export const SERVER_TOOLS_MCP_SERVER_NAME = "agentsdk-tools";
|
|
76
|
+
|
|
55
77
|
/**
|
|
56
78
|
* Always-on identity line prepended when composing instructions. Sourced from
|
|
57
79
|
* {@link ResolvedAgent.name} (`defineAgent({ name })`, else package/dir name)
|
|
@@ -143,16 +165,64 @@ export function buildAgentToolsCatalog(
|
|
|
143
165
|
return `${parts.join("\n").trimEnd()}\n`;
|
|
144
166
|
}
|
|
145
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Markdown catalog of authored server tools for the cloud first prompt.
|
|
170
|
+
*
|
|
171
|
+
* Server tools execute on the serve host and reach cloud turns only as MCP
|
|
172
|
+
* tools on {@link SERVER_TOOLS_MCP_SERVER_NAME}. Instructions reference them
|
|
173
|
+
* by bare name, and the agent-tool catalog teaches a "recreate the script if
|
|
174
|
+
* missing" pattern — without this section a cloud model that cannot find a
|
|
175
|
+
* script for a server tool improvises a local substitute instead of calling
|
|
176
|
+
* the real tool, so its host-side effects silently never happen.
|
|
177
|
+
*/
|
|
178
|
+
export function buildServerToolsCatalog(
|
|
179
|
+
tools: DiscoveredTool[],
|
|
180
|
+
options: { mcpServerName: string }
|
|
181
|
+
): string {
|
|
182
|
+
const serverTools = tools.filter((tool) => tool.execution === "server");
|
|
183
|
+
if (serverTools.length === 0) {
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const parts: string[] = [
|
|
188
|
+
"## Server tools",
|
|
189
|
+
"",
|
|
190
|
+
`These tools execute on the agent-serve host, not in this environment. They are available ONLY as MCP tools on the \`${options.mcpServerName}\` MCP server — list that server's tools and call them by name through your MCP tooling.`,
|
|
191
|
+
"There are no local scripts for these tools. NEVER create, edit, or run a local script or other substitute for them: a local re-implementation cannot produce their real effects on the host.",
|
|
192
|
+
"",
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
for (const tool of serverTools) {
|
|
196
|
+
parts.push(`### \`${tool.name}\``);
|
|
197
|
+
parts.push("");
|
|
198
|
+
parts.push(tool.description.trim());
|
|
199
|
+
parts.push("");
|
|
200
|
+
parts.push("Input schema:");
|
|
201
|
+
parts.push("");
|
|
202
|
+
parts.push("```json");
|
|
203
|
+
parts.push(JSON.stringify(tool.inputSchema, null, 2));
|
|
204
|
+
parts.push("```");
|
|
205
|
+
parts.push("");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return `${parts.join("\n").trimEnd()}\n`;
|
|
209
|
+
}
|
|
210
|
+
|
|
146
211
|
/**
|
|
147
212
|
* The full AGENTS.md-equivalent context for an agent: instructions, identity
|
|
148
213
|
* section, and the agent-side tool catalog. Written to the session workspace
|
|
149
214
|
* as `AGENTS.md`, and inlined into the first turn's prompt when the harness
|
|
150
215
|
* cannot discover that file (cloud runtime, or a local workspace the harness
|
|
151
216
|
* will not treat as the project root — see {@link isNestedInGitRepo}).
|
|
217
|
+
*
|
|
218
|
+
* `serverToolsMcpName` is cloud-only: when the turn's MCP config carries the
|
|
219
|
+
* session-bound server-tool endpoint, the catalog names that server so the
|
|
220
|
+
* model calls the real tools instead of inventing local stand-ins. Local
|
|
221
|
+
* runtimes omit it — there the harness exposes server tools natively.
|
|
152
222
|
*/
|
|
153
223
|
export function buildAgentsMdContent(
|
|
154
224
|
agent: ResolvedAgent,
|
|
155
|
-
options: { includeScripts: boolean }
|
|
225
|
+
options: { includeScripts: boolean; serverToolsMcpName?: string }
|
|
156
226
|
): string {
|
|
157
227
|
return [
|
|
158
228
|
agent.instructions?.trim() ?? "",
|
|
@@ -160,6 +230,11 @@ export function buildAgentsMdContent(
|
|
|
160
230
|
buildAgentToolsCatalog(agent.tools, {
|
|
161
231
|
includeScripts: options.includeScripts,
|
|
162
232
|
}),
|
|
233
|
+
options.serverToolsMcpName === undefined
|
|
234
|
+
? ""
|
|
235
|
+
: buildServerToolsCatalog(agent.tools, {
|
|
236
|
+
mcpServerName: options.serverToolsMcpName,
|
|
237
|
+
}),
|
|
163
238
|
]
|
|
164
239
|
.filter((part) => part !== "")
|
|
165
240
|
.join("\n\n");
|
package/src/memory.ts
CHANGED
|
@@ -16,14 +16,21 @@
|
|
|
16
16
|
* path — typically a server tool under `agent/tools/`, which can resolve
|
|
17
17
|
* state via `ctx.stateRoot`.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* On Cursor-managed hosting the default is {@link agentStoreMemoryBackend}:
|
|
20
|
+
* the journal lives on the deployment's Agent Store (survives deploys,
|
|
21
|
+
* visible on cloud VMs under the store mount) and is mirrored to
|
|
22
|
+
* `<stateRoot>/memory/journal.jsonl` so the workspace symlink read path
|
|
23
|
+
* keeps working for local-runtime turns.
|
|
23
24
|
*/
|
|
24
25
|
|
|
25
|
-
import {
|
|
26
|
+
import { randomUUID } from "node:crypto";
|
|
27
|
+
import { appendFile, mkdir, rename, stat, writeFile } from "node:fs/promises";
|
|
26
28
|
import { join } from "node:path";
|
|
29
|
+
import { agentStoreKeys, FileConflictError, type FileSink } from "./files.js";
|
|
30
|
+
import {
|
|
31
|
+
cursorHostedFiles,
|
|
32
|
+
isCursorHostedFilesAvailable,
|
|
33
|
+
} from "./files-backends/cursor-hosted.js";
|
|
27
34
|
import { defineHook } from "./hooks.js";
|
|
28
35
|
import type { HookContext, HookDefinition, TurnUsage } from "./types.js";
|
|
29
36
|
|
|
@@ -48,11 +55,11 @@ export type TurnMemoryRecord = {
|
|
|
48
55
|
usage?: TurnUsage;
|
|
49
56
|
};
|
|
50
57
|
|
|
51
|
-
/** Where turn records go. Receives the agent's state root per append. */
|
|
58
|
+
/** Where turn records go. Receives the agent's state root and name per append. */
|
|
52
59
|
export interface MemoryBackend {
|
|
53
60
|
appendTurn(
|
|
54
61
|
record: TurnMemoryRecord,
|
|
55
|
-
ctx: { stateRoot: string }
|
|
62
|
+
ctx: { stateRoot: string; agentName: string }
|
|
56
63
|
): Promise<void>;
|
|
57
64
|
}
|
|
58
65
|
|
|
@@ -65,7 +72,7 @@ export interface FileMemoryBackendOptions {
|
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
/**
|
|
68
|
-
*
|
|
75
|
+
* Local default backend: append-only JSONL at `<stateRoot>/memory/journal.jsonl`
|
|
69
76
|
* — the directory the framework symlinks into session workspaces. Rotated
|
|
70
77
|
* segments stay alongside as `journal-<epoch-ms>.jsonl`.
|
|
71
78
|
*/
|
|
@@ -105,6 +112,112 @@ export function fileMemoryBackend(
|
|
|
105
112
|
};
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
export interface AgentStoreMemoryBackendOptions {
|
|
116
|
+
/** Sink override (tests). Defaults to the deployment's Agent Store. */
|
|
117
|
+
sink?: FileSink;
|
|
118
|
+
/** Rotation threshold, same meaning as {@link FileMemoryBackendOptions}. */
|
|
119
|
+
maxJournalBytes?: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Journal on the deployment's Agent Store — durable across deploys, visible
|
|
124
|
+
* on cloud VMs under the store mount. Appends are read-modify-write with
|
|
125
|
+
* etag preconditions (retried on a lost race), then mirrored to
|
|
126
|
+
* `<stateRoot>/memory/journal.jsonl` for the workspace symlink read path.
|
|
127
|
+
*/
|
|
128
|
+
export function agentStoreMemoryBackend(
|
|
129
|
+
options: AgentStoreMemoryBackendOptions = {}
|
|
130
|
+
): MemoryBackend {
|
|
131
|
+
const sink = options.sink ?? cursorHostedFiles();
|
|
132
|
+
const maxJournalBytes = options.maxJournalBytes ?? 5 * 1024 * 1024;
|
|
133
|
+
// Appends are chained per journal key (same shape as fileMemoryBackend);
|
|
134
|
+
// a failed append must not poison the chain for later turns.
|
|
135
|
+
const appendChains = new Map<string, Promise<void>>();
|
|
136
|
+
|
|
137
|
+
const append = async (
|
|
138
|
+
record: TurnMemoryRecord,
|
|
139
|
+
ctx: { stateRoot: string; agentName: string }
|
|
140
|
+
): Promise<void> => {
|
|
141
|
+
const key = agentStoreKeys.memoryJournal(ctx.agentName);
|
|
142
|
+
const line = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
|
|
143
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
144
|
+
const current = await sink.get(key);
|
|
145
|
+
const currentBody =
|
|
146
|
+
current === undefined
|
|
147
|
+
? new Uint8Array(0)
|
|
148
|
+
: current instanceof Uint8Array
|
|
149
|
+
? current
|
|
150
|
+
: current.body;
|
|
151
|
+
const etag =
|
|
152
|
+
current === undefined || current instanceof Uint8Array
|
|
153
|
+
? undefined
|
|
154
|
+
: current.etag;
|
|
155
|
+
let next: Uint8Array;
|
|
156
|
+
let rotatedStamp: string | undefined;
|
|
157
|
+
if (currentBody.byteLength >= maxJournalBytes) {
|
|
158
|
+
rotatedStamp = `${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
159
|
+
await sink.put(
|
|
160
|
+
agentStoreKeys.memoryJournalRotated(ctx.agentName, rotatedStamp),
|
|
161
|
+
currentBody,
|
|
162
|
+
{ ifMatch: null }
|
|
163
|
+
);
|
|
164
|
+
next = line;
|
|
165
|
+
} else {
|
|
166
|
+
next = Buffer.concat([currentBody, line]);
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
await sink.put(key, next, {
|
|
170
|
+
ifMatch: current === undefined ? null : etag,
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof FileConflictError && attempt < 2) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
await mirrorJournalLocally(ctx.stateRoot, next);
|
|
179
|
+
if (rotatedStamp !== undefined) {
|
|
180
|
+
// Rotated segments sit beside the live journal locally too, so the
|
|
181
|
+
// workspace symlink read path keeps pre-rotation history.
|
|
182
|
+
await writeFile(
|
|
183
|
+
join(ctx.stateRoot, MEMORY_DIR_NAME, `journal-${rotatedStamp}.jsonl`),
|
|
184
|
+
currentBody
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
async appendTurn(record, ctx) {
|
|
193
|
+
const key = agentStoreKeys.memoryJournal(ctx.agentName);
|
|
194
|
+
const prior = appendChains.get(key) ?? Promise.resolve();
|
|
195
|
+
const next = prior.catch(() => {}).then(() => append(record, ctx));
|
|
196
|
+
appendChains.set(key, next);
|
|
197
|
+
await next;
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Atomic full rewrite of the local mirror; also hydrates a fresh pod after a deploy. */
|
|
203
|
+
async function mirrorJournalLocally(
|
|
204
|
+
stateRoot: string,
|
|
205
|
+
contents: Uint8Array
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
const dir = join(stateRoot, MEMORY_DIR_NAME);
|
|
208
|
+
await mkdir(dir, { recursive: true });
|
|
209
|
+
const tmp = join(dir, `.tmp-${randomUUID()}`);
|
|
210
|
+
await writeFile(tmp, contents);
|
|
211
|
+
await rename(tmp, join(dir, "journal.jsonl"));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Hosted deployments journal to the Agent Store; local dev to plain files. */
|
|
215
|
+
function defaultMemoryBackend(): MemoryBackend {
|
|
216
|
+
return isCursorHostedFilesAvailable()
|
|
217
|
+
? agentStoreMemoryBackend()
|
|
218
|
+
: fileMemoryBackend();
|
|
219
|
+
}
|
|
220
|
+
|
|
108
221
|
export interface MemoryHookOptions {
|
|
109
222
|
/** Where records go. Defaults to {@link fileMemoryBackend}. */
|
|
110
223
|
backend?: MemoryBackend;
|
|
@@ -126,7 +239,7 @@ function truncate(text: string, max: number): string {
|
|
|
126
239
|
* ```
|
|
127
240
|
*/
|
|
128
241
|
export function memoryHook(options: MemoryHookOptions = {}): HookDefinition {
|
|
129
|
-
const backend = options.backend ??
|
|
242
|
+
const backend = options.backend ?? defaultMemoryBackend();
|
|
130
243
|
const maxTextLength = options.maxTextLength ?? 2000;
|
|
131
244
|
// Inbound message per turn (truncated at receipt so large pastes are not
|
|
132
245
|
// retained), keyed `<sessionId>/<turnId>` — `message.received` and the
|
|
@@ -167,7 +280,10 @@ export function memoryHook(options: MemoryHookOptions = {}): HookDefinition {
|
|
|
167
280
|
if (usage !== undefined) {
|
|
168
281
|
entry.usage = usage;
|
|
169
282
|
}
|
|
170
|
-
await backend.appendTurn(entry, {
|
|
283
|
+
await backend.appendTurn(entry, {
|
|
284
|
+
stateRoot: ctx.stateRoot,
|
|
285
|
+
agentName: ctx.agent.name,
|
|
286
|
+
});
|
|
171
287
|
};
|
|
172
288
|
|
|
173
289
|
const sweepSession = (ctx: HookContext): void => {
|
package/src/storage.ts
CHANGED
|
@@ -276,7 +276,6 @@ export const STORAGE_KEY_ROOT = "agentkit/v1" as const;
|
|
|
276
276
|
* | `agentkit/v1/{agent}/session-events/{sessionId}/{index}` | `SessionEvent[]` chunk (index = first event's index, zero-padded) |
|
|
277
277
|
* | `agentkit/v1/{agent}/continuation/{channelId}/{token}` | `{ sessionId }` |
|
|
278
278
|
* | `agentkit/v1/{agent}/kv/{key}` | Author JSON via {@link HostContext.kv} |
|
|
279
|
-
* | `agentkit/v1/{agent}/artifacts/{id}` | `ArtifactRecord` via `ctx.artifacts` |
|
|
280
279
|
*
|
|
281
280
|
* Eval-run and A/B history do not flow through this KV scheme — they have
|
|
282
281
|
* dedicated tables ({@link StorageConfig.evals} / {@link StorageConfig.abs}).
|
|
@@ -307,11 +306,6 @@ export const storageKeys = {
|
|
|
307
306
|
kv: (agent: string, key: string): string =>
|
|
308
307
|
`${STORAGE_KEY_ROOT}/${agent}/kv/${keySegment(key)}`,
|
|
309
308
|
kvPrefix: (agent: string): string => `${STORAGE_KEY_ROOT}/${agent}/kv/`,
|
|
310
|
-
/** Tagged artifact rows (see `ctx.artifacts` / `ArtifactsApi`). */
|
|
311
|
-
artifact: (agent: string, id: string): string =>
|
|
312
|
-
`${STORAGE_KEY_ROOT}/${agent}/artifacts/${keySegment(id)}`,
|
|
313
|
-
artifactPrefix: (agent: string): string =>
|
|
314
|
-
`${STORAGE_KEY_ROOT}/${agent}/artifacts/`,
|
|
315
309
|
} as const;
|
|
316
310
|
|
|
317
311
|
/**
|
package/src/types.ts
CHANGED
|
@@ -1838,6 +1838,8 @@ export interface ArtifactRecord {
|
|
|
1838
1838
|
key?: string;
|
|
1839
1839
|
title?: string;
|
|
1840
1840
|
data: JsonValue;
|
|
1841
|
+
/** Present when the artifact carries a file/blob payload (served at `GET /v1/artifacts/:id/content`). */
|
|
1842
|
+
content?: { size: number; contentType?: string };
|
|
1841
1843
|
sessionId?: string;
|
|
1842
1844
|
turnId?: string;
|
|
1843
1845
|
source: ArtifactSource;
|
|
@@ -1853,6 +1855,13 @@ export interface ArtifactTagInput {
|
|
|
1853
1855
|
title?: string;
|
|
1854
1856
|
/** Upsert key: tagging the same key again replaces the row. */
|
|
1855
1857
|
key?: string;
|
|
1858
|
+
/**
|
|
1859
|
+
* File/blob payload, served via `GET /v1/artifacts/:id/content`.
|
|
1860
|
+
* Re-tagging a keyed artifact without `contents` keeps the existing payload.
|
|
1861
|
+
*/
|
|
1862
|
+
contents?: string | Uint8Array;
|
|
1863
|
+
/** MIME type reported when serving {@link contents}. */
|
|
1864
|
+
contentType?: string;
|
|
1856
1865
|
/**
|
|
1857
1866
|
* Session to attribute (and stream an `artifact.tagged` event to). On
|
|
1858
1867
|
* session-bound facades ({@link ToolContext.artifacts},
|
|
@@ -2460,6 +2469,12 @@ export interface ServeOptions {
|
|
|
2460
2469
|
* Local turns always use the loopback URL.
|
|
2461
2470
|
*/
|
|
2462
2471
|
publicUrl?: string;
|
|
2472
|
+
/**
|
|
2473
|
+
* Authenticated HTTP MCP URL that proxies this hosted deployment's
|
|
2474
|
+
* `/v1/mcp/tools` endpoint. Cloud turns use it to call authored server tools
|
|
2475
|
+
* on the AgentSDK host. The resolved Cursor API key authenticates requests.
|
|
2476
|
+
*/
|
|
2477
|
+
cloudToolsUrl?: string;
|
|
2463
2478
|
/**
|
|
2464
2479
|
* Pull GitHub webhook metadata from `/v0/scm-events` (Cursor account auth).
|
|
2465
2480
|
* The stream is read as the host's Cursor user, so this requires a signed-in
|