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.
Files changed (40) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +17 -4
  3. package/TELEGRAM-INGRESS.md +8 -2
  4. package/bin/isolated-runtime.js +5 -0
  5. package/bin/model-broker.js +5 -0
  6. package/docs/card-10520-plan.md +45 -0
  7. package/docs/container-runtime.md +3 -2
  8. package/docs/delegated-result-protocol.md +161 -0
  9. package/docs/evidence-artifacts.md +106 -0
  10. package/docs/isolated-provider-runtime.md +137 -0
  11. package/package.json +5 -1
  12. package/scripts/provider-shims/front-door.sh.template +18 -0
  13. package/scripts/verify-package.js +22 -1
  14. package/src/absolute-deadline.js +94 -0
  15. package/src/cli.js +362 -42
  16. package/src/context-budget-metrics.js +180 -0
  17. package/src/delegated-result-admission.js +377 -0
  18. package/src/docker-api.js +131 -0
  19. package/src/evidence-store.js +1472 -0
  20. package/src/isolated-runtime-client.js +149 -0
  21. package/src/isolated-runtime.js +982 -0
  22. package/src/isolated-state.js +409 -0
  23. package/src/isolated-worker.js +123 -0
  24. package/src/model-broker-policy.js +139 -0
  25. package/src/model-broker.js +313 -0
  26. package/src/mount-policy.js +28 -0
  27. package/src/normalized-output.js +68 -0
  28. package/src/notice-queue.js +22 -7
  29. package/src/providers/opencode.js +42 -18
  30. package/src/relay-write.js +44 -0
  31. package/src/relay.js +6 -6
  32. package/src/run-worker.js +158 -40
  33. package/src/telegram-ingress/command.js +16 -0
  34. package/src/telegram-ingress/config.js +96 -11
  35. package/src/telegram-ingress/core.js +201 -89
  36. package/src/telegram-ingress/http.js +17 -1
  37. package/src/telegram-webhook.js +22 -0
  38. package/src/types.js +2 -2
  39. package/src/worker-control.js +294 -0
  40. 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/hermes-protocol.js",
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
+ }