feat(telemetry): 1DS skill-run telemetry infra - #176
Merged
Conversation
…ders (#142) * docs: add design spec for 1DS telemetry infra Design for a shared telemetry library at shared/telemetry/ consumed by the power-pages plugin first, with interactive first-run consent, strict-allowlist payloads, and fail-closed emission via the existing PreToolUse/PostToolUse:Skill hook surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(telemetry): add shared 1DS telemetry library Introduces the shared 1DS telemetry infrastructure used by all adopting plugins. Anonymous, default-on, env-var opt-out only (POWER_PLATFORM_SKILLS_TELEMETRY=0). No persistent consent file, no first-run prompt. Library (shared/telemetry/lib/): - events.js — type-aware allowlist builders (PowerPagesPluginEvent envelope, top-level columns, per-event field set, durationMs as int) - emit-dispatcher.js — detached child that POSTs to OneCollector with the Common Schema 4.0 envelope, exits silently on any failure - emit-spawn.js — fire-and-forget spawn helper with restricted child env - emit-from-prompt.js — orchestrator for slash-command skill_started events (reads ikey.json, calls pac auth + agent info, builds event) - pac-auth.js — shells out to `pac auth who` for orgId/tenantId - agent-info.js — detects host AI agent (Claude Code, Copilot CLI) + pac CLI version via env override → auto-detect → empty - prompt-detector.js — matches /plugin:skill from user prompt text - local-log.js — placeholder-iKey JSONL fallback at ~/.power-platform-skills/events.jsonl - with-telemetry.js — runInstrumented() wrapper for script-level signals - correlation.js, session.js, scrubber.js — utilities Config: shared/telemetry/ikey.json carries the iKey, collector URL, per-plugin event_stream_name, and a 'disabled' kill switch. The flag ships true so infrastructure can land before tenant-side annotation + Kusto provisioning is complete; a single PR flips it false later. Sync: shared/telemetry/sync-to-plugin.js copies lib/ + ikey.json into <plugin>/scripts/lib/telemetry/. Adopting plugins never hand-edit the synced copy. Tests: full node:test coverage for every module plus the dispatcher and spawn integration (109 tests, all hermetic; envelope-shape probe via POWER_PLATFORM_SKILLS_FAKE_HTTPS). Docs: rebuild design spec (2026-05-04) + plugin adoption guide (2026-04-29) under docs/superpowers/specs/. Older draft spec (2026-04-20) removed. * docs(telemetry): align README with code — camelCase fields, correct PAC posture shared/telemetry/README.md drifted from the rebuild work: - Field names listed in snake_case (plugin_name, session_id, ...) but the code uses camelCase (pluginName, sessionId, ...). Names match Kusto column names directly. - "What is NEVER sent" claimed tenant IDs are not sent — but pac-auth.js explicitly emits orgId and tenantId GUIDs from the active PAC profile. Moved them to "What is sent" with the rest of the PAC + agent fields. - Missing from "What is sent": pluginVersion, osName/osVersion (was os_family), pacCliVersion, aiAgentName, aiAgentVersion, eventInfo, errorDescription. - Added a note about errorDescription being a 500-char-truncated Error.message — callers must keep PII out of exception messages. - Documented the ikey.json "disabled" repo-side kill switch under Privacy posture. * refactor(telemetry): address review feedback (PR #142) Four fixes from PR review: 1. **iKey full form in envelope** — emit-dispatcher.js now sends the complete iKey value in the Common Schema `iKey` field (`"o:" + IKEY`) instead of truncating to just the first hyphen-separated segment. Aligns with the powerplatform-vscode pattern. Multi-cloud key restructuring deferred to a follow-up PR. 2. **Drop POWER_PLATFORM_SKILLS_BYPASS_KILL_SWITCH** — production code no longer carries a test-only escape hatch. Tests now override the ikey.json path via POWER_PLATFORM_SKILLS_IKEY_JSON (parallels the existing IKEY/COLLECTOR/CONFIG_DIR/FAKE_HTTPS env-var pattern) and supply their own `disabled: false` config when they need to exercise emission paths. emit-spawn.js forwards the new var to the detached child. 3. **Defense-in-depth FIELD_TYPES enforcement in dispatcher** — events.js now exports `pick`. The dispatcher imports both `FIELD_TYPES` and `pick`, then re-runs the allowlist filter on `event.data` before building the envelope. Any field that bypasses the builders is dropped before it reaches the wire. The three reserved meta fields (eventName, eventType, severity) survive via an explicit allow list. 4. **errorDescription emits err.code only, never err.message** — with-telemetry.js no longer reads `err.message` (which can contain file paths, GUIDs, tokens). The field now captures `err.code` — short non-PII metadata like `ENOENT` or `ERR_INVALID_ARG_TYPE`. Test "errorDescription truncated to 500 chars" replaced with one that asserts a PII-bearing message is NOT emitted anywhere when `err.code` is set. README updated accordingly. New dispatcher test: `dispatcher strips unknown fields from event.data` hand-crafts an event with filePath/stackTrace/rawPrompt/tokenValue and asserts none survive the FIELD_TYPES filter. Tests: 110/110. * refactor(telemetry): rename ikey.json key — ikey → instrumentationKey Per PR #142 comment 1: the JSON property is now spelled in its full form (`instrumentationKey`) to match the powerplatform-vscode constants pattern instead of the abbreviated `ikey`. The envelope `iKey` Common Schema field stays at the standard tenant-routing short form (`o:<32-char-tenant>`); the previous attempt to put the full hyphenated key in the envelope is reverted because the full form already lives in the `x-apikey` header. - shared/telemetry/ikey.json: `"ikey"` → `"instrumentationKey"`. - shared/telemetry/lib/emit-from-prompt.js: read `cfg.instrumentationKey` (other JSON keys — `collector_url`, `event_stream_name`, `disabled` — unchanged). - shared/telemetry/lib/emit-dispatcher.js: envelope reverted to `iKey: "o:" + IKEY.split("-")[0]`. - Test fixtures updated across emit-dispatcher / emit-spawn / emit-from-prompt tests. Multi-cloud iKey restructuring (the second half of PR #142 comment 1) remains deferred to a follow-up PR. Tests: 110/110. --------- Co-authored-by: Amit Joshi <amitjoshi@microsoft.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#153) * feat(power-pages): adopt 1DS telemetry — synced library, hooks, validators Wires the shared 1DS telemetry library into the power-pages plugin. Synced copy of the library lives under scripts/lib/telemetry/ and runs at user time. PreToolUse:Skill emits skill_started; PostToolUse:Skill emits skill_completed wrapped around the existing validator chain. Plugin-side additions: - scripts/lib/telemetry/ — synced from shared/telemetry/ via sync-to-plugin.js (12 lib modules + ikey.json carrying the PowerPagesPluginEvent stream name + disabled:true kill switch). - scripts/lib/telemetry-runner.js — runInstrumented() shim plus the PowerPagesPluginEvent envelope name and tracked-script registry. - hooks/run-skill-pretool-telemetry.js, hooks/run-skill-posttool-validation.js — fire-and-forget telemetry around the Skill tool, preserving the existing validator's behavior and exit code. - hooks/hooks.json — registers the two Skill hooks. (UserPromptSubmit is registered separately by the slash-command branch.) - Validators (validate-activation/seo/site/webroles/audit/auth/datamodel/ cloudflow/serverlogic/webapi-integration) and four other plugin scripts (check-activation-status, clear-site-cache, render-audit-report, verify-dataverse-access) wrapped with runInstrumented() so each script run produces script_started/script_completed signals. - scripts/tests/{telemetry-hook-pretool,telemetry-hook-posttool,telemetry-runner}.test.js — hermetic node:test coverage for the new wiring. Repo updates: - AGENTS.md (plugin) gains the Telemetry section pointing at shared/telemetry/ as the canonical source of truth. - README.md (root) mentions anonymous telemetry + env opt-out. - docs/superpowers/handoff/PowerPagesPluginEvent-annotation.xml is the tenant-team handoff describing the EventStreamingAnnotation for the per-plugin Kusto stream. Privacy posture: default-on, env-var opt-out only (POWER_PLATFORM_SKILLS_TELEMETRY=0). No first-run prompt, no consent file. Telemetry stays gated by ikey.json's disabled:true flag until the tenant-side annotation + Kusto table are provisioned. * fix(power-pages): telemetry-runner — pass envelopeName, read instrumentationKey Two regressions caught during E2E verification of the local-log path: 1. **envelopeName was never forwarded** — `runInstrumented()` called `withTelemetry()` without an `envelopeName`, so events shipped with `name: ""`. In production this means the tenant's `EventStreamingAnnotation name="^PowerPagesPluginEvent$"` matcher silently drops every event (`acc:1` from the collector but zero rows in Kusto). Now reads `event_stream_name` from `ikey.json` and passes it through. 2. **iKey read the old JSON property** — `deps.ikeyCfg.ikey` was undefined after the `ikey` → `instrumentationKey` rename in the foundation library (PR #142). The dispatcher saw an empty `POWER_PLATFORM_SKILLS_IKEY` and always took the placeholder / local-log path, even with a real iKey provisioned. Now reads `deps.ikeyCfg.instrumentationKey`. Adds an opt-in `_overrides` test seam on `runInstrumented()` so unit tests can inject a fake `withTelemetry` and assert opts are propagated correctly. New tests: - `runInstrumented forwards envelopeName from ikey.json event_stream_name` - `runInstrumented reads iKey from instrumentationKey property (not legacy 'ikey')` Verified end-to-end by piping a synthetic event directly to the dispatcher: envelope `name` is now `"PowerPagesPluginEvent"`, `scriptName` is correctly set, event lands in the local JSONL. Tests: 4/4 telemetry-runner, full plugin suite still green. * telemetry: detect GitHub Copilot CLI as ai agent host readAiAgent now recognizes COPILOT_CLI=1 and reads COPILOT_CLI_BINARY_VERSION, in addition to Claude Code. Priority remains: explicit AI_AGENT_NAME env > Claude Code > Copilot CLI. Adds test coverage and re-syncs into plugins/power-pages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(telemetry): drop power-pages testing iKey from shared/telemetry/ikey.json `shared/telemetry/ikey.json` is the dev-time placeholder copied by `sync-to-plugin.js`. It was committed with power-pages's real testing iKey and INT collector URL, which meant any future adopter (canvas-apps, code-apps, model-apps) running the sync would inherit those values and — if they ever flipped `disabled: false` without replacing the iKey — silently route their telemetry to power-pages's Kusto stream. This commit replaces both with explicit placeholders so every plugin is forced to provision its own cluster config before going live. The plugin's own synced copy at `plugins/power-pages/scripts/lib/telemetry/ikey.json` keeps its real plugin-specific values; only the shared template is sanitized. - instrumentationKey: <pp's value> → "PLACEHOLDER_REPLACE_BEFORE_SHIPPING" - collector_url: <INT URL> → "" `event_stream_name` ("PluginEventStreamPlaceholder") and `disabled: true` were already plugin-agnostic and stay unchanged. Tests: 110/110 shared (no behavior change — the placeholder iKey constant matches the dispatcher's existing keyMissing check). * fix(power-pages): address PR #153 review — await, dedup, hook fast-path Three review fixes from priyanshu92 on PR #153: 1. **telemetry-runner.js: `return await asyncFn()`** — added the `await` on the no-telemetry-deps fallback path so stack traces include the `runInstrumented` frame. Behavioural no-op; debug-friendliness only. 2. **validate-auth.js: drop duplicate `runInstrumented` call** — the file invoked `runInstrumented('validate-setup-auth', main)` twice: once unconditionally at module top-level (line 69-72), and again inside the standard `if (require.main === module)` guard (line 137-142). When executed as a script (the actual PostToolUse-hook use case), `main()` ran twice — duplicating validator I/O and emitting two `script_started`/`script_completed` pairs per run. When required as a module (tests), the top-level call still fired, breaking import semantics. Deleted the top-level invocation, kept the require.main guard. 3. **Skill hooks: fast-path opt-out / kill switch BEFORE pac shell-outs** — `run-skill-pretool-telemetry.js` and `run-skill-posttool-validation.js` ran `pac auth who` (3s timeout) and `pac --version` (2s timeout) unconditionally on every tracked Skill invocation, even when the user had opted out (`POWER_PLATFORM_SKILLS_TELEMETRY=0`) or the shipped kill switch was on (`ikey.json` `disabled: true`). Worst case ~10s added latency per Skill tool use, currently hit by every user since `disabled: true` is the shipped default. Both hooks now check, in this order, BEFORE any pac shell-out: a) env opt-out (`POWER_PLATFORM_SKILLS_TELEMETRY === "0"`) b) repo-side kill switch (`ikey.json` `disabled === true`) c) unconfigured (`!ikey.instrumentationKey`) Any of those → exit early. The posttool hook still preserves the validator's exit code regardless of telemetry state. `readIkey()` in the pretool hook now also exposes the `disabled` flag so the gate can read it without re-parsing. Comment #3 (extend agent-info detection to Codex / OpenCode) is pending reviewer confirmation of those hosts' env-var conventions — not implemented here. Any host can already self-identify via the AI_AGENT_NAME / AI_AGENT_VERSION override. Tests: 10/10 plugin telemetry hooks + runner. Shared suite unchanged. --------- Co-authored-by: Amit Joshi <amitjoshi@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…168) Adds telemetry for skills invoked via slash command (e.g. /power-pages:add-seo) at the user-prompt boundary, which the existing PreToolUse:Skill hook does not cover (that fires only for programmatic Skill tool calls). - hooks/run-user-prompt-telemetry.js: UserPromptSubmit hook that matches /<plugin>:<skill> via shared/telemetry/lib/prompt-detector.js and calls emitSkillStartedFromPrompt() with PAC auth + agent info enrichment. Fire-and-forget; never blocks prompt submission. - hooks/hooks.json: registers the new UserPromptSubmit hook alongside the existing PreToolUse / PostToolUse:Skill hooks. - scripts/tests/run-user-prompt-telemetry.test.js: hermetic coverage for the hook (tracked-skill match, no-match no-op, malformed input, env opt-out). - docs/superpowers/specs/2026-04-23-slash-command-telemetry-design.md: design doc covering why this hook exists and how it composes with the Skill-tool path. skill_completed remains the responsibility of the PostToolUse:Skill hook for skills that flow through the Skill tool. For slash-only skills, completion is tracked indirectly via script-level runInstrumented() spans. Co-authored-by: Amit Joshi <amitjoshi@microsoft.com>
The 1DS telemetry foundation has landed and is in active use. The design specs (slash-command-telemetry-design, telemetry-rebuild-design, plugin adoption guide) and the PowerPagesPluginEvent annotation XML were implementation-time artifacts — the live integration is now documented in shared/telemetry/README.md, which covers what is sent, the privacy posture, and how to adopt the library in a new plugin. Updates plugins/power-pages/AGENTS.md to point readers at the README instead of the deleted spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… + script paths The pretool and posttool hooks already short-circuit on cfg.disabled before invoking pac auth who (~3s) and pac --version (~2s). The two remaining entry points — emit-from-prompt (used by the UserPromptSubmit hook) and telemetry-runner (used by scripts wrapped in runInstrumented) — were calling PAC and spawning the detached dispatcher regardless; the dispatcher caught disabled later but the user had already paid the shellout cost. Add three fast-path gates at both entry points, in this order: - POWER_PLATFORM_SKILLS_TELEMETRY=0 (env opt-out) - cfg.disabled === true (repo-side kill switch) - empty instrumentationKey (unprovisioned) Any of these returns immediately and runs the user's original async fn (telemetry-runner) or a no-op result (emit-from-prompt). Zero PAC, zero agent-info, zero spawn. Also fix a pre-existing destructuring bug in emit-from-prompt.test.js's mkTelemetryDir helper: callers passed 'instrumentationKey' but the helper destructured 'ikey', so every test ran with an empty iKey field by accident. Fix the helper and add test coverage for all three new fast paths in both modules. Synced shared/telemetry/lib/emit-from-prompt.js into the power-pages plugin copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorganize around the questions a plugin author actually asks: - What does the library do? (data flow diagram) - What goes on the wire? (fields + privacy posture) - Where do the modules live? (lib/ layout) - How do I adopt this in my plugin? (5 numbered steps) - How do I update the shared lib without clobbering my ikey.json? - What test seams exist? Drop the dangling reference to the deleted design spec. The new "Adopting in a new plugin" section walks through sync, ikey.json config, hook registration, optional script wrapping, and local verification — covering everything a new plugin needs without requiring the implementation history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two real bugs causing every emitted event to silently drop orgId,
tenantId, and pacCliVersion:
1) pac-auth.js TIMEOUT_MS was 3000, but `pac auth who` cold-start on
Windows consistently takes 3.5-4s (.NET runtime + cached-token
validation). Every call timed out, the catch handler swallowed it,
readPacAuth returned null, and the hooks emitted events without
the PAC identity fields. Bumped to 8000 — still well under the
30s hook budget.
2) agent-info.js readPacCliVersion ran `pac --version`, but PAC 2.x
treats `--version` as an unknown command: it prints the version
banner ("Version: 2.7.4+g06bb2eb ...") to stdout but exits with
status 1. execFileSync throws on non-zero exit, the old catch
discarded everything, and the version was lost. Now we read
err.stdout from the thrown error and parse the banner. Tightened
the regex from a bare semver match to one anchored on the
"Version:" prefix so we don't accidentally pick up the .NET
Framework version from the same line. Bumped its timeout to 8s
too.
Verified on this machine: cold-start total of both reads is ~5.7s,
returning real orgId/tenantId/cloud and pacCliVersion "2.7.4".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…de Code session session.getSessionId() cached a random UUID in module-level state, but each hook spawns as a fresh Node process — the cache reset every invocation, so every event in a Claude Code session carried a different sessionId. That broke session-scoped joins in Kusto. Claude Code sends session_id in the hook stdin payload. Wire it through: - session.js: getSessionId(override?) — if a truthy string is passed, prime the cache with it. Subsequent calls (with or without override) return the primed value. _resetCache() exported for tests. - pretool / posttool / user-prompt hooks: extract parsed.session_id from the hook payload and pass it to getSessionId / through emitSkillStartedFromPrompt opts. - emit-from-prompt.js: accept opts.sessionId and forward to getSessionId. Synced into the plugin copy. New session tests cover override winning over a previously-cached UUID, multi-process stability when callers pass the same primed value, and falsy-override safety. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion cleanup Records the audit findings and approved design for collapsing the telemetry event vocabulary to skill_started + skill_completed only. Removes the script-wrapping path (with-telemetry, telemetry-runner, runInstrumented) and adds a TTL sweep to correlation.js to bound leaks when PostToolUse never fires. Ratifies the just-shipped sessionId fix that sources Claude Code's session_id from hook stdin. Finding E (missing skill_completed in local trace) deferred to a separate investigation after this cleanup lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Copilot CLI) Per review feedback: sessionId should come from whichever host agent the user is running under. Add extractSessionIdFromPayload helper that checks both session_id (Claude Code convention) and sessionId (camelCase fallback for hosts that use that). aiAgentName already detects both hosts via agent-info.js; this brings sessionId into the same multi-host pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…opilot CLI Copilot CLI exposes its session ID via the env var COPILOT_SESSION_ID, not through the hook stdin payload. Rename the helper to resolveHostSessionId and have it consult, in precedence order: payload.session_id, payload.sessionId, env.COPILOT_SESSION_ID, empty. Each hook stays a single line; new hosts can be added by extending the resolver without touching hook code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…load Per user feedback: GitHub Copilot CLI uses the same hook stdin payload mechanism as Claude Code to surface its session id. Drop the COPILOT_SESSION_ID env var fallback; resolveHostSessionId now only inspects payload.session_id and payload.sessionId. Simpler resolver, one fewer test, no env coupling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…correlation cleanup Bite-sized TDD plan covering: resolveHostSessionId helper, correlation TTL sweep, removal of buildScript builders, deletion of with-telemetry + telemetry-runner, unwrap runInstrumented from 10 validators + 4 standalone scripts, switch 3 hooks to resolveHostSessionId, sync, README update, manual verification, push. 12 tasks total. Spec reference: docs/superpowers/specs/2026-05-28-session-correlation-cleanup-design.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…skill-only events
The const input was scoped to the first try block while the telemetry block (second try) also references it for resolveHostSessionId. The ReferenceError was silently swallowed by the outer telemetry catch, preventing all skill_completed events from emitting. This is the root cause of Finding E (9 skill_started, 0 skill_completed). Hoist to outer async scope so both blocks see the parsed payload.
…AGENT_NAME is set If the host sets AI_AGENT_NAME (e.g., via .claude/settings.local.json) but omits AI_AGENT_VERSION, the previous code returned aiAgentVersion="" and the hook silently dropped the field. Now: when AI_AGENT_NAME is explicit but the version is empty, fall through to the same built-in detection used when AI_AGENT_NAME isn't set at all — read the version from CLAUDE_CODE_EXECPATH/../package.json for Claude Code, or COPILOT_CLI_BINARY_VERSION for Copilot CLI. Explicit AI_AGENT_VERSION still wins over the backfill when present. Extracted readClaudeCodeVersion(env) helper to share the package.json read path between the explicit-name and built-in detection branches. Synced into the plugin copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflect actual agent-info logic: Claude Code version is read from the installed package.json via CLAUDE_CODE_EXECPATH, Copilot CLI version from COPILOT_CLI_BINARY_VERSION, and an explicit AI_AGENT_NAME backfills its version from the matching built-in detector when AI_AGENT_VERSION is empty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the telemetry feature branch up to date with main (canvas-apps, model-apps, Power Pages ALM, security skills, multi-provider setup-auth). Resolved conflict in setup-auth/scripts/validate-auth.js: kept the branch's async main() wrapper + module.exports/require.main guard and adopted main's new validation logic (marker gate, AUTH_PROVIDERS check, refined authz-utils check) with `return approve()`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The session/correlation cleanup work has shipped; drop the superpowers design spec and implementation plan that tracked it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These 10 validators and 4 standalone scripts diverged from main only by the leftover runInstrumented-unwrap shape (async main() wrapper + require.main guard + module.exports). That structure is a vestige of an earlier per-script telemetry approach that was dropped in favor of hook-based skill telemetry, so it's no longer needed. Restore main's versions to keep the telemetry PR scoped to telemetry-only changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
added 8 commits
June 8, 2026 14:03
Switch the telemetry skill from a repo-root-relative workflow reference
(${CLAUDE_PLUGIN_ROOT}/../../shared/...) to the bundled-path pattern used
by report-issue: SKILL.md points at ${CLAUDE_PLUGIN_ROOT}/skills/telemetry/
telemetry-workflow.md, backed by a per-plugin symlink to the shared source.
Marketplace installs copy only the plugin directory, so the old ../../shared
path did not resolve at user time. The symlink keeps a single source of truth
in-repo while letting installers dereference it into the plugin cache.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amitjoshi438
force-pushed
the
users/amitjosh/1ds-feature
branch
from
June 10, 2026 11:56
ae1c39f to
dc61be8
Compare
…try-symlink Brings the 18 commits of telemetry work (per-plugin opt-out CLI + config, removal of the POWER_PLATFORM_SKILLS_TELEMETRY env var, skill wrapper, README docs) plus the model-apps eval suite, report-issue bundling, and main merges into the symlink-experiment branch. Conflict resolution (architecture decision: keep the symlink experiment): - plugins/power-pages/scripts/lib/telemetry/lib: kept as a symlink to ../../../../../shared/telemetry/lib (ours). Discarded the real synced copies 1ds-feature reintroduced; the new shared modules (telemetry-config.js, user-config.js) are now exposed through the link. - shared/telemetry/lib/emit-spawn.js: dropped the POWER_PLATFORM_SKILLS_TELEMETRY forwarding (1ds-feature removed that env var and its reader entirely), kept the opts.ikeyJsonPath plumbing from b2b791b, which is load-bearing for the symlink architecture: it lets the dispatcher read the plugin's real ikey.json instead of shared/'s placeholder when lib/ is reached via the symlink. Verified: shared telemetry suite 151/151 pass; power-pages telemetry hook suite 23/23 pass (incl. the ikey.json override-seam tests that drive the plumbing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…update docs) Make the symlink the single source of truth for the shared telemetry lib, replacing the synced-copy mechanism: - remove shared/telemetry/sync-to-plugin.js and its test (superseded by the per-plugin lib symlink; the script would have copied lib/ onto itself) - rewrite the telemetry docs to the symlink model across root AGENTS.md, plugins/power-pages/AGENTS.md, and shared/telemetry/README.md, including the symlink adoption recipe and the ikey.json posture rule (committed stays disabled:true; working-tree disabled:false is a local experiment only) - gitignore .omc/ scratch directories Verified: shared telemetry suite 147/147, power-pages telemetry hooks 23/23. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…est comment)
- user-config.readConfig: ignore an array config.json. typeof [] === "object"
passed the old guard, so setTelemetryChoice set .telemetry on the array and
JSON.stringify silently dropped it — returning true while persisting nothing.
Now guarded with !Array.isArray + regression test.
- powerpages-hook-utils: build TRACKED_SKILLS with Object.create(null). Membership
is tested via bracket access, so a plain {} let inherited keys (toString,
constructor, __proto__) test truthy and emit bogus skill names. Added a test.
- events.test.js: fix the misleading "drop guarded by clamp" comment — boolean
true is kept and coerced to 1 (Number(true)), not dropped.
Addresses copilot review comments on PR #176. Shared telemetry 148/148,
power-pages telemetry hooks 24/24.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Priyanshu Agrawal (priyanshu92)
approved these changes
Jun 11, 2026
amitjoshi438
pushed a commit
that referenced
this pull request
Jun 15, 2026
Reconcile the two telemetry designs that diverged after PR #176: - Keep region routing (region-resolver/region-cache/artemis-service): the dispatcher resolves the iKey/collector per org geo + cloud stamp. - Adopt main's per-plugin opt-out (config.json via user-config/telemetry-config + the `telemetry` skill); remove the POWER_PLATFORM_SKILLS_TELEMETRY env kill switch everywhere. Dispatcher now writes the local mirror BEFORE the opt-out gate and fails closed on a missing/unreadable ikey.json. - Adopt main's symlink for the shared lib: plugins/power-pages/scripts/lib/ telemetry/lib -> shared/telemetry/lib (mode 120000); delete sync-to-plugin.js. The pretool hook + emit-spawn now pass the plugin's ikey.json via ikeyJsonPath so the symlinked dispatcher reads the plugin config, not shared/'s placeholder. - Plugin ikey.json + shared ikey.json keep the region-routing structure. - Take main's powerpages-hook-utils test (all skill folders are tracked). - Docs (root + plugin AGENTS.md, READMEs) describe symlink + config.json opt-out + region routing; drop the duplicated/stale sync sections. All shared/telemetry (190) and power-pages plugin (1105) tests pass; alm-lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amitjoshi438
pushed a commit
that referenced
this pull request
Jun 15, 2026
…y the merge A bulk `--ours` resolution during the earlier main→branch merge kept the branch's older (pre-#176-squash) versions of two NON-region files, reverting main's work: - prompt-detector.js: restore bare-form slash-command detection (`/add-seo` as well as `/power-pages:add-seo`). The branch's namespaced-only regex would have reverted main's enhancement and silently dropped telemetry for bare commands. - agent-info.js: restore the full AGENT_DETECTORS framework (Codex/OpenCode/ Hermes/OpenClaw in addition to Claude Code + Copilot). main is a strict superset (it already includes the branch's AI_AGENT-version + backfill fixes); keeping the branch's hand-rolled subset left the code inconsistent with the README that names those agents. Both files + their tests restored from origin/main. Also re-add the dropped emit-spawn `opts.ikeyJsonPath` (no-env-override) regression test — the production-faithful variant guarding the symlinked-dispatcher config path. shared 162/0, plugin 1153/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amitjoshi438
added a commit
that referenced
this pull request
Jun 17, 2026
#190) * feat(telemetry) 1/3: shared library — dispatcher, consent, sync, builders (#142) * docs: add design spec for 1DS telemetry infra Design for a shared telemetry library at shared/telemetry/ consumed by the power-pages plugin first, with interactive first-run consent, strict-allowlist payloads, and fail-closed emission via the existing PreToolUse/PostToolUse:Skill hook surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(telemetry): add shared 1DS telemetry library Introduces the shared 1DS telemetry infrastructure used by all adopting plugins. Anonymous, default-on, env-var opt-out only (POWER_PLATFORM_SKILLS_TELEMETRY=0). No persistent consent file, no first-run prompt. Library (shared/telemetry/lib/): - events.js — type-aware allowlist builders (PowerPagesPluginEvent envelope, top-level columns, per-event field set, durationMs as int) - emit-dispatcher.js — detached child that POSTs to OneCollector with the Common Schema 4.0 envelope, exits silently on any failure - emit-spawn.js — fire-and-forget spawn helper with restricted child env - emit-from-prompt.js — orchestrator for slash-command skill_started events (reads ikey.json, calls pac auth + agent info, builds event) - pac-auth.js — shells out to `pac auth who` for orgId/tenantId - agent-info.js — detects host AI agent (Claude Code, Copilot CLI) + pac CLI version via env override → auto-detect → empty - prompt-detector.js — matches /plugin:skill from user prompt text - local-log.js — placeholder-iKey JSONL fallback at ~/.power-platform-skills/events.jsonl - with-telemetry.js — runInstrumented() wrapper for script-level signals - correlation.js, session.js, scrubber.js — utilities Config: shared/telemetry/ikey.json carries the iKey, collector URL, per-plugin event_stream_name, and a 'disabled' kill switch. The flag ships true so infrastructure can land before tenant-side annotation + Kusto provisioning is complete; a single PR flips it false later. Sync: shared/telemetry/sync-to-plugin.js copies lib/ + ikey.json into <plugin>/scripts/lib/telemetry/. Adopting plugins never hand-edit the synced copy. Tests: full node:test coverage for every module plus the dispatcher and spawn integration (109 tests, all hermetic; envelope-shape probe via POWER_PLATFORM_SKILLS_FAKE_HTTPS). Docs: rebuild design spec (2026-05-04) + plugin adoption guide (2026-04-29) under docs/superpowers/specs/. Older draft spec (2026-04-20) removed. * docs(telemetry): align README with code — camelCase fields, correct PAC posture shared/telemetry/README.md drifted from the rebuild work: - Field names listed in snake_case (plugin_name, session_id, ...) but the code uses camelCase (pluginName, sessionId, ...). Names match Kusto column names directly. - "What is NEVER sent" claimed tenant IDs are not sent — but pac-auth.js explicitly emits orgId and tenantId GUIDs from the active PAC profile. Moved them to "What is sent" with the rest of the PAC + agent fields. - Missing from "What is sent": pluginVersion, osName/osVersion (was os_family), pacCliVersion, aiAgentName, aiAgentVersion, eventInfo, errorDescription. - Added a note about errorDescription being a 500-char-truncated Error.message — callers must keep PII out of exception messages. - Documented the ikey.json "disabled" repo-side kill switch under Privacy posture. * refactor(telemetry): address review feedback (PR #142) Four fixes from PR review: 1. **iKey full form in envelope** — emit-dispatcher.js now sends the complete iKey value in the Common Schema `iKey` field (`"o:" + IKEY`) instead of truncating to just the first hyphen-separated segment. Aligns with the powerplatform-vscode pattern. Multi-cloud key restructuring deferred to a follow-up PR. 2. **Drop POWER_PLATFORM_SKILLS_BYPASS_KILL_SWITCH** — production code no longer carries a test-only escape hatch. Tests now override the ikey.json path via POWER_PLATFORM_SKILLS_IKEY_JSON (parallels the existing IKEY/COLLECTOR/CONFIG_DIR/FAKE_HTTPS env-var pattern) and supply their own `disabled: false` config when they need to exercise emission paths. emit-spawn.js forwards the new var to the detached child. 3. **Defense-in-depth FIELD_TYPES enforcement in dispatcher** — events.js now exports `pick`. The dispatcher imports both `FIELD_TYPES` and `pick`, then re-runs the allowlist filter on `event.data` before building the envelope. Any field that bypasses the builders is dropped before it reaches the wire. The three reserved meta fields (eventName, eventType, severity) survive via an explicit allow list. 4. **errorDescription emits err.code only, never err.message** — with-telemetry.js no longer reads `err.message` (which can contain file paths, GUIDs, tokens). The field now captures `err.code` — short non-PII metadata like `ENOENT` or `ERR_INVALID_ARG_TYPE`. Test "errorDescription truncated to 500 chars" replaced with one that asserts a PII-bearing message is NOT emitted anywhere when `err.code` is set. README updated accordingly. New dispatcher test: `dispatcher strips unknown fields from event.data` hand-crafts an event with filePath/stackTrace/rawPrompt/tokenValue and asserts none survive the FIELD_TYPES filter. Tests: 110/110. * refactor(telemetry): rename ikey.json key — ikey → instrumentationKey Per PR #142 comment 1: the JSON property is now spelled in its full form (`instrumentationKey`) to match the powerplatform-vscode constants pattern instead of the abbreviated `ikey`. The envelope `iKey` Common Schema field stays at the standard tenant-routing short form (`o:<32-char-tenant>`); the previous attempt to put the full hyphenated key in the envelope is reverted because the full form already lives in the `x-apikey` header. - shared/telemetry/ikey.json: `"ikey"` → `"instrumentationKey"`. - shared/telemetry/lib/emit-from-prompt.js: read `cfg.instrumentationKey` (other JSON keys — `collector_url`, `event_stream_name`, `disabled` — unchanged). - shared/telemetry/lib/emit-dispatcher.js: envelope reverted to `iKey: "o:" + IKEY.split("-")[0]`. - Test fixtures updated across emit-dispatcher / emit-spawn / emit-from-prompt tests. Multi-cloud iKey restructuring (the second half of PR #142 comment 1) remains deferred to a follow-up PR. Tests: 110/110. --------- Co-authored-by: Amit Joshi <amitjoshi@microsoft.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(power-pages): adopt 1DS telemetry — synced library, hooks, valid… (#153) * feat(power-pages): adopt 1DS telemetry — synced library, hooks, validators Wires the shared 1DS telemetry library into the power-pages plugin. Synced copy of the library lives under scripts/lib/telemetry/ and runs at user time. PreToolUse:Skill emits skill_started; PostToolUse:Skill emits skill_completed wrapped around the existing validator chain. Plugin-side additions: - scripts/lib/telemetry/ — synced from shared/telemetry/ via sync-to-plugin.js (12 lib modules + ikey.json carrying the PowerPagesPluginEvent stream name + disabled:true kill switch). - scripts/lib/telemetry-runner.js — runInstrumented() shim plus the PowerPagesPluginEvent envelope name and tracked-script registry. - hooks/run-skill-pretool-telemetry.js, hooks/run-skill-posttool-validation.js — fire-and-forget telemetry around the Skill tool, preserving the existing validator's behavior and exit code. - hooks/hooks.json — registers the two Skill hooks. (UserPromptSubmit is registered separately by the slash-command branch.) - Validators (validate-activation/seo/site/webroles/audit/auth/datamodel/ cloudflow/serverlogic/webapi-integration) and four other plugin scripts (check-activation-status, clear-site-cache, render-audit-report, verify-dataverse-access) wrapped with runInstrumented() so each script run produces script_started/script_completed signals. - scripts/tests/{telemetry-hook-pretool,telemetry-hook-posttool,telemetry-runner}.test.js — hermetic node:test coverage for the new wiring. Repo updates: - AGENTS.md (plugin) gains the Telemetry section pointing at shared/telemetry/ as the canonical source of truth. - README.md (root) mentions anonymous telemetry + env opt-out. - docs/superpowers/handoff/PowerPagesPluginEvent-annotation.xml is the tenant-team handoff describing the EventStreamingAnnotation for the per-plugin Kusto stream. Privacy posture: default-on, env-var opt-out only (POWER_PLATFORM_SKILLS_TELEMETRY=0). No first-run prompt, no consent file. Telemetry stays gated by ikey.json's disabled:true flag until the tenant-side annotation + Kusto table are provisioned. * fix(power-pages): telemetry-runner — pass envelopeName, read instrumentationKey Two regressions caught during E2E verification of the local-log path: 1. **envelopeName was never forwarded** — `runInstrumented()` called `withTelemetry()` without an `envelopeName`, so events shipped with `name: ""`. In production this means the tenant's `EventStreamingAnnotation name="^PowerPagesPluginEvent$"` matcher silently drops every event (`acc:1` from the collector but zero rows in Kusto). Now reads `event_stream_name` from `ikey.json` and passes it through. 2. **iKey read the old JSON property** — `deps.ikeyCfg.ikey` was undefined after the `ikey` → `instrumentationKey` rename in the foundation library (PR #142). The dispatcher saw an empty `POWER_PLATFORM_SKILLS_IKEY` and always took the placeholder / local-log path, even with a real iKey provisioned. Now reads `deps.ikeyCfg.instrumentationKey`. Adds an opt-in `_overrides` test seam on `runInstrumented()` so unit tests can inject a fake `withTelemetry` and assert opts are propagated correctly. New tests: - `runInstrumented forwards envelopeName from ikey.json event_stream_name` - `runInstrumented reads iKey from instrumentationKey property (not legacy 'ikey')` Verified end-to-end by piping a synthetic event directly to the dispatcher: envelope `name` is now `"PowerPagesPluginEvent"`, `scriptName` is correctly set, event lands in the local JSONL. Tests: 4/4 telemetry-runner, full plugin suite still green. * telemetry: detect GitHub Copilot CLI as ai agent host readAiAgent now recognizes COPILOT_CLI=1 and reads COPILOT_CLI_BINARY_VERSION, in addition to Claude Code. Priority remains: explicit AI_AGENT_NAME env > Claude Code > Copilot CLI. Adds test coverage and re-syncs into plugins/power-pages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(telemetry): drop power-pages testing iKey from shared/telemetry/ikey.json `shared/telemetry/ikey.json` is the dev-time placeholder copied by `sync-to-plugin.js`. It was committed with power-pages's real testing iKey and INT collector URL, which meant any future adopter (canvas-apps, code-apps, model-apps) running the sync would inherit those values and — if they ever flipped `disabled: false` without replacing the iKey — silently route their telemetry to power-pages's Kusto stream. This commit replaces both with explicit placeholders so every plugin is forced to provision its own cluster config before going live. The plugin's own synced copy at `plugins/power-pages/scripts/lib/telemetry/ikey.json` keeps its real plugin-specific values; only the shared template is sanitized. - instrumentationKey: <pp's value> → "PLACEHOLDER_REPLACE_BEFORE_SHIPPING" - collector_url: <INT URL> → "" `event_stream_name` ("PluginEventStreamPlaceholder") and `disabled: true` were already plugin-agnostic and stay unchanged. Tests: 110/110 shared (no behavior change — the placeholder iKey constant matches the dispatcher's existing keyMissing check). * fix(power-pages): address PR #153 review — await, dedup, hook fast-path Three review fixes from priyanshu92 on PR #153: 1. **telemetry-runner.js: `return await asyncFn()`** — added the `await` on the no-telemetry-deps fallback path so stack traces include the `runInstrumented` frame. Behavioural no-op; debug-friendliness only. 2. **validate-auth.js: drop duplicate `runInstrumented` call** — the file invoked `runInstrumented('validate-setup-auth', main)` twice: once unconditionally at module top-level (line 69-72), and again inside the standard `if (require.main === module)` guard (line 137-142). When executed as a script (the actual PostToolUse-hook use case), `main()` ran twice — duplicating validator I/O and emitting two `script_started`/`script_completed` pairs per run. When required as a module (tests), the top-level call still fired, breaking import semantics. Deleted the top-level invocation, kept the require.main guard. 3. **Skill hooks: fast-path opt-out / kill switch BEFORE pac shell-outs** — `run-skill-pretool-telemetry.js` and `run-skill-posttool-validation.js` ran `pac auth who` (3s timeout) and `pac --version` (2s timeout) unconditionally on every tracked Skill invocation, even when the user had opted out (`POWER_PLATFORM_SKILLS_TELEMETRY=0`) or the shipped kill switch was on (`ikey.json` `disabled: true`). Worst case ~10s added latency per Skill tool use, currently hit by every user since `disabled: true` is the shipped default. Both hooks now check, in this order, BEFORE any pac shell-out: a) env opt-out (`POWER_PLATFORM_SKILLS_TELEMETRY === "0"`) b) repo-side kill switch (`ikey.json` `disabled === true`) c) unconfigured (`!ikey.instrumentationKey`) Any of those → exit early. The posttool hook still preserves the validator's exit code regardless of telemetry state. `readIkey()` in the pretool hook now also exposes the `disabled` flag so the gate can read it without re-parsing. Comment #3 (extend agent-info detection to Codex / OpenCode) is pending reviewer confirmation of those hosts' env-var conventions — not implemented here. Any host can already self-identify via the AI_AGENT_NAME / AI_AGENT_VERSION override. Tests: 10/10 plugin telemetry hooks + runner. Shared suite unchanged. --------- Co-authored-by: Amit Joshi <amitjoshi@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(power-pages): emit skill_started for slash-command invocations (#168) Adds telemetry for skills invoked via slash command (e.g. /power-pages:add-seo) at the user-prompt boundary, which the existing PreToolUse:Skill hook does not cover (that fires only for programmatic Skill tool calls). - hooks/run-user-prompt-telemetry.js: UserPromptSubmit hook that matches /<plugin>:<skill> via shared/telemetry/lib/prompt-detector.js and calls emitSkillStartedFromPrompt() with PAC auth + agent info enrichment. Fire-and-forget; never blocks prompt submission. - hooks/hooks.json: registers the new UserPromptSubmit hook alongside the existing PreToolUse / PostToolUse:Skill hooks. - scripts/tests/run-user-prompt-telemetry.test.js: hermetic coverage for the hook (tracked-skill match, no-match no-op, malformed input, env opt-out). - docs/superpowers/specs/2026-04-23-slash-command-telemetry-design.md: design doc covering why this hook exists and how it composes with the Skill-tool path. skill_completed remains the responsibility of the PostToolUse:Skill hook for skills that flow through the Skill tool. For slash-only skills, completion is tracked indirectly via script-level runInstrumented() spans. Co-authored-by: Amit Joshi <amitjoshi@microsoft.com> * docs(telemetry): design spec for region-aware telemetry routing Spec for resolving (iKey, collector URL) per user's tenant geography in the Power Pages plugin. Mirrors VSCode's pattern (Artemis service for geo discovery, static geo→region map) without pulling in the 1DS SDK. Key design choices: - One Artemis HTTPS GET (not 7-way fan-out) — we already know the sovereign cloud from `pac auth who`'s Cloud: field. - Disk cache keyed by orgId, 24h TTL — org-switching is naturally handled (different key), only true geo-migration causes a one-day staleness window. - Region resolution happens in the detached dispatcher child, NOT in the hook. User's prompt latency is unchanged. - PAC shellouts stay in the hook for now (deferred to future work). - Every failure path falls back to default_region; nothing throws. 3 new shared modules: artemis-service, region-resolver, region-cache. ikey.json restructures from flat (iKey, url) into regions map + default. Saved to docs/superpowers/specs/2026-05-27-region-routing-design.md. * docs(telemetry): implementation plan for region-aware telemetry routing 14 bite-sized tasks (TDD throughout). Order: 1. Extend pac-auth.js to parse Cloud line 2-4. New shared modules: region-cache, artemis-service, region-resolver 5-7. Extend emit-spawn / emit-dispatcher / with-telemetry to forward cloud 8. Restructure plugin's ikey.json into regions map 9-10. Update hooks + telemetry-runner to read cloud and pass through 11. Sync shared → plugin and run full suites 12. Update shared template ikey.json 13. README refresh 14. Push + manual verification + open PR Spec: docs/superpowers/specs/2026-05-27-region-routing-design.md * feat(telemetry): parse Cloud field from pac auth who * feat(telemetry): add region-cache disk cache (24h TTL, keyed by orgId) * test(telemetry): add guard-clause coverage for region-cache read/write * feat(telemetry): add artemis-service for one-call geo discovery * test(telemetry): add falsy-orgId guard coverage for artemis fetchGeo * feat(telemetry): add region-resolver (cache → Artemis → regions map) * feat(telemetry): forward POWER_PLATFORM_SKILLS_CLOUD to dispatcher * feat(telemetry): dispatcher resolves region from ikey.json + Artemis * docs(telemetry): restore intent comments in dispatcher * feat(telemetry): with-telemetry threads cloud through to spawn opts * feat(power-pages): restructure ikey.json into per-region map (kill switch on) * feat(power-pages): hooks read regions structure and pass cloud to dispatcher * feat(power-pages): telemetry-runner passes cloud through withTelemetry * chore(power-pages): sync region-routing changes into plugin copy * feat(telemetry): shared ikey.json template uses regions structure * docs(telemetry): README — describe regions map and Artemis-based resolution * docs(telemetry): remove implementation specs + handoff XML The 1DS telemetry foundation has landed and is in active use. The design specs (slash-command-telemetry-design, telemetry-rebuild-design, plugin adoption guide) and the PowerPagesPluginEvent annotation XML were implementation-time artifacts — the live integration is now documented in shared/telemetry/README.md, which covers what is sent, the privacy posture, and how to adopt the library in a new plugin. Updates plugins/power-pages/AGENTS.md to point readers at the README instead of the deleted spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(telemetry): honor disabled flag before PAC + spawn in user-prompt + script paths The pretool and posttool hooks already short-circuit on cfg.disabled before invoking pac auth who (~3s) and pac --version (~2s). The two remaining entry points — emit-from-prompt (used by the UserPromptSubmit hook) and telemetry-runner (used by scripts wrapped in runInstrumented) — were calling PAC and spawning the detached dispatcher regardless; the dispatcher caught disabled later but the user had already paid the shellout cost. Add three fast-path gates at both entry points, in this order: - POWER_PLATFORM_SKILLS_TELEMETRY=0 (env opt-out) - cfg.disabled === true (repo-side kill switch) - empty instrumentationKey (unprovisioned) Any of these returns immediately and runs the user's original async fn (telemetry-runner) or a no-op result (emit-from-prompt). Zero PAC, zero agent-info, zero spawn. Also fix a pre-existing destructuring bug in emit-from-prompt.test.js's mkTelemetryDir helper: callers passed 'instrumentationKey' but the helper destructured 'ikey', so every test ran with an empty iKey field by accident. Fix the helper and add test coverage for all three new fast paths in both modules. Synced shared/telemetry/lib/emit-from-prompt.js into the power-pages plugin copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): rewrite README as integration guide for new adopters Reorganize around the questions a plugin author actually asks: - What does the library do? (data flow diagram) - What goes on the wire? (fields + privacy posture) - Where do the modules live? (lib/ layout) - How do I adopt this in my plugin? (5 numbered steps) - How do I update the shared lib without clobbering my ikey.json? - What test seams exist? Drop the dangling reference to the deleted design spec. The new "Adopting in a new plugin" section walks through sync, ikey.json config, hook registration, optional script wrapping, and local verification — covering everything a new plugin needs without requiring the implementation history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(telemetry): make PAC shellouts actually capture data on Windows Two real bugs causing every emitted event to silently drop orgId, tenantId, and pacCliVersion: 1) pac-auth.js TIMEOUT_MS was 3000, but `pac auth who` cold-start on Windows consistently takes 3.5-4s (.NET runtime + cached-token validation). Every call timed out, the catch handler swallowed it, readPacAuth returned null, and the hooks emitted events without the PAC identity fields. Bumped to 8000 — still well under the 30s hook budget. 2) agent-info.js readPacCliVersion ran `pac --version`, but PAC 2.x treats `--version` as an unknown command: it prints the version banner ("Version: 2.7.4+g06bb2eb ...") to stdout but exits with status 1. execFileSync throws on non-zero exit, the old catch discarded everything, and the version was lost. Now we read err.stdout from the thrown error and parse the banner. Tightened the regex from a bare semver match to one anchored on the "Version:" prefix so we don't accidentally pick up the .NET Framework version from the same line. Bumped its timeout to 8s too. Verified on this machine: cold-start total of both reads is ~5.7s, returning real orgId/tenantId/cloud and pacCliVersion "2.7.4". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(telemetry): stabilize sessionId across hook invocations in a Claude Code session session.getSessionId() cached a random UUID in module-level state, but each hook spawns as a fresh Node process — the cache reset every invocation, so every event in a Claude Code session carried a different sessionId. That broke session-scoped joins in Kusto. Claude Code sends session_id in the hook stdin payload. Wire it through: - session.js: getSessionId(override?) — if a truthy string is passed, prime the cache with it. Subsequent calls (with or without override) return the primed value. _resetCache() exported for tests. - pretool / posttool / user-prompt hooks: extract parsed.session_id from the hook payload and pass it to getSessionId / through emitSkillStartedFromPrompt opts. - emit-from-prompt.js: accept opts.sessionId and forward to getSessionId. Synced into the plugin copy. New session tests cover override winning over a previously-cached UUID, multi-process stability when callers pass the same primed value, and falsy-override safety. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): design spec for skill-only events + session/correlation cleanup Records the audit findings and approved design for collapsing the telemetry event vocabulary to skill_started + skill_completed only. Removes the script-wrapping path (with-telemetry, telemetry-runner, runInstrumented) and adds a TTL sweep to correlation.js to bound leaks when PostToolUse never fires. Ratifies the just-shipped sessionId fix that sources Claude Code's session_id from hook stdin. Finding E (missing skill_completed in local trace) deferred to a separate investigation after this cleanup lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): make sessionId sourcing host-agnostic (Claude Code + Copilot CLI) Per review feedback: sessionId should come from whichever host agent the user is running under. Add extractSessionIdFromPayload helper that checks both session_id (Claude Code convention) and sessionId (camelCase fallback for hosts that use that). aiAgentName already detects both hosts via agent-info.js; this brings sessionId into the same multi-host pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): resolve session_id from COPILOT_SESSION_ID env for Copilot CLI Copilot CLI exposes its session ID via the env var COPILOT_SESSION_ID, not through the hook stdin payload. Rename the helper to resolveHostSessionId and have it consult, in precedence order: payload.session_id, payload.sessionId, env.COPILOT_SESSION_ID, empty. Each hook stays a single line; new hosts can be added by extending the resolver without touching hook code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): drop COPILOT_SESSION_ID env path; both hosts use payload Per user feedback: GitHub Copilot CLI uses the same hook stdin payload mechanism as Claude Code to surface its session id. Drop the COPILOT_SESSION_ID env var fallback; resolveHostSessionId now only inspects payload.session_id and payload.sessionId. Simpler resolver, one fewer test, no env coupling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): implementation plan for skill-only events + session/correlation cleanup Bite-sized TDD plan covering: resolveHostSessionId helper, correlation TTL sweep, removal of buildScript builders, deletion of with-telemetry + telemetry-runner, unwrap runInstrumented from 10 validators + 4 standalone scripts, switch 3 hooks to resolveHostSessionId, sync, README update, manual verification, push. 12 tasks total. Spec reference: docs/superpowers/specs/2026-05-28-session-correlation-cleanup-design.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(telemetry): add resolveHostSessionId helper for multi-host session sourcing * feat(telemetry): TTL-sweep stale correlation files in correlation.write * feat(telemetry): drop buildScript builders + scriptName field * feat(telemetry): delete with-telemetry script wrapper * feat(power-pages): delete telemetry-runner plugin wrapper * feat(power-pages): unwrap runInstrumented from validators * feat(power-pages): unwrap runInstrumented from standalone scripts * feat(power-pages): hooks use resolveHostSessionId for multi-host session sourcing * chore(power-pages): sync session/correlation cleanup into plugin copy * docs(telemetry): drop script-wrapper section from README; align with skill-only events * fix(power-pages): hoist input parse so posttool emits skill_completed The const input was scoped to the first try block while the telemetry block (second try) also references it for resolveHostSessionId. The ReferenceError was silently swallowed by the outer telemetry catch, preventing all skill_completed events from emitting. This is the root cause of Finding E (9 skill_started, 0 skill_completed). Hoist to outer async scope so both blocks see the parsed payload. * fix(telemetry): backfill aiAgentVersion from CLAUDECODE when only AI_AGENT_NAME is set If the host sets AI_AGENT_NAME (e.g., via .claude/settings.local.json) but omits AI_AGENT_VERSION, the previous code returned aiAgentVersion="" and the hook silently dropped the field. Now: when AI_AGENT_NAME is explicit but the version is empty, fall through to the same built-in detection used when AI_AGENT_NAME isn't set at all — read the version from CLAUDE_CODE_EXECPATH/../package.json for Claude Code, or COPILOT_CLI_BINARY_VERSION for Copilot CLI. Explicit AI_AGENT_VERSION still wins over the backfill when present. Extracted readClaudeCodeVersion(env) helper to share the package.json read path between the explicit-name and built-in detection branches. Synced into the plugin copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(telemetry): correct aiAgent detection description in README Reflect actual agent-info logic: Claude Code version is read from the installed package.json via CLAUDE_CODE_EXECPATH, Copilot CLI version from COPILOT_CLI_BINARY_VERSION, and an explicit AI_AGENT_NAME backfills its version from the matching built-in detector when AI_AGENT_VERSION is empty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): remove session-correlation cleanup design + plan The session/correlation cleanup work has shipped; drop the superpowers design spec and implementation plan that tracked it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * revert(power-pages): restore main's validators + standalone scripts These 10 validators and 4 standalone scripts diverged from main only by the leftover runInstrumented-unwrap shape (async main() wrapper + require.main guard + module.exports). That structure is a vestige of an earlier per-script telemetry approach that was dropped in favor of hook-based skill telemetry, so it's no longer needed. Restore main's versions to keep the telemetry PR scoped to telemetry-only changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): derive Claude Code version from AI_AGENT on non-npm installs aiAgentVersion was derived solely from CLAUDE_CODE_EXECPATH -> ../package.json, which only exists in the npm-global install layout. On native-installer Claude Code (standalone binary, now the default), that read fails and the version is dropped while aiAgentName still logs as "Claude Code" -- name present, version blank. Add an install-method-independent fallback: when the package.json read yields nothing, parse the dotted semver out of AI_AGENT (claude-code_<maj>-<min>-<patch>_agent), which Claude Code sets regardless of install method. The npm path stays primary, so npm installs are unaffected. Edited shared/telemetry and re-synced to plugins/power-pages. Adds test coverage for native-install fallback, EXECPATH-unset fallback, npm-wins precedence, and malformed AI_AGENT. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): address Copilot review on PR #176 - emit-dispatcher: pass url.port to https.request so a non-443 collector URL POSTs to the configured port instead of always 443 - events: tighten isPlainStructured() to only accept arrays and plain objects (prototype Object.prototype/null), rejecting class instances like Error from the dynamic eventInfo field - emit-from-prompt + posttool hook: add POWER_PLATFORM_SKILLS_IKEY_JSON override seam so tests point at a temp ikey.json instead of mutating the checked-in config (removes a parallel-test race) - posttool hook: skip telemetry emission when no tracked skill was resolved (malformed stdin / pre-detection error) so no incomplete skill_completed event is emitted; exit code unchanged - run-user-prompt-telemetry test: replace busy-wait loops with a non-spinning Atomics.wait sleep + waitForFile helper, and use the ikey override instead of mutating the shipped ikey.json Shared lib edits propagated to the power-pages synced copy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): migrate emit-from-prompt to regions structure The user-prompt skill-detection path still read the legacy flat ikey.json shape: readIkey() returned only { eventStreamName, disabled }, so the destructured `ikey` was always undefined and the `if (!ikey)` gate made emitSkillStartedFromPrompt always return { emitted: false }. Read default_region + regions[default_region].instrumentation_key instead and gate on defaultInstrumentationKey, matching the hooks and dispatcher. Update the test helper to the regions shape and correct one copy-paste assertion (skillName was asserted null for a detected slash command). Fixes 9 shared + 1 plugin test. Full suites now green: 184/184 shared, 1040/1040 plugin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(power-pages): provision real iKeys for gov/high/dod/mooncake regions Replace the four sovereign-cloud PLACEHOLDER_REPLACE_BEFORE_SHIPPING instrumentation keys with the tenant-team-provisioned values. internal/us/eu keys and all collector URLs were already correct. Ships disabled:true — a separate PR flips the kill switch once all six regions are verified end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(power-pages): fire skill_started telemetry hook in Copilot CLI Copilot CLI's skill-invocation tool is named `skill` (lowercase) while Claude Code's is `Skill`. Copilot applies the PreToolUse matcher as an anchored, case-sensitive regex against tool_name, so matcher `Skill` never matched and the pretool hook (which emits skill_started) never fired. PostToolUse ignores the matcher, so skill_completed still worked. Broaden both matchers to `Skill|skill` so the hooks fire on both surfaces, and add a regression test asserting the matcher matches both tool names. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(telemetry): design for removing skill_completed logging flow Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(telemetry): stop emitting skill_completed from posttool hook Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(telemetry): inline correlationId in pretool hook, drop disk correlation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(telemetry): delete unused correlation.js and its test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(telemetry): remove orphaned synced correlation.js Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): document removal of skill_completed flow Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): describe only live telemetry; drop skill_completed references Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: remove brainstorm/plan docs for skill_completed removal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): route GCC (UsGov) to the gov region normalizeCloud did not recognize "usgov" — the token `pac auth who` actually emits for GCC (per validation-helpers.js CLOUD_TO_API). GCC tenants fell through to Public, so both the Artemis geo probe and the final 1DS collector/iKey were routed to the public US endpoint — a sovereign-cloud data-residency violation. Add "usgov" to the Gov branch of normalizeCloud. Exact === matching means it cannot shadow usgovhigh/usgovdod. Add tests pinning the real PAC token on both layers (urlFor + mapToRegion); the prior tests only exercised the synthetic "Gov" string, which is why this slipped through. Re-synced into the power-pages plugin copy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): spec — decouple region routing from shared via pluggable resolver Design for moving artemis/region code out of shared/telemetry into the power-pages plugin behind a generic resolver contract (resolve/isProvisioned), discovered by convention next to ikey.json. Shared keeps a static-key fallback; resolution stays in the background dispatcher. Awaiting review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): drop resolver-path env var from spec — convention-only discovery The POWER_PLATFORM_SKILLS_RESOLVER override was a redundant test seam: tests already create a temp ikey.json and can drop a stub resolver.js beside it, so convention discovery covers it. One discovery mechanism, less adopter surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): lock in option (i) fast-gate + note dedup follow-up §6: keep the full pac-shellout fast-gate, made generic via the resolver's isProvisioned contract (no shared file reads the region shape). Record the decision in §13 and add an out-of-scope §14 to de-dupe the two emit entry points in a later cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): implementation plan for resolver decoupling Task-by-task TDD plan: resolver-loader, switchover (move region into plugin + adapter + generic dispatcher), generic isProvisioned fast-gate, docs/verify. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(telemetry): add generic resolver-loader (convention discovery) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(telemetry): move region routing into power-pages behind resolver contract Region trio relocates to plugins/power-pages/.../telemetry/region/; power-pages ships resolver.js adapting it to the shared dispatcher's resolve()/isProvisioned() contract. Shared dispatcher resolves via convention-discovered resolver.js with a static-key fallback and no longer knows about regions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(telemetry): generic provisioning fast-gate via resolver.isProvisioned emit-from-prompt (shared) and the pretool hook no longer read the region shape; they call the plugin resolver's isProvisioned, defaulting to a static-key check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): document the resolver contract + onboarding tiers Region routing is now a power-pages-owned resolver.js; shared ships the contract + a static-key fallback. README, AGENTS updated; memory note refreshed separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): user-prompt path forwards ikeyJsonPath so the dispatcher reads the plugin config emit-from-prompt now passes ikeyJsonPath into fireAndForget, mirroring the pretool hook. Without it the detached dispatcher fell back to shared/'s placeholder ikey.json (disabled) and skipped resolver discovery + emission for the user-prompt skill_started. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(telemetry): set power-pages event_stream_name to PagesAIPluginEvent Single-line ikey.json change; disabled stays true (hard-off preserved). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(telemetry): fix stale dispatcher comment (region resolution → resolver context) Comment-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: remove superpowers spec/plan process docs from the branch These brainstorming/implementation artifacts are dev-time only and should not ship to main. The product docs (README, AGENTS) are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): restore main's prompt-detector + agent-info dropped by the merge A bulk `--ours` resolution during the earlier main→branch merge kept the branch's older (pre-#176-squash) versions of two NON-region files, reverting main's work: - prompt-detector.js: restore bare-form slash-command detection (`/add-seo` as well as `/power-pages:add-seo`). The branch's namespaced-only regex would have reverted main's enhancement and silently dropped telemetry for bare commands. - agent-info.js: restore the full AGENT_DETECTORS framework (Codex/OpenCode/ Hermes/OpenClaw in addition to Claude Code + Copilot). main is a strict superset (it already includes the branch's AI_AGENT-version + backfill fixes); keeping the branch's hand-rolled subset left the code inconsistent with the README that names those agents. Both files + their tests restored from origin/main. Also re-add the dropped emit-spawn `opts.ikeyJsonPath` (no-env-override) regression test — the production-faithful variant guarding the symlinked-dispatcher config path. shared 162/0, plugin 1153/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): harden resolver/config handling to fail closed Address PR #190 review: defensive fixes so a caller bug or a throwing plugin resolver can never break prompt handling, the pretool hook, or the dispatcher. - emit-from-prompt readIkey(): guard a missing/non-string telemetryDir before path.join (the throw was outside the try); wrap resolver.isProvisioned() so a throw → not provisioned. - pretool hook: restore the POWER_PLATFORM_SKILLS_IKEY_JSON override seam (the only readIkey that had dropped it), forward the resolved ikeyPath to the dispatcher, and wrap resolver.isProvisioned() in try/catch. - dispatcher: wrap await resolver.resolve() so a throw/reject degrades to "no resolution" (local mirror already written, POST skipped) instead of exiting via the global unhandledRejection seam. - pretool test: use a temp ikey.json via the override seam instead of mutating the checked-in config (race-prone under parallel runs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): honor resolver→static precedence, abs resolver path, restore test budget Round 2 of PR #190 review: - emit-dispatcher: the static instrumentationKey/collector_url fallback now runs whether or not a resolver is present. A resolver that returns null/undefined (or threw) previously skipped the static branch entirely, contradicting the documented resolver → static → none precedence and silently disabling transmission even when a static key was configured. Added a dispatcher test covering resolver-resolves-to-nothing → static POST. - resolver-loader: require the resolver via path.resolve (absolute) instead of path.join, so a relative ikey-dir (e.g. a relative IKEY_JSON override) isn't misread by require() as a node_modules module ID. - run-user-prompt-telemetry test: restore the 30s spawn timeout + comment that a merge had reverted to 10s. The enabled path makes two 8s pac shellouts (16s worst case); 10s flakes when pac is installed and cold-starting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): restore fail-closed (disabled:true) on unreadable ikey.json Merge-drop sweep of PR #190 vs main: main's emit-from-prompt readIkey() returned `disabled: true` when ikey.json was missing/unreadable ("if we can't read the config we cannot confirm emission is authorized, so suppress"). The branch had softened both readIkey() catches to `disabled: false`, relying solely on the downstream provisioned-gate to suppress. Restore the explicit hard-off so all three layers (emit-from-prompt, pretool hook, dispatcher's isDisabledByConfig) agree: unreadable config = disabled. Also flips the round-1 missing-telemetryDir guard to disabled:true for the same reason. Behavior is unchanged for readable configs; only makes the missing/unreadable path explicitly fail-closed rather than incidentally so. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(telemetry): drop dead telemetry.json setup from user-prompt test helper PR #190 review: mkConfigDir wrote a telemetry.json ({version, enabled, recorded_at}) and took an `enabled` arg that nothing reads — the per-plugin opt-out is config.json -> telemetry[plugin] (see user-config.js), and telemetry.json appears nowhere in the telemetry code. Removed the dead write so the helper no longer implies telemetry.json gates emission; matches the sibling telemetry-hook-pretool test's mkConfigDir. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): make region-cache writes atomic to survive concurrent dispatchers PR #190 review (priyanshu92): region-cache write() did a non-atomic read-modify-writeFileSync of the shared region-cache.json. Each tracked-skill invocation spawns its own detached dispatcher, so concurrent writers could produce torn/half-written reads (a cache miss for ALL orgs) and lose entries. Write to a per-process temp file (pid + seq, so concurrent writers don't collide) then fs.renameSync over the target — an atomic replace (incl. Windows via MoveFileEx), so a reader always sees a complete old-or-new file. Best-effort temp cleanup on failure. The cross-org last-writer-wins residual is left as-is: rare (two parallel sessions, different orgs, same instant) and self-heals on the next resolve, so a file lock isn't warranted for a best-effort 24h cache. Added a test asserting the write leaves no .tmp litter and yields a complete file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): cache org→region only, per-org files — race-free and plugin-safe PR #190 review (priyanshu92): the region cache shared one machine-global file keyed by orgId but storing plugin-specific iKey/collector. Two problems beyond the torn-write already fixed in df0ea69: - Cross-org lost-update: shared read-modify-write let concurrent detached dispatchers clobber each other's entries, evicting orgs before the 24h TTL. - Cross-plugin misrouting (latent): the next plugin to adopt region routing would read power-pages' cached iKey for the same org and POST to the wrong plugin's collector. Root cause: the cache conflated plugin-independent data (org→region) with plugin-dependent data (region→iKey). Fix: - region-cache: store ONLY { region, expiresAt }, one file per org (region-cache/<orgId>.json), atomic temp+rename per file. Per-org files remove the shared RMW entirely (no cross-org lost-update); orgId is GUID-validated before use as a filename (path-traversal guard + falsy guard). - region-resolver: map the cached region → this plugin's iKey via its own regionsMap on every hit (closes the misrouting; makes the cache safely SHARED across plugins). Add deriveRegion() returning null for unrecognized geos so a per-plugin defaultRegion fallback is never cached. mapToRegion kept as a thin wrapper. Old region-cache.json is orphaned (it's a cache; no migration). Design doc: docs/superpowers/specs/2026-06-16-region-cache-redesign-design.md. 100 telemetry tests pass (region-cache/resolver tests rewritten for the new layout + shape). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: remove region-cache redesign spec doc The rationale lives in the commit history (51a213d) and code comments; the superpowers spec doc isn't needed in the repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Amit Joshi <amitjoshi@microsoft.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds 1DS skill-run telemetry to the repo, with
power-pagesas thefirst adopter. Telemetry is default-on and opt-out via
POWER_PLATFORM_SKILLS_TELEMETRY=0, with a repo-sidedisabledkill switch inikey.jsonthat shipstrueuntil the tenant-side Kusto stream is provisioned.What's included
shared/telemetry/) — zero-dependency, Node stdlib only.Strict
FIELD_TYPESallowlist (events.js), detached fire-and-forgetdispatcher (
emit-spawn.js/emit-dispatcher.js), CS4.0 envelope, PAC-authand a local dev-log fallback when no real iKey is configured.
scripts/lib/telemetry/, threehooks (
PreToolUse/PostToolUse/UserPromptSubmit) emittingskill_started. The PostToolUse hook keeps running eachskill's validator; telemetry is layered on, fail-closed, and never changes
exit codes.
(injectable seams, no real network / PAC shellouts).
shared/telemetry/README.mdintegration guide, plus telemetrysections in
AGENTS.md,README.md, andplugins/power-pages/AGENTS.md.Scope
The diff is telemetry-only (48 files, +4367/−22). Validators and standalone
scripts are unchanged from
main— the branch carries no incidental refactors.Privacy posture
Anonymous only. No file paths, cwd, env vars, site names, Dataverse URLs, stack
traces,
err.message, skill args, prompt text, usernames, or hostnames.errorClassis the constructor name;errorDescriptioniserr.codeonly. Adefense-in-depth allowlist filter runs in the dispatcher before serialization.