@twelvehart/orcats 0.2.2 → 0.3.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/README.md +3 -3
- package/dist/backends/acp-client.d.ts +1 -1
- package/dist/backends/acp-client.d.ts.map +1 -1
- package/dist/backends/acp-run.d.ts.map +1 -1
- package/dist/backends/claude-run.d.ts.map +1 -1
- package/dist/backends/codex-jsonl.d.ts +2 -1
- package/dist/backends/codex-jsonl.d.ts.map +1 -1
- package/dist/backends/codex-run.d.ts +2 -1
- package/dist/backends/codex-run.d.ts.map +1 -1
- package/dist/backends/codex.d.ts.map +1 -1
- package/dist/backends/pi-run.d.ts.map +1 -1
- package/dist/backends/select.d.ts +1 -1
- package/dist/backends/select.d.ts.map +1 -1
- package/dist/backends/subprocess-run.d.ts +4 -5
- package/dist/backends/subprocess-run.d.ts.map +1 -1
- package/dist/backends/subprocess-termination.d.ts +4 -0
- package/dist/backends/subprocess-termination.d.ts.map +1 -0
- package/dist/baseline/index.d.ts +3 -1
- package/dist/baseline/index.d.ts.map +1 -1
- package/dist/cli/version.d.ts +1 -1
- package/dist/conversation/conversation.d.ts +2 -0
- package/dist/conversation/conversation.d.ts.map +1 -1
- package/dist/conversation/settlement-reservation.d.ts +10 -0
- package/dist/conversation/settlement-reservation.d.ts.map +1 -0
- package/dist/model/backend-config.d.ts +2 -0
- package/dist/model/backend-config.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/backends/acp-client.ts +12 -10
- package/src/backends/acp-run.ts +18 -18
- package/src/backends/claude-run.ts +7 -5
- package/src/backends/codex-jsonl.ts +5 -0
- package/src/backends/codex-run.ts +15 -1
- package/src/backends/codex.ts +4 -1
- package/src/backends/pi-run.ts +8 -2
- package/src/backends/select.ts +4 -1
- package/src/backends/subprocess-run.ts +534 -58
- package/src/backends/subprocess-termination.ts +56 -0
- package/src/baseline/index.ts +4 -2
- package/src/cli/embedded.ts +19 -0
- package/src/cli/main.ts +6 -3
- package/src/cli/version.ts +1 -1
- package/src/conversation/conversation.ts +92 -7
- package/src/conversation/settlement-reservation.ts +127 -0
- package/src/model/backend-config.ts +8 -0
- package/src/monitor/index.ts +4 -2
- package/src/run-output/index.ts +2 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SubprocessProcess } from "./subprocess-run.ts";
|
|
2
|
+
|
|
3
|
+
const DefaultSubprocessTerminationGraceMs = 1_000;
|
|
4
|
+
const DefaultSubprocessForceGraceMs = 1_000;
|
|
5
|
+
|
|
6
|
+
const exitWaitCancellations = new WeakMap<
|
|
7
|
+
SubprocessProcess,
|
|
8
|
+
(error: unknown) => void
|
|
9
|
+
>();
|
|
10
|
+
|
|
11
|
+
export function registerSubprocessExitWaitCancellation(
|
|
12
|
+
process: SubprocessProcess,
|
|
13
|
+
cancel: (error: unknown) => void
|
|
14
|
+
): void {
|
|
15
|
+
exitWaitCancellations.set(process, cancel);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function terminateSubprocess(
|
|
19
|
+
process: SubprocessProcess,
|
|
20
|
+
gracefulMs = DefaultSubprocessTerminationGraceMs,
|
|
21
|
+
forceMs = DefaultSubprocessForceGraceMs
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
try {
|
|
24
|
+
process.kill("SIGTERM");
|
|
25
|
+
if (await exitsWithin(process.exit, gracefulMs)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process.kill("SIGKILL");
|
|
30
|
+
if (!(await exitsWithin(process.exit, forceMs))) {
|
|
31
|
+
throw new Error("subprocess did not exit after SIGKILL");
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const cancelExitWait = exitWaitCancellations.get(process);
|
|
35
|
+
exitWaitCancellations.delete(process);
|
|
36
|
+
cancelExitWait?.(error);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function exitsWithin(
|
|
42
|
+
exit: Promise<number | null>,
|
|
43
|
+
timeoutMs: number
|
|
44
|
+
): Promise<boolean> {
|
|
45
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
46
|
+
const timedOut = new Promise<false>((resolve) => {
|
|
47
|
+
timer = setTimeout(() => {
|
|
48
|
+
resolve(false);
|
|
49
|
+
}, Math.max(timeoutMs, 0));
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
return await Promise.race([exit.then(() => true as const), timedOut]);
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/baseline/index.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface BaselineRepairResult {
|
|
|
31
31
|
readonly usage?: Usage | undefined;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
type BaselineRepairOutput<T = void> = BaselineRepairResult | T;
|
|
35
|
+
|
|
34
36
|
export interface BaselineGateResult {
|
|
35
37
|
readonly policy: BaselinePolicy;
|
|
36
38
|
readonly status: "clean" | "repaired";
|
|
@@ -44,7 +46,7 @@ export interface RunBaselineGateOptions {
|
|
|
44
46
|
readonly commands: readonly VerificationCommand[];
|
|
45
47
|
readonly policy?: BaselinePolicy;
|
|
46
48
|
readonly commandTool?: CommandTool;
|
|
47
|
-
readonly repair?: (issues: readonly BaselineGateIssue[]) => Promise<
|
|
49
|
+
readonly repair?: (issues: readonly BaselineGateIssue[]) => Promise<BaselineRepairOutput>;
|
|
48
50
|
readonly monitor?: Pick<WorkflowMonitor, "stage" | "recordOutcome" | "recordFailure">;
|
|
49
51
|
readonly snapshotDir?: string;
|
|
50
52
|
readonly maxIterations?: number;
|
|
@@ -205,7 +207,7 @@ async function runBaselineGateInner(options: RunBaselineGateOptions): Promise<Ba
|
|
|
205
207
|
|
|
206
208
|
let usage: Usage | undefined;
|
|
207
209
|
const loop = await fixLoop<BaselineGateIssue>(
|
|
208
|
-
|
|
210
|
+
() => Promise.resolve(ok(latest.passed ? [] : [{ message: renderValidationFailure(latest.logs), fixable: true as const }])),
|
|
209
211
|
async (issues) => {
|
|
210
212
|
const repaired = await options.repair?.(issues);
|
|
211
213
|
usage = addUsage(usage, repaired?.usage);
|
package/src/cli/embedded.ts
CHANGED
|
@@ -37,6 +37,22 @@ export function canResolveOrca(scriptPath: string): boolean {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function canResolveFrom(specifier: string, fromDir: string): boolean {
|
|
40
|
+
if (isBunExecutable()) {
|
|
41
|
+
const probe = Bun.spawnSync(
|
|
42
|
+
[
|
|
43
|
+
process.execPath,
|
|
44
|
+
"--eval",
|
|
45
|
+
`Bun.resolveSync(${JSON.stringify(specifier)}, ${JSON.stringify(fromDir)});`,
|
|
46
|
+
],
|
|
47
|
+
{
|
|
48
|
+
stdin: "ignore",
|
|
49
|
+
stdout: "ignore",
|
|
50
|
+
stderr: "ignore",
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
return probe.exitCode === 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
40
56
|
try {
|
|
41
57
|
Bun.resolveSync(specifier, fromDir);
|
|
42
58
|
return true;
|
|
@@ -46,6 +62,9 @@ function canResolveFrom(specifier: string, fromDir: string): boolean {
|
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
function hasProjectPackage(specifier: string, fromDir: string, includeSelfReference: boolean): boolean {
|
|
65
|
+
if (!canResolveFrom(specifier, fromDir)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
49
68
|
for (let dir = fromDir; ; dir = dirname(dir)) {
|
|
50
69
|
if (existsSync(join(dir, "node_modules", ...specifier.split("/"), "package.json"))) {
|
|
51
70
|
return true;
|
package/src/cli/main.ts
CHANGED
|
@@ -304,8 +304,10 @@ function createCliReporter(): RunReporter {
|
|
|
304
304
|
return createRunReporter({
|
|
305
305
|
sinks: [
|
|
306
306
|
createRunPresenter({
|
|
307
|
-
isTTY: process.stderr.isTTY
|
|
308
|
-
writeDiagnostic: (message) =>
|
|
307
|
+
isTTY: process.stderr.isTTY,
|
|
308
|
+
writeDiagnostic: (message) => {
|
|
309
|
+
process.stderr.write(message);
|
|
310
|
+
},
|
|
309
311
|
}),
|
|
310
312
|
],
|
|
311
313
|
});
|
|
@@ -319,7 +321,8 @@ function describeError(error: unknown): string {
|
|
|
319
321
|
return error.reason;
|
|
320
322
|
}
|
|
321
323
|
try {
|
|
322
|
-
|
|
324
|
+
const serialized = JSON.stringify(error) as string | undefined;
|
|
325
|
+
return serialized ?? String(error);
|
|
323
326
|
} catch {
|
|
324
327
|
return String(error);
|
|
325
328
|
}
|
package/src/cli/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const ORCA_VERSION = "0.
|
|
1
|
+
export const ORCA_VERSION = "0.3.0";
|
|
@@ -4,9 +4,15 @@ import {
|
|
|
4
4
|
type BackendTag,
|
|
5
5
|
type ConversationEvent,
|
|
6
6
|
type RuntimeError,
|
|
7
|
+
backendFailed,
|
|
7
8
|
unsupportedFeature
|
|
8
9
|
} from "../model/index.ts";
|
|
9
10
|
import { BoundedAsyncQueue } from "./queue.ts";
|
|
11
|
+
import {
|
|
12
|
+
deferConversationSettlement,
|
|
13
|
+
markConversationCancellationComplete,
|
|
14
|
+
registerConversationCancellationFailureHandler
|
|
15
|
+
} from "./settlement-reservation.ts";
|
|
10
16
|
|
|
11
17
|
export type Outcome<B extends BackendTag = BackendTag> =
|
|
12
18
|
| { readonly type: "success"; readonly result: BackendResult<B> }
|
|
@@ -39,6 +45,7 @@ export class StreamConversation<B extends BackendTag> implements Conversation<B>
|
|
|
39
45
|
private readonly outcome: Promise<Outcome<B>>;
|
|
40
46
|
private settle!: (outcome: Outcome<B>) => void;
|
|
41
47
|
private settled = false;
|
|
48
|
+
private cancellation: Promise<void> | undefined;
|
|
42
49
|
|
|
43
50
|
constructor(private readonly options: StreamConversationOptions<B>) {
|
|
44
51
|
this.queue = new BoundedAsyncQueue(options.capacity ?? 32);
|
|
@@ -80,21 +87,80 @@ export class StreamConversation<B extends BackendTag> implements Conversation<B>
|
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
succeed(result: BackendResult<B>): void {
|
|
83
|
-
this.
|
|
90
|
+
if (this.cancellation !== undefined) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const outcome: Outcome<B> = { type: "success", result };
|
|
94
|
+
if (
|
|
95
|
+
deferConversationSettlement(this, "success", () => {
|
|
96
|
+
if (this.cancellation === undefined) {
|
|
97
|
+
this.complete(outcome);
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.complete(outcome);
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
fail(error: RuntimeError): void {
|
|
87
|
-
this.
|
|
107
|
+
if (this.cancellation !== undefined) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const outcome: Outcome<B> = { type: "failed", error };
|
|
111
|
+
if (
|
|
112
|
+
deferConversationSettlement(this, "failure", () => {
|
|
113
|
+
if (this.cancellation === undefined) {
|
|
114
|
+
this.complete(outcome);
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.complete(outcome);
|
|
88
121
|
}
|
|
89
122
|
|
|
90
|
-
|
|
123
|
+
cancel(reason?: string): Promise<void> {
|
|
91
124
|
if (this.settled) {
|
|
92
|
-
return;
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
if (this.cancellation !== undefined) {
|
|
128
|
+
return this.cancellation;
|
|
93
129
|
}
|
|
94
130
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this
|
|
131
|
+
const cancellation = Promise.withResolvers<undefined>();
|
|
132
|
+
this.cancellation = cancellation.promise;
|
|
133
|
+
registerConversationCancellationFailureHandler(this, (error: unknown) => {
|
|
134
|
+
this.completeCancellationFailure(error, cancellation.reject);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
let cleanup: Promise<void>;
|
|
138
|
+
try {
|
|
139
|
+
this.abortController.abort(reason);
|
|
140
|
+
cleanup = Promise.resolve(this.options.onCancel?.(reason));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this.completeCancellationFailure(error, cancellation.reject);
|
|
143
|
+
return cancellation.promise;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
void cleanup.then(
|
|
147
|
+
() => {
|
|
148
|
+
const outcome: Outcome<B> =
|
|
149
|
+
reason === undefined ? { type: "cancelled" } : { type: "cancelled", reason };
|
|
150
|
+
const publish = (): void => {
|
|
151
|
+
this.complete(outcome);
|
|
152
|
+
cancellation.resolve(undefined);
|
|
153
|
+
};
|
|
154
|
+
if (!deferConversationSettlement(this, "cancellation", publish)) {
|
|
155
|
+
publish();
|
|
156
|
+
}
|
|
157
|
+
markConversationCancellationComplete(this);
|
|
158
|
+
},
|
|
159
|
+
(error: unknown) => {
|
|
160
|
+
this.completeCancellationFailure(error, cancellation.reject);
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
return cancellation.promise;
|
|
98
164
|
}
|
|
99
165
|
|
|
100
166
|
private complete(outcome: Outcome<B>): void {
|
|
@@ -106,4 +172,23 @@ export class StreamConversation<B extends BackendTag> implements Conversation<B>
|
|
|
106
172
|
this.queue.close();
|
|
107
173
|
this.settle(outcome);
|
|
108
174
|
}
|
|
175
|
+
|
|
176
|
+
private completeCancellationFailure(
|
|
177
|
+
error: unknown,
|
|
178
|
+
rejectCancellation: (reason?: unknown) => void
|
|
179
|
+
): void {
|
|
180
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
181
|
+
const outcome: Outcome<B> = {
|
|
182
|
+
type: "failed",
|
|
183
|
+
error: backendFailed(this.backend, `${this.backend} cancellation cleanup failed: ${message}`)
|
|
184
|
+
};
|
|
185
|
+
const publish = (): void => {
|
|
186
|
+
this.complete(outcome);
|
|
187
|
+
rejectCancellation(error);
|
|
188
|
+
};
|
|
189
|
+
if (!deferConversationSettlement(this, "cancellation_failure", publish)) {
|
|
190
|
+
publish();
|
|
191
|
+
}
|
|
192
|
+
markConversationCancellationComplete(this);
|
|
193
|
+
}
|
|
109
194
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
type SettlementKind = "success" | "cancellation" | "failure" | "cancellation_failure";
|
|
2
|
+
|
|
3
|
+
const settlementPriority: Readonly<Record<SettlementKind, number>> = {
|
|
4
|
+
success: 0,
|
|
5
|
+
failure: 1,
|
|
6
|
+
cancellation: 2,
|
|
7
|
+
cancellation_failure: 3
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
interface PendingSettlement {
|
|
11
|
+
readonly kind: SettlementKind;
|
|
12
|
+
readonly publish: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SettlementReservation {
|
|
16
|
+
depth: number;
|
|
17
|
+
pending?: PendingSettlement;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const reservations = new WeakMap<object, SettlementReservation>();
|
|
21
|
+
|
|
22
|
+
interface CancellationCompletion {
|
|
23
|
+
readonly promise: Promise<void>;
|
|
24
|
+
readonly resolve: () => void;
|
|
25
|
+
completed: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cancellationCompletions = new WeakMap<object, CancellationCompletion>();
|
|
29
|
+
const cancellationFailureHandlers = new WeakMap<object, (error: unknown) => void>();
|
|
30
|
+
|
|
31
|
+
function cancellationCompletion(conversation: object): CancellationCompletion {
|
|
32
|
+
const existing = cancellationCompletions.get(conversation);
|
|
33
|
+
if (existing !== undefined) {
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const deferred = Promise.withResolvers<undefined>();
|
|
38
|
+
const created: CancellationCompletion = {
|
|
39
|
+
promise: deferred.promise,
|
|
40
|
+
resolve: () => {
|
|
41
|
+
deferred.resolve(undefined);
|
|
42
|
+
},
|
|
43
|
+
completed: false
|
|
44
|
+
};
|
|
45
|
+
cancellationCompletions.set(conversation, created);
|
|
46
|
+
return created;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function observeConversationCancellationCompletion(
|
|
50
|
+
conversation: object
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
return cancellationCompletion(conversation).promise;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function markConversationCancellationComplete(conversation: object): void {
|
|
56
|
+
const completion = cancellationCompletion(conversation);
|
|
57
|
+
if (completion.completed) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
completion.completed = true;
|
|
61
|
+
completion.resolve();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function registerConversationCancellationFailureHandler(
|
|
65
|
+
conversation: object,
|
|
66
|
+
handler: (error: unknown) => void
|
|
67
|
+
): void {
|
|
68
|
+
cancellationFailureHandlers.set(conversation, handler);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function reportConversationCancellationFailure(
|
|
72
|
+
conversation: object,
|
|
73
|
+
error: unknown
|
|
74
|
+
): boolean {
|
|
75
|
+
const handler = cancellationFailureHandlers.get(conversation);
|
|
76
|
+
if (handler === undefined) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
handler(error);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function reserveConversationSettlement(conversation: object): () => void {
|
|
84
|
+
const reservation = reservations.get(conversation) ?? { depth: 0 };
|
|
85
|
+
reservation.depth += 1;
|
|
86
|
+
reservations.set(conversation, reservation);
|
|
87
|
+
let released = false;
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
if (released) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
released = true;
|
|
94
|
+
reservation.depth -= 1;
|
|
95
|
+
if (reservation.depth > 0) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
reservations.delete(conversation);
|
|
100
|
+
const pending = reservation.pending;
|
|
101
|
+
delete reservation.pending;
|
|
102
|
+
pending?.publish();
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function isConversationSettlementReserved(conversation: object): boolean {
|
|
107
|
+
return reservations.has(conversation);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function deferConversationSettlement(
|
|
111
|
+
conversation: object,
|
|
112
|
+
kind: SettlementKind,
|
|
113
|
+
publish: () => void
|
|
114
|
+
): boolean {
|
|
115
|
+
const reservation = reservations.get(conversation);
|
|
116
|
+
if (reservation === undefined) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
reservation.pending === undefined ||
|
|
122
|
+
settlementPriority[kind] > settlementPriority[reservation.pending.kind]
|
|
123
|
+
) {
|
|
124
|
+
reservation.pending = { kind, publish };
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
@@ -4,6 +4,13 @@ import type { SessionId } from "./brand.ts";
|
|
|
4
4
|
|
|
5
5
|
export type BackendApprovalPolicy = "never" | "on-request" | "on-failure" | "granular" | "untrusted";
|
|
6
6
|
export type BackendSandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
|
7
|
+
export type CodexReasoningEffort =
|
|
8
|
+
| "low"
|
|
9
|
+
| "medium"
|
|
10
|
+
| "high"
|
|
11
|
+
| "xhigh"
|
|
12
|
+
| "max"
|
|
13
|
+
| "ultra";
|
|
7
14
|
|
|
8
15
|
export interface BackendRetryConfig {
|
|
9
16
|
readonly attempts: number;
|
|
@@ -17,6 +24,7 @@ export interface StructuredOutputConfig<Output = unknown> {
|
|
|
17
24
|
|
|
18
25
|
export interface BackendConfig<B extends BackendTag = BackendTag, Output = unknown> {
|
|
19
26
|
readonly model?: string;
|
|
27
|
+
readonly reasoningEffort?: B extends "codex" ? CodexReasoningEffort : never;
|
|
20
28
|
readonly systemPrompt?: string;
|
|
21
29
|
readonly approvalPolicy?: BackendApprovalPolicy;
|
|
22
30
|
readonly readOnly?: boolean;
|
package/src/monitor/index.ts
CHANGED
|
@@ -336,8 +336,10 @@ function defaultRunReporter(writeStatus: ((line: string) => void) | undefined):
|
|
|
336
336
|
return createRunReporter({
|
|
337
337
|
sinks: [
|
|
338
338
|
createRunPresenter({
|
|
339
|
-
isTTY: process.stderr.isTTY
|
|
340
|
-
writeDiagnostic: (text) =>
|
|
339
|
+
isTTY: process.stderr.isTTY,
|
|
340
|
+
writeDiagnostic: (text) => {
|
|
341
|
+
writer(text.endsWith("\n") ? text.slice(0, -1) : text);
|
|
342
|
+
},
|
|
341
343
|
}),
|
|
342
344
|
],
|
|
343
345
|
});
|
package/src/run-output/index.ts
CHANGED
|
@@ -186,7 +186,7 @@ export function createRunReporter(options: RunReporterOptions = {}): RunReporter
|
|
|
186
186
|
|
|
187
187
|
export function createRunPresenter(options: RunPresenterOptions = {}): RunEventSink {
|
|
188
188
|
const env = options.env ?? process.env;
|
|
189
|
-
const isTTY = options.isTTY ?? process.stderr.isTTY
|
|
189
|
+
const isTTY = options.isTTY ?? process.stderr.isTTY;
|
|
190
190
|
const writeDiagnostic =
|
|
191
191
|
options.writeDiagnostic ??
|
|
192
192
|
((text: string) => {
|
|
@@ -285,7 +285,7 @@ function formatCycleProgress(head: string, event: CycleProgressEvent): string {
|
|
|
285
285
|
|
|
286
286
|
function prefix(options: Pick<RunPresenterOptions, "env" | "isTTY"> = {}): string {
|
|
287
287
|
const env = options.env ?? process.env;
|
|
288
|
-
const isTTY = options.isTTY ?? process.stderr.isTTY
|
|
288
|
+
const isTTY = options.isTTY ?? process.stderr.isTTY;
|
|
289
289
|
if (isTTY && !env.NO_COLOR && !env.CI) {
|
|
290
290
|
return "\u001b[36morca\u001b[0m |";
|
|
291
291
|
}
|