Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/SUPPORTED-CLIENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ segmentation, as recorded below.
| Mistral Vibe | Setup-managed `vibe-incognito` and `mistral-vibe-incognito` commands | No | Wrapper/native sessions eligible when non-headless; headless skipped | Wrapper/native sessions eligible when non-headless; headless skipped | `privacy-features` E2E positive fixture proof; setup/inventory and command-behavior coverage |
| Grok Build | Setup-managed `grok-incognito` command and `trajectory-incognito` skill | No | Source-only sessions remain headless/unknown; explicit native mode may become eligible | Source-only sessions remain headless/unknown; explicit native mode may become eligible | Fixture/control-plane proof; authenticated native-hook and incognito UX pilot pending |
| Codebuff | Setup-managed `codebuff-incognito` and `cb-incognito` commands | No | Wrapper/imported sessions eligible when non-headless; headless skipped | Wrapper/imported sessions eligible when non-headless; headless skipped | `privacy-features` E2E positive fixture proof; setup/inventory and command-behavior coverage |
| Pi | Native `trajectory_incognito` tool plus MCP | Yes | Non-headless extension sessions eligible; extension-supplied verdicts accepted; headless skipped | Non-headless extension sessions eligible; headless skipped | Live incognito UX; `privacy-features` E2E positive fixture proof; extension verdict tests |
| Pi | MCP request path | Yes | Non-headless extension sessions eligible; extension-supplied verdicts accepted; headless skipped | Non-headless extension sessions eligible; headless skipped | Live incognito UX; `privacy-features` E2E positive fixture proof; extension verdict tests |
| Oh My Pi (`omp`) | MCP request path; no setup-managed slash command yet | Yes | Native extension marks headless state; non-headless sessions eligible, headless skipped | Non-headless sessions eligible; headless skipped | Setup and capture fixtures only; live incognito UX and positive privacy-feature proof pending |
| OpenCode | Incognito skill | Yes | Non-headless plugin SDK sessions eligible; headless skipped | Non-headless plugin SDK sessions eligible; headless skipped | Live incognito UX; `privacy-features` E2E positive fixture proof |
| Kilo Code | Incognito skill | Yes | Non-headless plugin SDK sessions eligible; headless skipped | Non-headless plugin SDK sessions eligible; headless skipped | `privacy-features` E2E positive fixture proof; setup/live capture coverage |
Expand Down Expand Up @@ -1768,9 +1768,9 @@ cp -R /path/to/trajectory/plugin/trajectory-pi ~/.pi/agent/extensions/trajectory

Then point `~/.pi/agent/mcp.json` at `~/.pi/agent/extensions/trajectory/bin/trajectory mcp`.

Setup writes `~/.pi/agent/extensions/trajectory/` with a `package.json` that declares `pi.extensions: ["./src/index.ts"]`, plus a root `index.ts` shim that re-exports `./src/index.ts`, and points `~/.pi/agent/mcp.json` at the extension-local `bin/trajectory mcp` command. Setup does not add Trajectory's extension entrypoint to `~/.pi/agent/settings.json`; Pi discovers the extension from its standard extensions directory. Pi uses a TypeScript extension API (`pi.on("event", handler)`) that subscribes to lifecycle events (session_start, agent_end, tool_call, tool_result, etc.) and queues every captured event through `trajectory capture-hook --client pi`; the helper safely retries retirement resets with one delivery ID. Native `turn_end` is one provider request/tool-loop step, so the extension retains that request usage on `AgentMessage`/`llm_call` but finalizes a Trajectory turn only at `agent_end`, using the complete native agent-run usage aggregate and a stable source event ID. The extension emits `PostCompact` and registers `trajectory_status`, `trajectory_flush`, `trajectory_incognito`, `trajectory_schema`, and `trajectory_query`; MCP exposes the shared cross-client tool surface in environments where Pi routes MCP tools. Pi supports multiple LLM providers - use any provider API key for testing.
Setup writes `~/.pi/agent/extensions/trajectory/` with a `package.json` that declares `pi.extensions: ["./src/index.ts"]`, plus a root `index.ts` shim that re-exports `./src/index.ts`, and points `~/.pi/agent/mcp.json` at the extension-local `bin/trajectory mcp` command. Setup does not add Trajectory's extension entrypoint to `~/.pi/agent/settings.json`; Pi discovers the extension from its standard extensions directory. Pi uses a TypeScript extension API (`pi.on("event", handler)`) that subscribes to lifecycle events (session_start, agent_end, tool_call, tool_result, etc.) and queues every captured event through `trajectory capture-hook --client pi`; the helper safely retries retirement resets with one delivery ID. Native `turn_end` is one provider request/tool-loop step, so the extension retains that request usage on `AgentMessage`/`llm_call` but finalizes a Trajectory turn only at `agent_end`, using the complete native agent-run usage aggregate and a stable source event ID. The extension emits `PostCompact` without registering native Pi tools, keeping Trajectory tool schemas out of the model context by default. MCP exposes the shared cross-client tool surface in environments where Pi routes MCP tools. Pi supports multiple LLM providers - use any provider API key for testing.

