kimaki 0.4.80 → 0.4.82

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 (38) hide show
  1. package/dist/anthropic-auth-plugin.js +114 -72
  2. package/dist/cli.js +2 -499
  3. package/dist/commands/memory-snapshot.js +24 -0
  4. package/dist/commands/restart-opencode-server.js +61 -8
  5. package/dist/condense-memory.js +1 -1
  6. package/dist/context-awareness-plugin.js +1 -1
  7. package/dist/discord-bot.js +11 -4
  8. package/dist/discord-command-registration.js +512 -0
  9. package/dist/interaction-handler.js +7 -0
  10. package/dist/ipc-polling.js +4 -3
  11. package/dist/ipc-tools-plugin.js +1 -1
  12. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +80 -0
  13. package/dist/kimaki-opencode-plugin.js +13 -0
  14. package/dist/kimaki-opencode-plugin.test.js +98 -0
  15. package/dist/opencode.js +1 -1
  16. package/dist/runtime-idle-sweeper.js +3 -1
  17. package/dist/sentry.js +1 -1
  18. package/dist/session-handler/thread-session-runtime.js +71 -14
  19. package/package.json +4 -4
  20. package/src/anthropic-auth-plugin.ts +121 -70
  21. package/src/cli.ts +2 -658
  22. package/src/commands/memory-snapshot.ts +30 -0
  23. package/src/commands/restart-opencode-server.ts +67 -7
  24. package/src/condense-memory.ts +1 -1
  25. package/src/context-awareness-plugin.ts +1 -1
  26. package/src/discord-bot.ts +14 -4
  27. package/src/discord-command-registration.ts +678 -0
  28. package/src/interaction-handler.ts +8 -0
  29. package/src/ipc-polling.ts +4 -3
  30. package/src/ipc-tools-plugin.ts +1 -1
  31. package/src/{opencode-plugin-loading.e2e.test.ts → kimaki-opencode-plugin-loading.e2e.test.ts} +1 -1
  32. package/src/opencode.ts +1 -1
  33. package/src/runtime-idle-sweeper.ts +3 -1
  34. package/src/sentry.ts +1 -1
  35. package/src/session-handler/thread-session-runtime.ts +87 -15
  36. package/skills/lintcn/SKILL.md +0 -749
  37. /package/src/{opencode-plugin.test.ts → kimaki-opencode-plugin.test.ts} +0 -0
  38. /package/src/{opencode-plugin.ts → kimaki-opencode-plugin.ts} +0 -0
@@ -15,6 +15,7 @@
15
15
  * - https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts
16
16
  */
17
17
  import { generatePKCE } from "@openauthjs/openauth/pkce";
18
+ import { spawn } from "node:child_process";
18
19
  import * as fs from "node:fs/promises";
19
20
  import { createServer } from "node:http";
20
21
  import { homedir } from "node:os";
@@ -62,37 +63,88 @@ const OPENCODE_TO_CLAUDE_CODE_TOOL_NAME = {
62
63
  write: "Write",
63
64
  };
64
65
  // --- HTTP helpers ---
