syntaur 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/index.js +146 -10
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/platforms/SESSION-ID-RESOLUTION.md +117 -0
  5. package/platforms/claude-code/agents/syntaur-expert.md +1 -1
  6. package/platforms/claude-code/commands/track-session/track-session.md +5 -4
  7. package/platforms/claude-code/hooks/hooks.json +1 -1
  8. package/platforms/claude-code/hooks/session-cleanup.sh +16 -8
  9. package/platforms/claude-code/skills/clear-assignment/SKILL.md +1 -1
  10. package/platforms/claude-code/skills/complete-assignment/SKILL.md +1 -1
  11. package/platforms/claude-code/skills/grab-assignment/SKILL.md +5 -4
  12. package/platforms/claude-code/skills/save-session-summary/SKILL.md +15 -6
  13. package/platforms/claude-code/skills/track-session/SKILL.md +5 -4
  14. package/platforms/codex/agents/syntaur-operator.md +1 -1
  15. package/platforms/codex/commands/save-session-summary.md +1 -1
  16. package/platforms/codex/scripts/session-cleanup.sh +63 -6
  17. package/platforms/codex/skills/clear-assignment/SKILL.md +1 -1
  18. package/platforms/codex/skills/complete-assignment/SKILL.md +1 -1
  19. package/platforms/codex/skills/grab-assignment/SKILL.md +5 -4
  20. package/platforms/codex/skills/save-session-summary/SKILL.md +15 -6
  21. package/platforms/codex/skills/track-session/SKILL.md +5 -4
  22. package/platforms/cursor/hooks/README.md +49 -0
  23. package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
  24. package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
  25. package/platforms/opencode/plugin/syntaur-session-env.js +30 -0
  26. package/platforms/pi/README.md +50 -0
  27. package/skills/clear-assignment/SKILL.md +1 -1
  28. package/skills/complete-assignment/SKILL.md +1 -1
  29. package/skills/grab-assignment/SKILL.md +5 -4
  30. package/skills/save-session-summary/SKILL.md +15 -6
  31. package/skills/track-session/SKILL.md +5 -4
@@ -0,0 +1,50 @@
1
+ # Pi Integration (Reference Only)
2
+
3
+ Syntaur resolves the **real** agent session id from the running process, not the
4
+ shared `.syntaur/context.json` scalar (a co-tenant clobbers it). For Pi, the job
5
+ is to inject `PI_SESSION_ID` per spawn so the CLI's `resolveOwnSessionId` picks
6
+ it up at layer 2. See `../SESSION-ID-RESOLUTION.md` for the full design.
7
+
8
+ > **Status:** Syntaur already ships a Pi extension at
9
+ > `extensions/syntaur/index.ts` (it tracks the dashboard session via
10
+ > `ctx.sessionId`). That extension does **not** yet inject `PI_SESSION_ID` into
11
+ > spawned commands — adding the `spawnHook` below to it is the remaining piece
12
+ > so Pi commands self-identify to `resolveOwnSessionId` (layer 2). This file
13
+ > documents that injector and its verification gate.
14
+
15
+ ## Injector — a `spawnHook` extension
16
+
17
+ Pi extensions (`@earendil-works/pi-coding-agent`, see its `docs/extensions.md`)
18
+ can capture the session id at `session_start` and inject env on every spawned
19
+ command via a bash `spawnHook`:
20
+
21
+ ```js
22
+ // syntaur-session-env extension (reference; verify against your Pi version)
23
+ export default {
24
+ name: 'syntaur-session-env',
25
+ session_start(ctx) {
26
+ // Capture the real id once, at session start.
27
+ this.sessionId = ctx.sessionManager.getSessionId();
28
+ },
29
+ // Injected into the environment of every spawned shell command.
30
+ spawnHook() {
31
+ return this.sessionId ? { env: { PI_SESSION_ID: this.sessionId } } : {};
32
+ },
33
+ };
34
+ ```
35
+
36
+ Alternatively, if Pi's start hook exposes the process pid, stamp the generic
37
+ marker `~/.syntaur/runtime/sessions/<pid>.json` (see `../SESSION-ID-RESOLUTION.md`)
38
+ and the resolver's layer-4 ancestor walk will find it without env injection.
39
+
40
+ ## Verification (needs a live Pi build — cannot run in CI)
41
+
42
+ From a Pi tool call:
43
+
44
+ ```bash
45
+ echo "$PI_SESSION_ID" # prints the real Pi session id
46
+ ```
47
+
48
+ `PI_SESSION_ID` is already in the resolver's layer-2 precedence list
49
+ (`src/utils/session-id.ts`), so once injected, `syntaur session save` (etc.)
50
+ attribute to the correct session automatically.
@@ -90,7 +90,7 @@ Do not delete the `.syntaur/` directory itself — other tooling may use it.
90
90
 
