thincoder 0.7.7 → 0.8.0

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 (97) hide show
  1. package/README.md +36 -13
  2. package/bin/thincoder.mjs +27 -346
  3. package/package.json +1 -1
  4. package/src/agent/dispatch.mjs +98 -0
  5. package/src/agent/helpers.mjs +185 -0
  6. package/src/agent/setup.mjs +117 -0
  7. package/src/agent-tools/goal.mjs +71 -0
  8. package/src/agent-tools/plan.mjs +31 -0
  9. package/src/agent-tools/recent-changes.mjs +23 -0
  10. package/src/agent-tools/skill.mjs +46 -0
  11. package/src/agent-tools/subagent.mjs +113 -0
  12. package/src/agent-tools/task.mjs +67 -0
  13. package/src/agent-tools/verify.mjs +198 -0
  14. package/src/agent-tools.mjs +12 -0
  15. package/src/agent.mjs +90 -1040
  16. package/src/cli/distill-command.mjs +85 -0
  17. package/src/cli/make-agent.mjs +85 -0
  18. package/src/cli/memory-command.mjs +63 -0
  19. package/src/cli/permission.mjs +41 -0
  20. package/src/cli/setup-wizard.mjs +70 -0
  21. package/src/config.mjs +5 -8
  22. package/src/context.mjs +10 -13
  23. package/src/distill.mjs +4 -3
  24. package/src/embedding.mjs +4 -2
  25. package/src/{checkpoint.mjs → git/checkpoint.mjs} +1 -1
  26. package/src/mcp/helpers.mjs +37 -0
  27. package/src/mcp/transport-http.mjs +176 -0
  28. package/src/mcp/transport-stdio.mjs +84 -0
  29. package/src/mcp/transport-ws.mjs +87 -0
  30. package/src/mcp.mjs +4 -428
  31. package/src/memory/code-index.mjs +211 -0
  32. package/src/memory/code-sync.mjs +306 -0
  33. package/src/memory/core.mjs +277 -0
  34. package/src/memory/docs.mjs +262 -0
  35. package/src/memory/schema.mjs +426 -0
  36. package/src/memory.mjs +12 -1403
  37. package/src/provider/core.mjs +239 -0
  38. package/src/provider/index.mjs +6 -0
  39. package/src/provider/rate.mjs +104 -0
  40. package/src/session.mjs +18 -5
  41. package/src/tools/bash.md +1 -0
  42. package/src/tools/bash.mjs +144 -0
  43. package/src/tools/file.mjs +205 -0
  44. package/src/tools/git.mjs +166 -0
  45. package/src/tools/glob.mjs +51 -0
  46. package/src/tools/grep.mjs +100 -0
  47. package/src/tools/index.mjs +22 -0
  48. package/src/tools/ls.mjs +36 -0
  49. package/src/tools/patch.mjs +226 -0
  50. package/src/tools/repomap-parse.mjs +168 -0
  51. package/src/tools/shared.mjs +257 -0
  52. package/src/tools/system.mjs +336 -0
  53. package/src/tools/web.mjs +121 -0
  54. package/src/tools.mjs +2 -1188
  55. package/src/tui/agent-turn.mjs +254 -0
  56. package/src/tui/ansi.mjs +32 -0
  57. package/src/tui/clipboard.mjs +48 -0
  58. package/src/tui/cmd-auto.mjs +21 -0
  59. package/src/tui/cmd-clear.mjs +26 -0
  60. package/src/tui/cmd-config.mjs +72 -0
  61. package/src/tui/cmd-exit.mjs +5 -0
  62. package/src/tui/cmd-extract.mjs +5 -0
  63. package/src/tui/cmd-goal.mjs +47 -0
  64. package/src/tui/cmd-help.mjs +25 -0
  65. package/src/tui/cmd-init.mjs +91 -0
  66. package/src/tui/cmd-mcp.mjs +146 -0
  67. package/src/tui/cmd-model.mjs +7 -0
  68. package/src/tui/cmd-new.mjs +18 -0
  69. package/src/tui/cmd-plan.mjs +21 -0
  70. package/src/tui/cmd-reindex.mjs +44 -0
  71. package/src/tui/cmd-restore.mjs +39 -0
  72. package/src/tui/cmd-session.mjs +42 -0
  73. package/src/tui/cmd-skills.mjs +17 -0
  74. package/src/tui/cmd-think.mjs +56 -0
  75. package/src/tui/config-helpers.mjs +34 -0
  76. package/src/tui/distill-cmd.mjs +45 -0
  77. package/src/tui/index.mjs +330 -0
  78. package/src/tui/interaction.mjs +79 -0
  79. package/src/tui/key-handler.mjs +267 -0
  80. package/src/tui/layout.mjs +115 -0
  81. package/src/tui/pickers.mjs +279 -0
  82. package/src/tui/render-frame.mjs +304 -0
  83. package/src/tui/render.mjs +205 -0
  84. package/src/tui/slash-commands.mjs +138 -0
  85. package/src/tui/startup.mjs +113 -0
  86. package/src/tui/wizard.mjs +168 -0
  87. package/src/tui-render.mjs +4 -0
  88. package/src/tui.mjs +3 -2546
  89. package/src/provider.mjs +0 -383
  90. /package/src/{gitmem.mjs → git/gitmem.mjs} +0 -0
  91. /package/src/{coder-overlay.md → prompts/coder.md} +0 -0
  92. /package/src/{discipline-rules.md → prompts/discipline.md} +0 -0
  93. /package/src/{explore-overlay.md → prompts/explore.md} +0 -0
  94. /package/src/{main-overlay.md → prompts/main.md} +0 -0
  95. /package/src/{plan-overlay.md → prompts/plan.md} +0 -0
  96. /package/src/{SYSTEM_PROMPT.md → prompts/system.md} +0 -0
  97. /package/src/{repomap.mjs → tools/repomap.mjs} +0 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * mcp/transport-http.mjs — MCP HTTP + SSE transport (Streamable HTTP)
