Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/DEBUG.md

Raw
Rendered preview

Extension debug contract

Every repository extension binds its own committed src/pi-ext-debug.ts through a handwritten src/debug.ts and remains usable when installed without the rest of pi-ext. For repository extensions, scripts/codegen/templates/pi-ext-debug.ts and scripts/codegen/targets.json generate these local copies through mise run //:codegen. Root and extension check tasks compare generated files byte-for-byte without rewriting them. No repository codegen runs at package installation or Pi startup, and no extension imports another extension or shared runtime code. Event names, span placement, and event-specific classifications remain handwritten in each extension.

Extension-owned schema

Every extension declares its debug schema in handwritten src/debug.ts; runtime callers import the needed dbg, span, and closeDebug bindings from that file, never directly from the generated module. Repository static checks require a schema-bound dbg and closeDebug export plus a runtime import of the handwritten module. For example:

import { defineDebug } from "./pi-ext-debug.ts";

export const { dbg, span, closeDebug } = defineDebug({
  events: ["session.start", "session.shutdown"],
  spans: ["request"],
  fields: {
    retryCount: "number",
    hasToken: "boolean",
    outcome: ["ok", "timeout"],
  },
} as const);

Declare literal events and span bases, plus only approved numeric measurements, booleans, and finite string labels. For spans, request allows request.start, request.finish, and request.error; the writer owns spanId and durationMs. Undeclared events and fields, invalid enum labels, raw objects, and unsafe numbers are omitted at runtime rather than written. Schema types catch ordinary mistakes at compile time; they do not replace the runtime checks. Do not pass credentials, prompts, or request bodies to the logger, even for redaction; record a presence boolean such as hasToken instead. All repository extensions use schema-bound runtime logging; the generated direct exports remain for low-level tests until removed. Do not edit generated files to add fields or bypass defineDebug in runtime code. The schema may declare more than 24 fields, but each record retains at most 24 approved metadata fields. File logging remains off unless enabled by the user or environment.

Selection

Only user-level Pi settings are consulted; project settings cannot enable debugging. In ~/.pi/agent/settings.json, "pi-ext": { "debug": true } enables every extension, a string enables one extension by directory name, and an array of strings enables the named extensions. Absent, false, and invalid values disable debugging. For an extension named firefox-bidi, PI_FIREFOX_BIDI_DEBUG=1 enables it and PI_FIREFOX_BIDI_DEBUG=0 disables it, overriding the global setting. The same rule applies to each extension after uppercasing its directory name and replacing hyphens with underscores. Unset or invalid environment values defer to the global setting. Changes take effect after Pi restarts.

Files

Debugging writes one JSONL file per uninterrupted logging segment under an OS user directory:

  • Linux: ${XDG_STATE_HOME:-~/.local/state}/pi-ext/debug/<extension>/.
  • macOS: ~/Library/Logs/pi-ext/<extension>/.
  • Windows: %LOCALAPPDATA%\pi-ext\debug\<extension>\, falling back to ~/AppData/Local/pi-ext/debug/<extension>/.

Files use a timestamp, process ID, and random suffix to avoid collisions; a reload or session shutdown followed by more work may create another file in the same Pi process. Clean closure releases the current file; a later record lazily starts another segment without rereading debug selection. A write failure or file-size limit permanently disables that loaded writer; clean closure never resets a fatal stop. On Linux, an empty or relative XDG_STATE_HOME falls back to ~/.local/state. Directories and files must be owner-private where the OS exposes Unix permissions; existing public files and symlinks are not reused. Each file is capped at 8 MiB; reaching the cap writes one terminal record and permanently disables further writes for that loaded writer. There is no automatic deletion or rotation. Logging failures must not fail extension work or print into Pi's TUI or machine-readable stdout; warn at most once when debugging was explicitly requested.

Records and timing

Each record has an ISO timestamp, extension name, process ID, event name, and bounded metadata. Operation spans include a local span ID, start and finish or error events, and a nonnegative duration in milliseconds measured with a monotonic clock. A span crossing clean closure can have its start and terminal records in different segment files; merge files by timestamp and match spanId. Wall-clock timestamps permit merging extension files into a timeline; monotonic durations are not comparable across processes. Log declared event names, counts, durations, and finite declared classifications, never prompts, command bodies, browser payloads, credentials, raw errors, or arbitrary objects. When disabled, skip record construction and timing work. Each extension documents its own safe events and keeps its implementation, including the enabled check and file writer, inside its own package.

Investigation

Find the extension directory under the OS user log root, read the newest JSONL file, and merge selected files by timestamp when cross-extension context matters. Replace <log-files> with selected JSONL paths for jq -s 'sort_by(.timestamp) | .[] | select(.durationMs != null and .durationMs >= 100)' <log-files> to show slow completed spans. In Nushell, use glob '<log-root>/*/*.jsonl' | each { |file| open --raw $file | lines | each { |line| $line | from json } } | flatten | sort-by timestamp | where { |row| ($row.durationMs? | default 0) >= 100 } after replacing <log-root> with the OS user debug directory. These comparisons are observational; enabled file I/O changes measured timings. SQLite is an optional offline analysis format, not a runtime logging dependency. Treat log files as sensitive metadata and remove them manually when no longer needed.

# Extension debug contract

