threadwire 0.1.11 → 0.1.13
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 +25 -0
- package/README.md +16 -8
- package/TELEGRAM-INGRESS.md +8 -6
- package/docs/container-runtime.md +56 -36
- package/docs/isolated-provider-runtime.md +113 -32
- package/package.json +2 -3
- package/scripts/provider-shims/front-door.sh.template +1 -5
- package/scripts/verify-package.js +2 -3
- package/src/absolute-deadline.js +8 -5
- package/src/activity-log.js +1 -14
- package/src/cli.js +17 -44
- package/src/docker-api.js +100 -14
- package/src/isolated-runtime-client.js +116 -44
- package/src/isolated-runtime.js +673 -64
- package/src/isolated-state.js +62 -11
- package/src/isolated-worker.js +231 -23
- package/src/kimi-model-broker-policy.js +16 -5
- package/src/kimi-model-broker.js +134 -92
- package/src/model-broker-policy.js +11 -8
- package/src/model-broker.js +14 -0
- package/src/providers/index.js +1 -1
- package/src/providers/kimi.js +40 -8
- package/src/run-worker.js +5 -5
- package/src/telegram-ingress/config.js +14 -8
- package/src/telegram-ingress/core.js +32 -28
- package/src/telegram-ingress/http.js +0 -6
- package/src/telegram-webhook.js +13 -2
- package/src/threadwire-binding.js +192 -0
- package/src/workspace-profile.js +0 -189
- package/threadwire.workspace-profiles.json +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.13 - 2026-08-01
|
|
6
|
+
|
|
7
|
+
- Remove the default one-hour full-run deadline from the isolated runtime: when `THREADWIRE_ISOLATED_RUNTIME_CLIENT_TIMEOUT_MS` (client) and `THREADWIRE_WORKER_TIMEOUT_MS` (supervisor) are unset or blank, a healthy running worker continues until natural completion, caller cancellation/disconnect, supervisor shutdown, or concrete failure. Setting either to a positive millisecond value enables the existing single absolute preflight+run deadline with unchanged validation and caps. Compose no longer inserts numeric timeout defaults. Preflight admission, per-operation Docker/network calls, state collection (30 seconds), and emergency cleanup stay independently bounded.
|
|
8
|
+
- Add authenticated broker grant-lease renewal (Kimi and Codex): grants are issued as a bounded lease (default 60 seconds, `THREADWIRE_GRANT_LEASE_MS`, capped at one hour) that the supervisor renews via `POST /admin/grants/{token}/renew` while the run is active. Cleanup still revokes explicitly; if the supervisor dies or loses contact, renewals stop and the broker expires and aborts the grant within one lease. Malformed renewals (unknown/expired token, invalid ttlMs, missing admin auth) fail closed without extending the grant, and no renewal route exists on worker endpoints.
|
|
9
|
+
- Add Kimi-only Threadwire binding v1 named-volume admission: task-labelled source/context/lease validation, immutable context manifest validation, offline validator, dynamic task UID/GID workers with context RO/state RW mounts and an exact binding-declared source mount mode, and v5 authenticated binding state. Kimi path/worktree preflights and supervisor host workspace/socket mounts are no longer accepted.
|
|
10
|
+
- Fail closed on Kimi context manifest task/image identity mismatch: the worker itself revalidates the mounted manifest against the binding task attestation before launch, so a mismatched manifest task identity can never produce a successful client run.
|
|
11
|
+
- Support explicitly read-only Kimi review sources: binding v1 `source.readOnly: true` pins a read-only lease/worker source mount for Docker-enforced reviewers, while writable implementation sources keep the exact read-write mode.
|
|
12
|
+
- Add a credential-free per-run cross-daemon relay: the central Kimi broker splits into an internal admin/control endpoint and a stable worker endpoint, and each run creates a tiny mount-free, credential-free relay on the separate task DinD that forwards only the fixed chat shape to the worker endpoint. The worker URL and published bind have no insecure default and must be explicitly task-DinD-reachable; the configured worker URL is the stable worker origin, which the relay normalizes to the fixed `/v1/chat/completions` path (rejecting any other path fail-closed). Kimi-specific values are load-time optional so a genuine Codex-only relay-write profile renders and starts without them, while the Kimi runtime and client still fail closed at startup or job launch when they are absent. Outer-Compose DNS and outer-host loopback are never assumed, and admin/worker authority stay distinct.
|
|
13
|
+
- Stream Kimi output incrementally over bounded framed NDJSON: the supervisor follows the worker's multiplexed Docker logs, validates each stdout envelope as it arrives, and relays `record` frames to the client before worker completion, followed by exactly one `terminal` exit-code frame. Session envelopes are buffered and published only once the run succeeds, so a resume hint followed by a nonzero exit never exposes an unusable resume session. Byte caps, redaction/admission rules, exact session validation, cancellation/deadline behavior, and cleanup are preserved; stderr is never relayed.
|
|
14
|
+
- Reconcile exact relay/worker/network/state/grant identity on restart: genuine resources are adopted and cleaned up by their exact sealed spec, while image, mount, network, or environment tampering fails closed and retains authenticated cleanup evidence. The sealed Kimi worker/validator/relay image is the resolved image content Id (the worker/validator pin the binding's `contextImageId`; the relay pins its inspected image Id), matching how Docker normalizes a created container's `Config.Image`, so a genuine crash survivor is recognized rather than retained as a cleanup failure. The validator and relay environments accept legitimate non-sensitive keys inherited from the image while every owned key must match its sealed value exactly once and any injected sensitive or controlled key fails closed.
|
|
15
|
+
- Qualify genuine separate-daemon fresh/resume/denial/read-only/concurrency operation with zero residue: adversarial split-daemon E2E proves exact resume, read-only review mounts, binding/argument denial, concurrent task isolation, and complete relay/network/state cleanup on the task DinD.
|
|
16
|
+
|
|
17
|
+
- Remove workspace profiles from normal provider execution. `--cwd` now passes
|
|
18
|
+
any caller-accessible directory directly to Codex, Claude, Kimi, and
|
|
19
|
+
OpenCode; no Git, source identity, task binding, or root-allowlist lookup is
|
|
20
|
+
performed. `--workspace-profile` is rejected—migrate invocations to
|
|
21
|
+
`--cwd <directory>`. Native Kimi is the default path, supports
|
|
22
|
+
`THREADWIRE_KIMI_BIN`, translates exact `--resume-session` values to Kimi's
|
|
23
|
+
native `--session` option, and passes compatible provider arguments while
|
|
24
|
+
Threadwire retains prompt and stream-output ownership. The optional isolated
|
|
25
|
+
Kimi path remains explicit and fail-closed without native fallback.
|
|
26
|
+
- Preserve only the explicit caller-owned native Kimi configuration contract,
|
|
27
|
+
report exactly one completed or failed terminal lifecycle outcome, and reject
|
|
28
|
+
provider option terminators that could neutralize Threadwire-owned arguments.
|
|
29
|
+
|
|
5
30
|
## 0.1.10 - 2026-07-28
|
|
6
31
|
|
|
7
32
|
- Accept official slash-qualified Kimi model aliases through workspace-free
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Threadwire
|
|
2
2
|
|
|
3
|
-
Threadwire is a Node.js CLI that launches a Codex, Claude, or OpenCode worker and streams a concise, human-readable activity feed to one explicitly selected Telegram destination: a direct message, normal chat, or forum topic. The default feed includes the assistant's response prose, lifecycle outcomes, and diagnostics. Provider tool-status messages are disabled by default.
|
|
3
|
+
Threadwire is a Node.js CLI that launches a Codex, Claude, Kimi, or OpenCode worker and streams a concise, human-readable activity feed to one explicitly selected Telegram destination: a direct message, normal chat, or forum topic. The default feed includes the assistant's response prose, lifecycle outcomes, and diagnostics. Provider tool-status messages are disabled by default.
|
|
4
4
|
|
|
5
5
|
For the container-native webhook deployment, including its named-volume worktree,
|
|
6
6
|
non-root Codex provider, Docker secrets, loopback TLS proxy, verification, and
|
|
@@ -8,10 +8,18 @@ rollback path, see [Container runtime](docs/container-runtime.md). The legacy
|
|
|
8
8
|
host launcher below remains supported and is separate from that deployment.
|
|
9
9
|
|
|
10
10
|
Relay-originated Codex writes are explicit and default off. `--relay-write`
|
|
11
|
-
|
|
12
|
-
and credential broker. The worker gets one writable task worktree, no upstream
|
|
11
|
+
selects the optional root-owned isolated runtime and credential broker. The worker gets one writable task worktree, no upstream
|
|
13
12
|
or Git credential, and no route except its run-scoped broker. Missing
|
|
14
13
|
prerequisites fail before prompt or credential reads with no native fallback.
|
|
14
|
+
The isolated runtime imposes no default full-run deadline: a healthy running
|
|
15
|
+
worker is never stopped merely because time elapsed. An operator may set
|
|
16
|
+
`THREADWIRE_WORKER_TIMEOUT_MS` (supervisor) and
|
|
17
|
+
`THREADWIRE_ISOLATED_RUNTIME_CLIENT_TIMEOUT_MS` (client) to a positive
|
|
18
|
+
millisecond value to enable one absolute preflight+run deadline; empty or
|
|
19
|
+
unset means unlimited. Broker grants use a bounded lease that the supervisor
|
|
20
|
+
renews while the run is active and that expires within one lease if the
|
|
21
|
+
supervisor dies; unrelated safety timeouts (preflight slice, per-operation
|
|
22
|
+
Docker/network calls, state collection, emergency cleanup) are unchanged.
|
|
15
23
|
See [Isolated provider runtime](docs/isolated-provider-runtime.md).
|
|
16
24
|
|
|
17
25
|
## Run with npx
|
|
@@ -62,11 +70,11 @@ Use the local launcher. Install or refresh it after updating Threadwire with `np
|
|
|
62
70
|
|
|
63
71
|
The target always has the form `telegram:<chat-id>:<thread-id>` for topics, or `telegram:<chat-id>` for direct messages (DMs) and ordinary chats. The chat ID is always required and must be a nonzero signed integer. The thread ID is optional and valid only as the explicit topic suffix: when present it must be a positive safe integer, and a DM target simply omits it — a missing thread ID is never an error. `--provider` must be exactly one of `codex`, `claude`, `kimi`, or `opencode`. Malformed or missing provider, chat ID, or thread ID arguments are hard failures (exit code 2) reported through Threadwire's normal error path with fixed messages; they fail before the Telegram token is read, before any transport is created, and before any provider is launched, and the messages never echo the bot token or the untrusted argument value. `--process-number` accepts an explicit positive safe integer for automation; when omitted, Threadwire uses its launcher PID. Every Telegram message is labeled consistently, such as `[P42] …`, including chunked continuations.
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
`--cwd` is the complete workspace contract for every provider. It may name any caller-accessible directory, including a non-Git directory. Threadwire passes it unchanged to the native provider and performs no profile, repository, source-identity, task-binding, or root-allowlist lookup. `--workspace-profile` has been removed and is rejected; migrate invocations by replacing it with `--cwd <directory>`.
|
|
66
74
|
|
|
67
|
-
Provider tool-start and tool-finish events do not produce Telegram messages by default. Pass the boolean `--tool-messages` flag to opt in, as shown above. When enabled, each tool is one concise, silent status line rather than a pair of noisy start/finish notices: when a tool starts, Threadwire sends `🛠 [P42] Tool: <safe command or tool description>` (for example `🛠 [P42] Tool: command — git status --short`), and when that same tool finishes, it edits the original message in place so it begins `✅` instead — no second completion message is sent, and Telegram edits raise no notification. The command/description shows a safe CLI-style preview with credential-bearing values (tokens, passwords, keys, authorization/cookie forms, credentials in URLs) redacted and control characters stripped; only normalized Codex command-completion output may also be appended to that same edited status as a Telegram-native expandable HTML blockquote with the visible label `Output →`; it is credential-redacted, HTML-escaped, and Unicode-safe bounded. Stderr, reasoning, and every other raw provider result remain excluded. Use `--max-output-length <positive-integer>` to truncate only that preview Unicode-safely; omit the option for unlimited tool detail. A prompt may instead come from `--prompt-file` or piped stdin, and every source rejects blank/whitespace content. Arguments following `--` pass to the selected provider, except output-stream options owned by Threadwire. Kimi
|
|
75
|
+
Provider tool-start and tool-finish events do not produce Telegram messages by default. Pass the boolean `--tool-messages` flag to opt in, as shown above. When enabled, each tool is one concise, silent status line rather than a pair of noisy start/finish notices: when a tool starts, Threadwire sends `🛠 [P42] Tool: <safe command or tool description>` (for example `🛠 [P42] Tool: command — git status --short`), and when that same tool finishes, it edits the original message in place so it begins `✅` instead — no second completion message is sent, and Telegram edits raise no notification. The command/description shows a safe CLI-style preview with credential-bearing values (tokens, passwords, keys, authorization/cookie forms, credentials in URLs) redacted and control characters stripped; only normalized Codex command-completion output may also be appended to that same edited status as a Telegram-native expandable HTML blockquote with the visible label `Output →`; it is credential-redacted, HTML-escaped, and Unicode-safe bounded. Stderr, reasoning, and every other raw provider result remain excluded. Use `--max-output-length <positive-integer>` to truncate only that preview Unicode-safely; omit the option for unlimited tool detail. A prompt may instead come from `--prompt-file` or piped stdin, and every source rejects blank/whitespace content. Arguments following `--` pass to the selected provider, except output-stream and session options owned by Threadwire. Native Kimi uses the caller's compatible configuration. Threadwire maps its public `--resume-session <exact-id>` option to Kimi's native `--session <exact-id>` option and owns `--prompt` plus `--output-format stream-json`. The caller environment or image must provide the Kimi executable at `/usr/local/bin/kimi` or set `THREADWIRE_KIMI_BIN`; a missing executable fails normally.
|
|
68
76
|
|
|
69
|
-
Threadwire directly spawns the structurally separate terminal adapters `/opt/data/libexec/threadwire/codex`, `/opt/data/libexec/threadwire/claude`, or `/opt/data/libexec/threadwire/opencode-local-fleet` with an argv array and `shell: false`. These libexec adapters hold the real provider-CLI logic and are never on a worker's `PATH`, so relaying can never recurse back into the front-door shims. Each spawn also sets `THREADWIRE_ACTIVE=1` in the provider child environment. The executable can be overridden per provider with `THREADWIRE_CODEX_BIN`, `THREADWIRE_CLAUDE_BIN`, or `THREADWIRE_OPENCODE_BIN` for staged cutover and rollback. It does not impose a worker timeout. `SIGINT` and `SIGTERM` are forwarded to the worker as explicit cancellation.
|
|
77
|
+
Threadwire directly spawns the structurally separate terminal adapters `/opt/data/libexec/threadwire/codex`, `/opt/data/libexec/threadwire/claude`, `/usr/local/bin/kimi`, or `/opt/data/libexec/threadwire/opencode-local-fleet` with an argv array and `shell: false`. These libexec adapters hold the real provider-CLI logic and are never on a worker's `PATH`, so relaying can never recurse back into the front-door shims. Each spawn also sets `THREADWIRE_ACTIVE=1` in the provider child environment. The executable can be overridden per provider with `THREADWIRE_CODEX_BIN`, `THREADWIRE_CLAUDE_BIN`, `THREADWIRE_KIMI_BIN`, or `THREADWIRE_OPENCODE_BIN` for staged cutover and rollback. It does not impose a worker timeout. `SIGINT` and `SIGTERM` are forwarded to the worker as explicit cancellation.
|
|
70
78
|
|
|
71
79
|
## Provider front-door shims
|
|
72
80
|
|
|
@@ -74,11 +82,11 @@ Threadwire directly spawns the structurally separate terminal adapters `/opt/dat
|
|
|
74
82
|
|
|
75
83
|
The relay contract: the caller supplies `THREADWIRE_TARGET` (`telegram:<chat-id>` or `telegram:<chat-id>:<thread-id>`) and, optionally, `THREADWIRE_PROCESS_NUMBER`. The prompt arrives on a piped stdin; all native provider flags pass through after `--`. A positional prompt is never parsed. A missing `THREADWIRE_TARGET` fails closed (exit code 2) and a malformed one is rejected by the launcher's token-free preflight — the shim never invents a default destination and never silently downgrades a requested relay to a direct run. A shim relays only for a worker mode (`codex exec …`, `opencode-local-fleet run …`, or any non-interactive `claude …`); it drops the worker subcommand before relaying and Threadwire re-adds it around the prompt. Version and help queries, non-worker subcommands, interactive TTY sessions, and any invocation already inside a relayed run (`THREADWIRE_ACTIVE=1`, so nested provider workers stay local/native) run the real CLI directly through the libexec adapter.
|
|
76
84
|
|
|
77
|
-
Pass `--resume-session <exact-provider-session-id>` to continue a stored Codex, Claude, or OpenCode session. Threadwire preserves the provider's native resume ordering and streaming options and never uses implicit latest-session behavior.
|
|
85
|
+
Pass `--resume-session <exact-provider-session-id>` to continue a stored Codex, Claude, Kimi, or OpenCode session. Threadwire preserves the provider's native resume ordering and streaming options and never uses implicit latest-session behavior.
|
|
78
86
|
|
|
79
87
|
Pass `--transcript <path>` to capture the exact normalized, caller-facing JSONL protocol written to run stdout. The file is created privately (mode `0600`), must not already exist, and contains the same records in the same byte order as stdout. It never contains provider JSONL, reasoning, tool payloads, prompts, stderr, or credentials.
|
|
80
88
|
|
|
81
|
-
`--activity-log <path>` is different: it retains its append contract and writes only minimal safe lifecycle facts—
|
|
89
|
+
`--activity-log <path>` is different: it retains its append contract and writes only minimal safe lifecycle facts—provider start (`provider`, child `pid`) and available native session ID. It is local-only and never enters Telegram or normalized stdout.
|
|
82
90
|
|
|
83
91
|
Provider stdout is decoded as JSONL with a finite 1 MiB UTF-8 byte limit for each pending unterminated record. Exceeding it rejects the run, removes listeners and signal handlers, and terminates the provider without parsing or rendering the oversized content.
|
|
84
92
|
|
package/TELEGRAM-INGRESS.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Telegram Ingress for Threadwire
|
|
2
2
|
|
|
3
3
|
Standalone Threadwire webhook service that accepts Telegram Bot API updates.
|
|
4
|
-
Codex, Claude, and OpenCode
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Codex, Claude, Kimi, and OpenCode use their native provider adapter by default.
|
|
5
|
+
An operator may explicitly configure the Kimi isolated client; that selected
|
|
6
|
+
path remains fail-closed and never falls back to native execution. Hermes is
|
|
7
|
+
not involved.
|
|
8
8
|
|
|
9
9
|
## Important bot ownership
|
|
10
10
|
|
|
@@ -83,8 +83,10 @@ Optional:
|
|
|
83
83
|
| `THREADWIRE_TELEGRAM_REQUEST_TIMEOUT_MS` | Deadline for each outbound Telegram request, including response parsing (default `30000`, positive safe integer no greater than `2147483647`) |
|
|
84
84
|
| `THREADWIRE_UPDATE_ID_CAPACITY` | Max retained completed `update_id` entries (default `10000`) |
|
|
85
85
|
| `THREADWIRE_UPDATE_ID_TTL_MS` | Retention TTL for completed `update_id` entries in ms (default `86400000`) |
|
|
86
|
-
| `THREADWIRE_KIMI_ISOLATED_RUNTIME_URL` |
|
|
87
|
-
| `THREADWIRE_KIMI_ISOLATED_RUNTIME_CONTROL_TOKEN` |
|
|
86
|
+
| `THREADWIRE_KIMI_ISOLATED_RUNTIME_URL` | Optional dedicated Kimi supervisor URL, used only with an explicit control token. |
|
|
87
|
+
| `THREADWIRE_KIMI_ISOLATED_RUNTIME_CONTROL_TOKEN` | Selects the optional dedicated Kimi isolation client. Without it, `/code kimi` runs natively. |
|
|
88
|
+
| `THREADWIRE_KIMI_TASK_BINDING` | Optional trusted binding used only by the explicitly selected Kimi isolation client; native Kimi never reads it. |
|
|
89
|
+
| `THREADWIRE_ISOLATED_RUNTIME_CLIENT_TIMEOUT_MS` | Optional absolute preflight+run deadline in ms for isolated (Codex relay-write and Kimi) runs. Empty/unset means no full-run deadline: a healthy running worker is never stopped merely because time elapsed. The supervisor-side equivalent is `THREADWIRE_WORKER_TIMEOUT_MS`. Broker grants always use a bounded lease renewed by the supervisor while the run is active, expiring within one lease if the supervisor dies; unrelated safety timeouts are unchanged. |
|
|
88
90
|
|
|
89
91
|
## Run
|
|
90
92
|
|
|
@@ -84,16 +84,13 @@ docker compose exec -T threadwire sh -c 'test "$(id -u):$(id -g)" = 10001:10001
|
|
|
84
84
|
|
|
85
85
|
Telegram commands derive their explicit destination from the authenticated
|
|
86
86
|
update. A credentialed direct smoke test must also name a target explicitly and
|
|
87
|
-
keeps both credentials mounted as secret files.
|
|
88
|
-
|
|
89
|
-
`/workspace/threadwire` worktree inside the current runtime, so `threadwire run`
|
|
90
|
-
selects that reviewed in-container workspace directly rather than deriving a
|
|
91
|
-
path from ambient host state or Telegram input:
|
|
87
|
+
keeps both credentials mounted as secret files. Direct execution supplies the
|
|
88
|
+
caller-accessible working directory explicitly:
|
|
92
89
|
|
|
93
90
|
```sh
|
|
94
91
|
node bin/threadwire.js run \
|
|
95
92
|
--provider codex --target telegram:-1001234567890:42 \
|
|
96
|
-
--workspace
|
|
93
|
+
--cwd /workspace/threadwire \
|
|
97
94
|
--prompt 'Inspect the container worktree.'
|
|
98
95
|
```
|
|
99
96
|
|
|
@@ -114,27 +111,11 @@ permissions that deny the provider, or move it to a separate container or
|
|
|
114
111
|
sandbox. The same-UID, same-container, capability-free design cannot truthfully
|
|
115
112
|
provide that stronger boundary.
|
|
116
113
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
The same reviewed profile file also governs authenticated webhook launches. The
|
|
124
|
-
default profile is selected server-side; Telegram message text never supplies a
|
|
125
|
-
raw path, container identifier, executable path, Git URL, or environment
|
|
126
|
-
assignment. Before every spawn, Threadwire verifies that the configured
|
|
127
|
-
workspace still exists, is still a Git worktree at the configured root, and
|
|
128
|
-
still has a stable revision/worktree identity. If the worktree is removed or
|
|
129
|
-
unhealthy, spawn fails before the provider starts so Telegram can retry after
|
|
130
|
-
remediation.
|
|
131
|
-
|
|
132
|
-
Provision profile changes and revision updates entirely inside the container
|
|
133
|
-
worktree. Edit `threadwire.workspace-profiles.json` in `/workspace/threadwire`,
|
|
134
|
-
commit the reviewed change in that worktree, and let the existing bootstrap and
|
|
135
|
-
rollback flow move the whole repository plus its `.threadwire-source-identity`
|
|
136
|
-
forward or backward together. Do not introduce host-only paths, Docker CLI
|
|
137
|
-
arguments, or Compose-generated container names into the profile file.
|
|
114
|
+
`--workspace-profile` is removed and rejected. Threadwire does not inspect Git,
|
|
115
|
+
source identity, task binding, or workspace allowlists for normal provider
|
|
116
|
+
launches. Telegram ingress uses its own process working directory unless its
|
|
117
|
+
operator supplies another caller-accessible directory through the embedding
|
|
118
|
+
configuration; Telegram message text never supplies a path.
|
|
138
119
|
|
|
139
120
|
## Verification and leakage audit
|
|
140
121
|
|
|
@@ -203,12 +184,20 @@ Never export or archive mounted secret files with workspace/provider backups.
|
|
|
203
184
|
|
|
204
185
|
## Kimi subscription containers
|
|
205
186
|
|
|
206
|
-
Kimi is
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
187
|
+
Kimi source is not a host worktree. The task controller creates one labelled source named volume (including its `.git` directory), one labelled immutable context named volume, and a running labelled lease container. The trusted Kimi supervisor reaches only the owning task-DinD API through `THREADWIRE_KIMI_TASK_DOCKER_HOST`; it has no workspace bind or outer Docker socket. It verifies the closed binding, exact labels/volume identities, lease image/user/workdir/mounts, references, Git revision, and manifest before issuing a broker grant. The worker has exactly the binding-declared source mount at `/workspace` (read-write for implementation sources, read-only for review sources), context RO at `/context`, and lineage state RW at `/state`. The worker entrypoint independently revalidates the mounted context manifest task/image identity and the exact source mount mode before Kimi starts; a mismatched manifest task identity or mount mode fails the run closed.
|
|
188
|
+
|
|
189
|
+
The checked-in standard `runtime` image installs Codex only: it does not install
|
|
190
|
+
Kimi or provide Kimi credentials. Native Kimi therefore requires a caller image
|
|
191
|
+
or environment that provides `/usr/local/bin/kimi` (or sets
|
|
192
|
+
`THREADWIRE_KIMI_BIN`); a missing executable fails normally. Never mount
|
|
193
|
+
`threadwire-kimi-oauth`, its access/refresh tokens, or the auth home into the
|
|
194
|
+
Threadwire runtime, supervisor, worker, workspace, or session-state volume.
|
|
195
|
+
The optional isolated Kimi flow uses five immutable build targets:
|
|
196
|
+
`kimi-auth`, `kimi-model-broker`, `kimi-model-relay`, `kimi-relay-worker`, and
|
|
197
|
+
`isolated-runtime`.
|
|
198
|
+
The first two share UID/GID 10003 and only the OAuth volume; the credential-free
|
|
199
|
+
relay also uses UID/GID 10003 with no mounts and no credential environment; the
|
|
200
|
+
worker uses
|
|
212
201
|
UID/GID 10002 with only the selected worktree and provider-bound state; the
|
|
213
202
|
root supervisor alone receives the Docker socket. The broker/auth service uses
|
|
214
203
|
Node `22.19.0` and exactly `@moonshot-ai/kimi-code@0.29.2` with the checked npm
|
|
@@ -216,8 +205,39 @@ integrity and archive SHA-512 in the Dockerfile.
|
|
|
216
205
|
|
|
217
206
|
Enable the checked-in override with `--profile kimi` only after the OAuth/model
|
|
218
207
|
operator flow in [Isolated provider runtime](isolated-provider-runtime.md).
|
|
219
|
-
The
|
|
220
|
-
|
|
221
|
-
|
|
208
|
+
The per-run relay is created on the separate task DinD, so the deployment has
|
|
209
|
+
no default central worker route: set
|
|
210
|
+
`THREADWIRE_KIMI_MODEL_BROKER_WORKER_URL` to an explicit address the task DinD
|
|
211
|
+
can reach (for example the outer host's task-DinD gateway) and
|
|
212
|
+
`THREADWIRE_KIMI_MODEL_BROKER_WORKER_BIND` to an explicit bind reachable by that
|
|
213
|
+
daemon. The URL is the stable worker origin; the relay normalizes a bare origin
|
|
214
|
+
to the fixed `/v1/chat/completions` path and rejects any other path fail-closed.
|
|
215
|
+
Outer-Compose DNS and outer-host loopback are unreachable from the task
|
|
216
|
+
daemon. Both values are load-time optional so a Codex-only relay-write profile
|
|
217
|
+
renders without them; the Kimi runtime then fails closed at startup when they
|
|
218
|
+
are empty, so the Kimi profile never becomes functional without both explicit
|
|
219
|
+
values. An unset bind renders the published worker port on a non-task-reachable
|
|
220
|
+
loopback sentinel (`127.0.0.1`) so the file loads without publishing on every
|
|
221
|
+
interface; the broker receives the raw setting and refuses to open either
|
|
222
|
+
listener until an explicit nonempty bind is configured, so the sentinel is
|
|
223
|
+
never an operational default.
|
|
224
|
+
Only the worker port is published, on that bind — the admin endpoint stays on
|
|
225
|
+
the internal control network.
|
|
226
|
+
The optional Kimi supervisor is selected only by its dedicated control
|
|
227
|
+
token/client. Without that explicit selection, CLI and Telegram Kimi jobs use
|
|
228
|
+
the native child path and caller/provider configuration. Keep broker egress
|
|
222
229
|
restricted to official Kimi Code subscription/auth endpoints and do not provide
|
|
223
230
|
a Moonshot API key, custom base URL, Docker socket, or ingress secret.
|
|
231
|
+
|
|
232
|
+
Neither the Kimi nor the Codex isolated runtime imposes a default full-run
|
|
233
|
+
deadline: `THREADWIRE_WORKER_TIMEOUT_MS` (supervisor) and
|
|
234
|
+
`THREADWIRE_ISOLATED_RUNTIME_CLIENT_TIMEOUT_MS` (client) pass through empty
|
|
235
|
+
when unset, and an empty/unset value means a healthy running worker is never
|
|
236
|
+
stopped merely because time elapsed. Setting either to a positive millisecond
|
|
237
|
+
value enables one absolute preflight+run deadline. Broker grants are bounded
|
|
238
|
+
leases (default 60 seconds, `THREADWIRE_GRANT_LEASE_MS`, maximum one hour;
|
|
239
|
+
invalid, zero, or excessive values fail startup) that the supervisor renews
|
|
240
|
+
over the authenticated admin endpoint while the run is active; the grant
|
|
241
|
+
expires within one lease if
|
|
242
|
+
the supervisor dies. Unrelated safety timeouts (preflight slice, per-operation
|
|
243
|
+
Docker/network calls, state collection, emergency cleanup) are unchanged.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Isolated provider runtime
|
|
2
2
|
|
|
3
3
|
Relay-originated Codex writes are disabled unless the caller supplies both
|
|
4
|
-
`--relay-write` and
|
|
4
|
+
`--relay-write` and an explicitly selected supervisor. The write path never
|
|
5
5
|
falls back to the native provider launcher.
|
|
6
6
|
When the Telegram service is configured with isolated-runtime authority, Codex
|
|
7
7
|
webhook jobs use the same preflight/run boundary exclusively; an unavailable
|
|
@@ -30,7 +30,11 @@ container-local tmp/run, resource limits, and exactly one writable
|
|
|
30
30
|
worktree mount. It has no host home, common Git directory, sibling worktree,
|
|
31
31
|
host temp, shared provider state, secret, Docker/SSH socket, or external route. Its
|
|
32
32
|
short-lived broker grant is revoked during cleanup.
|
|
33
|
-
The grant
|
|
33
|
+
The grant is issued as a bounded lease (default 60 seconds) that the
|
|
34
|
+
supervisor renews over the authenticated admin endpoint while its run is
|
|
35
|
+
active; cleanup still revokes it explicitly, and if the supervisor dies or
|
|
36
|
+
loses contact the broker's own expiry timer fires within one lease. The grant
|
|
37
|
+
owns every accepted socket. Expiry,
|
|
34
38
|
explicit revoke, credential reload, or broker shutdown aborts an incomplete
|
|
35
39
|
upload or upstream request, destroys accepted sockets, and closes the listener;
|
|
36
40
|
authorization is checked again after the bounded request body is acquired.
|
|
@@ -69,15 +73,19 @@ expire without requiring a matching `/run`.
|
|
|
69
73
|
Consumed capabilities enter a separate global/per-task active-run admission
|
|
70
74
|
before validation can yield. The lineage reservation is acquired synchronously
|
|
71
75
|
at that boundary, preventing periodic GC from deleting resume state during
|
|
72
|
-
revalidation. Docker and broker operations have bounded deadlines
|
|
73
|
-
|
|
76
|
+
revalidation. Docker and broker operations have bounded deadlines. There is no
|
|
77
|
+
default full-run deadline: a healthy running worker is never stopped merely
|
|
78
|
+
because time elapsed. An operator may set `THREADWIRE_WORKER_TIMEOUT_MS` to a
|
|
79
|
+
positive millisecond value to enable one absolute preflight+run deadline;
|
|
80
|
+
timeout cleanup revokes the
|
|
74
81
|
grant and removes the exactly labelled worker and network before capacity is
|
|
75
82
|
released.
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
When the operator enables it, the relay creates one absolute launch deadline
|
|
84
|
+
before preflight. That
|
|
85
|
+
deadline and the cancellation signal span prompt/stdin and file-secret reads,
|
|
78
86
|
evidence and Telegram setup, Docker setup, broker readiness, execution,
|
|
79
87
|
response/log reads, upstream work and exact cleanup. A disconnected caller or
|
|
80
|
-
supervisor shutdown aborts its run. Independent per-step timeouts cannot extend
|
|
88
|
+
supervisor shutdown aborts its run regardless. Independent per-step timeouts cannot extend
|
|
81
89
|
the overall budget, and response bodies are stream-limited to 2 MiB.
|
|
82
90
|
|
|
83
91
|
Codex bypasses its nested sandbox only inside this mandatory outer container.
|
|
@@ -105,10 +113,20 @@ credential. The supervisor alone receives `/var/run/docker.sock`. Restrict
|
|
|
105
113
|
broker egress at the host/firewall to provider endpoints.
|
|
106
114
|
|
|
107
115
|
The supervisor defaults to 16 active runs globally, two per reviewed task, a
|
|
108
|
-
30-second maximum preflight slice, and
|
|
109
|
-
|
|
116
|
+
30-second maximum preflight slice, and no full-run deadline: an unset or empty
|
|
117
|
+
`THREADWIRE_WORKER_TIMEOUT_MS` (supervisor) or
|
|
118
|
+
`THREADWIRE_ISOLATED_RUNTIME_CLIENT_TIMEOUT_MS` (client) means a run may
|
|
119
|
+
continue until natural completion, caller cancellation/disconnect, supervisor
|
|
120
|
+
shutdown, or concrete failure. Setting either to a positive millisecond value
|
|
121
|
+
enables one absolute preflight+run deadline. Operators may
|
|
122
|
+
lower the finite limits with `THREADWIRE_PREFLIGHT_TIMEOUT_MS`,
|
|
110
123
|
`THREADWIRE_ACTIVE_RUN_CAPACITY`, `THREADWIRE_ACTIVE_TASK_CAPACITY`, and
|
|
111
124
|
`THREADWIRE_WORKER_TIMEOUT_MS`; invalid, zero, or excessive values fail startup.
|
|
125
|
+
Broker grants always use a bounded lease (default 60 seconds, tunable with
|
|
126
|
+
`THREADWIRE_GRANT_LEASE_MS`, maximum one hour) that the supervisor renews while
|
|
127
|
+
the run is active; the grant expires within one lease if the supervisor dies.
|
|
128
|
+
An invalid, zero, or excessive `THREADWIRE_GRANT_LEASE_MS` value fails startup
|
|
129
|
+
rather than being silently coerced.
|
|
112
130
|
|
|
113
131
|
The authenticated registry persists through file fsync, atomic rename, and
|
|
114
132
|
parent-directory fsync. Startup removes only strictly named abandoned temp
|
|
@@ -137,7 +155,19 @@ by that script. E2E uses host-volume canaries and fake-upstream authorization
|
|
|
137
155
|
logs; worker self-report is not the sole assertion.
|
|
138
156
|
|
|
139
157
|
|
|
140
|
-
##
|
|
158
|
+
## Optional isolated Kimi Code subscription boundary
|
|
159
|
+
|
|
160
|
+
Normal `threadwire run --provider kimi` is native execution: it directly runs
|
|
161
|
+
the caller-provided Kimi executable at `/usr/local/bin/kimi`, or the path in
|
|
162
|
+
`THREADWIRE_KIMI_BIN`. It does not select this isolated supervisor, broker, or
|
|
163
|
+
model allowlist. The checked-in standard `runtime` image installs Codex only,
|
|
164
|
+
so native Kimi needs a caller-supplied executable and fails normally when it is
|
|
165
|
+
missing. The remaining section documents the separately configured, optional
|
|
166
|
+
isolated Kimi supervisor.
|
|
167
|
+
|
|
168
|
+
Kimi binding v1 is separate from the Codex worktree contract. A Kimi preflight contains `binding`, never `repositoryRoot` or `cwd`. The supervisor queries the task-DinD daemon for exactly one labelled source volume, context volume, and running lease, rejects ambiguity/RW context references, validates the lease and worker image, then uses an offline two-volume validator to check the real Git revision and complete immutable context manifest. Only after that does it create the per-run data plane on the task daemon: a private internal worker network, a task-owned non-internal egress network, one credential-free relay attached to both, and a three-volume Kimi worker attached only to the internal network. OAuth remains broker-only; workers receive a synthetic per-run grant and no raw binding, volume, lease, control, or OAuth value. Kimi v6 state seals include the binding digest, source/context identities, source mount mode, dynamic UID/GID/workdir, validator, worker, relay, relay image, both networks, the central endpoint identity, state, and image. Old path-based and pre-relay Kimi state is intentionally not adopted.
|
|
169
|
+
|
|
170
|
+
The binding's `source.readOnly` selects the exact admitted mount mode: `false` is a writable implementation source, `true` an explicitly read-only review source for Docker-enforced reviewers. Admission and revalidation pin the lease and worker source mounts to that exact mode, one-writer isolation is unchanged, and the worker entrypoint independently asserts the mounted mode from `/proc/self/mountinfo` before launch. Both the validator and the worker compare the mounted context manifest's task and image identity against the binding attestation; a mismatch fails the run closed and can never produce a successful client run.
|
|
141
171
|
|
|
142
172
|
Kimi uses the same proven supervisor mechanics but a distinct protocol and
|
|
143
173
|
credential service. `THREADWIRE_ISOLATED_PROVIDER=kimi` makes the supervisor
|
|
@@ -147,14 +177,54 @@ provider-bound state in the separate Kimi namespace. Codex requests, sessions,
|
|
|
147
177
|
workers, grants, and volumes cannot be adopted by this service, and the Codex
|
|
148
178
|
broker remains unchanged.
|
|
149
179
|
|
|
180
|
+
The central Kimi broker runs on an outer daemon, not the task daemon, so the
|
|
181
|
+
supervisor never resolves a broker container by name. It splits the broker into
|
|
182
|
+
an admin/control endpoint (grant lifecycle only) and a stable worker endpoint
|
|
183
|
+
(grant-auth chat proxy). For each run it creates a tiny credential-free relay
|
|
184
|
+
on the task daemon from the immutable `THREADWIRE_KIMI_MODEL_RELAY_IMAGE`
|
|
185
|
+
digest. The relay knows only the configured central worker URL
|
|
186
|
+
(`THREADWIRE_KIMI_MODEL_BROKER_WORKER_URL`) and its own listen port, receives
|
|
187
|
+
no grant, OAuth, admin token, binding, mount, or Docker socket, and is attached
|
|
188
|
+
to both the internal worker network and a dedicated egress network. The worker
|
|
189
|
+
reaches only the relay's internal address; the relay forwards the fixed
|
|
190
|
+
`POST /v1/chat/completions` shape to the central worker endpoint, preserving
|
|
191
|
+
the grant bearer. The configured worker URL is the stable worker origin: a bare
|
|
192
|
+
origin (for example `http://<gateway>:8792`) or the already-exact accepted path
|
|
193
|
+
is normalized to the fixed `/v1/chat/completions` path, and any other path is
|
|
194
|
+
rejected fail-closed at relay startup rather than forwarded verbatim. There is
|
|
195
|
+
no DNS or host-gateway fallback, no container-name resolution, and TLS
|
|
196
|
+
verification is never disabled.
|
|
197
|
+
|
|
198
|
+
Because the relay runs on the separate task daemon, the stable worker URL is
|
|
199
|
+
not defaulted: `THREADWIRE_KIMI_MODEL_BROKER_WORKER_URL` must be set to an
|
|
200
|
+
explicit address the task daemon can reach (for example the outer host's
|
|
201
|
+
task-DinD gateway), and the broker's published worker bind
|
|
202
|
+
(`THREADWIRE_KIMI_MODEL_BROKER_WORKER_BIND`) must likewise be chosen explicitly
|
|
203
|
+
to be reachable by that daemon. Outer-Compose DNS and outer-host loopback are
|
|
204
|
+
unreachable from the task daemon and are never assumed. These values are
|
|
205
|
+
load-time optional so a Codex-only relay-write profile renders and starts
|
|
206
|
+
without them; the Kimi runtime and client then fail closed at startup or job
|
|
207
|
+
launch when they are empty, so a Kimi deployment that does not supply an
|
|
208
|
+
explicit task-reachable URL and bind never becomes functional. When the bind
|
|
209
|
+
is unset, Compose renders the published worker port on a non-task-reachable
|
|
210
|
+
loopback sentinel (`127.0.0.1`) purely so the file loads — never on every
|
|
211
|
+
interface — and passes the raw setting through to the broker, which requires a
|
|
212
|
+
nonempty explicit bind before opening either listener. The sentinel is not an
|
|
213
|
+
operational default: without the explicit bind the broker exits instead of
|
|
214
|
+
serving. The
|
|
215
|
+
admin/control endpoint is never published — only the worker
|
|
216
|
+
port is, on the explicit bind — so admin and worker authority stay distinct.
|
|
217
|
+
|
|
150
218
|
The Kimi broker is not a generic credential proxy. It accepts only
|
|
151
219
|
`POST /v1/chat/completions`, validates a closed request schema and one approved
|
|
152
220
|
wire model, substitutes OAuth immediately before the fixed official
|
|
153
221
|
`https://api.kimi.com/coding/v1/chat/completions` request, and streams the
|
|
154
222
|
bounded response. A grant is pending until the inspected worker is ready and is
|
|
155
223
|
bound to provider, task hash, exact resume session (or fresh lineage), run,
|
|
156
|
-
Docker network, approved alias, and wire model.
|
|
157
|
-
|
|
224
|
+
Docker network, approved alias, and wire model. The grant is a bounded lease
|
|
225
|
+
renewed by the supervisor while the run is active. Revocation, lease expiry,
|
|
226
|
+
shutdown,
|
|
227
|
+
or cleanup rejects the grant and closes its active sockets.
|
|
158
228
|
|
|
159
229
|
The immutable Kimi worker image pins `@moonshot-ai/kimi-code@0.29.2` and verifies
|
|
160
230
|
the npm archive SHA-512 before installation. It receives no OAuth file, Docker
|
|
@@ -164,7 +234,8 @@ Its fixed entrypoint owns `--model`, optional exact `--session`, `--prompt`,
|
|
|
164
234
|
`--output-format stream-json`, and an empty skills directory. Static deny rules,
|
|
165
235
|
workspace/config ancestry checks, a sanitized private home, read-only root,
|
|
166
236
|
and the existing mount/inode checks remain mandatory. There is no native unsafe
|
|
167
|
-
fallback
|
|
237
|
+
fallback within the explicitly selected isolated path. Normal Kimi execution
|
|
238
|
+
uses the native adapter and may use `THREADWIRE_KIMI_BIN`.
|
|
168
239
|
|
|
169
240
|
Only exact pinned stream records are normalized: assistant string content,
|
|
170
241
|
validated tool name/key start and finish, and one exact
|
|
@@ -174,10 +245,26 @@ and OAuth material are discarded. The worker is the only reader of native Kimi
|
|
|
174
245
|
stdout and Docker receives only sanitized envelopes and fixed lifecycle events;
|
|
175
246
|
the supervisor returns `rawChunks: []` for every Kimi run.
|
|
176
247
|
|
|
248
|
+
The supervisor does not wait for worker completion before relaying output. It
|
|
249
|
+
follows the worker's multiplexed Docker logs, demultiplexes and validates each
|
|
250
|
+
stdout envelope as it arrives, and streams it to the relay caller over a single
|
|
251
|
+
framed NDJSON run response: `record` frames are written incrementally, then
|
|
252
|
+
exactly one `terminal` frame carries the final exit code. The client parses the
|
|
253
|
+
same NDJSON stream and forwards each validated event to `WorkerControl` as it
|
|
254
|
+
arrives, so long runs relay assistant/tool/lifecycle output to Telegram
|
|
255
|
+
incrementally. Session envelopes are the one exception: they are buffered and
|
|
256
|
+
only published once the run's success is known, so a resume hint followed by a
|
|
257
|
+
nonzero exit never exposes an unusable resume session. Stderr frames are
|
|
258
|
+
dropped, the same byte caps and conflicting-session rejection apply as the
|
|
259
|
+
buffered path, and a mid-stream
|
|
260
|
+
failure or cancellation still revokes the grant and removes the exact worker,
|
|
261
|
+
relay, and both networks before capacity is released.
|
|
262
|
+
|
|
177
263
|
### Operator OAuth and model approval
|
|
178
264
|
|
|
179
|
-
Build and publish the `kimi-auth`, `kimi-model-broker`, `kimi-relay
|
|
180
|
-
existing `isolated-runtime` targets as immutable
|
|
265
|
+
Build and publish the `kimi-auth`, `kimi-model-broker`, `kimi-model-relay`,
|
|
266
|
+
`kimi-relay-worker`, and existing `isolated-runtime` targets as immutable
|
|
267
|
+
digests. Set distinct random
|
|
181
268
|
Kimi runtime and broker admin tokens of at least 32 characters, the Kimi image digests, and an allowlist
|
|
182
269
|
such as:
|
|
183
270
|
|
|
@@ -185,7 +272,8 @@ such as:
|
|
|
185
272
|
export THREADWIRE_ALLOWED_KIMI_MODELS='{"default":{"model":"kimi-for-coding","protocol":"kimi"},"kimi-code/kimi-for-coding":{"model":"kimi-for-coding","protocol":"kimi"}}'
|
|
186
273
|
```
|
|
187
274
|
|
|
188
|
-
`default` is the server-owned selection for
|
|
275
|
+
`default` is the server-owned selection for isolated supervisor jobs without a
|
|
276
|
+
model argument.
|
|
189
277
|
`kimi-code/kimi-for-coding` is the exact official provider-qualified alias for
|
|
190
278
|
explicit callers. Each key is an opaque allowlisted value, not a path, and
|
|
191
279
|
Threadwire never normalizes or strips its prefix. The `model` field is the
|
|
@@ -194,8 +282,8 @@ alias.
|
|
|
194
282
|
|
|
195
283
|
OAuth writes only the named `threadwire-kimi-oauth` volume at the official
|
|
196
284
|
`credentials/kimi-code.json` location. Stop the broker during interactive auth
|
|
197
|
-
so the CLI and broker cannot race a refresh, then run the opt-in,
|
|
198
|
-
auth service:
|
|
285
|
+
so the auth CLI and broker cannot race a refresh, then run the opt-in,
|
|
286
|
+
workspace-free auth service:
|
|
199
287
|
|
|
200
288
|
```sh
|
|
201
289
|
docker compose -f compose.yaml -f compose.relay-write.yaml stop kimi-isolated-runtime kimi-model-broker
|
|
@@ -210,20 +298,12 @@ The auth service has no workspace, ingress network, Docker socket, Threadwire
|
|
|
210
298
|
control token, or Telegram credential and uses Docker logging driver `none`.
|
|
211
299
|
`verify <provider-qualified-alias>` discards native output and prints only a
|
|
212
300
|
fixed success/failure; use it to confirm each exact official alias before
|
|
213
|
-
adding that alias and its independently verified wire model to the
|
|
214
|
-
allowlist.
|
|
215
|
-
|
|
216
|
-
```sh
|
|
217
|
-
node bin/threadwire.js run \
|
|
218
|
-
--provider kimi \
|
|
219
|
-
--target telegram:-1001234567890:42 \
|
|
220
|
-
--workspace-profile container-runtime \
|
|
221
|
-
--prompt 'Inspect the project.' \
|
|
222
|
-
-- --model 'kimi-code/kimi-for-coding'
|
|
223
|
-
```
|
|
301
|
+
adding that alias and its independently verified wire model to the isolated
|
|
302
|
+
supervisor allowlist. This operator configuration does not configure or select
|
|
303
|
+
the native CLI Kimi model.
|
|
224
304
|
|
|
225
|
-
API keys, Moonshot platform mode, custom base
|
|
226
|
-
and arbitrary aliases are unsupported.
|
|
305
|
+
For the isolated supervisor, API keys, Moonshot platform mode, custom base
|
|
306
|
+
URLs, discovery by ordinary jobs, and arbitrary aliases are unsupported.
|
|
227
307
|
|
|
228
308
|
For logout or account rotation, stop the Kimi runtime and broker, run
|
|
229
309
|
`--profile kimi-auth run --rm kimi-auth logout` or `login`, verify approved
|
|
@@ -244,7 +324,8 @@ payload, or synthetic grant.
|
|
|
244
324
|
|
|
245
325
|
Rollback by stopping `kimi-isolated-runtime` and `kimi-model-broker`, removing
|
|
246
326
|
the `kimi` Compose profile/control variables, and reverting the Threadwire image.
|
|
247
|
-
Kimi
|
|
327
|
+
Isolated Kimi requests then fail closed; native Kimi remains independent of
|
|
328
|
+
that optional deployment. Codex behavior is unaffected. Preserve the OAuth
|
|
248
329
|
and Kimi state volumes for a recoverable rollback. Remove them only as an
|
|
249
330
|
explicit credential/session teardown after OAuth revocation; never archive them
|
|
250
331
|
with workspace backups.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "threadwire",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Stream Codex, Claude, Kimi Code, and OpenCode worker progress to an explicit Telegram destination",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
"docs/",
|
|
21
21
|
"scripts/",
|
|
22
22
|
"src/",
|
|
23
|
-
"TELEGRAM-INGRESS.md"
|
|
24
|
-
"threadwire.workspace-profiles.json"
|
|
23
|
+
"TELEGRAM-INGRESS.md"
|
|
25
24
|
],
|
|
26
25
|
"bin": {
|
|
27
26
|
"threadwire-isolated-runtime": "bin/isolated-runtime.js",
|
|
@@ -58,15 +58,11 @@ threadwire_relay_arguments=(run --provider "$THREADWIRE_PROVIDER" --target "$THR
|
|
|
58
58
|
case "${THREADWIRE_RELAY_WRITE:-0}" in
|
|
59
59
|
0) ;;
|
|
60
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
61
|
if [[ "$THREADWIRE_PROVIDER" != codex ]]; then
|
|
66
62
|
printf '%s\n' 'threadwire: relay write capability is only supported for codex' >&2
|
|
67
63
|
exit 2
|
|
68
64
|
fi
|
|
69
|
-
threadwire_relay_arguments+=(--
|
|
65
|
+
threadwire_relay_arguments+=(--relay-write)
|
|
70
66
|
;;
|
|
71
67
|
*)
|
|
72
68
|
printf '%s\n' 'threadwire: relay write capability must be enabled with THREADWIRE_RELAY_WRITE=1' >&2
|
|
@@ -77,10 +77,9 @@ const EXPECTED_FILES = [
|
|
|
77
77
|
"src/telegram-ingress/http.js",
|
|
78
78
|
"src/telegram-ingress/update-guard.js",
|
|
79
79
|
"src/telegram-webhook.js",
|
|
80
|
+
"src/threadwire-binding.js",
|
|
80
81
|
"src/types.js",
|
|
81
|
-
"src/worker-control.js"
|
|
82
|
-
"src/workspace-profile.js",
|
|
83
|
-
"threadwire.workspace-profiles.json"
|
|
82
|
+
"src/worker-control.js"
|
|
84
83
|
]
|
|
85
84
|
|
|
86
85
|
/** @returns {Promise<void>} */
|
package/src/absolute-deadline.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
export class AbsoluteDeadline {
|
|
5
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)
|
|
6
|
+
if (expiresAt !== undefined && (!Number.isSafeInteger(expiresAt) || expiresAt <= now())) throw new Error(timeoutMessage)
|
|
7
7
|
this.expiresAt = expiresAt
|
|
8
8
|
this.now = now
|
|
9
9
|
this.controller = new AbortController()
|
|
@@ -26,13 +26,14 @@ export class AbsoluteDeadline {
|
|
|
26
26
|
request?.once?.("aborted", this.abortRequest)
|
|
27
27
|
response?.once?.("close", this.abortResponse)
|
|
28
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()
|
|
29
|
+
this.timer = expiresAt === undefined ? undefined : setTimeout(() => this.controller.abort(new Error(timeoutMessage)), Math.max(1, expiresAt - now()))
|
|
31
30
|
this.signal.addEventListener("abort", () => request?.destroy?.(this.signal.reason), {once: true})
|
|
32
31
|
if (request?.aborted || (response?.destroyed && !response.writableEnded)) this.abortRequest()
|
|
33
32
|
}
|
|
34
33
|
remaining() {
|
|
35
34
|
this.throwIfAborted()
|
|
35
|
+
if (this.expiresAt === undefined) return undefined
|
|
36
|
+
if (this.expiresAt === undefined) return undefined
|
|
36
37
|
const remaining = this.expiresAt - this.now()
|
|
37
38
|
if (remaining <= 0) {
|
|
38
39
|
this.controller.abort(new Error(this.timeoutMessage))
|
|
@@ -41,13 +42,14 @@ export class AbsoluteDeadline {
|
|
|
41
42
|
return Math.max(1, remaining)
|
|
42
43
|
}
|
|
43
44
|
options() {
|
|
44
|
-
|
|
45
|
+
const timeoutMs = this.remaining()
|
|
46
|
+
return timeoutMs === undefined ? {signal: this.signal} : {signal: this.signal, timeoutMs}
|
|
45
47
|
}
|
|
46
48
|
throwIfAborted() {
|
|
47
49
|
if (this.signal.aborted) throw this.signal.reason instanceof Error ? this.signal.reason : new Error("Operation aborted")
|
|
48
50
|
}
|
|
49
51
|
close() {
|
|
50
|
-
clearTimeout(this.timer)
|
|
52
|
+
if (this.timer !== undefined) clearTimeout(this.timer)
|
|
51
53
|
this.abortParent && this.parentSignal?.removeEventListener?.("abort", this.abortParent)
|
|
52
54
|
this.request?.off?.("aborted", this.abortRequest)
|
|
53
55
|
this.response?.off?.("close", this.abortResponse)
|
|
@@ -89,6 +91,7 @@ export async function readResponseCapped(response, capacity, signal) {
|
|
|
89
91
|
}
|
|
90
92
|
return Buffer.concat(chunks, size)
|
|
91
93
|
} finally {
|
|
94
|
+
if (signal?.aborted) await reader.cancel(signal.reason).catch(() => {})
|
|
92
95
|
reader.releaseLock()
|
|
93
96
|
}
|
|
94
97
|
}
|