@cursor/july 0.1.47 → 0.1.49
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 +30 -35
- 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.IEXEim5m.js → app.BpWAgjaR.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D00d772K.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.78s6nqsH.js → VPLocalSearchBox.CnAktZtG.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DroGdz_Q.js → theme.B5IvUxOB.js} +2 -2
- 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/{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/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 +6 -6
- 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/opentelemetry.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/artifacts.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 +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 +3 -3
- package/dist/internal/authored-loaders.d.ts +19 -0
- package/dist/internal/authored-loaders.d.ts.map +1 -1
- package/dist/internal/authored-loaders.js +39 -0
- package/dist/internal/cli-deploy.d.ts +2 -1
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +97 -1
- package/dist/internal/cli-slack.js +0 -3
- package/dist/internal/deploy-client.d.ts +7 -2
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +12 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +2 -8
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +3 -8
- 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/logs-client.d.ts +1 -1
- package/dist/internal/logs-client.d.ts.map +1 -1
- package/dist/internal/logs-client.js +3 -0
- 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/serve-restart.d.ts +42 -0
- package/dist/internal/serve-restart.d.ts.map +1 -0
- package/dist/internal/serve-restart.js +85 -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 +4 -4
- package/dist/internal/slack-provision-client.js +7 -7
- package/dist/playground/assets/index-Dox74yWJ.css +1 -0
- package/dist/playground/assets/{index-BgUHfe3L.js → index-Dq_HqyU-.js} +46 -46
- 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/evals.md +10 -7
- package/docs/reference/http-api.md +4 -3
- package/docs/storage.md +38 -22
- package/package.json +1 -1
- package/skills/setup-slack/SKILL.md +1 -1
- package/src/bin/agent-serve.ts +39 -38
- package/src/internal/authored-loaders.ts +45 -0
- package/src/internal/cli-deploy.ts +103 -1
- package/src/internal/cli-slack.ts +0 -5
- package/src/internal/deploy-client.ts +19 -1
- package/src/internal/discovery.ts +5 -12
- package/src/internal/eval-runner.ts +3 -12
- package/src/internal/event-log.ts +45 -7
- package/src/internal/logs-client.ts +4 -0
- package/src/internal/process-memory-telemetry.ts +373 -0
- package/src/internal/serve-restart.ts +120 -0
- package/src/internal/server.ts +13 -0
- package/src/internal/session-engine.ts +20 -1
- package/src/internal/slack-provision-client.ts +7 -7
- package/src/storage.ts +130 -11
- package/dist/docs/assets/chunks/@localSearchIndexroot.D7w61u6Y.js +0 -1
- package/dist/docs/assets/storage.md.CJYd5YmN.lean.js +0 -1
- package/dist/playground/assets/index-uFaeqVZS.css +0 -1
- /package/dist/docs/assets/{reference_http-api.md.DGrw_wOu.lean.js → reference_http-api.md.DMbdFGVQ.lean.js} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enter-to-restart for `serve` / `dev` on a TTY.
|
|
3
|
+
*
|
|
4
|
+
* In-process close+re-serve cannot reload every authored change: Node ESM
|
|
5
|
+
* caches transitive imports, and module-level state (tsx, singletons) survives.
|
|
6
|
+
* The parent process therefore supervises a worker and respawns it when the
|
|
7
|
+
* worker exits with {@link ENTER_RESTART_EXIT_CODE}.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const ENTER_RESTART_EXIT_CODE = 75;
|
|
11
|
+
|
|
12
|
+
export const SERVE_RESTART_WORKER_ENV = "AGENT_SERVE_RESTART_WORKER";
|
|
13
|
+
|
|
14
|
+
export interface ServeRestartProcess {
|
|
15
|
+
execPath: string;
|
|
16
|
+
execArgv: readonly string[];
|
|
17
|
+
argv: readonly string[];
|
|
18
|
+
env: NodeJS.ProcessEnv;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ServeRestartSpawnPlan {
|
|
22
|
+
command: string;
|
|
23
|
+
args: string[];
|
|
24
|
+
env: NodeJS.ProcessEnv;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ServeRestartWorkerChild {
|
|
28
|
+
once(
|
|
29
|
+
event: "exit",
|
|
30
|
+
listener: (code: number | null, signal: NodeJS.Signals | null) => void
|
|
31
|
+
): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isServeRestartWorker(
|
|
35
|
+
env: NodeJS.ProcessEnv = process.env
|
|
36
|
+
): boolean {
|
|
37
|
+
return env[SERVE_RESTART_WORKER_ENV] === "1";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function serveRestartSpawnPlan(
|
|
41
|
+
proc: ServeRestartProcess
|
|
42
|
+
): ServeRestartSpawnPlan {
|
|
43
|
+
return {
|
|
44
|
+
command: proc.execPath,
|
|
45
|
+
args: [...proc.execArgv, ...proc.argv.slice(1)],
|
|
46
|
+
env: { ...proc.env, [SERVE_RESTART_WORKER_ENV]: "1" },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function shouldRespawnServeWorker(
|
|
51
|
+
code: number | null,
|
|
52
|
+
signal: NodeJS.Signals | null
|
|
53
|
+
): boolean {
|
|
54
|
+
return signal === null && code === ENTER_RESTART_EXIT_CODE;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run the TTY Enter-to-restart supervisor: spawn this CLI as a worker, and
|
|
59
|
+
* respawn whenever the worker exits asking for a restart. Other exits
|
|
60
|
+
* (Ctrl-C drain, crash, bind failure) propagate to the caller.
|
|
61
|
+
*/
|
|
62
|
+
export async function runEnterRestartSupervisor(options: {
|
|
63
|
+
spawn: (
|
|
64
|
+
command: string,
|
|
65
|
+
args: string[],
|
|
66
|
+
spawnOptions: { stdio: "inherit"; env: NodeJS.ProcessEnv }
|
|
67
|
+
) => ServeRestartWorkerChild;
|
|
68
|
+
proc?: ServeRestartProcess;
|
|
69
|
+
onRestart?: () => void;
|
|
70
|
+
installSignalGuards?: (handler: () => void) => () => void;
|
|
71
|
+
}): Promise<number> {
|
|
72
|
+
const proc = options.proc ?? {
|
|
73
|
+
execPath: process.execPath,
|
|
74
|
+
execArgv: process.execArgv,
|
|
75
|
+
argv: process.argv,
|
|
76
|
+
env: process.env,
|
|
77
|
+
};
|
|
78
|
+
const plan = serveRestartSpawnPlan(proc);
|
|
79
|
+
const uninstallSignals = (
|
|
80
|
+
options.installSignalGuards ?? installDefaultSignalGuards
|
|
81
|
+
)(() => undefined);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
for (;;) {
|
|
85
|
+
const child = options.spawn(plan.command, plan.args, {
|
|
86
|
+
stdio: "inherit",
|
|
87
|
+
env: plan.env,
|
|
88
|
+
});
|
|
89
|
+
const { code, signal } = await waitForExit(child);
|
|
90
|
+
if (!shouldRespawnServeWorker(code, signal)) {
|
|
91
|
+
if (signal !== null) {
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
return code ?? 1;
|
|
95
|
+
}
|
|
96
|
+
options.onRestart?.();
|
|
97
|
+
}
|
|
98
|
+
} finally {
|
|
99
|
+
uninstallSignals();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function waitForExit(
|
|
104
|
+
child: ServeRestartWorkerChild
|
|
105
|
+
): Promise<{ code: number | null; signal: NodeJS.Signals | null }> {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
child.once("exit", (code, signal) => {
|
|
108
|
+
resolve({ code, signal });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function installDefaultSignalGuards(handler: () => void): () => void {
|
|
114
|
+
process.on("SIGINT", handler);
|
|
115
|
+
process.on("SIGTERM", handler);
|
|
116
|
+
return () => {
|
|
117
|
+
process.off("SIGINT", handler);
|
|
118
|
+
process.off("SIGTERM", handler);
|
|
119
|
+
};
|
|
120
|
+
}
|
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();
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Auth is the user API key sent directly as the bearer (the routes are
|
|
8
8
|
* `wrapUserApiRoute` on the backend); there is no token exchange here.
|
|
9
|
-
* The endpoints
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The endpoints ride `requireAgentServeAccess` and 404 `{ error: "Not found" }`
|
|
10
|
+
* when Agent Serve is off — same mapping as other agent-sdk deploy 404s.
|
|
11
|
+
* Expected provisioning failures carry a stable JSON `code`
|
|
12
12
|
* (`not_connected`, `insufficient_scopes`, `manager_reauth_required`,
|
|
13
|
-
* `app_conflict`, `
|
|
13
|
+
* `app_conflict`, `ratelimited`, `invalid_app_id`, …)
|
|
14
14
|
* which flows through on {@link SlackProvisionApiError.code} so the CLI
|
|
15
15
|
* can branch.
|
|
16
16
|
*/
|
|
@@ -398,8 +398,8 @@ function mapErrorResponse(
|
|
|
398
398
|
{ status: 404 }
|
|
399
399
|
);
|
|
400
400
|
}
|
|
401
|
-
//
|
|
402
|
-
//
|
|
401
|
+
// `{ error: "Not found" }` is Agent Serve off (`requireAgentServeAccess`),
|
|
402
|
+
// not a Slack-only enrollment bar.
|
|
403
403
|
if (serverError !== undefined && serverError !== "Not found") {
|
|
404
404
|
return new SlackProvisionApiError(serverError, {
|
|
405
405
|
status: 404,
|
|
@@ -407,7 +407,7 @@ function mapErrorResponse(
|
|
|
407
407
|
});
|
|
408
408
|
}
|
|
409
409
|
return new SlackProvisionApiError(
|
|
410
|
-
`Not found —
|
|
410
|
+
`Not found — the agent_serve_mvp feature gate may not be enabled for your team.`,
|
|
411
411
|
{ status: 404 }
|
|
412
412
|
);
|
|
413
413
|
}
|