threadwire 0.1.5 → 0.1.8
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 +40 -0
- package/README.md +17 -4
- package/TELEGRAM-INGRESS.md +8 -2
- package/bin/isolated-runtime.js +5 -0
- package/bin/model-broker.js +5 -0
- package/docs/card-10520-plan.md +45 -0
- package/docs/container-runtime.md +3 -2
- package/docs/delegated-result-protocol.md +161 -0
- package/docs/evidence-artifacts.md +106 -0
- package/docs/isolated-provider-runtime.md +137 -0
- package/package.json +5 -1
- package/scripts/provider-shims/front-door.sh.template +18 -0
- package/scripts/verify-package.js +22 -1
- package/src/absolute-deadline.js +94 -0
- package/src/cli.js +362 -42
- package/src/context-budget-metrics.js +180 -0
- package/src/delegated-result-admission.js +377 -0
- package/src/docker-api.js +131 -0
- package/src/evidence-store.js +1472 -0
- package/src/isolated-runtime-client.js +149 -0
- package/src/isolated-runtime.js +982 -0
- package/src/isolated-state.js +409 -0
- package/src/isolated-worker.js +123 -0
- package/src/model-broker-policy.js +139 -0
- package/src/model-broker.js +313 -0
- package/src/mount-policy.js +28 -0
- package/src/normalized-output.js +68 -0
- package/src/notice-queue.js +22 -7
- package/src/providers/opencode.js +42 -18
- package/src/relay-write.js +44 -0
- package/src/relay.js +6 -6
- package/src/run-worker.js +158 -40
- package/src/telegram-ingress/command.js +16 -0
- package/src/telegram-ingress/config.js +96 -11
- package/src/telegram-ingress/core.js +201 -89
- package/src/telegram-ingress/http.js +17 -1
- package/src/telegram-webhook.js +22 -0
- package/src/types.js +2 -2
- package/src/worker-control.js +294 -0
- package/src/hermes-protocol.js +0 -126
|
@@ -55,6 +55,24 @@ fi
|
|
|
55
55
|
@@WORKER_SUBCOMMAND_SHIFT@@
|
|
56
56
|
|
|
57
57
|
threadwire_relay_arguments=(run --provider "$THREADWIRE_PROVIDER" --target "$THREADWIRE_TARGET")
|
|
58
|
+
case "${THREADWIRE_RELAY_WRITE:-0}" in
|
|
59
|
+
0) ;;
|
|
60
|
+
1)
|
|
61
|
+
if [[ ! "${THREADWIRE_WORKSPACE_PROFILE:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]]; then
|
|
62
|
+
printf '%s\n' 'threadwire: relay write capability requires a valid THREADWIRE_WORKSPACE_PROFILE' >&2
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
if [[ "$THREADWIRE_PROVIDER" != codex ]]; then
|
|
66
|
+
printf '%s\n' 'threadwire: relay write capability is only supported for codex' >&2
|
|
67
|
+
exit 2
|
|
68
|
+
fi
|
|
69
|
+
threadwire_relay_arguments+=(--workspace-profile "$THREADWIRE_WORKSPACE_PROFILE" --relay-write)
|
|
70
|
+
;;
|
|
71
|
+
*)
|
|
72
|
+
printf '%s\n' 'threadwire: relay write capability must be enabled with THREADWIRE_RELAY_WRITE=1' >&2
|
|
73
|
+
exit 2
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
58
76
|
if [[ -n "${THREADWIRE_PROCESS_NUMBER:-}" ]]; then
|
|
59
77
|
threadwire_relay_arguments+=(--process-number "$THREADWIRE_PROCESS_NUMBER")
|
|
60
78
|
fi
|
|
@@ -14,12 +14,19 @@ const npmEnvironment = {
|
|
|
14
14
|
npm_config_dry_run: "false"
|
|
15
15
|
}
|
|
16
16
|
const EXPECTED_FILES = [
|
|
17
|
+
"CHANGELOG.md",
|
|
17
18
|
"LICENSE",
|
|
18
19
|
"README.md",
|
|
19
20
|
"TELEGRAM-INGRESS.md",
|
|
20
21
|
"bin/telegram-webhook.js",
|
|
21
22
|
"bin/threadwire.js",
|
|
23
|
+
"bin/isolated-runtime.js",
|
|
24
|
+
"bin/model-broker.js",
|
|
25
|
+
"docs/card-10520-plan.md",
|
|
22
26
|
"docs/container-runtime.md",
|
|
27
|
+
"docs/delegated-result-protocol.md",
|
|
28
|
+
"docs/evidence-artifacts.md",
|
|
29
|
+
"docs/isolated-provider-runtime.md",
|
|
23
30
|
"package.json",
|
|
24
31
|
"scripts/atomic-install.js",
|
|
25
32
|
"scripts/install-local-launcher.js",
|
|
@@ -29,9 +36,21 @@ const EXPECTED_FILES = [
|
|
|
29
36
|
"scripts/provider-shims/front-door.sh.template",
|
|
30
37
|
"scripts/provider-shims/opencode-local-fleet.adapter.sh",
|
|
31
38
|
"scripts/verify-package.js",
|
|
39
|
+
"src/absolute-deadline.js",
|
|
32
40
|
"src/activity-log.js",
|
|
41
|
+
"src/normalized-output.js",
|
|
33
42
|
"src/cli.js",
|
|
34
|
-
"src/
|
|
43
|
+
"src/context-budget-metrics.js",
|
|
44
|
+
"src/delegated-result-admission.js",
|
|
45
|
+
"src/docker-api.js",
|
|
46
|
+
"src/evidence-store.js",
|
|
47
|
+
"src/isolated-runtime-client.js",
|
|
48
|
+
"src/isolated-runtime.js",
|
|
49
|
+
"src/isolated-state.js",
|
|
50
|
+
"src/isolated-worker.js",
|
|
51
|
+
"src/model-broker-policy.js",
|
|
52
|
+
"src/model-broker.js",
|
|
53
|
+
"src/mount-policy.js",
|
|
35
54
|
"src/notice-queue.js",
|
|
36
55
|
"src/notifiers/fetch-transport.js",
|
|
37
56
|
"src/notifiers/telegram.js",
|
|
@@ -41,6 +60,7 @@ const EXPECTED_FILES = [
|
|
|
41
60
|
"src/providers/index.js",
|
|
42
61
|
"src/providers/opencode.js",
|
|
43
62
|
"src/relay.js",
|
|
63
|
+
"src/relay-write.js",
|
|
44
64
|
"src/run-worker.js",
|
|
45
65
|
"src/sentence-buffer.js",
|
|
46
66
|
"src/telegram-ingress/command.js",
|
|
@@ -51,6 +71,7 @@ const EXPECTED_FILES = [
|
|
|
51
71
|
"src/telegram-ingress/update-guard.js",
|
|
52
72
|
"src/telegram-webhook.js",
|
|
53
73
|
"src/types.js",
|
|
74
|
+
"src/worker-control.js",
|
|
54
75
|
"src/workspace-profile.js",
|
|
55
76
|
"threadwire.workspace-profiles.json"
|
|
56
77
|
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable jsdoc/require-jsdoc */
|
|
3
|
+
|
|
4
|
+
export class AbsoluteDeadline {
|
|
5
|
+
constructor(expiresAt, {signal, request, response, now = Date.now, timeoutMessage = "Operation timeout", disconnectMessage = "Caller disconnected"} = {}) {
|
|
6
|
+
if (!Number.isSafeInteger(expiresAt) || expiresAt <= now()) throw new Error(timeoutMessage)
|
|
7
|
+
this.expiresAt = expiresAt
|
|
8
|
+
this.now = now
|
|
9
|
+
this.controller = new AbortController()
|
|
10
|
+
this.signal = this.controller.signal
|
|
11
|
+
this.parentSignal = signal
|
|
12
|
+
this.request = request
|
|
13
|
+
this.response = response
|
|
14
|
+
this.socket = request?.socket
|
|
15
|
+
this.timeoutMessage = timeoutMessage
|
|
16
|
+
this.abortParent = () => this.controller.abort(signal.reason instanceof Error ? signal.reason : new Error("Operation aborted"))
|
|
17
|
+
this.abortRequest = () => this.controller.abort(new Error(disconnectMessage))
|
|
18
|
+
this.abortResponse = () => {
|
|
19
|
+
if (!response?.writableEnded) this.abortRequest()
|
|
20
|
+
}
|
|
21
|
+
this.abortSocket = () => {
|
|
22
|
+
if (!response?.writableEnded) this.abortRequest()
|
|
23
|
+
}
|
|
24
|
+
if (signal?.aborted) this.abortParent()
|
|
25
|
+
else signal?.addEventListener("abort", this.abortParent, {once: true})
|
|
26
|
+
request?.once?.("aborted", this.abortRequest)
|
|
27
|
+
response?.once?.("close", this.abortResponse)
|
|
28
|
+
this.socket?.once?.("close", this.abortSocket)
|
|
29
|
+
this.timer = setTimeout(() => this.controller.abort(new Error(timeoutMessage)), Math.max(1, expiresAt - now()))
|
|
30
|
+
this.timer.unref()
|
|
31
|
+
this.signal.addEventListener("abort", () => request?.destroy?.(this.signal.reason), {once: true})
|
|
32
|
+
if (request?.aborted || (response?.destroyed && !response.writableEnded)) this.abortRequest()
|
|
33
|
+
}
|
|
34
|
+
remaining() {
|
|
35
|
+
this.throwIfAborted()
|
|
36
|
+
const remaining = this.expiresAt - this.now()
|
|
37
|
+
if (remaining <= 0) {
|
|
38
|
+
this.controller.abort(new Error(this.timeoutMessage))
|
|
39
|
+
this.throwIfAborted()
|
|
40
|
+
}
|
|
41
|
+
return Math.max(1, remaining)
|
|
42
|
+
}
|
|
43
|
+
options() {
|
|
44
|
+
return {signal: this.signal, timeoutMs: this.remaining()}
|
|
45
|
+
}
|
|
46
|
+
throwIfAborted() {
|
|
47
|
+
if (this.signal.aborted) throw this.signal.reason instanceof Error ? this.signal.reason : new Error("Operation aborted")
|
|
48
|
+
}
|
|
49
|
+
close() {
|
|
50
|
+
clearTimeout(this.timer)
|
|
51
|
+
this.abortParent && this.parentSignal?.removeEventListener?.("abort", this.abortParent)
|
|
52
|
+
this.request?.off?.("aborted", this.abortRequest)
|
|
53
|
+
this.response?.off?.("close", this.abortResponse)
|
|
54
|
+
this.socket?.off?.("close", this.abortSocket)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function deadlineAfter(timeoutMs, options = {}) {
|
|
59
|
+
const now = options.now ?? Date.now
|
|
60
|
+
return new AbsoluteDeadline(now() + timeoutMs, {...options, now})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function abortable(promise, signal) {
|
|
64
|
+
if (signal?.aborted) throw signal.reason
|
|
65
|
+
if (!signal) return promise
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const abort = () => reject(signal.reason instanceof Error ? signal.reason : new Error("Operation aborted"))
|
|
68
|
+
signal.addEventListener("abort", abort, {once: true})
|
|
69
|
+
Promise.resolve(promise).then(resolve, reject).finally(() => signal.removeEventListener("abort", abort))
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function readResponseCapped(response, capacity, signal) {
|
|
74
|
+
const reader = response.body?.getReader()
|
|
75
|
+
if (!reader) return Buffer.alloc(0)
|
|
76
|
+
const chunks = []
|
|
77
|
+
let size = 0
|
|
78
|
+
try {
|
|
79
|
+
while (true) {
|
|
80
|
+
const {done, value} = await abortable(reader.read(), signal)
|
|
81
|
+
if (done) break
|
|
82
|
+
const chunk = Buffer.from(value)
|
|
83
|
+
size += chunk.length
|
|
84
|
+
if (size > capacity) {
|
|
85
|
+
await reader.cancel(new Error("Response exceeded capacity")).catch(() => {})
|
|
86
|
+
throw new Error("Response exceeded capacity")
|
|
87
|
+
}
|
|
88
|
+
chunks.push(chunk)
|
|
89
|
+
}
|
|
90
|
+
return Buffer.concat(chunks, size)
|
|
91
|
+
} finally {
|
|
92
|
+
reader.releaseLock()
|
|
93
|
+
}
|
|
94
|
+
}
|