Pi does not currently consume the Codex/Claude-style `skills/` plugin directory. The Trajectory Pi extension vends incognito through its native `trajectory_incognito` tool; environments that expose MCP can also use the shared `trajectory_incognito` MCP tool.
Pi does not currently consume the Codex/Claude-style `skills/` plugin directory. Environments that expose MCP can use the shared `trajectory_incognito` MCP tool.

Current Pi reports fork and new-session transitions through `session_start`.
The extension preserves the exact provider session IDs and records a canonical
Expand Down
10 changes: 5 additions & 5 deletions docs/USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,10 @@ trajectory user-guide cursor-cost # Cursor token formula, rate provenance, a

The current OSS binary does not expose a general-purpose `trajectory query`
CLI. Use `trajectory status`, `trajectory local-ui`, `get_session_trajectory`, and
the `trajectory_schema` / `trajectory_query` tools for local inspection. Pi
registers those as native extension tools; setup-managed MCP clients get the
same schema-first workflow through `trajectory mcp`. The embedded query guide
documents schema-first inspection and `TRAJECTORY_CACHE_DB` handling.
the `trajectory_schema` / `trajectory_query` MCP tools for local inspection.
The Pi extension does not register native tools, keeping their schemas out of
the model context by default. The embedded query guide documents schema-first
inspection and `TRAJECTORY_CACHE_DB` handling.

Use `trajectory cost` for local cost tracking. It reads the local SQLite cache,
automatically repairs obsolete Codex token/cost projections when needed, shows
Expand Down Expand Up @@ -871,7 +871,7 @@ sensitivity classification and segmentation.
| Cursor Desktop | Incognito skill, using Claude skill when available or native Cursor fallback; setup also installs `cursor-agent-incognito` | Yes | Non-headless GUI sessions eligible; headless skipped | Non-headless GUI sessions eligible; headless skipped | Punted for positive privacy-feature proof: GUI/transcript watcher path has no stable credential-free non-headless hook stream |
| cursor-agent CLI | Setup-managed `cursor-agent-incognito` command when the Cursor integration is installed; watcher has no native slash surface | No | Passive history is local-only and replay-ineligible; native hook sessions use their proven surface | Passive history is local-only and replay-ineligible; native hook sessions use their proven surface | Protected `cursor-agent --print` native/passive identity gate; shared passive store remains surface-unknown |
| Factory Droid | Incognito skill in the local marketplace plugin | Yes | Non-headless plugin sessions eligible; headless skipped | Non-headless plugin sessions eligible; headless skipped | `privacy-features` E2E positive fixture proof; no live Droid incognito UX gate yet |
| Pi | Native `trajectory_incognito` tool plus MCP | Yes | Non-headless extension sessions eligible; extension-supplied verdicts accepted; headless skipped | Non-headless extension sessions eligible; headless skipped | Live incognito UX; `privacy-features` E2E positive fixture proof; extension verdict tests |
| Pi | MCP request path | Yes | Non-headless extension sessions eligible; extension-supplied verdicts accepted; headless skipped | Non-headless extension sessions eligible; headless skipped | Live incognito UX; `privacy-features` E2E positive fixture proof; extension verdict tests |
| Oh My Pi (`omp`) | MCP request path; no setup-managed slash command yet | Yes | Native extension marks headless state; non-headless eligible, headless skipped | Non-headless eligible; headless skipped | Sanitized v16.5.2 setup/capture/backfill fixtures; real executable and positive privacy proof pending |
| Hermes Agent | Incognito skill | Yes | Non-headless observer sessions eligible; headless skipped | Non-headless observer sessions eligible; headless skipped | `privacy-features` E2E positive fixture proof; protected live capture coverage; no live incognito UX gate yet |
| Amp Code | Setup-managed `amp-incognito` command plus MCP request path | Yes | Non-headless Amp plugin sessions eligible; headless skipped | Non-headless Amp plugin sessions eligible; headless skipped | `privacy-features` E2E positive fixture proof until a usable `AMP_API_KEY` exists |
Expand Down
2 changes: 1 addition & 1 deletion plugin/trajectory-pi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ extension is not an OhMyPi live-capture package.

