@ai-sdk/harness-claude-code 1.0.21 → 1.0.23
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/CHANGELOG.md +19 -0
- package/README.md +3 -1
- package/dist/bridge/index.mjs +152 -45
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.js +36 -113
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/index.ts +105 -80
- package/src/claude-code-bridge-protocol.ts +15 -1
- package/src/claude-code-harness.ts +33 -140
- package/src/claude-code-thinking.ts +8 -0
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @ai-sdk/harness-claude-code
|
|
2
2
|
|
|
3
|
+
## 1.0.23
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 39c8276: fix(harness): improve opaque sandbox bridge error handling
|
|
8
|
+
- 91fe6d8: fix(harness): emit `finish-step` stream parts correctly per the underlying model steps
|
|
9
|
+
- dcc7ecd: fix(harness-claude-code): fix Claude Code not displaying its reasoning by default
|
|
10
|
+
- Updated dependencies [39c8276]
|
|
11
|
+
- Updated dependencies [91fe6d8]
|
|
12
|
+
- Updated dependencies [0be5014]
|
|
13
|
+
- @ai-sdk/harness@1.0.23
|
|
14
|
+
|
|
15
|
+
## 1.0.22
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- @ai-sdk/harness@1.0.22
|
|
20
|
+
- @ai-sdk/provider-utils@5.0.7
|
|
21
|
+
|
|
3
22
|
## 1.0.21
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/README.md
CHANGED
package/dist/bridge/index.mjs
CHANGED
|
@@ -20,6 +20,15 @@ function formatBridgeError(err) {
|
|
|
20
20
|
if (err instanceof Error) {
|
|
21
21
|
return { name: err.name, message: err.message, stack: err.stack };
|
|
22
22
|
}
|
|
23
|
+
if (typeof err === "string") {
|
|
24
|
+
return { message: err };
|
|
25
|
+
}
|
|
26
|
+
if (err !== null && typeof err === "object") {
|
|
27
|
+
try {
|
|
28
|
+
return { message: JSON.stringify(err) };
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
23
32
|
return { message: String(err) };
|
|
24
33
|
}
|
|
25
34
|
function parseEnvList(value) {
|
|
@@ -47,6 +56,7 @@ async function runBridge(options) {
|
|
|
47
56
|
let isFirstTurn = true;
|
|
48
57
|
let turnAbort;
|
|
49
58
|
let currentUserMessages;
|
|
59
|
+
let currentInterruptHandler;
|
|
50
60
|
let debugConfig;
|
|
51
61
|
let consoleCaptureInstalled = false;
|
|
52
62
|
const envDebugEnabled = ENV_TRUTHY.has(
|
|
@@ -164,6 +174,38 @@ async function runBridge(options) {
|
|
|
164
174
|
};
|
|
165
175
|
const rawStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
166
176
|
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
177
|
+
const writeErrorToStderr = (input) => {
|
|
178
|
+
try {
|
|
179
|
+
const formatted = formatBridgeError(input.error);
|
|
180
|
+
rawStderrWrite(
|
|
181
|
+
`[harness:${bridgeType}:error] ${input.message}: ${formatted.message}
|
|
182
|
+
`
|
|
183
|
+
);
|
|
184
|
+
if (formatted.stack) {
|
|
185
|
+
rawStderrWrite(`${formatted.stack}
|
|
186
|
+
`);
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const emitWarning = (input) => {
|
|
192
|
+
try {
|
|
193
|
+
for (const line of input.message.split("\n")) {
|
|
194
|
+
if (line.trim().length > 0) {
|
|
195
|
+
rawStderrWrite(`[harness:${bridgeType}:warn] ${line}
|
|
196
|
+
`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const emitError = (input) => {
|
|
203
|
+
writeErrorToStderr({
|
|
204
|
+
message: input.message ?? "bridge error",
|
|
205
|
+
error: input.error
|
|
206
|
+
});
|
|
207
|
+
emit({ type: "error", error: serialiseError(input.error) });
|
|
208
|
+
};
|
|
167
209
|
const installConsoleCapture = () => {
|
|
168
210
|
if (consoleCaptureInstalled) return;
|
|
169
211
|
consoleCaptureInstalled = true;
|
|
@@ -221,6 +263,7 @@ async function runBridge(options) {
|
|
|
221
263
|
});
|
|
222
264
|
turnAbort = new AbortController();
|
|
223
265
|
currentTurnState = "running";
|
|
266
|
+
currentInterruptHandler = void 0;
|
|
224
267
|
void writeStartConfig(msg);
|
|
225
268
|
void writeBridgeMeta("running");
|
|
226
269
|
const startDebug = msg.debug;
|
|
@@ -242,6 +285,9 @@ async function runBridge(options) {
|
|
|
242
285
|
}),
|
|
243
286
|
pendingUserMessages: [],
|
|
244
287
|
abortSignal: turnAbort.signal,
|
|
288
|
+
onInterrupt: (handler) => {
|
|
289
|
+
currentInterruptHandler = handler;
|
|
290
|
+
},
|
|
245
291
|
firstTurn,
|
|
246
292
|
bridgeLog: (input) => {
|
|
247
293
|
const level = input.level ?? "debug";
|
|
@@ -254,14 +300,17 @@ async function runBridge(options) {
|
|
|
254
300
|
...input.attrs ? { attrs: input.attrs } : {},
|
|
255
301
|
...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
|
|
256
302
|
});
|
|
257
|
-
}
|
|
303
|
+
},
|
|
304
|
+
emitWarning,
|
|
305
|
+
emitError
|
|
258
306
|
};
|
|
259
307
|
currentUserMessages = turn.pendingUserMessages;
|
|
260
308
|
try {
|
|
261
309
|
await onStart(msg, turn);
|
|
262
310
|
} catch (err) {
|
|
263
|
-
|
|
311
|
+
emitError({ error: err, message: "bridge turn failed" });
|
|
264
312
|
} finally {
|
|
313
|
+
currentInterruptHandler = void 0;
|
|
265
314
|
currentTurnState = "waiting";
|
|
266
315
|
void writeBridgeMeta("waiting");
|
|
267
316
|
}
|
|
@@ -289,6 +338,22 @@ async function runBridge(options) {
|
|
|
289
338
|
case "abort":
|
|
290
339
|
turnAbort?.abort();
|
|
291
340
|
return;
|
|
341
|
+
case "interrupt":
|
|
342
|
+
try {
|
|
343
|
+
if (currentInterruptHandler) {
|
|
344
|
+
await currentInterruptHandler();
|
|
345
|
+
} else {
|
|
346
|
+
turnAbort?.abort();
|
|
347
|
+
}
|
|
348
|
+
sendControl({ type: "bridge-interrupted", ok: true });
|
|
349
|
+
} catch (err) {
|
|
350
|
+
sendControl({
|
|
351
|
+
type: "bridge-interrupted",
|
|
352
|
+
ok: false,
|
|
353
|
+
error: serialiseError(err)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
292
357
|
case "resume":
|
|
293
358
|
replay(ws, msg.lastSeenEventId);
|
|
294
359
|
return;
|
|
@@ -383,10 +448,10 @@ async function runBridge(options) {
|
|
|
383
448
|
});
|
|
384
449
|
});
|
|
385
450
|
process.on("uncaughtException", (err) => {
|
|
386
|
-
|
|
451
|
+
emitError({ error: err, message: "uncaught exception" });
|
|
387
452
|
});
|
|
388
453
|
process.on("unhandledRejection", (err) => {
|
|
389
|
-
|
|
454
|
+
emitError({ error: err, message: "unhandled rejection" });
|
|
390
455
|
});
|
|
391
456
|
await new Promise((resolve, reject) => {
|
|
392
457
|
if (wss.address() != null) {
|
|
@@ -488,6 +553,7 @@ var PUBLIC_TO_NATIVE = {
|
|
|
488
553
|
Skill: "Skill"
|
|
489
554
|
};
|
|
490
555
|
var PUBLIC_TOOL_NAMES = Object.keys(PUBLIC_TO_NATIVE);
|
|
556
|
+
var UNRECOVERABLE_API_RETRY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
491
557
|
var NATIVE_TOOL_KINDS = {
|
|
492
558
|
Read: "readonly",
|
|
493
559
|
Glob: "readonly",
|
|
@@ -536,18 +602,6 @@ function resolveInactiveNativeTools(start) {
|
|
|
536
602
|
) : toolFiltering.toolNames;
|
|
537
603
|
return inactiveToolNames.map((name) => toNativeName(name));
|
|
538
604
|
}
|
|
539
|
-
function toThinkingConfig(thinking) {
|
|
540
|
-
switch (thinking) {
|
|
541
|
-
case "adaptive":
|
|
542
|
-
return { type: "adaptive", display: "summarized" };
|
|
543
|
-
case "on":
|
|
544
|
-
return { type: "enabled", display: "summarized" };
|
|
545
|
-
case "off":
|
|
546
|
-
return { type: "disabled" };
|
|
547
|
-
default:
|
|
548
|
-
return void 0;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
605
|
var args = parseArgs(argv.slice(2));
|
|
552
606
|
var workdir = args.workdir;
|
|
553
607
|
var bridgeStateDir = args.bridgeStateDir;
|
|
@@ -610,6 +664,7 @@ function createPermissionOptions(input) {
|
|
|
610
664
|
approvalId,
|
|
611
665
|
toolCallId: approvalId
|
|
612
666
|
});
|
|
667
|
+
input.finishApprovalStep(approvalId);
|
|
613
668
|
const decision = await input.turn.requestToolApproval(approvalId);
|
|
614
669
|
return decision.approved ? { behavior: "allow", updatedInput: toolInput, toolUseID: approvalId } : {
|
|
615
670
|
behavior: "deny",
|
|
@@ -650,6 +705,28 @@ async function runTurn(start, turn) {
|
|
|
650
705
|
}
|
|
651
706
|
const nativeToolCallNames = /* @__PURE__ */ new Map();
|
|
652
707
|
const approvalRequestedToolUseIds = /* @__PURE__ */ new Set();
|
|
708
|
+
const partialBlocks = /* @__PURE__ */ new Map();
|
|
709
|
+
let stepUsage;
|
|
710
|
+
let pendingStepToolUseIds = /* @__PURE__ */ new Set();
|
|
711
|
+
let pendingStepUsage;
|
|
712
|
+
let stepOpen = false;
|
|
713
|
+
const emitFinishStep = (usage) => {
|
|
714
|
+
emit({
|
|
715
|
+
type: "finish-step",
|
|
716
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
717
|
+
usage: usage ?? defaultUsage()
|
|
718
|
+
});
|
|
719
|
+
stepUsage = usage ?? stepUsage;
|
|
720
|
+
pendingStepUsage = void 0;
|
|
721
|
+
pendingStepToolUseIds = /* @__PURE__ */ new Set();
|
|
722
|
+
stepOpen = false;
|
|
723
|
+
};
|
|
724
|
+
const closeStepIfReady = () => {
|
|
725
|
+
if (!stepOpen || pendingStepToolUseIds.size > 0 || partialBlocks.size > 0) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
emitFinishStep(pendingStepUsage);
|
|
729
|
+
};
|
|
653
730
|
const mcpToolUseIds = /* @__PURE__ */ new Set();
|
|
654
731
|
const mcpServers = {};
|
|
655
732
|
if (start.tools && start.tools.length > 0) {
|
|
@@ -707,6 +784,11 @@ async function runTurn(start, turn) {
|
|
|
707
784
|
inactiveNativeTools,
|
|
708
785
|
turn,
|
|
709
786
|
emit,
|
|
787
|
+
finishApprovalStep: (approvalId) => {
|
|
788
|
+
stepOpen = true;
|
|
789
|
+
pendingStepToolUseIds.delete(approvalId);
|
|
790
|
+
closeStepIfReady();
|
|
791
|
+
},
|
|
710
792
|
nativeToolCallNames,
|
|
711
793
|
approvalRequestedToolUseIds
|
|
712
794
|
});
|
|
@@ -718,7 +800,7 @@ async function runTurn(start, turn) {
|
|
|
718
800
|
...skillsOption ? { skills: skillsOption } : {},
|
|
719
801
|
...nativeTools !== void 0 ? { tools: nativeTools } : {},
|
|
720
802
|
...inactiveNativeTools.length > 0 ? { disallowedTools: inactiveNativeTools } : {},
|
|
721
|
-
|
|
803
|
+
thinking: start.thinking,
|
|
722
804
|
includePartialMessages: true,
|
|
723
805
|
// The `PostCompact` hook carries the compaction summary, which the
|
|
724
806
|
// `compact_boundary` system message does not. Latch it for the unified
|
|
@@ -748,28 +830,28 @@ async function runTurn(start, turn) {
|
|
|
748
830
|
abortSignal: abortCtl.signal
|
|
749
831
|
}
|
|
750
832
|
});
|
|
751
|
-
|
|
833
|
+
turn.onInterrupt(() => q.interrupt());
|
|
834
|
+
let turnUsage;
|
|
752
835
|
let totalCostUsd;
|
|
753
836
|
let observedTerminalError;
|
|
754
837
|
let emittedTerminalError = false;
|
|
755
838
|
let emittedTerminalFinish = false;
|
|
756
839
|
let streamStarted = false;
|
|
757
|
-
const partialBlocks = /* @__PURE__ */ new Map();
|
|
758
840
|
const emitTerminalError = (message) => {
|
|
759
841
|
const normalized = message?.trim();
|
|
760
842
|
if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
|
|
761
843
|
observedTerminalError = normalized;
|
|
762
844
|
emittedTerminalError = true;
|
|
763
|
-
|
|
845
|
+
turn.emitError({
|
|
846
|
+
error: normalized,
|
|
847
|
+
message: "claude-code terminal error"
|
|
848
|
+
});
|
|
764
849
|
queryInput.close();
|
|
765
850
|
abortCtl.abort();
|
|
766
851
|
};
|
|
767
852
|
try {
|
|
768
853
|
for await (const msg of q) {
|
|
769
854
|
if (abortCtl.signal.aborted) break;
|
|
770
|
-
if (typeof msg.error === "string" && msg.error.trim()) {
|
|
771
|
-
observedTerminalError = msg.error.trim();
|
|
772
|
-
}
|
|
773
855
|
const type = msg.type;
|
|
774
856
|
if (!streamStarted) {
|
|
775
857
|
const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
|
|
@@ -779,14 +861,21 @@ async function runTurn(start, turn) {
|
|
|
779
861
|
});
|
|
780
862
|
streamStarted = true;
|
|
781
863
|
}
|
|
782
|
-
if (type === "
|
|
783
|
-
|
|
864
|
+
if (type === "system" && msg.subtype === "api_retry") {
|
|
865
|
+
if (typeof msg.error_status === "number" && UNRECOVERABLE_API_RETRY_STATUSES.has(msg.error_status)) {
|
|
866
|
+
emitTerminalError(
|
|
867
|
+
`HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`
|
|
868
|
+
);
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
turn.emitWarning({ message: formatApiRetryWarning(msg) });
|
|
784
872
|
continue;
|
|
785
873
|
}
|
|
786
|
-
if (
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
874
|
+
if (typeof msg.error === "string" && msg.error.trim()) {
|
|
875
|
+
observedTerminalError = msg.error.trim();
|
|
876
|
+
}
|
|
877
|
+
if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
|
|
878
|
+
emitTerminalError(msg.error);
|
|
790
879
|
continue;
|
|
791
880
|
}
|
|
792
881
|
if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
|
|
@@ -809,17 +898,25 @@ async function runTurn(start, turn) {
|
|
|
809
898
|
continue;
|
|
810
899
|
}
|
|
811
900
|
if (type === "assistant" && msg.message?.content) {
|
|
901
|
+
const usage = mapUsage(msg.message.usage);
|
|
902
|
+
const toolUseIds = [];
|
|
903
|
+
let opensStep = false;
|
|
812
904
|
for (const block of msg.message.content) {
|
|
813
905
|
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
906
|
+
toolUseIds.push(block.id);
|
|
814
907
|
const mcpPrefix = "mcp__harness-tools__";
|
|
815
908
|
if (block.name.startsWith(mcpPrefix)) {
|
|
909
|
+
pendingStepToolUseIds.add(block.id);
|
|
816
910
|
mcpToolUseIds.add(block.id);
|
|
911
|
+
opensStep = true;
|
|
817
912
|
continue;
|
|
818
913
|
}
|
|
819
914
|
nativeToolCallNames.set(block.id, block.name);
|
|
820
915
|
if (approvalRequestedToolUseIds.has(block.id)) {
|
|
821
916
|
continue;
|
|
822
917
|
}
|
|
918
|
+
pendingStepToolUseIds.add(block.id);
|
|
919
|
+
opensStep = true;
|
|
823
920
|
emit({
|
|
824
921
|
type: "tool-call",
|
|
825
922
|
toolCallId: block.id,
|
|
@@ -830,6 +927,10 @@ async function runTurn(start, turn) {
|
|
|
830
927
|
});
|
|
831
928
|
}
|
|
832
929
|
}
|
|
930
|
+
if (opensStep || toolUseIds.length === 0) {
|
|
931
|
+
stepOpen = true;
|
|
932
|
+
if (usage) pendingStepUsage = usage;
|
|
933
|
+
}
|
|
833
934
|
continue;
|
|
834
935
|
}
|
|
835
936
|
if (type === "user" && msg.message?.content) {
|
|
@@ -837,6 +938,7 @@ async function runTurn(start, turn) {
|
|
|
837
938
|
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
838
939
|
if (mcpToolUseIds.has(block.tool_use_id)) {
|
|
839
940
|
mcpToolUseIds.delete(block.tool_use_id);
|
|
941
|
+
pendingStepToolUseIds.delete(block.tool_use_id);
|
|
840
942
|
continue;
|
|
841
943
|
}
|
|
842
944
|
approvalRequestedToolUseIds.delete(block.tool_use_id);
|
|
@@ -853,8 +955,10 @@ async function runTurn(start, turn) {
|
|
|
853
955
|
result,
|
|
854
956
|
isError
|
|
855
957
|
});
|
|
958
|
+
pendingStepToolUseIds.delete(block.tool_use_id);
|
|
856
959
|
}
|
|
857
960
|
}
|
|
961
|
+
closeStepIfReady();
|
|
858
962
|
continue;
|
|
859
963
|
}
|
|
860
964
|
if (type === "result") {
|
|
@@ -866,17 +970,11 @@ async function runTurn(start, turn) {
|
|
|
866
970
|
}
|
|
867
971
|
const usage = msg.usage ?? msg.message?.usage;
|
|
868
972
|
const harnessUsage = mapUsage(usage);
|
|
869
|
-
if (harnessUsage)
|
|
973
|
+
if (harnessUsage) turnUsage = harnessUsage;
|
|
870
974
|
if (typeof msg.total_cost_usd === "number") {
|
|
871
975
|
totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
|
|
872
976
|
}
|
|
873
|
-
|
|
874
|
-
emit({
|
|
875
|
-
type: "finish-step",
|
|
876
|
-
finishReason: { unified: "stop", raw: "stop" },
|
|
877
|
-
usage: harnessUsage ?? defaultUsage(),
|
|
878
|
-
...metadata ? { harnessMetadata: metadata } : {}
|
|
879
|
-
});
|
|
977
|
+
if (stepOpen) emitFinishStep(harnessUsage ?? pendingStepUsage);
|
|
880
978
|
queryInput.close();
|
|
881
979
|
break;
|
|
882
980
|
} else {
|
|
@@ -889,7 +987,7 @@ async function runTurn(start, turn) {
|
|
|
889
987
|
}
|
|
890
988
|
} catch (err) {
|
|
891
989
|
if (!(abortCtl.signal.aborted && emittedTerminalError)) {
|
|
892
|
-
|
|
990
|
+
turn.emitError({ error: err, message: "claude-code turn failed" });
|
|
893
991
|
}
|
|
894
992
|
return;
|
|
895
993
|
} finally {
|
|
@@ -901,10 +999,25 @@ async function runTurn(start, turn) {
|
|
|
901
999
|
emit({
|
|
902
1000
|
type: "finish",
|
|
903
1001
|
finishReason: { unified: "stop", raw: "stop" },
|
|
904
|
-
totalUsage: stepUsage ?? defaultUsage(),
|
|
1002
|
+
totalUsage: turnUsage ?? stepUsage ?? defaultUsage(),
|
|
905
1003
|
...totalCostUsd !== void 0 ? { harnessMetadata: { "claude-code": { costUsd: totalCostUsd } } } : {}
|
|
906
1004
|
});
|
|
907
1005
|
}
|
|
1006
|
+
function formatApiRetryWarning(msg) {
|
|
1007
|
+
const details = [];
|
|
1008
|
+
if (typeof msg.attempt === "number") {
|
|
1009
|
+
const maxRetries = typeof msg.max_retries === "number" ? `/${msg.max_retries}` : "";
|
|
1010
|
+
details.push(`attempt ${msg.attempt}${maxRetries}`);
|
|
1011
|
+
}
|
|
1012
|
+
if (typeof msg.error_status === "number") {
|
|
1013
|
+
details.push(`HTTP ${msg.error_status}`);
|
|
1014
|
+
}
|
|
1015
|
+
if (typeof msg.retry_delay_ms === "number") {
|
|
1016
|
+
details.push(`retrying in ${msg.retry_delay_ms}ms`);
|
|
1017
|
+
}
|
|
1018
|
+
if (msg.error) details.push(msg.error);
|
|
1019
|
+
return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
|
|
1020
|
+
}
|
|
908
1021
|
function handleStreamEvent(event, partialBlocks, send) {
|
|
909
1022
|
if (!event || typeof event.index !== "number") return;
|
|
910
1023
|
const index = event.index;
|
|
@@ -1071,12 +1184,6 @@ function parseArgs(args2) {
|
|
|
1071
1184
|
}
|
|
1072
1185
|
return out;
|
|
1073
1186
|
}
|
|
1074
|
-
function serialiseError2(err) {
|
|
1075
|
-
if (err instanceof Error) {
|
|
1076
|
-
return { name: err.name, message: err.message, stack: err.stack };
|
|
1077
|
-
}
|
|
1078
|
-
return err;
|
|
1079
|
-
}
|
|
1080
1187
|
function emitFatal(message) {
|
|
1081
1188
|
stdout2.write(JSON.stringify({ type: "bridge-fatal", message }) + "\n");
|
|
1082
1189
|
process.exit(1);
|