@ponythewhite/base-context 1.0.0 → 1.0.1
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/CHANGELOG.md +33 -0
- package/NOTICE +0 -5
- package/README.md +44 -27
- package/dist/NOTICE +0 -5
- package/dist/base-context-runtime/pyproject.toml +1 -1
- package/dist/base-context-runtime/src/rlm/__init__.py +7 -5
- package/dist/base-context-runtime/test/test_subagent_registry.py +28 -1
- package/dist/base-context-runtime/uv.lock +1 -1
- package/dist/build-info.json +1 -1
- package/dist/bundle/amazon-bedrock.js +21 -0
- package/dist/bundle/{anthropic-YY4S534V.js → anthropic-UV5GZDGU.js} +5 -5
- package/dist/bundle/{azure-openai-responses-JRP6MIM3.js → azure-openai-responses-WSNPXLYC.js} +6 -6
- package/dist/bundle/{bundled-modules-P5HARVZA.js → bundled-modules-TXUMJ2XQ.js} +71 -16
- package/dist/bundle/chunk-27QCJ2AZ.js +2578 -0
- package/dist/bundle/{chunk-MZHTNNVA.js → chunk-4GXPNXOJ.js} +8 -0
- package/dist/bundle/{chunk-SC5KG5R4.js → chunk-DGNX7HQ3.js} +1 -7
- package/dist/bundle/{chunk-A53O363M.js → chunk-GRJFXLBM.js} +0 -1
- package/dist/bundle/chunk-HKINIBP4.js +29 -0
- package/dist/bundle/{chunk-UDSW6FNG.js → chunk-KTNETTHI.js} +1 -1
- package/dist/bundle/{chunk-BYVP7LWV.js → chunk-S3INFPZZ.js} +1861 -4662
- package/dist/bundle/{chunk-73DCPU3D.js → chunk-V3L54UD6.js} +1019 -3427
- package/dist/bundle/{chunk-U7AUVG3B.js → chunk-VSIYFWYI.js} +24 -6
- package/dist/bundle/{chunk-IW77OXFS.js → chunk-W46VZFI6.js} +0 -2099
- package/dist/bundle/{cli-main-O4X7GFGY.js → cli-main-BYMSRPH6.js} +8 -6
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/{google-AUTVGDGZ.js → google-T32FMD4G.js} +6 -6
- package/dist/bundle/{google-vertex-Q75D7RKS.js → google-vertex-56KP6I3B.js} +3 -3
- package/dist/bundle/{main-XKOW2BV6.js → main-ODOYCEQC.js} +7 -5
- package/dist/bundle/{mistral-7ESB7QUD.js → mistral-WGBMYIVU.js} +5 -5
- package/dist/bundle/{openai-codex-responses-LV3QRPN4.js → openai-codex-responses-PIV2RQ3G.js} +6 -6
- package/dist/bundle/{openai-completions-OJECLPXJ.js → openai-completions-64C3JPR4.js} +11 -22
- package/dist/bundle/{openai-responses-FN6HZMBC.js → openai-responses-UI7RSTDV.js} +6 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +108 -59
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/daemon-ps.d.ts +4 -3
- package/dist/cli/daemon-ps.d.ts.map +1 -1
- package/dist/cli/daemon-ps.js +6 -1
- package/dist/cli/daemon-ps.js.map +1 -1
- package/dist/cli/product-doctor.d.ts +0 -1
- package/dist/cli/product-doctor.d.ts.map +1 -1
- package/dist/cli/product-doctor.js +6 -3
- package/dist/cli/product-doctor.js.map +1 -1
- package/dist/config.d.ts +0 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-config.d.ts +1 -2
- package/dist/core/agent-session-config.d.ts.map +1 -1
- package/dist/core/agent-session-config.js +0 -3
- package/dist/core/agent-session-config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +5 -4
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +5 -3
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +2 -27
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +10 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +107 -107
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +8 -35
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +33 -243
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/cron-jobs.d.ts.map +1 -1
- package/dist/core/cron-jobs.js +7 -1
- package/dist/core/cron-jobs.js.map +1 -1
- package/dist/core/inference-coordinator.d.ts +6 -1
- package/dist/core/inference-coordinator.d.ts.map +1 -1
- package/dist/core/inference-coordinator.js +18 -2
- package/dist/core/inference-coordinator.js.map +1 -1
- package/dist/core/kernel/bootstrap.d.ts.map +1 -1
- package/dist/core/kernel/bootstrap.js +2 -2
- package/dist/core/kernel/bootstrap.js.map +1 -1
- package/dist/core/kernel/repl-manager.d.ts +2 -0
- package/dist/core/kernel/repl-manager.d.ts.map +1 -1
- package/dist/core/kernel/repl-manager.js +44 -1
- package/dist/core/kernel/repl-manager.js.map +1 -1
- package/dist/core/logging.d.ts +4 -3
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/logging.js +6 -6
- package/dist/core/logging.js.map +1 -1
- package/dist/core/mcp/mcp-manager.d.ts.map +1 -1
- package/dist/core/mcp/mcp-manager.js +3 -3
- package/dist/core/mcp/mcp-manager.js.map +1 -1
- package/dist/core/model-registry.d.ts +1 -16
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +88 -196
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +2 -19
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +36 -266
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-contracts.d.ts +5 -6
- package/dist/core/provider-contracts.d.ts.map +1 -1
- package/dist/core/provider-contracts.js +25 -36
- package/dist/core/provider-contracts.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +0 -2
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/rlm-max-subagents.d.ts +32 -0
- package/dist/core/rlm-max-subagents.d.ts.map +1 -0
- package/dist/core/rlm-max-subagents.js +50 -0
- package/dist/core/rlm-max-subagents.js.map +1 -0
- package/dist/core/rlm-runtime.d.ts +3 -3
- package/dist/core/rlm-runtime.d.ts.map +1 -1
- package/dist/core/rlm-runtime.js.map +1 -1
- package/dist/core/sdk.d.ts +4 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +27 -14
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -16
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +12 -44
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +6 -6
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/installer.mjs +2 -3
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +39 -34
- package/dist/main.js.map +1 -1
- package/dist/modes/acp/acp-mode.d.ts.map +1 -1
- package/dist/modes/acp/acp-mode.js +1 -1
- package/dist/modes/acp/acp-mode.js.map +1 -1
- package/dist/modes/agent-connection/daemon-agent-connection.d.ts +6 -2
- package/dist/modes/agent-connection/daemon-agent-connection.d.ts.map +1 -1
- package/dist/modes/agent-connection/daemon-agent-connection.js +13 -2
- package/dist/modes/agent-connection/daemon-agent-connection.js.map +1 -1
- package/dist/modes/agent-connection/in-process-agent-connection.d.ts +2 -0
- package/dist/modes/agent-connection/in-process-agent-connection.d.ts.map +1 -1
- package/dist/modes/agent-connection/in-process-agent-connection.js +6 -0
- package/dist/modes/agent-connection/in-process-agent-connection.js.map +1 -1
- package/dist/modes/agent-connection/types.d.ts +3 -0
- package/dist/modes/agent-connection/types.d.ts.map +1 -1
- package/dist/modes/agent-connection/types.js.map +1 -1
- package/dist/modes/agents-view/agents-view-mode.d.ts.map +1 -1
- package/dist/modes/agents-view/agents-view-mode.js +2 -19
- package/dist/modes/agents-view/agents-view-mode.js.map +1 -1
- package/dist/modes/daemon/daemon-mode.d.ts +5 -0
- package/dist/modes/daemon/daemon-mode.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-mode.js +107 -12
- package/dist/modes/daemon/daemon-mode.js.map +1 -1
- package/dist/modes/daemon/daemon-protocol.d.ts +194 -165
- package/dist/modes/daemon/daemon-protocol.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-protocol.js +16 -11
- package/dist/modes/daemon/daemon-protocol.js.map +1 -1
- package/dist/modes/daemon/daemon-rlm-capacity-coordinator.d.ts +19 -0
- package/dist/modes/daemon/daemon-rlm-capacity-coordinator.d.ts.map +1 -0
- package/dist/modes/daemon/daemon-rlm-capacity-coordinator.js +74 -0
- package/dist/modes/daemon/daemon-rlm-capacity-coordinator.js.map +1 -0
- package/dist/modes/daemon/daemon-rlm-capacity.d.ts +19 -0
- package/dist/modes/daemon/daemon-rlm-capacity.d.ts.map +1 -0
- package/dist/modes/daemon/daemon-rlm-capacity.js +85 -0
- package/dist/modes/daemon/daemon-rlm-capacity.js.map +1 -0
- package/dist/modes/daemon/daemon-session-summarizer.d.ts +2 -3
- package/dist/modes/daemon/daemon-session-summarizer.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-session-summarizer.js +32 -23
- package/dist/modes/daemon/daemon-session-summarizer.js.map +1 -1
- package/dist/modes/daemon/daemon-state-root.d.ts +15 -0
- package/dist/modes/daemon/daemon-state-root.d.ts.map +1 -0
- package/dist/modes/daemon/daemon-state-root.js +40 -0
- package/dist/modes/daemon/daemon-state-root.js.map +1 -0
- package/dist/modes/daemon/daemon-supervisor-ownership.d.ts +2 -0
- package/dist/modes/daemon/daemon-supervisor-ownership.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-supervisor-ownership.js +27 -0
- package/dist/modes/daemon/daemon-supervisor-ownership.js.map +1 -1
- package/dist/modes/daemon/daemon-supervisor.d.ts +10 -1
- package/dist/modes/daemon/daemon-supervisor.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-supervisor.js +213 -20
- package/dist/modes/daemon/daemon-supervisor.js.map +1 -1
- package/dist/modes/daemon/daemon-worker-protocol.d.ts +28 -1
- package/dist/modes/daemon/daemon-worker-protocol.d.ts.map +1 -1
- package/dist/modes/daemon/daemon-worker-protocol.js +16 -2
- package/dist/modes/daemon/daemon-worker-protocol.js.map +1 -1
- package/dist/modes/daemon/saved-session-catalog.d.ts.map +1 -1
- package/dist/modes/daemon/saved-session-catalog.js +2 -0
- package/dist/modes/daemon/saved-session-catalog.js.map +1 -1
- package/dist/modes/interactive/auth-flows.d.ts +0 -6
- package/dist/modes/interactive/auth-flows.d.ts.map +1 -1
- package/dist/modes/interactive/auth-flows.js +6 -126
- package/dist/modes/interactive/auth-flows.js.map +1 -1
- package/dist/modes/interactive/components/configuration-menu.d.ts +3 -0
- package/dist/modes/interactive/components/configuration-menu.d.ts.map +1 -1
- package/dist/modes/interactive/components/configuration-menu.js +13 -2
- package/dist/modes/interactive/components/configuration-menu.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +2 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +0 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +0 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +0 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +3 -40
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +2 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +13 -2
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +2 -11
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/feature-hints.d.ts.map +1 -1
- package/dist/modes/interactive/feature-hints.js +0 -4
- package/dist/modes/interactive/feature-hints.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -12
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +81 -507
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/onboarding.d.ts +0 -3
- package/dist/modes/interactive/onboarding.d.ts.map +1 -1
- package/dist/modes/interactive/onboarding.js +0 -17
- package/dist/modes/interactive/onboarding.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +2 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +24 -11
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/node/amazon-bedrock.d.ts +2 -0
- package/dist/node/amazon-bedrock.d.ts.map +1 -0
- package/dist/node/amazon-bedrock.js +6 -0
- package/dist/node/amazon-bedrock.js.map +1 -0
- package/dist/product-identity.d.ts +0 -1
- package/dist/product-identity.d.ts.map +1 -1
- package/dist/product-identity.js +0 -1
- package/dist/product-identity.js.map +1 -1
- package/docs/development.md +1 -1
- package/docs/fork-philosophy.md +4 -8
- package/docs/installation.md +83 -62
- package/docs/providers.md +23 -25
- package/docs/quickstart.md +19 -10
- package/docs/rlm-runtime.md +13 -5
- package/docs/rpc.md +4 -4
- package/docs/sdk.md +20 -11
- package/docs/sessions.md +0 -1
- package/docs/settings.md +9 -42
- package/docs/skills.md +0 -1
- package/docs/upstream-0.9.5.md +171 -0
- package/docs/usage.md +14 -10
- package/package.json +4 -4
- package/dist/core/agent-traces.d.ts +0 -149
- package/dist/core/agent-traces.d.ts.map +0 -1
- package/dist/core/agent-traces.js +0 -904
- package/dist/core/agent-traces.js.map +0 -1
- package/dist/core/prime-inference-auth.d.ts +0 -70
- package/dist/core/prime-inference-auth.d.ts.map +0 -1
- package/dist/core/prime-inference-auth.js +0 -352
- package/dist/core/prime-inference-auth.js.map +0 -1
- package/dist/core/prime-inference-model-selection.d.ts +0 -16
- package/dist/core/prime-inference-model-selection.d.ts.map +0 -1
- package/dist/core/prime-inference-model-selection.js +0 -16
- package/dist/core/prime-inference-model-selection.js.map +0 -1
- package/dist/core/prime-inference-models.d.ts +0 -6
- package/dist/core/prime-inference-models.d.ts.map +0 -1
- package/dist/core/prime-inference-models.js +0 -62
- package/dist/core/prime-inference-models.js.map +0 -1
- package/dist/core/telemetry.d.ts +0 -95
- package/dist/core/telemetry.d.ts.map +0 -1
- package/dist/core/telemetry.js +0 -637
- package/dist/core/telemetry.js.map +0 -1
- package/dist/modes/interactive/components/prime-onboarding-splash.d.ts +0 -39
- package/dist/modes/interactive/components/prime-onboarding-splash.d.ts.map +0 -1
- package/dist/modes/interactive/components/prime-onboarding-splash.js +0 -286
- package/dist/modes/interactive/components/prime-onboarding-splash.js.map +0 -1
- package/dist/modes/interactive/components/prime-team-selector.d.ts +0 -30
- package/dist/modes/interactive/components/prime-team-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/prime-team-selector.js +0 -166
- package/dist/modes/interactive/components/prime-team-selector.js.map +0 -1
- package/dist/skills/prime-intellect/SKILL.md +0 -87
- package/dist/skills/prime-intellect/references/compute.md +0 -45
- package/dist/skills/prime-intellect/references/environments.md +0 -73
- package/dist/skills/prime-intellect/references/inference.md +0 -47
- package/dist/skills/prime-intellect/references/sandboxes.md +0 -75
- package/skills/prime-intellect/SKILL.md +0 -87
- package/skills/prime-intellect/references/compute.md +0 -45
- package/skills/prime-intellect/references/environments.md +0 -73
- package/skills/prime-intellect/references/inference.md +0 -47
- package/skills/prime-intellect/references/sandboxes.md +0 -75
|
@@ -1,904 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
-
import { appendRotatingLog, getAgentDir, getAgentTracesLogPath, getSessionsDir, VERSION } from "../config.js";
|
|
7
|
-
import { assertProductStatePath } from "../runtime-paths.js";
|
|
8
|
-
import { BASE_CONTEXT_TRACES_PROVIDER_ID, resolvePrimeAgentTracesBaseUrl } from "./prime-inference-auth.js";
|
|
9
|
-
import { readSessionJournal, readSessionJournalHeader } from "./session-journal-reader.js";
|
|
10
|
-
import { getSessionArtifactsRoot } from "./session-manager.js";
|
|
11
|
-
const MAX_TRACE_BYTES = 20 * 1024 * 1024;
|
|
12
|
-
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
13
|
-
const TRACE_UPLOAD_DEBOUNCE_MS = 1_000;
|
|
14
|
-
const TRACE_UPLOAD_MIN_INTERVAL_MS = 60_000;
|
|
15
|
-
const TRACE_UPLOAD_RETRY_BASE_DELAY_MS = 500;
|
|
16
|
-
const TRACE_UPLOAD_RETRY_MAX_DELAY_MS = 10_000;
|
|
17
|
-
const TRACE_UPLOAD_MAX_RETRIES = 3;
|
|
18
|
-
const TRACE_UPLOAD_RETRY_JITTER = 0.2;
|
|
19
|
-
const TRACE_PREVIEW_MAX_CHARS = 8_000;
|
|
20
|
-
const TRACE_UPLOAD_ALL_CONCURRENCY = 4;
|
|
21
|
-
const TRACE_UPLOAD_RATE_LIMIT_REQUESTS = 5;
|
|
22
|
-
const TRACE_UPLOAD_RATE_LIMIT_WINDOW_MS = 60_000;
|
|
23
|
-
const TRACE_UPLOAD_RATE_LIMIT_SAFETY_MS = 100;
|
|
24
|
-
const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
25
|
-
const TRACE_UPLOAD_ALL_MIN_REQUEST_INTERVAL_MS = Math.ceil(TRACE_UPLOAD_RATE_LIMIT_WINDOW_MS / TRACE_UPLOAD_RATE_LIMIT_REQUESTS) + TRACE_UPLOAD_RATE_LIMIT_SAFETY_MS;
|
|
26
|
-
function stringEnv(name) {
|
|
27
|
-
const value = process.env[name];
|
|
28
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
29
|
-
}
|
|
30
|
-
function isRecord(value) {
|
|
31
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
-
}
|
|
33
|
-
function describeError(error) {
|
|
34
|
-
if (!(error instanceof Error)) {
|
|
35
|
-
return String(error);
|
|
36
|
-
}
|
|
37
|
-
const cause = error.cause;
|
|
38
|
-
if (isRecord(cause)) {
|
|
39
|
-
const code = typeof cause.code === "string" ? cause.code : undefined;
|
|
40
|
-
const causeMessage = typeof cause.message === "string" ? cause.message : undefined;
|
|
41
|
-
const detail = code ?? causeMessage;
|
|
42
|
-
if (detail && detail !== error.message) {
|
|
43
|
-
return `${error.message} (${detail})`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return error.message;
|
|
47
|
-
}
|
|
48
|
-
class TraceUploadTimeoutError extends Error {
|
|
49
|
-
constructor(timeoutMs) {
|
|
50
|
-
super(`Trace upload timed out after ${timeoutMs}ms`);
|
|
51
|
-
this.name = "TraceUploadTimeoutError";
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const RETRIABLE_NETWORK_CODES = new Set([
|
|
55
|
-
"ECONNRESET",
|
|
56
|
-
"ECONNREFUSED",
|
|
57
|
-
"ECONNABORTED",
|
|
58
|
-
"EPIPE",
|
|
59
|
-
"ETIMEDOUT",
|
|
60
|
-
"ENETUNREACH",
|
|
61
|
-
"ENETDOWN",
|
|
62
|
-
"EAI_AGAIN",
|
|
63
|
-
"UND_ERR_SOCKET",
|
|
64
|
-
"UND_ERR_CONNECT_TIMEOUT",
|
|
65
|
-
]);
|
|
66
|
-
// 429 is deliberately absent: a rate-limited upload is rescheduled by its caller instead of sleeping in-request.
|
|
67
|
-
const RETRIABLE_HTTP_STATUSES = new Set([408, 425, 500, 502, 503, 504]);
|
|
68
|
-
function isRetriableNetworkError(error) {
|
|
69
|
-
if (error instanceof TraceUploadTimeoutError) {
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
if (!(error instanceof Error) || error.name === "AbortError") {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
const cause = error.cause;
|
|
76
|
-
return isRecord(cause) && typeof cause.code === "string" && RETRIABLE_NETWORK_CODES.has(cause.code);
|
|
77
|
-
}
|
|
78
|
-
function stringField(data, key) {
|
|
79
|
-
const value = data[key];
|
|
80
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
81
|
-
}
|
|
82
|
-
function numberField(data, key) {
|
|
83
|
-
const value = data[key];
|
|
84
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
85
|
-
}
|
|
86
|
-
function isSessionHeader(value) {
|
|
87
|
-
return (isRecord(value) &&
|
|
88
|
-
value.type === "session" &&
|
|
89
|
-
typeof value.id === "string" &&
|
|
90
|
-
typeof value.timestamp === "string" &&
|
|
91
|
-
typeof value.cwd === "string" &&
|
|
92
|
-
(value.parentSession === undefined || typeof value.parentSession === "string"));
|
|
93
|
-
}
|
|
94
|
-
function readSessionHeader(sessionFile) {
|
|
95
|
-
try {
|
|
96
|
-
const parsed = readSessionJournalHeader(sessionFile);
|
|
97
|
-
return isSessionHeader(parsed) ? parsed : undefined;
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async function readSessionTraceBody(sessionFile) {
|
|
104
|
-
const lines = [];
|
|
105
|
-
for await (const { json } of readSessionJournal(sessionFile)) {
|
|
106
|
-
lines.push(`${json}\n`);
|
|
107
|
-
}
|
|
108
|
-
return lines.join("");
|
|
109
|
-
}
|
|
110
|
-
/** Active-branch git for the indexing headers: walk leaf to root, not the last git_state in
|
|
111
|
-
* file order (which may belong to a sibling branch). */
|
|
112
|
-
export function activeGitContext(body, header) {
|
|
113
|
-
const byId = new Map();
|
|
114
|
-
let leafId = null;
|
|
115
|
-
for (const line of body.split("\n")) {
|
|
116
|
-
if (!line.trim())
|
|
117
|
-
continue;
|
|
118
|
-
let parsed;
|
|
119
|
-
try {
|
|
120
|
-
parsed = JSON.parse(line);
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (!isRecord(parsed) || parsed.type === "session" || typeof parsed.id !== "string")
|
|
126
|
-
continue;
|
|
127
|
-
byId.set(parsed.id, {
|
|
128
|
-
parentId: typeof parsed.parentId === "string" ? parsed.parentId : null,
|
|
129
|
-
type: typeof parsed.type === "string" ? parsed.type : "",
|
|
130
|
-
git: parsed.git,
|
|
131
|
-
});
|
|
132
|
-
leafId = parsed.id;
|
|
133
|
-
}
|
|
134
|
-
let current = leafId ? byId.get(leafId) : undefined;
|
|
135
|
-
for (let depth = 0; current && depth < byId.size + 1; depth += 1) {
|
|
136
|
-
if (current.type === "git_state" && isRecord(current.git)) {
|
|
137
|
-
return current.git;
|
|
138
|
-
}
|
|
139
|
-
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
140
|
-
}
|
|
141
|
-
return header.git;
|
|
142
|
-
}
|
|
143
|
-
function resolveParentSessionPath(sessionFile, parentSession) {
|
|
144
|
-
return isAbsolute(parentSession) ? parentSession : resolve(dirname(sessionFile), parentSession);
|
|
145
|
-
}
|
|
146
|
-
function resolveTraceContext(sessionFile, header) {
|
|
147
|
-
let traceId = header.id;
|
|
148
|
-
let parentSessionId;
|
|
149
|
-
let currentFile = sessionFile;
|
|
150
|
-
let currentHeader = header;
|
|
151
|
-
for (let depth = 0; depth < 32; depth += 1) {
|
|
152
|
-
if (!currentHeader.parentSession) {
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
const parentPath = resolveParentSessionPath(currentFile, currentHeader.parentSession);
|
|
156
|
-
const parentHeader = readSessionHeader(parentPath);
|
|
157
|
-
if (!parentHeader) {
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
if (depth === 0) {
|
|
161
|
-
parentSessionId = parentHeader.id;
|
|
162
|
-
}
|
|
163
|
-
traceId = parentHeader.id;
|
|
164
|
-
currentFile = parentPath;
|
|
165
|
-
currentHeader = parentHeader;
|
|
166
|
-
}
|
|
167
|
-
return { traceId, parentSessionId };
|
|
168
|
-
}
|
|
169
|
-
function parseResponseObject(text) {
|
|
170
|
-
if (!text.trim()) {
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
try {
|
|
174
|
-
const parsed = JSON.parse(text);
|
|
175
|
-
return isRecord(parsed) ? parsed : undefined;
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
async function readResponseMessage(response) {
|
|
182
|
-
const text = await response.text().catch(() => "");
|
|
183
|
-
if (!text.trim()) {
|
|
184
|
-
return response.statusText || "Unknown error";
|
|
185
|
-
}
|
|
186
|
-
const parsed = parseResponseObject(text);
|
|
187
|
-
if (parsed) {
|
|
188
|
-
const error = parsed.error;
|
|
189
|
-
if (isRecord(error)) {
|
|
190
|
-
const message = stringField(error, "message");
|
|
191
|
-
if (message)
|
|
192
|
-
return message;
|
|
193
|
-
}
|
|
194
|
-
const detail = stringField(parsed, "detail");
|
|
195
|
-
if (detail)
|
|
196
|
-
return detail;
|
|
197
|
-
const message = stringField(parsed, "message");
|
|
198
|
-
if (message)
|
|
199
|
-
return message;
|
|
200
|
-
}
|
|
201
|
-
return text.trim();
|
|
202
|
-
}
|
|
203
|
-
async function fetchWithTimeout(fetchFn, url, init, timeoutMs, signal) {
|
|
204
|
-
const controller = new AbortController();
|
|
205
|
-
const timeoutError = new TraceUploadTimeoutError(timeoutMs);
|
|
206
|
-
let timedOut = false;
|
|
207
|
-
const timeout = setTimeout(() => {
|
|
208
|
-
timedOut = true;
|
|
209
|
-
controller.abort(timeoutError);
|
|
210
|
-
}, timeoutMs);
|
|
211
|
-
timeout.unref();
|
|
212
|
-
const onAbort = () => controller.abort(signal?.reason);
|
|
213
|
-
if (signal?.aborted) {
|
|
214
|
-
onAbort();
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
return await fetchFn(url, { ...init, signal: controller.signal });
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
if (timedOut) {
|
|
224
|
-
throw timeoutError;
|
|
225
|
-
}
|
|
226
|
-
throw error;
|
|
227
|
-
}
|
|
228
|
-
finally {
|
|
229
|
-
clearTimeout(timeout);
|
|
230
|
-
signal?.removeEventListener("abort", onAbort);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
function delay(ms, signal) {
|
|
234
|
-
return new Promise((resolve) => {
|
|
235
|
-
if (signal?.aborted) {
|
|
236
|
-
resolve();
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const timeout = setTimeout(finish, ms);
|
|
240
|
-
timeout.unref();
|
|
241
|
-
const onAbort = () => finish();
|
|
242
|
-
function finish() {
|
|
243
|
-
clearTimeout(timeout);
|
|
244
|
-
signal?.removeEventListener("abort", onAbort);
|
|
245
|
-
resolve();
|
|
246
|
-
}
|
|
247
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
function traceUploadRetryDelay(retryIndex) {
|
|
251
|
-
const exponentialDelay = Math.min(TRACE_UPLOAD_RETRY_MAX_DELAY_MS, TRACE_UPLOAD_RETRY_BASE_DELAY_MS * 2 ** retryIndex);
|
|
252
|
-
const jitterMultiplier = 1 - TRACE_UPLOAD_RETRY_JITTER + Math.random() * TRACE_UPLOAD_RETRY_JITTER * 2;
|
|
253
|
-
return Math.max(0, Math.round(exponentialDelay * jitterMultiplier));
|
|
254
|
-
}
|
|
255
|
-
function retryAfterDelay(response, capMs = TRACE_UPLOAD_RATE_LIMIT_WINDOW_MS) {
|
|
256
|
-
const value = response.headers.get("retry-after")?.trim();
|
|
257
|
-
if (!value) {
|
|
258
|
-
return undefined;
|
|
259
|
-
}
|
|
260
|
-
const seconds = Number(value);
|
|
261
|
-
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
262
|
-
return Math.min(Math.ceil(seconds * 1_000), capMs);
|
|
263
|
-
}
|
|
264
|
-
const retryAt = Date.parse(value);
|
|
265
|
-
return Number.isFinite(retryAt) ? Math.min(Math.max(0, retryAt - Date.now()), capMs) : undefined;
|
|
266
|
-
}
|
|
267
|
-
async function fetchWithRetry(fetchFn, url, init, timeoutMs, signal, beforeRequest) {
|
|
268
|
-
for (let attempt = 0;; attempt += 1) {
|
|
269
|
-
let retryDelayMs;
|
|
270
|
-
try {
|
|
271
|
-
await beforeRequest?.();
|
|
272
|
-
if (signal?.aborted) {
|
|
273
|
-
throw signal.reason ?? new Error("Trace upload cancelled");
|
|
274
|
-
}
|
|
275
|
-
const response = await fetchWithTimeout(fetchFn, url, init, timeoutMs, signal);
|
|
276
|
-
if (attempt >= TRACE_UPLOAD_MAX_RETRIES || !RETRIABLE_HTTP_STATUSES.has(response.status)) {
|
|
277
|
-
return response;
|
|
278
|
-
}
|
|
279
|
-
if (response.status === 503) {
|
|
280
|
-
retryDelayMs = retryAfterDelay(response);
|
|
281
|
-
}
|
|
282
|
-
await response.body?.cancel().catch(() => undefined);
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
if (signal?.aborted) {
|
|
286
|
-
throw signal.reason ?? error;
|
|
287
|
-
}
|
|
288
|
-
if (attempt >= TRACE_UPLOAD_MAX_RETRIES || !isRetriableNetworkError(error)) {
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
await delay(retryDelayMs ?? traceUploadRetryDelay(attempt), signal);
|
|
293
|
-
if (signal?.aborted) {
|
|
294
|
-
throw signal.reason ?? new Error("Trace upload cancelled");
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function traceContentPreview(body, maxChars) {
|
|
299
|
-
if (body.length <= maxChars) {
|
|
300
|
-
return { content: body.trimEnd(), truncated: false };
|
|
301
|
-
}
|
|
302
|
-
const marker = "\n... middle of trace omitted ...\n";
|
|
303
|
-
const available = Math.max(0, maxChars - marker.length);
|
|
304
|
-
const headChars = Math.ceil(available / 2);
|
|
305
|
-
const tailChars = Math.floor(available / 2);
|
|
306
|
-
return {
|
|
307
|
-
content: `${body.slice(0, headChars).trimEnd()}${marker}${body.slice(body.length - tailChars).trimStart()}`,
|
|
308
|
-
truncated: true,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
export async function previewAgentTraceFile(options) {
|
|
312
|
-
if (!options.sessionFile) {
|
|
313
|
-
return { status: "no_session_file" };
|
|
314
|
-
}
|
|
315
|
-
let fileSize;
|
|
316
|
-
try {
|
|
317
|
-
const stats = await stat(options.sessionFile);
|
|
318
|
-
if (!stats.isFile()) {
|
|
319
|
-
return { status: "no_session_file" };
|
|
320
|
-
}
|
|
321
|
-
fileSize = stats.size;
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
324
|
-
return { status: "no_session_file" };
|
|
325
|
-
}
|
|
326
|
-
if (fileSize === 0) {
|
|
327
|
-
return { status: "empty_session" };
|
|
328
|
-
}
|
|
329
|
-
const header = readSessionHeader(options.sessionFile);
|
|
330
|
-
if (!header) {
|
|
331
|
-
return { status: "invalid_session", message: "Session file is missing a valid session header" };
|
|
332
|
-
}
|
|
333
|
-
let body = "";
|
|
334
|
-
if (fileSize <= MAX_TRACE_BYTES) {
|
|
335
|
-
try {
|
|
336
|
-
body = await readSessionTraceBody(options.sessionFile);
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
return { status: "failed", message: describeError(error) };
|
|
340
|
-
}
|
|
341
|
-
if (!body.trim()) {
|
|
342
|
-
return { status: "empty_session" };
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
const traceContext = resolveTraceContext(options.sessionFile, header);
|
|
346
|
-
const baseUrl = resolvePrimeAgentTracesBaseUrl(options.baseUrl);
|
|
347
|
-
const git = body ? activeGitContext(body, header) : header.git;
|
|
348
|
-
const preview = body
|
|
349
|
-
? traceContentPreview(body, Math.max(256, options.maxContentChars ?? TRACE_PREVIEW_MAX_CHARS))
|
|
350
|
-
: { content: "", truncated: true };
|
|
351
|
-
return {
|
|
352
|
-
status: "ready",
|
|
353
|
-
sessionFile: options.sessionFile,
|
|
354
|
-
sessionId: header.id,
|
|
355
|
-
traceId: traceContext.traceId,
|
|
356
|
-
parentSessionId: traceContext.parentSessionId,
|
|
357
|
-
cwd: header.cwd,
|
|
358
|
-
size: fileSize,
|
|
359
|
-
maxBytes: MAX_TRACE_BYTES,
|
|
360
|
-
uploadable: fileSize <= MAX_TRACE_BYTES && baseUrl !== undefined,
|
|
361
|
-
endpoint: baseUrl ? `${baseUrl}/api/v1/agent-traces/sessions/${encodeURIComponent(header.id)}` : undefined,
|
|
362
|
-
gitRepo: git?.repoUrl,
|
|
363
|
-
gitCommit: git?.commit,
|
|
364
|
-
contentPreview: preview.content,
|
|
365
|
-
truncated: preview.truncated,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
async function findSessionFilesUnder(root, files) {
|
|
369
|
-
let entries;
|
|
370
|
-
try {
|
|
371
|
-
entries = await readdir(root, { withFileTypes: true });
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
for (const entry of entries) {
|
|
377
|
-
const entryPath = join(root, entry.name);
|
|
378
|
-
if (entry.isDirectory()) {
|
|
379
|
-
await findSessionFilesUnder(entryPath, files);
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
if (entry.isFile() && entry.name.endsWith(".jsonl") && readSessionHeader(entryPath)) {
|
|
383
|
-
files.add(entryPath);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
export async function findAgentTraceFiles(sessionDir = getSessionsDir()) {
|
|
388
|
-
const files = new Set();
|
|
389
|
-
const roots = new Set([resolve(sessionDir), resolve(getSessionArtifactsRoot(sessionDir))]);
|
|
390
|
-
await Promise.all([...roots].map((root) => findSessionFilesUnder(root, files)));
|
|
391
|
-
return [...files].sort();
|
|
392
|
-
}
|
|
393
|
-
function createTraceUploadAllRequestGate(signal) {
|
|
394
|
-
let nextRequestAt = 0;
|
|
395
|
-
let queue = Promise.resolve();
|
|
396
|
-
return () => {
|
|
397
|
-
const slot = queue.then(async () => {
|
|
398
|
-
const waitMs = Math.max(0, nextRequestAt - Date.now());
|
|
399
|
-
if (waitMs > 0) {
|
|
400
|
-
await delay(waitMs, signal);
|
|
401
|
-
}
|
|
402
|
-
if (!signal?.aborted) {
|
|
403
|
-
nextRequestAt = Date.now() + TRACE_UPLOAD_ALL_MIN_REQUEST_INTERVAL_MS;
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
queue = slot.catch(() => undefined);
|
|
407
|
-
return slot;
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
export async function uploadAllAgentTraces(options) {
|
|
411
|
-
const { sessionDir, concurrency, onProgress, ...uploadOptions } = options;
|
|
412
|
-
const sessionFiles = await findAgentTraceFiles(sessionDir);
|
|
413
|
-
const results = new Array(sessionFiles.length);
|
|
414
|
-
let cursor = 0;
|
|
415
|
-
let completed = 0;
|
|
416
|
-
const beforeRequest = createTraceUploadAllRequestGate(uploadOptions.signal);
|
|
417
|
-
onProgress?.({ completed, total: sessionFiles.length });
|
|
418
|
-
const worker = async () => {
|
|
419
|
-
while (true) {
|
|
420
|
-
if (uploadOptions.signal?.aborted) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
const index = cursor;
|
|
424
|
-
cursor += 1;
|
|
425
|
-
const sessionFile = sessionFiles[index];
|
|
426
|
-
if (!sessionFile) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
const result = await uploadAgentTraceFileWithRequestGate({
|
|
430
|
-
...uploadOptions,
|
|
431
|
-
sessionFile,
|
|
432
|
-
reloadConfig: false,
|
|
433
|
-
}, beforeRequest);
|
|
434
|
-
if (uploadOptions.signal?.aborted && result.status === "failed") {
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
results[index] = { sessionFile, result };
|
|
438
|
-
completed += 1;
|
|
439
|
-
onProgress?.({ completed, total: sessionFiles.length, sessionFile, result });
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
const requestedConcurrency = concurrency ?? TRACE_UPLOAD_ALL_CONCURRENCY;
|
|
443
|
-
const normalizedConcurrency = Number.isFinite(requestedConcurrency) && requestedConcurrency > 0
|
|
444
|
-
? Math.max(1, Math.floor(requestedConcurrency))
|
|
445
|
-
: TRACE_UPLOAD_ALL_CONCURRENCY;
|
|
446
|
-
const workerCount = Math.min(sessionFiles.length, normalizedConcurrency);
|
|
447
|
-
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
448
|
-
const completedResults = results.filter((item) => item !== undefined);
|
|
449
|
-
let uploaded = 0;
|
|
450
|
-
let failed = 0;
|
|
451
|
-
let bytesStored = 0;
|
|
452
|
-
for (const item of completedResults) {
|
|
453
|
-
if (item.result.status === "uploaded") {
|
|
454
|
-
uploaded += 1;
|
|
455
|
-
bytesStored += item.result.bytesStored;
|
|
456
|
-
}
|
|
457
|
-
else if (item.result.status === "failed") {
|
|
458
|
-
failed += 1;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return {
|
|
462
|
-
total: sessionFiles.length,
|
|
463
|
-
uploaded,
|
|
464
|
-
failed,
|
|
465
|
-
skipped: sessionFiles.length - uploaded - failed,
|
|
466
|
-
bytesStored,
|
|
467
|
-
results: completedResults,
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
export const SEMANTIC_EDGES_OUTBOX_KIND = "semantic-edges";
|
|
471
|
-
function getAgentTraceOutboxDir() {
|
|
472
|
-
return assertProductStatePath(join(getAgentDir(), "trace-export-outbox"));
|
|
473
|
-
}
|
|
474
|
-
// One entry file per session file (keyed by path hash): concurrent writers cannot lose each other's cursors, and a bad read costs only its own entry.
|
|
475
|
-
function agentTraceOutboxEntryPath(sessionFile) {
|
|
476
|
-
const key = createHash("sha256").update(sessionFile).digest("hex").slice(0, 32);
|
|
477
|
-
return join(getAgentTraceOutboxDir(), `${key}.json`);
|
|
478
|
-
}
|
|
479
|
-
function parseOutboxEntry(raw) {
|
|
480
|
-
const parsed = parseResponseObject(raw);
|
|
481
|
-
if (!parsed || typeof parsed.sessionFile !== "string") {
|
|
482
|
-
return undefined;
|
|
483
|
-
}
|
|
484
|
-
const uploaded = typeof parsed.size === "number" && typeof parsed.mtimeMs === "number"
|
|
485
|
-
? { size: parsed.size, mtimeMs: parsed.mtimeMs }
|
|
486
|
-
: null;
|
|
487
|
-
return {
|
|
488
|
-
sessionFile: parsed.sessionFile,
|
|
489
|
-
kind: typeof parsed.kind === "string" ? parsed.kind : undefined,
|
|
490
|
-
uploaded,
|
|
491
|
-
uploadedBytes: typeof parsed.uploadedBytes === "number" ? parsed.uploadedBytes : undefined,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
/** `undefined` = no usable cursor; `null` = scheduled but never uploaded. */
|
|
495
|
-
async function readAgentTraceOutboxEntry(sessionFile) {
|
|
496
|
-
let raw;
|
|
497
|
-
try {
|
|
498
|
-
raw = await readFile(agentTraceOutboxEntryPath(sessionFile), "utf8");
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
return undefined;
|
|
502
|
-
}
|
|
503
|
-
const entry = parseOutboxEntry(raw);
|
|
504
|
-
return entry && entry.sessionFile === sessionFile ? entry.uploaded : undefined;
|
|
505
|
-
}
|
|
506
|
-
function signatureEquals(a, b) {
|
|
507
|
-
return a != null && a.size === b.size && a.mtimeMs === b.mtimeMs;
|
|
508
|
-
}
|
|
509
|
-
/** Session files with a live upload controller in this process; catch-up leaves them to their controller. */
|
|
510
|
-
const locallyManagedSessionFiles = new Set();
|
|
511
|
-
/** Best-effort and synchronous: upload intent must be on disk the moment the transcript persist returns. */
|
|
512
|
-
function markAgentTraceOutboxPendingSync(sessionFile, kind) {
|
|
513
|
-
try {
|
|
514
|
-
const entryPath = agentTraceOutboxEntryPath(sessionFile);
|
|
515
|
-
if (existsSync(entryPath)) {
|
|
516
|
-
return true;
|
|
517
|
-
}
|
|
518
|
-
mkdirSync(getAgentTraceOutboxDir(), { recursive: true });
|
|
519
|
-
const tempPath = `${entryPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
520
|
-
writeFileSync(tempPath, `${JSON.stringify(kind === undefined ? { sessionFile } : { sessionFile, kind })}\n`, "utf8");
|
|
521
|
-
renameSync(tempPath, entryPath);
|
|
522
|
-
return true;
|
|
523
|
-
}
|
|
524
|
-
catch {
|
|
525
|
-
// A broken agent dir must not break session persists.
|
|
526
|
-
return false;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
async function recordAgentTraceOutboxUpload(sessionFile, signature) {
|
|
530
|
-
const entryPath = agentTraceOutboxEntryPath(sessionFile);
|
|
531
|
-
await mkdir(getAgentTraceOutboxDir(), { recursive: true });
|
|
532
|
-
const tempPath = `${entryPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
533
|
-
await writeFile(tempPath, `${JSON.stringify({ sessionFile, ...signature })}\n`, "utf8");
|
|
534
|
-
await rename(tempPath, entryPath);
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Explicit catch-up for the Base Context export outbox. This is never run at
|
|
538
|
-
* startup and does not read inherited agent-traces-outbox entries.
|
|
539
|
-
*/
|
|
540
|
-
export async function catchUpAgentTraceUploads(options) {
|
|
541
|
-
const catchUp = { pruned: 0, semanticEdgeLedgersPending: 0, results: [] };
|
|
542
|
-
if (options.requireEnabled !== false && !(await getAgentTracesEnabled(options))) {
|
|
543
|
-
return catchUp;
|
|
544
|
-
}
|
|
545
|
-
let entryNames;
|
|
546
|
-
try {
|
|
547
|
-
entryNames = await readdir(getAgentTraceOutboxDir());
|
|
548
|
-
}
|
|
549
|
-
catch {
|
|
550
|
-
return catchUp;
|
|
551
|
-
}
|
|
552
|
-
const beforeRequest = createTraceUploadAllRequestGate(options.signal);
|
|
553
|
-
for (const entryName of entryNames) {
|
|
554
|
-
if (options.signal?.aborted) {
|
|
555
|
-
break;
|
|
556
|
-
}
|
|
557
|
-
if (!entryName.endsWith(".json")) {
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
const entryPath = join(getAgentTraceOutboxDir(), entryName);
|
|
561
|
-
let raw;
|
|
562
|
-
try {
|
|
563
|
-
raw = await readFile(entryPath, "utf8");
|
|
564
|
-
}
|
|
565
|
-
catch {
|
|
566
|
-
// Transient read error: keep the entry and retry at the next startup.
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
const entry = parseOutboxEntry(raw);
|
|
570
|
-
if (!entry) {
|
|
571
|
-
await unlink(entryPath).catch(() => undefined);
|
|
572
|
-
catchUp.pruned += 1;
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
if (entry.kind === SEMANTIC_EDGES_OUTBOX_KIND) {
|
|
576
|
-
let ledgerStats;
|
|
577
|
-
try {
|
|
578
|
-
ledgerStats = await stat(entry.sessionFile);
|
|
579
|
-
}
|
|
580
|
-
catch (error) {
|
|
581
|
-
if (error.code === "ENOENT") {
|
|
582
|
-
await unlink(entryPath).catch(() => undefined);
|
|
583
|
-
catchUp.pruned += 1;
|
|
584
|
-
}
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
if (!ledgerStats.isFile()) {
|
|
588
|
-
await unlink(entryPath).catch(() => undefined);
|
|
589
|
-
catchUp.pruned += 1;
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
|
-
// Append-only byte cursor: a ledger whose size equals its delivered offset has nothing new.
|
|
593
|
-
if (ledgerStats.size === entry.uploadedBytes) {
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
// No delivery endpoint exists yet (verifiers#2449 consumes edges in-band over ACP
|
|
597
|
-
// metadata; the trace server has no semantic-edges route). The delta and cursor stay
|
|
598
|
-
// untouched so the first real sender delivers the whole backlog.
|
|
599
|
-
catchUp.semanticEdgeLedgersPending += 1;
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
if (entry.kind !== undefined) {
|
|
603
|
-
// A newer build may register kinds this one cannot deliver; leave their cursors alone.
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
if (locallyManagedSessionFiles.has(entry.sessionFile)) {
|
|
607
|
-
continue;
|
|
608
|
-
}
|
|
609
|
-
let stats;
|
|
610
|
-
try {
|
|
611
|
-
stats = await stat(entry.sessionFile);
|
|
612
|
-
}
|
|
613
|
-
catch (error) {
|
|
614
|
-
if (error.code === "ENOENT") {
|
|
615
|
-
await unlink(entryPath).catch(() => undefined);
|
|
616
|
-
catchUp.pruned += 1;
|
|
617
|
-
}
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
if (!stats.isFile()) {
|
|
621
|
-
await unlink(entryPath).catch(() => undefined);
|
|
622
|
-
catchUp.pruned += 1;
|
|
623
|
-
continue;
|
|
624
|
-
}
|
|
625
|
-
if (signatureEquals(entry.uploaded, { size: stats.size, mtimeMs: stats.mtimeMs })) {
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
const result = await uploadAgentTraceFileWithRequestGate({ ...options, sessionFile: entry.sessionFile, reloadConfig: false }, beforeRequest);
|
|
629
|
-
catchUp.results.push({ sessionFile: entry.sessionFile, result });
|
|
630
|
-
}
|
|
631
|
-
return catchUp;
|
|
632
|
-
}
|
|
633
|
-
export async function getPrimeAgentTraceCredential(authStorage, options = {}) {
|
|
634
|
-
const traceEnvKey = stringEnv("BASE_CONTEXT_TRACES_API_KEY");
|
|
635
|
-
if (traceEnvKey) {
|
|
636
|
-
return { apiKey: traceEnvKey, source: "environment", label: "BASE_CONTEXT_TRACES_API_KEY" };
|
|
637
|
-
}
|
|
638
|
-
if (options.reloadAuth !== false) {
|
|
639
|
-
authStorage.reload();
|
|
640
|
-
}
|
|
641
|
-
const traceKey = await authStorage.getApiKey(BASE_CONTEXT_TRACES_PROVIDER_ID, { includeFallback: false });
|
|
642
|
-
if (traceKey) {
|
|
643
|
-
return { apiKey: traceKey, source: "stored", label: "Base Context Traces credential" };
|
|
644
|
-
}
|
|
645
|
-
return undefined;
|
|
646
|
-
}
|
|
647
|
-
async function getAgentTracesEnabled(options) {
|
|
648
|
-
if (options.reloadConfig !== false) {
|
|
649
|
-
await options.settingsManager.reload().catch(() => undefined);
|
|
650
|
-
}
|
|
651
|
-
return options.settingsManager.getAgentTracesEnabled();
|
|
652
|
-
}
|
|
653
|
-
async function uploadAgentTraceFileWithRequestGate(options, beforeRequest) {
|
|
654
|
-
const result = await performAgentTraceUpload(options, beforeRequest);
|
|
655
|
-
logAgentTraceOutcome(options.sessionFile, result);
|
|
656
|
-
return result;
|
|
657
|
-
}
|
|
658
|
-
export function uploadAgentTraceFile(options) {
|
|
659
|
-
return uploadAgentTraceFileWithRequestGate(options);
|
|
660
|
-
}
|
|
661
|
-
function logAgentTraceOutcome(sessionFile, result) {
|
|
662
|
-
let line;
|
|
663
|
-
switch (result.status) {
|
|
664
|
-
case "uploaded":
|
|
665
|
-
line = `uploaded session ${result.sessionId} (${result.bytesStored} bytes)`;
|
|
666
|
-
break;
|
|
667
|
-
case "failed":
|
|
668
|
-
line = `upload failed${result.statusCode ? ` (HTTP ${result.statusCode})` : ""}: ${result.message}`;
|
|
669
|
-
break;
|
|
670
|
-
case "too_large":
|
|
671
|
-
line = `upload skipped: session is ${result.size} bytes (limit ${result.maxBytes})`;
|
|
672
|
-
break;
|
|
673
|
-
case "invalid_session":
|
|
674
|
-
line = `upload skipped: ${result.message}`;
|
|
675
|
-
break;
|
|
676
|
-
case "missing_credentials":
|
|
677
|
-
line = "upload skipped: configure a dedicated BASE_CONTEXT_TRACES_API_KEY or Base Context Traces credential";
|
|
678
|
-
break;
|
|
679
|
-
default:
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const suffix = sessionFile ? ` [${sessionFile}]` : "";
|
|
683
|
-
appendRotatingLog(getAgentTracesLogPath(), `[${new Date().toISOString()}] ${line}${suffix}`);
|
|
684
|
-
}
|
|
685
|
-
async function performAgentTraceUpload(options, beforeRequest) {
|
|
686
|
-
const requireEnabled = options.requireEnabled !== false;
|
|
687
|
-
if (requireEnabled && !(await getAgentTracesEnabled(options))) {
|
|
688
|
-
return { status: "disabled" };
|
|
689
|
-
}
|
|
690
|
-
const baseUrl = resolvePrimeAgentTracesBaseUrl(options.baseUrl);
|
|
691
|
-
if (!baseUrl) {
|
|
692
|
-
return {
|
|
693
|
-
status: "failed",
|
|
694
|
-
message: "Trace export requires an explicit BASE_CONTEXT_TRACES_BASE_URL and dedicated BASE_CONTEXT_TRACES_API_KEY.",
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
if (!options.sessionFile) {
|
|
698
|
-
return { status: "no_session_file" };
|
|
699
|
-
}
|
|
700
|
-
let signature;
|
|
701
|
-
try {
|
|
702
|
-
const stats = await stat(options.sessionFile);
|
|
703
|
-
if (!stats.isFile()) {
|
|
704
|
-
return { status: "no_session_file" };
|
|
705
|
-
}
|
|
706
|
-
signature = { size: stats.size, mtimeMs: stats.mtimeMs };
|
|
707
|
-
}
|
|
708
|
-
catch {
|
|
709
|
-
return { status: "no_session_file" };
|
|
710
|
-
}
|
|
711
|
-
const fileSize = signature.size;
|
|
712
|
-
if (fileSize === 0) {
|
|
713
|
-
return { status: "empty_session" };
|
|
714
|
-
}
|
|
715
|
-
if (fileSize > MAX_TRACE_BYTES) {
|
|
716
|
-
return { status: "too_large", size: fileSize, maxBytes: MAX_TRACE_BYTES };
|
|
717
|
-
}
|
|
718
|
-
// Cursor invariant: an automatic upload never re-sends a file whose content already matches its uploaded cursor.
|
|
719
|
-
if (requireEnabled && signatureEquals(await readAgentTraceOutboxEntry(options.sessionFile), signature)) {
|
|
720
|
-
return { status: "unchanged" };
|
|
721
|
-
}
|
|
722
|
-
const header = readSessionHeader(options.sessionFile);
|
|
723
|
-
if (!header) {
|
|
724
|
-
return { status: "invalid_session", message: "Session file is missing a valid session header" };
|
|
725
|
-
}
|
|
726
|
-
const credential = await getPrimeAgentTraceCredential(options.authStorage, {
|
|
727
|
-
configPath: options.configPath,
|
|
728
|
-
reloadAuth: options.reloadConfig !== false,
|
|
729
|
-
});
|
|
730
|
-
if (!credential) {
|
|
731
|
-
return { status: "missing_credentials" };
|
|
732
|
-
}
|
|
733
|
-
if (requireEnabled && !(await getAgentTracesEnabled(options))) {
|
|
734
|
-
return { status: "disabled" };
|
|
735
|
-
}
|
|
736
|
-
let body;
|
|
737
|
-
try {
|
|
738
|
-
body = await readSessionTraceBody(options.sessionFile);
|
|
739
|
-
}
|
|
740
|
-
catch (error) {
|
|
741
|
-
return { status: "failed", message: describeError(error) };
|
|
742
|
-
}
|
|
743
|
-
if (!body.trim()) {
|
|
744
|
-
return { status: "empty_session" };
|
|
745
|
-
}
|
|
746
|
-
const traceContext = resolveTraceContext(options.sessionFile, header);
|
|
747
|
-
const bodyBytes = Buffer.byteLength(body, "utf8");
|
|
748
|
-
const headers = {
|
|
749
|
-
Authorization: `Bearer ${credential.apiKey}`,
|
|
750
|
-
"Content-Type": "application/x-ndjson",
|
|
751
|
-
Accept: "application/json",
|
|
752
|
-
"X-Trace-Id": traceContext.traceId,
|
|
753
|
-
"X-Cwd": header.cwd,
|
|
754
|
-
"X-Agent-Version": VERSION,
|
|
755
|
-
};
|
|
756
|
-
if (traceContext.parentSessionId) {
|
|
757
|
-
headers["X-Parent-Session"] = traceContext.parentSessionId;
|
|
758
|
-
}
|
|
759
|
-
const git = activeGitContext(body, header);
|
|
760
|
-
if (git?.repoUrl) {
|
|
761
|
-
headers["X-Git-Repo"] = git.repoUrl;
|
|
762
|
-
}
|
|
763
|
-
if (git?.commit) {
|
|
764
|
-
headers["X-Git-Commit"] = git.commit;
|
|
765
|
-
}
|
|
766
|
-
if (requireEnabled && !(await getAgentTracesEnabled(options))) {
|
|
767
|
-
return { status: "disabled" };
|
|
768
|
-
}
|
|
769
|
-
const url = `${baseUrl}/api/v1/agent-traces/sessions/${encodeURIComponent(header.id)}`;
|
|
770
|
-
const fetchFn = options.fetchFn ?? fetch;
|
|
771
|
-
let response;
|
|
772
|
-
try {
|
|
773
|
-
response = await fetchWithRetry(fetchFn, url, {
|
|
774
|
-
method: "PUT",
|
|
775
|
-
redirect: "error",
|
|
776
|
-
headers,
|
|
777
|
-
body,
|
|
778
|
-
}, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, options.signal, beforeRequest);
|
|
779
|
-
}
|
|
780
|
-
catch (error) {
|
|
781
|
-
return { status: "failed", message: describeError(error) };
|
|
782
|
-
}
|
|
783
|
-
if (!response.ok) {
|
|
784
|
-
return {
|
|
785
|
-
status: "failed",
|
|
786
|
-
statusCode: response.status,
|
|
787
|
-
message: await readResponseMessage(response),
|
|
788
|
-
retryAfterMs: retryAfterDelay(response, MAX_TIMER_DELAY_MS),
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
const responseText = await response.text().catch(() => "");
|
|
792
|
-
const responseData = parseResponseObject(responseText);
|
|
793
|
-
try {
|
|
794
|
-
await recordAgentTraceOutboxUpload(options.sessionFile, signature);
|
|
795
|
-
}
|
|
796
|
-
catch (error) {
|
|
797
|
-
return { status: "failed", message: `stored, but recording the upload cursor failed: ${describeError(error)}` };
|
|
798
|
-
}
|
|
799
|
-
return {
|
|
800
|
-
status: "uploaded",
|
|
801
|
-
sessionId: responseData ? (stringField(responseData, "session_id") ?? header.id) : header.id,
|
|
802
|
-
traceId: responseData ? (stringField(responseData, "trace_id") ?? traceContext.traceId) : traceContext.traceId,
|
|
803
|
-
bytesStored: responseData ? (numberField(responseData, "bytes_stored") ?? bodyBytes) : bodyBytes,
|
|
804
|
-
key: responseData ? stringField(responseData, "key") : undefined,
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
export function uploadAgentTraceSession(options) {
|
|
808
|
-
return uploadAgentTraceFile({
|
|
809
|
-
...options,
|
|
810
|
-
sessionFile: options.sessionManager.getSessionFile(),
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
class AgentTraceUploadController {
|
|
814
|
-
sessionManager;
|
|
815
|
-
options;
|
|
816
|
-
timeout;
|
|
817
|
-
pending = false;
|
|
818
|
-
inFlight;
|
|
819
|
-
lastUploadStartedAt;
|
|
820
|
-
notBeforeAt = 0;
|
|
821
|
-
constructor(sessionManager, options) {
|
|
822
|
-
this.sessionManager = sessionManager;
|
|
823
|
-
this.options = options;
|
|
824
|
-
}
|
|
825
|
-
update(options) {
|
|
826
|
-
this.options = options;
|
|
827
|
-
}
|
|
828
|
-
schedule = () => {
|
|
829
|
-
if (!this.options.settingsManager.getAgentTracesEnabled() ||
|
|
830
|
-
!resolvePrimeAgentTracesBaseUrl(this.options.baseUrl)) {
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
this.pending = true;
|
|
834
|
-
// Intent is consent-gated at persist time: an entry created while sharing
|
|
835
|
-
// is off would turn a later enable into retroactive collection of
|
|
836
|
-
// opted-out sessions. Marking re-runs every persist (existsSync-cheap),
|
|
837
|
-
// so an entry pruned by a racing catch-up is re-registered.
|
|
838
|
-
if (this.options.settingsManager.getAgentTracesEnabled()) {
|
|
839
|
-
const sessionFile = this.sessionManager.getSessionFile();
|
|
840
|
-
if (sessionFile &&
|
|
841
|
-
!locallyManagedSessionFiles.has(sessionFile) &&
|
|
842
|
-
markAgentTraceOutboxPendingSync(sessionFile)) {
|
|
843
|
-
locallyManagedSessionFiles.add(sessionFile);
|
|
844
|
-
}
|
|
845
|
-
const ledgerPath = this.options.semanticEdgesLedgerPath;
|
|
846
|
-
if (ledgerPath) {
|
|
847
|
-
markAgentTraceOutboxPendingSync(ledgerPath, SEMANTIC_EDGES_OUTBOX_KIND);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
this.arm();
|
|
851
|
-
};
|
|
852
|
-
arm() {
|
|
853
|
-
if (this.timeout) {
|
|
854
|
-
clearTimeout(this.timeout);
|
|
855
|
-
}
|
|
856
|
-
const elapsed = this.lastUploadStartedAt === undefined ? undefined : Date.now() - this.lastUploadStartedAt;
|
|
857
|
-
const throttleDelay = elapsed === undefined ? 0 : Math.max(0, TRACE_UPLOAD_MIN_INTERVAL_MS - elapsed);
|
|
858
|
-
const notBeforeDelay = Math.max(0, this.notBeforeAt - Date.now());
|
|
859
|
-
this.timeout = setTimeout(() => {
|
|
860
|
-
this.timeout = undefined;
|
|
861
|
-
void this.runScheduledUpload();
|
|
862
|
-
}, Math.max(TRACE_UPLOAD_DEBOUNCE_MS, throttleDelay, notBeforeDelay));
|
|
863
|
-
this.timeout.unref();
|
|
864
|
-
}
|
|
865
|
-
async runScheduledUpload() {
|
|
866
|
-
if (this.inFlight) {
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
this.pending = false;
|
|
870
|
-
this.lastUploadStartedAt = Date.now();
|
|
871
|
-
this.inFlight = uploadAgentTraceSession({
|
|
872
|
-
...this.options,
|
|
873
|
-
sessionManager: this.sessionManager,
|
|
874
|
-
}).then((result) => {
|
|
875
|
-
if (result.status === "failed" && isRescheduledUploadFailure(result.statusCode)) {
|
|
876
|
-
this.pending = true;
|
|
877
|
-
if (result.retryAfterMs !== undefined) {
|
|
878
|
-
this.notBeforeAt = Date.now() + result.retryAfterMs;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}, () => undefined);
|
|
882
|
-
await this.inFlight;
|
|
883
|
-
this.inFlight = undefined;
|
|
884
|
-
if (this.pending) {
|
|
885
|
-
this.arm();
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
function isRescheduledUploadFailure(statusCode) {
|
|
890
|
-
return statusCode === undefined || statusCode === 429 || RETRIABLE_HTTP_STATUSES.has(statusCode);
|
|
891
|
-
}
|
|
892
|
-
const traceUploadControllers = new WeakMap();
|
|
893
|
-
export function installAgentTraceUpload(sessionManager, options) {
|
|
894
|
-
// Enabling export applies to live sessions, not an inherited outbox backlog.
|
|
895
|
-
let controller = traceUploadControllers.get(sessionManager);
|
|
896
|
-
if (controller) {
|
|
897
|
-
controller.update(options);
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
controller = new AgentTraceUploadController(sessionManager, options);
|
|
901
|
-
traceUploadControllers.set(sessionManager, controller);
|
|
902
|
-
sessionManager.onPersist(controller.schedule);
|
|
903
|
-
}
|
|
904
|
-
//# sourceMappingURL=agent-traces.js.map
|