65
- const MAX_RETRIES = 3;
66
- const BASE_DELAY_MS = 2_000;
67
- async function sleep(ms) {
68
- return new Promise((resolve) => { setTimeout(resolve, ms); });
66
+ // Claude OAuth token exchange can 429 when this runs inside the opencode auth
67
+ // process, even with the same payload that succeeds in a plain Node process.
68
+ // Run these OAuth-only HTTP calls in an isolated Node child to avoid whatever
69
+ // parent-process runtime state is affecting the in-process requests.
70
+ async function requestText(urlString, options) {
71
+ return new Promise((resolve, reject) => {
72
+ const payload = JSON.stringify({
73
+ body: options.body,
74
+ headers: options.headers,
75
+ method: options.method,
76
+ url: urlString,
77
+ });
78
+ const child = spawn("node", ["-e", `
79
+ const input = JSON.parse(process.argv[1]);
80
+ (async () => {
81
+ const response = await fetch(input.url, {
82
+ method: input.method,
83
+ headers: input.headers,
84
+ body: input.body,
85
+ });
86
+ const text = await response.text();
87
+ if (!response.ok) {
88
+ console.error(JSON.stringify({ status: response.status, body: text }));
89
+ process.exit(1);
90
+ }
91
+ process.stdout.write(text);
92
+ })().catch((error) => {
93
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
94
+ process.exit(1);
95
+ });
96
+ `.trim(), payload], {
97
+ stdio: ["ignore", "pipe", "pipe"],
98
+ });
99
+ let stdout = "";
100
+ let stderr = "";
101
+ const timeout = setTimeout(() => {
102
+ child.kill();
103
+ reject(new Error(`Request timed out. url=${urlString}`));
104
+ }, 30_000);
105
+ child.stdout.on("data", (chunk) => {
106
+ stdout += String(chunk);
107
+ });
108
+ child.stderr.on("data", (chunk) => {
109
+ stderr += String(chunk);
110
+ });
111
+ child.on("error", (error) => {
112
+ clearTimeout(timeout);
113
+ reject(error);
114
+ });
115
+ child.on("close", (code) => {
116
+ clearTimeout(timeout);
117
+ if (code !== 0) {
118
+ let details = stderr.trim();
119
+ try {
120
+ const parsed = JSON.parse(details);
121
+ if (typeof parsed.status === "number") {
122
+ reject(new Error(`HTTP ${parsed.status} from ${urlString}: ${parsed.body ?? ""}`));
123
+ return;
124
+ }
125
+ }
126
+ catch {
127
+ // fall back to raw stderr
128
+ }
129
+ reject(new Error(details || `Node helper exited with code ${code}`));
130
+ return;
131
+ }
132
+ resolve(stdout);
133
+ });
134
+ });
69
135
  }
70
136
  async function postJson(url, body) {
71
- const jsonBody = JSON.stringify(body);
72
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
73
- const response = await fetch(url, {
74
- method: "POST",
75
- headers: { "Content-Type": "application/json", Accept: "application/json" },
76
- body: jsonBody,
77
- signal: AbortSignal.timeout(30_000),
78
- });
79
- if (response.status === 429 && attempt < MAX_RETRIES) {
80
- // Respect Retry-After header if present, otherwise exponential backoff
81
- const retryAfter = response.headers.get("retry-after");
82
- const delayMs = retryAfter
83
- ? Math.min(Number(retryAfter) * 1000, 60_000)
84
- : BASE_DELAY_MS * 2 ** attempt;
85
- console.warn(`[anthropic-auth] 429 from ${url}, retrying in ${delayMs}ms (attempt ${attempt + 1}/${MAX_RETRIES})`);
86
- await sleep(delayMs);
87
- continue;
88
- }
89
- if (!response.ok) {
90
- const text = await response.text().catch(() => "");
91
- throw new Error(`HTTP ${response.status} from ${url}: ${text}`);
92
- }
93
- return response.json();
94
- }
95
- throw new Error(`Exhausted retries for ${url}`);
137
+ const requestBody = JSON.stringify(body);
138
+ const responseText = await requestText(url, {
139
+ method: "POST",
140
+ headers: {
141
+ Accept: "application/json",
142
+ "Content-Length": String(Buffer.byteLength(requestBody)),
143
+ "Content-Type": "application/json",
144
+ },
145
+ body: requestBody,
146
+ });
147
+ return JSON.parse(responseText);
96
148
  }
97
149
  // --- File lock for token refresh ---
98
150
  let pendingRefresh;
@@ -163,33 +215,16 @@ async function refreshAnthropicToken(refreshToken) {
163
215
  };
164
216
  }
