@llblab/pi-actors 0.43.0 → 0.44.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/AGENTS.md +16 -10
- package/CHANGELOG.md +425 -536
- package/README.md +13 -11
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +2 -1
- package/dist/lib/async-runs.js +24 -34
- package/dist/lib/automatic-review-runtime.d.ts +1 -1
- package/dist/lib/automatic-review-runtime.js +5 -5
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +38 -4
- package/dist/lib/control-projection.d.ts +20 -0
- package/dist/lib/control-projection.js +66 -0
- package/dist/lib/control.d.ts +3 -0
- package/dist/lib/control.js +27 -14
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/file-state.d.ts +4 -1
- package/dist/lib/file-state.js +118 -44
- package/dist/lib/inspector-overlay.d.ts +2 -0
- package/dist/lib/inspector-overlay.js +124 -69
- package/dist/lib/limits.d.ts +15 -3
- package/dist/lib/limits.js +15 -3
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-control.js +6 -2
- package/dist/lib/review-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -5
- package/dist/lib/run-evidence-policy.d.ts +95 -0
- package/dist/lib/run-evidence-policy.js +177 -0
- package/dist/lib/run-ui-runtime.js +2 -0
- package/dist/lib/runs-control-delivery.d.ts +8 -1
- package/dist/lib/runs-control-delivery.js +38 -15
- package/dist/lib/runs-controls.d.ts +8 -4
- package/dist/lib/runs-controls.js +189 -50
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +26 -2
- package/dist/lib/runs-trace.js +411 -18
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- package/dist/lib/runtime-triage.d.ts +29 -0
- package/dist/lib/runtime-triage.js +60 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.js +91 -18
- package/dist/lib/tools-message.d.ts +1 -2
- package/dist/lib/tools-message.js +6 -6
- package/dist/lib/tools-response.d.ts +0 -1
- package/dist/lib/tools-response.js +0 -9
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +1 -1
- package/dist/lib/trace-projection.js +107 -41
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +40 -90
- package/dist/scripts/music-player.mjs +48 -142
- package/dist/scripts/release-gates.mjs +56 -3
- package/dist/scripts/validate-recipe.mjs +5 -4
- package/dist/skills/actors/SKILL.md +17 -11
- package/dist/skills/swarm/SKILL.md +2 -4
- package/docs/README.md +1 -4
- package/docs/actor-inspector.md +6 -5
- package/docs/async-runs.md +11 -9
- package/docs/command-templates.md +6 -116
- package/docs/recipe-library.md +4 -6
- package/docs/releasing.md +28 -0
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +2 -2
- package/index.ts +1 -1
- package/lib/async-runs.ts +26 -50
- package/lib/automatic-review-runtime.ts +7 -7
- package/lib/command-templates.ts +44 -4
- package/lib/control-projection.ts +105 -0
- package/lib/control.ts +33 -18
- package/lib/draft-sleep.ts +3 -3
- package/lib/file-state.ts +91 -63
- package/lib/inspector-overlay.ts +108 -61
- package/lib/limits.ts +15 -3
- package/lib/observability.ts +55 -57
- package/lib/prompts.ts +1 -1
- package/lib/recipe-control.ts +9 -2
- package/lib/review-control.ts +4 -5
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-control-delivery.ts +45 -17
- package/lib/runs-controls.ts +180 -102
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +499 -20
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-triage.ts +106 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +94 -20
- package/lib/tools-message.ts +7 -8
- package/lib/tools-response.ts +0 -12
- package/lib/tools.ts +4 -4
- package/lib/trace-projection.ts +156 -71
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +40 -90
- package/scripts/music-player.mjs +48 -142
- package/scripts/release-gates.mjs +56 -3
- package/scripts/validate-recipe.mjs +5 -4
- package/skills/actors/SKILL.md +17 -11
- package/skills/swarm/SKILL.md +2 -4
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -44
- package/docs/actors-deep-reference.md +0 -108
- package/docs/component-recipes.md +0 -45
- package/docs/task-first-recipes.md +0 -261
- package/lib/runtime-notifier.ts +0 -211
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure bounded Run-evidence policy.
|
|
3
|
+
* Zones: Trace retention, compaction accounting, Control capacity and admission
|
|
4
|
+
* Owns deterministic limits policy; journal I/O, locks, lifecycle authorization, and projection stay in adapters.
|
|
5
|
+
* Trace keeps the newest physical suffix because semantic priority would silently
|
|
6
|
+
* redefine causal history. Admitted Controls never expire: only a real terminal
|
|
7
|
+
* transition or generation replacement proves that durable work is finished.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as Limits from "./limits.ts";
|
|
11
|
+
|
|
12
|
+
export const TRACE_COMPACTION_KIND = "runtime.trace_compacted";
|
|
13
|
+
export const TRACE_COMPACTION_VERSION = 1;
|
|
14
|
+
export type TraceJournalUsage = { bytes: number; events: number };
|
|
15
|
+
export type TraceRetentionCandidate<T> = { encodedBytes: number; value: T };
|
|
16
|
+
export type TraceRetentionSelection<T> = {
|
|
17
|
+
droppedEvents: number; retained: TraceRetentionCandidate<T>[];
|
|
18
|
+
retainedBytes: number; retainedEvents: number;
|
|
19
|
+
};
|
|
20
|
+
export interface TraceCompactionData {
|
|
21
|
+
version: typeof TRACE_COMPACTION_VERSION;
|
|
22
|
+
compactions_total: number;
|
|
23
|
+
dropped_valid_events_total: number;
|
|
24
|
+
dropped_malformed_lines_total: number;
|
|
25
|
+
dropped_bytes_total: number;
|
|
26
|
+
dropped_event_count_exact: boolean;
|
|
27
|
+
retained_events: number;
|
|
28
|
+
retained_bytes: number;
|
|
29
|
+
history_complete: false;
|
|
30
|
+
}
|
|
31
|
+
export interface TraceCompactionDelta {
|
|
32
|
+
dropped_valid_events: number;
|
|
33
|
+
dropped_malformed_lines: number;
|
|
34
|
+
dropped_bytes: number;
|
|
35
|
+
dropped_event_count_exact: boolean;
|
|
36
|
+
retained_events: number;
|
|
37
|
+
retained_bytes: number;
|
|
38
|
+
}
|
|
39
|
+
export type RunControlStatus = "queued" | "delivered" | "claimed" | "handled" | "failed";
|
|
40
|
+
export type RunControlCapacity = {
|
|
41
|
+
available: number; backpressured: boolean; limit: number;
|
|
42
|
+
oldest_pending_at?: string; pending: number;
|
|
43
|
+
};
|
|
44
|
+
export type RunControlAdmissionIntegrity =
|
|
45
|
+
| "valid" | "unreadable" | "malformed" | "noncanonical"
|
|
46
|
+
| "invalid_record" | "invalid_status_timestamp" | "generation_mismatch"
|
|
47
|
+
| "journal_bytes";
|
|
48
|
+
export interface RunControlAdmissionErrorDetails {
|
|
49
|
+
reason: "control_backpressure" | "control_journal_integrity";
|
|
50
|
+
pending: number;
|
|
51
|
+
limit: number;
|
|
52
|
+
journal_bytes: number;
|
|
53
|
+
journal_limit: number;
|
|
54
|
+
oldest_pending_at?: string;
|
|
55
|
+
run_instance_id: string;
|
|
56
|
+
next_actions: string[];
|
|
57
|
+
integrity_reason?: string;
|
|
58
|
+
}
|
|
59
|
+
export type RunControlAdmissionDecision =
|
|
60
|
+
{ admitted: true; capacity: RunControlCapacity } |
|
|
61
|
+
{ admitted: false; capacity: RunControlCapacity; error: RunControlAdmissionErrorDetails };
|
|
62
|
+
export type RunControlAdmissionInput = {
|
|
63
|
+
integrity: RunControlAdmissionIntegrity; journalBytes: number;
|
|
64
|
+
newRecordBytes: number; records: readonly unknown[];
|
|
65
|
+
retainedJournalBytes: number; runInstanceId: string;
|
|
66
|
+
capacity?: RunControlCapacity;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const PENDING_CONTROL_STATUSES = new Set<RunControlStatus>(["queued", "delivered", "claimed"]);
|
|
70
|
+
const TERMINAL_CONTROL_STATUSES = new Set<RunControlStatus>(["handled", "failed"]);
|
|
71
|
+
const CONTROL_ADMISSION_NEXT_ACTIONS = [
|
|
72
|
+
"inspect target=run:<id> view=control",
|
|
73
|
+
"inspect target=runtime view=triage",
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function isCount(value: number): boolean {
|
|
77
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
78
|
+
}
|
|
79
|
+
function assertCount(value: number, name: string): void {
|
|
80
|
+
if (!isCount(value)) throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
81
|
+
}
|
|
82
|
+
function boundedAdd(left: number, right: number): number {
|
|
83
|
+
return Math.min(Number.MAX_SAFE_INTEGER, left + right);
|
|
84
|
+
}
|
|
85
|
+
export function traceAppendFits(usage: TraceJournalUsage, encodedEventBytes: number): boolean {
|
|
86
|
+
if (!isCount(usage.events) || !isCount(usage.bytes) || !isCount(encodedEventBytes)) return false;
|
|
87
|
+
return usage.events + 1 <= Limits.TRACE_JOURNAL_MAX_EVENTS &&
|
|
88
|
+
usage.bytes + encodedEventBytes <= Limits.TRACE_JOURNAL_MAX_BYTES;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function selectNewestTraceSuffix<T>(
|
|
92
|
+
candidates: readonly TraceRetentionCandidate<T>[],
|
|
93
|
+
reserve: { bytes?: number; events?: number } = {},
|
|
94
|
+
): TraceRetentionSelection<T> {
|
|
95
|
+
const reservedBytes = reserve.bytes ?? 0;
|
|
96
|
+
const reservedEvents = reserve.events ?? 0;
|
|
97
|
+
assertCount(reservedBytes, "reserved Trace bytes");
|
|
98
|
+
assertCount(reservedEvents, "reserved Trace events");
|
|
99
|
+
const byteBudget = Math.max(0, Limits.TRACE_JOURNAL_TARGET_BYTES - reservedBytes);
|
|
100
|
+
const eventBudget = Math.max(0, Limits.TRACE_JOURNAL_TARGET_EVENTS - reservedEvents);
|
|
101
|
+
let retainedBytes = 0;
|
|
102
|
+
let start = candidates.length;
|
|
103
|
+
while (start > 0 && candidates.length - start < eventBudget) {
|
|
104
|
+
const candidate = candidates[start - 1]!;
|
|
105
|
+
assertCount(candidate.encodedBytes, "encoded Trace bytes");
|
|
106
|
+
if (retainedBytes + candidate.encodedBytes > byteBudget) break;
|
|
107
|
+
retainedBytes += candidate.encodedBytes;
|
|
108
|
+
start -= 1;
|
|
109
|
+
}
|
|
110
|
+
const retained = candidates.slice(start);
|
|
111
|
+
return {
|
|
112
|
+
droppedEvents: candidates.length - retained.length,
|
|
113
|
+
retained,
|
|
114
|
+
retainedBytes,
|
|
115
|
+
retainedEvents: retained.length,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function accumulateTraceCompactionStatistics(
|
|
120
|
+
previous: TraceCompactionData | undefined,
|
|
121
|
+
delta: TraceCompactionDelta,
|
|
122
|
+
): TraceCompactionData {
|
|
123
|
+
for (const [name, value] of Object.entries(delta)) {
|
|
124
|
+
if (name !== "dropped_event_count_exact") assertCount(value as number, name);
|
|
125
|
+
}
|
|
126
|
+
const prior = previous ?? {
|
|
127
|
+
compactions_total: 0,
|
|
128
|
+
dropped_bytes_total: 0,
|
|
129
|
+
dropped_event_count_exact: true,
|
|
130
|
+
dropped_malformed_lines_total: 0,
|
|
131
|
+
dropped_valid_events_total: 0,
|
|
132
|
+
};
|
|
133
|
+
const eventCountOverflow = prior.dropped_valid_events_total +
|
|
134
|
+
delta.dropped_valid_events > Number.MAX_SAFE_INTEGER;
|
|
135
|
+
return {
|
|
136
|
+
version: TRACE_COMPACTION_VERSION,
|
|
137
|
+
compactions_total: boundedAdd(prior.compactions_total, 1),
|
|
138
|
+
dropped_valid_events_total: boundedAdd(prior.dropped_valid_events_total, delta.dropped_valid_events),
|
|
139
|
+
dropped_malformed_lines_total: boundedAdd(prior.dropped_malformed_lines_total, delta.dropped_malformed_lines),
|
|
140
|
+
dropped_bytes_total: boundedAdd(prior.dropped_bytes_total, delta.dropped_bytes),
|
|
141
|
+
dropped_event_count_exact: prior.dropped_event_count_exact &&
|
|
142
|
+
delta.dropped_event_count_exact && !eventCountOverflow,
|
|
143
|
+
retained_events: delta.retained_events,
|
|
144
|
+
retained_bytes: delta.retained_bytes,
|
|
145
|
+
history_complete: false,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function traceCompactionMarkerInput(data: TraceCompactionData): {
|
|
150
|
+
data: TraceCompactionData; kind: typeof TRACE_COMPACTION_KIND; level: "warning";
|
|
151
|
+
} {
|
|
152
|
+
return { data, kind: TRACE_COMPACTION_KIND, level: "warning" };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function classifyRunControlRecord(value: unknown): "pending" | "terminal" | undefined {
|
|
156
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
157
|
+
const status = (value as { status?: unknown }).status as RunControlStatus;
|
|
158
|
+
if (PENDING_CONTROL_STATUSES.has(status)) return "pending";
|
|
159
|
+
if (TERMINAL_CONTROL_STATUSES.has(status)) return "terminal";
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function runControlStatusTimestamp(
|
|
164
|
+
record: Record<string, unknown>, status: RunControlStatus,
|
|
165
|
+
): unknown {
|
|
166
|
+
if (status === "failed") return record.failed_at;
|
|
167
|
+
if (status === "handled") return record.handled_at;
|
|
168
|
+
if (status === "claimed") return record.claimed_at;
|
|
169
|
+
if (status === "delivered") return record.delivered_at;
|
|
170
|
+
return record.queued_at;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function computeRunControlCapacity(records: readonly unknown[]): RunControlCapacity {
|
|
174
|
+
let oldest: { timestamp: string; timestampMs: number } | undefined;
|
|
175
|
+
let pending = 0;
|
|
176
|
+
for (const value of records) {
|
|
177
|
+
if (classifyRunControlRecord(value) !== "pending") continue;
|
|
178
|
+
pending += 1;
|
|
179
|
+
const record = value as Record<string, unknown>;
|
|
180
|
+
const timestamp = runControlStatusTimestamp(record, record.status as RunControlStatus);
|
|
181
|
+
const timestampMs = typeof timestamp === "string" ? Date.parse(timestamp) : Number.NaN;
|
|
182
|
+
if (typeof timestamp === "string" && Number.isFinite(timestampMs) &&
|
|
183
|
+
(!oldest || timestampMs < oldest.timestampMs)) {
|
|
184
|
+
oldest = { timestamp, timestampMs };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
available: Math.max(0, Limits.RUN_CONTROL_PENDING_LIMIT - pending),
|
|
189
|
+
backpressured: pending >= Limits.RUN_CONTROL_PENDING_LIMIT,
|
|
190
|
+
limit: Limits.RUN_CONTROL_PENDING_LIMIT,
|
|
191
|
+
...(oldest ? { oldest_pending_at: oldest.timestamp } : {}),
|
|
192
|
+
pending,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function decideRunControlAdmission(
|
|
197
|
+
input: RunControlAdmissionInput,
|
|
198
|
+
): RunControlAdmissionDecision {
|
|
199
|
+
const capacity = input.capacity ?? computeRunControlCapacity(input.records);
|
|
200
|
+
const reject = (
|
|
201
|
+
reason: RunControlAdmissionErrorDetails["reason"],
|
|
202
|
+
integrityReason?: string,
|
|
203
|
+
): RunControlAdmissionDecision => ({
|
|
204
|
+
admitted: false,
|
|
205
|
+
capacity,
|
|
206
|
+
error: {
|
|
207
|
+
reason,
|
|
208
|
+
pending: capacity.pending,
|
|
209
|
+
limit: capacity.limit,
|
|
210
|
+
journal_bytes: input.journalBytes,
|
|
211
|
+
journal_limit: Limits.RUN_CONTROL_JOURNAL_MAX_BYTES,
|
|
212
|
+
...(capacity.oldest_pending_at ? { oldest_pending_at: capacity.oldest_pending_at } : {}),
|
|
213
|
+
run_instance_id: input.runInstanceId,
|
|
214
|
+
next_actions: [...CONTROL_ADMISSION_NEXT_ACTIONS],
|
|
215
|
+
...(integrityReason ? { integrity_reason: integrityReason } : {}),
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
if (input.integrity !== "valid") return reject("control_journal_integrity", input.integrity);
|
|
219
|
+
if (!isCount(input.journalBytes) || !isCount(input.retainedJournalBytes) ||
|
|
220
|
+
!isCount(input.newRecordBytes) ||
|
|
221
|
+
input.journalBytes > Limits.RUN_CONTROL_JOURNAL_MAX_BYTES) {
|
|
222
|
+
return reject("control_journal_integrity", "journal_bytes");
|
|
223
|
+
}
|
|
224
|
+
for (const value of input.records) {
|
|
225
|
+
const classification = classifyRunControlRecord(value);
|
|
226
|
+
if (!classification) return reject("control_journal_integrity", "invalid_record");
|
|
227
|
+
if (classification !== "pending") continue;
|
|
228
|
+
const record = value as Record<string, unknown>;
|
|
229
|
+
const timestamp = runControlStatusTimestamp(record, record.status as RunControlStatus);
|
|
230
|
+
if (typeof timestamp !== "string" || !Number.isFinite(Date.parse(timestamp))) {
|
|
231
|
+
return reject("control_journal_integrity", "invalid_status_timestamp");
|
|
232
|
+
}
|
|
233
|
+
if (record.run_instance_id !== input.runInstanceId) {
|
|
234
|
+
return reject("control_journal_integrity", "generation_mismatch");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (capacity.backpressured ||
|
|
238
|
+
input.retainedJournalBytes + input.newRecordBytes > Limits.RUN_CONTROL_JOURNAL_MAX_BYTES) {
|
|
239
|
+
return reject("control_backpressure");
|
|
240
|
+
}
|
|
241
|
+
return { admitted: true, capacity };
|
|
242
|
+
}
|
package/lib/run-ui-runtime.ts
CHANGED
|
@@ -106,6 +106,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
106
106
|
ownerId: deps.getRunOwnerId(ctx),
|
|
107
107
|
sink: Pi.createNotificationSink(deps.pi, ctx),
|
|
108
108
|
state: observation,
|
|
109
|
+
includeAttention: true,
|
|
109
110
|
});
|
|
110
111
|
reportDiagnostics(ctx);
|
|
111
112
|
},
|
|
@@ -141,6 +142,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
141
142
|
},
|
|
142
143
|
start(ctx) {
|
|
143
144
|
close();
|
|
145
|
+
Observability.primeRunAttentionState(observation, deps.getRunOwnerId(ctx));
|
|
144
146
|
update(ctx, true, true);
|
|
145
147
|
watcher.refresh();
|
|
146
148
|
reconciliation.start();
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
import { createConnection } from "node:net";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
|
|
18
|
+
import * as Control from "./control.ts";
|
|
19
|
+
import * as Limits from "./limits.ts";
|
|
18
20
|
import {
|
|
19
21
|
appendRunControlInStateDir,
|
|
20
22
|
updateRunControlStatusInStateDir,
|
|
@@ -37,7 +39,26 @@ export interface DeliverRunControlRequest {
|
|
|
37
39
|
run_instance_id: string;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
const PREFLIGHT_CONTROL_ID = "00000000-0000-4000-8000-000000000000";
|
|
43
|
+
|
|
44
|
+
export function encodeRunControlWire(wire: {
|
|
45
|
+
action: string;
|
|
46
|
+
id: string;
|
|
47
|
+
input?: unknown;
|
|
48
|
+
}): { bytes: number; payload: string } {
|
|
49
|
+
const payload = `${JSON.stringify({
|
|
50
|
+
id: wire.id,
|
|
51
|
+
action: wire.action,
|
|
52
|
+
...(wire.input !== undefined ? { input: wire.input } : {}),
|
|
53
|
+
})}\n`;
|
|
54
|
+
const bytes = Buffer.byteLength(payload);
|
|
55
|
+
if (bytes > Limits.CONTROL_WIRE_MAX_BYTES) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Run Control wire record exceeds the ${Limits.CONTROL_WIRE_MAX_BYTES}-byte portable bound`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return { bytes, payload };
|
|
61
|
+
}
|
|
41
62
|
|
|
42
63
|
export function readRunControlEndpoint(
|
|
43
64
|
stateDir: string,
|
|
@@ -107,7 +128,21 @@ export async function deliverRunControl(
|
|
|
107
128
|
request: DeliverRunControlRequest,
|
|
108
129
|
options: DeliverRunControlOptions = {},
|
|
109
130
|
): Promise<Record<string, unknown>> {
|
|
110
|
-
const
|
|
131
|
+
const action = Control.normalizeControlAction(request.action);
|
|
132
|
+
const input = request.input === undefined
|
|
133
|
+
? undefined
|
|
134
|
+
: Control.normalizeControlInput(request.input);
|
|
135
|
+
const normalizedRequest = {
|
|
136
|
+
action,
|
|
137
|
+
...(input !== undefined ? { input } : {}),
|
|
138
|
+
run_instance_id: request.run_instance_id,
|
|
139
|
+
};
|
|
140
|
+
encodeRunControlWire({
|
|
141
|
+
id: PREFLIGHT_CONTROL_ID,
|
|
142
|
+
action,
|
|
143
|
+
...(input !== undefined ? { input } : {}),
|
|
144
|
+
});
|
|
145
|
+
const control = appendRunControlInStateDir(stateDir, normalizedRequest);
|
|
111
146
|
const endpoint = readRunControlEndpoint(stateDir, request.run_instance_id);
|
|
112
147
|
if (!endpoint) {
|
|
113
148
|
updateRunControlStatusInStateDir(
|
|
@@ -118,31 +153,24 @@ export async function deliverRunControl(
|
|
|
118
153
|
["queued", "delivered"],
|
|
119
154
|
);
|
|
120
155
|
throw Object.assign(new Error("Run Control endpoint is not ready."), {
|
|
121
|
-
action
|
|
156
|
+
action,
|
|
122
157
|
control_id: control.id,
|
|
123
158
|
reason: "endpoint_not_ready",
|
|
124
159
|
run,
|
|
125
160
|
run_instance_id: request.run_instance_id,
|
|
126
161
|
});
|
|
127
162
|
}
|
|
128
|
-
const wire = {
|
|
129
|
-
id: control.id,
|
|
130
|
-
action: request.action,
|
|
131
|
-
...(request.input !== undefined ? { input: request.input } : {}),
|
|
132
|
-
};
|
|
133
|
-
const payload = `${JSON.stringify(wire)}\n`;
|
|
134
|
-
const bytes = Buffer.byteLength(payload);
|
|
135
163
|
try {
|
|
164
|
+
const { bytes, payload } = encodeRunControlWire({
|
|
165
|
+
id: control.id,
|
|
166
|
+
action,
|
|
167
|
+
...(input !== undefined ? { input } : {}),
|
|
168
|
+
});
|
|
136
169
|
let written: number;
|
|
137
170
|
if (endpoint.type === "fifo") {
|
|
138
171
|
if ((options.platform ?? process.platform) === "win32") {
|
|
139
172
|
throw new Error("FIFO Control delivery is unsupported on native Windows");
|
|
140
173
|
}
|
|
141
|
-
if (bytes > FIFO_ATOMIC_CONTROL_MAX_BYTES) {
|
|
142
|
-
throw new Error(
|
|
143
|
-
`FIFO Control payload exceeds the ${FIFO_ATOMIC_CONTROL_MAX_BYTES}-byte portable atomic-write bound`,
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
174
|
written = sendToFifo(endpoint, payload);
|
|
147
175
|
} else {
|
|
148
176
|
written = await sendToNamedPipe(endpoint, payload, options.namedPipeSend);
|
|
@@ -152,7 +180,7 @@ export async function deliverRunControl(
|
|
|
152
180
|
}
|
|
153
181
|
updateRunControlStatusInStateDir(stateDir, control.id, "delivered");
|
|
154
182
|
return {
|
|
155
|
-
action
|
|
183
|
+
action,
|
|
156
184
|
bytes,
|
|
157
185
|
control_id: control.id,
|
|
158
186
|
delivery: "delivered",
|
|
@@ -170,7 +198,7 @@ export async function deliverRunControl(
|
|
|
170
198
|
["queued", "delivered"],
|
|
171
199
|
);
|
|
172
200
|
throw Object.assign(new Error(`Run Control delivery failed: ${reason}`), {
|
|
173
|
-
action
|
|
201
|
+
action,
|
|
174
202
|
control_id: control.id,
|
|
175
203
|
endpoint_type: endpoint.type,
|
|
176
204
|
reason: "delivery_failed",
|