feat(create/monitor): implement automatic UUID materialization, auto-pinning, and drift-C2 resolution (13/13 PASS)

This commit is contained in:
2026-08-09 10:24:32 +09:00
parent 9df0fc36f3
commit 245abe62c8
16 changed files with 1239 additions and 61 deletions
@@ -124,15 +124,18 @@ YAML: no such session
→ report: "lab-paper-pdf2md-creator-agy: herdr found but not in YAML. Auto-registered."
```
### C. New session id materializes (claude first message sent)
### C. Session ID Discovery & Confirmation
```
YAML: claude_session_id_own=null (placeholder)
disk: ~/.claude/projects/.../b3a7...c2f.jsonl exists, mtime=now,
first line sessionId=b3a7...c2f
→ update claude_session_id_own=b3a7...c2f
→ report: "lab-landing-page-creator-claude: session id materialized b3a7...c2f"
```
- **C0. Assigned ID Confirmation**: For sessions created with an auto-assigned UUID (`session_id_source: assigned`, `session_id_verified: false`), the monitor verifies that the transcript file `.jsonl` has materialized on disk. Once verified, it promotes `session_id_verified: true` and updates `last_visible_status: pinned`.
- **C. New session id materializes (unassigned/legacy)**:
```
YAML: claude_session_id_own=null (placeholder)
disk: ~/.claude/projects/.../b3a7...c2f.jsonl exists, mtime=now,
first line sessionId=b3a7...c2f
→ update claude_session_id_own=b3a7...c2f
→ report: "lab-landing-page-creator-claude: session id materialized b3a7...c2f"
```
- **C-ambiguous. Multiple candidates detected**: If multiple candidate transcripts match an unassigned session, the monitor avoids random pinning, reports `C-ambiguous`, and sets `last_visible_status: "ambiguous: N candidates"`.
### D. Stale UUID (artifact gone)
@@ -565,9 +565,50 @@ if herdr_confirmed:
'msg': f"{name}: herdr found but not in YAML. Auto-registered (pane {pm['pid']}, cmd {pm['cmd']}, cwd {pm['cwd']})."})
actions.append(f"registered: {name}")
def row_agent(s):
cmd = ((s.get('pane') or {}).get('cmd') or '').strip()
if cmd in ('claude', 'agy', 'hermes', 'cline'):
return cmd
full = ((s.get('pane') or {}).get('cmd_full') or '')
for a in ('claude', 'agy', 'hermes', 'cline'):
if a in full:
return a
name = s.get('name', '')
for a in ('claude', 'agy', 'hermes', 'cline'):
if name.endswith('-creator-' + a):
return a
return None
OWN_KEY_BY_AGENT = {
'claude': 'claude_session_id_own',
'agy': 'agy_conversation_id_own',
'hermes': 'hermes_conversation_id_own',
'cline': 'cline_conversation_id_own'
}
# === drift C0: 지정된 ID 는 발견이 아니라 '확인'만 필요하다 ===
for s in d.get('herdr_sessions', []):
if s.get('status') != 'running':
continue
if s.get('session_id_source') != 'assigned' or s.get('session_id_verified'):
continue
agent = row_agent(s)
if not agent:
continue
uuid = s.get(OWN_KEY_BY_AGENT[agent])
cwd = (s.get('pane') or {}).get('cwd', '')
if not uuid or not cwd:
continue
if verify_session_uuid(cwd, agent, uuid, s, mode="discover"):
s['session_id_verified'] = True
s['last_visible_status'] = 'pinned'
drifts.append({'class': 'C', 'name': s['name'],
'msg': f"{s['name']}: assigned session id confirmed on disk: {uuid}"})
actions.append(f"confirmed session id: {uuid}")
# === drift C: claude 새 session id materialize (per-row own id) ===
for s in d.get('herdr_sessions', []):
if not s.get('name', '').endswith('-creator-claude'):
if row_agent(s) != 'claude':
continue
if s.get('status') != 'running':
continue
@@ -588,6 +629,11 @@ for s in d.get('herdr_sessions', []):
if verify_session_uuid(cwd, 'claude', uuid, s, mode="discover"):
valid_candidates.append(uuid)
if len(valid_candidates) > 1:
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
actions.append(f"ambiguous candidates: {s['name']}")
if len(valid_candidates) == 1:
uuid = valid_candidates[0]
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "claude" "{cwd}"']
@@ -601,7 +647,7 @@ for s in d.get('herdr_sessions', []):
# === drift C (agy): agy 새 session id materialize (per-row own id) ===
for s in d.get('herdr_sessions', []):
if not s.get('name', '').endswith('-creator-agy'):
if row_agent(s) != 'agy':
continue
if s.get('status') != 'running':
continue
@@ -632,6 +678,11 @@ for s in d.get('herdr_sessions', []):
if verify_session_uuid(cwd, 'agy', uuid, s_eval, mode="discover"):
valid_candidates.append(uuid)
if len(valid_candidates) > 1:
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
actions.append(f"ambiguous candidates: {s['name']}")
if len(valid_candidates) == 1:
uuid = valid_candidates[0]
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "agy" "{cwd}"']
@@ -645,7 +696,7 @@ for s in d.get('herdr_sessions', []):
# === drift C (hermes): hermes 새 session id materialize (per-row own id) ===
for s in d.get('herdr_sessions', []):
if not s.get('name', '').endswith('-creator-hermes'):
if row_agent(s) != 'hermes':
continue
if s.get('status') != 'running':
continue
@@ -670,6 +721,11 @@ for s in d.get('herdr_sessions', []):
except Exception:
pass
if len(valid_candidates) > 1:
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
actions.append(f"ambiguous candidates: {s['name']}")
if len(valid_candidates) == 1:
uuid = valid_candidates[0]
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "hermes" "{cwd}"']
@@ -683,7 +739,7 @@ for s in d.get('herdr_sessions', []):
# === drift C (cline): cline 새 session id materialize (per-row own id) ===
for s in d.get('herdr_sessions', []):
if not s.get('name', '').endswith('-creator-cline'):
if row_agent(s) != 'cline':
continue
if s.get('status') != 'running':
continue
@@ -709,8 +765,12 @@ for s in d.get('herdr_sessions', []):
uuid = os.path.basename(j)[:-5]
if verify_session_uuid(cwd, 'cline', uuid, s, mode="discover"):
valid_candidates.append(uuid)
break
if len(valid_candidates) > 1:
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
actions.append(f"ambiguous candidates: {s['name']}")
if len(valid_candidates) == 1:
uuid = valid_candidates[0]
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "cline" "{cwd}"']