@cotal-ai/connector-claude-code 0.20.1 → 0.22.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.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * §3.1 — the Claude Code session JSONL → AG-UI event mapping.
3
+ *
4
+ * This is the record-shaped half of the cutover. It lives HERE and not in `connector-core` on
5
+ * purpose: the JSONL entry shape is Claude's, and letting it into the shared layer would be exactly
6
+ * the leak AGENTS.md forbids ("never let an adapter's concepts leak into the shared layers"). What
7
+ * IS shared — the vocabulary, the frame, the bracket machine, the durable read discipline — is
8
+ * imported from `connector-core` and nothing about Claude goes back the other way.
9
+ *
10
+ * ---------------------------------------------------------------------------------------------
11
+ * THREE THINGS THE PLAN DOES NOT SETTLE, RECORDED AS GAPS RATHER THAN DECIDED HERE.
12
+ *
13
+ * **(A) The run brackets have no vehicle, and this is a real plan defect, not a detail.** §3.1
14
+ * sources `RUN_STARTED` from the `UserPromptSubmit` hook and `RUN_FINISHED` from the `Stop` hook
15
+ * (`mcp.ts:72`, `:96`). But `[P6]`/§5.4 makes the durable plane READ A FILE, and the emitter's
16
+ * mapper is `(record) => events` — a hook fires in a different process at a different time and
17
+ * produces NO JSONL record, so there is no vehicle by which a hook-sourced event can enter a
18
+ * record-sourced stream. The two halves of the design were specified against different inputs.
19
+ *
20
+ * What this file does instead is stated plainly so a ruling can replace it in one function:
21
+ * **brackets are derived from the record stream.** A human prompt opens a run; the NEXT human
22
+ * prompt closes the previous one. Two consequences, both of which a reader must know:
23
+ * 1. `RUN_FINISHED` lags by one turn. It is emitted when the next turn starts, not when the turn
24
+ * ends, so a consumer sees the finish later than the `Stop` hook would have said it.
25
+ * 2. **The last run of a session never closes.** There is no record after it to close it on.
26
+ * Neither is silently absorbed: {@link closeOpenRun} exists so the connector's `Stop` hook can shut
27
+ * the run at the real boundary once a vehicle is ruled, and until then the lag is honest.
28
+ *
29
+ * **(B) `origin.kind === "human"` OPENS NO RUN IN ANY AGENT-DRIVEN SESSION — MEASURED on three, and
30
+ * this is why the mapping smoke reads a real session rather than a fixture.** §3.1's rule is right
31
+ * about what it excludes (peer/mesh injections, task notifications, resumed-session summaries). The
32
+ * problem is what is left to select. Partitioned by CONTENT SHAPE, not just counted:
33
+ *
34
+ * | session | user entries | `tool_result` | mesh (`origin.kind:"channel"`) | compact summary | human |
35
+ * | --- | --- | --- | --- | --- | --- |
36
+ * | interactive, 5938 rec | 892 | 824 | 67 | 1 | **0** |
37
+ * | headless `claude -p`, 30 rec | 5 | 3 | 0 | 0 | **0** (2 prompts, `promptSource:"sdk"`) |
38
+ * | agent session, 1088 rec | 90 | 86 | 4 | 0 | **0** |
39
+ *
40
+ * **`kind:"human"` occurs zero times — but so does a human.** `~/.claude/history.jsonl`, which
41
+ * records typed prompts through a different mechanism entirely, reports **0** for all three sessions
42
+ * and 0 for this worktree. The two sources agree, so zero matches is the CORRECT result on these
43
+ * captures and NOT evidence the rule is wrong. **The rule is unexercised here, not disproven.**
44
+ *
45
+ * **AND IT IS EXERCISED ELSEWHERE — read the mapping rationale below before reading these numbers as
46
+ * a defect.** That section measured a real session a person was driving and counted `kind:"human"`
47
+ * **44 times**, with `promptSource: "typed"`/`"queued"`, beside 3068 `kind:"channel"` injections. So
48
+ * the predicate does select, on a session that contains the thing it selects. The three captures
49
+ * here simply contain none. **Both numbers belong together; either alone misleads.**
50
+ *
51
+ * **THIS WAS A COVERAGE GAP, IT WAS RULED, AND THE RULING IS IMPLEMENTED BELOW.** It read: on
52
+ * agent-driven sessions no run is ever opened and the connector emits nothing, because the mapping table
53
+ * sent every non-`human` origin to *nothing*. The session it was derived from had a human typing 44 times alongside
54
+ * its 3068 mesh messages; a spawned lane seat has **0 and 67**, so the open question was **what
55
+ * opens a run when nobody types**.
56
+ *
57
+ * **THE RULING: run-opening and attribution are two predicates, and that row was one predicate
58
+ * doing both jobs.** A run opens on
59
+ * `origin.kind ∈ { human, channel }`, ENUMERATED and never inferred; `task-notification` is named as
60
+ * known-and-not-a-turn; absent `origin` gets its own enumeration over `promptSource`. Attribution
61
+ * rides as `cotal.turnSource` — **a field on the run, never a gate on it**. The privacy argument is
62
+ * untouched: a `RUN_STARTED` attributed to a peer republishes no message body, so a peer-initiated
63
+ * turn can be a turn without re-emitting the peer's content. See {@link ORIGIN_RULE} and
64
+ * {@link ABSENT_ORIGIN_RULE}, which are where this now lives.
65
+ *
66
+ * **KEEP THIS PARAGRAPH HONEST.** Its earlier form said "no run is ever opened and the connector
67
+ * emits nothing" and "escalated as a plan defect rather than decided here" — describing the state
68
+ * before the ruling, directly above code that had already implemented it. A successor read it,
69
+ * believed it over the code, and escalated a closed question as a live blocker; the measurement that
70
+ * corrected it took one run of the real mapper (**67 runs / 5217 events** on the 5938-record
71
+ * session, `diagnose()` → `null`). **A stale header is not a documentation defect, it is a false
72
+ * claim about the function beneath it.** If the rule changes again, this paragraph changes with it.
73
+ *
74
+ * **DO NOT "FIX" THIS BY TREATING ABSENT `origin` AS HUMAN.** In a Claude session `user` is also the
75
+ * role of a TOOL RESULT: that predicate selects **825** of the interactive session's 892 user
76
+ * entries, and the single non-tool-result among them is a **context-compaction summary**
77
+ * (`isCompactSummary`), so the true human count is 0 and the predicate over-matches by 825. It would
78
+ * not emit nothing — it would emit a flood, each entry opening a run, which looks like the connector
79
+ * working. An earlier revision of this comment recorded (B) as HEADLESS-ONLY and asserted a human
80
+ * turn is "a `user` entry with no `origin`"; both halves were wrong.
81
+ *
82
+ * **The rule is implemented exactly as RULED, and still not guessed at.** `promptSource` was
83
+ * proposed as the selector and REJECTED: it is bounded by the partition it was inferred from, and
84
+ * "sdk" also covers programmatic injection. It survives only inside {@link ABSENT_ORIGIN_RULE},
85
+ * where there is no `origin.kind` to enumerate — a second table rather than a synthetic member,
86
+ * because an enumeration over `origin.kind` cannot classify a record that has none. Every value
87
+ * outside either table **fails loud** rather than being silently treated as not-a-turn.
88
+ *
89
+ * **(C) `TOOL_CALL_RESULT.messageId` is unstated in §3.1's table** (the row names only
90
+ * `toolCallId`) while the real schema REQUIRES it. It is keyed the same way every other message
91
+ * identity here is — entry `uuid` plus block index — so it is unique, stable, and derived rather
92
+ * than invented at a call site. Raised as a gap in `connector-core`'s constructor doc as well.
93
+ * ---------------------------------------------------------------------------------------------
94
+ *
95
+ * `messageId` is `${uuid}#${blockIndex}` and NOT `message.id`. `message.id` is a provider request
96
+ * id: measured over a real session, 67% of them appear in more than one entry and 59% carry more
97
+ * than one block type, so spending it as an AG-UI message identity opens and closes one id
98
+ * repeatedly and collapses text and reasoning into a single message in the reference reducer. The
99
+ * provider id is preserved as `cotal.providerMessageId`, which is what it is good for.
100
+ */
101
+ import { type AguiEvent, type RecordMapper } from "@cotal-ai/connector-core";
102
+ /**
103
+ * One JSONL entry, typed to what the mapping actually reads and no further.
104
+ *
105
+ * Every field is optional because a session file carries at least seven entry types
106
+ * (`user`, `assistant`, `attachment`, `queue-operation`, `ai-title`, `last-prompt`, `mode`, and
107
+ * more will be added by a harness release we do not control). Declaring them required would make
108
+ * the mapper's own type a lie about a file it does not own.
109
+ */
110
+ export interface ClaudeEntry {
111
+ type?: string;
112
+ uuid?: string;
113
+ sessionId?: string;
114
+ timestamp?: string;
115
+ isSidechain?: boolean;
116
+ origin?: {
117
+ kind?: string;
118
+ };
119
+ /**
120
+ * Present on every submitted prompt and absent on tool results. **Not the run-opening gate** —
121
+ * it is `"system"` on task-notifications and caveats too. Read ONLY where `origin` is absent, and
122
+ * only for the value `"sdk"`. See `ABSENT_ORIGIN_RULE`.
123
+ */
124
+ promptSource?: string;
125
+ /** The harness's own compaction record. A string-content `user` entry that is not a turn. */
126
+ isCompactSummary?: boolean;
127
+ isVisibleInTranscriptOnly?: boolean;
128
+ /**
129
+ * The session-level invocation marker — `"cli"` or `"sdk-cli"`, uniform across a session file.
130
+ * **Declared and deliberately NOT read.** It is here so the field's existence is recorded rather
131
+ * than rediscovered, and so a suite can drive both values against a rule that must ignore them.
132
+ */
133
+ entrypoint?: string;
134
+ message?: {
135
+ id?: string;
136
+ stop_reason?: string | null;
137
+ content?: string | ClaudeBlock[];
138
+ };
139
+ }
140
+ /** A content block. Same reasoning as above: shape-tolerant, read narrowly. */
141
+ export interface ClaudeBlock {
142
+ type?: string;
143
+ text?: string;
144
+ thinking?: string;
145
+ /** `tool_use` */
146
+ id?: string;
147
+ name?: string;
148
+ input?: unknown;
149
+ /** `tool_result` */
150
+ tool_use_id?: string;
151
+ content?: unknown;
152
+ is_error?: boolean;
153
+ }
154
+ export interface ClaudeMapperOptions {
155
+ /** The native session id — `threadId` for every event. §3 forbids anything else claiming it. */
156
+ threadId: string;
157
+ /** Mints a `runId`. Connector-minted by §3.1, so every `RUN_STARTED` carries `runIdSource`. */
158
+ mintRunId: () => string;
159
+ /**
160
+ * Emit `REASONING_*` for `thinking` blocks. **Off by default (§7 Q1).** The `signature` is never
161
+ * emitted at any setting (§3.5) and is not read by this module at all.
162
+ */
163
+ reasoning?: boolean;
164
+ /**
165
+ * Arrival clock, for the entries whose `timestamp` is missing or unparseable. Injectable so the
166
+ * mapping is deterministic under test; those events are labelled `cotal.tsSource: "arrival"`
167
+ * rather than being given a real-looking number.
168
+ */
169
+ now?: () => number;
170
+ }
171
+ /** What {@link createClaudeMapper} returns: the mapper plus the out-of-band run close. */
172
+ export interface ClaudeMapper {
173
+ map: RecordMapper<ClaudeEntry>;
174
+ /**
175
+ * Close the open run, if there is one, at a boundary the record stream cannot see — the `Stop`
176
+ * hook. Returns `null` when no run is open, so calling it twice is not an error and cannot
177
+ * manufacture a second `RUN_FINISHED` the bracket machine would refuse.
178
+ *
179
+ * It exists because gap (A) above is a gap: the durable plane has no way to hear a hook today,
180
+ * and this is the seam a ruling plugs into rather than a rewrite.
181
+ */
182
+ closeOpenRun: (timestamp: number, stopReason?: string) => {
183
+ runId: string;
184
+ events: AguiEvent[];
185
+ } | null;
186
+ /** The run currently open, or `null`. Read-only view for a caller that needs to know. */
187
+ openRun: () => string | null;
188
+ /**
189
+ * Forget a run the EMITTER closed out of band, so this mapper stops treating it as open.
190
+ *
191
+ * Two things close a run now, and they do not share state. The mapper closes one when the record
192
+ * stream shows a new turn beginning. The emitter closes one when a lifecycle hook says the turn
193
+ * ended, which is a boundary no record describes. After the second kind, the mapper still believes
194
+ * a run is open: it would attribute the next records to a run the stream has already finished, and
195
+ * the bracket machine would refuse the second terminal it eventually emitted. The emitter reports
196
+ * what it closed, and this is where that report lands.
197
+ *
198
+ * KEYED ON THE ID, and that is the whole safety of it. The report arrives asynchronously, so by
199
+ * the time it does the mapper may already have opened a NEWER run from a record that landed in
200
+ * between. Clearing unconditionally would orphan that one: its events would emit under no run at
201
+ * all, which the brackets refuse, and the session would halt on a frame it had every right to
202
+ * send. A run that is not the one named is left exactly as it is.
203
+ */
204
+ forgetOpenRun: (runId: string) => void;
205
+ /**
206
+ * **WHY THIS SESSION OPENED NO RUNS** — a sentence, or `null` once any run has opened.
207
+ *
208
+ * A mapper that opens zero runs is byte-indistinguishable from a session nobody prompted, and
209
+ * from a mapper that is simply broken. Refusing a record is a legitimate outcome; refusing it
210
+ * SILENTLY is not, and the silence is the defect, not the refusal. This is the production
211
+ * statement of that — it lives in the shipped mapper, not in a smoke summary, so the connector
212
+ * and any operator reading it get the same sentence the suite does.
213
+ *
214
+ * It is deliberately NOT a throw. Some sessions genuinely contain no prompt yet, and a mapper
215
+ * that threw on one would take down a live connector over an empty file.
216
+ */
217
+ diagnose: () => string | null;
218
+ }
219
+ export declare function createClaudeMapper(opts: ClaudeMapperOptions): ClaudeMapper;
220
+ //# sourceMappingURL=agui-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agui-map.d.ts","sourceRoot":"","sources":["../src/agui-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,OAAO,EACL,KAAK,SAAS,EACd,KAAK,YAAY,EAalB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE;QACR,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAAC;KAClC,CAAC;CACH;AAmHD,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oBAAoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC;IAC/B;;;;;;;OAOG;IACH,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACxG,yFAAyF;IACzF,OAAO,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;;;;;;;;;;OAWG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC/B;AAiBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,YAAY,CAuP1E"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAIA,OAAO,EAA2D,KAAK,SAAS,EAAoC,MAAM,gBAAgB,CAAC;AA0D3I,eAAO,MAAM,eAAe,EAAE,SAsI7B,CAAC"}
1
+ {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAIA,OAAO,EAA2D,KAAK,SAAS,EAAoC,MAAM,gBAAgB,CAAC;AA0D3I,eAAO,MAAM,eAAe,EAAE,SA4J7B,CAAC"}