## Tools

The extension registers `trajectory_status`, `trajectory_flush`, and `trajectory_incognito`. `trajectory_incognito` toggles publish suppression for the current Pi session while local JSONL capture continues.
The extension does not register native Pi tools, keeping Trajectory tool schemas out of the model context by default. Tool access remains available through the setup-managed MCP configuration when explicitly used.
11 changes: 1 addition & 10 deletions plugin/trajectory-pi/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions plugin/trajectory-pi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@
"@earendil-works/pi-ai": "0.83.0",
"@earendil-works/pi-coding-agent": "0.83.0",
"@types/node": "22.20.1",
"typebox": "1.3.10",
"typescript": "7.0.2"
},
"peerDependencies": {
"@earendil-works/pi-ai": "*",
"@earendil-works/pi-coding-agent": "*",
"typebox": "*"
"@earendil-works/pi-coding-agent": "*"
},
"license": "Apache-2.0",
"repository": {
Expand Down
150 changes: 0 additions & 150 deletions plugin/trajectory-pi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,15 @@
*/

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { BoundedSerialQueue } from "./async-queue.js";
import { aggregatePiAgentRun, PiAgentRunTracker } from "./agent-run.js";
import { buildTrajectorySchema, runTrajectoryQuery } from "./query-tools.js";
import { piSessionIdentityFields, readPiSessionHeaderId } from "./session-identity.js";
import { ensureTrajectoryServe as requestTrajectoryServe } from "./serve-ensure.js";

const DEFAULT_PORT = 19222;
const POST_TIMEOUT_MS = 2000;
const CAPTURE_HELPER_TIMEOUT_MS = 5000;
const CAPTURE_QUEUE_CAPACITY = 256;
const LIFECYCLE_QUEUE_CAPACITY = 64;
Expand Down Expand Up @@ -72,7 +69,6 @@ function withPluginProvenance(body: Record<string, unknown>): Record<string, unk

export function registerTrajectoryExtension(pi: ExtensionAPI, runtime: TrajectoryExtensionRuntime = {}) {
const port = parseInt(process.env.TRAJECTORY_PORT ?? String(DEFAULT_PORT), 10);
const baseUrl = `http://127.0.0.1:${port}`;
const captureQueue = runtime.captureQueue ?? new BoundedSerialQueue(CAPTURE_QUEUE_CAPACITY);
const lifecycleQueue = runtime.lifecycleQueue ?? new BoundedSerialQueue(LIFECYCLE_QUEUE_CAPACITY);

Expand Down Expand Up @@ -103,152 +99,6 @@ export function registerTrajectoryExtension(pi: ExtensionAPI, runtime: Trajector

const ensureServeTask = runtime.ensureServe ?? ensureTrajectoryServe;

// -- Tool registration --------------------------------------------

pi.registerTool({
name: "trajectory_status",
label: "Trajectory Status",
description: "Shows the current trajectory capture status including active sessions and event counts",
parameters: Type.Object({}),
async execute(_toolCallId, _params, signal) {
try {
const res = await fetch(`${baseUrl}/health`, {
signal: signal ?? AbortSignal.timeout(POST_TIMEOUT_MS),
});
const data = await res.json();
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
details: {},
};
} catch (err) {
return {
content: [{ type: "text", text: `Trajectory serve unreachable: ${err}` }],
details: {},
isError: true,
};
}
},
});

pi.registerTool({
name: "trajectory_flush",
label: "Trajectory Flush",
description: "Flushes any pending trajectory data to ensure all events are written",
parameters: Type.Object({}),
async execute(_toolCallId, _params, signal) {
try {
await fetch(`${baseUrl}/flush`, {
method: "POST",
signal: signal ?? AbortSignal.timeout(5000),
});
return {
content: [{ type: "text", text: "Trajectory data flushed." }],
details: {},
};
} catch (err) {
return {
content: [{ type: "text", text: `Flush failed: ${err}` }],
details: {},
isError: true,
};
}
},
});

pi.registerTool({
name: "trajectory_incognito",
label: "Trajectory Incognito",
description: "Toggles incognito mode for the current Pi session. Local JSONL capture continues, but publish to non-exempt Datadog destinations is suppressed while enabled.",
parameters: Type.Object({
enable: Type.Boolean({ description: "true to enable incognito, false to disable it" }),
}),
async execute(_toolCallId, params, signal) {
if (!sessionId) {
return {
content: [{ type: "text", text: "No active Pi session is registered yet." }],
details: {},
isError: true,
};
}

const enable = Boolean((params as { enable?: boolean }).enable);
try {
await ensureTrajectoryServe();
const res = await fetch(
`${baseUrl}/session/incognito?session_id=${encodeURIComponent(sessionId)}&enable=${enable}`,
{
method: "POST",
signal: signal ?? AbortSignal.timeout(POST_TIMEOUT_MS),
},
);
if (!res.ok) {
const body = await res.text();
return {
content: [{ type: "text", text: `Incognito toggle failed: ${res.status} ${body}` }],
details: {},
isError: true,
};
}
const state = enable ? "enabled" : "disabled";
const detail = enable
? "publish to non-exempt Datadog destinations is suppressed; local JSONL capture continues"
: "publish to non-exempt Datadog destinations is resumed";
return {
content: [{ type: "text", text: `Incognito ${state} for session ${sessionId}; ${detail}.` }],
details: {},
};
} catch (err) {
return {
content: [{ type: "text", text: `Incognito toggle failed: ${err}` }],
details: {},
isError: true,
};
}
},
});

pi.registerTool({
name: "trajectory_schema",
label: "Trajectory Schema",
description: "Introspects the local Trajectory SQLite cache schema. Call this before trajectory_query so SQL matches the live cache.",
parameters: Type.Object({
include_row_counts: Type.Optional(Type.Boolean({ description: "Include SELECT COUNT(*) per table. Defaults to false." })),
}),
async execute(_toolCallId, params) {
const result = buildTrajectorySchema(Boolean((params as { include_row_counts?: boolean }).include_row_counts));
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
details: { result },
isError: !result.ok,
};
},
});

pi.registerTool({
name: "trajectory_query",
label: "Trajectory Query",
description: "Runs a guarded read-only SQL query against the local Trajectory SQLite cache. Call trajectory_schema first unless the schema was already fetched. Only SELECT, WITH, and PRAGMA are allowed after stripping comments.",
parameters: Type.Object({
query: Type.String({ description: "SQL query. First keyword after comments must be SELECT, WITH, or PRAGMA." }),
params: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "Optional named SQL parameters. Use placeholders such as :session_id." })),
limit: Type.Optional(Type.Number({ description: "Maximum rows to return. Defaults to 100, max 1000." })),
row_limit: Type.Optional(Type.Number({ description: "Alias for limit." })),
}),
async execute(_toolCallId, params) {
const result = runTrajectoryQuery(params as {
query: string;
params?: Record<string, string | number | boolean | null>;
limit?: number;
row_limit?: number;
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
details: { result },
isError: !result.ok,
};
},
});

// -- CLI fallback for session lifecycle events -------------------
// Session lifecycle events (start/end) use CLI fallback to write directly
// to JSONL, independent of serve availability. Matches CC plugin pattern.
Expand Down
Loading