threadwire 0.1.14 → 0.1.17
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 +11 -0
- package/README.md +9 -7
- package/docs/delegated-result-protocol.md +20 -0
- package/docs/evidence-artifacts.md +37 -5
- package/package.json +1 -1
- package/scripts/install-local-launcher.js +23 -1
- package/scripts/verify-package.js +1 -0
- package/src/activity-log-status.js +291 -0
- package/src/activity-log.js +91 -3
- package/src/cli.js +198 -37
- package/src/delegated-result-admission.js +13 -0
- package/src/evidence-store.js +93 -23
- package/src/provider-capacity-codex.js +2 -1
- package/src/providers/executable.js +17 -4
- package/src/providers/index.js +109 -5
- package/src/telegram-ingress/core.js +180 -61
- package/src/worker-control.js +11 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {createHash, randomUUID, timingSafeEqual} from "node:crypto"
|
|
4
4
|
import {abortable} from "../absolute-deadline.js"
|
|
5
|
+
import {DelegatedResultAdmission} from "../delegated-result-admission.js"
|
|
5
6
|
import {evidenceTelegramDestination} from "../evidence-store.js"
|
|
6
7
|
import {NoticeQueue} from "../notice-queue.js"
|
|
7
8
|
import {createFetchTransport} from "../notifiers/fetch-transport.js"
|
|
@@ -136,6 +137,17 @@ export async function dispatchWorker(job, config, dependencies = {}) {
|
|
|
136
137
|
let control
|
|
137
138
|
/** @type {Awaited<ReturnType<import("../evidence-store.js").EvidenceStore["createArtifact"]>> | undefined} */
|
|
138
139
|
let evidence
|
|
140
|
+
/** @type {Awaited<ReturnType<import("../evidence-store.js").EvidenceStore["createArtifact"]>> | undefined} */
|
|
141
|
+
let resultEvidence
|
|
142
|
+
/** @type {{destinationId: string, runId: string} | undefined} */
|
|
143
|
+
let evidenceOwner
|
|
144
|
+
let resultEvidenceFinalized = false
|
|
145
|
+
/** @type {unknown} */
|
|
146
|
+
let evidenceError
|
|
147
|
+
/** @type {unknown} */
|
|
148
|
+
let deliveryError
|
|
149
|
+
const deliveryIdentity = randomUUID()
|
|
150
|
+
const admission = new DelegatedResultAdmission({output: {write: () => {}}})
|
|
139
151
|
try {
|
|
140
152
|
provider = createProviderImpl(job.provider, [], job.prompt, undefined, providerEnvironment)
|
|
141
153
|
const transport = createFetchTransportImpl(config.botToken, undefined, config.telegramRequestTimeoutMs)
|
|
@@ -147,18 +159,99 @@ export async function dispatchWorker(job, config, dependencies = {}) {
|
|
|
147
159
|
NoticeQueueClass: NoticeQueueImpl,
|
|
148
160
|
RelayClass: RelayImpl
|
|
149
161
|
})
|
|
150
|
-
|
|
162
|
+
evidenceOwner = dependencies.evidenceStore?.createOwnerScope({
|
|
151
163
|
destinationId: evidenceTelegramDestination(job.target, job.senderId),
|
|
152
|
-
runId:
|
|
153
|
-
})
|
|
154
|
-
evidence = evidenceOwner === undefined ? undefined : await dependencies.evidenceStore?.createArtifact(evidenceOwner, {
|
|
155
|
-
contentType: "text/plain; charset=utf-8",
|
|
156
|
-
redactions: await collectEvidenceRedactions(dependencies.providerEnvironment ?? {})
|
|
164
|
+
runId: deliveryIdentity
|
|
157
165
|
})
|
|
166
|
+
if (evidenceOwner !== undefined) {
|
|
167
|
+
const redactions = await collectEvidenceRedactions(dependencies.providerEnvironment ?? {})
|
|
168
|
+
evidence = await dependencies.evidenceStore?.createArtifact(evidenceOwner, {
|
|
169
|
+
contentType: "text/plain; charset=utf-8",
|
|
170
|
+
redactions
|
|
171
|
+
})
|
|
172
|
+
try {
|
|
173
|
+
resultEvidence = await dependencies.evidenceStore?.createArtifact(evidenceOwner, {
|
|
174
|
+
contentType: "application/json",
|
|
175
|
+
redactions,
|
|
176
|
+
delivery: {identity: deliveryIdentity, state: "delivery_pending"}
|
|
177
|
+
})
|
|
178
|
+
} catch (error) {
|
|
179
|
+
await evidence?.abort()
|
|
180
|
+
evidence = undefined
|
|
181
|
+
throw error
|
|
182
|
+
}
|
|
183
|
+
}
|
|
158
184
|
} catch (error) {
|
|
159
185
|
earlyKimiPreflight?.deadline?.close()
|
|
160
186
|
throw error
|
|
161
187
|
}
|
|
188
|
+
/** @param {import("../types.js").WorkerEvent} event */
|
|
189
|
+
const acceptEvent = async (event) => {
|
|
190
|
+
if (event.type === "text-delta") admission.acceptConclusionEvent(event)
|
|
191
|
+
try {
|
|
192
|
+
await control.accept(event)
|
|
193
|
+
} catch (error) {
|
|
194
|
+
deliveryError ??= error
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** @param {unknown} record */
|
|
198
|
+
const acceptRecord = (record) => {
|
|
199
|
+
const id = job.provider === "kimi" && isolatedRuntimeClient !== undefined
|
|
200
|
+
? kimiSessionEnvelopeId(record)
|
|
201
|
+
: provider.sessionId(record)
|
|
202
|
+
if (id !== undefined) {
|
|
203
|
+
admission.setContinuationHandle(id)
|
|
204
|
+
dependencies.activity?.recordSession(provider.name, id)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** @param {number} exitCode */
|
|
208
|
+
const finalizeEvidence = async (exitCode) => {
|
|
209
|
+
if (resultEvidence === undefined || resultEvidenceFinalized) return
|
|
210
|
+
let rawEvidenceReady = false
|
|
211
|
+
if (evidence !== undefined) {
|
|
212
|
+
try {
|
|
213
|
+
await evidence.finalize()
|
|
214
|
+
rawEvidenceReady = true
|
|
215
|
+
} catch (error) {
|
|
216
|
+
await evidence.abort()
|
|
217
|
+
evidenceError ??= error
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
admission.addArtifactHandle(resultEvidence.handle)
|
|
221
|
+
if (rawEvidenceReady) admission.addArtifactHandle(/** @type {NonNullable<typeof evidence>} */ (evidence).handle)
|
|
222
|
+
const terminal = {
|
|
223
|
+
state: /** @type {"completed" | "failed"} */ (exitCode === 0 ? "completed" : "failed"),
|
|
224
|
+
exitCode
|
|
225
|
+
}
|
|
226
|
+
await resultEvidence.append("provider-result", `${JSON.stringify(admission.createProviderResult(terminal))}\n`)
|
|
227
|
+
await resultEvidence.finalize()
|
|
228
|
+
resultEvidenceFinalized = true
|
|
229
|
+
}
|
|
230
|
+
const finishDelivery = async () => {
|
|
231
|
+
if (resultEvidence !== undefined && deliveryError === undefined) {
|
|
232
|
+
try {
|
|
233
|
+
await control.acceptEvidenceHandle(resultEvidence.handle)
|
|
234
|
+
} catch (error) {
|
|
235
|
+
deliveryError ??= error
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
await control.close()
|
|
240
|
+
} catch (error) {
|
|
241
|
+
deliveryError ??= error
|
|
242
|
+
}
|
|
243
|
+
if (
|
|
244
|
+
resultEvidenceFinalized && resultEvidence !== undefined && evidenceOwner !== undefined
|
|
245
|
+
&& dependencies.evidenceStore !== undefined
|
|
246
|
+
) {
|
|
247
|
+
await dependencies.evidenceStore.recordDelivery(
|
|
248
|
+
evidenceOwner,
|
|
249
|
+
resultEvidence.handle,
|
|
250
|
+
deliveryError === undefined ? "delivery_succeeded" : "delivery_failed"
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
if (deliveryError !== undefined) throw deliveryError
|
|
254
|
+
}
|
|
162
255
|
let evidenceTransferred = false
|
|
163
256
|
try {
|
|
164
257
|
await evidence?.append("prompt", `prompt\n${job.prompt}\nprovider-stream\n`)
|
|
@@ -181,33 +274,56 @@ export async function dispatchWorker(job, config, dependencies = {}) {
|
|
|
181
274
|
prompt: job.prompt,
|
|
182
275
|
providerArguments: [],
|
|
183
276
|
...(preflight.deadline === undefined ? {} : {deadline: preflight.deadline}),
|
|
184
|
-
onEvent:
|
|
185
|
-
onRecord: async (record) =>
|
|
186
|
-
const id = job.provider === "kimi" ? kimiSessionEnvelopeId(record) : provider.sessionId(record)
|
|
187
|
-
if (id !== undefined) dependencies.activity?.recordSession(provider.name, id)
|
|
188
|
-
},
|
|
277
|
+
onEvent: acceptEvent,
|
|
278
|
+
onRecord: async (record) => acceptRecord(record),
|
|
189
279
|
...(job.provider === "kimi" ? {} : {
|
|
190
280
|
onStdoutChunk: (chunk) => evidence?.append("provider-stdout", chunk),
|
|
191
281
|
onStderrChunk: (chunk) => evidence?.append("provider-stderr", chunk)
|
|
192
282
|
})
|
|
193
283
|
})
|
|
194
|
-
let exitCode
|
|
284
|
+
let exitCode = 2
|
|
285
|
+
/** @type {Error | undefined} */
|
|
286
|
+
let executionError
|
|
195
287
|
try {
|
|
196
288
|
exitCode = await abortable(isolatedRun.completion, preflight.deadline?.signal)
|
|
197
289
|
} catch (error) {
|
|
198
|
-
|
|
199
|
-
|
|
290
|
+
executionError = error instanceof Error ? error : new Error("Isolated runtime run failed")
|
|
291
|
+
try {
|
|
292
|
+
await isolatedRun.cancel(executionError)
|
|
293
|
+
} catch (cancelError) {
|
|
294
|
+
executionError = new AggregateError(
|
|
295
|
+
[executionError, cancelError],
|
|
296
|
+
"Isolated runtime failed and cleanup could not be confirmed"
|
|
297
|
+
)
|
|
298
|
+
}
|
|
200
299
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
await
|
|
205
|
-
|
|
300
|
+
await finalizeEvidence(exitCode)
|
|
301
|
+
let propagatedExecutionError = executionError
|
|
302
|
+
try {
|
|
303
|
+
await finishDelivery()
|
|
304
|
+
} catch (error) {
|
|
305
|
+
if (propagatedExecutionError === undefined) throw error
|
|
306
|
+
propagatedExecutionError = new AggregateError(
|
|
307
|
+
[propagatedExecutionError, error],
|
|
308
|
+
propagatedExecutionError.message
|
|
309
|
+
)
|
|
206
310
|
}
|
|
311
|
+
if (evidenceError !== undefined) {
|
|
312
|
+
if (propagatedExecutionError === undefined) throw evidenceError
|
|
313
|
+
propagatedExecutionError = new AggregateError(
|
|
314
|
+
[propagatedExecutionError, evidenceError],
|
|
315
|
+
propagatedExecutionError.message
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
if (propagatedExecutionError !== undefined) throw propagatedExecutionError
|
|
319
|
+
if (exitCode !== 0) throw new Error(`Isolated ${job.provider === "kimi" ? "Kimi" : "Codex"} worker exited with status ${exitCode}`)
|
|
207
320
|
return
|
|
208
321
|
} catch (error) {
|
|
209
322
|
await control.close().catch(() => {})
|
|
210
|
-
|
|
323
|
+
if (!resultEvidenceFinalized) {
|
|
324
|
+
await evidence?.abort()
|
|
325
|
+
await resultEvidence?.abort()
|
|
326
|
+
}
|
|
211
327
|
throw error
|
|
212
328
|
} finally {
|
|
213
329
|
isolatedDeadline?.close()
|
|
@@ -260,15 +376,12 @@ export async function dispatchWorker(job, config, dependencies = {}) {
|
|
|
260
376
|
provider: provider.name,
|
|
261
377
|
parse: provider.parse,
|
|
262
378
|
...(provider.completion === undefined ? {} : {completion: provider.completion}),
|
|
263
|
-
onEvent:
|
|
379
|
+
onEvent: acceptEvent,
|
|
264
380
|
onSpawn: (pid) => {
|
|
265
381
|
if (pid !== undefined) dependencies.activity?.recordStarted(provider.name, pid)
|
|
266
382
|
spawnGate.resolve()
|
|
267
383
|
},
|
|
268
|
-
onRecord: async (record) =>
|
|
269
|
-
const id = provider.sessionId(record)
|
|
270
|
-
if (id !== undefined) dependencies.activity?.recordSession(provider.name, id)
|
|
271
|
-
},
|
|
384
|
+
onRecord: async (record) => acceptRecord(record),
|
|
272
385
|
onStdoutChunk: (chunk) => evidence?.append("provider-stdout", chunk),
|
|
273
386
|
onStderrChunk: (chunk) => evidence?.append("provider-stderr", chunk)
|
|
274
387
|
})
|
|
@@ -278,49 +391,61 @@ export async function dispatchWorker(job, config, dependencies = {}) {
|
|
|
278
391
|
// reported. Always notify settlement so concurrency slots are released.
|
|
279
392
|
evidenceTransferred = true
|
|
280
393
|
void (async () => {
|
|
394
|
+
let exitCode = 2
|
|
281
395
|
/** @type {Error | undefined} */
|
|
282
|
-
let
|
|
396
|
+
let failure
|
|
397
|
+
let failureReported = false
|
|
283
398
|
try {
|
|
284
399
|
try {
|
|
285
|
-
|
|
400
|
+
exitCode = await running
|
|
286
401
|
if (job.provider === "kimi" && spawnGate.settled) {
|
|
287
|
-
|
|
288
|
-
await control.accept({
|
|
402
|
+
await acceptEvent({
|
|
289
403
|
type: "lifecycle",
|
|
290
404
|
phase: exitCode === 0 ? "completed" : "failed",
|
|
291
405
|
summary: exitCode === 0 ? "Kimi worker completed" : "Kimi worker failed"
|
|
292
406
|
})
|
|
293
|
-
if (
|
|
407
|
+
if (exitCode !== 0) failure = new Error(`Kimi worker exited with status ${exitCode}`)
|
|
294
408
|
}
|
|
295
409
|
} catch (error) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
try {
|
|
303
|
-
await control.close()
|
|
304
|
-
} catch {
|
|
305
|
-
/* Best-effort close after worker failure. */
|
|
306
|
-
}
|
|
307
|
-
return
|
|
410
|
+
failure = error instanceof Error ? error : new Error(safeFailureMessage(error))
|
|
411
|
+
}
|
|
412
|
+
if (!spawnGate.settled && failure === undefined) {
|
|
413
|
+
exitCode = 2
|
|
414
|
+
failure = new Error("Worker exited before spawn was observed")
|
|
308
415
|
}
|
|
309
416
|
|
|
310
417
|
try {
|
|
311
|
-
await
|
|
418
|
+
await finalizeEvidence(exitCode)
|
|
312
419
|
} catch (error) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
420
|
+
await evidence?.abort()
|
|
421
|
+
await resultEvidence?.abort()
|
|
422
|
+
failure ??= error instanceof Error ? error : new Error(safeFailureMessage(error))
|
|
423
|
+
}
|
|
424
|
+
if (evidenceError !== undefined) {
|
|
425
|
+
failure ??= evidenceError instanceof Error ? evidenceError : new Error(safeFailureMessage(evidenceError))
|
|
319
426
|
}
|
|
320
427
|
|
|
321
|
-
if (
|
|
322
|
-
|
|
428
|
+
if (spawnGate.settled && failure !== undefined) {
|
|
429
|
+
reportBackgroundFailure(failure)
|
|
430
|
+
failureReported = true
|
|
323
431
|
}
|
|
432
|
+
|
|
433
|
+
if (evidence === undefined || resultEvidenceFinalized) {
|
|
434
|
+
try {
|
|
435
|
+
await finishDelivery()
|
|
436
|
+
} catch (error) {
|
|
437
|
+
failure ??= error instanceof Error ? error : new Error(safeFailureMessage(error))
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
try {
|
|
441
|
+
await control.close()
|
|
442
|
+
} catch (error) {
|
|
443
|
+
failure ??= error instanceof Error ? error : new Error(safeFailureMessage(error))
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (!spawnGate.settled) spawnGate.reject(/** @type {Error} */ (failure))
|
|
448
|
+
else if (failure !== undefined && !failureReported) reportBackgroundFailure(failure)
|
|
324
449
|
} catch (error) {
|
|
325
450
|
if (spawnGate.settled) {
|
|
326
451
|
reportBackgroundFailure(error)
|
|
@@ -328,15 +453,6 @@ export async function dispatchWorker(job, config, dependencies = {}) {
|
|
|
328
453
|
spawnGate.reject(error instanceof Error ? error : new Error(safeFailureMessage(error)))
|
|
329
454
|
}
|
|
330
455
|
} finally {
|
|
331
|
-
if (evidence !== undefined) {
|
|
332
|
-
try {
|
|
333
|
-
await evidence.finalize()
|
|
334
|
-
await sender.send(`Evidence: ${evidence.handle}`)
|
|
335
|
-
} catch (error) {
|
|
336
|
-
await evidence.abort()
|
|
337
|
-
reportBackgroundFailure(error)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
456
|
notifySettled()
|
|
341
457
|
}
|
|
342
458
|
})()
|
|
@@ -344,7 +460,10 @@ export async function dispatchWorker(job, config, dependencies = {}) {
|
|
|
344
460
|
await spawned
|
|
345
461
|
} finally {
|
|
346
462
|
try {
|
|
347
|
-
if (!evidenceTransferred)
|
|
463
|
+
if (!evidenceTransferred) {
|
|
464
|
+
await evidence?.abort()
|
|
465
|
+
await resultEvidence?.abort()
|
|
466
|
+
}
|
|
348
467
|
} finally {
|
|
349
468
|
earlyKimiPreflight?.deadline?.close()
|
|
350
469
|
}
|
package/src/worker-control.js
CHANGED
|
@@ -98,6 +98,17 @@ export class WorkerControl {
|
|
|
98
98
|
})
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/** @param {string} handle */
|
|
102
|
+
acceptEvidenceHandle(handle) {
|
|
103
|
+
if (!/^evidence_[A-Za-z0-9_-]{43}$/u.test(handle)) throw new Error("Invalid evidence handle")
|
|
104
|
+
return this.enqueueOperation(async () => {
|
|
105
|
+
this.ensureHealthy()
|
|
106
|
+
this.renderedNotices.push({text: `Evidence: ${handle}`})
|
|
107
|
+
await this.flushLoop(false)
|
|
108
|
+
this.ensureHealthy()
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
101
112
|
close() {
|
|
102
113
|
if (!this.closePromise) {
|
|
103
114
|
this.closePromise = this.enqueueOperation(async () => {
|