@ericrisco/rsc 0.1.33 → 0.1.35
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/README.md +20 -0
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/agent-eval/SKILL.md +1 -1
- package/skills/analyze/SKILL.md +1 -1
- package/skills/brand-voice/SKILL.md +1 -1
- package/skills/brand-voice/references/voice-guide-template.md +14 -0
- package/skills/building-agents/SKILL.md +2 -2
- package/skills/competitor-watch/SKILL.md +9 -4
- package/skills/competitor-watch/references/tracker-schema.md +19 -0
- package/skills/constitution/references/constitution-template.md +10 -0
- package/skills/content-engine/SKILL.md +1 -1
- package/skills/content-engine/references/brief-and-pipeline.md +15 -1
- package/skills/course-builder/references/grounding-and-scoping.md +23 -0
- package/skills/course-storytelling/references/learner-grounding.md +22 -2
- package/skills/decision-records/SKILL.md +14 -1
- package/skills/decision-records/references/templates.md +20 -0
- package/skills/design/references/brand-grounding.md +11 -1
- package/skills/harness/SKILL.md +3 -3
- package/skills/harness/references/daily-curation-automation.md +8 -6
- package/skills/harness/references/obsidian-scaffolding.md +16 -9
- package/skills/harness/references/wiki-article-template.md +31 -14
- package/skills/harness/references/wiki-index-template.md +7 -3
- package/skills/harness/references/wiki-protocol.md +120 -54
- package/skills/harness/references/wiki-worklog-template.md +5 -1
- package/skills/instagram-api/SKILL.md +8 -1
- package/skills/instagram-api/references/insights-metrics.md +8 -1
- package/skills/knowledge-ops/SKILL.md +9 -9
- package/skills/knowledge-ops/evals/cases.yaml +1 -1
- package/skills/knowledge-ops/references/gardening-playbook.md +18 -8
- package/skills/linkedin-api/SKILL.md +10 -1
- package/skills/linkedin-strategy/SKILL.md +12 -0
- package/skills/linkedin-strategy/references/wiki-records.md +27 -1
- package/skills/marketing/references/brand-grounding.md +22 -2
- package/skills/medium-strategy/SKILL.md +12 -0
- package/skills/medium-strategy/references/wiki-records.md +39 -1
- package/skills/medium-writing/SKILL.md +19 -0
- package/skills/plan/references/plan-template.md +12 -1
- package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/base.py +12 -0
- package/skills/roast-me/tools/adapters/codex.py +146 -53
- package/skills/roast-me/tools/adapters/gemini.py +102 -81
- package/skills/roast-me/tools/extract_prompts.py +13 -14
- package/skills/shortform-packaging/SKILL.md +7 -2
- package/skills/shortform-packaging/references/package-templates.md +13 -2
- package/skills/shortform-strategy/SKILL.md +30 -6
- package/skills/shortform-strategy/references/learning-loop-template.md +9 -1
- package/skills/specify/references/spec-template.md +12 -1
- package/skills/tiktok-api/SKILL.md +6 -2
- package/skills/tiktok-api/references/wiki-schema.md +38 -4
- package/skills/verify/SKILL.md +11 -1
- package/skills/youtube-api/SKILL.md +7 -2
- package/skills/youtube-api/references/wiki-schema.md +25 -4
- package/skills/youtube-packaging/SKILL.md +10 -2
- package/skills/youtube-packaging/references/description-and-chapters.md +70 -2
- package/skills/youtube-strategy/SKILL.md +9 -2
- package/skills/youtube-strategy/references/wiki-records.md +39 -6
- package/skills/youtube-strategy/scripts/verify.sh +4 -0
- package/skills/youtube-thumbnails/SKILL.md +10 -5
- package/skills/youtube-thumbnails/references/experiment-log-format.md +28 -2
|
@@ -91,9 +91,19 @@ If the user can't answer a question, that dimension stays incomplete — note th
|
|
|
91
91
|
|
|
92
92
|
### Brand wiki article template
|
|
93
93
|
|
|
94
|
-
Each `02-DOCS/wiki/brand/*.md` article follows the harness wiki format:
|
|
94
|
+
Each `02-DOCS/wiki/brand/*.md` article follows the harness wiki format. It is an OKF v0.1 article: open it with YAML frontmatter carrying a non-empty `type:` (`brand-study`), then the H1 and body:
|
|
95
95
|
|
|
96
96
|
```markdown
|
|
97
|
+
---
|
|
98
|
+
type: brand-study
|
|
99
|
+
title: Brand — Value Proposition
|
|
100
|
+
description: The core benefit and what makes this brand different from the named alternatives.
|
|
101
|
+
tags: [brand, value-proposition]
|
|
102
|
+
timestamp: YYYY-MM-DDTHH:MM:SSZ
|
|
103
|
+
topic: brand
|
|
104
|
+
status: stable
|
|
105
|
+
---
|
|
106
|
+
|
|
97
107
|
# Brand — Value Proposition
|
|
98
108
|
|
|
99
109
|
> Sources: {user interview, YYYY-MM-DD}
|
|
@@ -124,9 +134,19 @@ One paragraph: the core benefit and what makes it different.
|
|
|
124
134
|
|
|
125
135
|
### Voice article specifics
|
|
126
136
|
|
|
127
|
-
`voice.md` must contain the do/don't word lists and link to every raw sample:
|
|
137
|
+
`voice.md` must contain the do/don't word lists and link to every raw sample (same OKF frontmatter; `type: brand-study`):
|
|
128
138
|
|
|
129
139
|
```markdown
|
|
140
|
+
---
|
|
141
|
+
type: brand-study
|
|
142
|
+
title: Brand — Tone & Voice
|
|
143
|
+
description: Tone descriptors, do/don't word lists, and the voice fingerprint extracted from real samples.
|
|
144
|
+
tags: [brand, voice, tone]
|
|
145
|
+
timestamp: YYYY-MM-DDTHH:MM:SSZ
|
|
146
|
+
topic: brand
|
|
147
|
+
status: stable
|
|
148
|
+
---
|
|
149
|
+
|
|
130
150
|
# Brand — Tone & Voice
|
|
131
151
|
|
|
132
152
|
> Sources: {user interview, YYYY-MM-DD}
|
|
@@ -133,6 +133,16 @@ On a monthly/quarterly review:
|
|
|
133
133
|
Every session ends with a dated record under `02-DOCS/wiki/medium/decisions/`. Minimum fields — the bet-on metric is mandatory, because a decision with no metric is an opinion, not a checkable bet:
|
|
134
134
|
|
|
135
135
|
```markdown
|
|
136
|
+
---
|
|
137
|
+
type: medium-decision
|
|
138
|
+
title: Solo vs publication for AI-tools series
|
|
139
|
+
description: Stay solo + canonical-import the blog series; pitch one Boost-eligible AI pub with a native original.
|
|
140
|
+
tags: [medium, publications, monetization, distribution]
|
|
141
|
+
timestamp: 2026-06-02T00:00:00Z
|
|
142
|
+
topic: medium
|
|
143
|
+
status: stable
|
|
144
|
+
---
|
|
145
|
+
|
|
136
146
|
# 2026-06-02 — Solo vs publication for AI-tools series
|
|
137
147
|
|
|
138
148
|
Decision: Stay solo profile + canonical-import the blog series; pitch one Boost-eligible AI pub with a native original.
|
|
@@ -140,6 +150,8 @@ Rationale: 400 followers, no editor nomination path; the blog has evergreen sear
|
|
|
140
150
|
Bets on metric: member reading time from external/search referral; 1 Boost nomination this quarter.
|
|
141
151
|
```
|
|
142
152
|
|
|
153
|
+
`type:` is the required OKF v0.1 field; `timestamp` is ISO 8601. The `Decision:` / `Bets on metric:` lines are domain signals `verify.sh` greps for — keep them.
|
|
154
|
+
|
|
143
155
|
After a review, append a `what-worked.md` entry tied to specific stories/pubs/tags (e.g. "`AI Coding Tools` long-tail + Boost → 3.2h member reading time"). Full templates, layout, and the verify.sh contract: [`references/wiki-records.md`](references/wiki-records.md). Sanity-check the wiki with `scripts/verify.sh 02-DOCS/wiki/medium/`.
|
|
144
156
|
|
|
145
157
|
## Anti-patterns
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This is the persistence layer that makes medium-strategy compound. Read SKILL.md §1 and §9 first; this file is the exact format.
|
|
4
4
|
|
|
5
|
+
Every `.md` file written under `02-DOCS/wiki/medium/` is part of the OKF v0.1 bundle: it carries YAML frontmatter with a non-empty `type:` (the only required OKF field), and any cross-references use **standard markdown links** (`[Text](./pubs.md)`), never wikilinks (`[[...]]`). The domain lines `verify.sh` greps for (the dated heading, `Decision:`, the metric/bet field) stay exactly as shown below.
|
|
6
|
+
|
|
5
7
|
## Layout under `02-DOCS/wiki/medium/`
|
|
6
8
|
|
|
7
9
|
```text
|
|
@@ -21,6 +23,16 @@ This is the persistence layer that makes medium-strategy compound. Read SKILL.md
|
|
|
21
23
|
Every decision file MUST carry three signals: a **date**, a **Decision:** line, and a **named metric/bet**. The metric is mandatory — a decision with no metric is an opinion, not something you can check next quarter.
|
|
22
24
|
|
|
23
25
|
```markdown
|
|
26
|
+
---
|
|
27
|
+
type: medium-decision
|
|
28
|
+
title: Solo vs publication for the AI-tools series
|
|
29
|
+
description: Stay solo; canonical-import the evergreen blog posts; pitch one Boost-eligible AI pub.
|
|
30
|
+
tags: [medium, publications, monetization, distribution]
|
|
31
|
+
timestamp: 2026-06-02T00:00:00Z
|
|
32
|
+
topic: medium
|
|
33
|
+
status: stable
|
|
34
|
+
---
|
|
35
|
+
|
|
24
36
|
# 2026-06-02 — Solo vs publication for the AI-tools series
|
|
25
37
|
|
|
26
38
|
Decision: Stay solo profile; canonical-import the 6 evergreen blog posts; pitch one Boost-eligible AI publication with a new native original.
|
|
@@ -29,11 +41,23 @@ Bets on metric: member reading time from external + search referral over 90 days
|
|
|
29
41
|
Status: open
|
|
30
42
|
```
|
|
31
43
|
|
|
32
|
-
When the bet resolves, append a short outcome to the
|
|
44
|
+
`type:` (required) + `timestamp:` (ISO 8601, last meaningful edit) are the OKF surface; the `Status:` line in the body is the domain bet-lifecycle marker — distinct from the frontmatter `status:` (the OKF article lifecycle: draft | stable | stale). When the bet resolves, append a short outcome to the body (`Status: won — Boost nomination 2026-07; +2.1h member reading time`) rather than editing the original — append-only history. A running `decisions.md` log at the root carries one frontmatter block at the top (`type: medium-decision`).
|
|
33
45
|
|
|
34
46
|
## what-worked.md entry template
|
|
35
47
|
|
|
48
|
+
`what-worked.md` is one running file; new review blocks are prepended newest-first under a single frontmatter block at the top.
|
|
49
|
+
|
|
36
50
|
```markdown
|
|
51
|
+
---
|
|
52
|
+
type: medium-record
|
|
53
|
+
title: What worked — Medium
|
|
54
|
+
description: Running log of outcomes tied to specific Medium stories, publications, and tags.
|
|
55
|
+
tags: [medium, what-worked, member-reading-time, boost]
|
|
56
|
+
timestamp: 2026-09-01T00:00:00Z
|
|
57
|
+
topic: medium
|
|
58
|
+
status: stable
|
|
59
|
+
---
|
|
60
|
+
|
|
37
61
|
## 2026-09-01 — Q3 review
|
|
38
62
|
- `AI Coding Tools` (long-tail) + Boost → 3.2h member reading time on one story. Double down: keep the long-tail tag, pitch the same pub again.
|
|
39
63
|
- Imported blog post w/ canonical + newsletter push → +5% external bonus visible, but low member conversion. Keep importing; stop expecting member reads from cold list.
|
|
@@ -44,6 +68,20 @@ When the bet resolves, append a short outcome to the same file (`Status: won —
|
|
|
44
68
|
|
|
45
69
|
Keep these lightweight. `pubs.md`: pub name | status (accepted/pitched/dropped) | niche fit | a one-line guideline note. `tags.md`: tag | popular-or-long-tail | did it pull member reading time?
|
|
46
70
|
|
|
71
|
+
Both still carry OKF frontmatter (one block at the top of each file):
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
---
|
|
75
|
+
type: medium-record
|
|
76
|
+
title: Publications tracker # or: Tag sets tried
|
|
77
|
+
description: Accepted / pitched / dropped Medium publications and their guideline notes.
|
|
78
|
+
tags: [medium, publications] # or [medium, tags, distribution] for tags.md
|
|
79
|
+
timestamp: 2026-09-01T00:00:00Z
|
|
80
|
+
topic: medium
|
|
81
|
+
status: stable
|
|
82
|
+
---
|
|
83
|
+
```
|
|
84
|
+
|
|
47
85
|
## The verify.sh contract
|
|
48
86
|
|
|
49
87
|
`scripts/verify.sh` is a **structural lint, not a craft judge.** Run it after a session:
|
|
@@ -117,6 +117,25 @@ If `02-DOCS/` exists, learn before you write:
|
|
|
117
117
|
- Bias toward the title/subtitle patterns and topics that historically beat a 50% read ratio for *this* author; avoid angles that drew clicks but bounced.
|
|
118
118
|
- After a notable result, append the learned pattern to `02-DOCS/wiki/medium/read-ratio-patterns.md` so the next draft inherits it.
|
|
119
119
|
|
|
120
|
+
`read-ratio-patterns.md` lives in the `02-DOCS/wiki/` OKF v0.1 bundle, so it carries YAML frontmatter with a non-empty `type:` — one block at the top, with new pattern blocks prepended newest-first below it. Use `type: medium-record` (the same type the `medium-strategy` skill writes under `02-DOCS/wiki/medium/`, keeping the two skills' wiki content consistent). Create it with this header if it does not yet exist:
|
|
121
|
+
|
|
122
|
+
```markdown
|
|
123
|
+
---
|
|
124
|
+
type: medium-record
|
|
125
|
+
title: Read-ratio patterns — Medium
|
|
126
|
+
description: Title/subtitle/topic patterns that historically beat a 50% read ratio for this author.
|
|
127
|
+
tags: [medium, read-ratio, titles, hooks]
|
|
128
|
+
timestamp: 2026-06-02T00:00:00Z
|
|
129
|
+
topic: medium
|
|
130
|
+
status: stable
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 2026-06-02 — <pattern learned>
|
|
134
|
+
- <title/subtitle archetype + topic that beat the 50% read ratio, with the number observed>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Cross-references use standard markdown links (`[Text](./what-worked.md)`), never wikilinks (`[[...]]`).
|
|
138
|
+
|
|
120
139
|
If `02-DOCS/` is absent, proceed from the craft defaults above and say so in one line, rather than inventing stats.
|
|
121
140
|
|
|
122
141
|
## Anti-patterns
|
|
@@ -9,9 +9,20 @@ flows, levels — not framework syntax.
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
```markdown
|
|
12
|
+
---
|
|
13
|
+
type: plan
|
|
14
|
+
title: Plan — <feature name>
|
|
15
|
+
description: The structure-level implementation plan for <feature> — contracts, shapes, flows.
|
|
16
|
+
tags: [sdd, plan]
|
|
17
|
+
timestamp: YYYY-MM-DDTHH:MM:SSZ
|
|
18
|
+
topic: sdd
|
|
19
|
+
slug: <slug>
|
|
20
|
+
status: draft
|
|
21
|
+
---
|
|
22
|
+
|
|
12
23
|
# Plan — <feature name>
|
|
13
24
|
|
|
14
|
-
> Spec: ../specs/<slug>.md · Constitution: ../constitution.md · Status: draft | approved
|
|
25
|
+
> Spec: [../specs/<slug>.md](../specs/<slug>.md) · Constitution: [../constitution.md](../constitution.md) · Status: draft | approved
|
|
15
26
|
> Last updated: YYYY-MM-DD
|
|
16
27
|
|
|
17
28
|
## 0. Global Constraints
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -17,10 +17,22 @@ An adapter MUST:
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import re
|
|
20
21
|
from abc import ABC, abstractmethod
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
from typing import Any
|
|
23
24
|
|
|
25
|
+
#: Shared correction detector — a user message matching this right after a prompt
|
|
26
|
+
#: signals the previous turn went wrong. Single source of truth for every adapter
|
|
27
|
+
#: (and re-used by extract_prompts.py) so the heuristic never drifts between runtimes.
|
|
28
|
+
CORRECTION_RE = re.compile(
|
|
29
|
+
r"\b(no[,.]?\s|wrong|instead|actually|don'?t|shouldn'?t|stop|not that|"
|
|
30
|
+
r"I said|I meant|I asked|that'?s not|please don'?t|why did you|"
|
|
31
|
+
r"you should have|that was wrong|incorrect|try again|redo|"
|
|
32
|
+
r"that broke|you broke|revert|undo)\b",
|
|
33
|
+
re.IGNORECASE,
|
|
34
|
+
)
|
|
35
|
+
|
|
24
36
|
|
|
25
37
|
class BaseAdapter(ABC):
|
|
26
38
|
"""Contract every runtime adapter must satisfy."""
|
|
@@ -1,36 +1,71 @@
|
|
|
1
1
|
"""Codex CLI adapter (@openai/codex).
|
|
2
2
|
|
|
3
3
|
Session files: ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>.jsonl
|
|
4
|
-
Format: JSON Lines. Each line is
|
|
5
|
-
|
|
6
|
-
CONFIRMED (2026-06,
|
|
7
|
-
- Base path: ~/.codex/sessions/
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
Format: JSON Lines. Each line is {"timestamp", "type", "payload"}.
|
|
5
|
+
|
|
6
|
+
CONFIRMED (2026-06, against live ~/.codex/sessions on macOS):
|
|
7
|
+
- Base path / layout / naming: ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl
|
|
8
|
+
- Line envelope: {"timestamp": ISO8601, "type": str, "payload": {...}}
|
|
9
|
+
- The genuine user prompt is an `event_msg` line whose payload.type is
|
|
10
|
+
"user_message", carrying the typed text in payload.message. (Injected
|
|
11
|
+
AGENTS.md / developer preambles appear only as response_item/message lines,
|
|
12
|
+
so keying on user_message excludes them.)
|
|
13
|
+
- Assistant turns: event_msg/agent_message (payload.message) and
|
|
14
|
+
response_item/message role=assistant.
|
|
15
|
+
- Tool calls: response_item/function_call (payload.name).
|
|
16
|
+
- Tool results: response_item/function_call_output (payload.output) — shell
|
|
17
|
+
execs include a "Process exited with code N" line; N != 0 (or common error
|
|
18
|
+
markers) means the command failed.
|
|
19
|
+
- Model: turn_context.model / session_meta payload.
|
|
20
|
+
|
|
21
|
+
Degradation: if ~/.codex/ does not exist, discover() returns []; any unexpected
|
|
22
|
+
line shape is skipped, so parse() never raises.
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
25
|
from __future__ import annotations
|
|
22
26
|
|
|
27
|
+
import datetime
|
|
23
28
|
import json
|
|
29
|
+
import re
|
|
24
30
|
from pathlib import Path
|
|
25
31
|
from typing import Any
|
|
26
32
|
|
|
27
|
-
from .base import BaseAdapter
|
|
33
|
+
from .base import CORRECTION_RE, BaseAdapter
|
|
34
|
+
|
|
35
|
+
# A shell tool result failed when it reports a non-zero exit code, or carries a
|
|
36
|
+
# common error marker with no explicit success code. Conservative on purpose —
|
|
37
|
+
# better to miss a soft error than to invent one.
|
|
38
|
+
_EXIT_CODE_RE = re.compile(r"exited with code\s+(\d+)", re.IGNORECASE)
|
|
39
|
+
_ERROR_MARKER_RE = re.compile(
|
|
40
|
+
r"\b(command not found|no such file|permission denied|traceback \(most recent|"
|
|
41
|
+
r"fatal:|panic:|segmentation fault|unhandled exception)\b",
|
|
42
|
+
re.IGNORECASE,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _looks_like_error(output: str) -> bool:
|
|
47
|
+
if not output:
|
|
48
|
+
return False
|
|
49
|
+
m = _EXIT_CODE_RE.search(output)
|
|
50
|
+
if m:
|
|
51
|
+
return m.group(1) != "0"
|
|
52
|
+
return bool(_ERROR_MARKER_RE.search(output))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _ts(value: Any) -> float | None:
|
|
56
|
+
if isinstance(value, (int, float)):
|
|
57
|
+
return float(value)
|
|
58
|
+
if isinstance(value, str):
|
|
59
|
+
try:
|
|
60
|
+
return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp()
|
|
61
|
+
except (ValueError, AttributeError):
|
|
62
|
+
return None
|
|
63
|
+
return None
|
|
28
64
|
|
|
29
65
|
|
|
30
66
|
class CodexAdapter(BaseAdapter):
|
|
31
67
|
RUNTIME_ID = "codex"
|
|
32
68
|
|
|
33
|
-
# CONFIRMED: path layout from official CLI docs and community tools.
|
|
34
69
|
SESSIONS_DIR = Path.home() / ".codex" / "sessions"
|
|
35
70
|
|
|
36
71
|
def discover(self) -> list[Path]:
|
|
@@ -50,17 +85,17 @@ class CodexAdapter(BaseAdapter):
|
|
|
50
85
|
return sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)
|
|
51
86
|
|
|
52
87
|
def parse(self, path: Path) -> list[dict[str, Any]]:
|
|
53
|
-
"""Parse one Codex session JSONL file
|
|
54
|
-
|
|
55
|
-
STUBBED schema: assumes {"role": "user", "content": "..."} event lines.
|
|
56
|
-
Falls back silently if the schema differs.
|
|
57
|
-
"""
|
|
58
|
-
records: list[dict[str, Any]] = []
|
|
88
|
+
"""Parse one Codex session JSONL file into user-prompt records with
|
|
89
|
+
session-aware error/correction context."""
|
|
59
90
|
try:
|
|
60
91
|
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
61
92
|
except OSError:
|
|
62
93
|
return []
|
|
63
94
|
|
|
95
|
+
# 1) Flatten the file into an ordered timeline of turns.
|
|
96
|
+
timeline: list[dict[str, Any]] = []
|
|
97
|
+
model = ""
|
|
98
|
+
pending_tool = ""
|
|
64
99
|
for line in lines:
|
|
65
100
|
line = line.strip()
|
|
66
101
|
if not line:
|
|
@@ -69,45 +104,103 @@ class CodexAdapter(BaseAdapter):
|
|
|
69
104
|
obj = json.loads(line)
|
|
70
105
|
except json.JSONDecodeError:
|
|
71
106
|
continue
|
|
107
|
+
if not isinstance(obj, dict):
|
|
108
|
+
continue
|
|
72
109
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if
|
|
110
|
+
top_type = obj.get("type")
|
|
111
|
+
payload = obj.get("payload")
|
|
112
|
+
if not isinstance(payload, dict):
|
|
113
|
+
payload = {}
|
|
114
|
+
ts = _ts(obj.get("timestamp"))
|
|
115
|
+
|
|
116
|
+
# Track the active model from session metadata / turn context.
|
|
117
|
+
if top_type in ("session_meta", "turn_context"):
|
|
118
|
+
m = payload.get("model")
|
|
119
|
+
if isinstance(m, str) and m:
|
|
120
|
+
model = m
|
|
76
121
|
continue
|
|
77
122
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
text = content
|
|
89
|
-
else:
|
|
90
|
-
text = ""
|
|
91
|
-
|
|
92
|
-
if not text.strip():
|
|
123
|
+
if top_type == "event_msg":
|
|
124
|
+
p_type = payload.get("type")
|
|
125
|
+
if p_type == "user_message":
|
|
126
|
+
text = payload.get("message", "")
|
|
127
|
+
if isinstance(text, str) and text.strip():
|
|
128
|
+
timeline.append({"kind": "user", "text": text, "ts": ts})
|
|
129
|
+
elif p_type == "agent_message":
|
|
130
|
+
text = payload.get("message", "")
|
|
131
|
+
if isinstance(text, str) and text.strip():
|
|
132
|
+
timeline.append({"kind": "assistant", "text": text, "ts": ts})
|
|
93
133
|
continue
|
|
94
134
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
if top_type == "response_item":
|
|
136
|
+
p_type = payload.get("type")
|
|
137
|
+
if p_type == "function_call":
|
|
138
|
+
name = payload.get("name", "")
|
|
139
|
+
pending_tool = name if isinstance(name, str) else ""
|
|
140
|
+
elif p_type == "function_call_output":
|
|
141
|
+
output = payload.get("output", "")
|
|
142
|
+
output = output if isinstance(output, str) else json.dumps(output)
|
|
143
|
+
timeline.append({
|
|
144
|
+
"kind": "tool_output",
|
|
145
|
+
"tool": pending_tool,
|
|
146
|
+
"is_error": _looks_like_error(output),
|
|
147
|
+
"text": output,
|
|
148
|
+
"ts": ts,
|
|
149
|
+
})
|
|
150
|
+
pending_tool = ""
|
|
151
|
+
# response_item/message (assistant/user/developer) is intentionally
|
|
152
|
+
# ignored: genuine prompts come from event_msg/user_message, and the
|
|
153
|
+
# assistant context comes from event_msg/agent_message.
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
# 2) Walk the timeline; enrich each user prompt with what follows it.
|
|
157
|
+
user_indices = [i for i, t in enumerate(timeline) if t["kind"] == "user"]
|
|
158
|
+
records: list[dict[str, Any]] = []
|
|
159
|
+
for n, idx in enumerate(user_indices):
|
|
160
|
+
turn = timeline[idx]
|
|
161
|
+
next_user = user_indices[n + 1] if n + 1 < len(user_indices) else len(timeline)
|
|
162
|
+
|
|
163
|
+
followed_by_error = False
|
|
164
|
+
error_tool = ""
|
|
165
|
+
error_text = ""
|
|
166
|
+
for j in range(idx + 1, next_user):
|
|
167
|
+
t = timeline[j]
|
|
168
|
+
if t["kind"] == "tool_output" and t.get("is_error"):
|
|
169
|
+
followed_by_error = True
|
|
170
|
+
error_tool = t.get("tool", "")
|
|
171
|
+
error_text = t.get("text", "")
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
followed_by_correction = False
|
|
175
|
+
correction_text = ""
|
|
176
|
+
if next_user < len(timeline):
|
|
177
|
+
nxt = timeline[next_user]["text"]
|
|
178
|
+
if CORRECTION_RE.search(nxt):
|
|
179
|
+
followed_by_correction = True
|
|
180
|
+
correction_text = nxt
|
|
181
|
+
|
|
182
|
+
# context_before: last assistant text strictly before this prompt.
|
|
183
|
+
context_before = ""
|
|
184
|
+
for j in range(idx - 1, -1, -1):
|
|
185
|
+
if timeline[j]["kind"] == "assistant":
|
|
186
|
+
context_before = timeline[j]["text"]
|
|
187
|
+
break
|
|
188
|
+
|
|
189
|
+
error_was_recovered = followed_by_error and not followed_by_correction
|
|
105
190
|
|
|
106
191
|
records.append({
|
|
107
192
|
"runtime": self.RUNTIME_ID,
|
|
108
193
|
"session_file": str(path),
|
|
109
|
-
"prompt_text": text,
|
|
110
|
-
"timestamp": ts,
|
|
194
|
+
"prompt_text": turn["text"],
|
|
195
|
+
"timestamp": turn.get("ts"),
|
|
196
|
+
"model": model,
|
|
197
|
+
"context_before": context_before,
|
|
198
|
+
"followed_by_error": followed_by_error,
|
|
199
|
+
"error_was_recovered": error_was_recovered,
|
|
200
|
+
"followed_by_correction": followed_by_correction,
|
|
201
|
+
"correction_text": correction_text,
|
|
202
|
+
"error_tool": error_tool,
|
|
203
|
+
"error_text": error_text,
|
|
111
204
|
})
|
|
112
205
|
|
|
113
206
|
return records
|