indusagi 0.12.34 → 0.13.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.
- package/dist/agent.js +1247 -184
- package/dist/ai.js +72 -4
- package/dist/capabilities.js +69 -2
- package/dist/cli.js +83 -13
- package/dist/connectors-saas.js +66 -0
- package/dist/index.js +83 -13
- package/dist/interop.js +66 -0
- package/dist/mcp.js +270 -363
- package/dist/react-ink.js +15 -11
- package/dist/shell-app.js +83 -13
- package/dist/smithy.js +69 -2
- package/dist/swarm.js +69 -2
- package/dist/types/capabilities/backends/node-backends.d.ts +3 -1
- package/dist/types/capabilities/files/read-state-gate.d.ts +69 -0
- package/dist/types/capabilities/files/read-state-gate.test.d.ts +14 -0
- package/dist/types/capabilities/kernel/context.d.ts +4 -0
- package/dist/types/capabilities/kernel/index.d.ts +2 -2
- package/dist/types/capabilities/kernel/spec.d.ts +55 -0
- package/dist/types/facade/bot/actions/bash.d.ts +15 -0
- package/dist/types/facade/bot/actions/bash.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/checkpoint.d.ts +49 -0
- package/dist/types/facade/bot/actions/checkpoint.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/edit-utils.d.ts +86 -0
- package/dist/types/facade/bot/actions/edit.d.ts +18 -0
- package/dist/types/facade/bot/actions/edit.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/find.d.ts +2 -0
- package/dist/types/facade/bot/actions/find.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/grep.d.ts +10 -0
- package/dist/types/facade/bot/actions/grep.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/index.d.ts +16 -0
- package/dist/types/facade/bot/actions/read-state.d.ts +83 -0
- package/dist/types/facade/bot/actions/read-state.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/read.d.ts +7 -0
- package/dist/types/facade/bot/actions/read.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/sandbox-backend.d.ts +99 -0
- package/dist/types/facade/bot/actions/sandbox-backend.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/websearch.d.ts +5 -2
- package/dist/types/facade/bot/actions/websearch.test.d.ts +1 -0
- package/dist/types/facade/bot/actions/write.d.ts +15 -0
- package/dist/types/facade/bot/agent-loop.d.ts +10 -0
- package/dist/types/facade/bot/agent-loop.test.d.ts +1 -0
- package/dist/types/facade/bot/agent.d.ts +9 -1
- package/dist/types/facade/bot/permission-gate.test.d.ts +1 -0
- package/dist/types/facade/bot/types.d.ts +60 -0
- package/dist/types/facade/mcp-core/client.d.ts +71 -15
- package/dist/types/facade/mcp-core/client.test.d.ts +18 -0
- package/dist/types/facade/mcp-core/types.d.ts +10 -0
- package/dist/types/facade/ml/adapters/anthropic-retry.test.d.ts +1 -0
- package/dist/types/facade/ml/adapters/anthropic.d.ts +17 -0
- package/dist/types/facade/ml/adapters/simple-options.d.ts +13 -0
- package/dist/types/facade/ml/adapters/simple-options.test.d.ts +1 -0
- package/dist/types/react-ink/components/StatusLine.d.ts +10 -1
- package/dist/types/react-ink/components/ToolEventBlock.d.ts +2 -1
- package/dist/types/react-ink/components/ToolEventBlock.test.d.ts +1 -0
- package/package.json +1 -1
package/dist/ai.js
CHANGED
|
@@ -13199,24 +13199,58 @@ function normalizeProviderError(error) {
|
|
|
13199
13199
|
}
|
|
13200
13200
|
return new SimpleOptionsProviderError("Unknown provider error", "unknown", error);
|
|
13201
13201
|
}
|
|
13202
|
+
function abortReason(signal) {
|
|
13203
|
+
const reason = signal.reason;
|
|
13204
|
+
if (reason instanceof SimpleOptionsProviderError) return reason;
|
|
13205
|
+
if (reason instanceof Error) return new SimpleOptionsProviderError(reason.message, "unknown", reason);
|
|
13206
|
+
return new SimpleOptionsProviderError("Request was aborted", "unknown", reason);
|
|
13207
|
+
}
|
|
13208
|
+
function abortableSleep(ms, signal) {
|
|
13209
|
+
return new Promise((resolve, reject) => {
|
|
13210
|
+
if (signal?.aborted) {
|
|
13211
|
+
reject(abortReason(signal));
|
|
13212
|
+
return;
|
|
13213
|
+
}
|
|
13214
|
+
let onAbort;
|
|
13215
|
+
const timer = setTimeout(() => {
|
|
13216
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
13217
|
+
resolve();
|
|
13218
|
+
}, ms);
|
|
13219
|
+
if (signal) {
|
|
13220
|
+
onAbort = () => {
|
|
13221
|
+
clearTimeout(timer);
|
|
13222
|
+
signal.removeEventListener("abort", onAbort);
|
|
13223
|
+
reject(abortReason(signal));
|
|
13224
|
+
};
|
|
13225
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13226
|
+
}
|
|
13227
|
+
});
|
|
13228
|
+
}
|
|
13202
13229
|
async function executeWithRetry(operation, policy) {
|
|
13203
13230
|
let attempt = 0;
|
|
13204
13231
|
let lastError;
|
|
13205
13232
|
while (attempt < policy.maxAttempts) {
|
|
13233
|
+
if (policy.signal?.aborted) {
|
|
13234
|
+
throw abortReason(policy.signal);
|
|
13235
|
+
}
|
|
13206
13236
|
attempt++;
|
|
13207
13237
|
try {
|
|
13208
13238
|
return await operation();
|
|
13209
13239
|
} catch (error) {
|
|
13210
13240
|
lastError = error;
|
|
13211
13241
|
const normalized = normalizeProviderError(error);
|
|
13242
|
+
if (policy.signal?.aborted) {
|
|
13243
|
+
throw normalized;
|
|
13244
|
+
}
|
|
13212
13245
|
const defaultRetryable = normalized.code === "rate_limit" || normalized.code === "timeout" || normalized.code === "network";
|
|
13213
13246
|
const retryable = policy.shouldRetry ? policy.shouldRetry(error, attempt) : defaultRetryable;
|
|
13214
13247
|
if (!retryable || attempt >= policy.maxAttempts) {
|
|
13215
13248
|
throw normalized;
|
|
13216
13249
|
}
|
|
13217
13250
|
const maxDelay = policy.maxDelayMs ?? Number.MAX_SAFE_INTEGER;
|
|
13218
|
-
const
|
|
13219
|
-
|
|
13251
|
+
const retryAfterMs = policy.getRetryAfterMs?.(error) ?? null;
|
|
13252
|
+
const backoff = retryAfterMs != null ? Math.min(retryAfterMs, maxDelay) : Math.min(policy.baseDelayMs * 2 ** (attempt - 1), maxDelay);
|
|
13253
|
+
await abortableSleep(backoff, policy.signal);
|
|
13220
13254
|
}
|
|
13221
13255
|
}
|
|
13222
13256
|
throw normalizeProviderError(lastError);
|
|
@@ -13692,6 +13726,30 @@ var AnthropicEventReducer = class {
|
|
|
13692
13726
|
calculateCost(this.model, this.output.usage);
|
|
13693
13727
|
}
|
|
13694
13728
|
};
|
|
13729
|
+
function parseAnthropicRetryAfterMs(error) {
|
|
13730
|
+
if (error instanceof Anthropic.APIError) {
|
|
13731
|
+
const header = error.headers?.get?.("retry-after");
|
|
13732
|
+
const seconds = header ? parseInt(header, 10) : Number.NaN;
|
|
13733
|
+
if (!Number.isNaN(seconds) && seconds >= 0) return seconds * 1e3;
|
|
13734
|
+
}
|
|
13735
|
+
return null;
|
|
13736
|
+
}
|
|
13737
|
+
function shouldRetryAnthropic(error, _attempt) {
|
|
13738
|
+
if (error instanceof Anthropic.APIError) {
|
|
13739
|
+
const status = error.status;
|
|
13740
|
+
if (status === 408 || status === 409 || status === 429 || status === 529) return true;
|
|
13741
|
+
if (typeof status === "number" && status >= 500) return true;
|
|
13742
|
+
if (typeof status === "number" && status >= 400 && status < 500) return false;
|
|
13743
|
+
const body = `${error.message} ${JSON.stringify(error.error ?? "")}`.toLowerCase();
|
|
13744
|
+
if (body.includes('"type":"overloaded_error"') || body.includes("overloaded")) return true;
|
|
13745
|
+
return false;
|
|
13746
|
+
}
|
|
13747
|
+
if (error instanceof Error) {
|
|
13748
|
+
const msg = error.message.toLowerCase();
|
|
13749
|
+
return msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("timeout") || msg.includes("timed out") || msg.includes("network") || msg.includes("econnreset") || msg.includes("etimedout") || msg.includes("overloaded");
|
|
13750
|
+
}
|
|
13751
|
+
return false;
|
|
13752
|
+
}
|
|
13695
13753
|
var streamAnthropic = (model, context, options) => {
|
|
13696
13754
|
const stream2 = new AssistantMessageEventStream();
|
|
13697
13755
|
const output = createAssistantMessageOutput(model);
|
|
@@ -13719,8 +13777,16 @@ var streamAnthropic = (model, context, options) => {
|
|
|
13719
13777
|
});
|
|
13720
13778
|
},
|
|
13721
13779
|
{
|
|
13722
|
-
|
|
13723
|
-
|
|
13780
|
+
// Transient retries are NO LONGER gated on signal presence:
|
|
13781
|
+
// the live path always carries a signal, so the old ternary
|
|
13782
|
+
// collapsed to a single attempt = zero retries on 429/529/5xx.
|
|
13783
|
+
// Abort still short-circuits immediately (see executeWithRetry).
|
|
13784
|
+
maxAttempts: 3,
|
|
13785
|
+
baseDelayMs: 500,
|
|
13786
|
+
maxDelayMs: 32e3,
|
|
13787
|
+
signal: options?.signal,
|
|
13788
|
+
shouldRetry: shouldRetryAnthropic,
|
|
13789
|
+
getRetryAfterMs: parseAnthropicRetryAfterMs
|
|
13724
13790
|
}
|
|
13725
13791
|
);
|
|
13726
13792
|
if (options?.signal?.aborted) {
|
|
@@ -19700,6 +19766,7 @@ export {
|
|
|
19700
19766
|
normalizeToolCallId,
|
|
19701
19767
|
normalizeToolName,
|
|
19702
19768
|
openaiCodexOAuthProvider,
|
|
19769
|
+
parseAnthropicRetryAfterMs,
|
|
19703
19770
|
parseStreamingJson,
|
|
19704
19771
|
parseStreamingJsonWithDiagnostics,
|
|
19705
19772
|
processResponsesStream,
|
|
@@ -19719,6 +19786,7 @@ export {
|
|
|
19719
19786
|
retainThoughtSignature,
|
|
19720
19787
|
rotateEnvApiKey,
|
|
19721
19788
|
sanitizeContentString,
|
|
19789
|
+
shouldRetryAnthropic,
|
|
19722
19790
|
stream,
|
|
19723
19791
|
streamAnthropic,
|
|
19724
19792
|
streamAzureOpenAIResponses,
|
package/dist/capabilities.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/capabilities/kernel/spec.ts
|
|
2
|
+
var READ_STATE_HANDLE_KEY = "readState";
|
|
2
3
|
function coerceInput(raw) {
|
|
3
4
|
if (typeof raw === "string") {
|
|
4
5
|
const trimmed = raw.trim();
|
|
@@ -452,7 +453,7 @@ var standardBudget = {
|
|
|
452
453
|
[${omitted} bytes elided to stay within the output ceiling]
|
|
453
454
|
`
|
|
454
455
|
};
|
|
455
|
-
function makeNodeContext(cwd, signal, budget) {
|
|
456
|
+
function makeNodeContext(cwd, signal, budget, framework) {
|
|
456
457
|
if (typeof cwd !== "string" || cwd.length === 0) {
|
|
457
458
|
throw new Error("makeNodeContext requires a non-empty working directory.");
|
|
458
459
|
}
|
|
@@ -461,10 +462,60 @@ function makeNodeContext(cwd, signal, budget) {
|
|
|
461
462
|
fs: nodeFs,
|
|
462
463
|
shell: nodeShell,
|
|
463
464
|
signal: signal ?? neverAborts(),
|
|
464
|
-
budget: budget ?? standardBudget
|
|
465
|
+
budget: budget ?? standardBudget,
|
|
466
|
+
...framework ? { framework } : {}
|
|
465
467
|
};
|
|
466
468
|
}
|
|
467
469
|
|
|
470
|
+
// src/capabilities/files/read-state-gate.ts
|
|
471
|
+
var READ_BEFORE_EDIT_MESSAGE = "File has not been read yet. Read it first before writing to it.";
|
|
472
|
+
var MODIFIED_SINCE_READ_MESSAGE = "File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.";
|
|
473
|
+
function getReadStateHandle(ctx) {
|
|
474
|
+
const bag = ctx.framework;
|
|
475
|
+
if (!bag || typeof bag !== "object") return void 0;
|
|
476
|
+
const candidate = bag[READ_STATE_HANDLE_KEY];
|
|
477
|
+
if (!candidate || typeof candidate !== "object") return void 0;
|
|
478
|
+
const handle = candidate;
|
|
479
|
+
if (typeof handle.get !== "function" || typeof handle.set !== "function" || typeof handle.has !== "function") {
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
return handle;
|
|
483
|
+
}
|
|
484
|
+
async function recordReadState(ctx, absPath, handle) {
|
|
485
|
+
if (!handle) return;
|
|
486
|
+
try {
|
|
487
|
+
const info = await ctx.fs.stat(absPath);
|
|
488
|
+
const record = {
|
|
489
|
+
mtimeMs: Math.floor(info.modifiedMs),
|
|
490
|
+
size: info.size,
|
|
491
|
+
readAt: Date.now()
|
|
492
|
+
};
|
|
493
|
+
handle.set(absPath, record);
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async function enforceReadGate(ctx, absPath, handle) {
|
|
498
|
+
if (!handle) return { ok: true };
|
|
499
|
+
if (!handle.has(absPath)) {
|
|
500
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
501
|
+
}
|
|
502
|
+
const recorded = handle.get(absPath);
|
|
503
|
+
if (!recorded) {
|
|
504
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
505
|
+
}
|
|
506
|
+
let info;
|
|
507
|
+
try {
|
|
508
|
+
info = await ctx.fs.stat(absPath);
|
|
509
|
+
} catch {
|
|
510
|
+
return { ok: true };
|
|
511
|
+
}
|
|
512
|
+
const currentMtime = Math.floor(info.modifiedMs);
|
|
513
|
+
if (currentMtime > recorded.mtimeMs || info.size !== recorded.size) {
|
|
514
|
+
return { ok: false, message: MODIFIED_SINCE_READ_MESSAGE };
|
|
515
|
+
}
|
|
516
|
+
return { ok: true };
|
|
517
|
+
}
|
|
518
|
+
|
|
468
519
|
// src/capabilities/files/read.ts
|
|
469
520
|
var GUTTER_WIDTH = 6;
|
|
470
521
|
var DESCRIPTION = [
|
|
@@ -567,6 +618,7 @@ var readTool = defineTool({
|
|
|
567
618
|
const detail = err instanceof Error ? err.message : String(err);
|
|
568
619
|
return failure(`Could not read ${path}: ${detail}`);
|
|
569
620
|
}
|
|
621
|
+
await recordReadState(ctx, path, getReadStateHandle(ctx));
|
|
570
622
|
const allLines = toLines(text);
|
|
571
623
|
const totalLines = allLines.length;
|
|
572
624
|
if (totalLines === 0) {
|
|
@@ -674,11 +726,19 @@ var writeTool = defineTool({
|
|
|
674
726
|
async run(input, ctx) {
|
|
675
727
|
const path = readPath(input?.path);
|
|
676
728
|
const content = readContent(input?.content);
|
|
729
|
+
const handle = getReadStateHandle(ctx);
|
|
730
|
+
if (handle && await ctx.fs.exists(path)) {
|
|
731
|
+
const gate = await enforceReadGate(ctx, path, handle);
|
|
732
|
+
if (!gate.ok) {
|
|
733
|
+
return asText(gate.message, true);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
677
736
|
const folder = parentDir(path);
|
|
678
737
|
if (folder.length > 0) {
|
|
679
738
|
await ctx.fs.mkdir(folder, { recursive: true });
|
|
680
739
|
}
|
|
681
740
|
await ctx.fs.writeFile(path, content, "utf8");
|
|
741
|
+
await recordReadState(ctx, path, handle);
|
|
682
742
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
683
743
|
const unit = bytes === 1 ? "byte" : "bytes";
|
|
684
744
|
return asText(`Saved ${bytes} ${unit} to ${path}.`);
|
|
@@ -968,6 +1028,11 @@ async function runEdit(input, ctx) {
|
|
|
968
1028
|
if (!info.isFile) {
|
|
969
1029
|
return failure2(`${path} is not a regular file, so it cannot be edited.`);
|
|
970
1030
|
}
|
|
1031
|
+
const handle = getReadStateHandle(ctx);
|
|
1032
|
+
const gate = await enforceReadGate(ctx, path, handle);
|
|
1033
|
+
if (!gate.ok) {
|
|
1034
|
+
return failure2(gate.message);
|
|
1035
|
+
}
|
|
971
1036
|
const before = await ctx.fs.readFile(path, "utf8");
|
|
972
1037
|
const literalHits = countLiteral(before, oldText);
|
|
973
1038
|
if (literalHits > 0) {
|
|
@@ -981,6 +1046,7 @@ async function runEdit(input, ctx) {
|
|
|
981
1046
|
return failure2(`The replacement left ${path} unchanged.`);
|
|
982
1047
|
}
|
|
983
1048
|
await ctx.fs.writeFile(path, after2, "utf8");
|
|
1049
|
+
await recordReadState(ctx, path, handle);
|
|
984
1050
|
return success(path, before, after2, replaceAll ? literalHits : 1);
|
|
985
1051
|
}
|
|
986
1052
|
const spans = findFuzzySpans(before, oldText);
|
|
@@ -1004,6 +1070,7 @@ async function runEdit(input, ctx) {
|
|
|
1004
1070
|
return failure2(`The fuzzy replacement left ${path} unchanged.`);
|
|
1005
1071
|
}
|
|
1006
1072
|
await ctx.fs.writeFile(path, after, "utf8");
|
|
1073
|
+
await recordReadState(ctx, path, handle);
|
|
1007
1074
|
return success(path, before, after, targets.length);
|
|
1008
1075
|
}
|
|
1009
1076
|
var editTool = defineTool({
|
package/dist/cli.js
CHANGED
|
@@ -4637,6 +4637,7 @@ function runErrorThrowable(kind, message) {
|
|
|
4637
4637
|
}
|
|
4638
4638
|
|
|
4639
4639
|
// src/capabilities/kernel/spec.ts
|
|
4640
|
+
var READ_STATE_HANDLE_KEY = "readState";
|
|
4640
4641
|
function coerceInput(raw) {
|
|
4641
4642
|
if (typeof raw === "string") {
|
|
4642
4643
|
const trimmed = raw.trim();
|
|
@@ -5090,7 +5091,7 @@ var standardBudget = {
|
|
|
5090
5091
|
[${omitted} bytes elided to stay within the output ceiling]
|
|
5091
5092
|
`
|
|
5092
5093
|
};
|
|
5093
|
-
function makeNodeContext(cwd, signal, budget) {
|
|
5094
|
+
function makeNodeContext(cwd, signal, budget, framework) {
|
|
5094
5095
|
if (typeof cwd !== "string" || cwd.length === 0) {
|
|
5095
5096
|
throw new Error("makeNodeContext requires a non-empty working directory.");
|
|
5096
5097
|
}
|
|
@@ -5099,10 +5100,60 @@ function makeNodeContext(cwd, signal, budget) {
|
|
|
5099
5100
|
fs: nodeFs,
|
|
5100
5101
|
shell: nodeShell,
|
|
5101
5102
|
signal: signal ?? neverAborts(),
|
|
5102
|
-
budget: budget ?? standardBudget
|
|
5103
|
+
budget: budget ?? standardBudget,
|
|
5104
|
+
...framework ? { framework } : {}
|
|
5103
5105
|
};
|
|
5104
5106
|
}
|
|
5105
5107
|
|
|
5108
|
+
// src/capabilities/files/read-state-gate.ts
|
|
5109
|
+
var READ_BEFORE_EDIT_MESSAGE = "File has not been read yet. Read it first before writing to it.";
|
|
5110
|
+
var MODIFIED_SINCE_READ_MESSAGE = "File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.";
|
|
5111
|
+
function getReadStateHandle(ctx) {
|
|
5112
|
+
const bag = ctx.framework;
|
|
5113
|
+
if (!bag || typeof bag !== "object") return void 0;
|
|
5114
|
+
const candidate = bag[READ_STATE_HANDLE_KEY];
|
|
5115
|
+
if (!candidate || typeof candidate !== "object") return void 0;
|
|
5116
|
+
const handle = candidate;
|
|
5117
|
+
if (typeof handle.get !== "function" || typeof handle.set !== "function" || typeof handle.has !== "function") {
|
|
5118
|
+
return void 0;
|
|
5119
|
+
}
|
|
5120
|
+
return handle;
|
|
5121
|
+
}
|
|
5122
|
+
async function recordReadState(ctx, absPath, handle) {
|
|
5123
|
+
if (!handle) return;
|
|
5124
|
+
try {
|
|
5125
|
+
const info = await ctx.fs.stat(absPath);
|
|
5126
|
+
const record = {
|
|
5127
|
+
mtimeMs: Math.floor(info.modifiedMs),
|
|
5128
|
+
size: info.size,
|
|
5129
|
+
readAt: Date.now()
|
|
5130
|
+
};
|
|
5131
|
+
handle.set(absPath, record);
|
|
5132
|
+
} catch {
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
async function enforceReadGate(ctx, absPath, handle) {
|
|
5136
|
+
if (!handle) return { ok: true };
|
|
5137
|
+
if (!handle.has(absPath)) {
|
|
5138
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
5139
|
+
}
|
|
5140
|
+
const recorded = handle.get(absPath);
|
|
5141
|
+
if (!recorded) {
|
|
5142
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
5143
|
+
}
|
|
5144
|
+
let info;
|
|
5145
|
+
try {
|
|
5146
|
+
info = await ctx.fs.stat(absPath);
|
|
5147
|
+
} catch {
|
|
5148
|
+
return { ok: true };
|
|
5149
|
+
}
|
|
5150
|
+
const currentMtime = Math.floor(info.modifiedMs);
|
|
5151
|
+
if (currentMtime > recorded.mtimeMs || info.size !== recorded.size) {
|
|
5152
|
+
return { ok: false, message: MODIFIED_SINCE_READ_MESSAGE };
|
|
5153
|
+
}
|
|
5154
|
+
return { ok: true };
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5106
5157
|
// src/capabilities/files/read.ts
|
|
5107
5158
|
var GUTTER_WIDTH = 6;
|
|
5108
5159
|
var DESCRIPTION = [
|
|
@@ -5205,6 +5256,7 @@ var readTool = defineTool({
|
|
|
5205
5256
|
const detail = err instanceof Error ? err.message : String(err);
|
|
5206
5257
|
return failure(`Could not read ${path2}: ${detail}`);
|
|
5207
5258
|
}
|
|
5259
|
+
await recordReadState(ctx, path2, getReadStateHandle(ctx));
|
|
5208
5260
|
const allLines = toLines(text);
|
|
5209
5261
|
const totalLines = allLines.length;
|
|
5210
5262
|
if (totalLines === 0) {
|
|
@@ -5312,11 +5364,19 @@ var writeTool = defineTool({
|
|
|
5312
5364
|
async run(input, ctx) {
|
|
5313
5365
|
const path2 = readPath(input?.path);
|
|
5314
5366
|
const content = readContent(input?.content);
|
|
5367
|
+
const handle = getReadStateHandle(ctx);
|
|
5368
|
+
if (handle && await ctx.fs.exists(path2)) {
|
|
5369
|
+
const gate = await enforceReadGate(ctx, path2, handle);
|
|
5370
|
+
if (!gate.ok) {
|
|
5371
|
+
return asText(gate.message, true);
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5315
5374
|
const folder = parentDir(path2);
|
|
5316
5375
|
if (folder.length > 0) {
|
|
5317
5376
|
await ctx.fs.mkdir(folder, { recursive: true });
|
|
5318
5377
|
}
|
|
5319
5378
|
await ctx.fs.writeFile(path2, content, "utf8");
|
|
5379
|
+
await recordReadState(ctx, path2, handle);
|
|
5320
5380
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
5321
5381
|
const unit = bytes === 1 ? "byte" : "bytes";
|
|
5322
5382
|
return asText(`Saved ${bytes} ${unit} to ${path2}.`);
|
|
@@ -5606,6 +5666,11 @@ async function runEdit(input, ctx) {
|
|
|
5606
5666
|
if (!info.isFile) {
|
|
5607
5667
|
return failure2(`${path2} is not a regular file, so it cannot be edited.`);
|
|
5608
5668
|
}
|
|
5669
|
+
const handle = getReadStateHandle(ctx);
|
|
5670
|
+
const gate = await enforceReadGate(ctx, path2, handle);
|
|
5671
|
+
if (!gate.ok) {
|
|
5672
|
+
return failure2(gate.message);
|
|
5673
|
+
}
|
|
5609
5674
|
const before = await ctx.fs.readFile(path2, "utf8");
|
|
5610
5675
|
const literalHits = countLiteral(before, oldText);
|
|
5611
5676
|
if (literalHits > 0) {
|
|
@@ -5619,6 +5684,7 @@ async function runEdit(input, ctx) {
|
|
|
5619
5684
|
return failure2(`The replacement left ${path2} unchanged.`);
|
|
5620
5685
|
}
|
|
5621
5686
|
await ctx.fs.writeFile(path2, after2, "utf8");
|
|
5687
|
+
await recordReadState(ctx, path2, handle);
|
|
5622
5688
|
return success(path2, before, after2, replaceAll ? literalHits : 1);
|
|
5623
5689
|
}
|
|
5624
5690
|
const spans = findFuzzySpans(before, oldText);
|
|
@@ -5642,6 +5708,7 @@ async function runEdit(input, ctx) {
|
|
|
5642
5708
|
return failure2(`The fuzzy replacement left ${path2} unchanged.`);
|
|
5643
5709
|
}
|
|
5644
5710
|
await ctx.fs.writeFile(path2, after, "utf8");
|
|
5711
|
+
await recordReadState(ctx, path2, handle);
|
|
5645
5712
|
return success(path2, before, after, targets.length);
|
|
5646
5713
|
}
|
|
5647
5714
|
var editTool = defineTool({
|
|
@@ -10373,6 +10440,9 @@ function statusMarker(status) {
|
|
|
10373
10440
|
return ">";
|
|
10374
10441
|
}
|
|
10375
10442
|
}
|
|
10443
|
+
function statusColorKey(status) {
|
|
10444
|
+
return status === "error" ? "error" : "text";
|
|
10445
|
+
}
|
|
10376
10446
|
function plainToolText(text) {
|
|
10377
10447
|
return stripAnsi5(text);
|
|
10378
10448
|
}
|
|
@@ -10414,7 +10484,7 @@ function ToolEventBlock({
|
|
|
10414
10484
|
showTitle = true,
|
|
10415
10485
|
status,
|
|
10416
10486
|
summary,
|
|
10417
|
-
theme
|
|
10487
|
+
theme,
|
|
10418
10488
|
title
|
|
10419
10489
|
}) {
|
|
10420
10490
|
const normalizedSummary = summary?.trim();
|
|
@@ -10430,19 +10500,19 @@ function ToolEventBlock({
|
|
|
10430
10500
|
const { visibleLines, hiddenLineCount } = clampContentLines(combinedLines, maxContentLines);
|
|
10431
10501
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom, marginLeft: indent, children: [
|
|
10432
10502
|
showTitle ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
10433
|
-
/* @__PURE__ */ jsx(Text, {
|
|
10434
|
-
/* @__PURE__ */ jsx(Text, {
|
|
10435
|
-
showSummaryInline && normalizedSummary ? /* @__PURE__ */ jsx(Text, {
|
|
10503
|
+
/* @__PURE__ */ jsx(Text, { children: theme.color(statusColorKey(status), plainToolText(`${statusMarker(status)} `)) }),
|
|
10504
|
+
/* @__PURE__ */ jsx(Text, { children: theme.color(statusColorKey(status), plainToolText(title)) }),
|
|
10505
|
+
showSummaryInline && normalizedSummary ? /* @__PURE__ */ jsx(Text, { children: theme.muted(plainToolText(` (${normalizedSummary})`)) }) : null
|
|
10436
10506
|
] }) : null,
|
|
10437
10507
|
visibleLines.map((line, index) => /* @__PURE__ */ jsx(
|
|
10438
10508
|
Box,
|
|
10439
10509
|
{
|
|
10440
10510
|
marginLeft: line.kind === "response" ? showTitle ? 2 : 0 : showTitle ? 4 : 2,
|
|
10441
|
-
children: preformatted ? /* @__PURE__ */ jsx(Text, { children: line.text }) : /* @__PURE__ */ jsx(Text, {
|
|
10511
|
+
children: preformatted ? /* @__PURE__ */ jsx(Text, { children: line.text }) : /* @__PURE__ */ jsx(Text, { children: theme.color("text", plainToolText(line.text)) })
|
|
10442
10512
|
},
|
|
10443
10513
|
`${line.kind}:${index}:${stripAnsi5(line.text)}`
|
|
10444
10514
|
)),
|
|
10445
|
-
hiddenLineCount > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: showTitle ? 4 : 2, children: /* @__PURE__ */ jsx(Text, {
|
|
10515
|
+
hiddenLineCount > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: showTitle ? 4 : 2, children: /* @__PURE__ */ jsx(Text, { children: theme.muted(plainToolText(`... ${hiddenLineCount} more line(s)`)) }) }) : null
|
|
10446
10516
|
] });
|
|
10447
10517
|
}
|
|
10448
10518
|
|
|
@@ -10785,20 +10855,20 @@ function MessageList({
|
|
|
10785
10855
|
}
|
|
10786
10856
|
|
|
10787
10857
|
// src/react-ink/components/StatusLine.tsx
|
|
10788
|
-
function StatusLine({ snapshot, status, theme }) {
|
|
10858
|
+
function StatusLine({ snapshot, status, theme, showBusyText = true }) {
|
|
10789
10859
|
let text = status?.text;
|
|
10790
10860
|
let tone = status?.kind ?? "info";
|
|
10791
10861
|
if (!text) {
|
|
10792
|
-
if (snapshot.isCompacting) {
|
|
10862
|
+
if (showBusyText && snapshot.isCompacting) {
|
|
10793
10863
|
text = "Compacting conversation context...";
|
|
10794
10864
|
tone = "busy";
|
|
10795
|
-
} else if (snapshot.isBashRunning) {
|
|
10865
|
+
} else if (showBusyText && snapshot.isBashRunning) {
|
|
10796
10866
|
text = "Running bash command...";
|
|
10797
10867
|
tone = "busy";
|
|
10798
|
-
} else if (snapshot.pendingToolCallCount > 0 || snapshot.pendingMessageCount > 0) {
|
|
10868
|
+
} else if (showBusyText && (snapshot.pendingToolCallCount > 0 || snapshot.pendingMessageCount > 0)) {
|
|
10799
10869
|
text = "Agent working...";
|
|
10800
10870
|
tone = "busy";
|
|
10801
|
-
} else if (snapshot.isStreaming) {
|
|
10871
|
+
} else if (showBusyText && snapshot.isStreaming) {
|
|
10802
10872
|
text = "Agent working...";
|
|
10803
10873
|
tone = "busy";
|
|
10804
10874
|
} else if (snapshot.error) {
|
package/dist/connectors-saas.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/capabilities/kernel/spec.ts
|
|
2
|
+
var READ_STATE_HANDLE_KEY = "readState";
|
|
2
3
|
function coerceInput(raw) {
|
|
3
4
|
if (typeof raw === "string") {
|
|
4
5
|
const trimmed = raw.trim();
|
|
@@ -450,6 +451,55 @@ var standardBudget = {
|
|
|
450
451
|
`
|
|
451
452
|
};
|
|
452
453
|
|
|
454
|
+
// src/capabilities/files/read-state-gate.ts
|
|
455
|
+
var READ_BEFORE_EDIT_MESSAGE = "File has not been read yet. Read it first before writing to it.";
|
|
456
|
+
var MODIFIED_SINCE_READ_MESSAGE = "File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.";
|
|
457
|
+
function getReadStateHandle(ctx) {
|
|
458
|
+
const bag = ctx.framework;
|
|
459
|
+
if (!bag || typeof bag !== "object") return void 0;
|
|
460
|
+
const candidate = bag[READ_STATE_HANDLE_KEY];
|
|
461
|
+
if (!candidate || typeof candidate !== "object") return void 0;
|
|
462
|
+
const handle = candidate;
|
|
463
|
+
if (typeof handle.get !== "function" || typeof handle.set !== "function" || typeof handle.has !== "function") {
|
|
464
|
+
return void 0;
|
|
465
|
+
}
|
|
466
|
+
return handle;
|
|
467
|
+
}
|
|
468
|
+
async function recordReadState(ctx, absPath, handle) {
|
|
469
|
+
if (!handle) return;
|
|
470
|
+
try {
|
|
471
|
+
const info = await ctx.fs.stat(absPath);
|
|
472
|
+
const record = {
|
|
473
|
+
mtimeMs: Math.floor(info.modifiedMs),
|
|
474
|
+
size: info.size,
|
|
475
|
+
readAt: Date.now()
|
|
476
|
+
};
|
|
477
|
+
handle.set(absPath, record);
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function enforceReadGate(ctx, absPath, handle) {
|
|
482
|
+
if (!handle) return { ok: true };
|
|
483
|
+
if (!handle.has(absPath)) {
|
|
484
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
485
|
+
}
|
|
486
|
+
const recorded = handle.get(absPath);
|
|
487
|
+
if (!recorded) {
|
|
488
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
489
|
+
}
|
|
490
|
+
let info;
|
|
491
|
+
try {
|
|
492
|
+
info = await ctx.fs.stat(absPath);
|
|
493
|
+
} catch {
|
|
494
|
+
return { ok: true };
|
|
495
|
+
}
|
|
496
|
+
const currentMtime = Math.floor(info.modifiedMs);
|
|
497
|
+
if (currentMtime > recorded.mtimeMs || info.size !== recorded.size) {
|
|
498
|
+
return { ok: false, message: MODIFIED_SINCE_READ_MESSAGE };
|
|
499
|
+
}
|
|
500
|
+
return { ok: true };
|
|
501
|
+
}
|
|
502
|
+
|
|
453
503
|
// src/capabilities/files/read.ts
|
|
454
504
|
var GUTTER_WIDTH = 6;
|
|
455
505
|
var DESCRIPTION = [
|
|
@@ -552,6 +602,7 @@ var readTool = defineTool({
|
|
|
552
602
|
const detail = err instanceof Error ? err.message : String(err);
|
|
553
603
|
return failure(`Could not read ${path}: ${detail}`);
|
|
554
604
|
}
|
|
605
|
+
await recordReadState(ctx, path, getReadStateHandle(ctx));
|
|
555
606
|
const allLines = toLines(text);
|
|
556
607
|
const totalLines = allLines.length;
|
|
557
608
|
if (totalLines === 0) {
|
|
@@ -659,11 +710,19 @@ var writeTool = defineTool({
|
|
|
659
710
|
async run(input, ctx) {
|
|
660
711
|
const path = readPath(input?.path);
|
|
661
712
|
const content = readContent(input?.content);
|
|
713
|
+
const handle = getReadStateHandle(ctx);
|
|
714
|
+
if (handle && await ctx.fs.exists(path)) {
|
|
715
|
+
const gate = await enforceReadGate(ctx, path, handle);
|
|
716
|
+
if (!gate.ok) {
|
|
717
|
+
return asText(gate.message, true);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
662
720
|
const folder = parentDir(path);
|
|
663
721
|
if (folder.length > 0) {
|
|
664
722
|
await ctx.fs.mkdir(folder, { recursive: true });
|
|
665
723
|
}
|
|
666
724
|
await ctx.fs.writeFile(path, content, "utf8");
|
|
725
|
+
await recordReadState(ctx, path, handle);
|
|
667
726
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
668
727
|
const unit = bytes === 1 ? "byte" : "bytes";
|
|
669
728
|
return asText(`Saved ${bytes} ${unit} to ${path}.`);
|
|
@@ -953,6 +1012,11 @@ async function runEdit(input, ctx) {
|
|
|
953
1012
|
if (!info.isFile) {
|
|
954
1013
|
return failure2(`${path} is not a regular file, so it cannot be edited.`);
|
|
955
1014
|
}
|
|
1015
|
+
const handle = getReadStateHandle(ctx);
|
|
1016
|
+
const gate = await enforceReadGate(ctx, path, handle);
|
|
1017
|
+
if (!gate.ok) {
|
|
1018
|
+
return failure2(gate.message);
|
|
1019
|
+
}
|
|
956
1020
|
const before = await ctx.fs.readFile(path, "utf8");
|
|
957
1021
|
const literalHits = countLiteral(before, oldText);
|
|
958
1022
|
if (literalHits > 0) {
|
|
@@ -966,6 +1030,7 @@ async function runEdit(input, ctx) {
|
|
|
966
1030
|
return failure2(`The replacement left ${path} unchanged.`);
|
|
967
1031
|
}
|
|
968
1032
|
await ctx.fs.writeFile(path, after2, "utf8");
|
|
1033
|
+
await recordReadState(ctx, path, handle);
|
|
969
1034
|
return success(path, before, after2, replaceAll ? literalHits : 1);
|
|
970
1035
|
}
|
|
971
1036
|
const spans = findFuzzySpans(before, oldText);
|
|
@@ -989,6 +1054,7 @@ async function runEdit(input, ctx) {
|
|
|
989
1054
|
return failure2(`The fuzzy replacement left ${path} unchanged.`);
|
|
990
1055
|
}
|
|
991
1056
|
await ctx.fs.writeFile(path, after, "utf8");
|
|
1057
|
+
await recordReadState(ctx, path, handle);
|
|
992
1058
|
return success(path, before, after, targets.length);
|
|
993
1059
|
}
|
|
994
1060
|
var editTool = defineTool({
|