@cursor/july 0.1.22 → 0.1.23
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 +5 -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 +21 -3
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.BKNKMLp4.js → app.DYcC9FY-.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.sOSfEZR6.js → VPLocalSearchBox.o4N_knTV.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BvCHT5lj.js → theme.DQ-njyo0.js} +2 -2
- package/dist/docs/assets/index.md.Dfv5ic9t.js +20 -0
- package/dist/docs/assets/{index.md.t0TM2Qzz.lean.js → index.md.Dfv5ic9t.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.DnYfr5V2.js → reference_cli.md.ccoKOoXt.js} +4 -3
- package/dist/docs/assets/{reference_cli.md.DnYfr5V2.lean.js → reference_cli.md.ccoKOoXt.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 +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 +6 -6
- 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 +7 -6
- 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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/cli-docs.d.ts +34 -0
- package/dist/internal/cli-docs.d.ts.map +1 -0
- package/dist/internal/cli-docs.js +162 -0
- package/dist/internal/distribution.d.ts +2 -1
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +3 -1
- package/dist/internal/docs-site.d.ts +4 -2
- package/dist/internal/docs-site.d.ts.map +1 -1
- package/dist/internal/docs-site.js +15 -11
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +19 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +5 -0
- package/dist/internal/workspace.d.ts +9 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +52 -5
- package/dist/memory.d.ts +79 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +164 -0
- package/dist/playground/assets/index-DqXdAFGa.js +85 -0
- package/dist/playground/index.html +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +8 -1
- package/docs/reference/cli.md +20 -4
- package/package.json +9 -1
- package/src/bin/agent-serve.ts +23 -3
- package/src/bin/agent-serve.version.test.ts +2 -0
- package/src/index.ts +2 -0
- package/src/internal/cli-docs.test.ts +161 -0
- package/src/internal/cli-docs.ts +191 -0
- package/src/internal/distribution.ts +3 -1
- package/src/internal/docs-site.ts +19 -11
- package/src/internal/init-project.test.ts +1 -0
- package/src/internal/init-project.ts +22 -0
- package/src/internal/session-engine.ts +5 -0
- package/src/internal/workspace.test.ts +127 -1
- package/src/internal/workspace.ts +81 -5
- package/src/memory.ts +215 -0
- package/src/types.ts +11 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.YFE6WoMB.js +0 -1
- package/dist/docs/assets/index.md.t0TM2Qzz.js +0 -20
- package/dist/playground/assets/index-dshZQJCp.js +0 -85
package/src/memory.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session memory — a journal of what every session of this agent did, so
|
|
3
|
+
* later sessions can recall and build on past work.
|
|
4
|
+
*
|
|
5
|
+
* The write side is {@link memoryHook}, authored as a thin re-export at
|
|
6
|
+
* `agent/hooks/memory.ts` (scaffolded by `agentkit init`): after each turn
|
|
7
|
+
* it appends one record (timestamp, session, user message, final result,
|
|
8
|
+
* usage) to `<stateRoot>/memory/journal.jsonl`.
|
|
9
|
+
*
|
|
10
|
+
* There is no read-side API. The framework symlinks `<stateRoot>/memory`
|
|
11
|
+
* into every session workspace as `memory/`, so agents read the journal
|
|
12
|
+
* with their normal file tools (`instructions.md` points them at it). That
|
|
13
|
+
* filesystem visibility is why memory does not route through
|
|
14
|
+
* `defineStorage` sinks, which are opaque to the model. A custom
|
|
15
|
+
* {@link MemoryBackend} (database, vector store, …) supplies its own read
|
|
16
|
+
* path — typically a server tool under `agent/tools/`, which can resolve
|
|
17
|
+
* state via `ctx.stateRoot`.
|
|
18
|
+
*
|
|
19
|
+
* TODO(agent-store): once agent-serve can mount the agent's AgentStore
|
|
20
|
+
* (durable S3-backed filesystem shared across serve hosts and cloud VMs),
|
|
21
|
+
* ship an AgentStore-backed backend and make it the default. The journal
|
|
22
|
+
* format is designed to survive that move unchanged.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { appendFile, mkdir, rename, stat } from "node:fs/promises";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
import { defineHook } from "./hooks.js";
|
|
28
|
+
import type { HookContext, HookDefinition, TurnUsage } from "./types.js";
|
|
29
|
+
|
|
30
|
+
/** Name of the shared memory directory under the agent state root. */
|
|
31
|
+
export const MEMORY_DIR_NAME = "memory";
|
|
32
|
+
|
|
33
|
+
/** One journal line: what a single turn did. */
|
|
34
|
+
export type TurnMemoryRecord = {
|
|
35
|
+
/** ISO timestamp of when the turn finished. */
|
|
36
|
+
at: string;
|
|
37
|
+
sessionId: string;
|
|
38
|
+
channelId: string;
|
|
39
|
+
status: "completed" | "failed";
|
|
40
|
+
/** Session title, when the channel set one. */
|
|
41
|
+
title?: string;
|
|
42
|
+
/** Cursor SDK agent id (cloud: `bc-…`; local: the session id). */
|
|
43
|
+
sdkAgentId?: string;
|
|
44
|
+
/** The user message that started the turn (truncated). */
|
|
45
|
+
userMessage?: string;
|
|
46
|
+
/** Final assistant text of the turn, or the failure message (truncated). */
|
|
47
|
+
result?: string;
|
|
48
|
+
usage?: TurnUsage;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Where turn records go. Receives the agent's state root per append. */
|
|
52
|
+
export interface MemoryBackend {
|
|
53
|
+
appendTurn(
|
|
54
|
+
record: TurnMemoryRecord,
|
|
55
|
+
ctx: { stateRoot: string }
|
|
56
|
+
): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FileMemoryBackendOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Rotate `journal.jsonl` to a timestamped sibling once it exceeds this
|
|
62
|
+
* size, keeping the file agents grep small. Default 5 MiB.
|
|
63
|
+
*/
|
|
64
|
+
maxJournalBytes?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Default backend: append-only JSONL at `<stateRoot>/memory/journal.jsonl`
|
|
69
|
+
* — the directory the framework symlinks into session workspaces. Rotated
|
|
70
|
+
* segments stay alongside as `journal-<epoch-ms>.jsonl`.
|
|
71
|
+
*/
|
|
72
|
+
export function fileMemoryBackend(
|
|
73
|
+
options: FileMemoryBackendOptions = {}
|
|
74
|
+
): MemoryBackend {
|
|
75
|
+
const maxJournalBytes = options.maxJournalBytes ?? 5 * 1024 * 1024;
|
|
76
|
+
// Event dispatch is serialized per session but concurrent across
|
|
77
|
+
// sessions, so appends to the shared journal are chained per path. A
|
|
78
|
+
// failed append must not poison the chain for later turns.
|
|
79
|
+
const appendChains = new Map<string, Promise<void>>();
|
|
80
|
+
// Same-millisecond rotations must not reuse a segment name — POSIX
|
|
81
|
+
// rename would silently replace the earlier archive.
|
|
82
|
+
let rotationSeq = 0;
|
|
83
|
+
return {
|
|
84
|
+
async appendTurn(record, ctx) {
|
|
85
|
+
const dir = join(ctx.stateRoot, MEMORY_DIR_NAME);
|
|
86
|
+
const path = join(dir, "journal.jsonl");
|
|
87
|
+
const prior = appendChains.get(path) ?? Promise.resolve();
|
|
88
|
+
const next = prior
|
|
89
|
+
.catch(() => {})
|
|
90
|
+
.then(async () => {
|
|
91
|
+
await mkdir(dir, { recursive: true });
|
|
92
|
+
const size = (await stat(path).catch(() => undefined))?.size ?? 0;
|
|
93
|
+
if (size >= maxJournalBytes) {
|
|
94
|
+
rotationSeq += 1;
|
|
95
|
+
await rename(
|
|
96
|
+
path,
|
|
97
|
+
join(dir, `journal-${Date.now()}-${rotationSeq}.jsonl`)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
await appendFile(path, `${JSON.stringify(record)}\n`, "utf8");
|
|
101
|
+
});
|
|
102
|
+
appendChains.set(path, next);
|
|
103
|
+
await next;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface MemoryHookOptions {
|
|
109
|
+
/** Where records go. Defaults to {@link fileMemoryBackend}. */
|
|
110
|
+
backend?: MemoryBackend;
|
|
111
|
+
/** Truncation cap for stored userMessage / result text. Default 2000. */
|
|
112
|
+
maxTextLength?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function truncate(text: string, max: number): string {
|
|
116
|
+
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Observe-only hook that journals every turn. Author it as
|
|
121
|
+
* `agent/hooks/memory.ts`:
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* import { memoryHook } from "@cursor/july/memory";
|
|
125
|
+
* export default memoryHook();
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export function memoryHook(options: MemoryHookOptions = {}): HookDefinition {
|
|
129
|
+
const backend = options.backend ?? fileMemoryBackend();
|
|
130
|
+
const maxTextLength = options.maxTextLength ?? 2000;
|
|
131
|
+
// Inbound message per turn (truncated at receipt so large pastes are not
|
|
132
|
+
// retained), keyed `<sessionId>/<turnId>` — `message.received` and the
|
|
133
|
+
// turn-end events share a turnId. Entries drop at turn end and any
|
|
134
|
+
// stragglers (turns that never reached a terminal event) at session end.
|
|
135
|
+
const pendingMessages = new Map<string, string>();
|
|
136
|
+
const pendingKey = (ctx: HookContext, turnId: string | undefined): string =>
|
|
137
|
+
`${ctx.session.id}/${turnId ?? "?"}`;
|
|
138
|
+
|
|
139
|
+
const record = async (
|
|
140
|
+
ctx: HookContext,
|
|
141
|
+
turnId: string | undefined,
|
|
142
|
+
status: TurnMemoryRecord["status"],
|
|
143
|
+
result: string | undefined,
|
|
144
|
+
usage: TurnUsage | undefined
|
|
145
|
+
): Promise<void> => {
|
|
146
|
+
const key = pendingKey(ctx, turnId);
|
|
147
|
+
const userMessage = pendingMessages.get(key);
|
|
148
|
+
pendingMessages.delete(key);
|
|
149
|
+
const entry: TurnMemoryRecord = {
|
|
150
|
+
at: new Date().toISOString(),
|
|
151
|
+
sessionId: ctx.session.id,
|
|
152
|
+
channelId: ctx.channel.id,
|
|
153
|
+
status,
|
|
154
|
+
};
|
|
155
|
+
if (ctx.session.title !== undefined) {
|
|
156
|
+
entry.title = ctx.session.title;
|
|
157
|
+
}
|
|
158
|
+
if (ctx.session.sdkAgentId !== undefined) {
|
|
159
|
+
entry.sdkAgentId = ctx.session.sdkAgentId;
|
|
160
|
+
}
|
|
161
|
+
if (userMessage !== undefined) {
|
|
162
|
+
entry.userMessage = userMessage;
|
|
163
|
+
}
|
|
164
|
+
if (result !== undefined) {
|
|
165
|
+
entry.result = truncate(result, maxTextLength);
|
|
166
|
+
}
|
|
167
|
+
if (usage !== undefined) {
|
|
168
|
+
entry.usage = usage;
|
|
169
|
+
}
|
|
170
|
+
await backend.appendTurn(entry, { stateRoot: ctx.stateRoot });
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const sweepSession = (ctx: HookContext): void => {
|
|
174
|
+
for (const key of pendingMessages.keys()) {
|
|
175
|
+
if (key.startsWith(`${ctx.session.id}/`)) {
|
|
176
|
+
pendingMessages.delete(key);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return defineHook({
|
|
182
|
+
events: {
|
|
183
|
+
async "message.received"(event, ctx) {
|
|
184
|
+
pendingMessages.set(
|
|
185
|
+
pendingKey(ctx, event.turnId),
|
|
186
|
+
truncate(event.data.text, maxTextLength)
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
async "turn.completed"(event, ctx) {
|
|
190
|
+
await record(
|
|
191
|
+
ctx,
|
|
192
|
+
event.turnId,
|
|
193
|
+
"completed",
|
|
194
|
+
event.data.result,
|
|
195
|
+
event.data.usage
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
async "turn.failed"(event, ctx) {
|
|
199
|
+
await record(
|
|
200
|
+
ctx,
|
|
201
|
+
event.turnId,
|
|
202
|
+
"failed",
|
|
203
|
+
event.data.message,
|
|
204
|
+
undefined
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
async "session.completed"(_event, ctx) {
|
|
208
|
+
sweepSession(ctx);
|
|
209
|
+
},
|
|
210
|
+
async "session.failed"(_event, ctx) {
|
|
211
|
+
sweepSession(ctx);
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -492,6 +492,11 @@ export interface ToolContext {
|
|
|
492
492
|
session: SessionInfo;
|
|
493
493
|
/** Absolute path of the session's materialized workspace directory. */
|
|
494
494
|
workspaceDir: string;
|
|
495
|
+
/**
|
|
496
|
+
* Absolute path of the agent's durable state root (shared across every
|
|
497
|
+
* session of this agent).
|
|
498
|
+
*/
|
|
499
|
+
stateRoot: string;
|
|
495
500
|
/** Shared host services (MCP / GitHub / Slack). */
|
|
496
501
|
host: HostContext;
|
|
497
502
|
/**
|
|
@@ -1564,6 +1569,12 @@ export interface HookContext {
|
|
|
1564
1569
|
agent: { name: string };
|
|
1565
1570
|
channel: { id: string; continuationToken: string | null };
|
|
1566
1571
|
session: SessionInfo;
|
|
1572
|
+
/**
|
|
1573
|
+
* Absolute path of the agent's durable state root (shared across every
|
|
1574
|
+
* session of this agent). Hooks that maintain derived state (e.g. the
|
|
1575
|
+
* memory journal) write here.
|
|
1576
|
+
*/
|
|
1577
|
+
stateRoot: string;
|
|
1567
1578
|
}
|
|
1568
1579
|
|
|
1569
1580
|
export type HookHandler<TEvent extends SessionEvent = SessionEvent> = (
|