orcareplay 0.1.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.
Files changed (109) hide show
  1. package/dist/api.d.ts +127 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/api.js +159 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/args.d.ts +24 -0
  6. package/dist/args.d.ts.map +1 -0
  7. package/dist/args.js +124 -0
  8. package/dist/args.js.map +1 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +4 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/commands/compare.d.ts +27 -0
  14. package/dist/commands/compare.d.ts.map +1 -0
  15. package/dist/commands/compare.js +182 -0
  16. package/dist/commands/compare.js.map +1 -0
  17. package/dist/commands/doctor.d.ts +24 -0
  18. package/dist/commands/doctor.d.ts.map +1 -0
  19. package/dist/commands/doctor.js +358 -0
  20. package/dist/commands/doctor.js.map +1 -0
  21. package/dist/commands/gc.d.ts +24 -0
  22. package/dist/commands/gc.d.ts.map +1 -0
  23. package/dist/commands/gc.js +326 -0
  24. package/dist/commands/gc.js.map +1 -0
  25. package/dist/commands/inspect.d.ts +24 -0
  26. package/dist/commands/inspect.d.ts.map +1 -0
  27. package/dist/commands/inspect.js +262 -0
  28. package/dist/commands/inspect.js.map +1 -0
  29. package/dist/commands/record.d.ts +17 -0
  30. package/dist/commands/record.d.ts.map +1 -0
  31. package/dist/commands/record.js +497 -0
  32. package/dist/commands/record.js.map +1 -0
  33. package/dist/commands/replay.d.ts +43 -0
  34. package/dist/commands/replay.d.ts.map +1 -0
  35. package/dist/commands/replay.js +800 -0
  36. package/dist/commands/replay.js.map +1 -0
  37. package/dist/commands/scrub.d.ts +34 -0
  38. package/dist/commands/scrub.d.ts.map +1 -0
  39. package/dist/commands/scrub.js +452 -0
  40. package/dist/commands/scrub.js.map +1 -0
  41. package/dist/commands/setup.d.ts +23 -0
  42. package/dist/commands/setup.d.ts.map +1 -0
  43. package/dist/commands/setup.js +177 -0
  44. package/dist/commands/setup.js.map +1 -0
  45. package/dist/config.d.ts +80 -0
  46. package/dist/config.d.ts.map +1 -0
  47. package/dist/config.js +117 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/exchange-events.d.ts +72 -0
  50. package/dist/exchange-events.d.ts.map +1 -0
  51. package/dist/exchange-events.js +170 -0
  52. package/dist/exchange-events.js.map +1 -0
  53. package/dist/flags.d.ts +10 -0
  54. package/dist/flags.d.ts.map +1 -0
  55. package/dist/flags.js +83 -0
  56. package/dist/flags.js.map +1 -0
  57. package/dist/fs-events.d.ts +19 -0
  58. package/dist/fs-events.d.ts.map +1 -0
  59. package/dist/fs-events.js +53 -0
  60. package/dist/fs-events.js.map +1 -0
  61. package/dist/index.d.ts +18 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +18 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/main.d.ts +2 -0
  66. package/dist/main.d.ts.map +1 -0
  67. package/dist/main.js +282 -0
  68. package/dist/main.js.map +1 -0
  69. package/dist/mcp-server.d.ts +44 -0
  70. package/dist/mcp-server.d.ts.map +1 -0
  71. package/dist/mcp-server.js +245 -0
  72. package/dist/mcp-server.js.map +1 -0
  73. package/dist/mcp.d.ts +111 -0
  74. package/dist/mcp.d.ts.map +1 -0
  75. package/dist/mcp.js +221 -0
  76. package/dist/mcp.js.map +1 -0
  77. package/dist/out.d.ts +69 -0
  78. package/dist/out.d.ts.map +1 -0
  79. package/dist/out.js +138 -0
  80. package/dist/out.js.map +1 -0
  81. package/dist/rasterize.d.ts +62 -0
  82. package/dist/rasterize.d.ts.map +1 -0
  83. package/dist/rasterize.js +154 -0
  84. package/dist/rasterize.js.map +1 -0
  85. package/dist/serial.d.ts +19 -0
  86. package/dist/serial.d.ts.map +1 -0
  87. package/dist/serial.js +34 -0
  88. package/dist/serial.js.map +1 -0
  89. package/dist/share-card.d.ts +73 -0
  90. package/dist/share-card.d.ts.map +1 -0
  91. package/dist/share-card.js +413 -0
  92. package/dist/share-card.js.map +1 -0
  93. package/dist/snapshot.d.ts +30 -0
  94. package/dist/snapshot.d.ts.map +1 -0
  95. package/dist/snapshot.js +43 -0
  96. package/dist/snapshot.js.map +1 -0
  97. package/dist/tls-capture.d.ts +57 -0
  98. package/dist/tls-capture.d.ts.map +1 -0
  99. package/dist/tls-capture.js +198 -0
  100. package/dist/tls-capture.js.map +1 -0
  101. package/dist/upstream.d.ts +20 -0
  102. package/dist/upstream.d.ts.map +1 -0
  103. package/dist/upstream.js +31 -0
  104. package/dist/upstream.js.map +1 -0
  105. package/dist/version.d.ts +2 -0
  106. package/dist/version.d.ts.map +1 -0
  107. package/dist/version.js +2 -0
  108. package/dist/version.js.map +1 -0
  109. package/package.json +49 -0
