@nexrall/code-core 1.4.62 → 1.4.65
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/dist/agent/agentRegistry.d.ts +14 -3
- package/dist/agent/agentRegistry.d.ts.map +1 -1
- package/dist/agent/agentRegistry.js +136 -6
- package/dist/agent/agentTypes.d.ts +51 -1
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +194 -11
- package/dist/agent/backgroundAgents.d.ts +66 -0
- package/dist/agent/backgroundAgents.d.ts.map +1 -0
- package/dist/agent/backgroundAgents.js +145 -0
- package/dist/agent/loop.d.ts +43 -6
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +952 -270
- package/dist/agent/modelCatalogue.d.ts +15 -0
- package/dist/agent/modelCatalogue.d.ts.map +1 -1
- package/dist/agent/modelCatalogue.js +46 -0
- package/dist/agent/peerTransport.d.ts.map +1 -1
- package/dist/agent/peerTransport.js +19 -12
- package/dist/agent/readDedupe.d.ts +15 -0
- package/dist/agent/readDedupe.d.ts.map +1 -0
- package/dist/agent/readDedupe.js +146 -0
- package/dist/agent/toolPrefetch.d.ts +44 -0
- package/dist/agent/toolPrefetch.d.ts.map +1 -0
- package/dist/agent/toolPrefetch.js +101 -0
- package/dist/agent/trust.d.ts +0 -5
- package/dist/agent/trust.d.ts.map +1 -1
- package/dist/agent/trust.js +41 -0
- package/dist/api/client.d.ts +38 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +132 -9
- package/dist/auth/index.d.ts +26 -18
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +45 -44
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/permissions/modePolicy.d.ts +10 -0
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +11 -0
- package/dist/tools/executor.d.ts +36 -0
- package/dist/tools/executor.d.ts.map +1 -1
- package/dist/tools/executor.js +416 -89
- package/dist/tools/tsLangService.d.ts.map +1 -1
- package/dist/tools/tsLangService.js +107 -21
- package/dist/types.d.ts +99 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -1
- package/dist/util/miniYaml.d.ts +10 -0
- package/dist/util/miniYaml.d.ts.map +1 -0
- package/dist/util/miniYaml.js +149 -0
- package/package.json +8 -17
package/dist/api/client.js
CHANGED
|
@@ -5,11 +5,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.API_BASE = void 0;
|
|
7
7
|
exports.chooseFinalContent = chooseFinalContent;
|
|
8
|
+
exports.earlyToolResolveEnabled = earlyToolResolveEnabled;
|
|
9
|
+
exports.gzipRequestsEnabled = gzipRequestsEnabled;
|
|
8
10
|
exports.streamChat = streamChat;
|
|
9
11
|
exports.cancelTurn = cancelTurn;
|
|
10
12
|
exports.revokeRefreshToken = revokeRefreshToken;
|
|
11
13
|
exports.describeAttachment = describeAttachment;
|
|
12
14
|
exports.getBalance = getBalance;
|
|
15
|
+
exports.billingOverrideFields = billingOverrideFields;
|
|
16
|
+
exports.setBillingTarget = setBillingTarget;
|
|
13
17
|
exports.getOrgs = getOrgs;
|
|
14
18
|
exports.getCodeModels = getCodeModels;
|
|
15
19
|
exports.getUsageDaily = getUsageDaily;
|
|
@@ -19,6 +23,7 @@ exports.getUserProfile = getUserProfile;
|
|
|
19
23
|
const eventsource_parser_1 = require("eventsource-parser");
|
|
20
24
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
21
25
|
const crypto_1 = require("crypto");
|
|
26
|
+
const zlib_1 = require("zlib");
|
|
22
27
|
const index_1 = require("../auth/index");
|
|
23
28
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
24
29
|
const DEFAULT_API_BASE = 'https://api.nexrall.com';
|
|
@@ -214,6 +219,20 @@ function isExpiredTokenResponse(status, body) {
|
|
|
214
219
|
const MAX_RETRIES = 5;
|
|
215
220
|
const RETRY_BASE_MS = 1000;
|
|
216
221
|
const RETRY_MAX_MS = 30000;
|
|
222
|
+
/**
|
|
223
|
+
* Whether a tool_use round may return as soon as message_complete + usage arrive,
|
|
224
|
+
* instead of waiting for the trailing `done` (which the backend only sends after its
|
|
225
|
+
* billing DB write). NEXRALL_EARLY_TOOL_RESOLVE=0 restores the old wait.
|
|
226
|
+
*/
|
|
227
|
+
function earlyToolResolveEnabled() {
|
|
228
|
+
return process.env.NEXRALL_EARLY_TOOL_RESOLVE !== '0';
|
|
229
|
+
}
|
|
230
|
+
/** Chat bodies smaller than this are sent uncompressed. */
|
|
231
|
+
const GZIP_MIN_BYTES = 32 * 1024;
|
|
232
|
+
/** NEXRALL_GZIP_REQUESTS=0 sends chat bodies uncompressed. */
|
|
233
|
+
function gzipRequestsEnabled() {
|
|
234
|
+
return process.env.NEXRALL_GZIP_REQUESTS !== '0';
|
|
235
|
+
}
|
|
217
236
|
/**
|
|
218
237
|
* SSE frames that are backend HOUSEKEEPING, not model output.
|
|
219
238
|
*
|
|
@@ -247,6 +266,7 @@ const RETRY_MAX_MS = 30000;
|
|
|
247
266
|
*/
|
|
248
267
|
const HOUSEKEEPING_FRAMES = new Set([
|
|
249
268
|
'resumable',
|
|
269
|
+
'runtime_context',
|
|
250
270
|
'context_warning',
|
|
251
271
|
'tool_progress',
|
|
252
272
|
]);
|
|
@@ -371,7 +391,7 @@ function sleep(ms) {
|
|
|
371
391
|
* the nested product (36) exists to bound pathological cases, not to be reached. */
|
|
372
392
|
const MAX_TOTAL_ATTEMPTS = (MAX_RETRIES + 1) * (MAX_RETRIES + 1);
|
|
373
393
|
async function streamChat(messages, options, onEvent) {
|
|
374
|
-
const { model, env, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents, canBrowseWeb } = options;
|
|
394
|
+
const { model, env, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents, canBrowseWeb, toolAllowlist } = options;
|
|
375
395
|
// IDEMPOTENCY KEY for this logical turn, generated ONCE and reused by every retry
|
|
376
396
|
// below (that reuse is the entire point — a fresh id per attempt would be useless).
|
|
377
397
|
//
|
|
@@ -447,6 +467,26 @@ async function streamChat(messages, options, onEvent) {
|
|
|
447
467
|
// chained one so the header wait can be timed out independently. It stays because
|
|
448
468
|
// this shape is also what an unwrapped fetch would need, and a request with no
|
|
449
469
|
// signal at all would silently become uncancellable if that wrapper were removed.
|
|
470
|
+
// The chat body is serialised (and, when large, gzipped) ONCE per streamChat call and
|
|
471
|
+
// reused by every retry: the payload is identical across attempts, and re-stringifying
|
|
472
|
+
// a multi-MB history per retry is pure waste. Gzip is only applied above a threshold —
|
|
473
|
+
// below it, the CPU cost outweighs the uplink saving. Every Nexrall API host parses
|
|
474
|
+
// bodies with express.json(), which inflates `Content-Encoding: gzip` natively
|
|
475
|
+
// (verified against production). NEXRALL_GZIP_REQUESTS=0 disables it.
|
|
476
|
+
// Keyed by turnId: a restart mints a NEW turnId (prepareRestart), and the cached body
|
|
477
|
+
// must not carry the old one. The billing override is read per call for the same reason
|
|
478
|
+
// the body used to be rebuilt per attempt — it cannot change mid-turn in practice, and a
|
|
479
|
+
// restart (the only path long enough for it to change) rebuilds anyway.
|
|
480
|
+
let chatBody = null;
|
|
481
|
+
const getChatBody = () => {
|
|
482
|
+
if (chatBody && chatBody.turnId === turnId)
|
|
483
|
+
return chatBody;
|
|
484
|
+
const json = JSON.stringify({ messages, model, env, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents, canBrowseWeb, toolAllowlist, ...billingOverrideFields() });
|
|
485
|
+
chatBody = gzipRequestsEnabled() && json.length >= GZIP_MIN_BYTES
|
|
486
|
+
? { data: (0, zlib_1.gzipSync)(json, { level: 5 }), gzip: true, turnId }
|
|
487
|
+
: { data: json, gzip: false, turnId };
|
|
488
|
+
return chatBody;
|
|
489
|
+
};
|
|
450
490
|
const buildFetchArgs = () => {
|
|
451
491
|
const signal = controller.signal;
|
|
452
492
|
if (resuming) {
|
|
@@ -461,12 +501,15 @@ async function streamChat(messages, options, onEvent) {
|
|
|
461
501
|
}
|
|
462
502
|
return [
|
|
463
503
|
`${exports.API_BASE}/api/code/chat`,
|
|
464
|
-
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
504
|
+
(() => {
|
|
505
|
+
const body = getChatBody();
|
|
506
|
+
return {
|
|
507
|
+
method: 'POST',
|
|
508
|
+
headers: body.gzip ? { ...authHeaders(), 'Content-Encoding': 'gzip' } : authHeaders(),
|
|
509
|
+
body: body.data,
|
|
510
|
+
signal,
|
|
511
|
+
};
|
|
512
|
+
})(),
|
|
470
513
|
];
|
|
471
514
|
};
|
|
472
515
|
// A transient mid-stream failure (Anthropic "overloaded_error") arrives as an SSE error
|
|
@@ -1272,6 +1315,28 @@ async function streamChat(messages, options, onEvent) {
|
|
|
1272
1315
|
cache_read_input_tokens: typeof evt.cache_read_input_tokens === 'number' ? evt.cache_read_input_tokens : undefined,
|
|
1273
1316
|
},
|
|
1274
1317
|
});
|
|
1318
|
+
// EARLY RESOLVE ON TOOL ROUNDS. The backend emits message_complete +
|
|
1319
|
+
// usage synchronously, then awaits its billing write (Postgres + Redis)
|
|
1320
|
+
// before sending `done`. For a tool_use turn nothing after this point is
|
|
1321
|
+
// needed to run the tools, so waiting for `done` put a DB round-trip on
|
|
1322
|
+
// the critical path of EVERY tool round. Return now; the socket keeps
|
|
1323
|
+
// draining in the background, so a late balance_status / done / error
|
|
1324
|
+
// still reaches onEvent. Only tool_use: a final (end_turn) answer keeps
|
|
1325
|
+
// waiting so the low-balance nudge lands before the turn is reported done.
|
|
1326
|
+
if (evt.partial !== true &&
|
|
1327
|
+
completedMessage?.stopReason === 'tool_use' &&
|
|
1328
|
+
earlyToolResolveEnabled()) {
|
|
1329
|
+
clearInterval(heartbeatWatchdog);
|
|
1330
|
+
// The watchdog is gone, so bound the background drain ourselves: a
|
|
1331
|
+
// backend that never sends `done` must not pin the socket forever.
|
|
1332
|
+
const drainGuard = setTimeout(() => {
|
|
1333
|
+
stream.destroy?.();
|
|
1334
|
+
}, HEARTBEAT_TIMEOUT_MS);
|
|
1335
|
+
drainGuard.unref?.();
|
|
1336
|
+
stream.once('end', () => clearTimeout(drainGuard));
|
|
1337
|
+
stream.once('close', () => clearTimeout(drainGuard));
|
|
1338
|
+
resolve();
|
|
1339
|
+
}
|
|
1275
1340
|
}
|
|
1276
1341
|
break;
|
|
1277
1342
|
}
|
|
@@ -1324,6 +1389,23 @@ async function streamChat(messages, options, onEvent) {
|
|
|
1324
1389
|
serverResumable = true;
|
|
1325
1390
|
break;
|
|
1326
1391
|
}
|
|
1392
|
+
case 'tool_use_ready': {
|
|
1393
|
+
// A complete tool_use block, sent before the message finishes (see
|
|
1394
|
+
// SSEToolUseReadyEvent). Forwarded as-is; the loop decides what to prefetch.
|
|
1395
|
+
if (typeof evt.id === 'string' && typeof evt.name === 'string') {
|
|
1396
|
+
const input = evt.input && typeof evt.input === 'object' ? evt.input : {};
|
|
1397
|
+
onEvent({ type: 'tool_use_ready', id: evt.id, name: evt.name, input });
|
|
1398
|
+
}
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
case 'runtime_context': {
|
|
1402
|
+
// The exact context block the backend appended to our final message. The
|
|
1403
|
+
// agent loop persists it so the next round carries it in the cached history.
|
|
1404
|
+
if (typeof evt.text === 'string' && evt.text) {
|
|
1405
|
+
onEvent({ type: 'runtime_context', text: evt.text });
|
|
1406
|
+
}
|
|
1407
|
+
break;
|
|
1408
|
+
}
|
|
1327
1409
|
case 'balance_status': {
|
|
1328
1410
|
const balance = typeof evt.balance === 'number' ? evt.balance : 0;
|
|
1329
1411
|
const zero = !!evt.zero;
|
|
@@ -1633,7 +1715,7 @@ async function describeAttachment(kind, data, mediaType, name) {
|
|
|
1633
1715
|
const fetchOnce = () => (0, node_fetch_1.default)(`${exports.API_BASE}/api/code/assets/describe`, {
|
|
1634
1716
|
method: 'POST',
|
|
1635
1717
|
headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
|
1636
|
-
body: JSON.stringify({ kind, data, media_type: mediaType, name,
|
|
1718
|
+
body: JSON.stringify({ kind, data, media_type: mediaType, name, ...billingOverrideFields() }),
|
|
1637
1719
|
});
|
|
1638
1720
|
let response = await fetchOnce();
|
|
1639
1721
|
// Same silent-refresh pattern as every other authenticated call here — an
|
|
@@ -1687,6 +1769,46 @@ async function getBalance() {
|
|
|
1687
1769
|
const data = (await response.json());
|
|
1688
1770
|
return typeof data.balance === 'number' ? data.balance : 0;
|
|
1689
1771
|
}
|
|
1772
|
+
// ─── Billing target ("Bill to", migrations 139 + 140) ────────────────────────
|
|
1773
|
+
/**
|
|
1774
|
+
* The `teamId` field for a request body — present ONLY when this run has an
|
|
1775
|
+
* explicit override (nexrall auth getBillingTeamOverride).
|
|
1776
|
+
*
|
|
1777
|
+
* Omitting the field is what makes the server resolve the ACCOUNT's saved
|
|
1778
|
+
* choice, which is the whole point of v1.1: the web app sets "bill to Acme"
|
|
1779
|
+
* once and this extension obeys it without any local state to keep in sync.
|
|
1780
|
+
* Sending `teamId: null` would mean the opposite — an explicit "personal" that
|
|
1781
|
+
* would override the user's own account setting on every turn.
|
|
1782
|
+
*/
|
|
1783
|
+
function billingOverrideFields() {
|
|
1784
|
+
const override = (0, index_1.getBillingTeamOverride)();
|
|
1785
|
+
return override === undefined ? {} : { teamId: override };
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Save the account's billing target. `null` clears it (personal wallet).
|
|
1789
|
+
*
|
|
1790
|
+
* Server-side since v1.1, so the web app and every CLI/extension share ONE
|
|
1791
|
+
* answer; the server validates membership and rejects a team the caller cannot
|
|
1792
|
+
* actually use, rather than storing a preference the money path would have to
|
|
1793
|
+
* clean up on the next request.
|
|
1794
|
+
*/
|
|
1795
|
+
async function setBillingTarget(teamId) {
|
|
1796
|
+
const response = await (0, node_fetch_1.default)(`${exports.API_BASE}/api/user/billing-target`, {
|
|
1797
|
+
method: 'PUT',
|
|
1798
|
+
headers: authHeaders(),
|
|
1799
|
+
body: JSON.stringify({ teamId }),
|
|
1800
|
+
});
|
|
1801
|
+
if (!response.ok) {
|
|
1802
|
+
let message = `API error ${response.status}`;
|
|
1803
|
+
try {
|
|
1804
|
+
const parsed = (await response.json());
|
|
1805
|
+
if (parsed.error)
|
|
1806
|
+
message = parsed.error;
|
|
1807
|
+
}
|
|
1808
|
+
catch { /* non-JSON body — keep the status message */ }
|
|
1809
|
+
throw new Error(message);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1690
1812
|
/**
|
|
1691
1813
|
* Personal balance + every team the caller belongs to, in one call.
|
|
1692
1814
|
*
|
|
@@ -1716,6 +1838,7 @@ async function getOrgs() {
|
|
|
1716
1838
|
return {
|
|
1717
1839
|
personal: { balance: typeof json.personal?.balance === 'number' ? json.personal.balance : 0 },
|
|
1718
1840
|
teams: Array.isArray(json.teams) ? json.teams : [],
|
|
1841
|
+
billingTargetTeamId: typeof json.billingTargetTeamId === 'number' ? json.billingTargetTeamId : null,
|
|
1719
1842
|
};
|
|
1720
1843
|
}
|
|
1721
1844
|
async function getCodeModels() {
|
|
@@ -1737,7 +1860,7 @@ async function getCodeModels() {
|
|
|
1737
1860
|
const data = (await response.json());
|
|
1738
1861
|
return {
|
|
1739
1862
|
models: Array.isArray(data.models) ? data.models : [],
|
|
1740
|
-
defaultModel: typeof data.defaultModel === 'string' ? data.defaultModel : '
|
|
1863
|
+
defaultModel: typeof data.defaultModel === 'string' ? data.defaultModel : 'deepseek-flash',
|
|
1741
1864
|
};
|
|
1742
1865
|
}
|
|
1743
1866
|
/**
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -26,26 +26,34 @@ export declare function getRefreshToken(): string | null;
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function updateTokens(token: string, refreshToken?: string): void;
|
|
28
28
|
/**
|
|
29
|
-
* The
|
|
30
|
-
*
|
|
29
|
+
* The per-RUN override for which budget pays, or `undefined` for "ask the
|
|
30
|
+
* account's saved choice".
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* The choice itself lives SERVER-side in v1.1 (`users.billing_team_id`, set from
|
|
33
|
+
* Settings > Teams in the web app): one account, one answer, shared by the web
|
|
34
|
+
* app, this extension and the CLI. v1 kept a copy in `~/.nexrall/config.json`
|
|
35
|
+
* and that could not work — the web app had no way to express the same choice,
|
|
36
|
+
* so a company could fund a pool that only ever paid for one product, and two
|
|
37
|
+
* clients could disagree about who was paying.
|
|
36
38
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
39
|
+
* What remains here is only the case where a run must NOT follow the account
|
|
40
|
+
* default:
|
|
41
|
+
*
|
|
42
|
+
* NEXRALL_BILLING_TEAM=personal|null|0 → explicit personal (returns null)
|
|
43
|
+
* NEXRALL_BILLING_TEAM=<team id> → that team (returns the id)
|
|
44
|
+
* NEXRALL_TOKEN set, no NEXRALL_BILLING_TEAM
|
|
45
|
+
* → explicit personal (returns null):
|
|
46
|
+
* CI/headless has no identity of its
|
|
47
|
+
* own, and charging a company's pool
|
|
48
|
+
* from a build job that merely happens
|
|
49
|
+
* to run on someone's machine is worse
|
|
50
|
+
* than the convenience.
|
|
51
|
+
* otherwise → undefined: omit the field and let
|
|
52
|
+
* the server resolve the saved choice.
|
|
44
53
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* stored credentials. Any other fields already in the file are preserved.
|
|
54
|
+
* The distinction between `undefined` and `null` is the wire's, not a nicety:
|
|
55
|
+
* absent means "no opinion" and null means "explicitly personal", which is what
|
|
56
|
+
* lets an override beat a saved team without the two ever being confused.
|
|
49
57
|
*/
|
|
50
|
-
export declare function
|
|
58
|
+
export declare function getBillingTeamOverride(): number | null | undefined;
|
|
51
59
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAiB3C,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGjD;AAED,wBAAgB,QAAQ,IAAI,UAAU,GAAG,IAAI,CAwB5C;AAED,wBAAgB,SAAS,IAAI,IAAI,CAQhC;AAED,wBAAgB,eAAe,IAAI,OAAO,CAGzC;AAED,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAGxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAM/C;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAsBvE;AAID
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAiB3C,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGjD;AAED,wBAAgB,QAAQ,IAAI,UAAU,GAAG,IAAI,CAwB5C;AAED,wBAAgB,SAAS,IAAI,IAAI,CAQhC;AAED,wBAAgB,eAAe,IAAI,OAAO,CAGzC;AAED,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAGxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAM/C;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAsBvE;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,CAiBlE"}
|
package/dist/auth/index.js
CHANGED
|
@@ -40,8 +40,7 @@ exports.isAuthenticated = isAuthenticated;
|
|
|
40
40
|
exports.getToken = getToken;
|
|
41
41
|
exports.getRefreshToken = getRefreshToken;
|
|
42
42
|
exports.updateTokens = updateTokens;
|
|
43
|
-
exports.
|
|
44
|
-
exports.setBillingTeamId = setBillingTeamId;
|
|
43
|
+
exports.getBillingTeamOverride = getBillingTeamOverride;
|
|
45
44
|
const fs = __importStar(require("fs"));
|
|
46
45
|
const path = __importStar(require("path"));
|
|
47
46
|
const os = __importStar(require("os"));
|
|
@@ -152,54 +151,56 @@ function updateTokens(token, refreshToken) {
|
|
|
152
151
|
};
|
|
153
152
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), { mode: 0o600 });
|
|
154
153
|
}
|
|
155
|
-
// ─── Billing target ("Bill to",
|
|
154
|
+
// ─── Billing target ("Bill to", migrations 139 + 140) ────────────────────────
|
|
156
155
|
/**
|
|
157
|
-
* The
|
|
158
|
-
*
|
|
156
|
+
* The per-RUN override for which budget pays, or `undefined` for "ask the
|
|
157
|
+
* account's saved choice".
|
|
159
158
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
159
|
+
* The choice itself lives SERVER-side in v1.1 (`users.billing_team_id`, set from
|
|
160
|
+
* Settings > Teams in the web app): one account, one answer, shared by the web
|
|
161
|
+
* app, this extension and the CLI. v1 kept a copy in `~/.nexrall/config.json`
|
|
162
|
+
* and that could not work — the web app had no way to express the same choice,
|
|
163
|
+
* so a company could fund a pool that only ever paid for one product, and two
|
|
164
|
+
* clients could disagree about who was paying.
|
|
164
165
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*
|
|
166
|
+
* What remains here is only the case where a run must NOT follow the account
|
|
167
|
+
* default:
|
|
168
|
+
*
|
|
169
|
+
* NEXRALL_BILLING_TEAM=personal|null|0 → explicit personal (returns null)
|
|
170
|
+
* NEXRALL_BILLING_TEAM=<team id> → that team (returns the id)
|
|
171
|
+
* NEXRALL_TOKEN set, no NEXRALL_BILLING_TEAM
|
|
172
|
+
* → explicit personal (returns null):
|
|
173
|
+
* CI/headless has no identity of its
|
|
174
|
+
* own, and charging a company's pool
|
|
175
|
+
* from a build job that merely happens
|
|
176
|
+
* to run on someone's machine is worse
|
|
177
|
+
* than the convenience.
|
|
178
|
+
* otherwise → undefined: omit the field and let
|
|
179
|
+
* the server resolve the saved choice.
|
|
176
180
|
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* stored credentials. Any other fields already in the file are preserved.
|
|
181
|
+
* The distinction between `undefined` and `null` is the wire's, not a nicety:
|
|
182
|
+
* absent means "no opinion" and null means "explicitly personal", which is what
|
|
183
|
+
* lets an override beat a saved team without the two ever being confused.
|
|
181
184
|
*/
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
function getBillingTeamOverride() {
|
|
186
|
+
const raw = process.env.NEXRALL_BILLING_TEAM;
|
|
187
|
+
if (raw != null && raw !== '') {
|
|
188
|
+
const trimmed = raw.trim();
|
|
189
|
+
if (/^(personal|null|0)$/i.test(trimmed))
|
|
190
|
+
return null;
|
|
191
|
+
// Decimal digits only: `Number()` would happily read '0x10' as 16, and an
|
|
192
|
+
// env var is typed by a human — a spelling nobody means should not silently
|
|
193
|
+
// resolve to some team.
|
|
194
|
+
if (/^\d+$/.test(trimmed)) {
|
|
195
|
+
const n = Number(trimmed);
|
|
196
|
+
if (Number.isSafeInteger(n) && n > 0)
|
|
197
|
+
return n;
|
|
189
198
|
}
|
|
199
|
+
// Garbage in the env var: fall through to the token rule rather than
|
|
200
|
+
// guessing a team id out of a typo.
|
|
190
201
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (typeof teamId === 'number' && Number.isSafeInteger(teamId) && teamId > 0) {
|
|
195
|
-
next.billingTeamId = teamId;
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
// Removed rather than set to null: an older client reading this file must
|
|
199
|
-
// see exactly what it saw before the field existed ("bill personal"), and
|
|
200
|
-
// `null` in a JSON config is a shape nothing else in this file uses.
|
|
201
|
-
delete next.billingTeamId;
|
|
202
|
-
}
|
|
203
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), { mode: 0o600 });
|
|
202
|
+
if (process.env.NEXRALL_TOKEN)
|
|
203
|
+
return null;
|
|
204
|
+
return undefined;
|
|
204
205
|
}
|
|
205
206
|
//# sourceMappingURL=index.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './commands/loader';
|
|
|
22
22
|
export * from './agent/agentTypes';
|
|
23
23
|
export * from './agent/planMode';
|
|
24
24
|
export * from './agent/agentRegistry';
|
|
25
|
+
export * from './agent/backgroundAgents';
|
|
25
26
|
export * from './permissions/rules';
|
|
26
27
|
export * from './permissions/modePolicy';
|
|
27
28
|
export * from './permissions/bashClassify';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __exportStar(require("./commands/loader"), exports);
|
|
|
38
38
|
__exportStar(require("./agent/agentTypes"), exports);
|
|
39
39
|
__exportStar(require("./agent/planMode"), exports);
|
|
40
40
|
__exportStar(require("./agent/agentRegistry"), exports);
|
|
41
|
+
__exportStar(require("./agent/backgroundAgents"), exports);
|
|
41
42
|
__exportStar(require("./permissions/rules"), exports);
|
|
42
43
|
__exportStar(require("./permissions/modePolicy"), exports);
|
|
43
44
|
__exportStar(require("./permissions/bashClassify"), exports);
|
|
@@ -18,6 +18,16 @@ export interface PolicyInput {
|
|
|
18
18
|
* in two windows. Note `memory_write` is NOT here even though the CLI's old list had it
|
|
19
19
|
* — it mutates durable cross-session state, and listing it under "read-only / safe"
|
|
20
20
|
* meant it silently bypassed plan mode's read-only guarantee.
|
|
21
|
+
*
|
|
22
|
+
* `task` IS here on purpose, to stay in lockstep with `planMode.ts`'s own
|
|
23
|
+
* READ_ONLY_TOOLS: that module already lets `task` through its plan-mode lock
|
|
24
|
+
* because delegating research to a sub-agent is safe — `runAgentLoop` passes
|
|
25
|
+
* `planMode` down, so the sub-agent is under the identical read-only lock and
|
|
26
|
+
* its own write tools are refused one level deeper. Without `task` here too,
|
|
27
|
+
* step 3 of `decide()` (`mode === 'plan' → deny`) would silently re-block a
|
|
28
|
+
* call that `checkPlanMode()` had already deliberately allowed through — the
|
|
29
|
+
* exact "two copies that drift" failure this module exists to prevent, just
|
|
30
|
+
* hitting `task` specifically instead of the read-only tools it was written for.
|
|
21
31
|
*/
|
|
22
32
|
export declare const READ_ONLY_TOOLS: Set<string>;
|
|
23
33
|
/** Tools that write to the filesystem. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/modePolicy.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,kDAAkD;AAClD,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED
|
|
1
|
+
{"version":3,"file":"modePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/modePolicy.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,kDAAkD;AAClD,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,eAAe,aAW1B,CAAC;AAEH,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB,aAG3B,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,UAAU,aAAsD,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAwDvD;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAGtD"}
|
|
@@ -13,6 +13,16 @@ const bashClassify_1 = require("./bashClassify");
|
|
|
13
13
|
* in two windows. Note `memory_write` is NOT here even though the CLI's old list had it
|
|
14
14
|
* — it mutates durable cross-session state, and listing it under "read-only / safe"
|
|
15
15
|
* meant it silently bypassed plan mode's read-only guarantee.
|
|
16
|
+
*
|
|
17
|
+
* `task` IS here on purpose, to stay in lockstep with `planMode.ts`'s own
|
|
18
|
+
* READ_ONLY_TOOLS: that module already lets `task` through its plan-mode lock
|
|
19
|
+
* because delegating research to a sub-agent is safe — `runAgentLoop` passes
|
|
20
|
+
* `planMode` down, so the sub-agent is under the identical read-only lock and
|
|
21
|
+
* its own write tools are refused one level deeper. Without `task` here too,
|
|
22
|
+
* step 3 of `decide()` (`mode === 'plan' → deny`) would silently re-block a
|
|
23
|
+
* call that `checkPlanMode()` had already deliberately allowed through — the
|
|
24
|
+
* exact "two copies that drift" failure this module exists to prevent, just
|
|
25
|
+
* hitting `task` specifically instead of the read-only tools it was written for.
|
|
16
26
|
*/
|
|
17
27
|
exports.READ_ONLY_TOOLS = new Set([
|
|
18
28
|
'read_file', 'list_directory', 'search_files', 'glob', 'grep',
|
|
@@ -24,6 +34,7 @@ exports.READ_ONLY_TOOLS = new Set([
|
|
|
24
34
|
'notebook_read',
|
|
25
35
|
'get_diagnostics', 'go_to_definition', 'find_references',
|
|
26
36
|
'get_symbols', 'get_workspace_symbols', 'get_hover',
|
|
37
|
+
'task',
|
|
27
38
|
]);
|
|
28
39
|
/** Tools that write to the filesystem. */
|
|
29
40
|
exports.FILE_WRITE_TOOLS = new Set([
|
package/dist/tools/executor.d.ts
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import type { ToolResult } from '../types';
|
|
2
2
|
import { type SandboxConfig } from './sandbox';
|
|
3
|
+
/**
|
|
4
|
+
* Cap output from a tool we do not control (MCP servers). Built-in tools all cap their
|
|
5
|
+
* own output, but an MCP result went into the conversation verbatim: one call to a
|
|
6
|
+
* DB/browser/log server could inline megabytes, which is then re-sent (and re-billed as
|
|
7
|
+
* cache writes) on every later round until compaction. Keep HEAD+TAIL inline and spill
|
|
8
|
+
* the full text to a temp file the model can read_file with offset/limit — the same
|
|
9
|
+
* contract as bash's GAP B spill.
|
|
10
|
+
*/
|
|
11
|
+
export declare function capExternalOutput(output: string, label: string, maxChars?: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Async drop-in for the subset of `spawnSync` the search tools use. spawnSync blocked
|
|
14
|
+
* the Node event loop for the whole search: several search_files calls issued in one
|
|
15
|
+
* parallel tool batch ran strictly one after another, and a search started by the
|
|
16
|
+
* mid-stream prefetcher froze SSE parsing and UI rendering until it finished. Result
|
|
17
|
+
* shape mirrors spawnSync's (`status`, `signal`, `error` with code ETIMEDOUT / ENOBUFS)
|
|
18
|
+
* so the callers' existing failure handling is unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export interface CaptureResult {
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
status: number | null;
|
|
24
|
+
signal: NodeJS.Signals | null;
|
|
25
|
+
error?: NodeJS.ErrnoException;
|
|
26
|
+
}
|
|
27
|
+
export declare function runCapture(cmd: string, args: string[], opts: {
|
|
28
|
+
cwd?: string;
|
|
29
|
+
timeout: number;
|
|
30
|
+
maxBuffer: number;
|
|
31
|
+
}): Promise<CaptureResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Glob → anchored RegExp. Scans the pattern once, so the `*` / `?` rules can never
|
|
34
|
+
* rewrite the regex emitted for `**` (the old chained .replace() calls turned
|
|
35
|
+
* `**\/` into `([^/]:[^/]+/)` — every `**` pattern silently matched nothing).
|
|
36
|
+
* Supports `**`, `*`, `?`, `{a,b}` and `[...]` classes.
|
|
37
|
+
*/
|
|
38
|
+
export declare function globToRegex(pattern: string): RegExp;
|
|
3
39
|
export declare function executeTool(name: string, input: Record<string, unknown>, abortSignal?: {
|
|
4
40
|
aborted: boolean;
|
|
5
41
|
}, sandbox?: SandboxConfig, workDir?: string, agentScope?: string, onStream?: (chunk: string) => void, selfPeer?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/tools/executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/tools/executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAokCtE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAmB,GAAG,MAAM,CAcpG;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AACD,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzD,OAAO,CAAC,aAAa,CAAC,CA0CxB;AAsoCD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAqCnD;AAwwBD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,CAAC,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAMlC,QAAQ,CAAC,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtC,OAAO,CAAC,UAAU,CAAC,CAkCrB"}
|