Skip to content
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
.runtime == "dotnet"
and .slice == "ML13"
and .normalizer_contract_version == "0.2.0"
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build"]
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build", "cursor"]
and .outputs == [
"letta-trajectory-v1",
"letta-canonical-v1",
Expand Down Expand Up @@ -236,7 +236,7 @@ jobs:
.runtime == "typescript"
and .slice == "ML13"
and .normalizer_contract_version == "0.2.0"
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build"]
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build", "cursor"]
and .outputs == [
"letta-trajectory-v1",
"letta-canonical-v1",
Expand Down Expand Up @@ -378,7 +378,7 @@ jobs:
.runtime == "python"
and .slice == "ML13"
and .normalizer_contract_version == "0.2.0"
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build"]
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build", "cursor"]
and .outputs == [
"letta-trajectory-v1",
"letta-canonical-v1",
Expand Down Expand Up @@ -530,7 +530,7 @@ jobs:
.runtime == "rust"
and .slice == "ML13"
and .normalizer_contract_version == "0.2.0"
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build"]
and .sources == ["pi", "claude-code", "codex", "openclaw", "hermes", "ahp", "grok-build", "cursor"]
and .outputs == [
"letta-trajectory-v1",
"letta-canonical-v1",
Expand Down
25 changes: 23 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,9 @@ jobs:
exit 1
fi
shopt -s nullglob
pkgs=(artifacts/release/nuget/*.nupkg artifacts/release/nuget/*.snupkg)
# Push nupkgs only. Adjacent .snupkg symbols ride along with the
# nupkg; a second glob of .snupkg hits Conflict after that upload.
pkgs=(artifacts/release/nuget/*.nupkg)
if [[ ${#pkgs[@]} -eq 0 ]]; then
echo "No NuGet packages found under artifacts/release/nuget" >&2
exit 1
Expand Down Expand Up @@ -445,6 +447,7 @@ jobs:
- name: Publish packages
env:
NODE_AUTH_TOKEN: ${{ needs.resolve.outputs.npm_auth == 'token' && secrets.NPM_TOKEN || '' }}
NPM_TOKEN_FALLBACK: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
VERSION="${{ needs.resolve.outputs.version }}"
Expand Down Expand Up @@ -473,8 +476,13 @@ jobs:
--registry npm \
--package "$workspace" \
--version "$VERSION"
elif [[ -n "${NPM_TOKEN_FALLBACK:-}" ]]; then
# OIDC cannot create a brand-new package name (0.1.3: trajectory-ahp / hermes).
echo "::notice::$workspace missing on npm; retrying first publish with NPM_TOKEN"
NODE_AUTH_TOKEN="$NPM_TOKEN_FALLBACK" npm publish --workspace "$workspace" --access public
else
echo "Failed to publish $workspace@${VERSION}" >&2
echo "Failed to publish $workspace@${VERSION}." >&2
echo "If this package name has never been published, create it once with an npm token (OIDC cannot create new names), then configure trusted publishing." >&2
exit 1
fi
done
Expand Down Expand Up @@ -505,6 +513,7 @@ jobs:
- name: Stamp version and publish
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
CARGO_REGISTRY_TOKEN_FALLBACK: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
VERSION="${{ needs.resolve.outputs.version }}"
Expand Down Expand Up @@ -535,6 +544,18 @@ jobs:
--version "$VERSION" || exit 1
return 0
fi
if echo "$out" | grep -qi 'Trusted Publishing tokens do not support creating new crates'; then
if [[ -n "${CARGO_REGISTRY_TOKEN_FALLBACK:-}" ]]; then
echo "::notice::${crate} is a new crate; retrying first publish with CARGO_REGISTRY_TOKEN"
CARGO_REGISTRY_TOKEN="$CARGO_REGISTRY_TOKEN_FALLBACK" \
cargo +stable publish --manifest-path rust/Cargo.toml \
-p "$crate" --locked --allow-dirty
return $?
fi
echo "${crate} has never been published. OIDC cannot create new crate names." >&2
echo "Publish it once with a crates.io token, then add a Trusted Publishing policy." >&2
exit 1
fi
if curl -fsSL "https://crates.io/api/v1/crates/${crate}/${VERSION}" >/dev/null 2>&1; then
echo "::notice::${crate}@${VERSION} already on crates.io; verifying stream content"
python3 tools/verify_published_stream_artifact.py \
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ remain synchronized across NuGet, npm, and crates.io preview artifacts.

## [Unreleased]

## 0.1.4 — 2026-08-13

Cursor Agent source plus a synchronized re-publish after the partial `0.1.3`
registry cut. **Do not retag `v0.1.3`** — PyPI / most NuGet / existing npm and
crates names already accepted `0.1.3`.

- **Source:** added Cursor Agent (`cursor`) transcript JSONL decoding, listing,
and file streaming across all four runtimes. Wire name `cursor` (alias
`cursor-agent`). Default store `$CURSOR_HOME` or `~/.cursor`. Encrypted
`store.db` is not v1 normalize input.

- **Release:** NuGet verify waits longer for the CDN and tries the v2 package
URL; `dotnet nuget push` no longer double-uploads `.snupkg`. New npm/crates
names (`trajectory-ahp`, `trajectory-hermes`, `hypabolic-trajectory-io` /
`-ahp` / `-hermes`) retry first-publish with token secrets when OIDC cannot
create them.

- **Docs:** removed delivery-plan / status trackers
(`implementation-plan`, `multi-language-plan`, `live-session-streaming-plan`,
`live-session-streaming-status`, `python-implementation-spec`,
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ GitHub Release. See [docs/publishing.md](docs/publishing.md).
## What you get

- **Multi-source ingest** — Pi, Claude Code, Codex, OpenClaw, Hermes, AHP
(Shape A offline ChatState snapshots), and Grok Build (`grok-build`)
(Shape A offline ChatState snapshots), Grok Build (`grok-build`), and Cursor Agent (`cursor`)
- **Deterministic normalization** — stable IDs, ordering, hashes, content-safe
diagnostics
- **Multiple outputs** from one decode: Hypabolic trajectory, canonical
Expand Down Expand Up @@ -251,6 +251,7 @@ timestamps, identity formulas, OTEL import matrix, filtered conformance argv).
| Hermes | Message array or `{ session, messages }` JSON | Export file; core listing is SQLite-free |
| AHP | Shape A chat snapshot `{ chat, session? }` JSON | Export file only; listing is Phase 3 (empty stub) |
| Grok Build | `chat_history.jsonl` (alias `grok`) | `$GROK_HOME/sessions` or `~/.grok/sessions` |
| Cursor Agent | `<session-id>.jsonl` (alias `cursor-agent`) | `$CURSOR_HOME` or `~/.cursor` |

Override listing roots with `--root` / `TRAJECTORY_<SOURCE>_ROOT` in the sample
CLIs, or pass an explicit root to listing APIs.
Expand Down Expand Up @@ -407,7 +408,7 @@ Shared flags:

| Flag | Meaning |
| --- | --- |
| `--source <name>` | `pi`, `claude-code`, `codex`, `openclaw`, `hermes`, `ahp`, `grok-build` (alias `grok`) |
| `--source <name>` | `pi`, `claude-code`, `codex`, `openclaw`, `hermes`, `ahp`, `grok-build`, `cursor` (aliases `grok`, `cursor-agent`) |
| `--root <path>` | Override store root |
| `--limit <n>` | Listing page size (default 50) |
| `--format <f>` | `both` (default), `messages`, or `hypabolic` |
Expand Down
8 changes: 8 additions & 0 deletions conformance/cases/cursor/byte-identity/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "../../../../contracts/schemas/conformance-case-v1.schema.json",
"case_version": 1, "id": "cursor/byte-identity",
"operation": {"normalize-letta": {"expected": "expected.letta.json", "comparison": "json-exact"}, "normalize-canonical": {"expected": "expected.canonical.json", "comparison": "json-exact"}},
"source": "cursor", "transcript": "input.jsonl", "native_container": "cursor-agent-transcript-jsonl",
"source_context": {"group_id": "019f0000-0000-7000-8000-00000000c005", "partial": true, "base_byte_offset": 100}, "bounds": {}, "filters": {}, "projection_options": {}, "mode": "partial",
"required_capabilities": ["normalize", "normalize-partial", "typed-diagnostics", "deterministic-rerun"], "expected": {"result": "diagnostics", "diagnostic_codes": ["tool_call_id_synthesized", "timestamps_synthesized"]}, "comparison": "json-exact"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"records":[{"source_type":"cursor","source_group_id":"019f0000-0000-7000-8000-00000000c005","stable_source_record_id":"794cd6244d15dc23087c4f46a0fb66716d93cb5c46af7ab414636a653cc067b5","source_identity_kind":"location","source_order_id":"1|0000-00-00T00:00:00.001Z|00000000000000000000|794cd6244d15dc23087c4f46a0fb66716d93cb5c46af7ab414636a653cc067b5","component_index":0,"record_type":"user","record_id":"c2a146bc4f8c4bd1d6bdb11cd3e50c9f682080229671f291cba4a572dc7124e3","record_hash":"1a783c3a645ad7625bdd4cfa0978030fa120bafbe2f151c59dcb5333ef8cbc55","content_hash":"5fbc4375f5663257a9217c0790626420f84e06a1374ea52e4edd45e1476571e3","source_timestamp":null,"record_timestamp":"2026-01-01T00:00:00.000Z","content":"café \uD83D\uDE80","tool_call_id":null,"tool_name":null,"tool_arguments_json":null,"tool_result_json":null,"record_json":"{\"content\":\"café \\uD83D\\uDE80\",\"role\":\"user\",\"timestamp\":\"2026-01-01T00:00:00.000Z\"}"},{"source_type":"cursor","source_group_id":"019f0000-0000-7000-8000-00000000c005","stable_source_record_id":"27e6cd3c2c9c8e595b169f76526a83931640f8894cc124257a85ca6efc74f586","source_identity_kind":"location","source_order_id":"1|0000-00-00T00:00:00.001Z|00000000000000000000|27e6cd3c2c9c8e595b169f76526a83931640f8894cc124257a85ca6efc74f586","component_index":0,"record_type":"assistant","record_id":"081bf4725a9b52917b8aa4f9b2f5ede056391d1127dafd59816a7425df9b431a","record_hash":"7cfe633def87ad6c849485d2d5dc59f4593875ef67bc816b03720e2bb72b21fe","content_hash":"1d6b4cd339ff333544c224663a177298cbe9d2a85b8547d0554f041033f34838","source_timestamp":null,"record_timestamp":"2026-01-01T00:00:15.000Z","content":"Ready.","tool_call_id":null,"tool_name":null,"tool_arguments_json":null,"tool_result_json":null,"record_json":"{\"content\":\"Ready.\",\"role\":\"assistant\",\"timestamp\":\"2026-01-01T00:00:15.000Z\"}"},{"source_type":"cursor","source_group_id":"019f0000-0000-7000-8000-00000000c005","stable_source_record_id":"27e6cd3c2c9c8e595b169f76526a83931640f8894cc124257a85ca6efc74f586","source_identity_kind":"location","source_order_id":"1|0000-00-00T00:00:00.001Z|00000000000000000000|27e6cd3c2c9c8e595b169f76526a83931640f8894cc124257a85ca6efc74f586","component_index":1,"record_type":"assistant-tool-call","record_id":"baeb465e971f9f34ffa07dc7f4bc7262115391f84007588b5e21e4d5bd029d3c","record_hash":"8d8cce2f4ac84457a17e5277cbcc11d04bf911c4df5c9031dc493a21dd16fc53","content_hash":"b1b7237cacbadf1811823159a094c36ba8131cb6f82bcfbbeb45a2929131d7ec","source_timestamp":null,"record_timestamp":"2026-01-01T00:00:30.000Z","content":null,"tool_call_id":"call_3","tool_name":"scan","tool_arguments_json":"{\"query\":\"\\uD83D\\uDE80\"}","tool_result_json":null,"record_json":"{\"content\":null,\"role\":\"assistant\",\"timestamp\":\"2026-01-01T00:00:30.000Z\",\"tool_calls\":[{\"args\":\"{\\\"query\\\":\\\"\\\\uD83D\\\\uDE80\\\"}\",\"id\":\"call_3\",\"name\":\"scan\"}]}"}],"diagnostics":[{"code":"tool_call_id_synthesized","message":"Synthesized tool-call ID \"call_3\".","inputLine":2,"recordIndex":3},{"code":"timestamps_synthesized","message":"Synthesized timestamps for 3 normalized records.","count":3}],"normalizer_version":"0.2.0","canonical_schema_version":1,"config":{"bounds":{"toolArguments":{"maxCharacters":20000},"toolResults":{"maxCharacters":2500,"strategy":"head-tail"}},"filters":{"toolResults":"include"}}}
1 change: 1 addition & 0 deletions conformance/cases/cursor/byte-identity/expected.letta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"records":[{"role":"meta","source":"cursor"},{"role":"user","content":"café \uD83D\uDE80","timestamp":"2026-01-01T00:00:00.000Z"},{"role":"assistant","content":"Ready.","timestamp":"2026-01-01T00:00:15.000Z"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_3","name":"scan","args":"{\"query\":\"\\uD83D\\uDE80\"}"}],"timestamp":"2026-01-01T00:00:30.000Z"}],"diagnostics":[{"code":"tool_call_id_synthesized","message":"Synthesized tool-call ID \"call_3\".","inputLine":2,"recordIndex":3},{"code":"timestamps_synthesized","message":"Synthesized timestamps for 3 normalized records.","count":3}]}
2 changes: 2 additions & 0 deletions conformance/cases/cursor/byte-identity/input.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"role":"user","message":{"content":[{"type":"text","text":"café 🚀"}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"Ready."},{"type":"tool_use","name":"scan","input":{"query":"🚀"}}]}}
8 changes: 8 additions & 0 deletions conformance/cases/cursor/cleanup/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "../../../../contracts/schemas/conformance-case-v1.schema.json",
"case_version": 1, "id": "cursor/cleanup",
"operation": {"normalize-letta": {"expected": "expected.letta.json", "comparison": "json-exact"}},
"source": "cursor", "transcript": "input.jsonl", "native_container": "cursor-agent-transcript-jsonl",
"source_context": {"group_id": "019f0000-0000-7000-8000-00000000c003"}, "bounds": {}, "filters": {}, "projection_options": {}, "mode": "whole",
"required_capabilities": ["normalize", "typed-diagnostics", "deterministic-rerun"], "expected": {"result": "diagnostics", "diagnostic_codes": ["invalid_json_line", "non_object_json_line", "unknown_semantic_record", "unknown_semantic_record", "image_content_dropped", "unknown_content_part", "tool_use_missing_name", "turn_ended_error", "timestamps_synthesized"]}, "comparison": "json-exact"
}
1 change: 1 addition & 0 deletions conformance/cases/cursor/cleanup/expected.letta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"records":[{"role":"meta","source":"cursor"},{"role":"user","content":"Clean this up.","timestamp":"2026-01-01T00:00:00.000Z"},{"role":"assistant","content":"Done.","timestamp":"2026-01-01T00:00:15.000Z"}],"diagnostics":[{"code":"invalid_json_line","message":"Skipped invalid JSON on line 1.","inputLine":1},{"code":"non_object_json_line","message":"Skipped non-object JSON on line 2.","inputLine":2},{"code":"unknown_semantic_record","message":"Skipped an unknown Cursor semantic record on line 3.","inputLine":3},{"code":"unknown_semantic_record","message":"Skipped an unknown Cursor semantic record on line 4.","inputLine":4},{"code":"image_content_dropped","message":"Dropped image content on a Cursor record on line 5.","inputLine":5},{"code":"unknown_content_part","message":"Skipped an unknown Cursor content part on line 5.","inputLine":5},{"code":"tool_use_missing_name","message":"Skipped a Cursor tool_use part without a name on line 6.","inputLine":6},{"code":"turn_ended_error","message":"A Cursor turn ended with an error.","inputLine":7},{"code":"timestamps_synthesized","message":"Synthesized timestamps for 2 normalized records.","count":2}]}
7 changes: 7 additions & 0 deletions conformance/cases/cursor/cleanup/input.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
not valid json
[1,2]
{"type":"future_record","secret":"SECRET_TOKEN_xyz"}
{"role":"sideways","message":{"content":[]}}
{"role":"user","message":{"content":[{"type":"text","text":"Clean this up."},{"type":"image","url":"/Users/real-user/image.png"},{"type":"future_part"},{"type":"tool_use","name":"ignored_user_tool","input":{"secret":"not emitted"}}]}}
{"role":"assistant","message":{"content":[{"type":"tool_use","input":{"x":1}},{"type":"text","text":"Done."}]}}
{"type":"turn_ended","status":"error","error":"SECRET_TOKEN_xyz /Users/real-user/"}
14 changes: 14 additions & 0 deletions conformance/cases/cursor/full/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "../../../../contracts/schemas/conformance-case-v1.schema.json",
"case_version": 1,
"id": "cursor/full",
"operation": {"normalize-letta": {"expected": "expected.letta.json", "comparison": "json-exact"}, "normalize-canonical": {"expected": "expected.canonical.json", "comparison": "json-exact"}},
"source": "cursor",
"transcript": "input.jsonl",
"native_container": "cursor-agent-transcript-jsonl",
"source_context": {"group_id": "019f0000-0000-7000-8000-00000000c001"},
"bounds": {}, "filters": {}, "projection_options": {}, "mode": "whole",
"required_capabilities": ["normalize", "typed-diagnostics", "deterministic-rerun"],
"expected": {"result": "diagnostics", "diagnostic_codes": ["tool_call_id_synthesized", "timestamps_synthesized"]},
"comparison": "json-exact"
}
Loading
Loading