Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
94fca80
Add Copilot capture contracts and identities
duncankmckinnon Sep 10, 2026
14d7bbb
Add comprehensive Copilot capture contract tests.
duncankmckinnon Sep 10, 2026
e9fd768
Fix Copilot capture contract review findings.
duncankmckinnon Sep 10, 2026
0665860
Add comprehensive hook observations tests for Copilot V1.
duncankmckinnon Sep 10, 2026
f2dbad7
Add Copilot hook payload parsing and durable observation spooling.
duncankmckinnon Sep 10, 2026
274662b
Add Copilot SQLite evidence reader
duncankmckinnon Sep 10, 2026
e6d361b
Add durable Copilot archive journal
duncankmckinnon Sep 10, 2026
3a4ccce
Add Copilot transcript reader
duncankmckinnon Sep 10, 2026
888dc26
Add comprehensive tests for Copilot SQLite database reader.
duncankmckinnon Sep 10, 2026
4b4c7cf
Add Copilot archive journal behavioral and recovery tests.
duncankmckinnon Sep 10, 2026
d32012b
Add comprehensive behavioral tests for Copilot transcript reader.
duncankmckinnon Sep 10, 2026
dafe33a
Fix Copilot hook timestamp parsing and complete spool-record validation.
duncankmckinnon Sep 10, 2026
c798468
Add Copilot hook installer
duncankmckinnon Sep 10, 2026
b2f3b25
Add comprehensive unit tests for Copilot hook installer.
duncankmckinnon Sep 10, 2026
78c03b9
Fix Copilot transcript snapshot, continuity, and path containment.
duncankmckinnon Sep 10, 2026
8ac9d29
Fix Copilot SQLite reader review findings.
duncankmckinnon Sep 10, 2026
1b57046
Fail Copilot hook install without a resolvable dispatcher and refuse …
duncankmckinnon Sep 10, 2026
626fdf9
Fix Copilot archive journal recovery, lifecycle, and cursor durability.
duncankmckinnon Sep 10, 2026
297f084
Stop Copilot SQLite pagination from resetting on live session writes.
duncankmckinnon Sep 10, 2026
ffcc496
Merge wb/sqlite-reader into copilot-capture-v1
duncankmckinnon Sep 10, 2026
50baa46
Merge wb/archive-journal into copilot-capture-v1
duncankmckinnon Sep 10, 2026
5a2d5a5
Merge wb/hook-observations into copilot-capture-v1
duncankmckinnon Sep 10, 2026
7deaefc
Merge wb/hook-installer into copilot-capture-v1
duncankmckinnon Sep 10, 2026
ef60d5b
Compose Copilot source ingestion
duncankmckinnon Sep 10, 2026
5949ea1
Add behavioral tests for Copilot ingestion composition.
duncankmckinnon Sep 10, 2026
c375e34
Fix Copilot composition drain, stale-retry accounting, and discovery …
duncankmckinnon Sep 10, 2026
3320a35
Freeze Copilot database drain to an invocation-time snapshot.
duncankmckinnon Sep 10, 2026
9d65388
test: cover raw Copilot capture event views
duncankmckinnon Sep 10, 2026
b7f9e8c
Add Copilot capture watch and status
duncankmckinnon Sep 10, 2026
d8693bc
test: broaden raw Copilot view compatibility coverage
duncankmckinnon Sep 10, 2026
037f7e4
Add Copilot hook lifecycle capture
duncankmckinnon Sep 10, 2026
a3263be
Add behavioral tests for Copilot watch polling and capture status.
duncankmckinnon Sep 10, 2026
baca8f4
Add behavioral tests for Copilot hook lifecycle and follow-up capture.
duncankmckinnon Sep 10, 2026
5f0acf0
test: tighten Copilot raw-view checks for envelope, tool search, and …
duncankmckinnon Sep 10, 2026
ea6b417
Stop Copilot watch from retrying deleted sources and tighten status r…
duncankmckinnon Sep 10, 2026
2ff4f4e
Make Copilot hook capture nonblocking and tag by observation id.
duncankmckinnon Sep 10, 2026
6665730
Bound Copilot hook tagging to the current capture window.
duncankmckinnon Sep 10, 2026
c123a39
Merge wb/watch-and-status into copilot-capture-v1
duncankmckinnon Sep 10, 2026
3ea3217
Merge wb/raw-event-views into copilot-capture-v1
duncankmckinnon Sep 10, 2026
898d001
test: add Copilot CLI registration and command coverage.
duncankmckinnon Sep 10, 2026
9d58d73
Register Copilot CLI commands so add, setup, and sync/watch/status re…
duncankmckinnon Sep 10, 2026
da1e1cf
Add targeted tests for Copilot CLI registration edge cases.
duncankmckinnon Sep 10, 2026
6872cc4
Keep Copilot CLI command output local and actionable.
duncankmckinnon Sep 10, 2026
c2a73dd
Document Copilot CLI V1 and fix status leases plus same-batch session…
duncankmckinnon Sep 11, 2026
f903e84
Merge remote-tracking branch 'origin/main' into copilot-capture-v1
duncankmckinnon Sep 11, 2026
034d57d
format
duncankmckinnon Sep 11, 2026
85e2f20
fix: make Copilot capture tests portable
duncankmckinnon Sep 11, 2026
e1b1379
test: make Copilot replacement cases deterministic
duncankmckinnon Sep 11, 2026
121e6a3
test: isolate Copilot wall-clock performance check
duncankmckinnon Sep 11, 2026
04a3942
Merge branch 'main' into copilot-capture-v1
duncankmckinnon Sep 11, 2026
d1209e1
Merge remote-tracking branch 'origin/main' into copilot-capture-v1
duncankmckinnon Sep 11, 2026
de82e99
Align Copilot tests with platform layout
duncankmckinnon Sep 11, 2026
6f3f28c
Run Windows CI on Python 3.12
duncankmckinnon Sep 11, 2026
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
7 changes: 2 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,9 @@ jobs:
python-version: "3.13"
- os: ubuntu-latest
python-version: "3.14"
# Windows support is experimental: minimum supported version plus a
# recent stable, to bound runner cost.
# Exercise Windows on the project's primary Python version.
- os: windows-latest
python-version: "3.11"
- os: windows-latest
python-version: "3.13"
python-version: "3.12"

