@ainecto/cli 0.12.0 → 0.12.1

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.
@@ -175,6 +175,259 @@ function isRecord(value) {
175
175
  return typeof value === "object" && value !== null && !Array.isArray(value);
176
176
  }
177
177
 
178
+ // src/core/http/rateLimit.ts
179
+ var RATE_LIMITED = "RATE_LIMITED";
180
+ function parseRetryAfter(value, now = Date.now()) {
181
+ if (value === null || value === void 0) return void 0;
182
+ const trimmed = value.trim();
183
+ if (/^\d+$/.test(trimmed)) return representable(Number(trimmed), now);
184
+ if (!/[a-z]/i.test(trimmed)) return void 0;
185
+ const at = Date.parse(trimmed);
186
+ if (Number.isNaN(at)) return void 0;
187
+ return Math.max(0, Math.ceil((at - now) / 1e3));
188
+ }
189
+ function representable(seconds, now) {
190
+ return Number.isNaN(new Date(now + seconds * 1e3).getTime()) ? void 0 : seconds;
191
+ }
192
+ function rateLimitDetails(headerValue, body, now = Date.now()) {
193
+ const fields = isRecord2(body) ? body : {};
194
+ const fromBody = typeof fields.retryAfterSeconds === "number" && Number.isFinite(fields.retryAfterSeconds) && fields.retryAfterSeconds >= 0 ? representable(Math.ceil(fields.retryAfterSeconds), now) : void 0;
195
+ const retryAfterSeconds = fromBody ?? parseRetryAfter(headerValue, now);
196
+ return {
197
+ status: 429,
198
+ ...retryAfterSeconds !== void 0 ? { retryAfterSeconds, retryAt: new Date(now + retryAfterSeconds * 1e3).toISOString() } : {},
199
+ ...typeof fields.scope === "string" ? { scope: fields.scope } : {},
200
+ ...typeof fields.policyId === "string" ? { policyId: fields.policyId } : {}
201
+ };
202
+ }
203
+ function isRateLimitedRpcError(error) {
204
+ return isRecord2(error) && error.code === 429 && isRecord2(error.data) && error.data.code === RATE_LIMITED;
205
+ }
206
+ function rateLimitMessage(where, details) {
207
+ return details.retryAfterSeconds === void 0 ? `${where} was rate limited (HTTP 429). Wait before trying again.` : `${where} was rate limited (HTTP 429). Retry after ${details.retryAfterSeconds} seconds (${details.retryAt}).`;
208
+ }
209
+ function isRecord2(value) {
210
+ return typeof value === "object" && value !== null && !Array.isArray(value);
211
+ }
212
+
213
+ // src/core/harness/legacyRole.ts
214
+ var LEGACY_ROLE_HEADER = "X-AiNecto-Role";
215
+ var LEGACY_ROLE_ENV_VAR = "AINECTO_ROLE";
216
+ var LEGACY_ROLE_NOTICE = "AiNecto session roles were removed; access follows your AiNecto permissions. Re-run `ainecto init` to tidy this repository.";
217
+ function legacyRoleInput(flag, envVars = process.env) {
218
+ const value = flag ?? envVars[LEGACY_ROLE_ENV_VAR];
219
+ return value !== void 0 && value.trim() ? value.trim() : void 0;
220
+ }
221
+
222
+ // src/core/version.ts
223
+ var UNBUNDLED_VERSION = "dev";
224
+ var CLI_VERSION = true ? "0.12.1" : UNBUNDLED_VERSION;
225
+ var PACKAGE_NAME = "@ainecto/mcp";
226
+ var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
227
+ function bridgePackageSpec(version = CLI_VERSION) {
228
+ return version === UNBUNDLED_VERSION ? PACKAGE_NAME : `${PACKAGE_NAME}@${version}`;
229
+ }
230
+
231
+ // src/core/mcp/rpcClient.ts
232
+ var CHANNEL_HEADER = "X-AiNecto-Channel";
233
+ var AGENT_HEADER = "X-AiNecto-Agent";
234
+ var AGENT_NAME_HEADER = "X-AiNecto-Agent-Name";
235
+ var MAX_AGENT_NAME_LENGTH = 100;
236
+ var McpRpcError = class extends Error {
237
+ constructor(message, code, details) {
238
+ super(message);
239
+ this.code = code;
240
+ this.details = details;
241
+ }
242
+ code;
243
+ details;
244
+ };
245
+ var RateLimitedError = class extends McpRpcError {
246
+ constructor(message, rateLimit) {
247
+ super(message, RATE_LIMITED, rateLimit);
248
+ this.rateLimit = rateLimit;
249
+ this.name = "RateLimitedError";
250
+ }
251
+ rateLimit;
252
+ };
253
+ var McpRpcClient = class {
254
+ constructor(options) {
255
+ this.options = options;
256
+ this.fetchImpl = options.fetchImpl ?? fetch;
257
+ }
258
+ options;
259
+ fetchImpl;
260
+ nextId = 1;
261
+ agent;
262
+ /**
263
+ * 브리지 전용 — 호스트 에이전트가 보낸 {@code initialize} 의 {@code clientInfo.name}(프로그램 고정 이름).
264
+ * 이후 모든 요청의 {@code X-AiNecto-Agent} 헤더로 싣는다. 직접 명령은 부르지 않는다(누가 셸을 돌렸는지 모른다).
265
+ */
266
+ useAgent(clientName) {
267
+ this.agent = agentHeaderValue(clientName);
268
+ }
269
+ initialize() {
270
+ return this.request("initialize", {
271
+ protocolVersion: "2024-11-05",
272
+ capabilities: {},
273
+ clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION }
274
+ });
275
+ }
276
+ async toolsList() {
277
+ const result = await this.request("tools/list", {});
278
+ if (!isRecord3(result) || !Array.isArray(result.tools)) {
279
+ throw new McpRpcError("tools/list response did not include a tools array.", "MCP_PROTOCOL_ERROR", result);
280
+ }
281
+ return result.tools;
282
+ }
283
+ async toolsCall(name, args) {
284
+ return this.request("tools/call", { name, arguments: args ?? {} });
285
+ }
286
+ async request(method, params) {
287
+ const response = await this.sendRaw({
288
+ jsonrpc: "2.0",
289
+ id: this.nextId++,
290
+ method,
291
+ params
292
+ });
293
+ if (!response) {
294
+ return void 0;
295
+ }
296
+ if ("error" in response) {
297
+ if (isRateLimitedRpcError(response.error)) {
298
+ const details = rateLimitDetails(void 0, response.error.data);
299
+ throw new RateLimitedError(rateLimitMessage(`MCP ${method}`, details), details);
300
+ }
301
+ throw new McpRpcError(response.error.message, "MCP_ERROR", response.error);
302
+ }
303
+ return response.result;
304
+ }
305
+ async sendRaw(request) {
306
+ const token = await this.options.tokenProvider?.getAccessToken();
307
+ let response = await this.postJson(request, token);
308
+ if (response.status === 401 && this.options.tokenProvider) {
309
+ const refreshed = await this.options.tokenProvider.refreshAfterUnauthorized();
310
+ if (refreshed) {
311
+ response = await this.postJson(request, refreshed);
312
+ }
313
+ }
314
+ if (response.status === 204 || response.status === 202) {
315
+ return void 0;
316
+ }
317
+ const text = await response.text();
318
+ if (response.status === 429) {
319
+ return rateLimitedResponse(text, response.headers.get("retry-after"));
320
+ }
321
+ if (!response.ok) {
322
+ const serverError = parseServerError(text);
323
+ throw new McpRpcError(
324
+ `MCP HTTP request failed with HTTP ${response.status}${serverError ? ` \u2014 ${describeServerError(serverError)}` : "."}`,
325
+ "MCP_HTTP_ERROR",
326
+ { ...httpErrorDetails(response.status, text, this.options.envVars ?? process.env), ...serverError ? { error: serverError.error } : {} }
327
+ );
328
+ }
329
+ if (!text.trim()) {
330
+ return void 0;
331
+ }
332
+ const parsed = JSON.parse(text);
333
+ if (!isJsonRpcResponse(parsed)) {
334
+ throw new McpRpcError("MCP response was not a JSON-RPC response.", "MCP_PROTOCOL_ERROR", parsed);
335
+ }
336
+ return parsed;
337
+ }
338
+ postJson(request, token) {
339
+ const headers = {
340
+ "content-type": "application/json",
341
+ accept: "application/json"
342
+ };
343
+ if (token) {
344
+ headers.authorization = `Bearer ${token}`;
345
+ }
346
+ const legacyRole = printableHeaderValue(this.options.legacyRole);
347
+ if (legacyRole) {
348
+ headers[LEGACY_ROLE_HEADER.toLowerCase()] = legacyRole;
349
+ }
350
+ headers[CHANNEL_HEADER.toLowerCase()] = this.options.channel;
351
+ if (this.agent) {
352
+ headers[AGENT_HEADER.toLowerCase()] = this.agent;
353
+ }
354
+ if (this.options.agentName) {
355
+ headers[AGENT_NAME_HEADER.toLowerCase()] = this.options.agentName;
356
+ }
357
+ return this.fetchImpl(this.options.endpoint, {
358
+ method: "POST",
359
+ headers,
360
+ body: JSON.stringify(request)
361
+ });
362
+ }
363
+ };
364
+ function rateLimitedResponse(text, retryAfter) {
365
+ let parsed;
366
+ try {
367
+ parsed = JSON.parse(text);
368
+ } catch {
369
+ parsed = void 0;
370
+ }
371
+ if (isJsonRpcResponse(parsed) && "error" in parsed) {
372
+ const data = isRecord3(parsed.error.data) ? parsed.error.data : {};
373
+ const seconds = parseRetryAfter(retryAfter);
374
+ if (data.retryAfterSeconds === void 0 && seconds !== void 0) {
375
+ parsed.error.data = { ...data, retryAfterSeconds: seconds };
376
+ }
377
+ return parsed;
378
+ }
379
+ const details = rateLimitDetails(retryAfter, parsed);
380
+ throw new RateLimitedError(rateLimitMessage("MCP HTTP request", details), details);
381
+ }
382
+ function isToolError(result) {
383
+ return isRecord3(result) && result.isError === true;
384
+ }
385
+ function agentHeaderValue(clientName) {
386
+ return printableHeaderValue(clientName);
387
+ }
388
+ function printableHeaderValue(value) {
389
+ if (typeof value !== "string") {
390
+ return void 0;
391
+ }
392
+ const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
393
+ return printable || void 0;
394
+ }
395
+ function parseServerError(body) {
396
+ let parsed;
397
+ try {
398
+ parsed = JSON.parse(body);
399
+ } catch {
400
+ return void 0;
401
+ }
402
+ if (!isRecord3(parsed) || typeof parsed.error !== "string" || !parsed.error.trim()) {
403
+ return void 0;
404
+ }
405
+ return {
406
+ error: parsed.error.trim(),
407
+ description: typeof parsed.error_description === "string" && parsed.error_description.trim() ? parsed.error_description : void 0
408
+ };
409
+ }
410
+ function describeServerError(serverError) {
411
+ const line = serverError.description ? `${serverError.error}: ${serverError.description}` : serverError.error;
412
+ return line.replace(/\s+/g, " ").trim();
413
+ }
414
+ function isJsonRpcResponse(value) {
415
+ return isRecord3(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
416
+ }
417
+ function isRecord3(value) {
418
+ return typeof value === "object" && value !== null && !Array.isArray(value);
419
+ }
420
+ function httpErrorDetails(status, body, envVars) {
421
+ const details = { status };
422
+ if (envVars.AINECTO_CLI_DEBUG === "1" || envVars.AINECTO_DEBUG === "1") {
423
+ details.body = safeBodyPreview(body);
424
+ }
425
+ return details;
426
+ }
427
+ function safeBodyPreview(body) {
428
+ return body.length > 500 ? `${body.slice(0, 500)}...` : body;
429
+ }
430
+
178
431
  // src/core/auth/oauth.ts
179
432
  var DEFAULT_LOGIN_TIMEOUT_MS = 3e5;
180
433
  var OAuthLoginError = class extends Error {
@@ -388,6 +641,10 @@ async function exchangeToken(tokenEndpoint, params, fetchImpl) {
388
641
  headers: { "content-type": "application/x-www-form-urlencoded" },
389
642
  body: new URLSearchParams(params)
390
643
  });
644
+ if (response.status === 429) {
645
+ const details = rateLimitDetails(response.headers.get("retry-after"), await readJsonObject(response));
646
+ throw new RateLimitedError(rateLimitMessage("OAuth token request", details), details);
647
+ }
391
648
  if (!response.ok) {
392
649
  const oauthError = await readOAuthErrorCode(response);
393
650
  if (params.grant_type === "refresh_token" && oauthError !== void 0 && REAUTH_REQUIRED_ERRORS.has(oauthError)) {
@@ -397,6 +654,13 @@ async function exchangeToken(tokenEndpoint, params, fetchImpl) {
397
654
  }
398
655
  return response.json();
399
656
  }
657
+ async function readJsonObject(response) {
658
+ try {
659
+ return await response.json();
660
+ } catch {
661
+ return void 0;
662
+ }
663
+ }
400
664
  async function readOAuthErrorCode(response) {
401
665
  try {
402
666
  const body = await response.json();
@@ -612,191 +876,6 @@ async function readAllStdin(stdin) {
612
876
  return text;
613
877
  }
614
878
 
615
- // src/core/harness/legacyRole.ts
616
- var LEGACY_ROLE_HEADER = "X-AiNecto-Role";
617
- var LEGACY_ROLE_ENV_VAR = "AINECTO_ROLE";
618
- var LEGACY_ROLE_NOTICE = "AiNecto session roles were removed; access follows your AiNecto permissions. Re-run `ainecto init` to tidy this repository.";
619
- function legacyRoleInput(flag, envVars = process.env) {
620
- const value = flag ?? envVars[LEGACY_ROLE_ENV_VAR];
621
- return value !== void 0 && value.trim() ? value.trim() : void 0;
622
- }
623
-
624
- // src/core/version.ts
625
- var UNBUNDLED_VERSION = "dev";
626
- var CLI_VERSION = true ? "0.12.0" : UNBUNDLED_VERSION;
627
- var PACKAGE_NAME = "@ainecto/mcp";
628
- var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
629
- function bridgePackageSpec(version = CLI_VERSION) {
630
- return version === UNBUNDLED_VERSION ? PACKAGE_NAME : `${PACKAGE_NAME}@${version}`;
631
- }
632
-
633
- // src/core/mcp/rpcClient.ts
634
- var CHANNEL_HEADER = "X-AiNecto-Channel";
635
- var AGENT_HEADER = "X-AiNecto-Agent";
636
- var AGENT_NAME_HEADER = "X-AiNecto-Agent-Name";
637
- var MAX_AGENT_NAME_LENGTH = 100;
638
- var McpRpcError = class extends Error {
639
- constructor(message, code, details) {
640
- super(message);
641
- this.code = code;
642
- this.details = details;
643
- }
644
- code;
645
- details;
646
- };
647
- var McpRpcClient = class {
648
- constructor(options) {
649
- this.options = options;
650
- this.fetchImpl = options.fetchImpl ?? fetch;
651
- }
652
- options;
653
- fetchImpl;
654
- nextId = 1;
655
- agent;
656
- /**
657
- * 브리지 전용 — 호스트 에이전트가 보낸 {@code initialize} 의 {@code clientInfo.name}(프로그램 고정 이름).
658
- * 이후 모든 요청의 {@code X-AiNecto-Agent} 헤더로 싣는다. 직접 명령은 부르지 않는다(누가 셸을 돌렸는지 모른다).
659
- */
660
- useAgent(clientName) {
661
- this.agent = agentHeaderValue(clientName);
662
- }
663
- initialize() {
664
- return this.request("initialize", {
665
- protocolVersion: "2024-11-05",
666
- capabilities: {},
667
- clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION }
668
- });
669
- }
670
- async toolsList() {
671
- const result = await this.request("tools/list", {});
672
- if (!isRecord2(result) || !Array.isArray(result.tools)) {
673
- throw new McpRpcError("tools/list response did not include a tools array.", "MCP_PROTOCOL_ERROR", result);
674
- }
675
- return result.tools;
676
- }
677
- async toolsCall(name, args) {
678
- return this.request("tools/call", { name, arguments: args ?? {} });
679
- }
680
- async request(method, params) {
681
- const response = await this.sendRaw({
682
- jsonrpc: "2.0",
683
- id: this.nextId++,
684
- method,
685
- params
686
- });
687
- if (!response) {
688
- return void 0;
689
- }
690
- if ("error" in response) {
691
- throw new McpRpcError(response.error.message, "MCP_ERROR", response.error);
692
- }
693
- return response.result;
694
- }
695
- async sendRaw(request) {
696
- const token = await this.options.tokenProvider?.getAccessToken();
697
- let response = await this.postJson(request, token);
698
- if (response.status === 401 && this.options.tokenProvider) {
699
- const refreshed = await this.options.tokenProvider.refreshAfterUnauthorized();
700
- if (refreshed) {
701
- response = await this.postJson(request, refreshed);
702
- }
703
- }
704
- if (response.status === 204 || response.status === 202) {
705
- return void 0;
706
- }
707
- const text = await response.text();
708
- if (!response.ok) {
709
- const serverError = parseServerError(text);
710
- throw new McpRpcError(
711
- `MCP HTTP request failed with HTTP ${response.status}${serverError ? ` \u2014 ${describeServerError(serverError)}` : "."}`,
712
- "MCP_HTTP_ERROR",
713
- { ...httpErrorDetails(response.status, text, this.options.envVars ?? process.env), ...serverError ? { error: serverError.error } : {} }
714
- );
715
- }
716
- if (!text.trim()) {
717
- return void 0;
718
- }
719
- const parsed = JSON.parse(text);
720
- if (!isJsonRpcResponse(parsed)) {
721
- throw new McpRpcError("MCP response was not a JSON-RPC response.", "MCP_PROTOCOL_ERROR", parsed);
722
- }
723
- return parsed;
724
- }
725
- postJson(request, token) {
726
- const headers = {
727
- "content-type": "application/json",
728
- accept: "application/json"
729
- };
730
- if (token) {
731
- headers.authorization = `Bearer ${token}`;
732
- }
733
- const legacyRole = printableHeaderValue(this.options.legacyRole);
734
- if (legacyRole) {
735
- headers[LEGACY_ROLE_HEADER.toLowerCase()] = legacyRole;
736
- }
737
- headers[CHANNEL_HEADER.toLowerCase()] = this.options.channel;
738
- if (this.agent) {
739
- headers[AGENT_HEADER.toLowerCase()] = this.agent;
740
- }
741
- if (this.options.agentName) {
742
- headers[AGENT_NAME_HEADER.toLowerCase()] = this.options.agentName;
743
- }
744
- return this.fetchImpl(this.options.endpoint, {
745
- method: "POST",
746
- headers,
747
- body: JSON.stringify(request)
748
- });
749
- }
750
- };
751
- function isToolError(result) {
752
- return isRecord2(result) && result.isError === true;
753
- }
754
- function agentHeaderValue(clientName) {
755
- return printableHeaderValue(clientName);
756
- }
757
- function printableHeaderValue(value) {
758
- if (typeof value !== "string") {
759
- return void 0;
760
- }
761
- const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
762
- return printable || void 0;
763
- }
764
- function parseServerError(body) {
765
- let parsed;
766
- try {
767
- parsed = JSON.parse(body);
768
- } catch {
769
- return void 0;
770
- }
771
- if (!isRecord2(parsed) || typeof parsed.error !== "string" || !parsed.error.trim()) {
772
- return void 0;
773
- }
774
- return {
775
- error: parsed.error.trim(),
776
- description: typeof parsed.error_description === "string" && parsed.error_description.trim() ? parsed.error_description : void 0
777
- };
778
- }
779
- function describeServerError(serverError) {
780
- const line = serverError.description ? `${serverError.error}: ${serverError.description}` : serverError.error;
781
- return line.replace(/\s+/g, " ").trim();
782
- }
783
- function isJsonRpcResponse(value) {
784
- return isRecord2(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
785
- }
786
- function isRecord2(value) {
787
- return typeof value === "object" && value !== null && !Array.isArray(value);
788
- }
789
- function httpErrorDetails(status, body, envVars) {
790
- const details = { status };
791
- if (envVars.AINECTO_CLI_DEBUG === "1" || envVars.AINECTO_DEBUG === "1") {
792
- details.body = safeBodyPreview(body);
793
- }
794
- return details;
795
- }
796
- function safeBodyPreview(body) {
797
- return body.length > 500 ? `${body.slice(0, 500)}...` : body;
798
- }
799
-
800
879
  // src/core/output/render.ts
801
880
  function renderSuccess(data, options = {}) {
802
881
  if (options.json) {
@@ -1159,7 +1238,7 @@ function inferPayloadMode(strippedName, inputSchema) {
1159
1238
  return "binary-upload";
1160
1239
  }
1161
1240
  const properties = inputSchema.properties;
1162
- if (!isRecord3(properties)) {
1241
+ if (!isRecord4(properties)) {
1163
1242
  return "flags";
1164
1243
  }
1165
1244
  const values = Object.values(properties);
@@ -1188,7 +1267,7 @@ function stableStringify(value) {
1188
1267
  if (Array.isArray(value)) {
1189
1268
  return `[${value.map(stableStringify).join(",")}]`;
1190
1269
  }
1191
- if (isRecord3(value)) {
1270
+ if (isRecord4(value)) {
1192
1271
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
1193
1272
  }
1194
1273
  return JSON.stringify(value);
@@ -1197,7 +1276,7 @@ function normalizeSchema(schema) {
1197
1276
  return JSON.parse(stableStringify(schema));
1198
1277
  }
1199
1278
  function isScalarSchema(value) {
1200
- if (!isRecord3(value)) {
1279
+ if (!isRecord4(value)) {
1201
1280
  return false;
1202
1281
  }
1203
1282
  const type = value.type;
@@ -1220,7 +1299,7 @@ function isVerb(value) {
1220
1299
  function snakeToKebab(value) {
1221
1300
  return value.replaceAll("_", "-");
1222
1301
  }
1223
- function isRecord3(value) {
1302
+ function isRecord4(value) {
1224
1303
  return typeof value === "object" && value !== null && !Array.isArray(value);
1225
1304
  }
1226
1305
 
@@ -1261,15 +1340,16 @@ async function runStdioBridge(options) {
1261
1340
  `);
1262
1341
  continue;
1263
1342
  }
1264
- writeLine(options.output, failure(request.id, INTERNAL_ERROR, error));
1343
+ writeLine(options.output, failure(request.id, error instanceof RateLimitedError ? RATE_LIMITED_CODE : INTERNAL_ERROR, error));
1265
1344
  }
1266
1345
  }
1267
1346
  }
1268
1347
  var PARSE_ERROR = -32700;
1269
1348
  var INVALID_REQUEST = -32600;
1270
1349
  var INTERNAL_ERROR = -32603;
1350
+ var RATE_LIMITED_CODE = 429;
1271
1351
  function failure(id, code, error) {
1272
- const data = error instanceof McpRpcError ? { code: error.code, ...isRecord4(error.details) ? error.details : {} } : void 0;
1352
+ const data = error instanceof McpRpcError ? { code: error.code, ...isRecord5(error.details) ? error.details : {} } : void 0;
1273
1353
  return {
1274
1354
  jsonrpc: "2.0",
1275
1355
  id,
@@ -1285,7 +1365,7 @@ function writeLine(output, message) {
1285
1365
  }
1286
1366
  function parseJsonRpcRequest(line) {
1287
1367
  const parsed = JSON.parse(line);
1288
- if (!isRecord4(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
1368
+ if (!isRecord5(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
1289
1369
  throw new Error("Invalid JSON-RPC request.");
1290
1370
  }
1291
1371
  return {
@@ -1296,9 +1376,9 @@ function parseJsonRpcRequest(line) {
1296
1376
  };
1297
1377
  }
1298
1378
  function clientInfoName(params) {
1299
- return isRecord4(params) && isRecord4(params.clientInfo) ? params.clientInfo.name : void 0;
1379
+ return isRecord5(params) && isRecord5(params.clientInfo) ? params.clientInfo.name : void 0;
1300
1380
  }
1301
- function isRecord4(value) {
1381
+ function isRecord5(value) {
1302
1382
  return typeof value === "object" && value !== null && !Array.isArray(value);
1303
1383
  }
1304
1384
 
@@ -1338,7 +1418,7 @@ function renderCodexConfig(entry) {
1338
1418
  `;
1339
1419
  }
1340
1420
  function isOurEntry(entry, endpoint) {
1341
- if (isRecord5(entry) && typeof entry.url === "string") {
1421
+ if (isRecord6(entry) && typeof entry.url === "string") {
1342
1422
  const url = normalizedServerUrl(entry.url);
1343
1423
  return url !== void 0 && (url === endpoint || isDefaultEndpoint(url));
1344
1424
  }
@@ -1365,10 +1445,10 @@ function buildServerEntry(input = {}) {
1365
1445
  return { command: "npx", args };
1366
1446
  }
1367
1447
  function entryCarriesRole(entry) {
1368
- return isRecord5(entry) && Array.isArray(entry.args) && entry.args.includes("--role");
1448
+ return isRecord6(entry) && Array.isArray(entry.args) && entry.args.includes("--role");
1369
1449
  }
1370
1450
  function pinnedBridgeVersion(entry) {
1371
- if (!isRecord5(entry) || !Array.isArray(entry.args)) {
1451
+ if (!isRecord6(entry) || !Array.isArray(entry.args)) {
1372
1452
  return void 0;
1373
1453
  }
1374
1454
  const pinned = entry.args.find((arg) => typeof arg === "string" && arg.startsWith(`${PACKAGE_NAME}@`));
@@ -1381,13 +1461,13 @@ function readServerEntry(existing) {
1381
1461
  try {
1382
1462
  const parsed = JSON.parse(existing);
1383
1463
  const servers = parsed?.mcpServers;
1384
- return isRecord5(servers) ? servers[SERVER_NAME] : void 0;
1464
+ return isRecord6(servers) ? servers[SERVER_NAME] : void 0;
1385
1465
  } catch {
1386
1466
  return void 0;
1387
1467
  }
1388
1468
  }
1389
1469
  function isOurBridge(entry) {
1390
- if (!isRecord5(entry)) {
1470
+ if (!isRecord6(entry)) {
1391
1471
  return false;
1392
1472
  }
1393
1473
  const { command, args } = entry;
@@ -1431,7 +1511,7 @@ function readServers(root) {
1431
1511
  if (servers === void 0) {
1432
1512
  return {};
1433
1513
  }
1434
- if (!isRecord5(servers)) {
1514
+ if (!isRecord6(servers)) {
1435
1515
  throw new Error(
1436
1516
  'Existing MCP config has an "mcpServers" value that is not an object, so it was left untouched. Fix it by hand and run init again.'
1437
1517
  );
@@ -1450,7 +1530,7 @@ function parseConfigObject(existing) {
1450
1530
  `Existing MCP config is not valid JSON, so it was left untouched. Fix it by hand and run init again. (${error instanceof Error ? error.message : String(error)})`
1451
1531
  );
1452
1532
  }
1453
- if (!isRecord5(parsed)) {
1533
+ if (!isRecord6(parsed)) {
1454
1534
  throw new Error("Existing MCP config must be a JSON object; it was left untouched.");
1455
1535
  }
1456
1536
  return parsed;
@@ -1459,7 +1539,7 @@ function stringify(value) {
1459
1539
  return `${JSON.stringify(value, null, 2)}
1460
1540
  `;
1461
1541
  }
1462
- function isRecord5(value) {
1542
+ function isRecord6(value) {
1463
1543
  return typeof value === "object" && value !== null && !Array.isArray(value);
1464
1544
  }
1465
1545
 
@@ -1977,7 +2057,7 @@ var enrichments = [
1977
2057
  // src/core/output/table.ts
1978
2058
  function renderTable(data, options = {}) {
1979
2059
  const rows = extractRows(data);
1980
- if (!rows.length || !rows.every(isRecord6)) {
2060
+ if (!rows.length || !rows.every(isRecord7)) {
1981
2061
  return void 0;
1982
2062
  }
1983
2063
  const columns = options.columns?.length ? options.columns : inferColumns(rows);
@@ -1994,7 +2074,7 @@ function extractRows(data) {
1994
2074
  if (Array.isArray(data)) {
1995
2075
  return data;
1996
2076
  }
1997
- if (isRecord6(data)) {
2077
+ if (isRecord7(data)) {
1998
2078
  for (const key of ["items", "projects", "workspaces", "folders", "documents", "tasks", "attachments", "results", "data"]) {
1999
2079
  const value = data[key];
2000
2080
  if (Array.isArray(value)) {
@@ -2022,7 +2102,7 @@ function formatCell(value) {
2022
2102
  }
2023
2103
  return String(value);
2024
2104
  }
2025
- function isRecord6(value) {
2105
+ function isRecord7(value) {
2026
2106
  return typeof value === "object" && value !== null && !Array.isArray(value);
2027
2107
  }
2028
2108
 
@@ -2075,7 +2155,7 @@ function payloadToArgs(value) {
2075
2155
  if (value === void 0 || value === null) {
2076
2156
  return {};
2077
2157
  }
2078
- if (!isRecord7(value)) {
2158
+ if (!isRecord8(value)) {
2079
2159
  throw new CliCommandError("INVALID_PAYLOAD", "Generated command payload must be a JSON object.", { value });
2080
2160
  }
2081
2161
  return { ...value };
@@ -2179,12 +2259,12 @@ function coerceFlagValue(value, schema, key) {
2179
2259
  }
2180
2260
  function getSchemaProperties(schema) {
2181
2261
  const properties = schema.properties;
2182
- if (!isRecord7(properties)) {
2262
+ if (!isRecord8(properties)) {
2183
2263
  return {};
2184
2264
  }
2185
2265
  const result = {};
2186
2266
  for (const [key, value] of Object.entries(properties)) {
2187
- if (isRecord7(value)) {
2267
+ if (isRecord8(value)) {
2188
2268
  result[key] = value;
2189
2269
  }
2190
2270
  }
@@ -2199,7 +2279,7 @@ function requireValue(flag, value) {
2199
2279
  }
2200
2280
  return value;
2201
2281
  }
2202
- function isRecord7(value) {
2282
+ function isRecord8(value) {
2203
2283
  return typeof value === "object" && value !== null && !Array.isArray(value);
2204
2284
  }
2205
2285
 
@@ -2330,10 +2410,10 @@ import { basename as basename2, extname } from "path";
2330
2410
 
2331
2411
  // src/core/mcp/toolPayload.ts
2332
2412
  function unwrapMcpToolPayload(value) {
2333
- if (!isRecord8(value) || !Array.isArray(value.content)) {
2413
+ if (!isRecord9(value) || !Array.isArray(value.content)) {
2334
2414
  return value;
2335
2415
  }
2336
- const text = value.content.map((item) => isRecord8(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
2416
+ const text = value.content.map((item) => isRecord9(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
2337
2417
  if (!text) {
2338
2418
  return value;
2339
2419
  }
@@ -2344,23 +2424,23 @@ function unwrapMcpToolPayload(value) {
2344
2424
  }
2345
2425
  }
2346
2426
  function toolErrorText(value) {
2347
- if (!isRecord8(value) || !Array.isArray(value.content)) {
2427
+ if (!isRecord9(value) || !Array.isArray(value.content)) {
2348
2428
  return "The tool returned an error.";
2349
2429
  }
2350
- const text = value.content.map((item) => isRecord8(item) && typeof item.text === "string" ? item.text : "").filter(Boolean).join("\n");
2430
+ const text = value.content.map((item) => isRecord9(item) && typeof item.text === "string" ? item.text : "").filter(Boolean).join("\n");
2351
2431
  return text || "The tool returned an error.";
2352
2432
  }
2353
- function isRecord8(value) {
2433
+ function isRecord9(value) {
2354
2434
  return typeof value === "object" && value !== null && !Array.isArray(value);
2355
2435
  }
2356
2436
  function extractProjects(value) {
2357
- const candidates = Array.isArray(value) ? value : isRecord8(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
2437
+ const candidates = Array.isArray(value) ? value : isRecord9(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
2358
2438
  const nested = value[key];
2359
2439
  return Array.isArray(nested) ? nested : [];
2360
2440
  }) : [];
2361
2441
  const projects = [];
2362
2442
  for (const candidate of candidates) {
2363
- if (isRecord8(candidate) && typeof candidate.uuid === "string") {
2443
+ if (isRecord9(candidate) && typeof candidate.uuid === "string") {
2364
2444
  projects.push({
2365
2445
  uuid: candidate.uuid,
2366
2446
  name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
@@ -2468,7 +2548,7 @@ async function requestUploadToken(client, metadata, context) {
2468
2548
  requestArgs.ttlSeconds = context.ttlSeconds;
2469
2549
  }
2470
2550
  const response = unwrapMcpToolPayload(await client.toolsCall(context.requestToolName, requestArgs));
2471
- if (!isRecord9(response)) {
2551
+ if (!isRecord10(response)) {
2472
2552
  throw new AttachmentUploadError("UPLOAD_TOKEN_PROTOCOL_ERROR", "request_upload_token response was not an object.");
2473
2553
  }
2474
2554
  const token = response.token;
@@ -2489,8 +2569,9 @@ async function putAttachmentBytes(token, metadata, fetchImpl) {
2489
2569
  const maxAttempts = 3;
2490
2570
  let lastRetryableError;
2491
2571
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
2572
+ let response;
2492
2573
  try {
2493
- const response = await fetchImpl(token.uploadUrl, {
2574
+ response = await fetchImpl(token.uploadUrl, {
2494
2575
  method: "PUT",
2495
2576
  headers: {
2496
2577
  authorization: `Bearer ${token.token}`,
@@ -2498,22 +2579,27 @@ async function putAttachmentBytes(token, metadata, fetchImpl) {
2498
2579
  },
2499
2580
  body: new Uint8Array(metadata.bytes)
2500
2581
  });
2501
- if (shouldRetryResponse(response.status) && attempt < maxAttempts) {
2502
- continue;
2503
- }
2504
- if (!response.ok) {
2505
- throw uploadHttpError(response.status);
2506
- }
2507
- return parsePutResponse(await response.text());
2508
2582
  } catch (error) {
2509
- if (error instanceof AttachmentUploadError) {
2510
- throw error;
2511
- }
2512
2583
  lastRetryableError = error;
2513
- if (attempt === maxAttempts) {
2514
- break;
2515
- }
2584
+ continue;
2585
+ }
2586
+ if (response.status === 429) {
2587
+ throw await uploadRateLimited(response);
2588
+ }
2589
+ if (shouldRetryResponse(response.status) && attempt < maxAttempts) {
2590
+ continue;
2591
+ }
2592
+ if (!response.ok) {
2593
+ throw uploadHttpError(response.status);
2594
+ }
2595
+ let text;
2596
+ try {
2597
+ text = await response.text();
2598
+ } catch (error) {
2599
+ lastRetryableError = error;
2600
+ continue;
2516
2601
  }
2602
+ return parsePutResponse(text);
2517
2603
  }
2518
2604
  throw new AttachmentUploadError("ATTACHMENT_UPLOAD_FAILED", "Attachment upload failed.", {
2519
2605
  cause: lastRetryableError instanceof Error ? lastRetryableError.message : String(lastRetryableError)
@@ -2529,7 +2615,17 @@ function uploadHttpError(status) {
2529
2615
  return new AttachmentUploadError("ATTACHMENT_UPLOAD_HTTP_ERROR", `Attachment upload failed with HTTP ${status}.`, { status });
2530
2616
  }
2531
2617
  function shouldRetryResponse(status) {
2532
- return status === 429 || status >= 500;
2618
+ return status >= 500;
2619
+ }
2620
+ async function uploadRateLimited(response) {
2621
+ let body;
2622
+ try {
2623
+ body = await response.json();
2624
+ } catch {
2625
+ body = void 0;
2626
+ }
2627
+ const details = rateLimitDetails(response.headers.get("retry-after"), body);
2628
+ return new AttachmentUploadError(RATE_LIMITED, rateLimitMessage("Attachment upload", details), details);
2533
2629
  }
2534
2630
  function parsePutResponse(text) {
2535
2631
  let parsed;
@@ -2538,7 +2634,7 @@ function parsePutResponse(text) {
2538
2634
  } catch {
2539
2635
  throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not valid JSON.");
2540
2636
  }
2541
- if (!isRecord9(parsed)) {
2637
+ if (!isRecord10(parsed)) {
2542
2638
  throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not an object.");
2543
2639
  }
2544
2640
  if (typeof parsed.storageKey !== "string") {
@@ -2602,7 +2698,7 @@ var MIME_BY_EXTENSION = {
2602
2698
  ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
2603
2699
  ".zip": "application/zip"
2604
2700
  };
2605
- function isRecord9(value) {
2701
+ function isRecord10(value) {
2606
2702
  return typeof value === "object" && value !== null && !Array.isArray(value);
2607
2703
  }
2608
2704
 
@@ -2834,7 +2930,7 @@ async function agentTodosPoll(options) {
2834
2930
  let after;
2835
2931
  for (let page = 0; page < 20; page += 1) {
2836
2932
  const result = await callTool(options.client, LIST_AGENT_TODOS, { state: ["OPEN"], ...scope, limit: 50, ...after ? { after } : {} });
2837
- const pageItems = Array.isArray(result.items) ? result.items.filter(isRecord10) : [];
2933
+ const pageItems = Array.isArray(result.items) ? result.items.filter(isRecord11) : [];
2838
2934
  items.push(...pageItems);
2839
2935
  const last = pageItems[pageItems.length - 1];
2840
2936
  if (result.hasMore !== true || !last || typeof last.todoId !== "string") break;
@@ -2859,7 +2955,7 @@ async function callTool(client, name, args) {
2859
2955
  throw new CliCommandError("TOOL_ERROR", toolErrorText(raw), { tool: name });
2860
2956
  }
2861
2957
  const payload = unwrapMcpToolPayload(raw);
2862
- if (!isRecord10(payload)) {
2958
+ if (!isRecord11(payload)) {
2863
2959
  throw new CliCommandError("MCP_PROTOCOL_ERROR", `${name} did not return an object.`, { tool: name });
2864
2960
  }
2865
2961
  return payload;
@@ -2934,7 +3030,7 @@ async function readCursorFile(path) {
2934
3030
  if (text === void 0 || !text.trim()) return void 0;
2935
3031
  try {
2936
3032
  const parsed = JSON.parse(text);
2937
- if (parsed?.v !== 1 || !isRecord10(parsed.filter)) throw new Error("shape");
3033
+ if (parsed?.v !== 1 || !isRecord11(parsed.filter)) throw new Error("shape");
2938
3034
  return { ...parsed, filter: normalizeSavedFilter(parsed.filter) };
2939
3035
  } catch {
2940
3036
  throw new CliCommandError("INVALID_CURSOR_FILE", `${path} is not a cursor file written by ainecto notifications poll.`, { cursorFile: path });
@@ -2969,7 +3065,7 @@ async function writeJsonAtomic(path, value) {
2969
3065
  }
2970
3066
  await rename2(temp, path);
2971
3067
  }
2972
- function isRecord10(value) {
3068
+ function isRecord11(value) {
2973
3069
  return typeof value === "object" && value !== null && !Array.isArray(value);
2974
3070
  }
2975
3071
 
@@ -3985,9 +4081,9 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
3985
4081
  }
3986
4082
  const applied = [];
3987
4083
  try {
3988
- const isRecord12 = (path) => !recordFirst && path === HARNESS_RECORD_PATH;
3989
- const recordWrites = plan.writes.filter((write) => isRecord12(write.path));
3990
- const recordDeletes = plan.deletes.filter((path) => isRecord12(path));
4084
+ const isRecord13 = (path) => !recordFirst && path === HARNESS_RECORD_PATH;
4085
+ const recordWrites = plan.writes.filter((write) => isRecord13(write.path));
4086
+ const recordDeletes = plan.deletes.filter((path) => isRecord13(path));
3991
4087
  const createdDirectories = [...plan.record?.createdDirectories ?? []];
3992
4088
  const rememberDirectories = (directories) => {
3993
4089
  for (const directory of directories) {
@@ -4029,8 +4125,8 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
4029
4125
  };
4030
4126
  try {
4031
4127
  await apply(
4032
- orderedWrites(plan.writes.filter((write) => !isRecord12(write.path)), recordFirst),
4033
- plan.deletes.filter((path) => !isRecord12(path))
4128
+ orderedWrites(plan.writes.filter((write) => !isRecord13(write.path)), recordFirst),
4129
+ plan.deletes.filter((path) => !isRecord13(path))
4034
4130
  );
4035
4131
  await recordDirectoriesOrExplain(false);
4036
4132
  await apply(recordWrites, recordDeletes);
@@ -4101,7 +4197,7 @@ async function fileMode(absolutePath) {
4101
4197
  try {
4102
4198
  return (await stat3(absolutePath)).mode & 4095;
4103
4199
  } catch (error) {
4104
- if (isRecord11(error) && error.code === "ENOENT") {
4200
+ if (isRecord12(error) && error.code === "ENOENT") {
4105
4201
  return void 0;
4106
4202
  }
4107
4203
  throw error;
@@ -4144,7 +4240,7 @@ function parseInitArgs(argv) {
4144
4240
  }
4145
4241
  return args;
4146
4242
  }
4147
- function isRecord11(value) {
4243
+ function isRecord12(value) {
4148
4244
  return typeof value === "object" && value !== null && !Array.isArray(value);
4149
4245
  }
4150
4246