@@ -0,0 +1,245 @@
1
+ import { JsonRpcFramer } from '@orcareplay/mcp-shim';
2
+ import { Orca } from './api.js';
3
+ import { ORCA_VERSION } from './version.js';
4
+ /**
5
+ * Orca as tools an agent can call.
6
+ *
7
+ * `@orcareplay/mcp-shim` records an agent's *own* MCP traffic — it is a tee, so nothing has ever
8
+ * let an agent ask orca a question. That is backwards for this product in particular: "replay my
9
+ * last run and tell me what diverged" is the most useful thing an agent could ask a replay
10
+ * debugger, and it is a question no observability tool can answer, because a trace is a file.
11
+ *
12
+ * Written against the framer the shim already has rather than an SDK. The stdio transport is
13
+ * newline-delimited JSON-RPC and the surface is four method names, so a runtime dependency here
14
+ * would cost more than it carries — and this package has none.
15
+ *
16
+ * The tool set is deliberately narrow and read-mostly. `orca_replay` costs nothing and touches no
17
+ * network; `orca_compare` spends real tokens, and says so in its own description, because an
18
+ * agent choosing a tool reads that string and nothing else.
19
+ */
20
+ /** The version of the MCP spec these messages are shaped for. */
21
+ const PROTOCOL_VERSION = '2025-06-18';
22
+ const METHOD_NOT_FOUND = -32601;
23
+ /** Every tool takes `run`, because "which run" is the only question all of them share. */
24
+ const RUN_ARG = {
25
+ run: {
26
+ type: 'string',
27
+ description: 'Run id, or "last" for the newest recording. Defaults to "last". Replay traces are ' +
28
+ 'skipped when resolving "last", so it means the newest run you actually recorded.',
29
+ },
30
+ };
31
+ export const MCP_TOOLS = [
32
+ {
33
+ name: 'orca_list_runs',
34
+ description: 'List every agent run recorded in this project, newest first, with the run it was forked ' +
35
+ 'from where there is one. Start here when you do not already know which run to look at.',
36
+ inputSchema: { type: 'object', properties: {} },
37
+ },
38
+ {
39
+ name: 'orca_show_run',
40
+ description: 'The full timeline of one run: every model turn with its token counts and stop reason, ' +
41
+ 'every tool call with its arguments and result, every shell command with its exit code, ' +
42
+ 'and every file the run changed. This is what tells you why an agent did something, ' +
43
+ 'rather than what it cost.',
44
+ inputSchema: { type: 'object', properties: { ...RUN_ARG } },
45
+ },
46
+ {
47
+ name: 'orca_checkpoints',
48
+ description: 'The points in a run a fork can start from — where the conversation prefix is complete and ' +
49
+ 'the workspace was snapshotted. Use before orca_compare to pick a fork point.',
50
+ inputSchema: { type: 'object', properties: { ...RUN_ARG } },
51
+ },
52
+ {
53
+ name: 'orca_graph',
54
+ description: 'What caused what in a run, as a list of edges. Each edge says which event produced which, ' +
55
+ 'and whether it is `recorded` — the recorder watched it happen and wrote it into the trace ' +
56
+ '— or `inferred`, meaning this derived it just now from the rule it names and the trace ' +
57
+ 'does not vouch for it. Pass `to` to get only the chain that produced one event, which is ' +
58
+ 'the shape of an answer to "why did this fail" rather than "what happened".',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ ...RUN_ARG,
63
+ to: {
64
+ type: 'number',
65
+ description: 'Narrow to the chain that produced this event seq. Omit for the whole run.',
66
+ },
67
+ },
68
+ },
69
+ },
70
+ {
71
+ name: 'orca_replay',
72
+ description: 'Re-run a recording exactly, with the network blocked and no tokens spent, and report what ' +
73
+ 'could not be reproduced: divergences, and requests the recording could not serve. Free ' +
74
+ 'and repeatable. Use it to confirm a failure is deterministic before trying to explain it.',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ ...RUN_ARG,
79
+ worktree: {
80
+ type: 'boolean',
81
+ description: 'Replay in a scratch copy instead of over the working tree. Slower, and leaves the ' +
82
+ 'files you are looking at untouched.',
83
+ },
84
+ },
85
+ },
86
+ },
87
+ {
88
+ name: 'orca_compare',
89
+ description: 'Fork one recorded run onto several models from the same checkpoint — same files, same ' +
90
+ 'conversation prefix — and grade each with a command you choose. SPENDS REAL TOKENS and ' +
91
+ 'reaches the network: every model named is actually called. Ask before using it.',
92
+ inputSchema: {
93
+ type: 'object',
94
+ properties: {
95
+ ...RUN_ARG,
96
+ models: {
97
+ type: 'array',
98
+ items: { type: 'string' },
99
+ description: 'Model ids to compare. Required.',
100
+ },
101
+ from: { type: 'number', description: 'Checkpoint seq to fork at, from orca_checkpoints.' },
102
+ verify: {
103
+ type: 'string',
104
+ description: 'Shell command whose exit code is the verdict, e.g. "npm test".',
105
+ },
106
+ },
107
+ required: ['models'],
108
+ },
109
+ },
110
+ ];
111
+ function asRecord(value) {
112
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
113
+ ? value
114
+ : {};
115
+ }
116
+ /** A tool result in MCP's shape: text content, plus the flag that says it failed. */
117
+ function content(text, isError = false) {
118
+ return { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) };
119
+ }
120
+ function ok(json) {
121
+ return content(JSON.stringify(json, null, 2));
122
+ }
123
+ /**
124
+ * Handle one message.
125
+ *
126
+ * Returns `undefined` for a notification, which by definition has no id to answer — replying to
127
+ * one is a protocol error that some clients treat as fatal.
128
+ */
129
+ export async function handleMcpMessage(orca, message) {
130
+ const req = asRecord(message);
131
+ const { id, method } = req;
132
+ if (id === undefined)
133
+ return undefined;
134
+ const reply = (result) => ({ jsonrpc: '2.0', id, result });
135
+ const fail = (code, message) => ({
136
+ jsonrpc: '2.0',
137
+ id,
138
+ error: { code, message },
139
+ });
140
+ switch (method) {
141
+ case 'initialize':
142
+ return reply({
143
+ protocolVersion: PROTOCOL_VERSION,
144
+ capabilities: { tools: { listChanged: false } },
145
+ serverInfo: { name: 'orcareplay', version: ORCA_VERSION },
146
+ });
147
+ case 'ping':
148
+ return reply({});
149
+ case 'tools/list':
150
+ return reply({ tools: MCP_TOOLS });
151
+ case 'tools/call':
152
+ return reply(await callTool(orca, asRecord(req.params)));
153
+ default:
154
+ return fail(METHOD_NOT_FOUND, `orca's MCP server does not implement '${method ?? '(none)'}'`);
155
+ }
156
+ }
157
+ /**
158
+ * Run one tool.
159
+ *
160
+ * Every failure comes back as `isError` content rather than a JSON-RPC error: the spec draws that
161
+ * line so a model can read what went wrong and try something else, where a transport error ends
162
+ * the session. A bad run id is an ordinary answer here, not a crash.
163
+ */
164
+ async function callTool(orca, params) {
165
+ const name = typeof params['name'] === 'string' ? params['name'] : '';
166
+ const args = asRecord(params['arguments']);
167
+ const run = typeof args['run'] === 'string' && args['run'] !== '' ? args['run'] : 'last';
168
+ const tool = MCP_TOOLS.find((t) => t.name === name);
169
+ if (!tool) {
170
+ return content(`no such tool '${name}'. orca serves: ${MCP_TOOLS.map((t) => t.name).join(', ')}`, true);
171
+ }
172
+ try {
173
+ switch (name) {
174
+ case 'orca_list_runs':
175
+ return ok(await orca.list());
176
+ case 'orca_show_run':
177
+ return ok(await orca.show(run));
178
+ case 'orca_checkpoints':
179
+ return ok(await orca.checkpoints(run));
180
+ case 'orca_graph': {
181
+ const to = args['to'];
182
+ if (to !== undefined && typeof to !== 'number') {
183
+ return content('to must be a number', true);
184
+ }
185
+ return ok(await orca.graph(run, to === undefined ? {} : { to }));
186
+ }
187
+ case 'orca_replay': {
188
+ const worktree = args['worktree'];
189
+ if (worktree !== undefined && typeof worktree !== 'boolean') {
190
+ return content('worktree must be a boolean', true);
191
+ }
192
+ return ok(await orca.replay(run, worktree === true ? { worktree: true } : {}));
193
+ }
194
+ case 'orca_compare': {
195
+ const models = Array.isArray(args['models'])
196
+ ? args['models'].filter((m) => typeof m === 'string')
197
+ : [];
198
+ if (models.length === 0)
199
+ return content('compare needs at least one model id', true);
200
+ const from = args['from'];
201
+ // Coercing a string here would silently fork from checkpoint 0 and produce a comparison
202
+ // whose one variable was not the model.
203
+ if (from !== undefined && typeof from !== 'number') {
204
+ return content(`from must be a number, got ${JSON.stringify(from)}`, true);
205
+ }
206
+ const verify = args['verify'];
207
+ if (verify !== undefined && typeof verify !== 'string') {
208
+ return content('verify must be a string', true);
209
+ }
210
+ return ok(await orca.compare(run, {
211
+ models,
212
+ ...(typeof from === 'number' ? { from } : {}),
213
+ ...(typeof verify === 'string' ? { verify } : {}),
214
+ }));
215
+ }
216
+ default:
217
+ return content(`no such tool '${name}'`, true);
218
+ }
219
+ }
220
+ catch (err) {
221
+ return content(err instanceof Error ? err.message : String(err), true);
222
+ }
223
+ }
224
+ /**
225
+ * Serve MCP over a stream pair until the input ends.
226
+ *
227
+ * Messages are handled one at a time. Concurrency would buy nothing — every tool here is either a
228
+ * file read or a subprocess that owns the working tree — and it would let two replays fight over
229
+ * the same checkout.
230
+ */
231
+ export async function serveMcp({ orca, input, output }) {
232
+ const framer = new JsonRpcFramer();
233
+ for await (const chunk of input) {
234
+ for (const frame of framer.push(chunk)) {
235
+ // A line that is not JSON is a stray log from something upstream, not a message. Dropping it
236
+ // is the only safe answer: there is no id to report an error against.
237
+ if (frame.message === undefined)
238
+ continue;
239
+ const response = await handleMcpMessage(orca, frame.message);
240
+ if (response !== undefined)
241
+ output.write(`${JSON.stringify(response)}\n`);
242
+ }
243
+ }
244
+ }
245
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,111 @@
1
+ import type { TraceWriter } from '@orcareplay/core';
2
+ import type { McpFrameRecord } from '@orcareplay/mcp-shim';
3
+ import type { Output } from './out.js';
4
+ export interface McpCapture {
5
+ /** Path to the rewritten config the agent should be pointed at. */
6
+ configPath: string;
7
+ rewritten: string[];
8
+ skipped: string[];
9
+ /** Frames observed so far, drained by the recorder. */
10
+ drain(): Promise<McpFrameRecord[]>;
11
+ }
12
+ /**
13
+ * A captured frame, as the shim writes it.
14
+ *
15
+ * Re-exported rather than redeclared. The version that used to live here named two of the same
16
+ * fields differently — `server` for `name`, `direction` for `dir` — and since `JSON.parse` casts to
17
+ * whatever the call site claims, nothing anywhere disagreed: every MCP event went into the trace
18
+ * with an undefined server, and `direction === 'in'` was false for all of them, so every request
19
+ * was recorded as a response. A format has one owner, and it is the thing that writes it.
20
+ */
21
+ export type { McpFrameRecord };
22
+ /**
23
+ * The MCP config a *replay* should instrument: the flag if one was given, else whatever the
24
+ * recording itself used.
25
+ *
26
+ * Replay and fork launch the same agent the recording did, so they hit the same blind spot record
27
+ * had before `--mcp-config` existed: without a config the harness either talks to servers orca
28
+ * cannot see, or — for one that requires the variable — does not start at all. The recording knows
29
+ * the answer; it just had nowhere to write it down, since `manifest.argv` holds only the agent's
30
+ * own arguments and `--mcp-config` is orca's.
31
+ *
32
+ * The path is read back from the run's own `mcp_instrumented` note. Nothing is re-instrumented from
33
+ * the *rewritten* config in the parent run directory: its servers already point at the parent's
34
+ * frames file, so reusing it would append this replay's traffic to the recording it is replaying.
35
+ */
36
+ export declare function mcpSourceFrom(flagValue: string | undefined, events: {
37
+ type: string;
38
+ attrs?: Record<string, unknown>;
39
+ }[]): string | undefined;
40
+ /** Did this run capture MCP at all? Decides whether a replay without a config is worth warning about. */
41
+ export declare function usedMcp(events: {
42
+ type: string;
43
+ }[]): boolean;
44
+ /**
45
+ * Set up MCP capture for a replay or a fork, from the flag or from what the recording used.
46
+ *
47
+ * Returns undefined — quietly — when the run never had MCP, which is most runs. When it did and the
48
+ * source config has since moved or been deleted, that is said out loud rather than silently
49
+ * dropping a capture layer: an absent `mcp.*` in the replay would otherwise read as "the agent made
50
+ * no MCP calls" instead of "orca was not looking".
51
+ */
52
+ export declare function mcpForReplay(args: {
53
+ str(name: string): string | undefined;
54
+ bool?(name: string, dflt?: boolean): boolean;
55
+ }, events: {
56
+ type: string;
57
+ attrs?: Record<string, unknown>;
58
+ }[], writer: TraceWriter, out: Output,
59
+ /** The recording's own frames, so the servers are answered from rather than started. */
60
+ recordedFrames?: string): Promise<McpCapture | undefined>;
61
+ /**
62
+ * Point the launched agent at the instrumented config.
63
+ *
64
+ * Every target harness reads one of these; setting all three costs nothing and avoids making the
65
+ * user work out which one their agent uses.
66
+ */
67
+ export declare function pointAtMcpConfig(env: Record<string, string>, configPath: string): void;
68
+ /**
69
+ * Drain captured frames into a trace as `mcp.request` / `mcp.response`.
70
+ *
71
+ * Frames are read off disk after the agent exits, so each carries the moment it passed through the
72
+ * shim and is stamped with that rather than with the drain — `mono_us` is authoritative for
73
+ * duration (spec §2.1), and a frame stamped at the drain can never interleave with the model turns
74
+ * it actually sat between.
75
+ */
76
+ export declare function drainMcpFrames(mcp: McpCapture, writer: TraceWriter, turnAt: (at: number) => number, fallbackTurn: number): Promise<void>;
77
+ /**
78
+ * Set up MCP capture for a run.
79
+ *
80
+ * The mechanism is a config rewrite, not a patch: each stdio server is relaunched through
81
+ * `orca-mcp-shim`, a transparent JSON-RPC tee. The agent's own config file is never touched — a
82
+ * rewritten copy goes in the run directory, and the agent is pointed at that.
83
+ *
84
+ * HTTP and SSE servers are deliberately left alone and reported as skipped; they route through the
85
+ * HTTP proxy instead, and silently dropping them would be worse than not capturing them.
86
+ */
87
+ export declare function setupMcpCapture(opts: {
88
+ sourceConfigPath: string;
89
+ runDir: string;
90
+ out: Output;
91
+ /**
92
+ * Frames to answer from instead of starting the servers.
93
+ *
94
+ * Capture was only half of what this layer is for. The reason to record a server's traffic is
95
+ * that the run stays reproducible once the server is not — the token revoked, the repository
96
+ * moved, the service retired. Replay re-instrumented the same config and started the real server
97
+ * again, so an MCP recording could be read but not reproduced, and came apart on the first call
98
+ * when the server had gone. Pointed at a capture, the shim serves from it and launches nothing.
99
+ */
100
+ replayFrames?: string;
101
+ }): Promise<McpCapture | undefined>;
102
+ /**
103
+ * Exposed for the doctor command: is the shim runnable at all?
104
+ *
105
+ * It checks that the thing it launched *is the shim*, not merely that a process started and
106
+ * stopped. Asking only for a clean exit is what let doctor vouch for a resolution that had landed
107
+ * on the library: that module exits 0 having done nothing, which is indistinguishable from success
108
+ * unless you look at what it said.
109
+ */
110
+ export declare function shimIsRunnable(): Promise<boolean>;
111
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uDAAuD;IACvD,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAAE,GAC1D,MAAM,GAAG,SAAS,CAQpB;AAED,yGAAyG;AACzG,wBAAgB,OAAO,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,OAAO,CAE3D;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAAE,EAC7F,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAAE,EAC3D,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,MAAM;AACX,wFAAwF;AACxF,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA2BjC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAItF;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,EAC9B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAsDlC;AAwBD;;;;;;;GAOG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAqBvD"}
package/dist/mcp.js ADDED
@@ -0,0 +1,221 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readFile, stat, writeFile } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import { join } from 'node:path';
5
+ import { rewriteMcpConfig } from '@orcareplay/adapters';
6
+ /**
7
+ * The MCP config a *replay* should instrument: the flag if one was given, else whatever the
8
+ * recording itself used.
9
+ *
10
+ * Replay and fork launch the same agent the recording did, so they hit the same blind spot record
11
+ * had before `--mcp-config` existed: without a config the harness either talks to servers orca
12
+ * cannot see, or — for one that requires the variable — does not start at all. The recording knows
13
+ * the answer; it just had nowhere to write it down, since `manifest.argv` holds only the agent's
14
+ * own arguments and `--mcp-config` is orca's.
15
+ *
16
+ * The path is read back from the run's own `mcp_instrumented` note. Nothing is re-instrumented from
17
+ * the *rewritten* config in the parent run directory: its servers already point at the parent's
18
+ * frames file, so reusing it would append this replay's traffic to the recording it is replaying.
19
+ */
20
+ export function mcpSourceFrom(flagValue, events) {
21
+ if (flagValue)
22
+ return flagValue;
23
+ for (const event of events) {
24
+ if (event.type !== 'note' || event.attrs?.rule !== 'mcp_instrumented')
25
+ continue;
26
+ const source = event.attrs.source;
27
+ if (typeof source === 'string' && source !== '')
28
+ return source;
29
+ }
30
+ return undefined;
31
+ }
32
+ /** Did this run capture MCP at all? Decides whether a replay without a config is worth warning about. */
33
+ export function usedMcp(events) {
34
+ return events.some((e) => e.type === 'mcp.request' || e.type === 'mcp.response');
35
+ }
36
+ /**
37
+ * Set up MCP capture for a replay or a fork, from the flag or from what the recording used.
38
+ *
39
+ * Returns undefined — quietly — when the run never had MCP, which is most runs. When it did and the
40
+ * source config has since moved or been deleted, that is said out loud rather than silently
41
+ * dropping a capture layer: an absent `mcp.*` in the replay would otherwise read as "the agent made
42
+ * no MCP calls" instead of "orca was not looking".
43
+ */
44
+ export async function mcpForReplay(args, events, writer, out,
45
+ /** The recording's own frames, so the servers are answered from rather than started. */
46
+ recordedFrames) {
47
+ const source = mcpSourceFrom(args.str('mcp-config'), events);
48
+ if (source === undefined) {
49
+ if (usedMcp(events)) {
50
+ out.warn('mcp.not_instrumented', {
51
+ why: 'the recording captured MCP but did not record which config it came from',
52
+ next: 'pass --mcp-config <path> to capture it here too',
53
+ });
54
+ }
55
+ return undefined;
56
+ }
57
+ if (!(await stat(source).catch(() => null))) {
58
+ out.warn('mcp.source_missing', {
59
+ path: source,
60
+ note: 'the config the recording used is no longer there; MCP will not be captured',
61
+ });
62
+ return undefined;
63
+ }
64
+ return setupMcpCapture({
65
+ sourceConfigPath: source,
66
+ runDir: writer.runDir,
67
+ out,
68
+ // A replay that starts the real servers is not a replay of them: it is a second live run whose
69
+ // answers happen to be read back through the same shim. Answering from the recording is what
70
+ // makes an MCP run reproducible after the server it talked to is gone.
71
+ ...(recordedFrames === undefined ? {} : { replayFrames: recordedFrames }),
72
+ });
73
+ }
74
+ /**
75
+ * Point the launched agent at the instrumented config.
76
+ *
77
+ * Every target harness reads one of these; setting all three costs nothing and avoids making the
78
+ * user work out which one their agent uses.
79
+ */
80
+ export function pointAtMcpConfig(env, configPath) {
81
+ env.MCP_CONFIG_PATH = configPath;
82
+ env.CLAUDE_MCP_CONFIG = configPath;
83
+ env.OPENCODE_MCP_CONFIG = configPath;
84
+ }
85
+ /**
86
+ * Drain captured frames into a trace as `mcp.request` / `mcp.response`.
87
+ *
88
+ * Frames are read off disk after the agent exits, so each carries the moment it passed through the
89
+ * shim and is stamped with that rather than with the drain — `mono_us` is authoritative for
90
+ * duration (spec §2.1), and a frame stamped at the drain can never interleave with the model turns
91
+ * it actually sat between.
92
+ */
93
+ export async function drainMcpFrames(mcp, writer, turnAt, fallbackTurn) {
94
+ for (const frame of await mcp.drain()) {
95
+ const at = frame.ts === undefined ? Number.NaN : Date.parse(frame.ts);
96
+ const when = Number.isNaN(at) ? undefined : new Date(at);
97
+ await writer.append({
98
+ type: frame.dir === 'in' ? 'mcp.request' : 'mcp.response',
99
+ actor: 'agent',
100
+ turn: when === undefined ? fallbackTurn : turnAt(at),
101
+ ...(when === undefined ? {} : { occurredAt: when }),
102
+ attrs: { server: frame.name, kind: frame.kind, method: frame.method, id: frame.id },
103
+ payload: frame.raw,
104
+ });
105
+ }
106
+ }
107
+ /**
108
+ * Set up MCP capture for a run.
109
+ *
110
+ * The mechanism is a config rewrite, not a patch: each stdio server is relaunched through
111
+ * `orca-mcp-shim`, a transparent JSON-RPC tee. The agent's own config file is never touched — a
112
+ * rewritten copy goes in the run directory, and the agent is pointed at that.
113
+ *
114
+ * HTTP and SSE servers are deliberately left alone and reported as skipped; they route through the
115
+ * HTTP proxy instead, and silently dropping them would be worse than not capturing them.
116
+ */
117
+ export async function setupMcpCapture(opts) {
118
+ const raw = await readFile(opts.sourceConfigPath, 'utf8').catch(() => undefined);
119
+ if (raw === undefined) {
120
+ opts.out.warn('mcp.config_unreadable', { path: opts.sourceConfigPath });
121
+ return undefined;
122
+ }
123
+ let parsed;
124
+ try {
125
+ parsed = JSON.parse(raw);
126
+ }
127
+ catch (err) {
128
+ opts.out.warn('mcp.config_unparseable', { path: opts.sourceConfigPath, error: String(err) });
129
+ return undefined;
130
+ }
131
+ const framesPath = join(opts.runDir, 'mcp-frames.jsonl');
132
+ const shim = resolveShimEntry();
133
+ // Recording and replay are the same rewrite with a different flag: `--out` writes what the
134
+ // servers say, `--replay` says it back. Both keep `--out` so a replay records its own frames and
135
+ // is itself a run that can be read.
136
+ const shimArgs = opts.replayFrames === undefined
137
+ ? [shim, '--out', framesPath]
138
+ : [shim, '--out', framesPath, '--replay', opts.replayFrames];
139
+ const { config, rewritten, skipped } = rewriteMcpConfig(parsed, process.execPath, shimArgs);
140
+ const configPath = join(opts.runDir, 'mcp-config.json');
141
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
142
+ opts.out.info('mcp.instrumented', {
143
+ servers: rewritten.length,
144
+ skipped: skipped.length,
145
+ mode: opts.replayFrames === undefined ? 'record' : 'replay',
146
+ config: configPath,
147
+ });
148
+ return {
149
+ configPath,
150
+ rewritten,
151
+ skipped,
152
+ async drain() {
153
+ const text = await readFile(framesPath, 'utf8').catch(() => '');
154
+ const records = [];
155
+ for (const line of text.split('\n')) {
156
+ if (line.trim() === '')
157
+ continue;
158
+ try {
159
+ records.push(JSON.parse(line));
160
+ }
161
+ catch {
162
+ // A malformed capture line must never break the recorder; the agent's run matters more.
163
+ }
164
+ }
165
+ return records;
166
+ },
167
+ };
168
+ }
169
+ /**
170
+ * Locate the installed shim entry point, whether running from source or from a published dist.
171
+ *
172
+ * This used to ask for `@orcareplay/mcp-shim/dist/cli.js` and fall back to `@orcareplay/mcp-shim`
173
+ * when that threw. It always threw — the package declares an `exports` map, and an exports map
174
+ * blocks every subpath it does not list — so the fallback ran every time and resolved to
175
+ * `dist/index.js`, which is the library.
176
+ *
177
+ * The consequence was not a missing capture layer. Every stdio MCP server in the agent's config was
178
+ * rewritten to launch that module, which exports and exits: the real server was never started at
179
+ * all, so `--mcp-config` silently broke the agent's MCP servers *and* recorded nothing, while
180
+ * `orca doctor` reported the shim runnable because the wrong process exited 0.
181
+ *
182
+ * So: one declared subpath, and no fallback that can succeed with the wrong file. A resolution
183
+ * failure has to stay a failure — `setupMcpCapture` reports it, and the run continues without MCP
184
+ * capture, which is the honest outcome.
185
+ */
186
+ function resolveShimEntry() {
187
+ const require = createRequire(import.meta.url);
188
+ return require.resolve('@orcareplay/mcp-shim/cli');
189
+ }
190
+ /**
191
+ * Exposed for the doctor command: is the shim runnable at all?
192
+ *
193
+ * It checks that the thing it launched *is the shim*, not merely that a process started and
194
+ * stopped. Asking only for a clean exit is what let doctor vouch for a resolution that had landed
195
+ * on the library: that module exits 0 having done nothing, which is indistinguishable from success
196
+ * unless you look at what it said.
197
+ */
198
+ export async function shimIsRunnable() {
199
+ let entry;
200
+ try {
201
+ entry = resolveShimEntry();
202
+ }
203
+ catch {
204
+ return false;
205
+ }
206
+ return new Promise((resolve) => {
207
+ const child = spawn(process.execPath, [entry, '--help'], {
208
+ stdio: ['ignore', 'ignore', 'pipe'],
209
+ });
210
+ let stderr = '';
211
+ child.stderr?.setEncoding('utf8');
212
+ child.stderr?.on('data', (chunk) => {
213
+ stderr += chunk;
214
+ });
215
+ child.on('error', () => resolve(false));
216
+ // `--help` is not a flag the shim takes, so it prints its usage and exits non-zero. That usage
217
+ // line is the identification: any other program is not this one.
218
+ child.on('close', () => resolve(stderr.includes('orca-mcp-shim --name')));
219
+ });
220
+ }
221
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}