91
91
  ## Step 5: Close Session (optional)
92
92
 
93
- If the original `context.json` included a `sessionId` and the Syntaur dashboard is running, mark the session as cleared so the dashboard does not keep showing it as active:
93
+ If the Syntaur dashboard is running, mark this session as cleared so the dashboard does not keep showing it as active. Resolve `<session-id>` from *your* running process — prefer `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`), otherwise run `syntaur session resolve-id`. Only if neither yields an id, fall back to the `sessionId` you captured from the original `context.json` in Step 1 (before deletion) — that scalar is a shared, legacy hint a co-tenant can clobber, so don't treat it as authoritative:
94
94
 
95
95
  ```bash
96
96
  curl -s -X PATCH "http://localhost:$(cat ~/.syntaur/dashboard-port 2>/dev/null || echo 4800)/api/agent-sessions/<session-id>/status" \
@@ -101,7 +101,7 @@ Do NOT uncheck or rewrite superseded todo lines matching `- [x] ~~...~~ (superse
101
101
 
102
102
  ## Step 6: Close Session (optional)
103
103
 
104
- If `.syntaur/context.json` includes a `sessionId` and the Syntaur dashboard is running, mark the session as completed:
104
+ If the Syntaur dashboard is running, mark this session as completed. Resolve `<session-id>` from *your* running process — prefer `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`), otherwise run `syntaur session resolve-id`; fall back to the `sessionId` scalar in `.syntaur/context.json` only as a last resort (it is a shared, legacy hint a co-tenant can clobber, not authoritative):
105
105
 
