indusagi 0.12.34 → 0.13.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.
- package/CHANGELOG.md +16 -0
- package/dist/agent.js +1281 -184
- package/dist/ai.js +106 -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/facade/ml/models.generated.d.ts +34 -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/agent.js
CHANGED
|
@@ -3509,6 +3509,23 @@ var MODELS = {
|
|
|
3509
3509
|
},
|
|
3510
3510
|
contextWindow: 204800,
|
|
3511
3511
|
maxTokens: 131072
|
|
3512
|
+
},
|
|
3513
|
+
"MiniMax-M3": {
|
|
3514
|
+
id: "MiniMax-M3",
|
|
3515
|
+
name: "MiniMax-M3",
|
|
3516
|
+
api: "anthropic-messages",
|
|
3517
|
+
provider: "minimax",
|
|
3518
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
3519
|
+
reasoning: true,
|
|
3520
|
+
input: ["text", "image"],
|
|
3521
|
+
cost: {
|
|
3522
|
+
input: 0.6,
|
|
3523
|
+
output: 2.4,
|
|
3524
|
+
cacheRead: 0.12,
|
|
3525
|
+
cacheWrite: 0
|
|
3526
|
+
},
|
|
3527
|
+
contextWindow: 1048576,
|
|
3528
|
+
maxTokens: 512e3
|
|
3512
3529
|
}
|
|
3513
3530
|
},
|
|
3514
3531
|
"minimax-cn": {
|
|
@@ -3579,6 +3596,23 @@ var MODELS = {
|
|
|
3579
3596
|
},
|
|
3580
3597
|
contextWindow: 204800,
|
|
3581
3598
|
maxTokens: 131072
|
|
3599
|
+
},
|
|
3600
|
+
"MiniMax-M3": {
|
|
3601
|
+
id: "MiniMax-M3",
|
|
3602
|
+
name: "MiniMax-M3",
|
|
3603
|
+
api: "anthropic-messages",
|
|
3604
|
+
provider: "minimax-cn",
|
|
3605
|
+
baseUrl: "https://api.minimaxi.com/anthropic",
|
|
3606
|
+
reasoning: true,
|
|
3607
|
+
input: ["text", "image"],
|
|
3608
|
+
cost: {
|
|
3609
|
+
input: 0.6,
|
|
3610
|
+
output: 2.4,
|
|
3611
|
+
cacheRead: 0.12,
|
|
3612
|
+
cacheWrite: 0
|
|
3613
|
+
},
|
|
3614
|
+
contextWindow: 1048576,
|
|
3615
|
+
maxTokens: 512e3
|
|
3582
3616
|
}
|
|
3583
3617
|
},
|
|
3584
3618
|
"mistral": {
|
|
@@ -13006,24 +13040,58 @@ function normalizeProviderError(error) {
|
|
|
13006
13040
|
}
|
|
13007
13041
|
return new SimpleOptionsProviderError("Unknown provider error", "unknown", error);
|
|
13008
13042
|
}
|
|
13043
|
+
function abortReason(signal) {
|
|
13044
|
+
const reason = signal.reason;
|
|
13045
|
+
if (reason instanceof SimpleOptionsProviderError) return reason;
|
|
13046
|
+
if (reason instanceof Error) return new SimpleOptionsProviderError(reason.message, "unknown", reason);
|
|
13047
|
+
return new SimpleOptionsProviderError("Request was aborted", "unknown", reason);
|
|
13048
|
+
}
|
|
13049
|
+
function abortableSleep(ms, signal) {
|
|
13050
|
+
return new Promise((resolve5, reject) => {
|
|
13051
|
+
if (signal?.aborted) {
|
|
13052
|
+
reject(abortReason(signal));
|
|
13053
|
+
return;
|
|
13054
|
+
}
|
|
13055
|
+
let onAbort;
|
|
13056
|
+
const timer = setTimeout(() => {
|
|
13057
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
13058
|
+
resolve5();
|
|
13059
|
+
}, ms);
|
|
13060
|
+
if (signal) {
|
|
13061
|
+
onAbort = () => {
|
|
13062
|
+
clearTimeout(timer);
|
|
13063
|
+
signal.removeEventListener("abort", onAbort);
|
|
13064
|
+
reject(abortReason(signal));
|
|
13065
|
+
};
|
|
13066
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13067
|
+
}
|
|
13068
|
+
});
|
|
13069
|
+
}
|
|
13009
13070
|
async function executeWithRetry(operation, policy) {
|
|
13010
13071
|
let attempt = 0;
|
|
13011
13072
|
let lastError;
|
|
13012
13073
|
while (attempt < policy.maxAttempts) {
|
|
13074
|
+
if (policy.signal?.aborted) {
|
|
13075
|
+
throw abortReason(policy.signal);
|
|
13076
|
+
}
|
|
13013
13077
|
attempt++;
|
|
13014
13078
|
try {
|
|
13015
13079
|
return await operation();
|
|
13016
13080
|
} catch (error) {
|
|
13017
13081
|
lastError = error;
|
|
13018
13082
|
const normalized = normalizeProviderError(error);
|
|
13083
|
+
if (policy.signal?.aborted) {
|
|
13084
|
+
throw normalized;
|
|
13085
|
+
}
|
|
13019
13086
|
const defaultRetryable = normalized.code === "rate_limit" || normalized.code === "timeout" || normalized.code === "network";
|
|
13020
13087
|
const retryable = policy.shouldRetry ? policy.shouldRetry(error, attempt) : defaultRetryable;
|
|
13021
13088
|
if (!retryable || attempt >= policy.maxAttempts) {
|
|
13022
13089
|
throw normalized;
|
|
13023
13090
|
}
|
|
13024
13091
|
const maxDelay = policy.maxDelayMs ?? Number.MAX_SAFE_INTEGER;
|
|
13025
|
-
const
|
|
13026
|
-
|
|
13092
|
+
const retryAfterMs = policy.getRetryAfterMs?.(error) ?? null;
|
|
13093
|
+
const backoff = retryAfterMs != null ? Math.min(retryAfterMs, maxDelay) : Math.min(policy.baseDelayMs * 2 ** (attempt - 1), maxDelay);
|
|
13094
|
+
await abortableSleep(backoff, policy.signal);
|
|
13027
13095
|
}
|
|
13028
13096
|
}
|
|
13029
13097
|
throw normalizeProviderError(lastError);
|
|
@@ -13499,6 +13567,30 @@ var AnthropicEventReducer = class {
|
|
|
13499
13567
|
calculateCost(this.model, this.output.usage);
|
|
13500
13568
|
}
|
|
13501
13569
|
};
|
|
13570
|
+
function parseAnthropicRetryAfterMs(error) {
|
|
13571
|
+
if (error instanceof Anthropic.APIError) {
|
|
13572
|
+
const header = error.headers?.get?.("retry-after");
|
|
13573
|
+
const seconds = header ? parseInt(header, 10) : Number.NaN;
|
|
13574
|
+
if (!Number.isNaN(seconds) && seconds >= 0) return seconds * 1e3;
|
|
13575
|
+
}
|
|
13576
|
+
return null;
|
|
13577
|
+
}
|
|
13578
|
+
function shouldRetryAnthropic(error, _attempt) {
|
|
13579
|
+
if (error instanceof Anthropic.APIError) {
|
|
13580
|
+
const status = error.status;
|
|
13581
|
+
if (status === 408 || status === 409 || status === 429 || status === 529) return true;
|
|
13582
|
+
if (typeof status === "number" && status >= 500) return true;
|
|
13583
|
+
if (typeof status === "number" && status >= 400 && status < 500) return false;
|
|
13584
|
+
const body = `${error.message} ${JSON.stringify(error.error ?? "")}`.toLowerCase();
|
|
13585
|
+
if (body.includes('"type":"overloaded_error"') || body.includes("overloaded")) return true;
|
|
13586
|
+
return false;
|
|
13587
|
+
}
|
|
13588
|
+
if (error instanceof Error) {
|
|
13589
|
+
const msg = error.message.toLowerCase();
|
|
13590
|
+
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");
|
|
13591
|
+
}
|
|
13592
|
+
return false;
|
|
13593
|
+
}
|
|
13502
13594
|
var streamAnthropic = (model, context, options) => {
|
|
13503
13595
|
const stream = new AssistantMessageEventStream();
|
|
13504
13596
|
const output = createAssistantMessageOutput(model);
|
|
@@ -13526,8 +13618,16 @@ var streamAnthropic = (model, context, options) => {
|
|
|
13526
13618
|
});
|
|
13527
13619
|
},
|
|
13528
13620
|
{
|
|
13529
|
-
|
|
13530
|
-
|
|
13621
|
+
// Transient retries are NO LONGER gated on signal presence:
|
|
13622
|
+
// the live path always carries a signal, so the old ternary
|
|
13623
|
+
// collapsed to a single attempt = zero retries on 429/529/5xx.
|
|
13624
|
+
// Abort still short-circuits immediately (see executeWithRetry).
|
|
13625
|
+
maxAttempts: 3,
|
|
13626
|
+
baseDelayMs: 500,
|
|
13627
|
+
maxDelayMs: 32e3,
|
|
13628
|
+
signal: options?.signal,
|
|
13629
|
+
shouldRetry: shouldRetryAnthropic,
|
|
13630
|
+
getRetryAfterMs: parseAnthropicRetryAfterMs
|
|
13531
13631
|
}
|
|
13532
13632
|
);
|
|
13533
13633
|
if (options?.signal?.aborted) {
|
|
@@ -18407,6 +18507,63 @@ var AgentTelemetry = class {
|
|
|
18407
18507
|
// src/facade/bot/agent-loop.ts
|
|
18408
18508
|
var errorHandler = new AgentErrorHandler();
|
|
18409
18509
|
var telemetry = new AgentTelemetry();
|
|
18510
|
+
var READ_ONLY_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
18511
|
+
"read",
|
|
18512
|
+
"ls",
|
|
18513
|
+
"grep",
|
|
18514
|
+
"find",
|
|
18515
|
+
"websearch",
|
|
18516
|
+
"webfetch",
|
|
18517
|
+
"todoread"
|
|
18518
|
+
]);
|
|
18519
|
+
function maxToolConcurrency(override) {
|
|
18520
|
+
if (typeof override === "number" && Number.isFinite(override) && override >= 1) {
|
|
18521
|
+
return Math.floor(override);
|
|
18522
|
+
}
|
|
18523
|
+
const fromEnv = parseInt(
|
|
18524
|
+
process.env.CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY || process.env.INDUS_MAX_TOOL_CONCURRENCY || "",
|
|
18525
|
+
10
|
|
18526
|
+
);
|
|
18527
|
+
return Number.isFinite(fromEnv) && fromEnv >= 1 ? fromEnv : 10;
|
|
18528
|
+
}
|
|
18529
|
+
function isConcurrencySafe(toolCall, tools, readOnly) {
|
|
18530
|
+
if (!readOnly.has(toolCall.name)) return false;
|
|
18531
|
+
return Boolean(tools?.some((candidate) => candidate.name === toolCall.name));
|
|
18532
|
+
}
|
|
18533
|
+
function partitionToolCalls(calls, tools, readOnly) {
|
|
18534
|
+
const batches = [];
|
|
18535
|
+
for (const call of calls) {
|
|
18536
|
+
const concurrent = isConcurrencySafe(call, tools, readOnly);
|
|
18537
|
+
const trailing = batches[batches.length - 1];
|
|
18538
|
+
if (concurrent && trailing && trailing.concurrent) {
|
|
18539
|
+
trailing.calls.push(call);
|
|
18540
|
+
} else {
|
|
18541
|
+
batches.push({ concurrent, calls: [call] });
|
|
18542
|
+
}
|
|
18543
|
+
}
|
|
18544
|
+
return batches;
|
|
18545
|
+
}
|
|
18546
|
+
async function runToolBatchConcurrently(calls, tools, signal, stream, limit, canUseTool) {
|
|
18547
|
+
const results = new Array(calls.length);
|
|
18548
|
+
let next = 0;
|
|
18549
|
+
const poolSize = Math.max(1, Math.min(limit, calls.length));
|
|
18550
|
+
const worker = async () => {
|
|
18551
|
+
for (; ; ) {
|
|
18552
|
+
const index = next++;
|
|
18553
|
+
if (index >= calls.length) return;
|
|
18554
|
+
const toolCall = calls[index];
|
|
18555
|
+
const key = `tool:${toolCall.name}:${toolCall.id}`;
|
|
18556
|
+
telemetry.start(key);
|
|
18557
|
+
results[index] = await executeToolCall(toolCall, tools, signal, stream, {
|
|
18558
|
+
emitMessageEvents: true,
|
|
18559
|
+
canUseTool
|
|
18560
|
+
});
|
|
18561
|
+
telemetry.log(`tool:${toolCall.name}`, telemetry.end(key));
|
|
18562
|
+
}
|
|
18563
|
+
};
|
|
18564
|
+
await Promise.all(Array.from({ length: poolSize }, () => worker()));
|
|
18565
|
+
return results;
|
|
18566
|
+
}
|
|
18410
18567
|
function agentLoop(prompts, context, config, signal, streamFn) {
|
|
18411
18568
|
const stream = createAgentStream();
|
|
18412
18569
|
void (async () => {
|
|
@@ -18496,7 +18653,12 @@ async function runLoop(liveContext, produced, config, signal, stream, streamFn)
|
|
|
18496
18653
|
assistantMessage,
|
|
18497
18654
|
signal,
|
|
18498
18655
|
stream,
|
|
18499
|
-
config.getSteeringMessages
|
|
18656
|
+
config.getSteeringMessages,
|
|
18657
|
+
{
|
|
18658
|
+
readOnlyToolNames: config.readOnlyToolNames ?? READ_ONLY_TOOL_NAMES,
|
|
18659
|
+
maxConcurrency: maxToolConcurrency(config.maxToolConcurrency),
|
|
18660
|
+
canUseTool: config.canUseTool
|
|
18661
|
+
}
|
|
18500
18662
|
);
|
|
18501
18663
|
for (const result of dispatch.toolResults) {
|
|
18502
18664
|
toolResults.push(result);
|
|
@@ -18581,7 +18743,40 @@ async function executeToolCall(toolCall, tools, signal, stream, options) {
|
|
|
18581
18743
|
let isError = false;
|
|
18582
18744
|
try {
|
|
18583
18745
|
if (!matchedTool) throw new Error(`No registered tool named "${toolCall.name}".`);
|
|
18584
|
-
|
|
18746
|
+
let validatedArgs = validateToolArguments(matchedTool, toolCall);
|
|
18747
|
+
if (options.canUseTool) {
|
|
18748
|
+
const decision = await options.canUseTool(toolCall.name, validatedArgs, { signal });
|
|
18749
|
+
if (decision.behavior === "deny") {
|
|
18750
|
+
const denied = {
|
|
18751
|
+
content: [{ type: "text", text: decision.message }],
|
|
18752
|
+
details: {},
|
|
18753
|
+
isError: true
|
|
18754
|
+
};
|
|
18755
|
+
stream.push({
|
|
18756
|
+
type: "tool_execution_end",
|
|
18757
|
+
toolCallId: toolCall.id,
|
|
18758
|
+
toolName: toolCall.name,
|
|
18759
|
+
result: denied,
|
|
18760
|
+
isError: true
|
|
18761
|
+
});
|
|
18762
|
+
const deniedMessage = {
|
|
18763
|
+
role: "toolResult",
|
|
18764
|
+
toolCallId: toolCall.id,
|
|
18765
|
+
toolName: toolCall.name,
|
|
18766
|
+
content: denied.content,
|
|
18767
|
+
details: denied.details,
|
|
18768
|
+
isError: true,
|
|
18769
|
+
timestamp: Date.now()
|
|
18770
|
+
};
|
|
18771
|
+
if (options.emitMessageEvents) {
|
|
18772
|
+
emitMessageStartEnd(stream, deniedMessage);
|
|
18773
|
+
}
|
|
18774
|
+
return deniedMessage;
|
|
18775
|
+
}
|
|
18776
|
+
if (decision.updatedInput !== void 0) {
|
|
18777
|
+
validatedArgs = decision.updatedInput;
|
|
18778
|
+
}
|
|
18779
|
+
}
|
|
18585
18780
|
result = await matchedTool.execute(toolCall.id, validatedArgs, signal, (partialResult) => {
|
|
18586
18781
|
stream.push({
|
|
18587
18782
|
type: "tool_execution_update",
|
|
@@ -18621,16 +18816,30 @@ async function executeToolCall(toolCall, tools, signal, stream, options) {
|
|
|
18621
18816
|
}
|
|
18622
18817
|
return toolResultMessage;
|
|
18623
18818
|
}
|
|
18624
|
-
async function executeToolCalls(tools, assistantMessage, signal, stream, getSteeringMessages) {
|
|
18819
|
+
async function executeToolCalls(tools, assistantMessage, signal, stream, getSteeringMessages, options) {
|
|
18625
18820
|
const requestedCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
|
18821
|
+
const readOnly = options?.readOnlyToolNames ?? READ_ONLY_TOOL_NAMES;
|
|
18822
|
+
const limit = options?.maxConcurrency ?? maxToolConcurrency();
|
|
18823
|
+
const canUseTool = options?.canUseTool;
|
|
18626
18824
|
const results = [];
|
|
18627
18825
|
let steeringMessages;
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
18631
|
-
|
|
18632
|
-
|
|
18633
|
-
|
|
18826
|
+
const batches = partitionToolCalls(requestedCalls, tools, readOnly);
|
|
18827
|
+
let dispatched = 0;
|
|
18828
|
+
for (const batch of batches) {
|
|
18829
|
+
if (batch.concurrent && batch.calls.length > 1) {
|
|
18830
|
+
const batchResults = await runToolBatchConcurrently(batch.calls, tools, signal, stream, limit, canUseTool);
|
|
18831
|
+
results.push(...batchResults);
|
|
18832
|
+
} else {
|
|
18833
|
+
const toolCall = batch.calls[0];
|
|
18834
|
+
telemetry.start(`tool:${toolCall.name}`);
|
|
18835
|
+
const toolResult = await executeToolCall(toolCall, tools, signal, stream, {
|
|
18836
|
+
emitMessageEvents: true,
|
|
18837
|
+
canUseTool
|
|
18838
|
+
});
|
|
18839
|
+
telemetry.log(`tool:${toolCall.name}`, telemetry.end(`tool:${toolCall.name}`));
|
|
18840
|
+
results.push(toolResult);
|
|
18841
|
+
}
|
|
18842
|
+
dispatched += batch.calls.length;
|
|
18634
18843
|
if (!getSteeringMessages) {
|
|
18635
18844
|
continue;
|
|
18636
18845
|
}
|
|
@@ -18639,7 +18848,7 @@ async function executeToolCalls(tools, assistantMessage, signal, stream, getStee
|
|
|
18639
18848
|
continue;
|
|
18640
18849
|
}
|
|
18641
18850
|
steeringMessages = steering;
|
|
18642
|
-
for (const skipped of requestedCalls.slice(
|
|
18851
|
+
for (const skipped of requestedCalls.slice(dispatched)) {
|
|
18643
18852
|
results.push(skipToolCall(skipped, stream));
|
|
18644
18853
|
}
|
|
18645
18854
|
break;
|
|
@@ -18868,6 +19077,7 @@ var LoopConfigFactory = class {
|
|
|
18868
19077
|
convertToLlm: args.convertToLlm,
|
|
18869
19078
|
transformContext: args.transformContext,
|
|
18870
19079
|
getApiKey: args.getApiKey,
|
|
19080
|
+
canUseTool: args.canUseTool,
|
|
18871
19081
|
getSteeringMessages: args.getSteeringMessages,
|
|
18872
19082
|
getFollowUpMessages: args.getFollowUpMessages
|
|
18873
19083
|
};
|
|
@@ -18940,6 +19150,7 @@ var Agent = class {
|
|
|
18940
19150
|
streamFn;
|
|
18941
19151
|
_sessionId;
|
|
18942
19152
|
getApiKey;
|
|
19153
|
+
canUseTool;
|
|
18943
19154
|
runningPrompt;
|
|
18944
19155
|
resolveRunningPrompt;
|
|
18945
19156
|
_thinkingBudgets;
|
|
@@ -18950,6 +19161,7 @@ var Agent = class {
|
|
|
18950
19161
|
this.streamFn = opts.streamFn || streamSimple;
|
|
18951
19162
|
this._sessionId = opts.sessionId;
|
|
18952
19163
|
this.getApiKey = opts.getApiKey;
|
|
19164
|
+
this.canUseTool = opts.canUseTool;
|
|
18953
19165
|
this._thinkingBudgets = opts.thinkingBudgets;
|
|
18954
19166
|
this.queuePolicy = new MessageQueuePolicy(opts.steeringMode || "one-at-a-time", opts.followUpMode || "one-at-a-time");
|
|
18955
19167
|
}
|
|
@@ -19109,6 +19321,7 @@ var Agent = class {
|
|
|
19109
19321
|
convertToLlm: this.convertToLlm,
|
|
19110
19322
|
transformContext: this.transformContext,
|
|
19111
19323
|
getApiKey: this.getApiKey,
|
|
19324
|
+
canUseTool: this.canUseTool,
|
|
19112
19325
|
getSteeringMessages: async () => this.queuePolicy.dequeueSteeringMessages(),
|
|
19113
19326
|
getFollowUpMessages: async () => this.queuePolicy.dequeueFollowUpMessages()
|
|
19114
19327
|
});
|
|
@@ -19421,7 +19634,7 @@ function isToolCall(value) {
|
|
|
19421
19634
|
// src/facade/bot/actions/bash.ts
|
|
19422
19635
|
import { randomBytes } from "node:crypto";
|
|
19423
19636
|
import { createWriteStream, existsSync as existsSync2 } from "node:fs";
|
|
19424
|
-
import { tmpdir } from "node:os";
|
|
19637
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
19425
19638
|
import { join } from "node:path";
|
|
19426
19639
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
19427
19640
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -19500,6 +19713,128 @@ function killProcessTree(pid) {
|
|
|
19500
19713
|
}
|
|
19501
19714
|
}
|
|
19502
19715
|
|
|
19716
|
+
// src/facade/bot/actions/sandbox-backend.ts
|
|
19717
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
19718
|
+
import { tmpdir } from "node:os";
|
|
19719
|
+
function resolveWritableRoots(config, cwd) {
|
|
19720
|
+
const tmp = config.tmpDir ?? tmpdir();
|
|
19721
|
+
const extra = config.writableRoots ?? [];
|
|
19722
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19723
|
+
const roots = [];
|
|
19724
|
+
for (const p of [cwd, tmp, ...extra]) {
|
|
19725
|
+
if (p && !seen.has(p)) {
|
|
19726
|
+
seen.add(p);
|
|
19727
|
+
roots.push(p);
|
|
19728
|
+
}
|
|
19729
|
+
}
|
|
19730
|
+
return roots;
|
|
19731
|
+
}
|
|
19732
|
+
function escapeSeatbeltPath(p) {
|
|
19733
|
+
return p.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
19734
|
+
}
|
|
19735
|
+
function buildSeatbeltProfile(config, cwd) {
|
|
19736
|
+
const roots = resolveWritableRoots(config, cwd);
|
|
19737
|
+
const writeRules = roots.map((p) => `(allow file-write* (subpath "${escapeSeatbeltPath(p)}"))`).join("\n");
|
|
19738
|
+
const networkRule = config.allowNetwork ? "(allow network*)" : "(deny network* (with no-log))";
|
|
19739
|
+
return [
|
|
19740
|
+
"(version 1)",
|
|
19741
|
+
"(deny default)",
|
|
19742
|
+
// Allow the command and its children to run.
|
|
19743
|
+
"(allow process-exec)",
|
|
19744
|
+
"(allow process-fork)",
|
|
19745
|
+
"(allow signal (target same-sandbox))",
|
|
19746
|
+
"(allow sysctl-read)",
|
|
19747
|
+
// Read is permitted everywhere; the boundary we enforce is on writes.
|
|
19748
|
+
"(allow file-read*)",
|
|
19749
|
+
writeRules,
|
|
19750
|
+
networkRule
|
|
19751
|
+
].filter((line) => line.length > 0).join("\n");
|
|
19752
|
+
}
|
|
19753
|
+
function buildSeatbeltArgv(config, cwd, command) {
|
|
19754
|
+
const profile = buildSeatbeltProfile(config, cwd);
|
|
19755
|
+
return ["sandbox-exec", "-p", profile, "/bin/sh", "-c", command];
|
|
19756
|
+
}
|
|
19757
|
+
function buildBwrapArgv(config, cwd, command) {
|
|
19758
|
+
const roots = resolveWritableRoots(config, cwd);
|
|
19759
|
+
const argv = ["bwrap", "--ro-bind", "/", "/"];
|
|
19760
|
+
for (const root of roots) {
|
|
19761
|
+
argv.push("--bind", root, root);
|
|
19762
|
+
}
|
|
19763
|
+
argv.push("--dev", "/dev", "--proc", "/proc");
|
|
19764
|
+
if (!config.allowNetwork) {
|
|
19765
|
+
argv.push("--unshare-net");
|
|
19766
|
+
}
|
|
19767
|
+
argv.push("--chdir", cwd, "/bin/sh", "-c", command);
|
|
19768
|
+
return argv;
|
|
19769
|
+
}
|
|
19770
|
+
function binaryOnPath(binary) {
|
|
19771
|
+
try {
|
|
19772
|
+
const result = spawnSync2("/bin/sh", ["-c", `command -v ${binary}`], {
|
|
19773
|
+
encoding: "utf8",
|
|
19774
|
+
timeout: 5e3
|
|
19775
|
+
});
|
|
19776
|
+
return result.status === 0 && typeof result.stdout === "string" && result.stdout.trim().length > 0;
|
|
19777
|
+
} catch {
|
|
19778
|
+
return false;
|
|
19779
|
+
}
|
|
19780
|
+
}
|
|
19781
|
+
function sandboxAvailability(platform = process.platform, probe = binaryOnPath) {
|
|
19782
|
+
if (platform === "darwin") {
|
|
19783
|
+
const binaryPresent = probe("sandbox-exec");
|
|
19784
|
+
return {
|
|
19785
|
+
platform: "macos",
|
|
19786
|
+
binary: "sandbox-exec",
|
|
19787
|
+
binaryPresent,
|
|
19788
|
+
reason: binaryPresent ? void 0 : "sandbox-exec not found on PATH"
|
|
19789
|
+
};
|
|
19790
|
+
}
|
|
19791
|
+
if (platform === "linux") {
|
|
19792
|
+
const binaryPresent = probe("bwrap");
|
|
19793
|
+
return {
|
|
19794
|
+
platform: "linux",
|
|
19795
|
+
binary: "bwrap",
|
|
19796
|
+
binaryPresent,
|
|
19797
|
+
reason: binaryPresent ? void 0 : "bwrap (bubblewrap) not found on PATH"
|
|
19798
|
+
};
|
|
19799
|
+
}
|
|
19800
|
+
return {
|
|
19801
|
+
platform: "unsupported",
|
|
19802
|
+
binary: null,
|
|
19803
|
+
binaryPresent: false,
|
|
19804
|
+
reason: `OS sandbox is not supported on platform "${platform}"`
|
|
19805
|
+
};
|
|
19806
|
+
}
|
|
19807
|
+
function buildSandboxArgv(config, cwd, command, availability = sandboxAvailability()) {
|
|
19808
|
+
if (!availability.binaryPresent) return null;
|
|
19809
|
+
if (availability.platform === "macos") return buildSeatbeltArgv(config, cwd, command);
|
|
19810
|
+
if (availability.platform === "linux") return buildBwrapArgv(config, cwd, command);
|
|
19811
|
+
return null;
|
|
19812
|
+
}
|
|
19813
|
+
function shellQuote(token) {
|
|
19814
|
+
return `'${token.replace(/'/g, "'\\''")}'`;
|
|
19815
|
+
}
|
|
19816
|
+
function argvToCommandString(argv) {
|
|
19817
|
+
return argv.map(shellQuote).join(" ");
|
|
19818
|
+
}
|
|
19819
|
+
function createSandboxedBashOperations(config, options) {
|
|
19820
|
+
if (!config.enabled) return options.base;
|
|
19821
|
+
const availability = options.availability ?? sandboxAvailability();
|
|
19822
|
+
return {
|
|
19823
|
+
exec: (command, cwd, execOptions) => {
|
|
19824
|
+
const argv = buildSandboxArgv(config, cwd, command, availability);
|
|
19825
|
+
if (!argv) {
|
|
19826
|
+
options.onNote?.(
|
|
19827
|
+
`sandbox NOT applied (${availability.reason ?? "unavailable"}); ran un-sandboxed`
|
|
19828
|
+
);
|
|
19829
|
+
return options.base.exec(command, cwd, execOptions);
|
|
19830
|
+
}
|
|
19831
|
+
options.onNote?.(`sandbox applied via ${availability.binary}`);
|
|
19832
|
+
const wrapped = argvToCommandString(argv);
|
|
19833
|
+
return options.base.exec(wrapped, cwd, execOptions);
|
|
19834
|
+
}
|
|
19835
|
+
};
|
|
19836
|
+
}
|
|
19837
|
+
|
|
19503
19838
|
// src/facade/bot/actions/truncate.ts
|
|
19504
19839
|
var DEFAULT_MAX_LINES = 2500;
|
|
19505
19840
|
var DEFAULT_MAX_BYTES = 64 * 1024;
|
|
@@ -19687,7 +20022,7 @@ function truncateLine(line, maxChars = GREP_MAX_LINE_LENGTH) {
|
|
|
19687
20022
|
// src/facade/bot/actions/bash.ts
|
|
19688
20023
|
function allocateSpillFilePath() {
|
|
19689
20024
|
const suffix = randomBytes(8).toString("hex");
|
|
19690
|
-
return join(
|
|
20025
|
+
return join(tmpdir2(), `indusvx-bash-${suffix}.log`);
|
|
19691
20026
|
}
|
|
19692
20027
|
var BashCommandBuilder = class {
|
|
19693
20028
|
constructor(prefix) {
|
|
@@ -19864,9 +20199,15 @@ ${note}` : note);
|
|
|
19864
20199
|
}
|
|
19865
20200
|
};
|
|
19866
20201
|
var DEFAULT_BLOCKED_PATTERNS = [/\brm\s+-rf\s+\/$/, /:\(\)\s*\{\s*:\|:\s*&\s*\};:/];
|
|
20202
|
+
var DEFAULT_BASH_TIMEOUT_SECONDS = 120;
|
|
20203
|
+
var MAX_BASH_TIMEOUT_SECONDS = 600;
|
|
20204
|
+
function computeEffectiveTimeout(requested, def, max) {
|
|
20205
|
+
const base = requested !== void 0 && requested > 0 ? requested : def;
|
|
20206
|
+
return Math.min(base, max);
|
|
20207
|
+
}
|
|
19867
20208
|
var bashSchema = Type2.Object({
|
|
19868
20209
|
command: Type2.String({ description: "The bash command line to execute" }),
|
|
19869
|
-
timeout: Type2.Optional(Type2.Number({ description:
|
|
20210
|
+
timeout: Type2.Optional(Type2.Number({ description: `Optional limit, in seconds, after which the command is terminated (default ${DEFAULT_BASH_TIMEOUT_SECONDS}s, max ${MAX_BASH_TIMEOUT_SECONDS}s).` }))
|
|
19870
20211
|
});
|
|
19871
20212
|
var defaultBashOperations = {
|
|
19872
20213
|
exec: (command, cwd, { onData, signal, timeout, env }) => {
|
|
@@ -19932,21 +20273,28 @@ var defaultBashOperations = {
|
|
|
19932
20273
|
}
|
|
19933
20274
|
};
|
|
19934
20275
|
function createBashTool(cwd, options) {
|
|
19935
|
-
const
|
|
20276
|
+
const baseOps = options?.operations ?? defaultBashOperations;
|
|
20277
|
+
const ops = options?.sandbox?.enabled ? createSandboxedBashOperations(options.sandbox, {
|
|
20278
|
+
base: baseOps,
|
|
20279
|
+
onNote: options.onSandboxNote
|
|
20280
|
+
}) : baseOps;
|
|
19936
20281
|
const commandBuilder = new BashCommandBuilder(options?.commandPrefix);
|
|
19937
20282
|
const securityPolicy = new BashSecurityPolicy(
|
|
19938
20283
|
options?.security?.blockedPatterns ?? DEFAULT_BLOCKED_PATTERNS,
|
|
19939
20284
|
options?.commandHistory
|
|
19940
20285
|
);
|
|
19941
20286
|
const hookRunner = options?.hookRunner;
|
|
20287
|
+
const defaultTimeoutSeconds = options?.defaultTimeoutSeconds ?? DEFAULT_BASH_TIMEOUT_SECONDS;
|
|
20288
|
+
const maxTimeoutSeconds = Math.max(options?.maxTimeoutSeconds ?? MAX_BASH_TIMEOUT_SECONDS, defaultTimeoutSeconds);
|
|
19942
20289
|
return {
|
|
19943
20290
|
name: "bash",
|
|
19944
20291
|
label: "bash",
|
|
19945
|
-
description: `Execute a bash command in the current working directory and return its merged stdout and stderr. At most the final ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB are returned, whichever limit is hit first; once that occurs the complete output is saved to a temp file. An optional timeout in seconds may be supplied.`,
|
|
20292
|
+
description: `Execute a bash command in the current working directory and return its merged stdout and stderr. At most the final ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB are returned, whichever limit is hit first; once that occurs the complete output is saved to a temp file. An optional timeout in seconds may be supplied. Commands time out after ${defaultTimeoutSeconds}s by default (max ${maxTimeoutSeconds}s).`,
|
|
19946
20293
|
parameters: bashSchema,
|
|
19947
20294
|
execute: async (_toolCallId, { command, timeout }, signal, onUpdate) => {
|
|
19948
20295
|
securityPolicy.assertAllowed(command);
|
|
19949
20296
|
securityPolicy.record(command);
|
|
20297
|
+
const effectiveTimeout = computeEffectiveTimeout(timeout, defaultTimeoutSeconds, maxTimeoutSeconds);
|
|
19950
20298
|
const resolvedCommand = commandBuilder.build(command);
|
|
19951
20299
|
const hookEnv = hookRunner?.hasHandlers("shell.env") ? (await hookRunner.trigger("shell.env", { cwd }, { env: {} })).env : void 0;
|
|
19952
20300
|
const safeEnv = hookEnv ? Object.fromEntries(
|
|
@@ -19958,7 +20306,7 @@ function createBashTool(cwd, options) {
|
|
|
19958
20306
|
ops.exec(resolvedCommand, cwd, {
|
|
19959
20307
|
onData: (data) => outputCapture.appendChunk(data, emitPartial),
|
|
19960
20308
|
signal,
|
|
19961
|
-
timeout,
|
|
20309
|
+
timeout: effectiveTimeout,
|
|
19962
20310
|
env: safeEnv
|
|
19963
20311
|
}).then(({ exitCode }) => {
|
|
19964
20312
|
outputCapture.close();
|
|
@@ -20291,6 +20639,216 @@ async function computeEditDiff(path9, oldText, newText, cwd) {
|
|
|
20291
20639
|
return computation.compute(path9, oldText, newText);
|
|
20292
20640
|
}
|
|
20293
20641
|
|
|
20642
|
+
// src/facade/bot/actions/edit-utils.ts
|
|
20643
|
+
import { readdirSync } from "node:fs";
|
|
20644
|
+
import { realpath, stat } from "node:fs/promises";
|
|
20645
|
+
import { basename, dirname, extname, join as join2, relative, sep } from "node:path";
|
|
20646
|
+
var LEFT_SINGLE_CURLY_QUOTE = "\u2018";
|
|
20647
|
+
var RIGHT_SINGLE_CURLY_QUOTE = "\u2019";
|
|
20648
|
+
var LEFT_DOUBLE_CURLY_QUOTE = "\u201C";
|
|
20649
|
+
var RIGHT_DOUBLE_CURLY_QUOTE = "\u201D";
|
|
20650
|
+
function isOpeningContext(chars, index) {
|
|
20651
|
+
if (index === 0) {
|
|
20652
|
+
return true;
|
|
20653
|
+
}
|
|
20654
|
+
const prev = chars[index - 1];
|
|
20655
|
+
return prev === " " || prev === " " || prev === "\n" || prev === "\r" || prev === "(" || prev === "[" || prev === "{" || prev === "\u2014" || // em dash
|
|
20656
|
+
prev === "\u2013";
|
|
20657
|
+
}
|
|
20658
|
+
function applyCurlyDoubleQuotes(str) {
|
|
20659
|
+
const chars = [...str];
|
|
20660
|
+
const result = [];
|
|
20661
|
+
for (let i = 0; i < chars.length; i++) {
|
|
20662
|
+
if (chars[i] === '"') {
|
|
20663
|
+
result.push(isOpeningContext(chars, i) ? LEFT_DOUBLE_CURLY_QUOTE : RIGHT_DOUBLE_CURLY_QUOTE);
|
|
20664
|
+
} else {
|
|
20665
|
+
result.push(chars[i]);
|
|
20666
|
+
}
|
|
20667
|
+
}
|
|
20668
|
+
return result.join("");
|
|
20669
|
+
}
|
|
20670
|
+
function applyCurlySingleQuotes(str) {
|
|
20671
|
+
const chars = [...str];
|
|
20672
|
+
const result = [];
|
|
20673
|
+
for (let i = 0; i < chars.length; i++) {
|
|
20674
|
+
if (chars[i] === "'") {
|
|
20675
|
+
const prev = i > 0 ? chars[i - 1] : void 0;
|
|
20676
|
+
const next = i < chars.length - 1 ? chars[i + 1] : void 0;
|
|
20677
|
+
const prevIsLetter = prev !== void 0 && new RegExp("\\p{L}", "u").test(prev);
|
|
20678
|
+
const nextIsLetter = next !== void 0 && new RegExp("\\p{L}", "u").test(next);
|
|
20679
|
+
if (prevIsLetter && nextIsLetter) {
|
|
20680
|
+
result.push(RIGHT_SINGLE_CURLY_QUOTE);
|
|
20681
|
+
} else {
|
|
20682
|
+
result.push(isOpeningContext(chars, i) ? LEFT_SINGLE_CURLY_QUOTE : RIGHT_SINGLE_CURLY_QUOTE);
|
|
20683
|
+
}
|
|
20684
|
+
} else {
|
|
20685
|
+
result.push(chars[i]);
|
|
20686
|
+
}
|
|
20687
|
+
}
|
|
20688
|
+
return result.join("");
|
|
20689
|
+
}
|
|
20690
|
+
function preserveQuoteStyle(oldString, actualOldString, newString) {
|
|
20691
|
+
if (oldString === actualOldString) {
|
|
20692
|
+
return newString;
|
|
20693
|
+
}
|
|
20694
|
+
const hasDoubleQuotes = actualOldString.includes(LEFT_DOUBLE_CURLY_QUOTE) || actualOldString.includes(RIGHT_DOUBLE_CURLY_QUOTE);
|
|
20695
|
+
const hasSingleQuotes = actualOldString.includes(LEFT_SINGLE_CURLY_QUOTE) || actualOldString.includes(RIGHT_SINGLE_CURLY_QUOTE);
|
|
20696
|
+
if (!hasDoubleQuotes && !hasSingleQuotes) {
|
|
20697
|
+
return newString;
|
|
20698
|
+
}
|
|
20699
|
+
let result = newString;
|
|
20700
|
+
if (hasDoubleQuotes) {
|
|
20701
|
+
result = applyCurlyDoubleQuotes(result);
|
|
20702
|
+
}
|
|
20703
|
+
if (hasSingleQuotes) {
|
|
20704
|
+
result = applyCurlySingleQuotes(result);
|
|
20705
|
+
}
|
|
20706
|
+
return result;
|
|
20707
|
+
}
|
|
20708
|
+
var DESANITIZATIONS = {
|
|
20709
|
+
"<fnr>": "<function_results>",
|
|
20710
|
+
"<n>": "<name>",
|
|
20711
|
+
"</n>": "</name>",
|
|
20712
|
+
"<o>": "<output>",
|
|
20713
|
+
"</o>": "</output>",
|
|
20714
|
+
"<e>": "<error>",
|
|
20715
|
+
"</e>": "</error>",
|
|
20716
|
+
"<s>": "<system>",
|
|
20717
|
+
"</s>": "</system>",
|
|
20718
|
+
"<r>": "<result>",
|
|
20719
|
+
"</r>": "</result>",
|
|
20720
|
+
"< META_START >": "<META_START>",
|
|
20721
|
+
"< META_END >": "<META_END>",
|
|
20722
|
+
"< EOT >": "<EOT>",
|
|
20723
|
+
"< META >": "<META>",
|
|
20724
|
+
"< SOS >": "<SOS>",
|
|
20725
|
+
"\n\nH:": "\n\nHuman:",
|
|
20726
|
+
"\n\nA:": "\n\nAssistant:"
|
|
20727
|
+
};
|
|
20728
|
+
function desanitizeMatchString(matchString) {
|
|
20729
|
+
let result = matchString;
|
|
20730
|
+
const appliedReplacements = [];
|
|
20731
|
+
for (const [from, to] of Object.entries(DESANITIZATIONS)) {
|
|
20732
|
+
const beforeReplace = result;
|
|
20733
|
+
result = result.split(from).join(to);
|
|
20734
|
+
if (beforeReplace !== result) {
|
|
20735
|
+
appliedReplacements.push({ from, to });
|
|
20736
|
+
}
|
|
20737
|
+
}
|
|
20738
|
+
return { result, appliedReplacements };
|
|
20739
|
+
}
|
|
20740
|
+
function findSimilarFile(filePath) {
|
|
20741
|
+
try {
|
|
20742
|
+
const dir = dirname(filePath);
|
|
20743
|
+
const fileBaseName = basename(filePath, extname(filePath));
|
|
20744
|
+
const files = readdirSync(dir, { withFileTypes: true });
|
|
20745
|
+
const similarFiles = files.filter(
|
|
20746
|
+
(file) => basename(file.name, extname(file.name)) === fileBaseName && join2(dir, file.name) !== filePath
|
|
20747
|
+
);
|
|
20748
|
+
const firstMatch = similarFiles[0];
|
|
20749
|
+
return firstMatch ? firstMatch.name : void 0;
|
|
20750
|
+
} catch {
|
|
20751
|
+
return void 0;
|
|
20752
|
+
}
|
|
20753
|
+
}
|
|
20754
|
+
var FILE_NOT_FOUND_CWD_NOTE = "Note: your current working directory is";
|
|
20755
|
+
async function suggestPathUnderCwd(requestedPath, cwd) {
|
|
20756
|
+
const cwdParent = dirname(cwd);
|
|
20757
|
+
let resolvedPath = requestedPath;
|
|
20758
|
+
try {
|
|
20759
|
+
const resolvedDir = await realpath(dirname(requestedPath));
|
|
20760
|
+
resolvedPath = join2(resolvedDir, basename(requestedPath));
|
|
20761
|
+
} catch {
|
|
20762
|
+
}
|
|
20763
|
+
const cwdParentPrefix = cwdParent === sep ? sep : cwdParent + sep;
|
|
20764
|
+
if (!resolvedPath.startsWith(cwdParentPrefix) || resolvedPath.startsWith(cwd + sep) || resolvedPath === cwd) {
|
|
20765
|
+
return void 0;
|
|
20766
|
+
}
|
|
20767
|
+
const relFromParent = relative(cwdParent, resolvedPath);
|
|
20768
|
+
const correctedPath = join2(cwd, relFromParent);
|
|
20769
|
+
try {
|
|
20770
|
+
await stat(correctedPath);
|
|
20771
|
+
return correctedPath;
|
|
20772
|
+
} catch {
|
|
20773
|
+
return void 0;
|
|
20774
|
+
}
|
|
20775
|
+
}
|
|
20776
|
+
function replaceAllLiteral(haystack, needle, value) {
|
|
20777
|
+
if (needle.length === 0) return haystack;
|
|
20778
|
+
const pieces = [];
|
|
20779
|
+
let from = 0;
|
|
20780
|
+
for (; ; ) {
|
|
20781
|
+
const at = haystack.indexOf(needle, from);
|
|
20782
|
+
if (at === -1) {
|
|
20783
|
+
pieces.push(haystack.slice(from));
|
|
20784
|
+
break;
|
|
20785
|
+
}
|
|
20786
|
+
pieces.push(haystack.slice(from, at), value);
|
|
20787
|
+
from = at + needle.length;
|
|
20788
|
+
}
|
|
20789
|
+
return pieces.join("");
|
|
20790
|
+
}
|
|
20791
|
+
|
|
20792
|
+
// src/facade/bot/actions/checkpoint.ts
|
|
20793
|
+
import { readFileSync, statSync } from "node:fs";
|
|
20794
|
+
function recordCheckpoint(handle, absPath) {
|
|
20795
|
+
if (!handle) return;
|
|
20796
|
+
let previous;
|
|
20797
|
+
try {
|
|
20798
|
+
statSync(absPath);
|
|
20799
|
+
previous = readFileSync(absPath, "utf-8");
|
|
20800
|
+
} catch (error) {
|
|
20801
|
+
if (error?.code === "ENOENT") {
|
|
20802
|
+
previous = null;
|
|
20803
|
+
} else {
|
|
20804
|
+
return;
|
|
20805
|
+
}
|
|
20806
|
+
}
|
|
20807
|
+
try {
|
|
20808
|
+
handle.record(absPath, previous);
|
|
20809
|
+
} catch {
|
|
20810
|
+
}
|
|
20811
|
+
}
|
|
20812
|
+
|
|
20813
|
+
// src/facade/bot/actions/read-state.ts
|
|
20814
|
+
import { statSync as statSync2 } from "node:fs";
|
|
20815
|
+
var READ_BEFORE_EDIT_MESSAGE = "File has not been read yet. Read it first before writing to it.";
|
|
20816
|
+
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.";
|
|
20817
|
+
function recordReadState(handle, absPath) {
|
|
20818
|
+
if (!handle) return;
|
|
20819
|
+
try {
|
|
20820
|
+
const info = statSync2(absPath);
|
|
20821
|
+
const mtimeMs = Math.floor(info.mtimeMs);
|
|
20822
|
+
handle.set(absPath, {
|
|
20823
|
+
mtimeMs,
|
|
20824
|
+
size: info.size,
|
|
20825
|
+
readAt: mtimeMs
|
|
20826
|
+
});
|
|
20827
|
+
} catch {
|
|
20828
|
+
}
|
|
20829
|
+
}
|
|
20830
|
+
function enforceReadGate(handle, absPath) {
|
|
20831
|
+
if (!handle) return { ok: true };
|
|
20832
|
+
if (!handle.has(absPath)) {
|
|
20833
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
20834
|
+
}
|
|
20835
|
+
const recorded = handle.get(absPath);
|
|
20836
|
+
if (!recorded) {
|
|
20837
|
+
return { ok: false, message: READ_BEFORE_EDIT_MESSAGE };
|
|
20838
|
+
}
|
|
20839
|
+
let info;
|
|
20840
|
+
try {
|
|
20841
|
+
info = statSync2(absPath);
|
|
20842
|
+
} catch {
|
|
20843
|
+
return { ok: true };
|
|
20844
|
+
}
|
|
20845
|
+
const currentMtime = Math.floor(info.mtimeMs);
|
|
20846
|
+
if (currentMtime > recorded.mtimeMs || info.size !== recorded.size) {
|
|
20847
|
+
return { ok: false, message: MODIFIED_SINCE_READ_MESSAGE };
|
|
20848
|
+
}
|
|
20849
|
+
return { ok: true };
|
|
20850
|
+
}
|
|
20851
|
+
|
|
20294
20852
|
// src/facade/bot/actions/edit.ts
|
|
20295
20853
|
var ExactThenFuzzyStrategy = class {
|
|
20296
20854
|
find(content, oldText) {
|
|
@@ -20311,7 +20869,12 @@ function rejectWith(message) {
|
|
|
20311
20869
|
var editSchema = Type3.Object({
|
|
20312
20870
|
path: Type3.String({ description: "File to modify, given as a relative or absolute path" }),
|
|
20313
20871
|
oldText: Type3.String({ description: "The current text to locate; it must be reproduced verbatim" }),
|
|
20314
|
-
newText: Type3.String({ description: "The text that should take the place of oldText" })
|
|
20872
|
+
newText: Type3.String({ description: "The text that should take the place of oldText" }),
|
|
20873
|
+
replaceAll: Type3.Optional(
|
|
20874
|
+
Type3.Boolean({
|
|
20875
|
+
description: "Replace every occurrence of oldText instead of requiring a single unique match. Defaults to false, which refuses the edit when oldText appears more than once."
|
|
20876
|
+
})
|
|
20877
|
+
)
|
|
20315
20878
|
});
|
|
20316
20879
|
var defaultEditOperations = {
|
|
20317
20880
|
readFile: (path9) => fsReadFile(path9),
|
|
@@ -20353,38 +20916,76 @@ function withAbort(signal, task) {
|
|
|
20353
20916
|
});
|
|
20354
20917
|
}
|
|
20355
20918
|
var EditSession = class {
|
|
20356
|
-
constructor(input, absolutePath, ops, matchingStrategy) {
|
|
20919
|
+
constructor(input, absolutePath, ops, matchingStrategy, cwd, readState, checkpoint) {
|
|
20357
20920
|
this.input = input;
|
|
20358
20921
|
this.absolutePath = absolutePath;
|
|
20359
20922
|
this.ops = ops;
|
|
20360
20923
|
this.matchingStrategy = matchingStrategy;
|
|
20924
|
+
this.cwd = cwd;
|
|
20925
|
+
this.readState = readState;
|
|
20926
|
+
this.checkpoint = checkpoint;
|
|
20927
|
+
this.replaceAll = input.replaceAll === true;
|
|
20361
20928
|
}
|
|
20362
20929
|
input;
|
|
20363
20930
|
absolutePath;
|
|
20364
20931
|
ops;
|
|
20365
20932
|
matchingStrategy;
|
|
20933
|
+
cwd;
|
|
20934
|
+
readState;
|
|
20935
|
+
checkpoint;
|
|
20936
|
+
/** Whether every occurrence should be swapped (vs. requiring a unique hit). */
|
|
20937
|
+
replaceAll;
|
|
20366
20938
|
async run(throwIfAborted2) {
|
|
20367
20939
|
throwIfAborted2();
|
|
20940
|
+
const gate = enforceReadGate(this.readState, this.absolutePath);
|
|
20941
|
+
if (!gate.ok) {
|
|
20942
|
+
rejectWith(gate.message);
|
|
20943
|
+
}
|
|
20944
|
+
recordCheckpoint(this.checkpoint, this.absolutePath);
|
|
20368
20945
|
const { sourceText } = await this.loadSource();
|
|
20369
20946
|
throwIfAborted2();
|
|
20370
20947
|
const normalized = this.normalizeInputs(sourceText);
|
|
20371
|
-
const
|
|
20372
|
-
const
|
|
20948
|
+
const search = this.resolveSearch(normalized);
|
|
20949
|
+
const match = this.locateUniqueMatch(normalized, search);
|
|
20950
|
+
const replacement = this.applyReplacement(normalized, search, match);
|
|
20373
20951
|
throwIfAborted2();
|
|
20374
20952
|
await this.persist(replacement.finalContent);
|
|
20375
20953
|
throwIfAborted2();
|
|
20376
|
-
return this.buildResponse(replacement.editableBase, replacement.replacedContent);
|
|
20954
|
+
return this.buildResponse(replacement.editableBase, replacement.replacedContent, replacement.replacements);
|
|
20377
20955
|
}
|
|
20378
20956
|
async loadSource() {
|
|
20379
20957
|
try {
|
|
20380
20958
|
await this.ops.access(this.absolutePath);
|
|
20381
20959
|
} catch {
|
|
20382
|
-
rejectWith(
|
|
20960
|
+
rejectWith(await this.buildNotFoundMessage());
|
|
20383
20961
|
}
|
|
20384
20962
|
const buffer = await this.ops.readFile(this.absolutePath);
|
|
20385
20963
|
const sourceText = buffer.toString("utf-8");
|
|
20386
20964
|
return { sourceText };
|
|
20387
20965
|
}
|
|
20966
|
+
/**
|
|
20967
|
+
* Compose the "No readable file …" rejection, enriched with a "Did you mean
|
|
20968
|
+
* …?" hint when we can spot either a sibling file sharing the base name or a
|
|
20969
|
+
* corrected path re-rooted under the working directory. Falls back to the
|
|
20970
|
+
* bare message when no suggestion applies, so the default behaviour is
|
|
20971
|
+
* preserved for the common case.
|
|
20972
|
+
*/
|
|
20973
|
+
async buildNotFoundMessage() {
|
|
20974
|
+
let message = `No readable file at ${this.input.path}. ${FILE_NOT_FOUND_CWD_NOTE} ${this.cwd}.`;
|
|
20975
|
+
try {
|
|
20976
|
+
const cwdSuggestion = await suggestPathUnderCwd(this.absolutePath, this.cwd);
|
|
20977
|
+
if (cwdSuggestion) {
|
|
20978
|
+
message += ` Did you mean ${cwdSuggestion}?`;
|
|
20979
|
+
return message;
|
|
20980
|
+
}
|
|
20981
|
+
const similarFilename = findSimilarFile(this.absolutePath);
|
|
20982
|
+
if (similarFilename) {
|
|
20983
|
+
message += ` Did you mean ${similarFilename}?`;
|
|
20984
|
+
}
|
|
20985
|
+
} catch {
|
|
20986
|
+
}
|
|
20987
|
+
return message;
|
|
20988
|
+
}
|
|
20388
20989
|
normalizeInputs(sourceText) {
|
|
20389
20990
|
const { bom, text } = stripBom(sourceText);
|
|
20390
20991
|
const originalEnding = detectLineEnding(text);
|
|
@@ -20399,41 +21000,106 @@ var EditSession = class {
|
|
|
20399
21000
|
normalizedNewText
|
|
20400
21001
|
};
|
|
20401
21002
|
}
|
|
20402
|
-
|
|
20403
|
-
|
|
21003
|
+
/**
|
|
21004
|
+
* Decide which oldText/newText pair to actually edit with. The literal
|
|
21005
|
+
* normalized inputs win whenever they appear in the file; only when they are
|
|
21006
|
+
* absent do we try a de-sanitized variant (repairing mangled tag tokens like
|
|
21007
|
+
* `<o>` → `<output>`), mirroring the same token expansions onto newText so
|
|
21008
|
+
* the replacement stays consistent. The de-sanitize table overlaps ordinary
|
|
21009
|
+
* code, so it is deliberately a *fallback*, never the first attempt.
|
|
21010
|
+
*/
|
|
21011
|
+
resolveSearch(normalized) {
|
|
21012
|
+
const literal = {
|
|
21013
|
+
searchOldText: normalized.normalizedOldText,
|
|
21014
|
+
searchNewText: normalized.normalizedNewText
|
|
21015
|
+
};
|
|
21016
|
+
if (this.matchingStrategy.find(normalized.normalizedContent, normalized.normalizedOldText).found) {
|
|
21017
|
+
return literal;
|
|
21018
|
+
}
|
|
21019
|
+
const { result: desanitizedOldText, appliedReplacements } = desanitizeMatchString(
|
|
21020
|
+
normalized.normalizedOldText
|
|
21021
|
+
);
|
|
21022
|
+
if (appliedReplacements.length === 0) {
|
|
21023
|
+
return literal;
|
|
21024
|
+
}
|
|
21025
|
+
if (!this.matchingStrategy.find(normalized.normalizedContent, desanitizedOldText).found) {
|
|
21026
|
+
return literal;
|
|
21027
|
+
}
|
|
21028
|
+
let desanitizedNewText = normalized.normalizedNewText;
|
|
21029
|
+
for (const { from, to } of appliedReplacements) {
|
|
21030
|
+
desanitizedNewText = desanitizedNewText.split(from).join(to);
|
|
21031
|
+
}
|
|
21032
|
+
return { searchOldText: desanitizedOldText, searchNewText: desanitizedNewText };
|
|
21033
|
+
}
|
|
21034
|
+
locateUniqueMatch(normalized, search) {
|
|
21035
|
+
const matchResult = this.matchingStrategy.find(normalized.normalizedContent, search.searchOldText);
|
|
20404
21036
|
if (!matchResult.found) {
|
|
20405
21037
|
rejectWith(
|
|
20406
21038
|
`The supplied text was not located in ${this.input.path}. It has to line up character for character, including every space and line break.`
|
|
20407
21039
|
);
|
|
20408
21040
|
}
|
|
20409
21041
|
const fuzzyContent = normalizeForFuzzyMatch(normalized.normalizedContent);
|
|
20410
|
-
const fuzzyOldText = normalizeForFuzzyMatch(
|
|
21042
|
+
const fuzzyOldText = normalizeForFuzzyMatch(search.searchOldText);
|
|
20411
21043
|
const matchCount = fuzzyContent.split(fuzzyOldText).length - 1;
|
|
20412
|
-
if (matchCount > 1) {
|
|
21044
|
+
if (matchCount > 1 && !this.replaceAll) {
|
|
20413
21045
|
rejectWith(
|
|
20414
|
-
`The text appears ${matchCount} times in ${this.input.path}, so the target is ambiguous. Include surrounding lines so it matches in exactly one place.`
|
|
21046
|
+
`The text appears ${matchCount} times in ${this.input.path}, so the target is ambiguous. Include surrounding lines so it matches in exactly one place, or set replaceAll to true to change every occurrence.`
|
|
20415
21047
|
);
|
|
20416
21048
|
}
|
|
20417
21049
|
return { matchResult, matchCount };
|
|
20418
21050
|
}
|
|
20419
|
-
applyReplacement(normalized, match) {
|
|
20420
|
-
const
|
|
20421
|
-
const
|
|
21051
|
+
applyReplacement(normalized, search, match) {
|
|
21052
|
+
const usedFuzzy = match.matchResult.usedFuzzyMatch;
|
|
21053
|
+
const matchedNeedle = usedFuzzy ? this.recoverOriginalSpan(normalized.normalizedContent, search.searchOldText) : search.searchOldText;
|
|
21054
|
+
const editableBase = normalized.normalizedContent;
|
|
21055
|
+
const replacementText = usedFuzzy ? preserveQuoteStyle(search.searchOldText, matchedNeedle, search.searchNewText) : search.searchNewText;
|
|
21056
|
+
const firstIndex = editableBase.indexOf(matchedNeedle);
|
|
21057
|
+
if (firstIndex === -1) {
|
|
21058
|
+
rejectWith(
|
|
21059
|
+
`The supplied text was not located in ${this.input.path}. It has to line up character for character, including every space and line break.`
|
|
21060
|
+
);
|
|
21061
|
+
}
|
|
21062
|
+
const replacedContent = this.replaceAll ? replaceAllLiteral(editableBase, matchedNeedle, replacementText) : editableBase.substring(0, firstIndex) + replacementText + editableBase.substring(firstIndex + matchedNeedle.length);
|
|
20422
21063
|
if (editableBase === replacedContent) {
|
|
20423
21064
|
rejectWith(
|
|
20424
21065
|
`The edit left ${this.input.path} unchanged because the replacement matches the original. Often this points to unexpected special characters or text that is not actually present.`
|
|
20425
21066
|
);
|
|
20426
21067
|
}
|
|
21068
|
+
const replacements = this.replaceAll ? Math.max(match.matchCount, 1) : 1;
|
|
20427
21069
|
const finalContent = normalized.bom + restoreLineEndings(replacedContent, normalized.originalEnding);
|
|
20428
|
-
return { editableBase, replacedContent, finalContent };
|
|
21070
|
+
return { editableBase, replacedContent, finalContent, replacements };
|
|
21071
|
+
}
|
|
21072
|
+
/**
|
|
21073
|
+
* Recover the original (un-folded) span in `content` whose glyph-folded form
|
|
21074
|
+
* equals the folded `searchOldText`. Mirrors induscode's `findActualString`:
|
|
21075
|
+
* locate the match in fully-folded space, then slice the same window out of
|
|
21076
|
+
* the original content so curly quotes and other glyphs are preserved. Falls
|
|
21077
|
+
* back to the supplied text if the window can't be recovered.
|
|
21078
|
+
*/
|
|
21079
|
+
recoverOriginalSpan(content, searchOldText) {
|
|
21080
|
+
const foldedContent = normalizeForFuzzyMatch(content);
|
|
21081
|
+
const foldedNeedle = normalizeForFuzzyMatch(searchOldText);
|
|
21082
|
+
const at = foldedContent.indexOf(foldedNeedle);
|
|
21083
|
+
if (at === -1) {
|
|
21084
|
+
return searchOldText;
|
|
21085
|
+
}
|
|
21086
|
+
for (let len = foldedNeedle.length; at + len <= content.length; len++) {
|
|
21087
|
+
const candidate = content.slice(at, at + len);
|
|
21088
|
+
if (normalizeForFuzzyMatch(candidate) === foldedNeedle) {
|
|
21089
|
+
return candidate;
|
|
21090
|
+
}
|
|
21091
|
+
}
|
|
21092
|
+
return content.slice(at, at + foldedNeedle.length);
|
|
20429
21093
|
}
|
|
20430
21094
|
async persist(finalContent) {
|
|
20431
21095
|
await this.ops.writeFile(this.absolutePath, finalContent);
|
|
21096
|
+
recordReadState(this.readState, this.absolutePath);
|
|
20432
21097
|
}
|
|
20433
|
-
buildResponse(editableBase, replacedContent) {
|
|
21098
|
+
buildResponse(editableBase, replacedContent, replacements) {
|
|
20434
21099
|
const diffResult = generateDiffString(editableBase, replacedContent);
|
|
21100
|
+
const summary = replacements > 1 ? `Replaced the target text in ${this.input.path} (${replacements} occurrences).` : `Replaced the target text in ${this.input.path}.`;
|
|
20435
21101
|
return {
|
|
20436
|
-
content: [{ type: "text", text:
|
|
21102
|
+
content: [{ type: "text", text: summary }],
|
|
20437
21103
|
details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }
|
|
20438
21104
|
};
|
|
20439
21105
|
}
|
|
@@ -20451,12 +21117,20 @@ function createEditTool(cwd, options) {
|
|
|
20451
21117
|
return {
|
|
20452
21118
|
name: "edit",
|
|
20453
21119
|
label: "edit",
|
|
20454
|
-
description: "Make a targeted change to a file by swapping one exact span of text for another. Provide oldText that reproduces the existing content character for character, whitespace included. Best for narrow, precise edits.",
|
|
21120
|
+
description: "Make a targeted change to a file by swapping one exact span of text for another. Provide oldText that reproduces the existing content character for character, whitespace included. Best for narrow, precise edits. By default oldText must match exactly once; set replaceAll to true to change every occurrence instead.",
|
|
20455
21121
|
parameters: editSchema,
|
|
20456
21122
|
execute: async (_toolCallId, input, signal) => {
|
|
20457
21123
|
validator.validate(input.path, input.oldText);
|
|
20458
21124
|
const absolutePath = resolveToCwd(input.path, cwd);
|
|
20459
|
-
const session = new EditSession(
|
|
21125
|
+
const session = new EditSession(
|
|
21126
|
+
input,
|
|
21127
|
+
absolutePath,
|
|
21128
|
+
ops,
|
|
21129
|
+
matchingStrategy,
|
|
21130
|
+
cwd,
|
|
21131
|
+
options?.readState,
|
|
21132
|
+
options?.checkpoint
|
|
21133
|
+
);
|
|
20460
21134
|
return withAbort(signal, async ({ throwIfAborted: throwIfAborted2 }) => session.run(throwIfAborted2));
|
|
20461
21135
|
}
|
|
20462
21136
|
};
|
|
@@ -20465,28 +21139,96 @@ var editTool = createEditTool(process.cwd());
|
|
|
20465
21139
|
|
|
20466
21140
|
// src/facade/bot/actions/find.ts
|
|
20467
21141
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
20468
|
-
import { existsSync as existsSync3 } from "fs";
|
|
21142
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
20469
21143
|
import path from "path";
|
|
20470
21144
|
var findSchema = Type4.Object({
|
|
20471
21145
|
pattern: Type4.String({
|
|
20472
21146
|
description: 'Glob that the filenames should match, such as "*.ts", "**/*.json", or "lib/**/*.test.ts"'
|
|
20473
21147
|
}),
|
|
20474
21148
|
path: Type4.Optional(Type4.String({ description: "Directory tree to traverse. Falls back to the working directory." })),
|
|
21149
|
+
type: Type4.Optional(
|
|
21150
|
+
Type4.String({
|
|
21151
|
+
description: `Restrict results to a language/category (for example "ts", "js", "py"). Filters by the entry's file extension. Defaults to no type filter.`
|
|
21152
|
+
})
|
|
21153
|
+
),
|
|
20475
21154
|
limit: Type4.Optional(Type4.Number({ description: "Maximum count of paths handed back. Defaults to 1024." }))
|
|
20476
21155
|
});
|
|
20477
21156
|
var DEFAULT_LIMIT = 1024;
|
|
20478
21157
|
var DEFAULT_EXCLUDES = ["node_modules", ".git"];
|
|
20479
21158
|
var MAX_DIRECTORY_DEPTH = 20;
|
|
21159
|
+
function escapeRegexLiteral(literal) {
|
|
21160
|
+
return literal.replace(/[\\.*+?^${}()|[\]]/g, "\\$&");
|
|
21161
|
+
}
|
|
21162
|
+
function compileGlob(pattern) {
|
|
21163
|
+
let body = "";
|
|
21164
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
21165
|
+
const ch = pattern[i];
|
|
21166
|
+
if (ch === "*") {
|
|
21167
|
+
if (pattern[i + 1] === "*") {
|
|
21168
|
+
i++;
|
|
21169
|
+
if (pattern[i + 1] === "/") {
|
|
21170
|
+
i++;
|
|
21171
|
+
body += "(?:[^/]*/)*";
|
|
21172
|
+
} else {
|
|
21173
|
+
body += ".*";
|
|
21174
|
+
}
|
|
21175
|
+
} else {
|
|
21176
|
+
body += "[^/]*";
|
|
21177
|
+
}
|
|
21178
|
+
} else if (ch === "?") {
|
|
21179
|
+
body += "[^/]";
|
|
21180
|
+
} else {
|
|
21181
|
+
body += escapeRegexLiteral(ch);
|
|
21182
|
+
}
|
|
21183
|
+
}
|
|
21184
|
+
try {
|
|
21185
|
+
return new RegExp(`^${body}$`);
|
|
21186
|
+
} catch {
|
|
21187
|
+
return null;
|
|
21188
|
+
}
|
|
21189
|
+
}
|
|
20480
21190
|
var GlobMatcher = class {
|
|
20481
|
-
|
|
21191
|
+
relRegex;
|
|
21192
|
+
baseRegex;
|
|
21193
|
+
hasSeparator;
|
|
20482
21194
|
constructor(pattern) {
|
|
20483
|
-
|
|
20484
|
-
this.
|
|
20485
|
-
|
|
20486
|
-
|
|
20487
|
-
|
|
21195
|
+
this.hasSeparator = pattern.includes("/");
|
|
21196
|
+
this.relRegex = compileGlob(pattern);
|
|
21197
|
+
this.baseRegex = this.hasSeparator ? null : this.relRegex;
|
|
21198
|
+
}
|
|
21199
|
+
/** Test against the root-relative path (preferred), falling back to basename. */
|
|
21200
|
+
matchesPath(relativePath) {
|
|
21201
|
+
const normalized = relativePath.split(path.sep).join("/");
|
|
21202
|
+
if (this.relRegex && this.relRegex.test(normalized)) {
|
|
21203
|
+
return true;
|
|
21204
|
+
}
|
|
21205
|
+
if (this.baseRegex) {
|
|
21206
|
+
const base = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
21207
|
+
return this.baseRegex.test(base);
|
|
21208
|
+
}
|
|
21209
|
+
return false;
|
|
20488
21210
|
}
|
|
20489
21211
|
};
|
|
21212
|
+
function typeToExtensions(type) {
|
|
21213
|
+
const key = type.trim().toLowerCase();
|
|
21214
|
+
if (key.length === 0) return null;
|
|
21215
|
+
const TABLE = {
|
|
21216
|
+
ts: ["ts", "tsx", "mts", "cts"],
|
|
21217
|
+
js: ["js", "jsx", "mjs", "cjs"],
|
|
21218
|
+
py: ["py", "pyi"],
|
|
21219
|
+
rs: ["rs"],
|
|
21220
|
+
go: ["go"],
|
|
21221
|
+
java: ["java"],
|
|
21222
|
+
c: ["c", "h"],
|
|
21223
|
+
cpp: ["cpp", "cc", "cxx", "hpp", "hh"],
|
|
21224
|
+
md: ["md", "markdown"],
|
|
21225
|
+
json: ["json"],
|
|
21226
|
+
yaml: ["yaml", "yml"],
|
|
21227
|
+
sh: ["sh", "bash", "zsh"]
|
|
21228
|
+
};
|
|
21229
|
+
const exts = TABLE[key] ?? [key];
|
|
21230
|
+
return new Set(exts.map((e) => e.toLowerCase()));
|
|
21231
|
+
}
|
|
20490
21232
|
var DirectoryWalkPolicy = class {
|
|
20491
21233
|
constructor(limit) {
|
|
20492
21234
|
this.limit = limit;
|
|
@@ -20500,39 +21242,50 @@ var DirectoryWalkPolicy = class {
|
|
|
20500
21242
|
}
|
|
20501
21243
|
};
|
|
20502
21244
|
var DirectoryFinder = class {
|
|
20503
|
-
constructor(matcher, walkPolicy, limit) {
|
|
21245
|
+
constructor(matcher, walkPolicy, limit, root, typeExtensions) {
|
|
20504
21246
|
this.matcher = matcher;
|
|
20505
21247
|
this.walkPolicy = walkPolicy;
|
|
20506
21248
|
this.limit = limit;
|
|
21249
|
+
this.root = root;
|
|
21250
|
+
this.typeExtensions = typeExtensions;
|
|
20507
21251
|
}
|
|
20508
21252
|
matcher;
|
|
20509
21253
|
walkPolicy;
|
|
20510
21254
|
limit;
|
|
20511
|
-
|
|
21255
|
+
root;
|
|
21256
|
+
typeExtensions;
|
|
20512
21257
|
async find(dir) {
|
|
20513
21258
|
const results = [];
|
|
20514
21259
|
await this.searchRecursive(dir, 0, results);
|
|
20515
21260
|
return results;
|
|
20516
21261
|
}
|
|
21262
|
+
extensionOf(name) {
|
|
21263
|
+
const dot = name.lastIndexOf(".");
|
|
21264
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
21265
|
+
}
|
|
20517
21266
|
async searchRecursive(currentDir, depth, results) {
|
|
20518
21267
|
if (!this.walkPolicy.canContinue(results.length, depth)) {
|
|
20519
21268
|
return;
|
|
20520
21269
|
}
|
|
20521
21270
|
try {
|
|
20522
|
-
const entries =
|
|
21271
|
+
const entries = readdirSync2(currentDir);
|
|
20523
21272
|
for (const entry of entries) {
|
|
20524
21273
|
if (results.length >= this.limit) {
|
|
20525
21274
|
break;
|
|
20526
21275
|
}
|
|
20527
21276
|
const fullPath = path.join(currentDir, entry);
|
|
20528
|
-
const
|
|
20529
|
-
if (
|
|
21277
|
+
const stat3 = statSync3(fullPath);
|
|
21278
|
+
if (stat3.isDirectory()) {
|
|
20530
21279
|
if (this.walkPolicy.shouldDescend(entry)) {
|
|
20531
21280
|
await this.searchRecursive(fullPath, depth + 1, results);
|
|
20532
21281
|
}
|
|
20533
21282
|
continue;
|
|
20534
21283
|
}
|
|
20535
|
-
if (this.
|
|
21284
|
+
if (this.typeExtensions && !this.typeExtensions.has(this.extensionOf(entry))) {
|
|
21285
|
+
continue;
|
|
21286
|
+
}
|
|
21287
|
+
const relativePath = path.relative(this.root, fullPath);
|
|
21288
|
+
if (this.matcher.matchesPath(relativePath)) {
|
|
20536
21289
|
results.push(fullPath);
|
|
20537
21290
|
}
|
|
20538
21291
|
}
|
|
@@ -20587,9 +21340,10 @@ function createFindTool(cwd, options) {
|
|
|
20587
21340
|
return {
|
|
20588
21341
|
name: "find",
|
|
20589
21342
|
label: "find",
|
|
21343
|
+
readOnly: true,
|
|
20590
21344
|
description: `Find files whose names match a glob, returning each result as a path relative to the directory that was searched. The listing halts at ${DEFAULT_LIMIT} paths or ${DEFAULT_MAX_BYTES / 1024}KB of output, whichever it reaches first.`,
|
|
20591
21345
|
parameters: findSchema,
|
|
20592
|
-
execute: async (_toolCallId, { pattern, path: searchDir, limit }, signal) => {
|
|
21346
|
+
execute: async (_toolCallId, { pattern, path: searchDir, type, limit }, signal) => {
|
|
20593
21347
|
return new Promise((resolve5, reject) => {
|
|
20594
21348
|
if (signal?.aborted) {
|
|
20595
21349
|
reject(new Error("Operation aborted"));
|
|
@@ -20608,7 +21362,8 @@ function createFindTool(cwd, options) {
|
|
|
20608
21362
|
}
|
|
20609
21363
|
const matcher = new GlobMatcher(pattern);
|
|
20610
21364
|
const walkPolicy = new DirectoryWalkPolicy(effectiveLimit);
|
|
20611
|
-
const
|
|
21365
|
+
const typeExtensions = type ? typeToExtensions(type) : null;
|
|
21366
|
+
const finder = new DirectoryFinder(matcher, walkPolicy, effectiveLimit, searchPath, typeExtensions);
|
|
20612
21367
|
let results = await finder.find(searchPath);
|
|
20613
21368
|
results = results.filter(
|
|
20614
21369
|
(entryPath) => ![...excludes].some((excluded) => entryPath.includes(`/${excluded}/`) || entryPath.endsWith(`/${excluded}`))
|
|
@@ -20634,7 +21389,7 @@ var findTool = createFindTool(process.cwd());
|
|
|
20634
21389
|
|
|
20635
21390
|
// src/facade/bot/actions/grep.ts
|
|
20636
21391
|
import { Type as Type5 } from "@sinclair/typebox";
|
|
20637
|
-
import { readFileSync, statSync } from "fs";
|
|
21392
|
+
import { readFileSync as readFileSync2, statSync as statSync4, readdirSync as readdirSync3 } from "fs";
|
|
20638
21393
|
import path2 from "path";
|
|
20639
21394
|
var grepSchema = Type5.Object({
|
|
20640
21395
|
pattern: Type5.String({ description: "What to search for, given either as a regular expression or as ordinary text" }),
|
|
@@ -20644,20 +21399,158 @@ var grepSchema = Type5.Object({
|
|
|
20644
21399
|
Type5.Boolean({ description: "Set to true to treat the pattern as exact text instead of a regex. Defaults to regex mode." })
|
|
20645
21400
|
),
|
|
20646
21401
|
context: Type5.Optional(
|
|
20647
|
-
Type5.Number({ description: "Number of neighboring lines to print above and below each hit. Defaults to zero." })
|
|
21402
|
+
Type5.Number({ description: "Number of neighboring lines to print above and below each hit (content mode). Defaults to zero." })
|
|
21403
|
+
),
|
|
21404
|
+
before: Type5.Optional(
|
|
21405
|
+
Type5.Number({ description: "Number of lines to print BEFORE each hit (-B). Overrides `context` for leading lines." })
|
|
21406
|
+
),
|
|
21407
|
+
after: Type5.Optional(
|
|
21408
|
+
Type5.Number({ description: "Number of lines to print AFTER each hit (-A). Overrides `context` for trailing lines." })
|
|
21409
|
+
),
|
|
21410
|
+
output_mode: Type5.Optional(
|
|
21411
|
+
Type5.Union(
|
|
21412
|
+
[Type5.Literal("content"), Type5.Literal("files_with_matches"), Type5.Literal("count")],
|
|
21413
|
+
{
|
|
21414
|
+
description: 'How results are reported: "content" (default) shows matching lines, "files_with_matches" lists matching file paths, "count" reports per-file occurrence totals.'
|
|
21415
|
+
}
|
|
21416
|
+
)
|
|
21417
|
+
),
|
|
21418
|
+
glob: Type5.Optional(
|
|
21419
|
+
Type5.String({
|
|
21420
|
+
description: 'Only search files whose root-relative path matches this glob (e.g. "*.ts" or "src/**/*.test.ts"). Multiple globs may be comma-separated. Defaults to no glob filter.'
|
|
21421
|
+
})
|
|
21422
|
+
),
|
|
21423
|
+
type: Type5.Optional(
|
|
21424
|
+
Type5.String({
|
|
21425
|
+
description: 'Only search files of this language/category (for example "ts", "js", "py"), matched by extension. Defaults to no type filter.'
|
|
21426
|
+
})
|
|
20648
21427
|
),
|
|
20649
21428
|
limit: Type5.Optional(Type5.Number({ description: "Upper bound on the count of hits returned. Defaults to 128." }))
|
|
20650
21429
|
});
|
|
20651
21430
|
var DEFAULT_LIMIT2 = 128;
|
|
20652
21431
|
var regexCache = /* @__PURE__ */ new Map();
|
|
20653
21432
|
var REGEX_CACHE_MAX_SIZE = 256;
|
|
20654
|
-
var
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
21433
|
+
var PRUNED_DIRS = /* @__PURE__ */ new Set([".git", ".svn", ".hg", ".bzr", ".jj", ".sl", "node_modules"]);
|
|
21434
|
+
function escapeGlobLiteral(literal) {
|
|
21435
|
+
return literal.replace(/[\\.*+?^${}()|[\]]/g, "\\$&");
|
|
21436
|
+
}
|
|
21437
|
+
function compileGlob2(glob) {
|
|
21438
|
+
let body = "";
|
|
21439
|
+
for (let i = 0; i < glob.length; i++) {
|
|
21440
|
+
const ch = glob[i];
|
|
21441
|
+
if (ch === "*") {
|
|
21442
|
+
if (glob[i + 1] === "*") {
|
|
21443
|
+
i++;
|
|
21444
|
+
if (glob[i + 1] === "/") {
|
|
21445
|
+
i++;
|
|
21446
|
+
body += "(?:[^/]*/)*";
|
|
21447
|
+
} else {
|
|
21448
|
+
body += ".*";
|
|
21449
|
+
}
|
|
21450
|
+
} else {
|
|
21451
|
+
body += "[^/]*";
|
|
21452
|
+
}
|
|
21453
|
+
} else if (ch === "?") {
|
|
21454
|
+
body += "[^/]";
|
|
21455
|
+
} else {
|
|
21456
|
+
body += escapeGlobLiteral(ch);
|
|
21457
|
+
}
|
|
21458
|
+
}
|
|
21459
|
+
try {
|
|
21460
|
+
return new RegExp(`^${body}$`);
|
|
21461
|
+
} catch {
|
|
21462
|
+
return null;
|
|
21463
|
+
}
|
|
21464
|
+
}
|
|
21465
|
+
function buildIncludePredicate(glob, type) {
|
|
21466
|
+
const globRegexes = [];
|
|
21467
|
+
if (glob && glob.trim().length > 0) {
|
|
21468
|
+
for (const part of glob.split(",")) {
|
|
21469
|
+
const trimmed = part.trim();
|
|
21470
|
+
if (trimmed.length === 0) continue;
|
|
21471
|
+
const re = compileGlob2(trimmed);
|
|
21472
|
+
if (re) globRegexes.push(re);
|
|
21473
|
+
}
|
|
21474
|
+
}
|
|
21475
|
+
const extensions = type ? typeToExtensions2(type) : null;
|
|
21476
|
+
if (globRegexes.length === 0 && !extensions) return null;
|
|
21477
|
+
return (relPath) => {
|
|
21478
|
+
const normalized = relPath.split(path2.sep).join("/");
|
|
21479
|
+
if (globRegexes.length > 0) {
|
|
21480
|
+
const matchesGlob = globRegexes.some((re) => {
|
|
21481
|
+
if (re.test(normalized)) return true;
|
|
21482
|
+
const base = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
21483
|
+
return re.test(base);
|
|
21484
|
+
});
|
|
21485
|
+
if (!matchesGlob) return false;
|
|
21486
|
+
}
|
|
21487
|
+
if (extensions) {
|
|
21488
|
+
const base = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
21489
|
+
const dot = base.lastIndexOf(".");
|
|
21490
|
+
const ext = dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
21491
|
+
if (!extensions.has(ext)) return false;
|
|
21492
|
+
}
|
|
21493
|
+
return true;
|
|
21494
|
+
};
|
|
21495
|
+
}
|
|
21496
|
+
function typeToExtensions2(type) {
|
|
21497
|
+
const key = type.trim().toLowerCase();
|
|
21498
|
+
if (key.length === 0) return null;
|
|
21499
|
+
const TABLE = {
|
|
21500
|
+
ts: ["ts", "tsx", "mts", "cts"],
|
|
21501
|
+
js: ["js", "jsx", "mjs", "cjs"],
|
|
21502
|
+
py: ["py", "pyi"],
|
|
21503
|
+
rs: ["rs"],
|
|
21504
|
+
go: ["go"],
|
|
21505
|
+
java: ["java"],
|
|
21506
|
+
c: ["c", "h"],
|
|
21507
|
+
cpp: ["cpp", "cc", "cxx", "hpp", "hh"],
|
|
21508
|
+
md: ["md", "markdown"],
|
|
21509
|
+
json: ["json"],
|
|
21510
|
+
yaml: ["yaml", "yml"],
|
|
21511
|
+
sh: ["sh", "bash", "zsh"]
|
|
21512
|
+
};
|
|
21513
|
+
const exts = TABLE[key] ?? [key];
|
|
21514
|
+
return new Set(exts.map((e) => e.toLowerCase()));
|
|
21515
|
+
}
|
|
21516
|
+
async function loadGitignore(rootDir, readFile3) {
|
|
21517
|
+
let body;
|
|
21518
|
+
try {
|
|
21519
|
+
body = await readFile3(path2.join(rootDir, ".gitignore"));
|
|
21520
|
+
} catch {
|
|
21521
|
+
return () => false;
|
|
21522
|
+
}
|
|
21523
|
+
const regexes = [];
|
|
21524
|
+
for (const raw of body.split("\n")) {
|
|
21525
|
+
const line = raw.trim();
|
|
21526
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
21527
|
+
const anchored = line.startsWith("/");
|
|
21528
|
+
const pat = anchored ? line.slice(1) : line;
|
|
21529
|
+
const re = compileGlob2(pat.replace(/\/$/, ""));
|
|
21530
|
+
if (!re) continue;
|
|
21531
|
+
if (anchored) {
|
|
21532
|
+
regexes.push(new RegExp(`${re.source.slice(0, -1)}(?:/.*)?$`));
|
|
21533
|
+
} else {
|
|
21534
|
+
regexes.push(new RegExp(`(?:^|/)${re.source.slice(1, -1)}(?:/.*)?$`));
|
|
21535
|
+
}
|
|
21536
|
+
}
|
|
21537
|
+
if (regexes.length === 0) return () => false;
|
|
21538
|
+
return (relPath) => {
|
|
21539
|
+
const normalized = relPath.split(path2.sep).join("/");
|
|
21540
|
+
return regexes.some((re) => re.test(normalized));
|
|
21541
|
+
};
|
|
21542
|
+
}
|
|
21543
|
+
function looksBinary(text) {
|
|
21544
|
+
const limit = Math.min(text.length, 4096);
|
|
21545
|
+
for (let i = 0; i < limit; i++) {
|
|
21546
|
+
if (text.charCodeAt(i) === 0) return true;
|
|
20660
21547
|
}
|
|
21548
|
+
return false;
|
|
21549
|
+
}
|
|
21550
|
+
var defaultGrepOperations = {
|
|
21551
|
+
isDirectory: (p) => statSync4(p).isDirectory(),
|
|
21552
|
+
readFile: (p) => readFileSync2(p, "utf-8"),
|
|
21553
|
+
readdir: (p) => readdirSync3(p)
|
|
20661
21554
|
};
|
|
20662
21555
|
var RegexFactory = class {
|
|
20663
21556
|
static create(pattern, ignoreCase, literal) {
|
|
@@ -20678,12 +21571,22 @@ var RegexFactory = class {
|
|
|
20678
21571
|
}
|
|
20679
21572
|
};
|
|
20680
21573
|
var SearchPlan = class {
|
|
20681
|
-
constructor(searchPath, ops) {
|
|
21574
|
+
constructor(searchPath, ops, options) {
|
|
20682
21575
|
this.searchPath = searchPath;
|
|
20683
21576
|
this.ops = ops;
|
|
21577
|
+
this.include = options?.include ?? null;
|
|
21578
|
+
this.ignored = options?.ignored ?? null;
|
|
20684
21579
|
}
|
|
20685
21580
|
searchPath;
|
|
20686
21581
|
ops;
|
|
21582
|
+
include;
|
|
21583
|
+
ignored;
|
|
21584
|
+
/**
|
|
21585
|
+
* Fully recursive (depth-first) enumeration of every readable file beneath
|
|
21586
|
+
* the search root. Pruned VCS/dependency folders and dot-directories are
|
|
21587
|
+
* skipped, directories are NEVER pushed as if they were files, and the
|
|
21588
|
+
* optional include/gitignore predicates scope or exclude the result set.
|
|
21589
|
+
*/
|
|
20687
21590
|
async collectFiles() {
|
|
20688
21591
|
const filesToSearch = [];
|
|
20689
21592
|
const isDir = await this.ops.isDirectory(this.searchPath);
|
|
@@ -20691,38 +21594,57 @@ var SearchPlan = class {
|
|
|
20691
21594
|
filesToSearch.push(this.searchPath);
|
|
20692
21595
|
return filesToSearch;
|
|
20693
21596
|
}
|
|
20694
|
-
|
|
21597
|
+
await this.walk(this.searchPath, filesToSearch);
|
|
21598
|
+
return filesToSearch;
|
|
21599
|
+
}
|
|
21600
|
+
relativeOf(fullPath) {
|
|
21601
|
+
return path2.relative(this.searchPath, fullPath);
|
|
21602
|
+
}
|
|
21603
|
+
async walk(dir, sink) {
|
|
21604
|
+
let entries;
|
|
21605
|
+
try {
|
|
21606
|
+
entries = await this.ops.readdir(dir);
|
|
21607
|
+
} catch {
|
|
21608
|
+
return;
|
|
21609
|
+
}
|
|
21610
|
+
entries.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
20695
21611
|
for (const entry of entries) {
|
|
20696
|
-
const fullPath = path2.join(
|
|
21612
|
+
const fullPath = path2.join(dir, entry);
|
|
21613
|
+
let entryIsDir;
|
|
20697
21614
|
try {
|
|
20698
|
-
|
|
20699
|
-
const subEntries = await this.ops.readdir(fullPath);
|
|
20700
|
-
for (const subEntry of subEntries) {
|
|
20701
|
-
filesToSearch.push(path2.join(fullPath, subEntry));
|
|
20702
|
-
}
|
|
20703
|
-
} else if (!await this.ops.isDirectory(fullPath)) {
|
|
20704
|
-
filesToSearch.push(fullPath);
|
|
20705
|
-
}
|
|
21615
|
+
entryIsDir = await this.ops.isDirectory(fullPath);
|
|
20706
21616
|
} catch {
|
|
21617
|
+
continue;
|
|
20707
21618
|
}
|
|
21619
|
+
const rel = this.relativeOf(fullPath);
|
|
21620
|
+
if (entryIsDir) {
|
|
21621
|
+
if (PRUNED_DIRS.has(entry) || entry.startsWith(".")) continue;
|
|
21622
|
+
if (this.ignored && this.ignored(rel)) continue;
|
|
21623
|
+
await this.walk(fullPath, sink);
|
|
21624
|
+
continue;
|
|
21625
|
+
}
|
|
21626
|
+
if (this.ignored && this.ignored(rel)) continue;
|
|
21627
|
+
if (this.include && !this.include(rel)) continue;
|
|
21628
|
+
sink.push(fullPath);
|
|
20708
21629
|
}
|
|
20709
|
-
return filesToSearch;
|
|
20710
21630
|
}
|
|
20711
21631
|
};
|
|
20712
21632
|
var MatchRenderer = class {
|
|
20713
|
-
constructor(searchPath, contextValue) {
|
|
21633
|
+
constructor(searchPath, contextValue, before, after) {
|
|
20714
21634
|
this.searchPath = searchPath;
|
|
20715
|
-
this.
|
|
21635
|
+
this.before = before !== void 0 && before > 0 ? before : contextValue;
|
|
21636
|
+
this.after = after !== void 0 && after > 0 ? after : contextValue;
|
|
20716
21637
|
}
|
|
20717
21638
|
searchPath;
|
|
20718
|
-
|
|
21639
|
+
before;
|
|
21640
|
+
after;
|
|
20719
21641
|
renderMatch(filePath, lines, lineIndex) {
|
|
20720
21642
|
const relativePath = path2.relative(this.searchPath, filePath);
|
|
20721
21643
|
const line = lines[lineIndex] ?? "";
|
|
20722
21644
|
const { wasTruncated } = truncateLine(line, GREP_MAX_LINE_LENGTH);
|
|
20723
21645
|
const output = [];
|
|
20724
|
-
const start = Math.max(0, lineIndex - this.
|
|
20725
|
-
const end = Math.min(lines.length - 1, lineIndex + this.
|
|
21646
|
+
const start = Math.max(0, lineIndex - this.before);
|
|
21647
|
+
const end = Math.min(lines.length - 1, lineIndex + this.after);
|
|
20726
21648
|
for (let j = start; j <= end; j++) {
|
|
20727
21649
|
const isMatch = j === lineIndex;
|
|
20728
21650
|
const prefix = isMatch ? ":" : "-";
|
|
@@ -20768,12 +21690,32 @@ var MatchRenderer = class {
|
|
|
20768
21690
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
20769
21691
|
};
|
|
20770
21692
|
}
|
|
21693
|
+
/** `files_with_matches` mode: one root-relative path per matching file. */
|
|
21694
|
+
formatFilesWithMatches(files) {
|
|
21695
|
+
if (files.length === 0) {
|
|
21696
|
+
return { output: "Pattern not found in any file" };
|
|
21697
|
+
}
|
|
21698
|
+
return { output: files.join("\n") };
|
|
21699
|
+
}
|
|
21700
|
+
/** `count` mode: `path:N` lines plus a roll-up of occurrences across files. */
|
|
21701
|
+
formatCount(counts) {
|
|
21702
|
+
if (counts.length === 0) {
|
|
21703
|
+
return { output: "Pattern not found in any file" };
|
|
21704
|
+
}
|
|
21705
|
+
const lines = counts.map((c) => `${c.file}:${c.count}`);
|
|
21706
|
+
const total = counts.reduce((sum, c) => sum + c.count, 0);
|
|
21707
|
+
const fileWord = counts.length === 1 ? "file" : "files";
|
|
21708
|
+
const occWord = total === 1 ? "occurrence" : "occurrences";
|
|
21709
|
+
lines.push("", `Found ${total} ${occWord} across ${counts.length} ${fileWord}`);
|
|
21710
|
+
return { output: lines.join("\n") };
|
|
21711
|
+
}
|
|
20771
21712
|
};
|
|
20772
21713
|
function createGrepTool(cwd, options) {
|
|
20773
21714
|
const customOps = options?.operations;
|
|
20774
21715
|
return {
|
|
20775
21716
|
name: "grep",
|
|
20776
21717
|
label: "grep",
|
|
21718
|
+
readOnly: true,
|
|
20777
21719
|
description: `Search file contents for a pattern, returning every hit alongside its path and line number. The search halts at ${DEFAULT_LIMIT2} hits or ${DEFAULT_MAX_BYTES / 1024}KB of output, whichever it reaches first. Any line over ${GREP_MAX_LINE_LENGTH} characters is shortened.`,
|
|
20778
21720
|
parameters: grepSchema,
|
|
20779
21721
|
execute: async (_toolCallId, {
|
|
@@ -20782,6 +21724,11 @@ function createGrepTool(cwd, options) {
|
|
|
20782
21724
|
ignoreCase,
|
|
20783
21725
|
literal,
|
|
20784
21726
|
context,
|
|
21727
|
+
before,
|
|
21728
|
+
after,
|
|
21729
|
+
output_mode,
|
|
21730
|
+
glob,
|
|
21731
|
+
type,
|
|
20785
21732
|
limit
|
|
20786
21733
|
}, signal) => {
|
|
20787
21734
|
return new Promise((resolve5, reject) => {
|
|
@@ -20797,7 +21744,10 @@ function createGrepTool(cwd, options) {
|
|
|
20797
21744
|
const ops = customOps ?? defaultGrepOperations;
|
|
20798
21745
|
const effectiveLimit = limit ?? DEFAULT_LIMIT2;
|
|
20799
21746
|
const contextValue = context && context > 0 ? context : 0;
|
|
20800
|
-
const
|
|
21747
|
+
const mode = output_mode ?? "content";
|
|
21748
|
+
const include = buildIncludePredicate(glob, type);
|
|
21749
|
+
const ignored = await loadGitignore(searchPath, (p) => ops.readFile(p));
|
|
21750
|
+
const searchPlan = new SearchPlan(searchPath, ops, { include, ignored });
|
|
20801
21751
|
const filesToSearch = await searchPlan.collectFiles();
|
|
20802
21752
|
if (filesToSearch.length === 0) {
|
|
20803
21753
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -20812,34 +21762,63 @@ function createGrepTool(cwd, options) {
|
|
|
20812
21762
|
reject(new Error(`Invalid regex pattern: ${e.message}`));
|
|
20813
21763
|
return;
|
|
20814
21764
|
}
|
|
20815
|
-
const matchRenderer = new MatchRenderer(searchPath, contextValue);
|
|
21765
|
+
const matchRenderer = new MatchRenderer(searchPath, contextValue, before, after);
|
|
20816
21766
|
const matches = [];
|
|
21767
|
+
const filesWithMatches = [];
|
|
21768
|
+
const counts = [];
|
|
20817
21769
|
let linesTruncated = false;
|
|
21770
|
+
let stopScanning = false;
|
|
20818
21771
|
for (const filePath of filesToSearch) {
|
|
20819
|
-
if (
|
|
21772
|
+
if (stopScanning) break;
|
|
21773
|
+
if (mode === "content" && matches.length >= effectiveLimit) {
|
|
20820
21774
|
break;
|
|
20821
21775
|
}
|
|
20822
21776
|
try {
|
|
20823
21777
|
const content = await ops.readFile(filePath);
|
|
21778
|
+
if (looksBinary(content)) {
|
|
21779
|
+
continue;
|
|
21780
|
+
}
|
|
20824
21781
|
const lines = content.split("\n");
|
|
21782
|
+
const relativePath = path2.relative(searchPath, filePath);
|
|
21783
|
+
let fileCount = 0;
|
|
20825
21784
|
for (let i = 0; i < lines.length; i++) {
|
|
20826
|
-
if (matches.length >= effectiveLimit) {
|
|
21785
|
+
if (mode === "content" && matches.length >= effectiveLimit) {
|
|
21786
|
+
stopScanning = true;
|
|
20827
21787
|
break;
|
|
20828
21788
|
}
|
|
20829
21789
|
const line = lines[i] ?? "";
|
|
20830
21790
|
if (regex.test(line)) {
|
|
20831
|
-
|
|
20832
|
-
if (
|
|
20833
|
-
|
|
21791
|
+
fileCount++;
|
|
21792
|
+
if (mode === "content") {
|
|
21793
|
+
const rendered = matchRenderer.renderMatch(filePath, lines, i);
|
|
21794
|
+
if (rendered.lineTruncated) {
|
|
21795
|
+
linesTruncated = true;
|
|
21796
|
+
}
|
|
21797
|
+
matches.push({ file: relativePath, line: i + 1, text: rendered.text });
|
|
21798
|
+
} else if (mode === "files_with_matches") {
|
|
21799
|
+
break;
|
|
20834
21800
|
}
|
|
20835
|
-
|
|
21801
|
+
}
|
|
21802
|
+
}
|
|
21803
|
+
if (fileCount > 0) {
|
|
21804
|
+
if (mode === "files_with_matches") {
|
|
21805
|
+
filesWithMatches.push(relativePath);
|
|
21806
|
+
} else if (mode === "count") {
|
|
21807
|
+
counts.push({ file: relativePath, count: fileCount });
|
|
20836
21808
|
}
|
|
20837
21809
|
}
|
|
20838
21810
|
} catch {
|
|
20839
21811
|
}
|
|
20840
21812
|
}
|
|
20841
21813
|
signal?.removeEventListener("abort", onAbort);
|
|
20842
|
-
|
|
21814
|
+
let formatted;
|
|
21815
|
+
if (mode === "files_with_matches") {
|
|
21816
|
+
formatted = matchRenderer.formatFilesWithMatches(filesWithMatches);
|
|
21817
|
+
} else if (mode === "count") {
|
|
21818
|
+
formatted = matchRenderer.formatCount(counts);
|
|
21819
|
+
} else {
|
|
21820
|
+
formatted = matchRenderer.formatOutput(matches, effectiveLimit, linesTruncated);
|
|
21821
|
+
}
|
|
20843
21822
|
resolve5({
|
|
20844
21823
|
content: [{ type: "text", text: formatted.output }],
|
|
20845
21824
|
details: formatted.details
|
|
@@ -20857,7 +21836,7 @@ var grepTool = createGrepTool(process.cwd());
|
|
|
20857
21836
|
|
|
20858
21837
|
// src/facade/bot/actions/ls.ts
|
|
20859
21838
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
20860
|
-
import { existsSync as existsSync4, readdirSync, statSync as
|
|
21839
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
20861
21840
|
import nodePath from "path";
|
|
20862
21841
|
var lsSchema = Type6.Object({
|
|
20863
21842
|
path: Type6.Optional(Type6.String({ description: "Directory whose entries should be listed. Falls back to the working directory." })),
|
|
@@ -20866,8 +21845,8 @@ var lsSchema = Type6.Object({
|
|
|
20866
21845
|
var DEFAULT_LIMIT3 = 512;
|
|
20867
21846
|
var defaultLsOperations = {
|
|
20868
21847
|
exists: existsSync4,
|
|
20869
|
-
stat:
|
|
20870
|
-
readdir:
|
|
21848
|
+
stat: statSync5,
|
|
21849
|
+
readdir: readdirSync4
|
|
20871
21850
|
};
|
|
20872
21851
|
function createAbortGuard(signal) {
|
|
20873
21852
|
let aborted = signal?.aborted ?? false;
|
|
@@ -20952,8 +21931,8 @@ var DirectoryListRunner = class {
|
|
|
20952
21931
|
if (!await this.ops.exists(resolvedDir)) {
|
|
20953
21932
|
throw new Error(`No such path: ${resolvedDir}`);
|
|
20954
21933
|
}
|
|
20955
|
-
const
|
|
20956
|
-
if (!
|
|
21934
|
+
const stat3 = await this.ops.stat(resolvedDir);
|
|
21935
|
+
if (!stat3.isDirectory()) {
|
|
20957
21936
|
throw new Error(`Target is not a directory: ${resolvedDir}`);
|
|
20958
21937
|
}
|
|
20959
21938
|
}
|
|
@@ -21009,6 +21988,7 @@ function createLsTool(cwd, options) {
|
|
|
21009
21988
|
return {
|
|
21010
21989
|
name: "ls",
|
|
21011
21990
|
label: "ls",
|
|
21991
|
+
readOnly: true,
|
|
21012
21992
|
description: `List the contents of a directory. Names are returned sorted alphabetically with dotfiles kept in, and subdirectories carry a trailing '/'. The listing halts at ${DEFAULT_LIMIT3} entries or ${DEFAULT_MAX_BYTES / 1024}KB of output, whichever it reaches first.`,
|
|
21013
21993
|
parameters: lsSchema,
|
|
21014
21994
|
execute: async (_toolCallId, { path: path9, limit }, signal) => {
|
|
@@ -22435,24 +23415,25 @@ function withAbort2(signal, task) {
|
|
|
22435
23415
|
});
|
|
22436
23416
|
}
|
|
22437
23417
|
var ReadExecutionPipeline = class {
|
|
22438
|
-
constructor(cwd, ops, autoResizeImages) {
|
|
23418
|
+
constructor(cwd, ops, autoResizeImages, readState) {
|
|
22439
23419
|
this.cwd = cwd;
|
|
22440
23420
|
this.ops = ops;
|
|
22441
23421
|
this.autoResizeImages = autoResizeImages;
|
|
23422
|
+
this.readState = readState;
|
|
22442
23423
|
}
|
|
22443
23424
|
cwd;
|
|
22444
23425
|
ops;
|
|
22445
23426
|
autoResizeImages;
|
|
23427
|
+
readState;
|
|
22446
23428
|
async run(input, throwIfAborted2) {
|
|
22447
23429
|
const absolutePath = resolveReadPath(input.path, this.cwd);
|
|
22448
23430
|
await this.ops.access(absolutePath);
|
|
22449
23431
|
throwIfAborted2();
|
|
22450
23432
|
const mimeType = this.ops.detectImageMimeType ? await this.ops.detectImageMimeType(absolutePath) : void 0;
|
|
22451
23433
|
throwIfAborted2();
|
|
22452
|
-
|
|
22453
|
-
|
|
22454
|
-
|
|
22455
|
-
return this.readTextVariant(absolutePath, input.path, input.offset, input.limit, throwIfAborted2);
|
|
23434
|
+
const result = mimeType ? await this.readImageVariant(absolutePath, mimeType, throwIfAborted2) : await this.readTextVariant(absolutePath, input.path, input.offset, input.limit, throwIfAborted2);
|
|
23435
|
+
recordReadState(this.readState, absolutePath);
|
|
23436
|
+
return result;
|
|
22456
23437
|
}
|
|
22457
23438
|
async readImageVariant(absolutePath, mimeType, throwIfAborted2) {
|
|
22458
23439
|
const buffer = await this.ops.readFile(absolutePath);
|
|
@@ -22500,12 +23481,14 @@ ${dimensionNote}`;
|
|
|
22500
23481
|
renderedText = `[Line ${startLineDisplay} weighs ${firstLineSize}, over the ${formatSize(DEFAULT_MAX_BYTES)} cap. Pull a byte slice via bash: sed -n '${startLineDisplay}p' ${requestedPath} | head -c ${DEFAULT_MAX_BYTES}]`;
|
|
22501
23482
|
details = { truncation };
|
|
22502
23483
|
} else if (truncation.truncated) {
|
|
22503
|
-
|
|
23484
|
+
const gutteredBody = this.addGutter(truncation.content, startLineDisplay);
|
|
23485
|
+
renderedText = this.formatTruncationNotice(gutteredBody, truncation, totalFileLines, startLineDisplay);
|
|
22504
23486
|
details = { truncation };
|
|
22505
23487
|
} else if (requestedLineCount !== void 0 && startLine + requestedLineCount < fileLines.length) {
|
|
22506
|
-
|
|
23488
|
+
const gutteredBody = this.addGutter(truncation.content, startLineDisplay);
|
|
23489
|
+
renderedText = this.formatUserLimitNotice(gutteredBody, fileLines.length, startLine, requestedLineCount);
|
|
22507
23490
|
} else {
|
|
22508
|
-
renderedText = truncation.content;
|
|
23491
|
+
renderedText = this.addGutter(truncation.content, startLineDisplay);
|
|
22509
23492
|
}
|
|
22510
23493
|
throwIfAborted2();
|
|
22511
23494
|
return {
|
|
@@ -22533,14 +23516,30 @@ ${dimensionNote}`;
|
|
|
22533
23516
|
requestedLineCount: void 0
|
|
22534
23517
|
};
|
|
22535
23518
|
}
|
|
22536
|
-
formatTruncationNotice(truncation, totalFileLines, startLineDisplay) {
|
|
23519
|
+
formatTruncationNotice(body, truncation, totalFileLines, startLineDisplay) {
|
|
22537
23520
|
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
22538
23521
|
const nextOffset = endLineDisplay + 1;
|
|
22539
23522
|
const continuationHint = this.buildContinuationHint(truncation, totalFileLines, startLineDisplay, endLineDisplay, nextOffset);
|
|
22540
|
-
return `${
|
|
23523
|
+
return `${body}
|
|
22541
23524
|
|
|
22542
23525
|
${continuationHint}`;
|
|
22543
23526
|
}
|
|
23527
|
+
/**
|
|
23528
|
+
* Prefix each line with a right-aligned 1-based absolute line number in the
|
|
23529
|
+
* cat -n style, giving the model stable edit/diff coordinates. `content` is
|
|
23530
|
+
* already windowed; `startLineDisplay` is the whole-file number of its first
|
|
23531
|
+
* line so the gutter stays correct under offset/limit paging. Empty content
|
|
23532
|
+
* (e.g. binary or an over-budget first line yielding "") passes through
|
|
23533
|
+
* unchanged so non-text output stays stable.
|
|
23534
|
+
*/
|
|
23535
|
+
addGutter(content, startLineDisplay) {
|
|
23536
|
+
if (content.length === 0) return content;
|
|
23537
|
+
return content.split(/\r?\n/).map((line, i) => {
|
|
23538
|
+
const numStr = String(startLineDisplay + i);
|
|
23539
|
+
const label = numStr.length >= 6 ? numStr : numStr.padStart(6, " ");
|
|
23540
|
+
return `${label}\u2192${line}`;
|
|
23541
|
+
}).join("\n");
|
|
23542
|
+
}
|
|
22544
23543
|
formatUserLimitNotice(baseText, totalLines, startLine, requestedLineCount) {
|
|
22545
23544
|
const remaining = totalLines - (startLine + requestedLineCount);
|
|
22546
23545
|
const nextOffset = startLine + requestedLineCount + 1;
|
|
@@ -22558,11 +23557,12 @@ ${continuationHint}`;
|
|
|
22558
23557
|
function createReadTool(cwd, options) {
|
|
22559
23558
|
const autoResizeImages = options?.autoResizeImages ?? true;
|
|
22560
23559
|
const ops = options?.operations ?? defaultReadOperations;
|
|
22561
|
-
const pipeline = new ReadExecutionPipeline(cwd, ops, autoResizeImages);
|
|
23560
|
+
const pipeline = new ReadExecutionPipeline(cwd, ops, autoResizeImages, options?.readState);
|
|
22562
23561
|
return {
|
|
22563
23562
|
name: "read",
|
|
22564
23563
|
label: "read",
|
|
22565
|
-
|
|
23564
|
+
readOnly: true,
|
|
23565
|
+
description: `Open a file and return its contents. Both text and images (jpg, png, gif, webp) work; images come back as attachments. Output uses cat -n format - each line is prefixed with its 1-based line number. Text output is capped at ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB, whichever comes first. For big files, page through with offset and limit, advancing offset until you have read everything you need.`,
|
|
22566
23566
|
parameters: readSchema,
|
|
22567
23567
|
execute: async (_toolCallId, { path: path9, offset, limit }, signal) => {
|
|
22568
23568
|
return withAbort2(signal, async ({ throwIfAborted: throwIfAborted2 }) => {
|
|
@@ -22579,9 +23579,9 @@ import { Type as Type15 } from "@sinclair/typebox";
|
|
|
22579
23579
|
// src/facade/bot/actions/process-manager.ts
|
|
22580
23580
|
import { spawn as spawn3 } from "node:child_process";
|
|
22581
23581
|
import { EventEmitter } from "node:events";
|
|
22582
|
-
import { appendFileSync, existsSync as existsSync5, mkdirSync, readFileSync as
|
|
22583
|
-
import { tmpdir as
|
|
22584
|
-
import { isAbsolute as isAbsolute2, join as
|
|
23582
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync, readFileSync as readFileSync3, rmSync, statSync as statSync6 } from "node:fs";
|
|
23583
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
23584
|
+
import { isAbsolute as isAbsolute2, join as join3 } from "node:path";
|
|
22585
23585
|
|
|
22586
23586
|
// src/facade/bot/actions/process-types.ts
|
|
22587
23587
|
var MESSAGE_TYPE_PROCESS_UPDATE = "ad-process:update";
|
|
@@ -22646,7 +23646,7 @@ var ProcessManager = class {
|
|
|
22646
23646
|
watcher = null;
|
|
22647
23647
|
getConfiguredShellPath;
|
|
22648
23648
|
constructor(options) {
|
|
22649
|
-
this.logDir =
|
|
23649
|
+
this.logDir = join3(tmpdir3(), `indusagi-processes-${Date.now()}`);
|
|
22650
23650
|
mkdirSync(this.logDir, { recursive: true });
|
|
22651
23651
|
this.getConfiguredShellPath = options?.getConfiguredShellPath ?? (() => void 0);
|
|
22652
23652
|
}
|
|
@@ -22715,9 +23715,9 @@ var ProcessManager = class {
|
|
|
22715
23715
|
}
|
|
22716
23716
|
start(name, command, cwd, options) {
|
|
22717
23717
|
const id = `proc_${++this.counter}`;
|
|
22718
|
-
const stdoutFile =
|
|
22719
|
-
const stderrFile =
|
|
22720
|
-
const combinedFile =
|
|
23718
|
+
const stdoutFile = join3(this.logDir, `${id}-stdout.log`);
|
|
23719
|
+
const stderrFile = join3(this.logDir, `${id}-stderr.log`);
|
|
23720
|
+
const combinedFile = join3(this.logDir, `${id}-combined.log`);
|
|
22721
23721
|
appendFileSync(stdoutFile, "");
|
|
22722
23722
|
appendFileSync(stderrFile, "");
|
|
22723
23723
|
appendFileSync(combinedFile, "");
|
|
@@ -22867,8 +23867,8 @@ var ProcessManager = class {
|
|
|
22867
23867
|
}
|
|
22868
23868
|
try {
|
|
22869
23869
|
return {
|
|
22870
|
-
stdout:
|
|
22871
|
-
stderr:
|
|
23870
|
+
stdout: readFileSync3(managed.stdoutFile, "utf-8"),
|
|
23871
|
+
stderr: readFileSync3(managed.stderrFile, "utf-8")
|
|
22872
23872
|
};
|
|
22873
23873
|
} catch {
|
|
22874
23874
|
return { stdout: "", stderr: "" };
|
|
@@ -23024,8 +24024,8 @@ var ProcessManager = class {
|
|
|
23024
24024
|
}
|
|
23025
24025
|
try {
|
|
23026
24026
|
return {
|
|
23027
|
-
stdout:
|
|
23028
|
-
stderr:
|
|
24027
|
+
stdout: statSync6(managed.stdoutFile).size,
|
|
24028
|
+
stderr: statSync6(managed.stderrFile).size
|
|
23029
24029
|
};
|
|
23030
24030
|
} catch {
|
|
23031
24031
|
return { stdout: 0, stderr: 0 };
|
|
@@ -23033,7 +24033,7 @@ var ProcessManager = class {
|
|
|
23033
24033
|
}
|
|
23034
24034
|
readTailLines(filePath, lines) {
|
|
23035
24035
|
try {
|
|
23036
|
-
const content =
|
|
24036
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
23037
24037
|
const allLines = content.split("\n");
|
|
23038
24038
|
if (allLines.length > 0 && allLines[allLines.length - 1] === "") {
|
|
23039
24039
|
allLines.pop();
|
|
@@ -23596,6 +24596,7 @@ function createTodoReadTool(store) {
|
|
|
23596
24596
|
return {
|
|
23597
24597
|
name: "todoread",
|
|
23598
24598
|
label: "todoread",
|
|
24599
|
+
readOnly: true,
|
|
23599
24600
|
description: "Read the current todo list. Returns items with content, status, and priority.",
|
|
23600
24601
|
parameters: TodoReadSchema,
|
|
23601
24602
|
execute: async () => {
|
|
@@ -23736,6 +24737,7 @@ function createWebFetchTool(options) {
|
|
|
23736
24737
|
return {
|
|
23737
24738
|
name: "webfetch",
|
|
23738
24739
|
label: "webfetch",
|
|
24740
|
+
readOnly: true,
|
|
23739
24741
|
description: "Fetches content from a specified URL. Takes a URL and optional format as input. Fetches URL content, converts to requested format (markdown by default). Returns content in the specified format. Use this tool when you need to retrieve and analyze web content. The URL must be a fully-formed valid URL starting with http:// or https://. Format options: 'markdown' (default), 'text', or 'html'. This tool is read-only and does not modify any files.",
|
|
23740
24742
|
parameters: webFetchSchema,
|
|
23741
24743
|
execute: async (_toolCallId, params, signal) => {
|
|
@@ -23832,6 +24834,62 @@ var webFetchTool = createWebFetchTool();
|
|
|
23832
24834
|
|
|
23833
24835
|
// src/facade/bot/actions/websearch.ts
|
|
23834
24836
|
import { Type as Type18 } from "@sinclair/typebox";
|
|
24837
|
+
var SEARCH_ENDPOINT = "https://html.duckduckgo.com/html/";
|
|
24838
|
+
var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36";
|
|
24839
|
+
function toPlainText(html) {
|
|
24840
|
+
const withoutTags = html.replace(/<[^>]*>/g, "");
|
|
24841
|
+
const decoded = withoutTags.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'").replace(/ /g, " ");
|
|
24842
|
+
return decoded.replace(/\s+/g, " ").trim();
|
|
24843
|
+
}
|
|
24844
|
+
function unwrapTarget(href) {
|
|
24845
|
+
let candidate = href;
|
|
24846
|
+
const redirectMatch = /[?&]uddg=([^&]+)/.exec(candidate);
|
|
24847
|
+
if (redirectMatch) {
|
|
24848
|
+
try {
|
|
24849
|
+
candidate = decodeURIComponent(redirectMatch[1]);
|
|
24850
|
+
} catch {
|
|
24851
|
+
}
|
|
24852
|
+
}
|
|
24853
|
+
if (candidate.startsWith("//")) {
|
|
24854
|
+
candidate = `https:${candidate}`;
|
|
24855
|
+
}
|
|
24856
|
+
return candidate;
|
|
24857
|
+
}
|
|
24858
|
+
function extractHits(body, limit) {
|
|
24859
|
+
const hits = [];
|
|
24860
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24861
|
+
const titleAnchor = /<a\b[^>]*\bclass="[^"]*\bresult__a\b[^"]*"[^>]*\bhref="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
24862
|
+
const snippetBlock = /\bclass="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/[a-z]+>/i;
|
|
24863
|
+
let match;
|
|
24864
|
+
while ((match = titleAnchor.exec(body)) !== null) {
|
|
24865
|
+
if (hits.length >= limit) break;
|
|
24866
|
+
const url = unwrapTarget(match[1]);
|
|
24867
|
+
const title = toPlainText(match[2]);
|
|
24868
|
+
if (url.length === 0 || title.length === 0) continue;
|
|
24869
|
+
if (seen.has(url)) continue;
|
|
24870
|
+
const tail = body.slice(titleAnchor.lastIndex);
|
|
24871
|
+
const snippetMatch = snippetBlock.exec(tail);
|
|
24872
|
+
const snippet = snippetMatch ? toPlainText(snippetMatch[1]) : "";
|
|
24873
|
+
seen.add(url);
|
|
24874
|
+
hits.push({ title, url, snippet });
|
|
24875
|
+
}
|
|
24876
|
+
return hits;
|
|
24877
|
+
}
|
|
24878
|
+
function renderHits(query, hits) {
|
|
24879
|
+
const lines = [
|
|
24880
|
+
`${hits.length} result${hits.length === 1 ? "" : "s"} for "${query}":`,
|
|
24881
|
+
""
|
|
24882
|
+
];
|
|
24883
|
+
hits.forEach((hit, index) => {
|
|
24884
|
+
lines.push(`${index + 1}. ${hit.title}`);
|
|
24885
|
+
lines.push(` ${hit.url}`);
|
|
24886
|
+
if (hit.snippet.length > 0) {
|
|
24887
|
+
lines.push(` ${hit.snippet}`);
|
|
24888
|
+
}
|
|
24889
|
+
lines.push("");
|
|
24890
|
+
});
|
|
24891
|
+
return lines.join("\n").trimEnd();
|
|
24892
|
+
}
|
|
23835
24893
|
var webSearchSchema = Type18.Object({
|
|
23836
24894
|
query: Type18.String({ description: "Web search query" }),
|
|
23837
24895
|
numResults: Type18.Optional(
|
|
@@ -23843,48 +24901,31 @@ var requestCount = 0;
|
|
|
23843
24901
|
async function defaultWebSearch(query, numResults, signal) {
|
|
23844
24902
|
const count = Math.min(numResults ?? 8, 10);
|
|
23845
24903
|
try {
|
|
23846
|
-
const
|
|
23847
|
-
const response = await fetch(
|
|
24904
|
+
const form = new URLSearchParams({ q: query });
|
|
24905
|
+
const response = await fetch(SEARCH_ENDPOINT, {
|
|
24906
|
+
method: "POST",
|
|
23848
24907
|
signal,
|
|
23849
24908
|
headers: {
|
|
23850
|
-
"
|
|
23851
|
-
|
|
24909
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
24910
|
+
"User-Agent": USER_AGENT,
|
|
24911
|
+
Accept: "text/html"
|
|
24912
|
+
},
|
|
24913
|
+
body: form.toString()
|
|
23852
24914
|
});
|
|
23853
24915
|
if (!response.ok) {
|
|
23854
24916
|
throw new Error(`Search request failed: ${response.status}`);
|
|
23855
24917
|
}
|
|
23856
|
-
const
|
|
23857
|
-
const
|
|
23858
|
-
if (
|
|
23859
|
-
results.push(`**Summary:** ${data.AbstractText}`);
|
|
23860
|
-
if (data.AbstractURL) {
|
|
23861
|
-
results.push(`Source: ${data.AbstractURL}`);
|
|
23862
|
-
}
|
|
23863
|
-
}
|
|
23864
|
-
if (data.RelatedTopics && Array.isArray(data.RelatedTopics)) {
|
|
23865
|
-
const topics = data.RelatedTopics.filter((t) => t.Text && t.FirstURL).slice(0, count);
|
|
23866
|
-
if (topics.length > 0) {
|
|
23867
|
-
results.push(`
|
|
23868
|
-
**Related Results:**`);
|
|
23869
|
-
for (const topic of topics) {
|
|
23870
|
-
results.push(`- ${topic.Text}`);
|
|
23871
|
-
results.push(` URL: ${topic.FirstURL}`);
|
|
23872
|
-
}
|
|
23873
|
-
}
|
|
23874
|
-
}
|
|
23875
|
-
if (results.length === 0) {
|
|
24918
|
+
const body = await response.text();
|
|
24919
|
+
const hits = extractHits(body, count);
|
|
24920
|
+
if (hits.length === 0) {
|
|
23876
24921
|
return `No results found for: "${query}". Try a different search query.`;
|
|
23877
24922
|
}
|
|
23878
|
-
return
|
|
23879
|
-
|
|
23880
|
-
${results.join("\n")}`;
|
|
24923
|
+
return renderHits(query, hits);
|
|
23881
24924
|
} catch (error) {
|
|
23882
|
-
if (error
|
|
24925
|
+
if (error?.name === "AbortError") {
|
|
23883
24926
|
throw new Error("Web search request timed out or was aborted");
|
|
23884
24927
|
}
|
|
23885
|
-
|
|
23886
|
-
|
|
23887
|
-
Unable to perform live search. Please try again or use a different query.`;
|
|
24928
|
+
throw error;
|
|
23888
24929
|
}
|
|
23889
24930
|
}
|
|
23890
24931
|
function createWebSearchTool(options) {
|
|
@@ -23892,6 +24933,7 @@ function createWebSearchTool(options) {
|
|
|
23892
24933
|
return {
|
|
23893
24934
|
name: "websearch",
|
|
23894
24935
|
label: "websearch",
|
|
24936
|
+
readOnly: true,
|
|
23895
24937
|
description: "Search the web - performs real-time web searches and can scrape content from specific URLs. Provides up-to-date information for current events and recent data. Use this tool for accessing information beyond knowledge cutoff. The current year is 2026. You MUST use this year when searching for recent information or current events (e.g., search for 'AI news 2026', NOT 'AI news 2025').",
|
|
23896
24938
|
parameters: webSearchSchema,
|
|
23897
24939
|
execute: async (_toolCallId, { query, numResults }, signal) => {
|
|
@@ -23950,8 +24992,9 @@ var webSearchTool = createWebSearchTool();
|
|
|
23950
24992
|
|
|
23951
24993
|
// src/facade/bot/actions/write.ts
|
|
23952
24994
|
import { Type as Type19 } from "@sinclair/typebox";
|
|
24995
|
+
import { existsSync as existsSync6 } from "fs";
|
|
23953
24996
|
import { copyFile as fsCopyFile, mkdir as fsMkdir, writeFile as fsWriteFile2 } from "fs/promises";
|
|
23954
|
-
import { dirname } from "path";
|
|
24997
|
+
import { dirname as dirname2 } from "path";
|
|
23955
24998
|
var writeSchema = Type19.Object({
|
|
23956
24999
|
path: Type19.String({ description: "Destination file, given as a relative or absolute path" }),
|
|
23957
25000
|
content: Type19.String({ description: "The text that will become the file's contents" })
|
|
@@ -23970,22 +25013,48 @@ var FileValidator = class {
|
|
|
23970
25013
|
}
|
|
23971
25014
|
};
|
|
23972
25015
|
var WriteSession = class {
|
|
23973
|
-
constructor(cwd, relativePath, fileContent, operations, createBackup) {
|
|
25016
|
+
constructor(cwd, relativePath, fileContent, operations, createBackup, readState, checkpoint) {
|
|
23974
25017
|
this.cwd = cwd;
|
|
23975
25018
|
this.relativePath = relativePath;
|
|
23976
25019
|
this.fileContent = fileContent;
|
|
23977
25020
|
this.operations = operations;
|
|
23978
25021
|
this.createBackup = createBackup;
|
|
25022
|
+
this.readState = readState;
|
|
25023
|
+
this.checkpoint = checkpoint;
|
|
23979
25024
|
this.absolutePath = resolveToCwd(relativePath, cwd);
|
|
23980
|
-
this.targetDir =
|
|
25025
|
+
this.targetDir = dirname2(this.absolutePath);
|
|
23981
25026
|
}
|
|
23982
25027
|
cwd;
|
|
23983
25028
|
relativePath;
|
|
23984
25029
|
fileContent;
|
|
23985
25030
|
operations;
|
|
23986
25031
|
createBackup;
|
|
25032
|
+
readState;
|
|
25033
|
+
checkpoint;
|
|
23987
25034
|
absolutePath;
|
|
23988
25035
|
targetDir;
|
|
25036
|
+
/**
|
|
25037
|
+
* Enforce the read-before-edit + staleness gate ahead of an OVERWRITE.
|
|
25038
|
+
* Brand-new files (those not yet on disk) are exempt — there is nothing the
|
|
25039
|
+
* model could have clobbered. No-op without a handle.
|
|
25040
|
+
*/
|
|
25041
|
+
enforceGate() {
|
|
25042
|
+
if (!this.readState) return;
|
|
25043
|
+
if (!existsSync6(this.absolutePath)) return;
|
|
25044
|
+
const gate = enforceReadGate(this.readState, this.absolutePath);
|
|
25045
|
+
if (!gate.ok) {
|
|
25046
|
+
throw new Error(gate.message);
|
|
25047
|
+
}
|
|
25048
|
+
}
|
|
25049
|
+
/**
|
|
25050
|
+
* Snapshot the file's pre-mutation on-disk content for rewind. Runs AFTER the
|
|
25051
|
+
* read-state gate and BEFORE any mutation (mkdir/write), so the captured bytes
|
|
25052
|
+
* are the OLD content; a brand-new file is recorded as `null`. No-op without a
|
|
25053
|
+
* handle.
|
|
25054
|
+
*/
|
|
25055
|
+
recordCheckpoint() {
|
|
25056
|
+
recordCheckpoint(this.checkpoint, this.absolutePath);
|
|
25057
|
+
}
|
|
23989
25058
|
async prepareDirectories() {
|
|
23990
25059
|
await this.operations.mkdir(this.targetDir);
|
|
23991
25060
|
}
|
|
@@ -24000,6 +25069,7 @@ var WriteSession = class {
|
|
|
24000
25069
|
}
|
|
24001
25070
|
async persistContent() {
|
|
24002
25071
|
await this.operations.writeFile(this.absolutePath, this.fileContent);
|
|
25072
|
+
recordReadState(this.readState, this.absolutePath);
|
|
24003
25073
|
}
|
|
24004
25074
|
buildResponse() {
|
|
24005
25075
|
return {
|
|
@@ -24064,8 +25134,20 @@ function createWriteTool(cwd, options) {
|
|
|
24064
25134
|
parameters: writeSchema,
|
|
24065
25135
|
execute: async (_toolCallId, { path: path9, content }, signal) => {
|
|
24066
25136
|
validator.validate(path9);
|
|
24067
|
-
const writeSession = new WriteSession(
|
|
25137
|
+
const writeSession = new WriteSession(
|
|
25138
|
+
cwd,
|
|
25139
|
+
path9,
|
|
25140
|
+
content,
|
|
25141
|
+
operations,
|
|
25142
|
+
Boolean(options?.createBackup),
|
|
25143
|
+
options?.readState,
|
|
25144
|
+
options?.checkpoint
|
|
25145
|
+
);
|
|
24068
25146
|
return withAbort3(signal, async (isAborted) => {
|
|
25147
|
+
writeSession.enforceGate();
|
|
25148
|
+
throwIfAborted(isAborted);
|
|
25149
|
+
writeSession.recordCheckpoint();
|
|
25150
|
+
throwIfAborted(isAborted);
|
|
24069
25151
|
await writeSession.prepareDirectories();
|
|
24070
25152
|
throwIfAborted(isAborted);
|
|
24071
25153
|
await writeSession.backupIfEnabled();
|
|
@@ -24689,13 +25771,13 @@ async function gcStaleTeamDirs(opts) {
|
|
|
24689
25771
|
continue;
|
|
24690
25772
|
}
|
|
24691
25773
|
const teamDir = path4.join(teamsRootAbs, teamId);
|
|
24692
|
-
let
|
|
25774
|
+
let stat3;
|
|
24693
25775
|
try {
|
|
24694
|
-
|
|
25776
|
+
stat3 = await fs2.promises.stat(teamDir);
|
|
24695
25777
|
} catch {
|
|
24696
25778
|
continue;
|
|
24697
25779
|
}
|
|
24698
|
-
if (!
|
|
25780
|
+
if (!stat3.isDirectory()) continue;
|
|
24699
25781
|
let ageMs;
|
|
24700
25782
|
try {
|
|
24701
25783
|
const configPath = path4.join(teamDir, "config.json");
|
|
@@ -24704,12 +25786,12 @@ async function gcStaleTeamDirs(opts) {
|
|
|
24704
25786
|
const createdAt = typeof config === "object" && config !== null && "createdAt" in config ? config.createdAt : void 0;
|
|
24705
25787
|
if (typeof createdAt === "string") {
|
|
24706
25788
|
const ts = Date.parse(createdAt);
|
|
24707
|
-
ageMs = Number.isFinite(ts) ? now - ts : now -
|
|
25789
|
+
ageMs = Number.isFinite(ts) ? now - ts : now - stat3.mtimeMs;
|
|
24708
25790
|
} else {
|
|
24709
|
-
ageMs = now -
|
|
25791
|
+
ageMs = now - stat3.mtimeMs;
|
|
24710
25792
|
}
|
|
24711
25793
|
} catch {
|
|
24712
|
-
ageMs = now -
|
|
25794
|
+
ageMs = now - stat3.mtimeMs;
|
|
24713
25795
|
}
|
|
24714
25796
|
if (ageMs < maxAgeMs) {
|
|
24715
25797
|
skipped.push({ teamId, reason: "too recent" });
|
|
@@ -26352,17 +27434,17 @@ import { homedir as homedir2 } from "os";
|
|
|
26352
27434
|
import {
|
|
26353
27435
|
appendFileSync as appendFileSync2,
|
|
26354
27436
|
closeSync as closeSync2,
|
|
26355
|
-
existsSync as
|
|
27437
|
+
existsSync as existsSync8,
|
|
26356
27438
|
mkdirSync as mkdirSync2,
|
|
26357
27439
|
openSync as openSync2,
|
|
26358
|
-
readdirSync as
|
|
26359
|
-
readFileSync as
|
|
27440
|
+
readdirSync as readdirSync5,
|
|
27441
|
+
readFileSync as readFileSync4,
|
|
26360
27442
|
readSync,
|
|
26361
|
-
statSync as
|
|
27443
|
+
statSync as statSync8,
|
|
26362
27444
|
writeFileSync as writeFileSync2
|
|
26363
27445
|
} from "fs";
|
|
26364
|
-
import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
26365
|
-
import { join as
|
|
27446
|
+
import { readdir, readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
27447
|
+
import { join as join10, resolve as resolve4 } from "path";
|
|
26366
27448
|
var CURRENT_SESSION_VERSION = 3;
|
|
26367
27449
|
function generateId(byId) {
|
|
26368
27450
|
let attempt = 0;
|
|
@@ -26542,11 +27624,11 @@ function getDefaultAgentDir() {
|
|
|
26542
27624
|
return expandHomePath(explicitAgentDir);
|
|
26543
27625
|
}
|
|
26544
27626
|
const rawBase = process.env[ENV_BASE_DIR];
|
|
26545
|
-
const baseDir = rawBase ? expandHomePath(rawBase) :
|
|
26546
|
-
return
|
|
27627
|
+
const baseDir = rawBase ? expandHomePath(rawBase) : join10(homedir2(), DEFAULT_CONFIG_DIR);
|
|
27628
|
+
return join10(baseDir, "agent");
|
|
26547
27629
|
}
|
|
26548
27630
|
function getSessionsDir() {
|
|
26549
|
-
return
|
|
27631
|
+
return join10(getDefaultAgentDir(), "sessions");
|
|
26550
27632
|
}
|
|
26551
27633
|
function encodeCwdForDir(cwd) {
|
|
26552
27634
|
const trimmedLeadingSlash = cwd.replace(/^[/\\]/, "");
|
|
@@ -26554,8 +27636,8 @@ function encodeCwdForDir(cwd) {
|
|
|
26554
27636
|
return `--${sanitized}--`;
|
|
26555
27637
|
}
|
|
26556
27638
|
function getDefaultSessionDir(cwd) {
|
|
26557
|
-
const sessionDir =
|
|
26558
|
-
if (!
|
|
27639
|
+
const sessionDir = join10(getDefaultAgentDir(), "sessions", encodeCwdForDir(cwd));
|
|
27640
|
+
if (!existsSync8(sessionDir)) {
|
|
26559
27641
|
mkdirSync2(sessionDir, { recursive: true });
|
|
26560
27642
|
}
|
|
26561
27643
|
return sessionDir;
|
|
@@ -26565,10 +27647,10 @@ function hasValidSessionHeader(entries) {
|
|
|
26565
27647
|
return first !== void 0 && first.type === "session" && typeof first.id === "string";
|
|
26566
27648
|
}
|
|
26567
27649
|
function loadEntriesFromFile(filePath) {
|
|
26568
|
-
if (!
|
|
27650
|
+
if (!existsSync8(filePath)) {
|
|
26569
27651
|
return [];
|
|
26570
27652
|
}
|
|
26571
|
-
const entries = decodeJsonlLines(
|
|
27653
|
+
const entries = decodeJsonlLines(readFileSync4(filePath, "utf8"));
|
|
26572
27654
|
if (entries.length === 0) {
|
|
26573
27655
|
return entries;
|
|
26574
27656
|
}
|
|
@@ -26596,7 +27678,7 @@ function isValidSessionFile(filePath) {
|
|
|
26596
27678
|
}
|
|
26597
27679
|
function findMostRecentSession(sessionDir) {
|
|
26598
27680
|
try {
|
|
26599
|
-
const ranked =
|
|
27681
|
+
const ranked = readdirSync5(sessionDir).filter((f) => f.endsWith(".jsonl")).map((f) => join10(sessionDir, f)).filter(isValidSessionFile).map((path9) => ({ path: path9, mtime: statSync8(path9).mtime })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
26600
27682
|
return ranked[0]?.path || null;
|
|
26601
27683
|
} catch {
|
|
26602
27684
|
return null;
|
|
@@ -26665,7 +27747,7 @@ async function buildSessionInfo(filePath) {
|
|
|
26665
27747
|
return null;
|
|
26666
27748
|
}
|
|
26667
27749
|
const sessionHeader = header;
|
|
26668
|
-
const stats = await
|
|
27750
|
+
const stats = await stat2(filePath);
|
|
26669
27751
|
let messageCount = 0;
|
|
26670
27752
|
let firstMessage = "";
|
|
26671
27753
|
let name;
|
|
@@ -26710,11 +27792,11 @@ async function buildSessionInfo(filePath) {
|
|
|
26710
27792
|
}
|
|
26711
27793
|
}
|
|
26712
27794
|
async function listSessionsFromDir(dir, onProgress, progressOffset = 0, progressTotal) {
|
|
26713
|
-
if (!
|
|
27795
|
+
if (!existsSync8(dir)) {
|
|
26714
27796
|
return [];
|
|
26715
27797
|
}
|
|
26716
27798
|
try {
|
|
26717
|
-
const files = (await readdir(dir)).filter((f) => f.endsWith(".jsonl")).map((f) =>
|
|
27799
|
+
const files = (await readdir(dir)).filter((f) => f.endsWith(".jsonl")).map((f) => join10(dir, f));
|
|
26718
27800
|
const total = progressTotal ?? files.length;
|
|
26719
27801
|
let loaded = 0;
|
|
26720
27802
|
const results = await Promise.all(
|
|
@@ -26745,7 +27827,7 @@ var SessionManager = class _SessionManager {
|
|
|
26745
27827
|
this.cwd = cwd;
|
|
26746
27828
|
this.sessionDir = sessionDir;
|
|
26747
27829
|
this.persist = persist;
|
|
26748
|
-
if (persist && sessionDir && !
|
|
27830
|
+
if (persist && sessionDir && !existsSync8(sessionDir)) {
|
|
26749
27831
|
mkdirSync2(sessionDir, { recursive: true });
|
|
26750
27832
|
}
|
|
26751
27833
|
if (sessionFile) {
|
|
@@ -26758,7 +27840,7 @@ var SessionManager = class _SessionManager {
|
|
|
26758
27840
|
setSessionFile(sessionFile) {
|
|
26759
27841
|
const resolved = resolve4(sessionFile);
|
|
26760
27842
|
this.sessionFile = resolved;
|
|
26761
|
-
if (!
|
|
27843
|
+
if (!existsSync8(resolved)) {
|
|
26762
27844
|
this.newSession();
|
|
26763
27845
|
this.sessionFile = resolved;
|
|
26764
27846
|
return;
|
|
@@ -26797,7 +27879,7 @@ var SessionManager = class _SessionManager {
|
|
|
26797
27879
|
this.flushed = false;
|
|
26798
27880
|
if (this.persist) {
|
|
26799
27881
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
26800
|
-
this.sessionFile =
|
|
27882
|
+
this.sessionFile = join10(this.getSessionDir(), `${fileTimestamp}_${this.sessionId}.jsonl`);
|
|
26801
27883
|
}
|
|
26802
27884
|
return this.sessionFile;
|
|
26803
27885
|
}
|
|
@@ -27153,7 +28235,7 @@ var SessionManager = class _SessionManager {
|
|
|
27153
28235
|
const newSessionId = randomUUID();
|
|
27154
28236
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
27155
28237
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
27156
|
-
const newSessionFile =
|
|
28238
|
+
const newSessionFile = join10(this.getSessionDir(), `${fileTimestamp}_${newSessionId}.jsonl`);
|
|
27157
28239
|
const header = {
|
|
27158
28240
|
type: "session",
|
|
27159
28241
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -27270,13 +28352,13 @@ var SessionManager = class _SessionManager {
|
|
|
27270
28352
|
throw new Error(`Fork aborted \u2014 source session ${sourcePath} is missing its header record`);
|
|
27271
28353
|
}
|
|
27272
28354
|
const dir = sessionDir ?? getDefaultSessionDir(targetCwd);
|
|
27273
|
-
if (!
|
|
28355
|
+
if (!existsSync8(dir)) {
|
|
27274
28356
|
mkdirSync2(dir, { recursive: true });
|
|
27275
28357
|
}
|
|
27276
28358
|
const newSessionId = randomUUID();
|
|
27277
28359
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
27278
28360
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
27279
|
-
const newSessionFile =
|
|
28361
|
+
const newSessionFile = join10(dir, `${fileTimestamp}_${newSessionId}.jsonl`);
|
|
27280
28362
|
const newHeader = {
|
|
27281
28363
|
type: "session",
|
|
27282
28364
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -27314,16 +28396,16 @@ var SessionManager = class _SessionManager {
|
|
|
27314
28396
|
static async listAll(onProgress) {
|
|
27315
28397
|
const sessionsDir = getSessionsDir();
|
|
27316
28398
|
try {
|
|
27317
|
-
if (!
|
|
28399
|
+
if (!existsSync8(sessionsDir)) {
|
|
27318
28400
|
return [];
|
|
27319
28401
|
}
|
|
27320
28402
|
const rootEntries = await readdir(sessionsDir, { withFileTypes: true });
|
|
27321
|
-
const dirs = rootEntries.filter((e) => e.isDirectory()).map((e) =>
|
|
28403
|
+
const dirs = rootEntries.filter((e) => e.isDirectory()).map((e) => join10(sessionsDir, e.name));
|
|
27322
28404
|
const perDirFiles = [];
|
|
27323
28405
|
let totalFiles = 0;
|
|
27324
28406
|
for (const dir of dirs) {
|
|
27325
28407
|
try {
|
|
27326
|
-
const jsonlFiles = (await readdir(dir)).filter((f) => f.endsWith(".jsonl")).map((f) =>
|
|
28408
|
+
const jsonlFiles = (await readdir(dir)).filter((f) => f.endsWith(".jsonl")).map((f) => join10(dir, f));
|
|
27327
28409
|
perDirFiles.push(jsonlFiles);
|
|
27328
28410
|
totalFiles += jsonlFiles.length;
|
|
27329
28411
|
} catch {
|
|
@@ -27361,11 +28443,14 @@ export {
|
|
|
27361
28443
|
ComposioService,
|
|
27362
28444
|
DEFAULT_MAX_BYTES,
|
|
27363
28445
|
DEFAULT_MAX_LINES,
|
|
28446
|
+
DESANITIZATIONS,
|
|
28447
|
+
FILE_NOT_FOUND_CWD_NOTE,
|
|
27364
28448
|
IndusagiComposioProvider,
|
|
27365
28449
|
LIVE_STATUSES,
|
|
27366
28450
|
MESSAGE_TYPE_PROCESS_UPDATE,
|
|
27367
28451
|
PIRATE_NAME_POOL,
|
|
27368
28452
|
ProcessManager,
|
|
28453
|
+
READ_ONLY_TOOL_NAMES,
|
|
27369
28454
|
SessionManager,
|
|
27370
28455
|
TEAM_ATTACH_CLAIM_FILE,
|
|
27371
28456
|
TEAM_ATTACH_CLAIM_STALE_MS,
|
|
@@ -27384,10 +28469,15 @@ export {
|
|
|
27384
28469
|
agentLoop,
|
|
27385
28470
|
agentLoopContinue,
|
|
27386
28471
|
allTools,
|
|
28472
|
+
argvToCommandString,
|
|
27387
28473
|
assertTeamDirWithinTeamsRoot,
|
|
27388
28474
|
assessAttachClaimFreshness,
|
|
27389
28475
|
bashExecutionToText,
|
|
27390
28476
|
bashTool,
|
|
28477
|
+
buildBwrapArgv,
|
|
28478
|
+
buildSandboxArgv,
|
|
28479
|
+
buildSeatbeltArgv,
|
|
28480
|
+
buildSeatbeltProfile,
|
|
27391
28481
|
buildSessionContext,
|
|
27392
28482
|
claimNextAvailableTask,
|
|
27393
28483
|
claimTask,
|
|
@@ -27424,6 +28514,7 @@ export {
|
|
|
27424
28514
|
createProcessTool,
|
|
27425
28515
|
createReadOnlyTools,
|
|
27426
28516
|
createReadTool,
|
|
28517
|
+
createSandboxedBashOperations,
|
|
27427
28518
|
createTask,
|
|
27428
28519
|
createTodoReadTool,
|
|
27429
28520
|
createTodoWriteTool,
|
|
@@ -27431,11 +28522,13 @@ export {
|
|
|
27431
28522
|
createWebFetchTool,
|
|
27432
28523
|
createWebSearchTool,
|
|
27433
28524
|
createWriteTool,
|
|
28525
|
+
desanitizeMatchString,
|
|
27434
28526
|
editTool,
|
|
27435
28527
|
ensureTeamConfig,
|
|
27436
28528
|
ensureWorktreeCwd,
|
|
27437
28529
|
expandPath,
|
|
27438
28530
|
findMostRecentSession,
|
|
28531
|
+
findSimilarFile,
|
|
27439
28532
|
findTool,
|
|
27440
28533
|
formatProviderModel,
|
|
27441
28534
|
formatSize,
|
|
@@ -27477,21 +28570,25 @@ export {
|
|
|
27477
28570
|
pickNamesFromPool,
|
|
27478
28571
|
pickPirateNames,
|
|
27479
28572
|
popUnreadMessages,
|
|
28573
|
+
preserveQuoteStyle,
|
|
27480
28574
|
processTool,
|
|
27481
28575
|
readOnlyTools,
|
|
27482
28576
|
readTeamAttachClaim,
|
|
27483
28577
|
readTool,
|
|
27484
28578
|
releaseTeamAttachClaim,
|
|
27485
28579
|
removeTaskDependency,
|
|
28580
|
+
replaceAllLiteral,
|
|
27486
28581
|
resolveReadPath,
|
|
27487
28582
|
resolveTeammateModelSelection,
|
|
27488
28583
|
resolveToCwd,
|
|
28584
|
+
sandboxAvailability,
|
|
27489
28585
|
sanitizeName,
|
|
27490
28586
|
setMemberStatus,
|
|
27491
28587
|
setTeamStyle,
|
|
27492
28588
|
shortTaskId,
|
|
27493
28589
|
startAssignedTask,
|
|
27494
28590
|
streamProxy,
|
|
28591
|
+
suggestPathUnderCwd,
|
|
27495
28592
|
taskAssignmentPayload,
|
|
27496
28593
|
todoReadTool,
|
|
27497
28594
|
todoWriteTool,
|