@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.
- package/dist/agui-map.d.ts +220 -0
- package/dist/agui-map.d.ts.map +1 -0
- package/dist/extension.d.ts.map +1 -1
- package/dist/hook.cjs +21 -10
- package/dist/hooks.d.ts +4 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.js +57 -35
- package/dist/mcp.cjs +2085 -490
- package/package.json +4 -4
- package/dist/transcript.d.ts +0 -27
- package/dist/transcript.d.ts.map +0 -1
|
@@ -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"}
|
package/dist/extension.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|