threadwire 0.1.11 → 0.1.12
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 +10 -0
- package/README.md +10 -1
- package/TELEGRAM-INGRESS.md +2 -0
- package/docs/container-runtime.md +39 -3
- package/docs/isolated-provider-runtime.md +90 -12
- package/package.json +1 -1
- package/scripts/verify-package.js +1 -0
- package/src/absolute-deadline.js +8 -5
- package/src/cli.js +14 -3
- 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/kimi.js +15 -3
- package/src/telegram-ingress/core.js +7 -5
- package/src/telegram-webhook.js +10 -0
- package/src/threadwire-binding.js +192 -0
- package/src/workspace-profile.js +31 -8
- package/threadwire.workspace-profiles.json +3 -2
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import {createHash} from "node:crypto"
|
|
4
|
+
import {isAbsolute, normalize, relative} from "node:path"
|
|
5
|
+
|
|
6
|
+
const UUID = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/u
|
|
7
|
+
const VOLUME = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u
|
|
8
|
+
const REVISION = /^[0-9a-f]{40}$/u
|
|
9
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/u
|
|
10
|
+
const CONTAINER_ID = /^[0-9a-f]{64}$/u
|
|
11
|
+
const POSITIVE_ID = /^[1-9]\d{0,9}$/u
|
|
12
|
+
const MAX_ID = 2_147_483_647
|
|
13
|
+
const MAX_MANIFEST_BYTES = 1_048_576
|
|
14
|
+
const MAX_CONTEXT_ENTRIES = 4_096
|
|
15
|
+
const MAX_CONTEXT_BYTES = 67_108_864
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse the closed Threadwire binding transport schema. This function is the
|
|
19
|
+
* single authority for a binding: callers must not re-parse loose fields.
|
|
20
|
+
* @param {unknown} value
|
|
21
|
+
*/
|
|
22
|
+
export function parseThreadwireBinding(value) {
|
|
23
|
+
if (!record(value) || !exactKeys(value, ["version", "taskId", "source", "context", "runtime", "leaseContainerId"])
|
|
24
|
+
|| value.version !== 1 || !canonicalUuid(value.taskId) || !CONTAINER_ID.test(value.leaseContainerId)) failBinding()
|
|
25
|
+
const source = parseSource(value.source)
|
|
26
|
+
const context = parseContext(value.context)
|
|
27
|
+
const runtime = parseRuntime(value.runtime)
|
|
28
|
+
if (source.volume === context.volume || runtime.workdir !== "/workspace" && !within("/workspace", runtime.workdir)) failBinding()
|
|
29
|
+
return {
|
|
30
|
+
version: 1,
|
|
31
|
+
taskId: value.taskId,
|
|
32
|
+
source,
|
|
33
|
+
context,
|
|
34
|
+
runtime,
|
|
35
|
+
leaseContainerId: value.leaseContainerId
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse the trusted launcher transport without retaining or reporting its raw
|
|
41
|
+
* text. A binding is control metadata, but still never belongs in a provider
|
|
42
|
+
* or worker environment.
|
|
43
|
+
* @param {unknown} value
|
|
44
|
+
*/
|
|
45
|
+
export function parseTrustedThreadwireBinding(value) {
|
|
46
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 32_768) failBinding()
|
|
47
|
+
try { return parseThreadwireBinding(JSON.parse(value)) } catch { failBinding() }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @param {ReturnType<typeof parseThreadwireBinding>} binding */
|
|
51
|
+
export function canonicalThreadwireBinding(binding) {
|
|
52
|
+
const parsed = parseThreadwireBinding(binding)
|
|
53
|
+
return JSON.stringify({
|
|
54
|
+
version: parsed.version,
|
|
55
|
+
taskId: parsed.taskId,
|
|
56
|
+
source: {
|
|
57
|
+
volume: parsed.source.volume, target: parsed.source.target,
|
|
58
|
+
readOnly: parsed.source.readOnly, revision: parsed.source.revision
|
|
59
|
+
},
|
|
60
|
+
context: {
|
|
61
|
+
volume: parsed.context.volume, target: parsed.context.target,
|
|
62
|
+
readOnly: parsed.context.readOnly,
|
|
63
|
+
digests: {manifest: parsed.context.digests.manifest, content: parsed.context.digests.content},
|
|
64
|
+
imageId: parsed.context.imageId
|
|
65
|
+
},
|
|
66
|
+
runtime: {uid: parsed.runtime.uid, gid: parsed.runtime.gid, workdir: parsed.runtime.workdir},
|
|
67
|
+
leaseContainerId: parsed.leaseContainerId
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @param {ReturnType<typeof parseThreadwireBinding>} binding */
|
|
72
|
+
export function threadwireBindingDigest(binding) {
|
|
73
|
+
return `sha256:${createHash("sha256").update(canonicalThreadwireBinding(binding)).digest("hex")}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A stable identity for state adoption. It intentionally excludes only the
|
|
78
|
+
* mutable source commit and recreatable lease identifier.
|
|
79
|
+
* @param {ReturnType<typeof parseThreadwireBinding>} binding
|
|
80
|
+
*/
|
|
81
|
+
export function resumeTaskIdentity(binding) {
|
|
82
|
+
const parsed = parseThreadwireBinding(binding)
|
|
83
|
+
return JSON.stringify({
|
|
84
|
+
version: parsed.version,
|
|
85
|
+
taskId: parsed.taskId,
|
|
86
|
+
source: {volume: parsed.source.volume, target: parsed.source.target, readOnly: parsed.source.readOnly},
|
|
87
|
+
context: {
|
|
88
|
+
volume: parsed.context.volume, target: parsed.context.target,
|
|
89
|
+
digests: {manifest: parsed.context.digests.manifest, content: parsed.context.digests.content},
|
|
90
|
+
imageId: parsed.context.imageId
|
|
91
|
+
},
|
|
92
|
+
runtime: parsed.runtime
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @param {Buffer | string} bytes */
|
|
97
|
+
export function parseContextManifest(bytes) {
|
|
98
|
+
const buffer = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes)
|
|
99
|
+
if (buffer.length === 0 || buffer.length > MAX_MANIFEST_BYTES) failManifest()
|
|
100
|
+
let value
|
|
101
|
+
try { value = JSON.parse(buffer.toString("utf8")) } catch { failManifest() }
|
|
102
|
+
if (!record(value) || !exactKeys(value, ["version", "taskId", "imageId", "content", "entries"])
|
|
103
|
+
|| value.version !== 1 || !canonicalUuid(value.taskId) || !DIGEST.test(value.imageId) || !DIGEST.test(value.content)
|
|
104
|
+
|| !Array.isArray(value.entries) || value.entries.length > MAX_CONTEXT_ENTRIES) failManifest()
|
|
105
|
+
let previous
|
|
106
|
+
let total = 0
|
|
107
|
+
const entries = value.entries.map((entry) => {
|
|
108
|
+
if (!record(entry) || !exactKeys(entry, ["path", "type", "mode", "bytes", "sha256"])
|
|
109
|
+
|| !contextPath(entry.path) || entry.type !== "file" || !Number.isSafeInteger(entry.mode) || entry.mode < 0 || entry.mode > 0o777 || (entry.mode & 0o222) !== 0
|
|
110
|
+
|| !Number.isSafeInteger(entry.bytes) || entry.bytes < 0 || entry.bytes > MAX_CONTEXT_BYTES || !DIGEST.test(entry.sha256)) failManifest()
|
|
111
|
+
if (previous !== undefined && Buffer.compare(Buffer.from(previous), Buffer.from(entry.path)) >= 0) failManifest()
|
|
112
|
+
previous = entry.path
|
|
113
|
+
total += entry.bytes
|
|
114
|
+
if (total > MAX_CONTEXT_BYTES) failManifest()
|
|
115
|
+
return {path: entry.path, type: "file", mode: entry.mode, bytes: entry.bytes, sha256: entry.sha256}
|
|
116
|
+
})
|
|
117
|
+
return {version: 1, taskId: value.taskId, imageId: value.imageId, content: value.content, entries}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {ReturnType<typeof parseContextManifest>} manifest */
|
|
121
|
+
export function canonicalContextInventory(manifest) {
|
|
122
|
+
const parsed = parseContextManifest(Buffer.from(JSON.stringify(manifest)))
|
|
123
|
+
return JSON.stringify(parsed.entries.map((entry) => ({
|
|
124
|
+
path: entry.path, type: entry.type, mode: entry.mode, bytes: entry.bytes, sha256: entry.sha256
|
|
125
|
+
})))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** @param {ReturnType<typeof parseContextManifest>} manifest */
|
|
129
|
+
export function contextInventoryDigest(manifest) {
|
|
130
|
+
return `sha256:${createHash("sha256").update(canonicalContextInventory(manifest)).digest("hex")}`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Bind a parsed manifest to the exact task and immutable image of the active
|
|
135
|
+
* binding before any inventory is accepted.
|
|
136
|
+
* @param {ReturnType<typeof parseContextManifest>} manifest
|
|
137
|
+
* @param {unknown} taskId
|
|
138
|
+
* @param {unknown} imageId
|
|
139
|
+
*/
|
|
140
|
+
export function assertContextManifestIdentity(manifest, taskId, imageId) {
|
|
141
|
+
if (!canonicalUuid(taskId) || typeof imageId !== "string" || !DIGEST.test(imageId)
|
|
142
|
+
|| manifest.taskId !== taskId || manifest.imageId !== imageId) failManifest()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @param {unknown} value */
|
|
146
|
+
function parseSource(value) {
|
|
147
|
+
// readOnly selects the exact admitted mount mode: false is a writable
|
|
148
|
+
// implementation source, true an explicitly read-only review source.
|
|
149
|
+
if (!record(value) || !exactKeys(value, ["volume", "target", "readOnly", "revision"])
|
|
150
|
+
|| !VOLUME.test(value.volume) || value.target !== "/workspace" || typeof value.readOnly !== "boolean" || !REVISION.test(value.revision)) failBinding()
|
|
151
|
+
return {volume: value.volume, target: value.target, readOnly: value.readOnly, revision: value.revision}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** @param {unknown} value */
|
|
155
|
+
function parseContext(value) {
|
|
156
|
+
if (!record(value) || !exactKeys(value, ["volume", "target", "readOnly", "digests", "imageId"])
|
|
157
|
+
|| !VOLUME.test(value.volume) || value.target !== "/context" || value.readOnly !== true || !DIGEST.test(value.imageId)
|
|
158
|
+
|| !record(value.digests) || !exactKeys(value.digests, ["manifest", "content"])
|
|
159
|
+
|| !DIGEST.test(value.digests.manifest) || !DIGEST.test(value.digests.content)) failBinding()
|
|
160
|
+
return {
|
|
161
|
+
volume: value.volume, target: value.target, readOnly: value.readOnly,
|
|
162
|
+
digests: {manifest: value.digests.manifest, content: value.digests.content}, imageId: value.imageId
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @param {unknown} value */
|
|
167
|
+
function parseRuntime(value) {
|
|
168
|
+
if (!record(value) || !exactKeys(value, ["uid", "gid", "workdir"])
|
|
169
|
+
|| !safeContainerId(value.uid) || !safeContainerId(value.gid) || typeof value.workdir !== "string"
|
|
170
|
+
|| !isAbsolute(value.workdir) || normalize(value.workdir) !== value.workdir) failBinding()
|
|
171
|
+
return {uid: value.uid, gid: value.gid, workdir: value.workdir}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @param {unknown} value */
|
|
175
|
+
function safeContainerId(value) { return typeof value === "number" && Number.isSafeInteger(value) && POSITIVE_ID.test(String(value)) && value <= MAX_ID }
|
|
176
|
+
/** @param {unknown} value */
|
|
177
|
+
function canonicalUuid(value) { return typeof value === "string" && UUID.test(value) }
|
|
178
|
+
/** @param {string} root @param {string} path */
|
|
179
|
+
function within(root, path) { const path_ = relative(root, path); return path_ === "" || (!path_.startsWith("..") && !isAbsolute(path_)) }
|
|
180
|
+
/** @param {unknown} value */
|
|
181
|
+
function contextPath(value) {
|
|
182
|
+
return typeof value === "string" && value.length > 0 && value.length <= 1024 && !value.startsWith("/")
|
|
183
|
+
&& !value.includes("\\") && value.split("/").every((part) => part !== "" && part !== "." && part !== "..")
|
|
184
|
+
}
|
|
185
|
+
/** @param {unknown} value @param {string[]} keys */
|
|
186
|
+
function exactKeys(value, keys) { return record(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key)) }
|
|
187
|
+
/** @param {unknown} value */
|
|
188
|
+
function record(value) { return typeof value === "object" && value !== null && !Array.isArray(value) }
|
|
189
|
+
/** Fail without reflecting untrusted binding input. */
|
|
190
|
+
function failBinding() { throw new Error("Threadwire binding is invalid") }
|
|
191
|
+
/** Fail without reflecting untrusted manifest input. */
|
|
192
|
+
function failManifest() { throw new Error("Threadwire context manifest is invalid") }
|
package/src/workspace-profile.js
CHANGED
|
@@ -6,6 +6,7 @@ import {dirname, join, resolve, sep} from "node:path"
|
|
|
6
6
|
import {fileURLToPath} from "node:url"
|
|
7
7
|
import {promisify} from "node:util"
|
|
8
8
|
import {PROVIDERS} from "./providers/index.js"
|
|
9
|
+
import {parseThreadwireBinding} from "./threadwire-binding.js"
|
|
9
10
|
|
|
10
11
|
const execFile = promisify(nodeExecFile)
|
|
11
12
|
const DEFAULT_PROFILES_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "threadwire.workspace-profiles.json")
|
|
@@ -14,9 +15,9 @@ const SOURCE_IDENTITY_PATTERN = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/u
|
|
|
14
15
|
const PROFILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* @typedef {{repositoryRoot: string, cwd: string, providers: ("codex" | "claude" | "kimi" | "opencode")[]}} WorkspaceProfile
|
|
18
|
-
* @typedef {{version: 1, defaultProfile: string, profiles: Record<string, WorkspaceProfile>}} WorkspaceProfilesConfig
|
|
19
|
-
* @typedef {{profile: string, repositoryRoot: string, cwd: string, revision: string, sourceIdentity: string}} ResolvedWorkspaceProfile
|
|
18
|
+
* @typedef {{repositoryRoot: string, cwd: string, providers: ("codex" | "claude" | "kimi" | "opencode")[], bindings?: {kimi: "threadwire-v1"}}} WorkspaceProfile
|
|
19
|
+
* @typedef {{version: 1 | 2, defaultProfile: string, profiles: Record<string, WorkspaceProfile>}} WorkspaceProfilesConfig
|
|
20
|
+
* @typedef {{profile: string, repositoryRoot: string, cwd: string, revision: string, sourceIdentity: string, binding?: unknown}} ResolvedWorkspaceProfile
|
|
20
21
|
* @typedef {{repositoryRoot: string, revision: string, sourceIdentity: string}} GitProvenance
|
|
21
22
|
* @typedef {{
|
|
22
23
|
* readProfiles?: () => Promise<unknown>,
|
|
@@ -27,7 +28,7 @@ const PROFILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u
|
|
|
27
28
|
|
|
28
29
|
/** @param {unknown} value @returns {WorkspaceProfilesConfig} */
|
|
29
30
|
export function parseWorkspaceProfiles(value) {
|
|
30
|
-
if (!isRecord(value) || value.version !== 1 || typeof value.defaultProfile !== "string" || !isRecord(value.profiles)) {
|
|
31
|
+
if (!isRecord(value) || (value.version !== 1 && value.version !== 2) || typeof value.defaultProfile !== "string" || !isRecord(value.profiles)) {
|
|
31
32
|
throw new Error("Workspace profile configuration is invalid")
|
|
32
33
|
}
|
|
33
34
|
assertExactKeys(value, ["version", "defaultProfile", "profiles"])
|
|
@@ -35,24 +36,31 @@ export function parseWorkspaceProfiles(value) {
|
|
|
35
36
|
const profiles = {}
|
|
36
37
|
for (const [name, profile] of Object.entries(value.profiles)) {
|
|
37
38
|
if (!PROFILE_NAME_PATTERN.test(name) || !isRecord(profile)) throw new Error("Workspace profile configuration is invalid")
|
|
38
|
-
assertExactKeys(profile, ["repositoryRoot", "cwd", "providers"])
|
|
39
|
+
assertExactKeys(profile, value.version === 2 ? ["repositoryRoot", "cwd", "providers", "bindings"] : ["repositoryRoot", "cwd", "providers"])
|
|
39
40
|
const repositoryRoot = absoluteNormalizedPath(profile.repositoryRoot)
|
|
40
41
|
const cwd = absoluteAbsolutePath(profile.cwd)
|
|
41
42
|
if (cwd !== resolve(cwd) || !pathWithin(repositoryRoot, resolve(cwd))) {
|
|
42
43
|
throw new Error(`Workspace profile ${name} cwd must stay within its repository root`)
|
|
43
44
|
}
|
|
44
45
|
const providers = parseProviders(profile.providers)
|
|
45
|
-
|
|
46
|
+
let bindings
|
|
47
|
+
if (value.version === 2 && Object.hasOwn(profile, "bindings")) {
|
|
48
|
+
if (!isRecord(profile.bindings)) throw new Error("Workspace profile configuration is invalid")
|
|
49
|
+
assertExactKeys(profile.bindings, ["kimi"])
|
|
50
|
+
if (profile.bindings.kimi !== "threadwire-v1") throw new Error("Workspace profile configuration is invalid")
|
|
51
|
+
bindings = /** @type {{kimi: "threadwire-v1"}} */ ({kimi: "threadwire-v1"})
|
|
52
|
+
}
|
|
53
|
+
profiles[name] = {repositoryRoot, cwd: resolve(cwd), providers, ...(bindings === undefined ? {} : {bindings})}
|
|
46
54
|
}
|
|
47
55
|
if (!Object.hasOwn(profiles, value.defaultProfile)) {
|
|
48
56
|
throw new Error("Workspace profile configuration is invalid")
|
|
49
57
|
}
|
|
50
|
-
return {version:
|
|
58
|
+
return {version: value.version, defaultProfile: value.defaultProfile, profiles}
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
54
62
|
* Resolve a reviewed workspace profile to a validated in-container workspace.
|
|
55
|
-
* @param {{provider: "codex" | "claude" | "kimi" | "opencode", profile?: string}} selection
|
|
63
|
+
* @param {{provider: "codex" | "claude" | "kimi" | "opencode", profile?: string, binding?: unknown}} selection
|
|
56
64
|
* @param {WorkspaceProfileOperations} [operations]
|
|
57
65
|
* @returns {Promise<ResolvedWorkspaceProfile>}
|
|
58
66
|
*/
|
|
@@ -65,6 +73,21 @@ export async function resolveWorkspaceProfile(selection, operations = {}) {
|
|
|
65
73
|
if (!profile.providers.includes(selection.provider)) {
|
|
66
74
|
throw new WorkspaceProviderMismatchError(profileName, selection.provider)
|
|
67
75
|
}
|
|
76
|
+
if (selection.provider === "kimi") {
|
|
77
|
+
if (config.version !== 2 || profile.bindings?.kimi !== "threadwire-v1" || selection.binding === undefined) {
|
|
78
|
+
throw new Error("Kimi requires Threadwire binding schema v1")
|
|
79
|
+
}
|
|
80
|
+
let binding
|
|
81
|
+
try { binding = parseThreadwireBinding(selection.binding) } catch { throw new Error("Kimi requires Threadwire binding schema v1") }
|
|
82
|
+
return {
|
|
83
|
+
profile: profileName,
|
|
84
|
+
repositoryRoot: binding.source.target,
|
|
85
|
+
cwd: binding.runtime.workdir,
|
|
86
|
+
revision: binding.source.revision,
|
|
87
|
+
sourceIdentity: binding.context.digests.content.slice("sha256:".length),
|
|
88
|
+
binding
|
|
89
|
+
}
|
|
90
|
+
}
|
|
68
91
|
const directoryExists = operations.directoryExists ?? defaultDirectoryExists
|
|
69
92
|
if (!await directoryExists(profile.repositoryRoot) || !await directoryExists(profile.cwd)) {
|
|
70
93
|
throw new Error(`Workspace profile ${profileName} workspace is unavailable`)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version":
|
|
2
|
+
"version": 2,
|
|
3
3
|
"defaultProfile": "container-runtime",
|
|
4
4
|
"profiles": {
|
|
5
5
|
"container-runtime": {
|
|
6
6
|
"repositoryRoot": "/workspace/threadwire",
|
|
7
7
|
"cwd": "/workspace/threadwire",
|
|
8
|
-
"providers": ["codex", "kimi"]
|
|
8
|
+
"providers": ["codex", "kimi"],
|
|
9
|
+
"bindings": {"kimi": "threadwire-v1"}
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
}
|