3
+ */
4
+ import { rpcId, CALL_TIMEOUT_MS, ENDPOINT_WAIT_MS, withTimeout } from "./helpers.mjs"
5
+
6
+ export function httpTransport(baseURL, extraHeaders = {}) {
7
+ const url = baseURL.replace(/\/+$/, "")
8
+ let sessionId = null
9
+ let closed = false
10
+ let eventSource = null
11
+ let abortController = null
12
+ let postUrl = url
13
+ let legacySSE = false
14
+
15
+ const headers = () => {
16
+ const h = { "Content-Type": "application/json", Accept: "text/event-stream, application/json", ...extraHeaders }
17
+ if (sessionId) h["Mcp-Session-Id"] = sessionId
18
+ return h
19
+ }
20
+
21
+ const pending = new Map()
22
+
23
+ async function* parseSSE(response) {
24
+ const reader = response.body.getReader()
25
+ const decoder = new TextDecoder()
26
+ let buf = ""
27
+ let current = { data: "", event: "message" }
28
+ try {
29
+ while (true) {
30
+ const { done, value } = await reader.read()
31
+ if (done) break
32
+ buf += decoder.decode(value, { stream: true })
33
+ const lines = buf.split("\n")
34
+ buf = lines.pop() ?? ""
35
+ for (const raw of lines) {
36
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw
37
+ if (line === "") {
38
+ if (current.data) {
39
+ yield { event: current.event, data: current.data.trimEnd() }
40
+ current = { data: "", event: "message" }
41
+ }
42
+ } else if (line.startsWith("data:")) {
43
+ current.data += (current.data ? "\n" : "") + line.slice(5).replace(/^ /, "")
44
+ } else if (line.startsWith("event:")) {
45
+ current.event = line.slice(6).trim()
46
+ }
47
+ }
48
+ }
49
+ } finally {
50
+ reader.releaseLock()
51
+ }
52
+ }
53
+
54
+ async function openSSE() {
55
+ if (closed) return
56
+ abortController?.abort()
57
+ abortController = new AbortController()
58
+ const resp = await fetch(url, {
59
+ method: "GET",
60
+ headers: { Accept: "text/event-stream", ...extraHeaders },
61
+ signal: abortController.signal,
62
+ })
63
+ if (!resp.ok) throw new Error(`SSE connect failed: HTTP ${resp.status}`)
64
+ eventSource = parseSSE(resp)
65
+ let endpointReady
66
+ const gotEndpoint = new Promise((resolve) => { endpointReady = resolve })
67
+
68
+ ;(async () => {
69
+ try {
70
+ for await (const { event, data } of eventSource) {
71
+ if (closed) break
72
+ if (event === "endpoint") {
73
+ postUrl = new URL(data.trim(), url).href
74
+ legacySSE = true
75
+ endpointReady()
76
+ continue
77
+ }
78
+ try {
79
+ const msg = JSON.parse(data)
80
+ const resolver = pending.get(msg.id)
81
+ if (resolver) {
82
+ pending.delete(msg.id)
83
+ resolver(msg)
84
+ }
85
+ } catch { /* 非 JSON,忽略 */ }
86
+ }
87
+ } catch (error) {
88
+ if (!closed) {
89
+ for (const [, resolve] of pending) resolve({ id: null, error: { code: -32000, message: `SSE error: ${error.message}` } })
90
+ pending.clear()
91
+ }
92
+ }
93
+ })()
94
+
95
+ const wait = new Promise((resolve) => {
96
+ const t = setTimeout(resolve, ENDPOINT_WAIT_MS)
97
+ t.unref?.()
98
+ })
99
+ await Promise.race([gotEndpoint, wait])
100
+ }
101
+
102
+ async function postRequest(method, params) {
103
+ const id = rpcId()
104
+ const body = JSON.stringify({ jsonrpc: "2.0", id, method, params })
105
+
106
+ if (legacySSE) {
107
+ return new Promise((resolve) => {
108
+ pending.set(id, resolve)
109
+ fetch(postUrl, { method: "POST", headers: headers(), body, signal: AbortSignal.timeout(CALL_TIMEOUT_MS) })
110
+ .then((resp) => {
111
+ if (!resp.ok) {
112
+ pending.delete(id)
113
+ resolve({ id, error: { code: -32000, message: `POST failed: HTTP ${resp.status}` } })
114
+ }
115
+ })
116
+ .catch((e) => {
117
+ pending.delete(id)
118
+ resolve({ id, error: { code: -32000, message: `POST failed: ${e.message}` } })
119
+ })
120
+ }).finally(() => pending.delete(id))
121
+ }
122
+
123
+ const resp = await fetch(postUrl, {
124
+ method: "POST",
125
+ headers: headers(),
126
+ body,
127
+ signal: AbortSignal.timeout(CALL_TIMEOUT_MS),
128
+ })
129
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
130
+
131
+ const ct = resp.headers.get("content-type") ?? ""
132
+ const newSessionId = resp.headers.get("Mcp-Session-Id")
133
+ if (newSessionId) sessionId = newSessionId
134
+
135
+ if (ct.includes("text/event-stream")) {
136
+ const sse = parseSSE(resp)
137
+ for await (const { data } of sse) {
138
+ try {
139
+ const msg = JSON.parse(data)
140
+ if (msg.id === id) return msg
141
+ } catch { /* skip */ }
142
+ }
143
+ return { id, error: { code: -32000, message: "No JSON-RPC response in SSE stream" } }
144
+ }
145
+
146
+ return resp.json()
147
+ }
148
+
149
+ const send = async (method, params) => withTimeout(postRequest(method, params), CALL_TIMEOUT_MS)
150
+
151
+ const notify = (method, params) => {
152
+ fetch(postUrl, {
153
+ method: "POST",
154
+ headers: headers(),
155
+ body: JSON.stringify({ jsonrpc: "2.0", method, params }),
156
+ signal: AbortSignal.timeout(10_000),
157
+ }).catch(() => {})
158
+ }
159
+
160
+ const close = () => {
161
+ closed = true
162
+ abortController?.abort()
163
+ if (sessionId) {
164
+ fetch(postUrl, {
165
+ method: "DELETE",
166
+ headers: { "Mcp-Session-Id": sessionId, ...extraHeaders },
167
+ signal: AbortSignal.timeout(5_000),
168
+ }).catch(() => {})
169
+ sessionId = null
170
+ }
171
+ for (const [, resolve] of pending) resolve({ id: null, error: { code: -32000, message: "Connection closed" } })
172
+ pending.clear()
173
+ }
174
+
175
+ return { send, notify, close, openSSE, url, headers: extraHeaders }
176
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * mcp/transport-stdio.mjs — MCP stdio transport
3
+ */
4
+ import { spawn } from "node:child_process"
5
+ import { rpcId, CALL_TIMEOUT_MS, withTimeout, quoteArg } from "./helpers.mjs"
6
+
7
+ export function stdioTransport(command, args) {
8
+ const spawnOptions = { stdio: ["pipe", "pipe", "pipe"], windowsHide: true, env: { ...process.env } }
9
+ const child =
10
+ process.platform === "win32" && !/\.exe$/i.test(command)
11
+ ? spawn("cmd.exe", ["/d", "/s", "/c", [command, ...(args ?? [])].map(quoteArg).join(" ")], {
12
+ ...spawnOptions,
13
+ windowsVerbatimArguments: true,
14
+ })
15
+ : spawn(command, args ?? [], spawnOptions)
16
+
17
+ const pending = new Map()
18
+ const decoder = new TextDecoder()
19
+ let buffer = ""
20
+ let stderrTail = ""
21
+ let spawnError = null
22
+ let closed = false
23
+
24
+ const failAll = (message) => {
25
+ for (const [, resolve] of pending) resolve({ id: null, error: { code: -32000, message } })
26
+ pending.clear()
27
+ }
28
+
29
+ child.stdout.on("data", (chunk) => {
30
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true })
31
+ const lines = buffer.split("\n")
32
+ buffer = lines.pop() ?? ""
33
+ for (const line of lines) {
34
+ if (!line.trim()) continue
35
+ try {
36
+ const msg = JSON.parse(line)
37
+ const resolver = pending.get(msg.id)
38
+ if (resolver) {
39
+ pending.delete(msg.id)
40
+ resolver(msg)
41
+ }
42
+ } catch { /* 非 JSON 行忽略 */ }
43
+ }
44
+ })
45
+
46
+ child.stderr.on("data", (chunk) => {
47
+ stderrTail = (stderrTail + chunk.toString()).slice(-2000)
48
+ })
49
+
50
+ child.stdin.on("error", () => {})
51
+ child.on("error", (error) => {
52
+ spawnError = error
53
+ closed = true
54
+ failAll(`spawn failed: ${error.message}`)
55
+ })
56
+ child.on("close", () => {
57
+ closed = true
58
+ const lastLine = stderrTail.trim().split("\n").pop()
59
+ failAll(`Connection closed${lastLine ? ` | stderr: ${lastLine}` : ""}`)
60
+ })
61
+
62
+ const send = (method, params) => {
63
+ if (spawnError) return Promise.resolve({ id: null, error: { code: -32000, message: `spawn failed: ${spawnError.message}` } })
64
+ if (closed) return Promise.reject(new Error("MCP connection closed"))
65
+ const id = rpcId()
66
+ const promise = new Promise((resolve) => pending.set(id, resolve))
67
+ try {
68
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n")
69
+ } catch (error) {
70
+ pending.delete(id)
71
+ return Promise.resolve({ id: null, error: { code: -32000, message: `stdin write failed: ${error.message}` } })
72
+ }
73
+ return withTimeout(promise, CALL_TIMEOUT_MS).finally(() => pending.delete(id))
74
+ }
75
+
76
+ const notify = (method, params) => {
77
+ if (closed) return
78
+ try {
79
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n")
80
+ } catch { /* 忽略 */ }
81
+ }
82
+
83
+ return { send, notify, close: () => { if (!closed) child.kill() } }
84
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * mcp/transport-ws.mjs — MCP WebSocket transport
3
+ */
4
+ import { rpcId, INIT_TIMEOUT_MS, CALL_TIMEOUT_MS, withTimeout, withAuthToken } from "./helpers.mjs"
5
+
6
+ export function wsTransport(wsUrl, extraHeaders = {}) {
7
+ const pending = new Map()
8
+ let closed = false
9
+ let ws = null
10
+
11
+ const failAll = (message) => {
12
+ for (const [, resolve] of pending) resolve({ id: null, error: { code: -32000, message } })
13
+ pending.clear()
14
+ }
15
+
16
+ const connect = () => {
17
+ if (closed) throw new Error("MCP WebSocket connection closed")
18
+ ws = new WebSocket(withAuthToken(wsUrl, extraHeaders.Authorization))
19
+
20
+ return new Promise((resolve, reject) => {
21
+ const timeout = setTimeout(() => {
22
+ ws.close()
23
+ reject(new Error(`WebSocket connect timeout: ${wsUrl}`))
24
+ }, INIT_TIMEOUT_MS)
25
+
26
+ ws.addEventListener("open", () => {
27
+ clearTimeout(timeout)
28
+ resolve()
29
+ })
30
+
31
+ ws.addEventListener("message", (event) => {
32
+ try {
33
+ const msg = JSON.parse(event.data.toString())
34
+ const resolver = pending.get(msg.id)
35
+ if (resolver) {
36
+ pending.delete(msg.id)
37
+ resolver(msg)
38
+ }
39
+ } catch { /* 非 JSON,忽略 */ }
40
+ })
41
+
42
+ ws.addEventListener("error", (event) => {
43
+ clearTimeout(timeout)
44
+ closed = true
45
+ const errMsg = event.message || "WebSocket error"
46
+ if (pending.size > 0) {
47
+ failAll(errMsg)
48
+ } else {
49
+ reject(new Error(errMsg))
50
+ }
51
+ })
52
+
53
+ ws.addEventListener("close", () => {
54
+ clearTimeout(timeout)
55
+ closed = true
56
+ failAll("WebSocket closed")
57
+ })
58
+ })
59
+ }
60
+
61
+ const send = (method, params) => {
62
+ if (closed) return Promise.reject(new Error("MCP WebSocket connection closed"))
63
+ const id = rpcId()
64
+ const promise = new Promise((resolve) => pending.set(id, resolve))
65
+ try {
66
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }))
67
+ } catch (error) {
68
+ pending.delete(id)
69
+ return Promise.resolve({ id: null, error: { code: -32000, message: `ws send failed: ${error.message}` } })
70
+ }
71
+ return withTimeout(promise, CALL_TIMEOUT_MS).finally(() => pending.delete(id))
72
+ }
73
+
74
+ const notify = (method, params) => {
75
+ if (!closed && ws?.readyState === WebSocket.OPEN) {
76
+ ws.send(JSON.stringify({ jsonrpc: "2.0", method, params }))
77
+ }
78
+ }
79
+
80
+ const close = () => {
81
+ closed = true
82
+ failAll("Connection closed")
83
+ try { ws?.close() } catch { /* 忽略 */ }
84
+ }
85
+
86
+ return { send, notify, close, connect }
87
+ }