Every repository extension binds its own committed `src/pi-ext-debug.ts` through a handwritten `src/debug.ts` and remains usable when installed without the rest of `pi-ext`.
For repository extensions, `scripts/codegen/templates/pi-ext-debug.ts` and `scripts/codegen/targets.json` generate these local copies through `mise run //:codegen`.
Root and extension `check` tasks compare generated files byte-for-byte without rewriting them.
No repository codegen runs at package installation or Pi startup, and no extension imports another extension or shared runtime code.
Event names, span placement, and event-specific classifications remain handwritten in each extension.

## Extension-owned schema

Every extension declares its debug schema in handwritten `src/debug.ts`; runtime callers import the needed `dbg`, `span`, and `closeDebug` bindings from that file, never directly from the generated module.
Repository static checks require a schema-bound `dbg` and `closeDebug` export plus a runtime import of the handwritten module.
For example:

```ts
import { defineDebug } from "./pi-ext-debug.ts";

export const { dbg, span, closeDebug } = defineDebug({
  events: ["session.start", "session.shutdown"],
  spans: ["request"],
  fields: {
    retryCount: "number",
    hasToken: "boolean",
    outcome: ["ok", "timeout"],
  },
} as const);
```

Declare literal events and span bases, plus only approved numeric measurements, booleans, and finite string labels.
For spans, `request` allows `request.start`, `request.finish`, and `request.error`; the writer owns `spanId` and `durationMs`.
Undeclared events and fields, invalid enum labels, raw objects, and unsafe numbers are omitted at runtime rather than written.
Schema types catch ordinary mistakes at compile time; they do not replace the runtime checks.
Do not pass credentials, prompts, or request bodies to the logger, even for redaction; record a presence boolean such as `hasToken` instead.
All repository extensions use schema-bound runtime logging; the generated direct exports remain for low-level tests until removed.
Do not edit generated files to add fields or bypass `defineDebug` in runtime code.
The schema may declare more than 24 fields, but each record retains at most 24 approved metadata fields.
File logging remains off unless enabled by the user or environment.

## Selection

Only user-level Pi settings are consulted; project settings cannot enable debugging.
In `~/.pi/agent/settings.json`, `"pi-ext": { "debug": true }` enables every extension, a string enables one extension by directory name, and an array of strings enables the named extensions.
Absent, `false`, and invalid values disable debugging.
For an extension named `firefox-bidi`, `PI_FIREFOX_BIDI_DEBUG=1` enables it and `PI_FIREFOX_BIDI_DEBUG=0` disables it, overriding the global setting.
The same rule applies to each extension after uppercasing its directory name and replacing hyphens with underscores.
Unset or invalid environment values defer to the global setting.
Changes take effect after Pi restarts.

## Files

Debugging writes one JSONL file per uninterrupted logging segment under an OS user directory:

- Linux: `${XDG_STATE_HOME:-~/.local/state}/pi-ext/debug/<extension>/`.
- macOS: `~/Library/Logs/pi-ext/<extension>/`.
- Windows: `%LOCALAPPDATA%\pi-ext\debug\<extension>\`, falling back to `~/AppData/Local/pi-ext/debug/<extension>/`.

Files use a timestamp, process ID, and random suffix to avoid collisions; a reload or session shutdown followed by more work may create another file in the same Pi process.
Clean closure releases the current file; a later record lazily starts another segment without rereading debug selection.
A write failure or file-size limit permanently disables that loaded writer; clean closure never resets a fatal stop.
On Linux, an empty or relative `XDG_STATE_HOME` falls back to `~/.local/state`.
Directories and files must be owner-private where the OS exposes Unix permissions; existing public files and symlinks are not reused.
Each file is capped at 8 MiB; reaching the cap writes one terminal record and permanently disables further writes for that loaded writer.
There is no automatic deletion or rotation.
Logging failures must not fail extension work or print into Pi's TUI or machine-readable stdout; warn at most once when debugging was explicitly requested.

## Records and timing

Each record has an ISO timestamp, extension name, process ID, event name, and bounded metadata.
Operation spans include a local span ID, start and finish or error events, and a nonnegative duration in milliseconds measured with a monotonic clock.
A span crossing clean closure can have its start and terminal records in different segment files; merge files by timestamp and match `spanId`.
Wall-clock timestamps permit merging extension files into a timeline; monotonic durations are not comparable across processes.
Log declared event names, counts, durations, and finite declared classifications, never prompts, command bodies, browser payloads, credentials, raw errors, or arbitrary objects.
When disabled, skip record construction and timing work.
Each extension documents its own safe events and keeps its implementation, including the enabled check and file writer, inside its own package.

## Investigation

Find the extension directory under the OS user log root, read the newest JSONL file, and merge selected files by timestamp when cross-extension context matters.
Replace `<log-files>` with selected JSONL paths for `jq -s 'sort_by(.timestamp) | .[] | select(.durationMs != null and .durationMs >= 100)' <log-files>` to show slow completed spans.
In Nushell, use `glob '<log-root>/*/*.jsonl' | each { |file| open --raw $file | lines | each { |line| $line | from json } } | flatten | sort-by timestamp | where { |row| ($row.durationMs? | default 0) >= 100 }` after replacing `<log-root>` with the OS user debug directory.
These comparisons are observational; enabled file I/O changes measured timings.
SQLite is an optional offline analysis format, not a runtime logging dependency.
Treat log files as sensitive metadata and remove them manually when no longer needed.