@yagni-app/code 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -27
- package/dist/claudeCompat.d.ts +59 -0
- package/dist/claudeCompat.js +109 -2
- package/dist/claudePlugins.d.ts +45 -3
- package/dist/claudePlugins.js +129 -19
- package/dist/cli.js +28 -4
- package/dist/doctor.d.ts +42 -4
- package/dist/doctor.js +150 -15
- package/dist/extension/condensedTools.d.ts +12 -1
- package/dist/extension/condensedTools.js +17 -9
- package/dist/extension/index.d.ts +18 -5
- package/dist/extension/index.js +319 -72
- package/dist/extension/mcp/approval.d.ts +45 -0
- package/dist/extension/mcp/approval.js +164 -0
- package/dist/extension/mcp/auth.d.ts +124 -0
- package/dist/extension/mcp/auth.js +560 -0
- package/dist/extension/mcp/authStore.d.ts +61 -0
- package/dist/extension/mcp/authStore.js +105 -0
- package/dist/extension/mcp/callbackPage.d.ts +31 -0
- package/dist/extension/mcp/callbackPage.js +222 -0
- package/dist/extension/mcp/cliConfig.d.ts +12 -0
- package/dist/extension/mcp/cliConfig.js +12 -0
- package/dist/extension/mcp/config.d.ts +153 -0
- package/dist/extension/mcp/config.js +381 -0
- package/dist/extension/mcp/log.d.ts +28 -0
- package/dist/extension/mcp/log.js +82 -0
- package/dist/extension/mcp/manager.d.ts +100 -0
- package/dist/extension/mcp/manager.js +273 -0
- package/dist/extension/mcp/names.d.ts +25 -0
- package/dist/extension/mcp/names.js +40 -0
- package/dist/extension/mcp/panel.d.ts +33 -0
- package/dist/extension/mcp/panel.js +268 -0
- package/dist/extension/mcp/prompts.d.ts +23 -0
- package/dist/extension/mcp/prompts.js +93 -0
- package/dist/extension/mcp/startup.d.ts +55 -0
- package/dist/extension/mcp/startup.js +152 -0
- package/dist/extension/mcp/tools.d.ts +31 -0
- package/dist/extension/mcp/tools.js +117 -0
- package/dist/extension/mcp/transports.d.ts +17 -0
- package/dist/extension/mcp/transports.js +44 -0
- package/dist/extension/permission/gate.d.ts +104 -2
- package/dist/extension/permission/gate.js +387 -31
- package/dist/extension/permission/guardian.d.ts +24 -5
- package/dist/extension/permission/guardian.js +162 -24
- package/dist/extension/permissionRules/bashFileArgs.d.ts +39 -0
- package/dist/extension/permissionRules/bashFileArgs.js +236 -0
- package/dist/extension/permissionRules/engine.d.ts +50 -0
- package/dist/extension/permissionRules/engine.js +238 -0
- package/dist/extension/permissionRules/loadConfig.d.ts +53 -0
- package/dist/extension/permissionRules/loadConfig.js +90 -0
- package/dist/extension/permissionRules/parser.d.ts +38 -0
- package/dist/extension/permissionRules/parser.js +136 -0
- package/dist/extension/permissionRules/pathRules.d.ts +58 -0
- package/dist/extension/permissionRules/pathRules.js +120 -0
- package/dist/extension/permissionRules/shellRules.d.ts +52 -0
- package/dist/extension/permissionRules/shellRules.js +221 -0
- package/dist/extension/pipeline/invocation.d.ts +3 -6
- package/dist/extension/pipeline/invocation.js +3 -6
- package/dist/extension/pipeline/personas.js +5 -0
- package/dist/extension/pipeline/runner.d.ts +0 -1
- package/dist/extension/pipeline/runner.js +6 -14
- package/dist/extension/plugins/inventory.d.ts +88 -0
- package/dist/extension/plugins/inventory.js +144 -0
- package/dist/extension/plugins/panel.d.ts +45 -0
- package/dist/extension/plugins/panel.js +293 -0
- package/dist/extension/sandbox/bash.d.ts +99 -0
- package/dist/extension/sandbox/bash.js +190 -0
- package/dist/extension/sandbox/config.d.ts +114 -0
- package/dist/extension/sandbox/config.js +366 -0
- package/dist/extension/sandbox/manager.d.ts +98 -0
- package/dist/extension/sandbox/manager.js +216 -0
- package/dist/extension/sandbox/panel.d.ts +111 -0
- package/dist/extension/sandbox/panel.js +342 -0
- package/dist/extension/sandbox/session.d.ts +85 -0
- package/dist/extension/sandbox/session.js +775 -0
- package/dist/extension/telemetry/attrs.d.ts +96 -0
- package/dist/extension/telemetry/attrs.js +149 -0
- package/dist/extension/telemetry/config.d.ts +99 -0
- package/dist/extension/telemetry/config.js +193 -0
- package/dist/extension/telemetry/index.d.ts +7 -0
- package/dist/extension/telemetry/index.js +7 -0
- package/dist/extension/telemetry/probe.d.ts +29 -0
- package/dist/extension/telemetry/probe.js +122 -0
- package/dist/extension/telemetry/register.d.ts +40 -0
- package/dist/extension/telemetry/register.js +192 -0
- package/dist/extension/telemetry/sdk.d.ts +63 -0
- package/dist/extension/telemetry/sdk.js +207 -0
- package/dist/extension/telemetry/tracker.d.ts +131 -0
- package/dist/extension/telemetry/tracker.js +551 -0
- package/dist/extension/vendor/IGNORE-LICENSE-MIT +21 -0
- package/dist/extension/vendor/ignore.d.ts +86 -0
- package/dist/extension/vendor/ignore.js +788 -0
- package/dist/goHeadless.d.ts +1 -1
- package/dist/goHeadless.js +2 -2
- package/dist/launch.d.ts +4 -3
- package/dist/launch.js +7 -4
- package/dist/mcpCommand.d.ts +122 -0
- package/dist/mcpCommand.js +787 -0
- package/dist/otel.d.ts +69 -63
- package/dist/otel.js +154 -119
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +18 -0
- package/dist/pluginCommand.d.ts +43 -0
- package/dist/pluginCommand.js +499 -0
- package/dist/pluginStore.d.ts +170 -0
- package/dist/pluginStore.js +554 -0
- package/dist/upgrade.d.ts +11 -2
- package/dist/upgrade.js +48 -8
- package/package.json +20 -3
- package/dist/extension/mcpTools.d.ts +0 -57
- package/dist/extension/mcpTools.js +0 -132
package/dist/otel.d.ts
CHANGED
|
@@ -1,59 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OTel export
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* OTel export gate (Updater pilot ask, now Claude Code parity): decide
|
|
3
|
+
* whether a launch streams session telemetry — traces, metrics, and log
|
|
4
|
+
* events — to the workspace's own OTLP collector (Datadog, Grafana, an
|
|
5
|
+
* in-house collector), and hand the session the standard OpenTelemetry env
|
|
6
|
+
* it needs. The exporter itself lives in the bundled extension
|
|
7
|
+
* (`pi-extension-yagni/src/telemetry`), so there is nothing extra to load and
|
|
8
|
+
* nothing that can go missing on a customer's `npm install`.
|
|
6
9
|
*
|
|
7
|
-
* Three deliberate policies,
|
|
10
|
+
* Three deliberate policies, enforced here rather than left to defaults:
|
|
8
11
|
*
|
|
9
|
-
* 1. GATED, not always-on.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 1. GATED, not always-on. Nothing exports unless an OTLP destination is
|
|
13
|
+
* configured: the user's own OTEL_* env, the workspace's admin-set config
|
|
14
|
+
* (fetched fail-soft and cached), or the repo's committed
|
|
15
|
+
* `.pi/settings.json`. The gate result travels to the session as
|
|
16
|
+
* `YAGNI_OTEL_EXPORT=1` plus the OTLP variables; children (/go stages,
|
|
17
|
+
* subagents) inherit the env and export too.
|
|
15
18
|
*
|
|
16
|
-
* 2. METADATA-ONLY,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* precedence — a repo settings file asking for "full" is overridden, not
|
|
20
|
-
* honored. Session content leaving the machine is a contract change, not a
|
|
21
|
-
* config knob.
|
|
19
|
+
* 2. METADATA-ONLY, in code. Token counts, cost, tier, durations, tool names,
|
|
20
|
+
* and sizes export; prompt, response, and tool content never do. The
|
|
21
|
+
* extension enforces this without a knob — no env var re-enables content.
|
|
22
22
|
*
|
|
23
|
-
* 3. Cost is the customer's SELL rate.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* exported model name is the opaque tier id, never the backing model.
|
|
23
|
+
* 3. Cost is the customer's SELL rate. Usage cost is pi's `usage.cost.total`,
|
|
24
|
+
* computed from the catalog's tier rates (YAG-381), and the exported model
|
|
25
|
+
* name is the opaque tier id, never the backing model.
|
|
27
26
|
*
|
|
28
|
-
* Everything here is fail-soft: telemetry must never block a launch, so
|
|
29
|
-
*
|
|
27
|
+
* Everything here is fail-soft: telemetry must never block a launch, so an
|
|
28
|
+
* unreadable settings file or an offline backend resolves to "disabled" or
|
|
29
|
+
* to last-known config.
|
|
30
30
|
*/
|
|
31
31
|
import { readFileSync } from "node:fs";
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
* subagents: they build their own pi argv inside pi-extension-yagni (which
|
|
35
|
-
* cannot import this package), so the path crosses over env. The extension
|
|
36
|
-
* reads the same literal in `pipeline/runner.ts`.
|
|
37
|
-
*/
|
|
38
|
-
export declare const OTEL_EXTENSION_PATH_ENV = "YAGNI_OTEL_EXTENSION_PATH";
|
|
32
|
+
/** The launcher's "this session exports" gate, read by the bundled extension. */
|
|
33
|
+
export declare const OTEL_EXPORT_ENV = "YAGNI_OTEL_EXPORT";
|
|
39
34
|
/** The service name a collector sees unless the user set their own. */
|
|
40
35
|
export declare const DEFAULT_OTEL_SERVICE_NAME = "yagni-code";
|
|
36
|
+
/** The standard per-signal endpoint variables, generic first (its precedence). */
|
|
37
|
+
declare const ENDPOINT_ENV_VARS: readonly ["OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"];
|
|
38
|
+
export type EndpointEnvVar = (typeof ENDPOINT_ENV_VARS)[number];
|
|
41
39
|
export interface OtelLaunchConfig {
|
|
42
|
-
/** Absolute path to pi-otel's extension entry. */
|
|
43
|
-
extensionPath: string;
|
|
44
40
|
/**
|
|
45
|
-
* Where the enablement signal came from. "workspace"
|
|
46
|
-
* protocol/headers/serviceName
|
|
47
|
-
*
|
|
41
|
+
* Where the enablement signal came from. "workspace" and "project-settings"
|
|
42
|
+
* carry endpoint/protocol/headers/serviceName that the launcher delivers as
|
|
43
|
+
* env; "env" is the user's own setup, passed through untouched.
|
|
48
44
|
*/
|
|
49
45
|
source: "env" | "workspace" | "project-settings";
|
|
50
|
-
/** The
|
|
46
|
+
/** The env var the endpoint came from (env source only; doctor names it). */
|
|
47
|
+
envVar?: EndpointEnvVar;
|
|
48
|
+
/** The configured endpoint: a BASE URL for workspace/project sources. */
|
|
51
49
|
endpoint: string;
|
|
52
|
-
/** Workspace-configured OTLP protocol (workspace source only). */
|
|
53
50
|
protocol?: string;
|
|
54
|
-
/**
|
|
51
|
+
/** Collector headers (workspace / project sources; carries secrets). */
|
|
55
52
|
headers?: Record<string, string>;
|
|
56
|
-
/** Workspace-configured service name (workspace source only). */
|
|
57
53
|
serviceName?: string;
|
|
58
54
|
}
|
|
59
55
|
/**
|
|
@@ -69,34 +65,44 @@ export interface WorkspaceOtelConfig {
|
|
|
69
65
|
headers?: Record<string, string>;
|
|
70
66
|
}
|
|
71
67
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
68
|
+
* Reduce an endpoint that carries a signal path to its base
|
|
69
|
+
* (`https://otlp.datadoghq.com/v1/traces` → `https://otlp.datadoghq.com`).
|
|
70
|
+
* The extension appends `/v1/<signal>` per signal for http transports, so a
|
|
71
|
+
* workspace config saved before logs and metrics existed keeps exporting all
|
|
72
|
+
* three without an admin re-save. Mirrors the extension's own helper.
|
|
73
|
+
*/
|
|
74
|
+
export declare function stripSignalPath(endpoint: string): string;
|
|
75
|
+
/** Personal kill switch: beats every source. Both spellings accepted. */
|
|
76
|
+
export declare function envDisabled(env: NodeJS.ProcessEnv): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Decide whether this launch exports, from the user's env and the repo's
|
|
79
|
+
* settings. Returns undefined when export stays off: no endpoint configured,
|
|
80
|
+
* or a kill switch set.
|
|
81
|
+
*
|
|
82
|
+
* Any of the standard endpoint variables enables export — the generic
|
|
83
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` or a per-signal one — so a machine that
|
|
84
|
+
* carries a Claude Code telemetry env (typically all three per-signal
|
|
85
|
+
* endpoints) lights up with no translation. The extension reads the same
|
|
86
|
+
* variables, per signal, following the OTLP exporter spec.
|
|
75
87
|
*/
|
|
76
88
|
export declare function resolveOtelLaunch(opts: {
|
|
77
89
|
env: NodeJS.ProcessEnv;
|
|
78
90
|
cwd: string;
|
|
79
|
-
/** Injectable
|
|
80
|
-
resolveExtension?: () => string;
|
|
91
|
+
/** Injectable seam for tests. */
|
|
81
92
|
readFile?: typeof readFileSync;
|
|
82
93
|
}): OtelLaunchConfig | undefined;
|
|
83
94
|
/**
|
|
84
|
-
*
|
|
85
|
-
* extension
|
|
86
|
-
*
|
|
87
|
-
* "export off", not an error.
|
|
88
|
-
*/
|
|
89
|
-
export declare function resolveOtelExtensionPath(): string;
|
|
90
|
-
/**
|
|
91
|
-
* The env keys an OTel-exporting child must carry. `PI_OTEL_CAPTURE_CONTENT`
|
|
92
|
-
* is pinned unconditionally (policy 2 above); the service name only fills in
|
|
93
|
-
* when the user has not chosen their own.
|
|
95
|
+
* The env keys an exporting child must carry. `YAGNI_OTEL_EXPORT=1` is the
|
|
96
|
+
* gate the extension reads; the service name only fills in when the user has
|
|
97
|
+
* not chosen their own.
|
|
94
98
|
*
|
|
95
|
-
* A "workspace"-
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
99
|
+
* A "workspace" or "project-settings" config delivers its endpoint (as the
|
|
100
|
+
* generic BASE endpoint — the extension derives the per-signal URLs),
|
|
101
|
+
* protocol, headers, and service name over the standard OTel env vars — but
|
|
102
|
+
* any of those the USER already set in their own environment wins (local
|
|
103
|
+
* env > workspace config), so an engineer can point one session at a
|
|
104
|
+
* scratch collector without an admin change. An "env" config is the user's
|
|
105
|
+
* own setup and passes through untouched.
|
|
100
106
|
*/
|
|
101
107
|
export declare function otelChildEnv(config: OtelLaunchConfig, baseEnv: NodeJS.ProcessEnv): Record<string, string>;
|
|
102
108
|
export interface WorkspaceOtelFetchDeps {
|
|
@@ -125,10 +131,10 @@ export declare function fetchWorkspaceOtel(creds: {
|
|
|
125
131
|
token: string;
|
|
126
132
|
}, profileName: string, deps?: WorkspaceOtelFetchDeps): Promise<WorkspaceOtelConfig | null>;
|
|
127
133
|
/**
|
|
128
|
-
* Full launch-time resolution, all
|
|
134
|
+
* Full launch-time resolution, all four sources in precedence order:
|
|
129
135
|
*
|
|
130
|
-
* 1. `PI_OTEL_DISABLED`
|
|
131
|
-
* 2. env `
|
|
136
|
+
* 1. `PI_OTEL_DISABLED` / `YAGNI_OTEL_DISABLED` — personal kill switch.
|
|
137
|
+
* 2. env `OTEL_EXPORTER_OTLP_*_ENDPOINT` — the user's own setup, untouched.
|
|
132
138
|
* 3. workspace config — admin-set in the web app, fetched/cached.
|
|
133
139
|
* 4. repo `.pi/settings.json` — committed per-repo config.
|
|
134
140
|
*
|
|
@@ -142,9 +148,9 @@ export declare function resolveOtelLaunchWithWorkspace(opts: {
|
|
|
142
148
|
token: string;
|
|
143
149
|
} | null;
|
|
144
150
|
profileName?: string;
|
|
145
|
-
resolveExtension?: () => string;
|
|
146
151
|
readFile?: typeof readFileSync;
|
|
147
152
|
fetchDeps?: WorkspaceOtelFetchDeps;
|
|
148
153
|
fetchWorkspace?: typeof fetchWorkspaceOtel;
|
|
149
154
|
}): Promise<OtelLaunchConfig | undefined>;
|
|
155
|
+
export {};
|
|
150
156
|
//# sourceMappingURL=otel.d.ts.map
|
package/dist/otel.js
CHANGED
|
@@ -1,53 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OTel export
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* OTel export gate (Updater pilot ask, now Claude Code parity): decide
|
|
3
|
+
* whether a launch streams session telemetry — traces, metrics, and log
|
|
4
|
+
* events — to the workspace's own OTLP collector (Datadog, Grafana, an
|
|
5
|
+
* in-house collector), and hand the session the standard OpenTelemetry env
|
|
6
|
+
* it needs. The exporter itself lives in the bundled extension
|
|
7
|
+
* (`pi-extension-yagni/src/telemetry`), so there is nothing extra to load and
|
|
8
|
+
* nothing that can go missing on a customer's `npm install`.
|
|
6
9
|
*
|
|
7
|
-
* Three deliberate policies,
|
|
10
|
+
* Three deliberate policies, enforced here rather than left to defaults:
|
|
8
11
|
*
|
|
9
|
-
* 1. GATED, not always-on.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 1. GATED, not always-on. Nothing exports unless an OTLP destination is
|
|
13
|
+
* configured: the user's own OTEL_* env, the workspace's admin-set config
|
|
14
|
+
* (fetched fail-soft and cached), or the repo's committed
|
|
15
|
+
* `.pi/settings.json`. The gate result travels to the session as
|
|
16
|
+
* `YAGNI_OTEL_EXPORT=1` plus the OTLP variables; children (/go stages,
|
|
17
|
+
* subagents) inherit the env and export too.
|
|
15
18
|
*
|
|
16
|
-
* 2. METADATA-ONLY,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* precedence — a repo settings file asking for "full" is overridden, not
|
|
20
|
-
* honored. Session content leaving the machine is a contract change, not a
|
|
21
|
-
* config knob.
|
|
19
|
+
* 2. METADATA-ONLY, in code. Token counts, cost, tier, durations, tool names,
|
|
20
|
+
* and sizes export; prompt, response, and tool content never do. The
|
|
21
|
+
* extension enforces this without a knob — no env var re-enables content.
|
|
22
22
|
*
|
|
23
|
-
* 3. Cost is the customer's SELL rate.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* exported model name is the opaque tier id, never the backing model.
|
|
23
|
+
* 3. Cost is the customer's SELL rate. Usage cost is pi's `usage.cost.total`,
|
|
24
|
+
* computed from the catalog's tier rates (YAG-381), and the exported model
|
|
25
|
+
* name is the opaque tier id, never the backing model.
|
|
27
26
|
*
|
|
28
|
-
* Everything here is fail-soft: telemetry must never block a launch, so
|
|
29
|
-
*
|
|
27
|
+
* Everything here is fail-soft: telemetry must never block a launch, so an
|
|
28
|
+
* unreadable settings file or an offline backend resolves to "disabled" or
|
|
29
|
+
* to last-known config.
|
|
30
30
|
*/
|
|
31
|
-
import {
|
|
31
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
32
32
|
import { join } from "node:path";
|
|
33
|
-
import { fileURLToPath } from "node:url";
|
|
34
33
|
import { credentialsDir } from "./credentials.js";
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
* subagents: they build their own pi argv inside pi-extension-yagni (which
|
|
38
|
-
* cannot import this package), so the path crosses over env. The extension
|
|
39
|
-
* reads the same literal in `pipeline/runner.ts`.
|
|
40
|
-
*/
|
|
41
|
-
export const OTEL_EXTENSION_PATH_ENV = "YAGNI_OTEL_EXTENSION_PATH";
|
|
34
|
+
/** The launcher's "this session exports" gate, read by the bundled extension. */
|
|
35
|
+
export const OTEL_EXPORT_ENV = "YAGNI_OTEL_EXPORT";
|
|
42
36
|
/** The service name a collector sees unless the user set their own. */
|
|
43
37
|
export const DEFAULT_OTEL_SERVICE_NAME = "yagni-code";
|
|
44
38
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
39
|
+
* Headers that belong on the TRACES request only. Datadog's LLM Observability
|
|
40
|
+
* routes OTLP traces to its LLM product when `dd-otlp-source=llmobs` rides
|
|
41
|
+
* along; the same header on the metrics and logs intakes is meaningless at
|
|
42
|
+
* best, so the launcher scopes it to `OTEL_EXPORTER_OTLP_TRACES_HEADERS`.
|
|
43
|
+
*/
|
|
44
|
+
const TRACE_ONLY_HEADERS = new Set(["dd-otlp-source"]);
|
|
45
|
+
/** The standard per-signal endpoint variables, generic first (its precedence). */
|
|
46
|
+
const ENDPOINT_ENV_VARS = [
|
|
47
|
+
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
48
|
+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
|
|
49
|
+
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
|
|
50
|
+
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
|
|
51
|
+
];
|
|
52
|
+
/** Trailing `/v1/<signal>` on a URL — the shape pre-parity configs stored. */
|
|
53
|
+
const SIGNAL_PATH_RE = /\/v1\/(traces|metrics|logs)\/?$/i;
|
|
54
|
+
/**
|
|
55
|
+
* Reduce an endpoint that carries a signal path to its base
|
|
56
|
+
* (`https://otlp.datadoghq.com/v1/traces` → `https://otlp.datadoghq.com`).
|
|
57
|
+
* The extension appends `/v1/<signal>` per signal for http transports, so a
|
|
58
|
+
* workspace config saved before logs and metrics existed keeps exporting all
|
|
59
|
+
* three without an admin re-save. Mirrors the extension's own helper.
|
|
49
60
|
*/
|
|
50
|
-
function
|
|
61
|
+
export function stripSignalPath(endpoint) {
|
|
62
|
+
return endpoint.replace(SIGNAL_PATH_RE, "").replace(/\/+$/, "");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the `otel` block from the repo's `.pi/settings.json`, the project-level
|
|
66
|
+
* file a team commits once so every clone exports. Returns undefined on any
|
|
67
|
+
* problem — a malformed settings file must not block a launch.
|
|
68
|
+
*/
|
|
69
|
+
function projectOtelSettings(cwd, readFile) {
|
|
51
70
|
try {
|
|
52
71
|
const raw = readFile(join(cwd, ".pi", "settings.json"), "utf8");
|
|
53
72
|
const parsed = JSON.parse(String(raw));
|
|
@@ -56,92 +75,126 @@ function projectOtelEndpoint(cwd, readFile) {
|
|
|
56
75
|
const otel = parsed.otel;
|
|
57
76
|
if (typeof otel !== "object" || otel === null)
|
|
58
77
|
return undefined;
|
|
59
|
-
const
|
|
60
|
-
|
|
78
|
+
const o = otel;
|
|
79
|
+
if (o.enabled === false)
|
|
80
|
+
return undefined;
|
|
81
|
+
const endpoint = typeof o.endpoint === "string" && o.endpoint.trim() !== "" ? o.endpoint.trim() : undefined;
|
|
82
|
+
if (!endpoint)
|
|
83
|
+
return undefined;
|
|
84
|
+
const headers = {};
|
|
85
|
+
if (typeof o.headers === "object" && o.headers !== null) {
|
|
86
|
+
for (const [k, v] of Object.entries(o.headers)) {
|
|
87
|
+
// Headers travel to the session as the comma-separated
|
|
88
|
+
// OTEL_EXPORTER_OTLP_HEADERS list; a comma in a value would corrupt
|
|
89
|
+
// every pair after it, so such a header is dropped (same rule the
|
|
90
|
+
// workspace settings enforce at save time).
|
|
91
|
+
if (typeof v === "string" && !v.includes(",") && !k.includes(","))
|
|
92
|
+
headers[k] = v;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
endpoint,
|
|
97
|
+
...(typeof o.protocol === "string" && o.protocol.trim() ? { protocol: o.protocol.trim() } : {}),
|
|
98
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
99
|
+
...(typeof o.serviceName === "string" && o.serviceName.trim() ? { serviceName: o.serviceName.trim() } : {}),
|
|
100
|
+
};
|
|
61
101
|
}
|
|
62
102
|
catch {
|
|
63
103
|
return undefined;
|
|
64
104
|
}
|
|
65
105
|
}
|
|
66
|
-
/**
|
|
67
|
-
function envDisabled(env) {
|
|
68
|
-
|
|
106
|
+
/** Personal kill switch: beats every source. Both spellings accepted. */
|
|
107
|
+
export function envDisabled(env) {
|
|
108
|
+
const truthy = (v) => v === "1" || v === "true";
|
|
109
|
+
return truthy(env.PI_OTEL_DISABLED) || truthy(env.YAGNI_OTEL_DISABLED);
|
|
110
|
+
}
|
|
111
|
+
/** The first configured endpoint variable in the user's env, generic first. */
|
|
112
|
+
function envEndpoint(env) {
|
|
113
|
+
for (const envVar of ENDPOINT_ENV_VARS) {
|
|
114
|
+
const value = env[envVar]?.trim();
|
|
115
|
+
if (value)
|
|
116
|
+
return { envVar, endpoint: value };
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
69
119
|
}
|
|
70
120
|
/**
|
|
71
|
-
* Decide whether this launch exports
|
|
72
|
-
* Returns undefined when export stays off: no endpoint configured
|
|
73
|
-
*
|
|
121
|
+
* Decide whether this launch exports, from the user's env and the repo's
|
|
122
|
+
* settings. Returns undefined when export stays off: no endpoint configured,
|
|
123
|
+
* or a kill switch set.
|
|
124
|
+
*
|
|
125
|
+
* Any of the standard endpoint variables enables export — the generic
|
|
126
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` or a per-signal one — so a machine that
|
|
127
|
+
* carries a Claude Code telemetry env (typically all three per-signal
|
|
128
|
+
* endpoints) lights up with no translation. The extension reads the same
|
|
129
|
+
* variables, per signal, following the OTLP exporter spec.
|
|
74
130
|
*/
|
|
75
131
|
export function resolveOtelLaunch(opts) {
|
|
76
132
|
const { env, cwd } = opts;
|
|
77
133
|
if (envDisabled(env))
|
|
78
134
|
return undefined;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!
|
|
135
|
+
const fromEnv = envEndpoint(env);
|
|
136
|
+
if (fromEnv)
|
|
137
|
+
return { source: "env", envVar: fromEnv.envVar, endpoint: fromEnv.endpoint };
|
|
138
|
+
const settings = projectOtelSettings(cwd, opts.readFile ?? readFileSync);
|
|
139
|
+
if (!settings)
|
|
84
140
|
return undefined;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
return envEndpoint
|
|
93
|
-
? { extensionPath, source: "env", endpoint: envEndpoint }
|
|
94
|
-
: { extensionPath, source: "project-settings", endpoint: settingsEndpoint };
|
|
141
|
+
return {
|
|
142
|
+
source: "project-settings",
|
|
143
|
+
endpoint: settings.endpoint,
|
|
144
|
+
...(settings.protocol ? { protocol: settings.protocol } : {}),
|
|
145
|
+
...(settings.headers ? { headers: settings.headers } : {}),
|
|
146
|
+
...(settings.serviceName ? { serviceName: settings.serviceName } : {}),
|
|
147
|
+
};
|
|
95
148
|
}
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*/
|
|
102
|
-
export function resolveOtelExtensionPath() {
|
|
103
|
-
const path = fileURLToPath(import.meta.resolve("pi-otel"));
|
|
104
|
-
if (!existsSync(path)) {
|
|
105
|
-
throw new Error(`pi-otel entry not found at ${path}`);
|
|
106
|
-
}
|
|
107
|
-
return path;
|
|
149
|
+
/** Render a header record as the comma-separated `k=v` list the OTel env carries. */
|
|
150
|
+
function headerList(headers) {
|
|
151
|
+
return Object.entries(headers)
|
|
152
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
153
|
+
.join(",");
|
|
108
154
|
}
|
|
109
155
|
/**
|
|
110
|
-
* The env keys an
|
|
111
|
-
*
|
|
112
|
-
*
|
|
156
|
+
* The env keys an exporting child must carry. `YAGNI_OTEL_EXPORT=1` is the
|
|
157
|
+
* gate the extension reads; the service name only fills in when the user has
|
|
158
|
+
* not chosen their own.
|
|
113
159
|
*
|
|
114
|
-
* A "workspace"-
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
160
|
+
* A "workspace" or "project-settings" config delivers its endpoint (as the
|
|
161
|
+
* generic BASE endpoint — the extension derives the per-signal URLs),
|
|
162
|
+
* protocol, headers, and service name over the standard OTel env vars — but
|
|
163
|
+
* any of those the USER already set in their own environment wins (local
|
|
164
|
+
* env > workspace config), so an engineer can point one session at a
|
|
165
|
+
* scratch collector without an admin change. An "env" config is the user's
|
|
166
|
+
* own setup and passes through untouched.
|
|
119
167
|
*/
|
|
120
168
|
export function otelChildEnv(config, baseEnv) {
|
|
121
|
-
const
|
|
122
|
-
if (config.source === "workspace") {
|
|
123
|
-
|
|
124
|
-
|
|
169
|
+
const out = {};
|
|
170
|
+
if (config.source === "workspace" || config.source === "project-settings") {
|
|
171
|
+
const userHasEndpoint = ENDPOINT_ENV_VARS.some((v) => baseEnv[v]?.trim());
|
|
172
|
+
if (!userHasEndpoint) {
|
|
173
|
+
out.OTEL_EXPORTER_OTLP_ENDPOINT = stripSignalPath(config.endpoint);
|
|
125
174
|
}
|
|
126
175
|
if (config.protocol && !baseEnv.OTEL_EXPORTER_OTLP_PROTOCOL) {
|
|
127
|
-
|
|
176
|
+
out.OTEL_EXPORTER_OTLP_PROTOCOL = config.protocol;
|
|
128
177
|
}
|
|
129
178
|
if (config.headers && Object.keys(config.headers).length > 0 && !baseEnv.OTEL_EXPORTER_OTLP_HEADERS) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
179
|
+
const shared = {};
|
|
180
|
+
const traceOnly = {};
|
|
181
|
+
for (const [k, v] of Object.entries(config.headers)) {
|
|
182
|
+
(TRACE_ONLY_HEADERS.has(k.toLowerCase()) ? traceOnly : shared)[k] = v;
|
|
183
|
+
}
|
|
184
|
+
if (Object.keys(shared).length > 0)
|
|
185
|
+
out.OTEL_EXPORTER_OTLP_HEADERS = headerList(shared);
|
|
186
|
+
if (Object.keys(traceOnly).length > 0 && !baseEnv.OTEL_EXPORTER_OTLP_TRACES_HEADERS) {
|
|
187
|
+
out.OTEL_EXPORTER_OTLP_TRACES_HEADERS = headerList(traceOnly);
|
|
188
|
+
}
|
|
133
189
|
}
|
|
134
190
|
if (config.serviceName && !baseEnv.OTEL_SERVICE_NAME) {
|
|
135
|
-
|
|
191
|
+
out.OTEL_SERVICE_NAME = config.serviceName;
|
|
136
192
|
}
|
|
137
193
|
}
|
|
138
194
|
return {
|
|
139
|
-
...
|
|
140
|
-
[
|
|
141
|
-
|
|
142
|
-
...(baseEnv.OTEL_SERVICE_NAME || workspace.OTEL_SERVICE_NAME
|
|
143
|
-
? {}
|
|
144
|
-
: { OTEL_SERVICE_NAME: DEFAULT_OTEL_SERVICE_NAME }),
|
|
195
|
+
...out,
|
|
196
|
+
[OTEL_EXPORT_ENV]: "1",
|
|
197
|
+
...(baseEnv.OTEL_SERVICE_NAME || out.OTEL_SERVICE_NAME ? {} : { OTEL_SERVICE_NAME: DEFAULT_OTEL_SERVICE_NAME }),
|
|
145
198
|
};
|
|
146
199
|
}
|
|
147
200
|
// ── Workspace-configured export (admin sets it once in the web app) ─────────
|
|
@@ -235,10 +288,10 @@ function readWorkspaceOtelCache(cachePath) {
|
|
|
235
288
|
}
|
|
236
289
|
}
|
|
237
290
|
/**
|
|
238
|
-
* Full launch-time resolution, all
|
|
291
|
+
* Full launch-time resolution, all four sources in precedence order:
|
|
239
292
|
*
|
|
240
|
-
* 1. `PI_OTEL_DISABLED`
|
|
241
|
-
* 2. env `
|
|
293
|
+
* 1. `PI_OTEL_DISABLED` / `YAGNI_OTEL_DISABLED` — personal kill switch.
|
|
294
|
+
* 2. env `OTEL_EXPORTER_OTLP_*_ENDPOINT` — the user's own setup, untouched.
|
|
242
295
|
* 3. workspace config — admin-set in the web app, fetched/cached.
|
|
243
296
|
* 4. repo `.pi/settings.json` — committed per-repo config.
|
|
244
297
|
*
|
|
@@ -249,27 +302,14 @@ export async function resolveOtelLaunchWithWorkspace(opts) {
|
|
|
249
302
|
if (envDisabled(env))
|
|
250
303
|
return undefined;
|
|
251
304
|
// Source 2: the user's own env config short-circuits — no fetch needed.
|
|
252
|
-
if (env
|
|
253
|
-
return resolveOtelLaunch({
|
|
254
|
-
env,
|
|
255
|
-
cwd,
|
|
256
|
-
...(opts.resolveExtension ? { resolveExtension: opts.resolveExtension } : {}),
|
|
257
|
-
...(opts.readFile ? { readFile: opts.readFile } : {}),
|
|
258
|
-
});
|
|
305
|
+
if (envEndpoint(env)) {
|
|
306
|
+
return resolveOtelLaunch({ env, cwd, ...(opts.readFile ? { readFile: opts.readFile } : {}) });
|
|
259
307
|
}
|
|
260
308
|
// Source 3: workspace config.
|
|
261
309
|
if (opts.creds?.token && opts.profileName) {
|
|
262
310
|
const workspace = await (opts.fetchWorkspace ?? fetchWorkspaceOtel)({ baseUrl: opts.creds.baseUrl, token: opts.creds.token }, opts.profileName, opts.fetchDeps ?? {});
|
|
263
311
|
if (workspace) {
|
|
264
|
-
let extensionPath;
|
|
265
|
-
try {
|
|
266
|
-
extensionPath = (opts.resolveExtension ?? resolveOtelExtensionPath)();
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
return undefined;
|
|
270
|
-
}
|
|
271
312
|
return {
|
|
272
|
-
extensionPath,
|
|
273
313
|
source: "workspace",
|
|
274
314
|
endpoint: workspace.endpoint,
|
|
275
315
|
...(workspace.protocol ? { protocol: workspace.protocol } : {}),
|
|
@@ -281,11 +321,6 @@ export async function resolveOtelLaunchWithWorkspace(opts) {
|
|
|
281
321
|
}
|
|
282
322
|
}
|
|
283
323
|
// Source 4: repo-committed settings.
|
|
284
|
-
return resolveOtelLaunch({
|
|
285
|
-
env,
|
|
286
|
-
cwd,
|
|
287
|
-
...(opts.resolveExtension ? { resolveExtension: opts.resolveExtension } : {}),
|
|
288
|
-
...(opts.readFile ? { readFile: opts.readFile } : {}),
|
|
289
|
-
});
|
|
324
|
+
return resolveOtelLaunch({ env, cwd, ...(opts.readFile ? { readFile: opts.readFile } : {}) });
|
|
290
325
|
}
|
|
291
326
|
//# sourceMappingURL=otel.js.map
|
package/dist/paths.d.ts
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
* fall back to resolving the workspace-linked package, which keeps `pnpm test`
|
|
20
20
|
* and a fresh checkout working before anything is bundled.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Absolute path to this CLI's entry (`dist/cli.js`). Forwarded to sessions as
|
|
24
|
+
* `YAGNI_CLI_PATH` so the extension's `/plugin` panel can run
|
|
25
|
+
* `yagni plugin …` with the exact binary that launched it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveCliPath(): string;
|
|
22
28
|
export declare function resolveExtensionPath(): string;
|
|
23
29
|
/**
|
|
24
30
|
* Absolute path to the extension's headless pipeline entry
|
|
@@ -29,6 +35,13 @@ export declare function resolveExtensionPath(): string;
|
|
|
29
35
|
* it pulls in the pipeline alone, with none of pi's TUI surface.
|
|
30
36
|
*/
|
|
31
37
|
export declare function resolveHeadlessGoPath(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Absolute path to the extension's telemetry probe (`telemetry/probe.js`),
|
|
40
|
+
* the module `yagni doctor` imports to send one real record per signal.
|
|
41
|
+
* Same derivation as `resolveHeadlessGoPath`; only this module (and the OTel
|
|
42
|
+
* SDK it pulls in) loads, none of pi's TUI surface.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveTelemetryProbePath(): string;
|
|
32
45
|
/**
|
|
33
46
|
* Absolute path to the extension's session-worktree entry
|
|
34
47
|
* (`pipeline/sessionWorktree.js`), the module `yagni -w` imports.
|
package/dist/paths.js
CHANGED
|
@@ -25,6 +25,14 @@ function resolveModulePath(specifier) {
|
|
|
25
25
|
* fall back to resolving the workspace-linked package, which keeps `pnpm test`
|
|
26
26
|
* and a fresh checkout working before anything is bundled.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Absolute path to this CLI's entry (`dist/cli.js`). Forwarded to sessions as
|
|
30
|
+
* `YAGNI_CLI_PATH` so the extension's `/plugin` panel can run
|
|
31
|
+
* `yagni plugin …` with the exact binary that launched it.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveCliPath() {
|
|
34
|
+
return fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
35
|
+
}
|
|
28
36
|
export function resolveExtensionPath() {
|
|
29
37
|
const bundled = fileURLToPath(new URL("./extension/index.js", import.meta.url));
|
|
30
38
|
if (existsSync(bundled))
|
|
@@ -43,6 +51,16 @@ export function resolveHeadlessGoPath() {
|
|
|
43
51
|
const entry = resolveExtensionPath();
|
|
44
52
|
return join(dirname(entry), "pipeline", "headlessGo.js");
|
|
45
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Absolute path to the extension's telemetry probe (`telemetry/probe.js`),
|
|
56
|
+
* the module `yagni doctor` imports to send one real record per signal.
|
|
57
|
+
* Same derivation as `resolveHeadlessGoPath`; only this module (and the OTel
|
|
58
|
+
* SDK it pulls in) loads, none of pi's TUI surface.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveTelemetryProbePath() {
|
|
61
|
+
const entry = resolveExtensionPath();
|
|
62
|
+
return join(dirname(entry), "telemetry", "probe.js");
|
|
63
|
+
}
|
|
46
64
|
/**
|
|
47
65
|
* Absolute path to the extension's session-worktree entry
|
|
48
66
|
* (`pipeline/sessionWorktree.js`), the module `yagni -w` imports.
|