steps:
- uses: actions/checkout@v5
Expand Down
40 changes: 34 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
[![Python](https://img.shields.io/pypi/pyversions/thrdi.svg)](https://pypi.org/project/thrdi/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Trace every agent session on your machine — Claude Code, Codex, Cursor — into one history you and your agents can manage, search, and evaluate.
Trace every agent session on your machine — Claude Code, Codex, Cursor, GitHub Copilot CLI — into one history you and your agents can manage, search, and evaluate.

## Install

> **Windows support is experimental.** The test suite runs on Windows in CI, and
> Claude Code tracing is the verified integration there. The Codex CLI and Cursor
> installers are implemented but have not been verified against those tools on
> Windows. Please report Windows problems at the
> Claude Code tracing is the verified integration there. The Codex CLI, Cursor,
> and Copilot CLI installers are implemented but have not been live-certified
> against those tools on Windows. Please report Windows problems at the
> [issue tracker](https://github.com/duncankmckinnon/thirdeye/issues). See
> [docs/windows.md](docs/windows.md) for the deliberate platform differences.

Expand Down Expand Up @@ -79,10 +79,38 @@ rerun `thirdeye skills add --force` to refresh them. See
## Enable tracing

```bash
thirdeye add --claude # also: --cursor, --codex
thirdeye add --claude # also: --cursor, --codex, --copilot
```

To detach: `thirdeye remove --claude`.
To detach: `thirdeye remove --claude` (also `--cursor`, `--codex`, `--copilot`).

## Copilot CLI V1

GitHub Copilot CLI capture is a V1 immutable raw archive. V2 (reconstructed
turns, usage accounting, and OTel export) is out of scope. V1 does not export
Copilot content.

```bash
thirdeye add --copilot
thirdeye copilot status --source-home "$COPILOT_HOME"
thirdeye copilot sync --source-home "$COPILOT_HOME"
thirdeye copilot watch --source-home "$COPILOT_HOME" --interval 1
thirdeye remove --copilot
```

`--source-home` is optional. Resolution is `--source-home`, then `COPILOT_HOME`,
then `~/.copilot`. V1 reads only that home's `session-state/**/events.jsonl`,
`workspace.yaml`, and `session-store.db` (`sessions`, `turns`,
`assistant_usage_events`). It does not read credentials, token-bearing config,
or other Copilot files.

`thirdeye add --copilot` writes user-level hooks at
`$COPILOT_HOME/hooks/thirdeye.json` (default `~/.copilot/hooks/thirdeye.json`).
The 1.0.83 live probe used repository hooks; V1 does not. `watch` is an explicit
foreground poller and is not started by add or setup. Captured content has the
same local sensitivity as other thirdeye sessions and is not exported in V1.
`sync` and `status` print recoverable source diagnostics (locations and reasons,
not prompt bodies). Passing unit tests is not live certification of Copilot CLI.

## Read your history

Expand Down
19 changes: 11 additions & 8 deletions docs/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

Windows support is **experimental**. The test suite runs on `windows-latest` in
CI across Python 3.11 and 3.13, and Claude Code tracing is the verified
integration. The Codex CLI and Cursor installers are implemented but have not
been exercised against those tools on Windows. Please file Windows issues at the
[issue tracker](https://github.com/duncankmckinnon/thirdeye/issues).
integration. The Codex CLI, Cursor, and Copilot CLI installers are implemented
but have not been live-certified against those tools on Windows. Passing Copilot
unit tests is not live certification of Copilot CLI. Please file Windows issues
at the [issue tracker](https://github.com/duncankmckinnon/thirdeye/issues).

Install with `pipx` or `uv` — Homebrew stays macOS/Linux only.

Expand Down Expand Up @@ -35,12 +36,14 @@ A copied skill does not track a thirdeye upgrade the way a symlink does. After
`pipx upgrade thrdi` (or `uv tool upgrade thrdi`), rerun `thirdeye skills add --force`
to refresh the copied skills.

### 3. Unverified Codex and Cursor hooks
### 3. Unverified Codex, Cursor, and Copilot hooks

The Codex CLI and Cursor installers are correct by construction but have not been
run against the real tools on Windows. How each tool invokes a hook command
there — `cmd.exe`, PowerShell, or a direct `CreateProcess` — is unconfirmed, and
therefore so is whether a path containing spaces needs quoting.
The Codex CLI, Cursor, and Copilot CLI installers are correct by construction
but have not been run against the real tools on Windows. How each tool invokes a
hook command there — `cmd.exe`, PowerShell, or a direct `CreateProcess` — is
unconfirmed, and therefore so is whether a path containing spaces needs quoting.
Copilot V1 still writes user-level `$COPILOT_HOME/hooks/thirdeye.json` (default
`~/.copilot/hooks/thirdeye.json`); the 1.0.83 probe used repository hooks.

To sidestep the question, on Windows only, when the resolved hook binary path
contains a space thirdeye writes the bare binary name into the tool's config
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ thirdeye-codex-pre-compact = "thirdeye.platforms.codex.hooks_json:pre_compact"
thirdeye-codex-post-compact = "thirdeye.platforms.codex.hooks_json:post_compact"
thirdeye-codex-session-end = "thirdeye.platforms.codex.hooks_json:session_end"
thirdeye-cursor-hook = "thirdeye.platforms.cursor.hook:main"
thirdeye-copilot-hook = "thirdeye.platforms.copilot.hooks:main"

[build-system]
requires = ["setuptools>=64", "setuptools-scm>=8", "wheel"]
Expand Down
21 changes: 21 additions & 0 deletions src/thirdeye/_compat/fsops.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,27 @@ def unlink(path: Path, *, missing_ok: bool = False) -> None:
path.unlink(missing_ok=missing_ok)


def sync_directory(path: Path | str) -> None:
"""Best-effort ``fsync`` of a directory after ``replace`` or ``unlink``.

Publishing a file with tmp-plus-replace (or removing a journal) is not
durable until the directory entry itself is synced. Some platforms,
notably Windows, reject directory ``fsync``; those errors are ignored so
callers stay portable.
"""
path = Path(path)
try:
fd = os.open(path, os.O_RDONLY)
except OSError:
return
try:
os.fsync(fd)
except OSError:
return
finally:
os.close(fd)


def read_text(path: Path | str, *, encoding: str = "utf-8") -> str:
"""``Path.read_text`` with bounded retry on Windows ``PermissionError``.

Expand Down
2 changes: 2 additions & 0 deletions src/thirdeye/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from thirdeye.commands.add import add, remove
from thirdeye.commands.agent import agent_cmd
from thirdeye.commands.capture_env import capture_env_group
from thirdeye.commands.copilot import copilot_group
from thirdeye.commands.eval import eval_group
from thirdeye.commands.ingest import ingest
from thirdeye.commands.logfire_cmd import logfire_group
Expand All @@ -28,6 +29,7 @@ def main() -> None:
main.add_command(add)
main.add_command(remove)
main.add_command(ingest)
main.add_command(copilot_group)
main.add_command(list_sessions)
main.add_command(show)
main.add_command(events)
Expand Down
7 changes: 6 additions & 1 deletion src/thirdeye/commands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
from thirdeye.platforms.base import Platform
from thirdeye.platforms.claude.install import ClaudePlatform
from thirdeye.platforms.codex.install import CodexPlatform
from thirdeye.platforms.copilot.install import CopilotPlatform
from thirdeye.platforms.cursor.install import CursorPlatform

PLATFORMS: dict[str, type[Platform]] = {
"claude": ClaudePlatform,
"codex": CodexPlatform,
"cursor": CursorPlatform,
"copilot": CopilotPlatform,
}

# Config files that may still reference console scripts for platforms this
Expand All @@ -25,6 +27,9 @@


def _platform_options(fn):
fn = click.option(
"--copilot", "platform_flag", flag_value="copilot", help="GitHub Copilot CLI."
)(fn)
fn = click.option("--cursor", "platform_flag", flag_value="cursor", help="Cursor.")(fn)
fn = click.option("--codex", "platform_flag", flag_value="codex", help="Codex CLI.")(fn)
fn = click.option("--claude", "platform_flag", flag_value="claude", help="Claude Code.")(fn)
Expand All @@ -33,7 +38,7 @@ def _platform_options(fn):

def _resolve_platform(platform_flag: str | None, *, force: bool = False) -> Platform:
if not platform_flag:
raise click.UsageError("Pick a platform: --claude, --codex, --cursor")
raise click.UsageError("Pick a platform: " + ", ".join(f"--{name}" for name in PLATFORMS))
platform_cls = PLATFORMS[platform_flag]
if platform_flag == "codex" and force:
return platform_cls(force=True)
Expand Down
Loading
Loading