106
106
  ```bash
107
107
  curl -s -X PATCH "http://localhost:$(cat ~/.syntaur/dashboard-port 2>/dev/null || echo 4800)/api/agent-sessions/<session-id>/status" \
@@ -113,14 +113,15 @@ Use absolute paths (expand `~` to the home directory). If `workspace.repository`
113
113
 
114
114
  ## Step 6: Register Agent Session (real IDs only — no UUIDs)
115
115
 
116
- `syntaur track-session` requires a `--session-id` from the agent runtime. Synthetic UUIDs are rejected. Source the real id in this order:
116
+ `syntaur track-session` requires a `--session-id` from the agent runtime. Synthetic UUIDs are rejected. Source the real per-process id in this order:
117
117
 
118
- 1. If `.syntaur/context.json` already has `sessionId` (platform SessionStart hook populated it), use it and the companion `transcriptPath` if present.
118
+ 1. Prefer the env var your runtime injects: `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`).
119
119
  2. Otherwise, fall back to the per-agent lookup:
120
- - **Claude Code**: the most-recently-modified `~/.claude/sessions/*.json` whose `cwd` matches `$(pwd)` — read its `sessionId`. The transcript path is `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl` (omit if the file is absent).
120
+ - **Claude Code**: the most-recently-modified `~/.claude/sessions/<pid>.json` whose `cwd` matches `$(pwd)` — read its `sessionId`. The transcript path is `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl` (omit if the file is absent).
121
121
  - **Codex**: the most-recently-modified file under `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` whose first-line `session_meta.payload.cwd` matches `$(pwd)`. Use `payload.id` as the session id and the full rollout path as the transcript path.
122
122
  - **Other agents**: use whatever real session identifier the runtime exposes. Do not invent one.
123
- 3. If no real id can be resolved, stop and tell the user to restart the session so the platform hook can populate it, or to run `/rename <assignment-slug>` (Claude Code) and retry.
123
+ 3. Only as a last resort, fall back to the `sessionId` scalar already in `.syntaur/context.json` (and the companion `transcriptPath` if present). That scalar is a shared, legacy hint a co-tenant sharing this workspace can clobber — never treat it as authoritative.
124
+ 4. If no real id can be resolved, stop and tell the user to restart the session so the platform hook can populate it, or to run `/rename <assignment-slug>` (Claude Code) and retry.
124
125
 
125
126
  After resolving, merge `sessionId` + `transcriptPath` back into context.json. Then register:
126
127
 
@@ -32,9 +32,14 @@ If the file does not exist, tell the user: "No active assignment found. Run `gra
32
32
 
33
33
  Extract:
34
34
  - `assignmentDir` (absolute path) — required.
35
- - `sessionId` — required, must be the real agent runtime session id.
36
35
 
37
- If `sessionId` is missing, empty, or `null`, abort with: "Session not tracked. Run `syntaur track-session ...` first." Do not invent or generate a session id.
36
+ **Do not read the session id from `context.json` for identity.** That scalar is
37
+ a shared, legacy hint a co-tenant can clobber. The session id is resolved from
38
+ *your* running process — prefer, in order:
39
+ 1. `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`) if your runtime injects it.
40
+ 2. Otherwise omit `--session-id` entirely and let `syntaur session save` resolve it (it walks env → process tree → transcript, and falls back to the `context.json` hint only as a last resort).
41
+
42
+ Never invent or generate a session id.
38
43
 
39
44
  ## Step 2: Author the summary body
40
45
 
@@ -75,13 +80,17 @@ Pass the body to `syntaur session save` (it owns the directory, frontmatter,
75
80
  and `created`-preservation):
76
81
 
77
82
  ```bash
78
- syntaur session save --session-id <sessionId> --from-file <body.md>
79
- # or pipe it: printf '%s' "$BODY" | syntaur session save --session-id <sessionId>
83
+ # Recommended: omit --session-id and let the CLI resolve YOUR own session id.
84
+ printf '%s' "$BODY" | syntaur session save
85
+ # or from a file: syntaur session save --from-file <body.md>
86
+ # Pass --session-id <id> only to override (e.g. you already have $CLAUDE_CODE_SESSION_ID).
80
87
  ```
81
88
 
82
89
  Resolves the active assignment from `.syntaur/context.json` (or pass
83
- `--assignment <slug> [--project <slug>]`), and `--session-id` defaults to the
84
- context's `sessionId`. The command:
90
+ `--assignment <slug> [--project <slug>]`). `--session-id` now defaults to the
91
+ **resolved** session (env → process tree → transcript), falling back to the
92
+ `context.json` hint only as a last resort — so a co-tenant that clobbered the
93
+ shared scalar can't make you write under the wrong id. The command:
85
94
 
86
95
  - Creates `<assignmentDir>/sessions/<sessionId>/` (idempotent) and writes
87
96
  `summary.md` — a **single document per session id**, overwritten in place;
@@ -29,11 +29,12 @@ Extract optional flags from the argument string:
29
29
 
30
30
  ### Step 2: Source the real session id + transcript path
31
31
 
32
- In priority order:
32
+ Resolve the session id from *your* running process, in priority order:
33
33
 
34
- 1. Read `.syntaur/context.json` if present. If it contains `sessionId`, use it. Also pick up `transcriptPath` if present.
35
- 2. Otherwise, read the most-recently-modified file under `~/.claude/sessions/*.json` whose `cwd` matches `$(pwd)` and use its `sessionId` field. The transcript path is conventionally `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`; include it if the file exists, otherwise omit.
36
- 3. If neither source yields an id, abort with: "Could not resolve a real Claude Code session id. Restart the Claude session so the SessionStart hook can populate `.syntaur/context.json`, or run `/rename <slug>` then try again."
34
+ 1. `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`) if your runtime injects it.
35
+ 2. Otherwise, read the most-recently-modified file under `~/.claude/sessions/<pid>.json` whose `cwd` matches `$(pwd)` and use its `sessionId` field. The transcript path is conventionally `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`; include it if the file exists, otherwise omit.
36
+ 3. Only as a last resort, fall back to the `sessionId` scalar in `.syntaur/context.json` (and the companion `transcriptPath` if present). This scalar is a shared, legacy hint a co-tenant sharing this workspace can clobber never treat it as authoritative.
37
+ 4. If no source yields an id, abort with: "Could not resolve a real Claude Code session id. Restart the Claude session so the SessionStart hook can populate `.syntaur/context.json`, or run `/rename <slug>` then try again."
37
38
 
38
39
  DO NOT generate a UUID. `syntaur track-session` rejects missing session IDs.
39
40