@cursor/july 0.1.48 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -3
- package/README.md +7 -14
- package/dist/bin/agent-serve.js +22 -8
- package/dist/channels/slack/init.d.ts +2 -3
- package/dist/channels/slack/init.d.ts.map +1 -1
- package/dist/channels/slack/init.js +8 -43
- package/dist/channels/slack/manifest.d.ts +3 -3
- package/dist/channels/slack/manifest.js +3 -3
- package/dist/channels/slack/setup.d.ts +2 -11
- package/dist/channels/slack/setup.d.ts.map +1 -1
- package/dist/channels/slack/setup.js +26 -94
- package/dist/channels/slack/types.d.ts +3 -4
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +5 -5
- package/dist/docs/assets/{ab.md.hdxsvX5M.js → ab.md.DYjwREAP.js} +2 -2
- package/dist/docs/assets/{ab.md.hdxsvX5M.lean.js → ab.md.DYjwREAP.lean.js} +1 -1
- package/dist/docs/assets/{app.BC1P20xI.js → app.C51TX1Bw.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D2DpXdZN.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.C_900xn2.js → VPLocalSearchBox.BB9HCJPq.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CT135Jhh.js → theme.Dp-0SZcu.js} +2 -2
- package/dist/docs/assets/{deployment.md.Cdp4rTE4.js → deployment.md.B8kW-h7P.js} +1 -1
- package/dist/docs/assets/{evals.md.qeoGgPw0.js → evals.md.CVe_O75-.js} +3 -3
- package/dist/docs/assets/{evals.md.qeoGgPw0.lean.js → evals.md.CVe_O75-.lean.js} +1 -1
- package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.js → example-agents_benny.md.B-LIDGja.js} +1 -1
- package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.js → example-agents_oncall.md.CBmyuAKc.js} +2 -2
- package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.js → guides_human-in-the-loop.md.Cf8kIIqC.js} +2 -2
- package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.lean.js → guides_human-in-the-loop.md.Cf8kIIqC.lean.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.zriQpU_9.js → guides_slack.md.D2nTRu9M.js} +7 -12
- package/dist/docs/assets/guides_slack.md.D2nTRu9M.lean.js +1 -0
- package/dist/docs/assets/{reference_cli.md.BDmjPPAm.js → reference_cli.md.CGKfz_Yj.js} +3 -7
- package/dist/docs/assets/{reference_cli.md.BDmjPPAm.lean.js → reference_cli.md.CGKfz_Yj.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.DGrw_wOu.js → reference_http-api.md.DMbdFGVQ.js} +1 -1
- package/dist/docs/assets/{storage.md.CJYd5YmN.js → storage.md.D8NQ389l.js} +3 -3
- package/dist/docs/assets/storage.md.D8NQ389l.lean.js +1 -0
- package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.js → troubleshooting.md.C6n3wCr6.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +5 -5
- package/dist/docs/evals.html +6 -6
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +5 -5
- 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 +6 -6
- 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 +5 -5
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +11 -16
- 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/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +6 -10
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +5 -5
- 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 +5 -5
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/internal/cli-ax.js +3 -3
- package/dist/internal/cli-cursor.d.ts +18 -0
- package/dist/internal/cli-cursor.d.ts.map +1 -1
- package/dist/internal/cli-cursor.js +25 -0
- package/dist/internal/cli-slack.d.ts +3 -4
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +126 -325
- package/dist/internal/convert-automation/convert-workflow.js +2 -2
- package/dist/internal/event-log.d.ts +17 -0
- package/dist/internal/event-log.d.ts.map +1 -1
- package/dist/internal/event-log.js +32 -2
- package/dist/internal/process-memory-telemetry.d.ts +90 -0
- package/dist/internal/process-memory-telemetry.d.ts.map +1 -0
- package/dist/internal/process-memory-telemetry.js +259 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +9 -10
- package/dist/internal/session-engine.d.ts +4 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +19 -5
- package/dist/internal/slack-provision-client.d.ts +41 -6
- package/dist/internal/slack-provision-client.d.ts.map +1 -1
- package/dist/internal/slack-provision-client.js +53 -7
- package/dist/playground/assets/{index-DEVTyeOu.js → index-BKMz70wx.js} +41 -41
- package/dist/playground/assets/index-BmMrulSc.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +36 -10
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +82 -6
- package/docs/ab.md +4 -3
- package/docs/deployment.md +1 -1
- package/docs/evals.md +10 -7
- package/docs/example-agents/benny.md +2 -3
- package/docs/example-agents/oncall.md +4 -5
- package/docs/guides/human-in-the-loop.md +1 -1
- package/docs/guides/slack.md +51 -112
- package/docs/reference/cli.md +14 -37
- package/docs/reference/http-api.md +4 -3
- package/docs/storage.md +38 -22
- package/docs/troubleshooting.md +2 -2
- package/package.json +1 -1
- package/skills/create-agent/SKILL.md +4 -5
- package/skills/setup-slack/SKILL.md +74 -132
- package/src/bin/agent-serve.ts +26 -9
- package/src/channels/slack/init.ts +10 -71
- package/src/channels/slack/manifest.ts +3 -3
- package/src/channels/slack/setup.ts +25 -103
- package/src/channels/slack/types.ts +3 -4
- package/src/internal/cli-ax.ts +3 -3
- package/src/internal/cli-cursor.ts +34 -0
- package/src/internal/cli-slack.ts +136 -394
- package/src/internal/convert-automation/convert-workflow.ts +2 -2
- package/src/internal/event-log.ts +45 -7
- package/src/internal/process-memory-telemetry.ts +373 -0
- package/src/internal/server.ts +13 -0
- package/src/internal/session-engine.ts +20 -1
- package/src/internal/slack-provision-client.ts +90 -9
- package/src/storage.ts +130 -11
- package/dist/docs/assets/chunks/@localSearchIndexroot.DV5SAHnQ.js +0 -1
- package/dist/docs/assets/guides_slack.md.zriQpU_9.lean.js +0 -1
- package/dist/docs/assets/storage.md.CJYd5YmN.lean.js +0 -1
- package/dist/playground/assets/index-CiCmH6R-.css +0 -1
- /package/dist/docs/assets/{deployment.md.Cdp4rTE4.lean.js → deployment.md.B8kW-h7P.lean.js} +0 -0
- /package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.lean.js → example-agents_benny.md.B-LIDGja.lean.js} +0 -0
- /package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.lean.js → example-agents_oncall.md.CBmyuAKc.lean.js} +0 -0
- /package/dist/docs/assets/{reference_http-api.md.DGrw_wOu.lean.js → reference_http-api.md.DMbdFGVQ.lean.js} +0 -0
- /package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.lean.js → troubleshooting.md.C6n3wCr6.lean.js} +0 -0
|
@@ -999,7 +999,7 @@ function emitSlackChannel(
|
|
|
999
999
|
}
|
|
1000
1000
|
if (socketMode) {
|
|
1001
1001
|
out.setupSteps.push(
|
|
1002
|
-
`Slack channel-watch / reaction / channel-created triggers need a dedicated Socket Mode Slack app (not the Cursor connection): run \`${CLI_COMMAND_NAME} slack
|
|
1002
|
+
`Slack channel-watch / reaction / channel-created triggers need a dedicated Socket Mode Slack app (not the Cursor connection): run \`${CLI_COMMAND_NAME} slack create --channel-posts\`, then finish in the dashboard wizard.`
|
|
1003
1003
|
);
|
|
1004
1004
|
}
|
|
1005
1005
|
|
|
@@ -1069,7 +1069,7 @@ function emitSlackChannel(
|
|
|
1069
1069
|
body = [
|
|
1070
1070
|
"//",
|
|
1071
1071
|
"// This channel needs a dedicated Socket Mode Slack app (not the Cursor",
|
|
1072
|
-
`// connection): run \`${CLI_COMMAND_NAME} slack
|
|
1072
|
+
`// connection): run \`${CLI_COMMAND_NAME} slack create --channel-posts\`.`,
|
|
1073
1073
|
"export default slackChannel({",
|
|
1074
1074
|
...engagement,
|
|
1075
1075
|
"});",
|
|
@@ -59,6 +59,17 @@ export class SessionEventLog {
|
|
|
59
59
|
return this.events.length;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* In-process residency vs recorded length. Today those are the same
|
|
64
|
+
* (`events` is the cache). A later change can drop the cache and keep
|
|
65
|
+
* `eventsTotal` as the durable count while `eventsRetained` shrinks to
|
|
66
|
+
* the unflushed tail.
|
|
67
|
+
*/
|
|
68
|
+
memoryStats(): { eventsRetained: number; eventsTotal: number } {
|
|
69
|
+
const n = this.events.length;
|
|
70
|
+
return { eventsRetained: n, eventsTotal: n };
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
/**
|
|
63
74
|
* Assign the next index, record the event, and fan out to live
|
|
64
75
|
* subscribers. Ordering is synchronous; file persistence is chained in
|
|
@@ -154,9 +165,16 @@ export class SessionEventLog {
|
|
|
154
165
|
}
|
|
155
166
|
}
|
|
156
167
|
|
|
168
|
+
export type EventLogMemoryStats = {
|
|
169
|
+
residentSessions: number;
|
|
170
|
+
eventsRetained: number;
|
|
171
|
+
eventsTotal: number;
|
|
172
|
+
};
|
|
173
|
+
|
|
157
174
|
/** Lazily opens one {@link SessionEventLog} per session. */
|
|
158
175
|
export class EventLogStore {
|
|
159
176
|
private readonly logs = new Map<string, Promise<SessionEventLog>>();
|
|
177
|
+
private readonly opened = new Map<string, SessionEventLog>();
|
|
160
178
|
|
|
161
179
|
constructor(
|
|
162
180
|
private readonly eventFilePathFor: (sessionId: string) => string,
|
|
@@ -170,18 +188,38 @@ export class EventLogStore {
|
|
|
170
188
|
let log = this.logs.get(sessionId);
|
|
171
189
|
if (log === undefined) {
|
|
172
190
|
const filePath = this.eventFilePathFor(sessionId);
|
|
173
|
-
log = mkdir(dirname(filePath), { recursive: true })
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
191
|
+
log = mkdir(dirname(filePath), { recursive: true })
|
|
192
|
+
.then(() =>
|
|
193
|
+
SessionEventLog.open({
|
|
194
|
+
sessionId,
|
|
195
|
+
filePath,
|
|
196
|
+
onPersistError: (error) => this.onPersistError(sessionId, error),
|
|
197
|
+
})
|
|
198
|
+
)
|
|
199
|
+
.then((opened) => {
|
|
200
|
+
this.opened.set(sessionId, opened);
|
|
201
|
+
return opened;
|
|
202
|
+
});
|
|
180
203
|
this.logs.set(sessionId, log);
|
|
181
204
|
}
|
|
182
205
|
return log;
|
|
183
206
|
}
|
|
184
207
|
|
|
208
|
+
memoryStats(): EventLogMemoryStats {
|
|
209
|
+
let eventsRetained = 0;
|
|
210
|
+
let eventsTotal = 0;
|
|
211
|
+
for (const log of this.opened.values()) {
|
|
212
|
+
const stats = log.memoryStats();
|
|
213
|
+
eventsRetained += stats.eventsRetained;
|
|
214
|
+
eventsTotal += stats.eventsTotal;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
residentSessions: this.logs.size,
|
|
218
|
+
eventsRetained,
|
|
219
|
+
eventsTotal,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
185
223
|
async flushAll(): Promise<void> {
|
|
186
224
|
await Promise.all(
|
|
187
225
|
[...this.logs.values()].map((logPromise) =>
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process heap + event-log residency for hosted engines.
|
|
3
|
+
*
|
|
4
|
+
* Pod RSS (`runner_daemon.pod.memory_usage_bytes`) cannot show a V8 heap-cap
|
|
5
|
+
* OOM: the cgroup limit is tens of GB while Node aborts near
|
|
6
|
+
* `--max-old-space-size`. The live path is `/v1/health`: the control plane
|
|
7
|
+
* already probes every engine and emits `cursor.agent_serve.engine.*` gauges.
|
|
8
|
+
* An optional in-process sampler (`AGENT_SDK_MEMORY_METRICS=1`) still logs
|
|
9
|
+
* and, if a Datadog key happens to be in the pod, posts directly.
|
|
10
|
+
*
|
|
11
|
+
* `event_log.events_retained` is the in-process cache size (the OOM
|
|
12
|
+
* suspect). `event_log.events_total` is the durable recorded count. They
|
|
13
|
+
* are equal today; a later consume-and-drop change should split them.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { getHeapStatistics } from "node:v8";
|
|
17
|
+
import type { Attributes, Meter } from "@opentelemetry/api";
|
|
18
|
+
import type { EventLogMemoryStats } from "./event-log.js";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_INTERVAL_MS = 15_000;
|
|
21
|
+
const DEFAULT_DD_SITE = "us5.datadoghq.com";
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
23
|
+
const MB = 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
export const MEMORY_METRIC_PREFIX = "agent_sdk.memory";
|
|
26
|
+
export const EVENT_LOG_METRIC_PREFIX = "agent_sdk.event_log";
|
|
27
|
+
|
|
28
|
+
export type ProcessMemorySample = {
|
|
29
|
+
heapUsedBytes: number;
|
|
30
|
+
heapTotalBytes: number;
|
|
31
|
+
heapLimitBytes: number;
|
|
32
|
+
rssBytes: number;
|
|
33
|
+
externalBytes: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type DatadogMemoryMetricsConfig = {
|
|
37
|
+
apiKey: string;
|
|
38
|
+
seriesUrl: string;
|
|
39
|
+
timeoutMs: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ProcessMemoryTelemetryOptions = {
|
|
43
|
+
agentName: string;
|
|
44
|
+
getEventLogStats: () => EventLogMemoryStats;
|
|
45
|
+
logger?: (line: string) => void;
|
|
46
|
+
meter?: Meter;
|
|
47
|
+
env?: NodeJS.ProcessEnv;
|
|
48
|
+
intervalMs?: number;
|
|
49
|
+
now?: () => number;
|
|
50
|
+
memoryUsage?: () => {
|
|
51
|
+
heapUsed: number;
|
|
52
|
+
heapTotal: number;
|
|
53
|
+
rss: number;
|
|
54
|
+
external: number;
|
|
55
|
+
};
|
|
56
|
+
heapSizeLimit?: () => number;
|
|
57
|
+
postSeries?: (
|
|
58
|
+
config: DatadogMemoryMetricsConfig,
|
|
59
|
+
body: string
|
|
60
|
+
) => Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function shouldStartProcessMemoryTelemetry(
|
|
64
|
+
env: NodeJS.ProcessEnv = process.env
|
|
65
|
+
): boolean {
|
|
66
|
+
const flag = env.AGENT_SDK_MEMORY_METRICS?.trim().toLowerCase();
|
|
67
|
+
return flag === "1" || flag === "true";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveDatadogMemoryMetricsConfig(
|
|
71
|
+
env: NodeJS.ProcessEnv = process.env
|
|
72
|
+
): DatadogMemoryMetricsConfig | undefined {
|
|
73
|
+
const apiKey = env.DATADOG_API_KEY?.trim() || env.DD_API_KEY?.trim();
|
|
74
|
+
if (apiKey === undefined || apiKey === "") {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const site = env.DD_SITE?.trim() || DEFAULT_DD_SITE;
|
|
78
|
+
return {
|
|
79
|
+
apiKey,
|
|
80
|
+
seriesUrl: `https://api.${site}/api/v1/series`,
|
|
81
|
+
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function sampleProcessMemory(
|
|
86
|
+
args: {
|
|
87
|
+
memoryUsage?: ProcessMemoryTelemetryOptions["memoryUsage"];
|
|
88
|
+
heapSizeLimit?: ProcessMemoryTelemetryOptions["heapSizeLimit"];
|
|
89
|
+
} = {}
|
|
90
|
+
): ProcessMemorySample {
|
|
91
|
+
const usage = args.memoryUsage?.() ?? process.memoryUsage();
|
|
92
|
+
const heapLimitBytes =
|
|
93
|
+
args.heapSizeLimit?.() ?? getHeapStatistics().heap_size_limit;
|
|
94
|
+
return {
|
|
95
|
+
heapUsedBytes: usage.heapUsed,
|
|
96
|
+
heapTotalBytes: usage.heapTotal,
|
|
97
|
+
heapLimitBytes,
|
|
98
|
+
rssBytes: usage.rss,
|
|
99
|
+
externalBytes: usage.external,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type MemoryHealthFields = {
|
|
104
|
+
memory: ProcessMemorySample & { heapUsedRatio: number };
|
|
105
|
+
eventLog: EventLogMemoryStats;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export function heapUsedRatio(memory: ProcessMemorySample): number {
|
|
109
|
+
return memory.heapLimitBytes > 0
|
|
110
|
+
? memory.heapUsedBytes / memory.heapLimitBytes
|
|
111
|
+
: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function sumEventLogStats(
|
|
115
|
+
stats: readonly EventLogMemoryStats[]
|
|
116
|
+
): EventLogMemoryStats {
|
|
117
|
+
let residentSessions = 0;
|
|
118
|
+
let eventsRetained = 0;
|
|
119
|
+
let eventsTotal = 0;
|
|
120
|
+
for (const item of stats) {
|
|
121
|
+
residentSessions += item.residentSessions;
|
|
122
|
+
eventsRetained += item.eventsRetained;
|
|
123
|
+
eventsTotal += item.eventsTotal;
|
|
124
|
+
}
|
|
125
|
+
return { residentSessions, eventsRetained, eventsTotal };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Fields attached to `/v1/health` so the control plane can scrape them. */
|
|
129
|
+
export function buildMemoryHealthFields(
|
|
130
|
+
eventLog: EventLogMemoryStats,
|
|
131
|
+
sample: ProcessMemorySample = sampleProcessMemory()
|
|
132
|
+
): MemoryHealthFields {
|
|
133
|
+
return {
|
|
134
|
+
memory: { ...sample, heapUsedRatio: heapUsedRatio(sample) },
|
|
135
|
+
eventLog,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Best-effort health payload. Sampling or event-log stats must never fail
|
|
141
|
+
* `/v1/health`; omit the fields instead.
|
|
142
|
+
*/
|
|
143
|
+
export function collectMemoryHealthFields(args: {
|
|
144
|
+
eventLog: () => EventLogMemoryStats;
|
|
145
|
+
sample?: () => ProcessMemorySample;
|
|
146
|
+
}): MemoryHealthFields | Record<string, never> {
|
|
147
|
+
try {
|
|
148
|
+
return buildMemoryHealthFields(
|
|
149
|
+
args.eventLog(),
|
|
150
|
+
args.sample === undefined ? sampleProcessMemory() : args.sample()
|
|
151
|
+
);
|
|
152
|
+
} catch {
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function formatMemoryLogLine(args: {
|
|
158
|
+
agentName: string;
|
|
159
|
+
memory: ProcessMemorySample;
|
|
160
|
+
eventLog: EventLogMemoryStats;
|
|
161
|
+
}): string {
|
|
162
|
+
const ratio = heapUsedRatio(args.memory);
|
|
163
|
+
return (
|
|
164
|
+
`[agent-sdk:memory] agent=${args.agentName}` +
|
|
165
|
+
` heap_used_mb=${mb(args.memory.heapUsedBytes)}` +
|
|
166
|
+
` heap_total_mb=${mb(args.memory.heapTotalBytes)}` +
|
|
167
|
+
` heap_limit_mb=${mb(args.memory.heapLimitBytes)}` +
|
|
168
|
+
` heap_used_ratio=${ratio.toFixed(3)}` +
|
|
169
|
+
` rss_mb=${mb(args.memory.rssBytes)}` +
|
|
170
|
+
` event_log.sessions=${args.eventLog.residentSessions}` +
|
|
171
|
+
` event_log.retained=${args.eventLog.eventsRetained}` +
|
|
172
|
+
` event_log.total=${args.eventLog.eventsTotal}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function buildDatadogGaugeSeries(args: {
|
|
177
|
+
agentName: string;
|
|
178
|
+
memory: ProcessMemorySample;
|
|
179
|
+
eventLog: EventLogMemoryStats;
|
|
180
|
+
timestampSec: number;
|
|
181
|
+
}): Array<{
|
|
182
|
+
metric: string;
|
|
183
|
+
type: "gauge";
|
|
184
|
+
points: [[number, number]];
|
|
185
|
+
tags: string[];
|
|
186
|
+
}> {
|
|
187
|
+
const tags = [`agent_sdk.agent:${sanitizeTag(args.agentName)}`];
|
|
188
|
+
const heapRatio = heapUsedRatio(args.memory);
|
|
189
|
+
const points: Array<{ name: string; value: number }> = [
|
|
190
|
+
{
|
|
191
|
+
name: `${MEMORY_METRIC_PREFIX}.heap_used_bytes`,
|
|
192
|
+
value: args.memory.heapUsedBytes,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: `${MEMORY_METRIC_PREFIX}.heap_total_bytes`,
|
|
196
|
+
value: args.memory.heapTotalBytes,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: `${MEMORY_METRIC_PREFIX}.heap_limit_bytes`,
|
|
200
|
+
value: args.memory.heapLimitBytes,
|
|
201
|
+
},
|
|
202
|
+
{ name: `${MEMORY_METRIC_PREFIX}.rss_bytes`, value: args.memory.rssBytes },
|
|
203
|
+
{
|
|
204
|
+
name: `${MEMORY_METRIC_PREFIX}.external_bytes`,
|
|
205
|
+
value: args.memory.externalBytes,
|
|
206
|
+
},
|
|
207
|
+
{ name: `${MEMORY_METRIC_PREFIX}.heap_used_ratio`, value: heapRatio },
|
|
208
|
+
{
|
|
209
|
+
name: `${EVENT_LOG_METRIC_PREFIX}.resident_sessions`,
|
|
210
|
+
value: args.eventLog.residentSessions,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: `${EVENT_LOG_METRIC_PREFIX}.events_retained`,
|
|
214
|
+
value: args.eventLog.eventsRetained,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: `${EVENT_LOG_METRIC_PREFIX}.events_total`,
|
|
218
|
+
value: args.eventLog.eventsTotal,
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
return points.map((point) => ({
|
|
222
|
+
metric: point.name,
|
|
223
|
+
type: "gauge",
|
|
224
|
+
points: [[args.timestampSec, point.value]],
|
|
225
|
+
tags,
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Start the sampler. Returns a stop function. */
|
|
230
|
+
export function startProcessMemoryTelemetry(
|
|
231
|
+
options: ProcessMemoryTelemetryOptions
|
|
232
|
+
): () => void {
|
|
233
|
+
const env = options.env ?? process.env;
|
|
234
|
+
const logger = options.logger ?? (() => {});
|
|
235
|
+
const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
236
|
+
const datadog = resolveDatadogMemoryMetricsConfig(env);
|
|
237
|
+
const attributes: Attributes = { "agent_sdk.agent": options.agentName };
|
|
238
|
+
let stopped = false;
|
|
239
|
+
let inFlight: Promise<void> | undefined;
|
|
240
|
+
|
|
241
|
+
const sample = (): void => {
|
|
242
|
+
if (stopped) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const memory = sampleProcessMemory({
|
|
246
|
+
memoryUsage: options.memoryUsage,
|
|
247
|
+
heapSizeLimit: options.heapSizeLimit,
|
|
248
|
+
});
|
|
249
|
+
const eventLog = options.getEventLogStats();
|
|
250
|
+
logger(
|
|
251
|
+
formatMemoryLogLine({ agentName: options.agentName, memory, eventLog })
|
|
252
|
+
);
|
|
253
|
+
recordOtel(options.meter, memory, eventLog, attributes);
|
|
254
|
+
if (datadog === undefined) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const body = JSON.stringify({
|
|
258
|
+
series: buildDatadogGaugeSeries({
|
|
259
|
+
agentName: options.agentName,
|
|
260
|
+
memory,
|
|
261
|
+
eventLog,
|
|
262
|
+
timestampSec: Math.floor((options.now?.() ?? Date.now()) / 1000),
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
const post = options.postSeries ?? defaultPostSeries;
|
|
266
|
+
inFlight = post(datadog, body).catch((error: unknown) => {
|
|
267
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
268
|
+
logger(`[agent-sdk:memory] datadog flush failed: ${message}`);
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
sample();
|
|
273
|
+
const timer = setInterval(sample, intervalMs);
|
|
274
|
+
timer.unref();
|
|
275
|
+
return () => {
|
|
276
|
+
stopped = true;
|
|
277
|
+
clearInterval(timer);
|
|
278
|
+
void inFlight;
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function recordOtel(
|
|
283
|
+
meter: Meter | undefined,
|
|
284
|
+
memory: ProcessMemorySample,
|
|
285
|
+
eventLog: EventLogMemoryStats,
|
|
286
|
+
attributes: Attributes
|
|
287
|
+
): void {
|
|
288
|
+
if (meter === undefined) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
histogram(meter, `${MEMORY_METRIC_PREFIX}.heap_used_bytes`).record(
|
|
292
|
+
memory.heapUsedBytes,
|
|
293
|
+
attributes
|
|
294
|
+
);
|
|
295
|
+
histogram(meter, `${MEMORY_METRIC_PREFIX}.heap_limit_bytes`).record(
|
|
296
|
+
memory.heapLimitBytes,
|
|
297
|
+
attributes
|
|
298
|
+
);
|
|
299
|
+
histogram(meter, `${MEMORY_METRIC_PREFIX}.rss_bytes`).record(
|
|
300
|
+
memory.rssBytes,
|
|
301
|
+
attributes
|
|
302
|
+
);
|
|
303
|
+
histogram(meter, `${EVENT_LOG_METRIC_PREFIX}.events_retained`).record(
|
|
304
|
+
eventLog.eventsRetained,
|
|
305
|
+
attributes
|
|
306
|
+
);
|
|
307
|
+
histogram(meter, `${EVENT_LOG_METRIC_PREFIX}.events_total`).record(
|
|
308
|
+
eventLog.eventsTotal,
|
|
309
|
+
attributes
|
|
310
|
+
);
|
|
311
|
+
histogram(meter, `${EVENT_LOG_METRIC_PREFIX}.resident_sessions`).record(
|
|
312
|
+
eventLog.residentSessions,
|
|
313
|
+
attributes
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type HistogramInstrument = {
|
|
318
|
+
record: (value: number, attributes?: Attributes) => void;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const histograms = new WeakMap<Meter, Map<string, HistogramInstrument>>();
|
|
322
|
+
|
|
323
|
+
function histogram(meter: Meter, name: string): HistogramInstrument {
|
|
324
|
+
let byName = histograms.get(meter);
|
|
325
|
+
if (byName === undefined) {
|
|
326
|
+
byName = new Map();
|
|
327
|
+
histograms.set(meter, byName);
|
|
328
|
+
}
|
|
329
|
+
const existing = byName.get(name);
|
|
330
|
+
if (existing !== undefined) {
|
|
331
|
+
return existing;
|
|
332
|
+
}
|
|
333
|
+
const unit = name.endsWith("_bytes") ? "By" : "{count}";
|
|
334
|
+
const created = meter.createHistogram(name, { unit });
|
|
335
|
+
byName.set(name, created);
|
|
336
|
+
return created;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function defaultPostSeries(
|
|
340
|
+
config: DatadogMemoryMetricsConfig,
|
|
341
|
+
body: string
|
|
342
|
+
): Promise<void> {
|
|
343
|
+
const controller = new AbortController();
|
|
344
|
+
const timer = setTimeout(() => controller.abort(), config.timeoutMs);
|
|
345
|
+
try {
|
|
346
|
+
const response = await fetch(config.seriesUrl, {
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: {
|
|
349
|
+
"Content-Type": "application/json",
|
|
350
|
+
"DD-API-KEY": config.apiKey,
|
|
351
|
+
},
|
|
352
|
+
body,
|
|
353
|
+
signal: controller.signal,
|
|
354
|
+
});
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
throw new Error(`HTTP ${response.status}`);
|
|
357
|
+
}
|
|
358
|
+
} finally {
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function mb(bytes: number): string {
|
|
364
|
+
return (bytes / MB).toFixed(1);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function sanitizeTag(value: string): string {
|
|
368
|
+
const normalized = value
|
|
369
|
+
.trim()
|
|
370
|
+
.toLowerCase()
|
|
371
|
+
.replace(/[^a-z0-9_\-./:]+/g, "_");
|
|
372
|
+
return normalized === "" ? "unknown" : normalized;
|
|
373
|
+
}
|
package/src/internal/server.ts
CHANGED
|
@@ -135,6 +135,10 @@ import {
|
|
|
135
135
|
playgroundDevBasePath,
|
|
136
136
|
startPlaygroundDevServer,
|
|
137
137
|
} from "./playground/toolchain.js";
|
|
138
|
+
import {
|
|
139
|
+
collectMemoryHealthFields,
|
|
140
|
+
sumEventLogStats,
|
|
141
|
+
} from "./process-memory-telemetry.js";
|
|
138
142
|
import {
|
|
139
143
|
ReminderRunner,
|
|
140
144
|
ReminderValidationError,
|
|
@@ -162,6 +166,13 @@ import {
|
|
|
162
166
|
} from "./session-engine.js";
|
|
163
167
|
import { sessionUrlsForRequest } from "./session-urls.js";
|
|
164
168
|
|
|
169
|
+
function memoryHealthFields(engines: readonly SessionEngine[]) {
|
|
170
|
+
return collectMemoryHealthFields({
|
|
171
|
+
eventLog: () =>
|
|
172
|
+
sumEventLogStats(engines.map((engine) => engine.eventLogMemoryStats())),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
const MAX_BODY_BYTES = 32 * 1024 * 1024;
|
|
166
177
|
|
|
167
178
|
/** `GET /v1/artifacts` limit defaults / ceiling. */
|
|
@@ -636,6 +647,7 @@ export async function startServer(
|
|
|
636
647
|
slug,
|
|
637
648
|
name: project.name,
|
|
638
649
|
})),
|
|
650
|
+
...memoryHealthFields(runtimes.map((runtime) => runtime.engine)),
|
|
639
651
|
}),
|
|
640
652
|
});
|
|
641
653
|
}
|
|
@@ -1049,6 +1061,7 @@ function mountAgentRoutes(
|
|
|
1049
1061
|
ok: true,
|
|
1050
1062
|
name: project.name,
|
|
1051
1063
|
version: packageVersion(),
|
|
1064
|
+
...memoryHealthFields([engine]),
|
|
1052
1065
|
}),
|
|
1053
1066
|
});
|
|
1054
1067
|
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
namespacedKv,
|
|
92
92
|
storageCasKv,
|
|
93
93
|
} from "./durable-session-store.js";
|
|
94
|
-
import { EventLogStore } from "./event-log.js";
|
|
94
|
+
import { type EventLogMemoryStats, EventLogStore } from "./event-log.js";
|
|
95
95
|
import { TurnEventMapper } from "./event-mapper.js";
|
|
96
96
|
import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
|
|
97
97
|
import { createHostFilesApi } from "./host-files.js";
|
|
@@ -112,6 +112,10 @@ import { validateToolInput } from "./json-schema.js";
|
|
|
112
112
|
import { LedgerServing, newLedgerPodId } from "./ledger-serving.js";
|
|
113
113
|
import { McpHost } from "./mcp-host.js";
|
|
114
114
|
import type { OtelCollector } from "./otel/collector.js";
|
|
115
|
+
import {
|
|
116
|
+
shouldStartProcessMemoryTelemetry,
|
|
117
|
+
startProcessMemoryTelemetry,
|
|
118
|
+
} from "./process-memory-telemetry.js";
|
|
115
119
|
import {
|
|
116
120
|
buildSessionContextBlock,
|
|
117
121
|
SCHEDULE_CHANNEL_PREFIX,
|
|
@@ -364,6 +368,7 @@ export class SessionEngine {
|
|
|
364
368
|
private readonly abCollector: ABCollector;
|
|
365
369
|
private readonly otel: OtelCollector | undefined;
|
|
366
370
|
private readonly otelHost: HostOtelSessionApi;
|
|
371
|
+
private readonly stopMemoryTelemetry: (() => void) | undefined;
|
|
367
372
|
private reminderApi: HostContext["reminders"];
|
|
368
373
|
private evalsApi: HostContext["evals"];
|
|
369
374
|
private readonly kvApi: HostContext["kv"];
|
|
@@ -480,6 +485,14 @@ export class SessionEngine {
|
|
|
480
485
|
}
|
|
481
486
|
this.ledgerServing = this.buildLedgerServing(options);
|
|
482
487
|
this.ledgerServing?.start();
|
|
488
|
+
this.stopMemoryTelemetry = shouldStartProcessMemoryTelemetry()
|
|
489
|
+
? startProcessMemoryTelemetry({
|
|
490
|
+
agentName: options.project.name,
|
|
491
|
+
getEventLogStats: () => this.logs.memoryStats(),
|
|
492
|
+
logger: this.logger,
|
|
493
|
+
meter: options.otelMeter,
|
|
494
|
+
})
|
|
495
|
+
: undefined;
|
|
483
496
|
}
|
|
484
497
|
|
|
485
498
|
/**
|
|
@@ -3445,8 +3458,14 @@ export class SessionEngine {
|
|
|
3445
3458
|
});
|
|
3446
3459
|
}
|
|
3447
3460
|
|
|
3461
|
+
/** In-process event-log residency (OOM-suspect cache vs durable count). */
|
|
3462
|
+
eventLogMemoryStats(): EventLogMemoryStats {
|
|
3463
|
+
return this.logs.memoryStats();
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3448
3466
|
/** Drain running turns, dispatches, and pending writes, then dispose. */
|
|
3449
3467
|
async close(): Promise<void> {
|
|
3468
|
+
this.stopMemoryTelemetry?.();
|
|
3450
3469
|
this.approvals.cancelAll("deny");
|
|
3451
3470
|
// Ledger deliveries spawn turns, so settle them before activeTurns.
|
|
3452
3471
|
await this.ledgerServing?.close();
|