165
217
  async function createApiKey(accessToken) {
166
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
167
- const response = await fetch(CREATE_API_KEY_URL, {
168
- method: "POST",
169
- headers: {
170
- Accept: "application/json",
171
- authorization: `Bearer ${accessToken}`,
172
- "Content-Type": "application/json",
173
- },
174
- signal: AbortSignal.timeout(30_000),
175
- });
176
- if (response.status === 429 && attempt < MAX_RETRIES) {
177
- const retryAfter = response.headers.get("retry-after");
178
- const delayMs = retryAfter
179
- ? Math.min(Number(retryAfter) * 1000, 60_000)
180
- : BASE_DELAY_MS * 2 ** attempt;
181
- console.warn(`[anthropic-auth] 429 creating API key, retrying in ${delayMs}ms (attempt ${attempt + 1}/${MAX_RETRIES})`);
182
- await sleep(delayMs);
183
- continue;
184
- }
185
- if (!response.ok) {
186
- const text = await response.text().catch(() => "");
187
- throw new Error(`HTTP ${response.status} creating API key: ${text}`);
188
- }
189
- const json = (await response.json());
190
- return { type: "success", key: json.raw_key };
191
- }
192
- throw new Error(`Exhausted retries for ${CREATE_API_KEY_URL}`);
218
+ const responseText = await requestText(CREATE_API_KEY_URL, {
219
+ method: "POST",
220
+ headers: {
221
+ Accept: "application/json",
222
+ authorization: `Bearer ${accessToken}`,
223
+ "Content-Type": "application/json",
224
+ },
225
+ });
226
+ const json = JSON.parse(responseText);
227
+ return { type: "success", key: json.raw_key };
193
228
  }
194
229
  async function startCallbackServer(expectedState) {
195
230
  return new Promise((resolve, reject) => {
@@ -317,6 +352,7 @@ function buildAuthorizeHandler(mode) {
317
352
  return async () => {
318
353
  const auth = await beginAuthorizationFlow();
319
354
  const isRemote = Boolean(process.env.KIMAKI);
355
+ let pendingAuthResult;
320
356
  const finalize = async (result) => {
321
357
  const verifier = auth.verifier;
322
358
  const creds = await exchangeAuthorizationCode(result.code, result.state || verifier, verifier, REDIRECT_URI);
@@ -331,14 +367,17 @@ function buildAuthorizeHandler(mode) {
331
367
  instructions: "Complete login in your browser on this machine. OpenCode will catch the localhost callback automatically.",
332
368
  method: "auto",
333
369
  callback: async () => {
334
- try {
335
- const result = await waitForCallback(auth.callbackServer);
336
- return finalize(result);
337
- }
338
- catch (error) {
339
- console.error(`[anthropic-auth] ${error}`);
340
- return { type: "failed" };
341
- }
370
+ pendingAuthResult ??= (async () => {
371
+ try {
372
+ const result = await waitForCallback(auth.callbackServer);
373
+ return await finalize(result);
374
+ }
375
+ catch (error) {
376
+ console.error(`[anthropic-auth] ${error}`);
377
+ return { type: "failed" };
378
+ }
379
+ })();
380
+ return pendingAuthResult;
342
381
  },
343
382
  };
344
383
  }
@@ -347,14 +386,17 @@ function buildAuthorizeHandler(mode) {
347
386
  instructions: "Complete login in your browser, then paste the final redirect URL from the address bar here. Pasting just the authorization code also works.",
348
387
  method: "code",
349
388
  callback: async (input) => {
350
- try {
351
- const result = await waitForCallback(auth.callbackServer, input);
352
- return finalize(result);
353
- }
354
- catch (error) {
355
- console.error(`[anthropic-auth] ${error}`);
356
- return { type: "failed" };
357
- }
389
+ pendingAuthResult ??= (async () => {
390
+ try {
391
+ const result = await waitForCallback(auth.callbackServer, input);
392
+ return await finalize(result);
393
+ }
394
+ catch (error) {
395
+ console.error(`[anthropic-auth] ${error}`);
396
+ return { type: "failed" };
397
+ }
398
+ })();
399
+ return pendingAuthResult;
358
400
  },
359
401
  };
360
402
  };