syntaur 0.45.0 → 0.47.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.
- package/.claude-plugin/plugin.json +1 -1
- package/dashboard/dist/assets/{_basePickBy-RQBuJKcX.js → _basePickBy-DgR0_P-o.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-_J7s4kD3.js → _baseUniq-C8_Ych09.js} +1 -1
- package/dashboard/dist/assets/{arc-_9SyUgKQ.js → arc-yMHz4vGa.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-C8LeFMgr.js → architectureDiagram-2XIMDMQ5-ColWcH3P.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-gMh0EPEh.js → blockDiagram-WCTKOSBZ-Bo8Npvfq.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-cHwecwLI.js → c4Diagram-IC4MRINW-B2ky8AT7.js} +1 -1
- package/dashboard/dist/assets/channel-CUTEvTdk.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-Bb2anYuQ.js → chunk-4BX2VUAB-CyF6Z6dx.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-DYIRGzA1.js → chunk-55IACEB6-BJOEnwNN.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-sgRWBbaF.js → chunk-FMBD7UC4-D3siQyQ4.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-DlYKMl_j.js → chunk-JSJVCQXG-DKGuxEMf.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-D0YDLAOF.js → chunk-KX2RTZJC-CNIWWO2F.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-D-Y-CUx6.js → chunk-NQ4KR5QH-DXt05c7h.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-D7FpSvb5.js → chunk-QZHKN3VN-CM63uYnf.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-CtXgQLdS.js → chunk-WL4C6EOR-Dqvl_14m.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-Bkoc7orC.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Bkoc7orC.js +1 -0
- package/dashboard/dist/assets/clone-CltBg7cH.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-YbTaohoJ.js → cose-bilkent-S5V4N54A-WBLtT1w9.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CMtwGAnP.js → dagre-KLK3FWXG-DIdQdwa7.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-D8wBMBAX.js → diagram-E7M64L7V-BEH6P_Sk.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-DfudLpiJ.js → diagram-IFDJBPK2-BuhxBcSy.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-CyMy61wE.js → diagram-P4PSJMXO-DPSNVVzN.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BlB4ZQl9.js → erDiagram-INFDFZHY-DYJb_rF5.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-DbhDQJM3.js → flowDiagram-PKNHOUZH-B9_8BI26.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-DJFqteNi.js → ganttDiagram-A5KZAMGK-Bsg3QOhs.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-D8etA_mm.js → gitGraphDiagram-K3NZZRJ6-Cf5G9x_K.js} +1 -1
- package/dashboard/dist/assets/{graph-Ce86jeZn.js → graph-DyXfcrIH.js} +1 -1
- package/dashboard/dist/assets/index-C3kYxhbQ.js +567 -0
- package/dashboard/dist/assets/index-DKr21dk8.css +1 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Cx35U-h8.js → infoDiagram-LFFYTUFH-Bu1zlXs2.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-C04Y2nj8.js → ishikawaDiagram-PHBUUO56-fb8C-XRT.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-D8-cxbxE.js → journeyDiagram-4ABVD52K-smlBWs2O.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-DVKqMylP.js → kanban-definition-K7BYSVSG-Bz1AxFRE.js} +1 -1
- package/dashboard/dist/assets/{layout-98xZDpgu.js → layout-VsTD3onG.js} +1 -1
- package/dashboard/dist/assets/{linear-0jk_IwAc.js → linear-CE8xncGu.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-C337VWfr.js → mermaid.core-C0KQpDyW.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-8sNYGYEP.js → mindmap-definition-YRQLILUH-SRE5Immj.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-afcmzHxf.js → pieDiagram-SKSYHLDU-CaZ_aCcD.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-B4RjcpOq.js → quadrantDiagram-337W2JSQ-Dd6MIruu.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CRavU6cI.js → requirementDiagram-Z7DCOOCP-BBXvP53l.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DFomU3z-.js → sankeyDiagram-WA2Y5GQK-DnS1SMIm.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CGKO7nmK.js → sequenceDiagram-2WXFIKYE-CLHJ1Uhx.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BjFI1K8h.js → stateDiagram-RAJIS63D-B6vrAeYw.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BeqNZKbk.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BBo8XJFG.js → timeline-definition-YZTLITO2-BlHwGfnL.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-COd6i6TE.js → treemap-KZPCXAKY-D9kOGUYR.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CGQweQ36.js → vennDiagram-LZ73GAT5-BpQgeveT.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-mfJ5So7N.js → xychartDiagram-JWTSCODW-DRch79fE.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +1405 -210
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +2092 -1485
- package/dist/index.js.map +1 -1
- package/dist/launch/index.d.ts +19 -0
- package/dist/launch/index.js +528 -17
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/SESSION-ID-RESOLUTION.md +41 -4
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
- package/platforms/claude-code/hooks/session-cleanup.sh +25 -64
- package/platforms/claude-code/hooks/session-start.sh +35 -109
- package/platforms/claude-code/skills/track-session/SKILL.md +12 -60
- package/platforms/codex/.codex-plugin/plugin.json +1 -1
- package/platforms/codex/skills/track-session/SKILL.md +12 -60
- package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
- package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
- package/skills/track-session/SKILL.md +12 -60
- package/dashboard/dist/assets/channel-C36dnl_e.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BsoGa6_a.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BsoGa6_a.js +0 -1
- package/dashboard/dist/assets/clone-Bz6jW3OY.js +0 -1
- package/dashboard/dist/assets/index-DRng26Jg.js +0 -567
- package/dashboard/dist/assets/index-DzHQIE2n.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BtxefYKD.js +0 -1
package/package.json
CHANGED
|
@@ -18,6 +18,32 @@ Each agent's job is only to make its real id reachable by layer 2, 3, or 4 — i
|
|
|
18
18
|
to **normalize the key**, never to synthesize an id. Per-agent status and the
|
|
19
19
|
exact injector below.
|
|
20
20
|
|
|
21
|
+
## Session TRACKING vs id resolution (scanner is the universal floor)
|
|
22
|
+
|
|
23
|
+
Id resolution (above) answers "what is MY session id, right now, in-process."
|
|
24
|
+
Session **tracking** — every session eventually appearing in the sessions DB —
|
|
25
|
+
no longer depends on it:
|
|
26
|
+
|
|
27
|
+
- **Floor — filesystem scanner** (`syntaur session scan`,
|
|
28
|
+
`src/sessions/scanner.ts`): walks each registry target's `sessions` descriptor
|
|
29
|
+
(`AgentTarget.sessions` in `src/targets/registry.ts`; claude + codex today),
|
|
30
|
+
upserts every discovered session with its real transcript timestamps, links
|
|
31
|
+
project/assignment from `<cwd>/.syntaur/context.json`, derives liveness
|
|
32
|
+
(lsof transcript-open, else mtime freshness), and sweeps stale `active` rows
|
|
33
|
+
to `stopped` (`ended` backdated to last mtime). Runs on the dashboard's
|
|
34
|
+
autodiscovery interval (and at start) plus standalone via the CLI. This is
|
|
35
|
+
**Codex's only tracking path** (no SessionStart hook) and the retroactive
|
|
36
|
+
backfill for everything.
|
|
37
|
+
- **Fast path — hooks**: Claude's SessionStart/SessionEnd hooks are thin
|
|
38
|
+
wrappers over `syntaur session register|stop --from-hook` (direct DB writes,
|
|
39
|
+
zero tokens, no dashboard needed). Every session registers — standalone ones
|
|
40
|
+
included.
|
|
41
|
+
- **Birth path — launcher**: `executeLaunchPlan` writes a runtime marker for
|
|
42
|
+
any agent it spawns (pending — no sessionId — for fresh/fork; with the id for
|
|
43
|
+
resume-mode, plus an immediate DB row).
|
|
44
|
+
- Config: `session.autoTrack: all | workspaces-only | off` in
|
|
45
|
+
`~/.syntaur/config.md` (default `all`) gates all three paths.
|
|
46
|
+
|
|
21
47
|
## Generic runtime marker (layer 4)
|
|
22
48
|
|
|
23
49
|
Any agent whose start/early hook learns the real id but cannot inject env can
|
|
@@ -33,6 +59,11 @@ stamp a marker that both the resolver and the Codex cleanup hook read:
|
|
|
33
59
|
Helpers: `writeRuntimeMarker` / `readRuntimeMarker` in `src/utils/session-id.ts`.
|
|
34
60
|
Override the dir in tests/hooks via `$SYNTAUR_RUNTIME_SESSIONS_DIR`.
|
|
35
61
|
|
|
62
|
+
The launch path also writes **pending** markers (no `sessionId`) at spawn time
|
|
63
|
+
for fresh/fork launches — ids are never synthesized. `readRuntimeMarker`
|
|
64
|
+
rejects pending markers, so they never resolve an id until backfilled; the
|
|
65
|
+
scanner registers those sessions from their transcripts instead.
|
|
66
|
+
|
|
36
67
|
---
|
|
37
68
|
|
|
38
69
|
## Claude Code — EXACT (shipped, no new runtime code)
|
|
@@ -40,8 +71,10 @@ Override the dir in tests/hooks via `$SYNTAUR_RUNTIME_SESSIONS_DIR`.
|
|
|
40
71
|
Native `CLAUDE_CODE_SESSION_ID` env is injected into every child process, so a
|
|
41
72
|
`syntaur` command is a child and layer 2 hits. Confirmed live:
|
|
42
73
|
`CLAUDE_CODE_SESSION_ID` and the ancestor-pid file `~/.claude/sessions/<pid>.json`
|
|
43
|
-
resolve to the same id. The SessionStart hook
|
|
44
|
-
`
|
|
74
|
+
resolve to the same id. The SessionStart hook (now a thin wrapper over
|
|
75
|
+
`syntaur session register --from-hook`) registers EVERY session directly in the
|
|
76
|
+
DB and still mirrors the id into `context.json` as a legacy hint (back-compat).
|
|
77
|
+
**Fixes the reported bug.**
|
|
45
78
|
|
|
46
79
|
## OpenCode — injector ships as a plugin (live-build gate)
|
|
47
80
|
|
|
@@ -109,9 +142,13 @@ id on an existing hook's stdin. If/when it does, an early hook can
|
|
|
109
142
|
Codex cleanup hook will resolve it exactly.
|
|
110
143
|
|
|
111
144
|
**Honest floor today:**
|
|
145
|
+
- The **session scanner** is Codex's tracking path: its `sessions` descriptor
|
|
146
|
+
walks `~/.codex/sessions/**/rollout-*.jsonl` and registers every session with
|
|
147
|
+
real timestamps — no hook required.
|
|
112
148
|
- `session-cleanup.sh` no longer trusts the clobbered scalar; it resolves only
|
|
113
|
-
from an exact runtime marker and otherwise **skips** (the
|
|
114
|
-
|
|
149
|
+
from an exact runtime marker and otherwise **skips** (the scanner's sweep
|
|
150
|
+
marks the dead session stopped on its next tick). It never mis-stops a
|
|
151
|
+
co-tenant.
|
|
115
152
|
- For attribution that must be exact now, pass explicit `--session-id`
|
|
116
153
|
(sourced from `payload.id` of the matching `~/.codex/sessions/.../rollout-*.jsonl`,
|
|
117
154
|
as `platforms/codex/scripts/resolve-session.sh` already does on a cwd basis).
|
|
@@ -1,72 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Syntaur SessionEnd Hook
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# Reads JSON from stdin, always exits 0.
|
|
2
|
+
# Syntaur SessionEnd Hook — thin wrapper around `syntaur session stop`.
|
|
3
|
+
# The CLI resolves the ENDING session's id (stdin .session_id first, the shared
|
|
4
|
+
# context.json scalar only as a fallback) and marks the row stopped with a
|
|
5
|
+
# direct DB write. No dashboard required. Reads JSON from stdin, always exits 0.
|
|
6
6
|
|
|
7
|
-
# --- Safety: never fail ---
|
|
8
7
|
set -o pipefail 2>/dev/null || true
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
if ! command -v jq &>/dev/null; then
|
|
12
|
-
exit 0
|
|
13
|
-
fi
|
|
9
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
14
10
|
|
|
15
|
-
# --- Step 2: Read stdin ---
|
|
16
11
|
INPUT=$(cat)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# workspace.
|
|
38
|
-
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
|
39
|
-
if [ -z "$SESSION_ID" ]; then
|
|
40
|
-
SESSION_ID=$(jq -r '.sessionId // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
41
|
-
fi
|
|
42
|
-
MISSION_SLUG=$(jq -r '.projectSlug // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
43
|
-
ASSIGNMENT_SLUG=$(jq -r '.assignmentSlug // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
44
|
-
|
|
45
|
-
# No real session id available — exit quietly. We never synthesize one.
|
|
46
|
-
[ -z "$SESSION_ID" ] && exit 0
|
|
47
|
-
|
|
48
|
-
# Defensive: the id becomes a URL path segment — reject anything that isn't a
|
|
49
|
-
# plain id (UUID/ULID charset). Real Claude session ids never trip this.
|
|
50
|
-
case "$SESSION_ID" in
|
|
51
|
-
*[!A-Za-z0-9_-]*) exit 0 ;;
|
|
52
|
-
esac
|
|
53
|
-
|
|
54
|
-
# --- Dashboard endpoint resolution (mirror session-start.sh exactly so start
|
|
55
|
-
# and end hooks always target the same host:port) ---
|
|
56
|
-
PORT="${SYNTAUR_DASHBOARD_PORT:-}"
|
|
57
|
-
if [ -z "$PORT" ]; then
|
|
58
|
-
PORT=$(cat "$HOME/.syntaur/dashboard-port" 2>/dev/null || echo "4800")
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# --- Step 5: Mark session as stopped via dashboard API ---
|
|
62
|
-
BODY="{\"status\": \"stopped\"}"
|
|
63
|
-
if [ -n "$MISSION_SLUG" ]; then
|
|
64
|
-
BODY="{\"status\": \"stopped\", \"projectSlug\": \"${MISSION_SLUG}\"}"
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
curl -sf --max-time 3 -X PATCH "http://127.0.0.1:${PORT}/api/agent-sessions/${SESSION_ID}/status" \
|
|
68
|
-
-H "Content-Type: application/json" \
|
|
69
|
-
-d "$BODY" \
|
|
70
|
-
-o /dev/null 2>/dev/null || true
|
|
12
|
+
[ -z "$INPUT" ] && exit 0
|
|
13
|
+
|
|
14
|
+
command -v syntaur >/dev/null 2>&1 || exit 0
|
|
15
|
+
|
|
16
|
+
# Bounded SIGKILL watchdog (portable — no `timeout` on stock macOS). ~4s stays
|
|
17
|
+
# under the hook's `timeout: 5` budget. A stale CLI without the subcommand
|
|
18
|
+
# exits non-zero — swallowed; the scanner sweeps the row on its next tick.
|
|
19
|
+
syntaur_bounded_stop() {
|
|
20
|
+
local cpid kpid rc
|
|
21
|
+
printf '%s' "$INPUT" | syntaur session stop --from-hook >/dev/null 2>&1 &
|
|
22
|
+
cpid=$!
|
|
23
|
+
( sleep 4; kill -KILL "$cpid" 2>/dev/null ) >/dev/null 2>&1 &
|
|
24
|
+
kpid=$!
|
|
25
|
+
wait "$cpid" 2>/dev/null
|
|
26
|
+
rc=$?
|
|
27
|
+
kill -KILL "$kpid" 2>/dev/null
|
|
28
|
+
wait "$kpid" 2>/dev/null
|
|
29
|
+
return "$rc"
|
|
30
|
+
}
|
|
31
|
+
syntaur_bounded_stop || true
|
|
71
32
|
|
|
72
33
|
exit 0
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Syntaur SessionStart Hook
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
2
|
+
# Syntaur SessionStart Hook — thin wrapper around `syntaur session register`.
|
|
3
|
+
#
|
|
4
|
+
# Registers EVERY session (standalone sessions included — no context.json
|
|
5
|
+
# required). The CLI does the deterministic work: parses the stdin payload,
|
|
6
|
+
# merges session fields into an EXISTING .syntaur/context.json (never creates
|
|
7
|
+
# one), and writes the session row directly to the sessions DB. No dashboard
|
|
8
|
+
# required.
|
|
9
9
|
#
|
|
10
10
|
# Reads JSON from stdin per Claude Code SessionStart contract:
|
|
11
11
|
# { "session_id": "...", "transcript_path": "...", "cwd": "...", ... }
|
|
@@ -19,20 +19,26 @@ command -v jq >/dev/null 2>&1 || exit 0
|
|
|
19
19
|
INPUT=$(cat)
|
|
20
20
|
[ -z "$INPUT" ] && exit 0
|
|
21
21
|
|
|
22
|
-
# Run
|
|
23
|
-
# is bounded even where `timeout`/`gtimeout` are absent (stock
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
22
|
+
# Run a syntaur CLI invocation with a PORTABLE SIGKILL watchdog (background +
|
|
23
|
+
# kill) so it is bounded even where `timeout`/`gtimeout` are absent (stock
|
|
24
|
+
# macOS). $1 = deadline in seconds; remaining args = the syntaur subcommand.
|
|
25
|
+
# Stdin is forwarded; stdout is captured to a temp file and printed on success.
|
|
26
|
+
# Returns non-zero if the CLI is absent, hangs past the deadline, or fails —
|
|
27
|
+
# including a stale installed CLI that predates the subcommand.
|
|
28
|
+
syntaur_bounded() {
|
|
27
29
|
command -v syntaur >/dev/null 2>&1 || return 1
|
|
28
|
-
local out cpid kpid rc
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
local deadline out cpid kpid rc
|
|
31
|
+
deadline=$1
|
|
32
|
+
shift
|
|
33
|
+
out="${TMPDIR:-/tmp}/syntaur-hook.$$"
|
|
34
|
+
# `<&0` forwards the caller's stdin explicitly — background commands default
|
|
35
|
+
# to stdin-from-/dev/null in non-interactive shells, which would silently
|
|
36
|
+
# drop the piped hook payload.
|
|
37
|
+
syntaur "$@" <&0 >"$out" 2>/dev/null &
|
|
31
38
|
cpid=$!
|
|
32
39
|
# Hard deadline via SIGKILL (uncatchable — a TERM-ignoring or hung CLI cannot
|
|
33
|
-
# block us)
|
|
34
|
-
|
|
35
|
-
( sleep 1; kill -KILL "$cpid" 2>/dev/null ) >/dev/null 2>&1 &
|
|
40
|
+
# block us), guaranteeing the `wait` below returns.
|
|
41
|
+
( sleep "$deadline"; kill -KILL "$cpid" 2>/dev/null ) >/dev/null 2>&1 &
|
|
36
42
|
kpid=$!
|
|
37
43
|
wait "$cpid" 2>/dev/null
|
|
38
44
|
rc=$?
|
|
@@ -40,7 +46,7 @@ syntaur_bounded_version() {
|
|
|
40
46
|
kill -KILL "$kpid" 2>/dev/null
|
|
41
47
|
wait "$kpid" 2>/dev/null
|
|
42
48
|
if [ "$rc" -eq 0 ]; then
|
|
43
|
-
|
|
49
|
+
cat "$out" 2>/dev/null
|
|
44
50
|
rm -f "$out"
|
|
45
51
|
return 0
|
|
46
52
|
fi
|
|
@@ -50,15 +56,14 @@ syntaur_bounded_version() {
|
|
|
50
56
|
|
|
51
57
|
# --- Best-effort, non-blocking: warn when the installed plugin is stale vs the
|
|
52
58
|
# running CLI (the CLI updates via npm; the marketplace plugin copy does not).
|
|
53
|
-
#
|
|
54
|
-
# failure → do nothing. NEVER changes the exit status (the hook always exits 0).
|
|
59
|
+
# Any failure → do nothing. NEVER changes the exit status (always exits 0).
|
|
55
60
|
syntaur_plugin_drift_warn() {
|
|
56
61
|
local marker marker_ver cli_ver msg
|
|
57
62
|
marker="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/syntaur}/.syntaur-install.json"
|
|
58
63
|
[ -f "$marker" ] || return 0
|
|
59
64
|
marker_ver=$(jq -r '.packageVersion // empty' "$marker" 2>/dev/null)
|
|
60
65
|
[ -n "$marker_ver" ] || return 0
|
|
61
|
-
cli_ver=$(
|
|
66
|
+
cli_ver=$(syntaur_bounded 1 --version | tr -d '[:space:]') || return 0
|
|
62
67
|
[ -n "$cli_ver" ] || return 0
|
|
63
68
|
[ "$marker_ver" = "$cli_ver" ] && return 0
|
|
64
69
|
msg="Syntaur plugin v${marker_ver} differs from the installed CLI v${cli_ver} — run \`syntaur install-plugin --force\` to refresh."
|
|
@@ -66,100 +71,21 @@ syntaur_plugin_drift_warn() {
|
|
|
66
71
|
}
|
|
67
72
|
syntaur_plugin_drift_warn || true
|
|
68
73
|
|
|
69
|
-
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
|
70
|
-
TRANSCRIPT_PATH=$(printf '%s' "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
71
|
-
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
72
|
-
|
|
73
|
-
[ -z "$SESSION_ID" ] && exit 0
|
|
74
|
-
[ -z "$CWD" ] && exit 0
|
|
75
|
-
|
|
76
|
-
CONTEXT_FILE="$CWD/.syntaur/context.json"
|
|
77
|
-
|
|
78
|
-
# REQUIRED invariant: only operate on an EXISTING context file. If the current
|
|
79
|
-
# cwd has no active Syntaur assignment, leave the filesystem untouched.
|
|
80
|
-
[ ! -f "$CONTEXT_FILE" ] && exit 0
|
|
81
|
-
|
|
82
|
-
# --- (1) Merge session fields into context.json.
|
|
83
|
-
# Always replace both sessionId and transcriptPath together. If the incoming
|
|
84
|
-
# transcript_path is empty, explicitly null the stored transcriptPath so a new
|
|
85
|
-
# session never inherits a stale transcript path from the prior session.
|
|
86
|
-
#
|
|
87
|
-
# Also resolve the latest session-summary path (mid-assignment continuity) so
|
|
88
|
-
# the resuming agent's first protocol-read can pick it up. Selection rule:
|
|
89
|
-
# the summary.md with the most recent file mtime under
|
|
90
|
-
# <assignmentDir>/sessions/*/summary.md. Null if none exists.
|
|
91
|
-
ASSIGNMENT_DIR_RAW=$(jq -r '.assignmentDir // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
92
|
-
ASSIGNMENT_DIR="${ASSIGNMENT_DIR_RAW/#\~/$HOME}"
|
|
93
|
-
|
|
94
|
-
LATEST_SUMMARY=""
|
|
95
|
-
if [ -n "$ASSIGNMENT_DIR" ] && [ -d "$ASSIGNMENT_DIR/sessions" ]; then
|
|
96
|
-
# Prefer GNU-style stat formatting if available (Linux); fall back to BSD
|
|
97
|
-
# (macOS). Resolve newest-by-mtime portably.
|
|
98
|
-
while IFS= read -r -d '' f; do
|
|
99
|
-
if M=$(stat -f '%m' "$f" 2>/dev/null) || M=$(stat -c '%Y' "$f" 2>/dev/null); then
|
|
100
|
-
printf '%s\t%s\n' "$M" "$f"
|
|
101
|
-
fi
|
|
102
|
-
done < <(find "$ASSIGNMENT_DIR/sessions" -mindepth 2 -maxdepth 2 -type f -name 'summary.md' -print0 2>/dev/null) \
|
|
103
|
-
| sort -rn -k1,1 \
|
|
104
|
-
| head -1 \
|
|
105
|
-
| cut -f2- > "${CONTEXT_FILE}.summary.tmp.$$" 2>/dev/null
|
|
106
|
-
LATEST_SUMMARY=$(cat "${CONTEXT_FILE}.summary.tmp.$$" 2>/dev/null || true)
|
|
107
|
-
rm -f "${CONTEXT_FILE}.summary.tmp.$$"
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
TMP="${CONTEXT_FILE}.tmp.$$"
|
|
111
|
-
jq \
|
|
112
|
-
--arg sid "$SESSION_ID" \
|
|
113
|
-
--arg tp "$TRANSCRIPT_PATH" \
|
|
114
|
-
--arg lsp "$LATEST_SUMMARY" \
|
|
115
|
-
'. + {sessionId: $sid, transcriptPath: (if ($tp | length) > 0 then $tp else null end), latestSessionSummaryPath: (if ($lsp | length) > 0 then $lsp else null end)}' \
|
|
116
|
-
"$CONTEXT_FILE" > "$TMP" 2>/dev/null \
|
|
117
|
-
&& mv "$TMP" "$CONTEXT_FILE" 2>/dev/null \
|
|
118
|
-
|| rm -f "$TMP"
|
|
119
|
-
|
|
120
|
-
# --- (2) Best-effort pre-registration in the dashboard.
|
|
121
|
-
# Read project/assignment context if present so the pre-registered row is
|
|
122
|
-
# already linked. Upsert semantics on the server mean this is idempotent with
|
|
123
|
-
# later /track-session or grab-assignment calls.
|
|
124
|
-
MISSION_SLUG=$(jq -r '.projectSlug // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
125
|
-
ASSIGNMENT_SLUG=$(jq -r '.assignmentSlug // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
126
|
-
|
|
127
74
|
# Capture the terminal-session PID that owns this Claude process. Claude
|
|
128
75
|
# Code's SessionStart payload does NOT include a parent PID, so we approximate
|
|
129
|
-
# by walking up one level from the hook's own PID —
|
|
130
|
-
# hook was spawned by, which is the shell that owns claude. The server uses
|
|
131
|
-
# this for liveness checks (and an auto-captured ps lstart= for recycling
|
|
132
|
-
# defense). If `ps` is unavailable or returns nothing we just omit the field.
|
|
76
|
+
# by walking up one level from the hook's own PID — the shell that owns claude.
|
|
133
77
|
PID="$(ps -o ppid= -p $$ 2>/dev/null | tr -d '[:space:]' || true)"
|
|
134
78
|
if [ -n "$PID" ] && ! printf '%s' "$PID" | grep -q '^[0-9]\+$'; then
|
|
135
79
|
PID=""
|
|
136
80
|
fi
|
|
137
81
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
--arg tp "$TRANSCRIPT_PATH" \
|
|
146
|
-
--arg proj "$MISSION_SLUG" \
|
|
147
|
-
--arg assn "$ASSIGNMENT_SLUG" \
|
|
148
|
-
--arg path "$CWD" \
|
|
149
|
-
--arg pid "$PID" \
|
|
150
|
-
'{ agent: "claude", sessionId: $sid, path: $path }
|
|
151
|
-
+ (if ($tp | length) > 0 then {transcriptPath: $tp} else {} end)
|
|
152
|
-
+ (if ($proj | length) > 0 then {projectSlug: $proj} else {} end)
|
|
153
|
-
+ (if ($assn | length) > 0 then {assignmentSlug: $assn} else {} end)
|
|
154
|
-
+ (if ($pid | length) > 0 then {pid: ($pid | tonumber)} else {} end)' 2>/dev/null)
|
|
155
|
-
|
|
156
|
-
if [ -n "$BODY" ]; then
|
|
157
|
-
# --max-time bounds the hook's wall-clock cost if the dashboard socket
|
|
158
|
-
# accepts but then hangs. The hook itself is registered with timeout: 5.
|
|
159
|
-
curl -sf --max-time 3 -X POST "http://127.0.0.1:${PORT}/api/agent-sessions" \
|
|
160
|
-
-H "Content-Type: application/json" \
|
|
161
|
-
-d "$BODY" \
|
|
162
|
-
-o /dev/null 2>/dev/null || true
|
|
82
|
+
# Register EVERY session via the CLI (context.json merge + direct DB write).
|
|
83
|
+
# ~4s deadline stays under the hook's `timeout: 5` budget. A stale CLI without
|
|
84
|
+
# the subcommand exits non-zero — swallowed; the scanner is the safety net.
|
|
85
|
+
if [ -n "$PID" ]; then
|
|
86
|
+
printf '%s' "$INPUT" | syntaur_bounded 4 session register --from-hook --pid "$PID" >/dev/null 2>&1 || true
|
|
87
|
+
else
|
|
88
|
+
printf '%s' "$INPUT" | syntaur_bounded 4 session register --from-hook >/dev/null 2>&1 || true
|
|
163
89
|
fi
|
|
164
90
|
|
|
165
91
|
exit 0
|
|
@@ -5,84 +5,36 @@ description: Use when the user asks to track, register, or log this Claude Code
|
|
|
5
5
|
|
|
6
6
|
# Track Session
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Attach a description and/or a project+assignment link to the current agent session's row in the Syntaur dashboard.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Plain registration is automatic now — the SessionStart hook registers every session (and the background scanner backfills any the hook missed), so this skill only matters for the one remaining manual case: adding a description or an explicit project/assignment link. The CLI self-resolves the calling session's id (env → process-tree markers → transcript scan); never pass a synthesized id.
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
14
|
User arguments: `$ARGUMENTS`
|
|
15
15
|
|
|
16
|
-
- (no args) —
|
|
17
|
-
- `--description "<text>"` —
|
|
18
|
-
- `--project <slug> --assignment <slug>` —
|
|
16
|
+
- (no args) — upsert the session row as-is (rarely needed; the hook already did this)
|
|
17
|
+
- `--description "<text>"` — attach a description
|
|
18
|
+
- `--project <slug> --assignment <slug>` — link to a project assignment
|
|
19
19
|
- `--description "<text>" --project <slug> --assignment <slug>` — both
|
|
20
20
|
|
|
21
21
|
## Workflow
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Extract optional flags from the argument string:
|
|
26
|
-
- `--description "<text>"` or `--description <text>` — session description
|
|
27
|
-
- `--project <slug>` — project to link to
|
|
28
|
-
- `--assignment <slug>` — assignment to link to
|
|
29
|
-
|
|
30
|
-
### Step 2: Source the real session id + transcript path
|
|
31
|
-
|
|
32
|
-
Resolve the session id from *your* running process, in priority order:
|
|
33
|
-
|
|
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."
|
|
38
|
-
|
|
39
|
-
DO NOT generate a UUID. `syntaur track-session` rejects missing session IDs.
|
|
40
|
-
|
|
41
|
-
### Step 3: Run the CLI command
|
|
42
|
-
|
|
43
|
-
Run the track-session CLI via Bash (use `dangerouslyDisableSandbox: true` since it writes to `~/.syntaur/`):
|
|
23
|
+
Run one Bash call (use `dangerouslyDisableSandbox: true` since it writes to `~/.syntaur/`), passing through whatever optional flags the user gave:
|
|
44
24
|
|
|
45
25
|
```bash
|
|
46
|
-
syntaur track-session \
|
|
47
|
-
--agent claude \
|
|
48
|
-
--session-id "$SESSION_ID" \
|
|
49
|
-
--transcript-path "$TRANSCRIPT_PATH" \
|
|
50
|
-
--path "$(pwd)" \
|
|
51
|
-
--pid "$(ps -o ppid= -p $$ | tr -d ' ')" \
|
|
26
|
+
syntaur track-session --agent claude \
|
|
52
27
|
[--description "<text>"] \
|
|
53
|
-
[--project <slug>]
|
|
54
|
-
[--assignment <slug>]
|
|
28
|
+
[--project <slug>] [--assignment <slug>]
|
|
55
29
|
```
|
|
56
30
|
|
|
57
|
-
|
|
31
|
+
The CLI resolves the session id, transcript-derived path, owning pid, and HEAD sha itself, and prints one of:
|
|
58
32
|
|
|
59
|
-
The CLI prints one of:
|
|
60
33
|
- `Registered standalone agent session <sessionId>.`
|
|
61
34
|
- `Registered agent session <sessionId> for <assignment> in <project>.`
|
|
62
35
|
|
|
63
|
-
Registration is idempotent — re-running
|
|
64
|
-
|
|
65
|
-
### Step 4: Merge context.json
|
|
66
|
-
|
|
67
|
-
Ensure `.syntaur/context.json` has the session fields (so SessionEnd and future `track-session` runs find them). Merge, don't overwrite:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
mkdir -p .syntaur
|
|
71
|
-
if [ -f .syntaur/context.json ]; then
|
|
72
|
-
jq --arg sid "$SESSION_ID" --arg tp "$TRANSCRIPT_PATH" \
|
|
73
|
-
'. + {sessionId: $sid} + (if ($tp | length) > 0 then {transcriptPath: $tp} else {} end)' \
|
|
74
|
-
.syntaur/context.json > .syntaur/context.json.tmp \
|
|
75
|
-
&& mv .syntaur/context.json.tmp .syntaur/context.json
|
|
76
|
-
else
|
|
77
|
-
jq -n --arg sid "$SESSION_ID" --arg tp "$TRANSCRIPT_PATH" \
|
|
78
|
-
'{sessionId: $sid} + (if ($tp | length) > 0 then {transcriptPath: $tp} else {} end)' \
|
|
79
|
-
> .syntaur/context.json
|
|
80
|
-
fi
|
|
81
|
-
```
|
|
36
|
+
Registration is idempotent — re-running with the same session id safely upserts the description/link onto the existing row.
|
|
82
37
|
|
|
83
|
-
|
|
38
|
+
If it errors with "Could not resolve a session id", restart the Claude session so the SessionStart hook can register it (or pass `--session-id <id>` with a real agent-generated id — never synthesize one).
|
|
84
39
|
|
|
85
|
-
|
|
86
|
-
- The session was registered (include the short session id).
|
|
87
|
-
- It will be auto-stopped when this conversation ends via the SessionEnd hook.
|
|
88
|
-
- If linked to a project, mention which project/assignment.
|
|
40
|
+
Confirm to the user: the row was updated (include the short session id), it auto-stops at SessionEnd, and which project/assignment it is linked to, if any.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "syntaur",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Run Syntaur project and assignment workflows from Codex, including claiming work, planning, completing handoffs, session/server tracking, save-session-summary continuity, and write-boundary enforcement.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Brennen"
|
|
@@ -5,84 +5,36 @@ description: Use when the user asks to track, register, or log this Claude Code
|
|
|
5
5
|
|
|
6
6
|
# Track Session
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Attach a description and/or a project+assignment link to the current agent session's row in the Syntaur dashboard.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Plain registration is automatic now — the SessionStart hook registers every session (and the background scanner backfills any the hook missed), so this skill only matters for the one remaining manual case: adding a description or an explicit project/assignment link. The CLI self-resolves the calling session's id (env → process-tree markers → transcript scan); never pass a synthesized id.
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
14
|
User arguments: `$ARGUMENTS`
|
|
15
15
|
|
|
16
|
-
- (no args) —
|
|
17
|
-
- `--description "<text>"` —
|
|
18
|
-
- `--project <slug> --assignment <slug>` —
|
|
16
|
+
- (no args) — upsert the session row as-is (rarely needed; the hook already did this)
|
|
17
|
+
- `--description "<text>"` — attach a description
|
|
18
|
+
- `--project <slug> --assignment <slug>` — link to a project assignment
|
|
19
19
|
- `--description "<text>" --project <slug> --assignment <slug>` — both
|
|
20
20
|
|
|
21
21
|
## Workflow
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Extract optional flags from the argument string:
|
|
26
|
-
- `--description "<text>"` or `--description <text>` — session description
|
|
27
|
-
- `--project <slug>` — project to link to
|
|
28
|
-
- `--assignment <slug>` — assignment to link to
|
|
29
|
-
|
|
30
|
-
### Step 2: Source the real session id + transcript path
|
|
31
|
-
|
|
32
|
-
Resolve the session id from *your* running process, in priority order:
|
|
33
|
-
|
|
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."
|
|
38
|
-
|
|
39
|
-
DO NOT generate a UUID. `syntaur track-session` rejects missing session IDs.
|
|
40
|
-
|
|
41
|
-
### Step 3: Run the CLI command
|
|
42
|
-
|
|
43
|
-
Run the track-session CLI via Bash (use `dangerouslyDisableSandbox: true` since it writes to `~/.syntaur/`):
|
|
23
|
+
Run one Bash call (use `dangerouslyDisableSandbox: true` since it writes to `~/.syntaur/`), passing through whatever optional flags the user gave:
|
|
44
24
|
|
|
45
25
|
```bash
|
|
46
|
-
syntaur track-session \
|
|
47
|
-
--agent claude \
|
|
48
|
-
--session-id "$SESSION_ID" \
|
|
49
|
-
--transcript-path "$TRANSCRIPT_PATH" \
|
|
50
|
-
--path "$(pwd)" \
|
|
51
|
-
--pid "$(ps -o ppid= -p $$ | tr -d ' ')" \
|
|
26
|
+
syntaur track-session --agent claude \
|
|
52
27
|
[--description "<text>"] \
|
|
53
|
-
[--project <slug>]
|
|
54
|
-
[--assignment <slug>]
|
|
28
|
+
[--project <slug>] [--assignment <slug>]
|
|
55
29
|
```
|
|
56
30
|
|
|
57
|
-
|
|
31
|
+
The CLI resolves the session id, transcript-derived path, owning pid, and HEAD sha itself, and prints one of:
|
|
58
32
|
|
|
59
|
-
The CLI prints one of:
|
|
60
33
|
- `Registered standalone agent session <sessionId>.`
|
|
61
34
|
- `Registered agent session <sessionId> for <assignment> in <project>.`
|
|
62
35
|
|
|
63
|
-
Registration is idempotent — re-running
|
|
64
|
-
|
|
65
|
-
### Step 4: Merge context.json
|
|
66
|
-
|
|
67
|
-
Ensure `.syntaur/context.json` has the session fields (so SessionEnd and future `track-session` runs find them). Merge, don't overwrite:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
mkdir -p .syntaur
|
|
71
|
-
if [ -f .syntaur/context.json ]; then
|
|
72
|
-
jq --arg sid "$SESSION_ID" --arg tp "$TRANSCRIPT_PATH" \
|
|
73
|
-
'. + {sessionId: $sid} + (if ($tp | length) > 0 then {transcriptPath: $tp} else {} end)' \
|
|
74
|
-
.syntaur/context.json > .syntaur/context.json.tmp \
|
|
75
|
-
&& mv .syntaur/context.json.tmp .syntaur/context.json
|
|
76
|
-
else
|
|
77
|
-
jq -n --arg sid "$SESSION_ID" --arg tp "$TRANSCRIPT_PATH" \
|
|
78
|
-
'{sessionId: $sid} + (if ($tp | length) > 0 then {transcriptPath: $tp} else {} end)' \
|
|
79
|
-
> .syntaur/context.json
|
|
80
|
-
fi
|
|
81
|
-
```
|
|
36
|
+
Registration is idempotent — re-running with the same session id safely upserts the description/link onto the existing row.
|
|
82
37
|
|
|
83
|
-
|
|
38
|
+
If it errors with "Could not resolve a session id", restart the Claude session so the SessionStart hook can register it (or pass `--session-id <id>` with a real agent-generated id — never synthesize one).
|
|
84
39
|
|
|
85
|
-
|
|
86
|
-
- The session was registered (include the short session id).
|
|
87
|
-
- It will be auto-stopped when this conversation ends via the SessionEnd hook.
|
|
88
|
-
- If linked to a project, mention which project/assignment.
|
|
40
|
+
Confirm to the user: the row was updated (include the short session id), it auto-stops at SessionEnd, and which project/assignment it is linked to, if any.
|
|
Binary file
|
|
Binary file
|