@cursor/july 0.1.42 → 0.1.43
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 +1 -0
- package/dist/bin/agent-serve.js +3 -1
- package/dist/channels/github/api.d.ts +5 -0
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +18 -0
- package/dist/channels/github/defaults.d.ts +4 -5
- package/dist/channels/github/defaults.d.ts.map +1 -1
- package/dist/channels/github/defaults.js +27 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +28 -5
- package/dist/channels/github/index.d.ts +1 -0
- package/dist/channels/github/index.d.ts.map +1 -1
- package/dist/channels/github/index.js +1 -0
- package/dist/channels/github/progress.d.ts +54 -0
- package/dist/channels/github/progress.d.ts.map +1 -0
- package/dist/channels/github/progress.js +168 -0
- package/dist/channels/github/state.d.ts +2 -0
- package/dist/channels/github/state.d.ts.map +1 -1
- package/dist/channels/github/state.js +1 -1
- package/dist/channels/github/types.d.ts +8 -4
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.d.ts +4 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.C4-Gwi7g.js → app.DsTLo2j1.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BZx4pmZL.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DDoLm843.js → VPLocalSearchBox.DoVfutcV.js} +1 -1
- package/dist/docs/assets/chunks/{theme.SbjGIiM0.js → theme.DtuVtKkc.js} +2 -2
- package/dist/docs/assets/{deployment.md.BHVjVqRa.js → deployment.md.Cdp4rTE4.js} +1 -1
- package/dist/docs/assets/{guides_github.md.R2QlpR75.js → guides_github.md.DSg0j9qj.js} +29 -3
- package/dist/docs/assets/{guides_github.md.R2QlpR75.lean.js → guides_github.md.DSg0j9qj.lean.js} +1 -1
- package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.js +20 -0
- package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.lean.js +1 -0
- package/dist/docs/assets/{index.md.BKpbkCa6.js → index.md.CMc5FP7E.js} +2 -1
- package/dist/docs/assets/{reference_channels.md.BIabFUAI.js → reference_channels.md.MN8iDy8V.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.B40QAG6m.js → reference_hooks.md.4cbRRIIu.js} +2 -2
- package/dist/docs/assets/{reference_hooks.md.B40QAG6m.lean.js → reference_hooks.md.4cbRRIIu.lean.js} +1 -1
- package/dist/docs/assets/{reference_project-layout.md._XdeMahr.js → reference_project-layout.md.D3MdHM2z.js} +2 -1
- package/dist/docs/assets/templates_triage.md.CConZt1z.js +11 -0
- package/dist/docs/assets/templates_triage.md.CConZt1z.lean.js +1 -0
- package/dist/docs/building-with-agents.html +5 -5
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +6 -6
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +4 -4
- package/dist/docs/example-agents/benny.html +4 -4
- package/dist/docs/example-agents/bugbot.html +4 -4
- package/dist/docs/example-agents/codebase-wiki.html +4 -4
- package/dist/docs/example-agents/codeowners-review.html +4 -4
- package/dist/docs/example-agents/concierge.html +4 -4
- package/dist/docs/example-agents/fsd.html +4 -4
- package/dist/docs/example-agents/index.html +5 -5
- package/dist/docs/example-agents/knowledge-base.html +4 -4
- package/dist/docs/example-agents/oncall.html +4 -4
- package/dist/docs/example-agents/security-reviewer.html +4 -4
- package/dist/docs/example-agents/slack-agent.html +4 -4
- package/dist/docs/example-agents/weather-agent.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +33 -7
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +44 -0
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +7 -6
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +6 -6
- package/dist/docs/reference/cli.html +4 -4
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +6 -6
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +7 -6
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/triage.html +35 -0
- package/dist/docs/troubleshooting.html +4 -4
- 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-ax.d.ts +1 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +18 -3
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +36 -4
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/init-project.d.ts +24 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +126 -7
- package/dist/internal/otel/attrs.d.ts +7 -0
- package/dist/internal/otel/attrs.d.ts.map +1 -0
- package/dist/internal/otel/attrs.js +13 -0
- package/dist/internal/otel/collector.d.ts +16 -0
- package/dist/internal/otel/collector.d.ts.map +1 -0
- package/dist/internal/otel/collector.js +564 -0
- package/dist/internal/otel/config.d.ts +42 -0
- package/dist/internal/otel/config.d.ts.map +1 -0
- package/dist/internal/otel/config.js +236 -0
- package/dist/internal/otel/http.d.ts +11 -0
- package/dist/internal/otel/http.d.ts.map +1 -0
- package/dist/internal/otel/http.js +97 -0
- package/dist/internal/otel/runtime.d.ts +21 -0
- package/dist/internal/otel/runtime.d.ts.map +1 -0
- package/dist/internal/otel/runtime.js +188 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +25 -1
- package/dist/internal/session-engine.d.ts +10 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +35 -2
- package/dist/memory.d.ts +4 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +4 -0
- package/dist/otel.d.ts +88 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +64 -0
- package/dist/playground/assets/{index-B3HBneZZ.js → index-D-DIYQzw.js} +31 -31
- package/dist/playground/assets/index-DQTH622f.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +22 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -0
- package/docs/deployment.md +2 -1
- package/docs/guides/github.md +79 -6
- package/docs/guides/opentelemetry.md +184 -0
- package/docs/reference/channels.md +3 -1
- package/docs/reference/hooks.md +21 -4
- package/docs/reference/project-layout.md +2 -0
- package/docs/templates/triage.md +147 -0
- package/package.json +23 -1
- package/skills/create-agent/SKILL.md +2 -2
- package/skills/framework-map/SKILL.md +2 -0
- package/skills/otel/SKILL.md +80 -0
- package/src/bin/agent-serve.ts +3 -1
- package/src/channels/github/api.ts +22 -0
- package/src/channels/github/defaults.ts +27 -6
- package/src/channels/github/github-channel.ts +27 -1
- package/src/channels/github/index.ts +1 -0
- package/src/channels/github/progress.ts +243 -0
- package/src/channels/github/state.ts +3 -0
- package/src/channels/github/types.ts +8 -4
- package/src/channels/slack/slack-channel.ts +4 -0
- package/src/index.ts +6 -0
- package/src/internal/cli-ax.ts +19 -1
- package/src/internal/discovery.ts +59 -4
- package/src/internal/distribution.ts +1 -0
- package/src/internal/init-project.ts +150 -7
- package/src/internal/otel/attrs.ts +18 -0
- package/src/internal/otel/collector.ts +796 -0
- package/src/internal/otel/config.ts +307 -0
- package/src/internal/otel/http.ts +126 -0
- package/src/internal/otel/runtime.ts +271 -0
- package/src/internal/server.ts +38 -1
- package/src/internal/session-engine.ts +51 -0
- package/src/memory.ts +4 -0
- package/src/otel.ts +155 -0
- package/src/types.ts +20 -1
- package/templates/triage/README.md +48 -0
- package/templates/triage/agent/agent.ts +14 -0
- package/templates/triage/agent/channels/intake.ts +20 -0
- package/templates/triage/agent/channels/slack.ts +3 -0
- package/templates/triage/agent/hooks/memory.ts +3 -0
- package/templates/triage/agent/instructions.md +27 -0
- package/templates/triage/agent/skills/triage-rubric.md +43 -0
- package/templates/triage/agent/tools/record_triage.ts +23 -0
- package/templates/triage/evals/evals.config.ts +5 -0
- package/templates/triage/evals/triage.eval.ts +43 -0
- package/templates/triage/package.json +18 -0
- package/templates/triage/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BlU3A8EB.js +0 -1
- package/dist/playground/assets/index-Bqk9nplh.css +0 -1
- /package/dist/docs/assets/{deployment.md.BHVjVqRa.lean.js → deployment.md.Cdp4rTE4.lean.js} +0 -0
- /package/dist/docs/assets/{index.md.BKpbkCa6.lean.js → index.md.CMc5FP7E.lean.js} +0 -0
- /package/dist/docs/assets/{reference_channels.md.BIabFUAI.lean.js → reference_channels.md.MN8iDy8V.lean.js} +0 -0
- /package/dist/docs/assets/{reference_project-layout.md._XdeMahr.lean.js → reference_project-layout.md.D3MdHM2z.lean.js} +0 -0
package/src/internal/server.ts
CHANGED
|
@@ -112,6 +112,10 @@ import {
|
|
|
112
112
|
TOOL_MCP_ENDPOINT_PATH,
|
|
113
113
|
} from "./mcp-endpoint.js";
|
|
114
114
|
import { isLoopbackAddress } from "./net.js";
|
|
115
|
+
import { createOtelCollector, type OtelCollector } from "./otel/collector.js";
|
|
116
|
+
import { pickAuthoredOtel, resolveOtelConfig } from "./otel/config.js";
|
|
117
|
+
import { instrumentHttp } from "./otel/http.js";
|
|
118
|
+
import { createOtelRuntime, shutdownOtelRuntime } from "./otel/runtime.js";
|
|
115
119
|
import { collectPeerConnections } from "./peer-connections.js";
|
|
116
120
|
import {
|
|
117
121
|
PLATFORM_SCHEDULE_CATALOG_FILENAME,
|
|
@@ -222,6 +226,30 @@ export async function startServer(
|
|
|
222
226
|
// agentkit_enabled gate applies to every turn.
|
|
223
227
|
process.env.CURSOR_AGENTKIT_VERSION ??= packageVersion();
|
|
224
228
|
|
|
229
|
+
const authoredOtel = pickAuthoredOtel(
|
|
230
|
+
mounts.map((m) => ({
|
|
231
|
+
slug: m.slug,
|
|
232
|
+
name: m.project.name,
|
|
233
|
+
otel: m.project.otel,
|
|
234
|
+
}))
|
|
235
|
+
);
|
|
236
|
+
const otelRuntime = createOtelRuntime(
|
|
237
|
+
resolveOtelConfig({
|
|
238
|
+
authored: authoredOtel,
|
|
239
|
+
serve: options.otel,
|
|
240
|
+
}),
|
|
241
|
+
logger
|
|
242
|
+
);
|
|
243
|
+
if (
|
|
244
|
+
otelRuntime === undefined &&
|
|
245
|
+
options.otel !== false &&
|
|
246
|
+
authoredOtel !== undefined
|
|
247
|
+
) {
|
|
248
|
+
logger(
|
|
249
|
+
"[agent-sdk] otel: agent/otel.ts present but no OTLP exporters configured (set exporters or OTEL_EXPORTER_OTLP_ENDPOINT)"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
225
253
|
const cursorGithubTargets = collectCursorGithubTargets(mounts);
|
|
226
254
|
const cursorGithubConfig = resolveCursorGithubEventConfig({
|
|
227
255
|
configured: options.cursorEvents,
|
|
@@ -396,12 +424,17 @@ export async function startServer(
|
|
|
396
424
|
const baseStateRoot =
|
|
397
425
|
options.stateRoot ?? join(project.rootDir, ".agent-serve");
|
|
398
426
|
const stateRoot = slug === "" ? baseStateRoot : join(baseStateRoot, slug);
|
|
427
|
+
const otelCollector: OtelCollector | undefined =
|
|
428
|
+
otelRuntime === undefined
|
|
429
|
+
? undefined
|
|
430
|
+
: createOtelCollector(otelRuntime, project.name, logger);
|
|
399
431
|
const engine = new SessionEngine({
|
|
400
432
|
project,
|
|
401
433
|
stateRoot,
|
|
402
434
|
runner,
|
|
403
435
|
logger,
|
|
404
436
|
platforms: hostPlatforms,
|
|
437
|
+
otel: otelCollector,
|
|
405
438
|
});
|
|
406
439
|
// Rehydrate sessions from the authored storage sink before any
|
|
407
440
|
// channel or route can address them (continuation tokens must resolve).
|
|
@@ -696,7 +729,9 @@ export async function startServer(
|
|
|
696
729
|
});
|
|
697
730
|
|
|
698
731
|
const server = createServer((req, res) => {
|
|
699
|
-
void
|
|
732
|
+
void instrumentHttp(otelRuntime, req, res, () =>
|
|
733
|
+
handleRequest(router, req, res, logger)
|
|
734
|
+
);
|
|
700
735
|
});
|
|
701
736
|
|
|
702
737
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -715,6 +750,7 @@ export async function startServer(
|
|
|
715
750
|
await cursorEventRelay?.stop();
|
|
716
751
|
await cursorSlackRelay?.stop();
|
|
717
752
|
cursorGithubCredentials?.stop();
|
|
753
|
+
await shutdownOtelRuntime(otelRuntime);
|
|
718
754
|
throw error;
|
|
719
755
|
}
|
|
720
756
|
const address = server.address() as AddressInfo;
|
|
@@ -900,6 +936,7 @@ export async function startServer(
|
|
|
900
936
|
await Promise.all(runtimes.map((runtime) => runtime.engine.close()));
|
|
901
937
|
} finally {
|
|
902
938
|
cursorGithubCredentials?.stop();
|
|
939
|
+
await shutdownOtelRuntime(otelRuntime);
|
|
903
940
|
}
|
|
904
941
|
},
|
|
905
942
|
};
|
|
@@ -109,6 +109,7 @@ import {
|
|
|
109
109
|
import { validateToolInput } from "./json-schema.js";
|
|
110
110
|
import { LedgerServing, newLedgerPodId } from "./ledger-serving.js";
|
|
111
111
|
import { McpHost } from "./mcp-host.js";
|
|
112
|
+
import type { OtelCollector } from "./otel/collector.js";
|
|
112
113
|
import {
|
|
113
114
|
buildSessionContextBlock,
|
|
114
115
|
SCHEDULE_CHANNEL_PREFIX,
|
|
@@ -243,6 +244,8 @@ export interface SessionEngineOptions {
|
|
|
243
244
|
* *without* `cas` fails fast. Ignored for resident-mode agents.
|
|
244
245
|
*/
|
|
245
246
|
durableSessionStore?: HostKvApi;
|
|
247
|
+
/** Process-level OTel collector; observe-only, never fatal. */
|
|
248
|
+
otel?: OtelCollector;
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
/**
|
|
@@ -358,6 +361,7 @@ export class SessionEngine {
|
|
|
358
361
|
private readonly platforms: HostPlatforms;
|
|
359
362
|
private readonly approvals = new ApprovalGate();
|
|
360
363
|
private readonly abCollector: ABCollector;
|
|
364
|
+
private readonly otel: OtelCollector | undefined;
|
|
361
365
|
private reminderApi: HostContext["reminders"];
|
|
362
366
|
private evalsApi: HostContext["evals"];
|
|
363
367
|
private readonly kvApi: HostContext["kv"];
|
|
@@ -390,6 +394,7 @@ export class SessionEngine {
|
|
|
390
394
|
this.logger = options.logger ?? (() => {});
|
|
391
395
|
this.getCloudUsage = options.getCloudUsage ?? defaultGetCloudUsage;
|
|
392
396
|
this.estimateTurnCost = options.estimateTurnCost ?? estimateTurnCostFromApi;
|
|
397
|
+
this.otel = options.otel;
|
|
393
398
|
this.turnGovernor = new TurnGovernor(options.project.agent.concurrency);
|
|
394
399
|
this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
|
|
395
400
|
this.nudgeStore = new SlackNudgeStore(
|
|
@@ -775,6 +780,17 @@ export class SessionEngine {
|
|
|
775
780
|
cloudOverride: options.cloud,
|
|
776
781
|
}));
|
|
777
782
|
}
|
|
783
|
+
// Refresh wake-derived channel state (GitHub headSha / refs) without
|
|
784
|
+
// wiping sticky fields. Null patch values are skipped.
|
|
785
|
+
if (options.refreshState !== undefined) {
|
|
786
|
+
record = await this.updateRecord(record.sessionId, (current) => ({
|
|
787
|
+
...current,
|
|
788
|
+
channelState: mergeChannelStateSkipNull(
|
|
789
|
+
current.channelState,
|
|
790
|
+
options.refreshState
|
|
791
|
+
),
|
|
792
|
+
}));
|
|
793
|
+
}
|
|
778
794
|
const coalesced = await this.startTurn(record.sessionId, message, {
|
|
779
795
|
admission: options.admission ?? "preempt",
|
|
780
796
|
...(options.coalesceSourceTs === undefined
|
|
@@ -2457,6 +2473,8 @@ export class SessionEngine {
|
|
|
2457
2473
|
this.project.name
|
|
2458
2474
|
);
|
|
2459
2475
|
}
|
|
2476
|
+
|
|
2477
|
+
this.otel?.onEvent(event, this.sessionInfo(record));
|
|
2460
2478
|
}
|
|
2461
2479
|
|
|
2462
2480
|
private channelEventHandlersFor(
|
|
@@ -3352,6 +3370,7 @@ export class SessionEngine {
|
|
|
3352
3370
|
await this.storage?.close();
|
|
3353
3371
|
await this.mcpHost.close();
|
|
3354
3372
|
await this.runner.dispose?.();
|
|
3373
|
+
this.otel?.close();
|
|
3355
3374
|
}
|
|
3356
3375
|
}
|
|
3357
3376
|
|
|
@@ -3385,6 +3404,38 @@ function mergePendingWorkspaceFiles(
|
|
|
3385
3404
|
return { ...current, ...incoming };
|
|
3386
3405
|
}
|
|
3387
3406
|
|
|
3407
|
+
/**
|
|
3408
|
+
* Shallow-merge a wake patch into durable channel state. Null / undefined
|
|
3409
|
+
* patch values leave the existing key alone so sticky fields survive.
|
|
3410
|
+
*/
|
|
3411
|
+
export function mergeChannelStateSkipNull(
|
|
3412
|
+
current: JsonValue | undefined,
|
|
3413
|
+
patch: JsonValue | undefined
|
|
3414
|
+
): JsonValue | undefined {
|
|
3415
|
+
if (patch === undefined) {
|
|
3416
|
+
return current;
|
|
3417
|
+
}
|
|
3418
|
+
if (
|
|
3419
|
+
typeof patch !== "object" ||
|
|
3420
|
+
patch === null ||
|
|
3421
|
+
Array.isArray(patch) ||
|
|
3422
|
+
typeof current !== "object" ||
|
|
3423
|
+
current === null ||
|
|
3424
|
+
Array.isArray(current)
|
|
3425
|
+
) {
|
|
3426
|
+
return structuredClone(patch);
|
|
3427
|
+
}
|
|
3428
|
+
const merged: Record<string, JsonValue> = {
|
|
3429
|
+
...(current as Record<string, JsonValue>),
|
|
3430
|
+
};
|
|
3431
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
3432
|
+
if (value !== null && value !== undefined) {
|
|
3433
|
+
merged[key] = value as JsonValue;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
return merged;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3388
3439
|
function eventsNdjson(events: SessionEvent[]): string | undefined {
|
|
3389
3440
|
if (events.length === 0) {
|
|
3390
3441
|
return undefined;
|
package/src/memory.ts
CHANGED
|
@@ -237,6 +237,10 @@ function truncate(text: string, max: number): string {
|
|
|
237
237
|
* import { memoryHook } from "@cursor/july/memory";
|
|
238
238
|
* export default memoryHook();
|
|
239
239
|
* ```
|
|
240
|
+
*
|
|
241
|
+
* The journal is shared across every session — and every user — of the
|
|
242
|
+
* agent. Pass your own {@link MemoryHookOptions.backend} to change where
|
|
243
|
+
* memories live; delete the hook file to opt out entirely.
|
|
240
244
|
*/
|
|
241
245
|
export function memoryHook(options: MemoryHookOptions = {}): HookDefinition {
|
|
242
246
|
const backend = options.backend ?? defaultMemoryBackend();
|
package/src/otel.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry for Agent SDK — author `agent/otel.ts`, pass `serve({ otel })`,
|
|
3
|
+
* or set `OTEL_EXPORTER_OTLP_ENDPOINT` (standard OTel env).
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { defineOtel } from "@cursor/july/otel";
|
|
7
|
+
*
|
|
8
|
+
* export default defineOtel({
|
|
9
|
+
* serviceName: "cursor",
|
|
10
|
+
* exporters: [{ url: "https://otel.example.com", protocol: "http/protobuf" }],
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { brandDefinition } from "./internal/brand.js";
|
|
16
|
+
|
|
17
|
+
export type OtelSignal = "traces" | "metrics" | "logs";
|
|
18
|
+
|
|
19
|
+
/** One OTLP destination. Multiple entries fan out. */
|
|
20
|
+
export interface OtelExporterConfig {
|
|
21
|
+
/**
|
|
22
|
+
* OTLP base URL (`https://otel.example.com`) or signal path
|
|
23
|
+
* (`…/v1/traces`). Signal paths are stripped to the base; the runtime
|
|
24
|
+
* re-appends `/v1/{signal}`.
|
|
25
|
+
*/
|
|
26
|
+
url: string;
|
|
27
|
+
/** Extra HTTP headers (merged with `OTEL_EXPORTER_OTLP_HEADERS`). */
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Wire format. Default `http/protobuf` (matches Cursor enterprise OTLP).
|
|
31
|
+
* Use `http/json` for collectors that only accept JSON.
|
|
32
|
+
*/
|
|
33
|
+
protocol?: "http/json" | "http/protobuf";
|
|
34
|
+
/**
|
|
35
|
+
* Restrict this destination to these signals. Default: all enabled
|
|
36
|
+
* signals. Set by env when per-signal `OTEL_EXPORTER_OTLP_*_ENDPOINT`
|
|
37
|
+
* vars differ.
|
|
38
|
+
*/
|
|
39
|
+
signals?: OtelSignal[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OtelTraceOptions {
|
|
43
|
+
/** Sample ratio in `[0, 1]`. Default `1`. */
|
|
44
|
+
sampleRatio?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface OtelMetricOptions {
|
|
48
|
+
/** Export interval in ms. Default `10000`. */
|
|
49
|
+
exportIntervalMillis?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface OtelLogOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Include user prompt text on log records / span events.
|
|
55
|
+
* Default false; also toggled by `OTEL_LOG_USER_PROMPTS=1`.
|
|
56
|
+
*/
|
|
57
|
+
userPrompts?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Include tool argument / result payloads and failure text (truncated).
|
|
60
|
+
* Default false; also `OTEL_LOG_TOOL_CONTENT=1`.
|
|
61
|
+
*/
|
|
62
|
+
toolContent?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Authored / programmatic OTel config. Empty config still enables when
|
|
67
|
+
* standard `OTEL_EXPORTER_OTLP_*` env vars are set.
|
|
68
|
+
*/
|
|
69
|
+
export interface OtelConfig {
|
|
70
|
+
/**
|
|
71
|
+
* Resource `service.name`. Default: `OTEL_SERVICE_NAME` or `"cursor"`
|
|
72
|
+
* (same as enterprise export) so Agent SDK signals join org streams.
|
|
73
|
+
*/
|
|
74
|
+
serviceName?: string;
|
|
75
|
+
/** Resource `service.version`. */
|
|
76
|
+
serviceVersion?: string;
|
|
77
|
+
/** Extra resource attributes on every signal. */
|
|
78
|
+
resourceAttributes?: Record<string, string>;
|
|
79
|
+
/**
|
|
80
|
+
* OTLP destinations. Default: from `OTEL_EXPORTER_OTLP_ENDPOINT` /
|
|
81
|
+
* per-signal `*_TRACES_ENDPOINT` etc. Empty with no env = disabled.
|
|
82
|
+
*/
|
|
83
|
+
exporters?: OtelExporterConfig[];
|
|
84
|
+
/** Traces on/off or options. Default on. */
|
|
85
|
+
traces?: boolean | OtelTraceOptions;
|
|
86
|
+
/** Metrics on/off or options. Default on. */
|
|
87
|
+
metrics?: boolean | OtelMetricOptions;
|
|
88
|
+
/** Logs on/off or options. Default off (opt-in content). */
|
|
89
|
+
logs?: boolean | OtelLogOptions;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type OtelDefinition = OtelConfig & { readonly __agentServe: "otel" };
|
|
93
|
+
|
|
94
|
+
/** Author OpenTelemetry export under `agent/otel.ts`. */
|
|
95
|
+
export function defineOtel(config: OtelConfig = {}): OtelDefinition {
|
|
96
|
+
if (config.exporters !== undefined) {
|
|
97
|
+
for (const exporter of config.exporters) {
|
|
98
|
+
if (typeof exporter.url !== "string" || exporter.url.length === 0) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"defineOtel: exporters[].url must be a non-empty string"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (
|
|
104
|
+
exporter.protocol !== undefined &&
|
|
105
|
+
exporter.protocol !== "http/json" &&
|
|
106
|
+
exporter.protocol !== "http/protobuf"
|
|
107
|
+
) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
'defineOtel: exporters[].protocol must be "http/json" or "http/protobuf"'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
assertSignals(exporter.signals);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (config.resourceAttributes !== undefined) {
|
|
116
|
+
for (const [key, value] of Object.entries(config.resourceAttributes)) {
|
|
117
|
+
if (typeof value !== "string") {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`defineOtel: resourceAttributes.${key} must be a string`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
assertSampleRatio(config.traces);
|
|
125
|
+
return brandDefinition("otel", config);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function assertSignals(signals: OtelSignal[] | undefined): void {
|
|
129
|
+
if (signals === undefined) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (signals.length === 0) {
|
|
133
|
+
throw new Error("defineOtel: exporters[].signals must be non-empty");
|
|
134
|
+
}
|
|
135
|
+
for (const signal of signals) {
|
|
136
|
+
if (signal !== "traces" && signal !== "metrics" && signal !== "logs") {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'defineOtel: exporters[].signals must be "traces" | "metrics" | "logs"'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function assertSampleRatio(traces: OtelConfig["traces"]): void {
|
|
145
|
+
if (traces === undefined || typeof traces === "boolean") {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const ratio = traces.sampleRatio;
|
|
149
|
+
if (ratio === undefined) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) {
|
|
153
|
+
throw new Error("defineOtel: traces.sampleRatio must be in [0, 1]");
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type { InteractionUpdate, SDKCustomTool } from "@cursor/sdk";
|
|
12
12
|
import type { z } from "zod";
|
|
13
13
|
import type { ABConfigFile } from "./ab.js";
|
|
14
|
+
import type { OtelConfig, OtelDefinition } from "./otel.js";
|
|
14
15
|
import type { StorageDefinition } from "./storage.js";
|
|
15
16
|
|
|
16
17
|
// ============================================================================
|
|
@@ -46,7 +47,8 @@ export type DefinitionKind =
|
|
|
46
47
|
| "eval"
|
|
47
48
|
| "ab"
|
|
48
49
|
| "storage"
|
|
49
|
-
| "artifacts"
|
|
50
|
+
| "artifacts"
|
|
51
|
+
| "otel";
|
|
50
52
|
|
|
51
53
|
export interface BrandedDefinition<K extends DefinitionKind> {
|
|
52
54
|
readonly __agentServe: K;
|
|
@@ -1422,6 +1424,14 @@ export interface SendMessageOptions {
|
|
|
1422
1424
|
continuationToken?: string;
|
|
1423
1425
|
/** Initial channel state for newly created sessions. */
|
|
1424
1426
|
state?: JsonValue;
|
|
1427
|
+
/**
|
|
1428
|
+
* On resume (continuation hit), shallow-merge into the existing session
|
|
1429
|
+
* channel state before the turn starts. Null / undefined values in the
|
|
1430
|
+
* patch do not overwrite existing keys, so sticky fields (stream ts,
|
|
1431
|
+
* progress banner comment id, …) survive while wake-derived fields
|
|
1432
|
+
* (`headSha`, refs) refresh. Ignored on create; pass {@link state} there.
|
|
1433
|
+
*/
|
|
1434
|
+
refreshState?: JsonValue;
|
|
1425
1435
|
/** Session display title (applied when creating a new session). */
|
|
1426
1436
|
title?: string;
|
|
1427
1437
|
/**
|
|
@@ -2374,6 +2384,8 @@ export interface AgentProject {
|
|
|
2374
2384
|
storage?: StorageDefinition;
|
|
2375
2385
|
/** Optional `agent/artifacts.ts` (`defineArtifacts`). */
|
|
2376
2386
|
artifacts?: ArtifactsDefinition;
|
|
2387
|
+
/** Optional `agent/otel.ts` (`defineOtel`). */
|
|
2388
|
+
otel?: OtelDefinition;
|
|
2377
2389
|
diagnostics: Diagnostic[];
|
|
2378
2390
|
}
|
|
2379
2391
|
|
|
@@ -2456,6 +2468,7 @@ export interface AgentProjectInfo {
|
|
|
2456
2468
|
};
|
|
2457
2469
|
/** Project storage sink from `agent/storage.ts`, when authored. */
|
|
2458
2470
|
storage?: { name?: string };
|
|
2471
|
+
otel?: { serviceName?: string; enabled: true };
|
|
2459
2472
|
/** Declared artifact kinds from `agent/artifacts.ts`, when authored. */
|
|
2460
2473
|
artifacts?: { kinds: string[]; agentTool: boolean; max: number };
|
|
2461
2474
|
diagnostics: Diagnostic[];
|
|
@@ -2646,6 +2659,12 @@ export interface ServeOptions {
|
|
|
2646
2659
|
apiKey?: string;
|
|
2647
2660
|
};
|
|
2648
2661
|
logger?: (line: string) => void;
|
|
2662
|
+
/**
|
|
2663
|
+
* OpenTelemetry export. Pass a config object, or `false` to force-disable
|
|
2664
|
+
* even when `OTEL_EXPORTER_OTLP_*` env vars / `agent/otel.ts` are set.
|
|
2665
|
+
* When omitted, env + authored `defineOtel` still enable export.
|
|
2666
|
+
*/
|
|
2667
|
+
otel?: OtelConfig | false;
|
|
2649
2668
|
}
|
|
2650
2669
|
|
|
2651
2670
|
/** One agent mounted on a server (its URL slug + compiled project). */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Triage agent
|
|
2
|
+
|
|
3
|
+
A triage front door for your team. Send it bug reports, questions, and
|
|
4
|
+
feedback; it classifies each report against a rubric, judges severity,
|
|
5
|
+
records a structured verdict, and replies with the next step.
|
|
6
|
+
|
|
7
|
+
Scaffolded with:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @cursor/july init ./my-triage-agent --template triage
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Run it
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
agent-sdk login # once per machine
|
|
17
|
+
agent-sdk dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Open the playground URL it prints. Chat directly, or use the `/report`
|
|
21
|
+
slash command to submit a report the way an integration would
|
|
22
|
+
(`POST /v1/channels/intake/report`).
|
|
23
|
+
|
|
24
|
+
## Wire it to Slack
|
|
25
|
+
|
|
26
|
+
The Slack channel idles until its tokens exist. Create a Slack app and
|
|
27
|
+
its env with:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
agent-sdk slack setup
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then @mention the bot or DM it. The setup-slack skill in the
|
|
34
|
+
@cursor/july package has the full walkthrough.
|
|
35
|
+
|
|
36
|
+
## Make it yours
|
|
37
|
+
|
|
38
|
+
- `agent/instructions.md` owns the flow every report follows.
|
|
39
|
+
- `agent/skills/triage-rubric.md` owns the kinds, the tie-breakers, and
|
|
40
|
+
the severity ladder. Edit this first; it is the judgment.
|
|
41
|
+
- `agent/tools/record_triage.ts` owns the verdict shape. Point its
|
|
42
|
+
`execute` at your tracker (Linear, GitHub Issues, a webhook) to file
|
|
43
|
+
real tickets.
|
|
44
|
+
- `evals/triage.eval.ts` holds three smoke cases. Run `agent-sdk eval`
|
|
45
|
+
after every rubric change.
|
|
46
|
+
|
|
47
|
+
The full walkthrough, from scaffold through deploy, is the triage
|
|
48
|
+
cookbook: run `agent-sdk docs` and open Templates, then Triage agent.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineChannel, POST } from "@cursor/july/channels";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export default defineChannel({
|
|
5
|
+
routes: [
|
|
6
|
+
POST("/report", {
|
|
7
|
+
description: "Submit a report for triage",
|
|
8
|
+
bodySchema: z.object({
|
|
9
|
+
message: z.string(),
|
|
10
|
+
reporter: z.string().optional(),
|
|
11
|
+
}),
|
|
12
|
+
handler: async (_req, { send, body }) => {
|
|
13
|
+
const from =
|
|
14
|
+
body.reporter === undefined ? "" : ` (from ${body.reporter})`;
|
|
15
|
+
const session = await send(`New report${from}:\n\n${body.message}`);
|
|
16
|
+
return Response.json({ ok: true, sessionId: session.id });
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Triage agent
|
|
2
|
+
|
|
3
|
+
You are the triage front door for this team. People send you reports:
|
|
4
|
+
bug reports, questions, feedback, feature ideas. You classify each one,
|
|
5
|
+
judge its severity, and hand back a verdict the team can act on.
|
|
6
|
+
|
|
7
|
+
Read the `triage-rubric` skill before classifying. It defines the three
|
|
8
|
+
kinds and the tie-breakers.
|
|
9
|
+
|
|
10
|
+
## Every report
|
|
11
|
+
|
|
12
|
+
1. Read the report. If it is too thin to classify (no symptom, no
|
|
13
|
+
question, no opinion), ask for the missing piece once. Otherwise do
|
|
14
|
+
not ask; classify with what you have.
|
|
15
|
+
2. If a `memory/` directory exists in your workspace, grep the journal
|
|
16
|
+
for similar past reports and say when one matches.
|
|
17
|
+
3. Call `record_triage` exactly once with your verdict.
|
|
18
|
+
4. Reply with the verdict: kind, severity, a one-line reason, and the
|
|
19
|
+
next step. Keep the reply under six lines. No preamble.
|
|
20
|
+
|
|
21
|
+
## Boundaries
|
|
22
|
+
|
|
23
|
+
- You classify and route. You do not fix bugs, write code, or promise
|
|
24
|
+
timelines.
|
|
25
|
+
- When a report mixes kinds, pick the dominant one and name the split
|
|
26
|
+
in your reason.
|
|
27
|
+
- Never invent reporter details. Missing fields stay empty.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Definitions and tie-breakers for classifying a report as bug, question, or feedback, plus the severity ladder.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Triage rubric
|
|
6
|
+
|
|
7
|
+
## Kinds
|
|
8
|
+
|
|
9
|
+
- **bug**: something is broken, erroring, slow, or behaving wrong right
|
|
10
|
+
now, and the reporter wants it diagnosed or fixed. Usually has a
|
|
11
|
+
symptom, an error, an id, a timestamp, or steps.
|
|
12
|
+
- **question**: the reporter wants to know something that has an answer
|
|
13
|
+
(how does X work, is Y expected, who owns Z). Someone has to look it
|
|
14
|
+
up, not fix it.
|
|
15
|
+
- **feedback**: an opinion, suggestion, complaint about how something
|
|
16
|
+
behaves, or a feature idea. The reporter is telling the team
|
|
17
|
+
something, not asking it to find anything out.
|
|
18
|
+
|
|
19
|
+
## Tie-breakers
|
|
20
|
+
|
|
21
|
+
- "This is annoying, you should change X" is feedback even when it
|
|
22
|
+
sounds like a complaint.
|
|
23
|
+
- A question about a specific failure that already happened is a bug.
|
|
24
|
+
"Why did my export fail yesterday?" needs an investigation, not a
|
|
25
|
+
lookup.
|
|
26
|
+
- A bug report that also suggests a fix is a bug.
|
|
27
|
+
- A known bug with a workaround that only works sometimes is a bug. The
|
|
28
|
+
unreliable workaround is the thing to explain.
|
|
29
|
+
- A feature idea phrased as a question ("could we have dark mode?") is
|
|
30
|
+
feedback.
|
|
31
|
+
|
|
32
|
+
## Severity
|
|
33
|
+
|
|
34
|
+
- **urgent**: data loss, security exposure, or many users blocked with
|
|
35
|
+
no workaround.
|
|
36
|
+
- **high**: a core flow is broken or wrong for some users, no
|
|
37
|
+
reasonable workaround.
|
|
38
|
+
- **medium**: broken or wrong with a workaround, or a sharp edge that
|
|
39
|
+
costs time.
|
|
40
|
+
- **low**: cosmetic, minor friction, questions, and most feedback.
|
|
41
|
+
|
|
42
|
+
Questions and feedback default to low unless the report shows real
|
|
43
|
+
user impact.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineTool } from "@cursor/july/tools";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export default defineTool({
|
|
5
|
+
description:
|
|
6
|
+
"Record the structured triage verdict for the current report. Call exactly once per report, before replying.",
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
kind: z.enum(["bug", "question", "feedback"]),
|
|
9
|
+
severity: z.enum(["low", "medium", "high", "urgent"]),
|
|
10
|
+
title: z
|
|
11
|
+
.string()
|
|
12
|
+
.max(80)
|
|
13
|
+
.describe("Short, imperative summary of the report."),
|
|
14
|
+
summary: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("What the reporter said, in two sentences or less."),
|
|
17
|
+
nextStep: z.string().describe("The single next action for the team."),
|
|
18
|
+
reporter: z.string().optional().describe("Who reported it, when known."),
|
|
19
|
+
}),
|
|
20
|
+
async execute(verdict) {
|
|
21
|
+
return { recorded: true, at: new Date().toISOString(), verdict };
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineEval, includes } from "@cursor/july/evals";
|
|
2
|
+
|
|
3
|
+
export default defineEval({
|
|
4
|
+
tags: ["smoke"],
|
|
5
|
+
cases: [
|
|
6
|
+
{
|
|
7
|
+
id: "bug",
|
|
8
|
+
description: "A concrete failure is recorded as a bug.",
|
|
9
|
+
async test(t) {
|
|
10
|
+
await t.send(
|
|
11
|
+
"Export to CSV has failed with a 500 since this morning. Request id 41ac. Retrying does not help."
|
|
12
|
+
);
|
|
13
|
+
t.succeeded();
|
|
14
|
+
t.calledTool("record_triage");
|
|
15
|
+
t.check(t.reply, includes(/bug/i));
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "question",
|
|
20
|
+
description: "A how-does-it-work ask is recorded as a question.",
|
|
21
|
+
async test(t) {
|
|
22
|
+
await t.send(
|
|
23
|
+
"How do I rotate the API keys for the staging environment?"
|
|
24
|
+
);
|
|
25
|
+
t.succeeded();
|
|
26
|
+
t.calledTool("record_triage");
|
|
27
|
+
t.check(t.reply, includes(/question/i));
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "feedback",
|
|
32
|
+
description: "A suggestion is recorded as feedback.",
|
|
33
|
+
async test(t) {
|
|
34
|
+
await t.send(
|
|
35
|
+
"The onboarding wizard has too many steps. You should collapse the profile pages into one."
|
|
36
|
+
);
|
|
37
|
+
t.succeeded();
|
|
38
|
+
t.calledTool("record_triage");
|
|
39
|
+
t.check(t.reply, includes(/feedback/i));
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "triage-agent",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Triage agent: classifies bug reports, questions, and feedback into structured verdicts.",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"check": "tsc --noEmit",
|
|
8
|
+
"dev": "agent-sdk dev"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@cursor/july": "latest",
|
|
12
|
+
"zod": "^3.25.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22.14.0",
|
|
16
|
+
"typescript": "^5.8.0"
|
|
17
|
+
}
|
|
18
|
+
}
|