@hasna/assistants 0.6.54 → 0.6.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -280,7 +280,34 @@ function substituteVariables(template, args, env = {}) {
|
|
|
280
280
|
});
|
|
281
281
|
return result;
|
|
282
282
|
}
|
|
283
|
-
|
|
283
|
+
function getRandomLoadingWord() {
|
|
284
|
+
return LOADING_WORDS[Math.floor(Math.random() * LOADING_WORDS.length)];
|
|
285
|
+
}
|
|
286
|
+
var LOADING_WORDS;
|
|
287
|
+
var init_utils = __esm(() => {
|
|
288
|
+
LOADING_WORDS = [
|
|
289
|
+
"Metamorphosing",
|
|
290
|
+
"Conjuring",
|
|
291
|
+
"Transmuting",
|
|
292
|
+
"Alchemizing",
|
|
293
|
+
"Manifesting",
|
|
294
|
+
"Brewing",
|
|
295
|
+
"Summoning",
|
|
296
|
+
"Cogitating",
|
|
297
|
+
"Ruminating",
|
|
298
|
+
"Percolating",
|
|
299
|
+
"Incubating",
|
|
300
|
+
"Gestating",
|
|
301
|
+
"Synthesizing",
|
|
302
|
+
"Crystallizing",
|
|
303
|
+
"Catalyzing",
|
|
304
|
+
"Distilling",
|
|
305
|
+
"Fermenting",
|
|
306
|
+
"Marinating",
|
|
307
|
+
"Simmering",
|
|
308
|
+
"Concocting"
|
|
309
|
+
];
|
|
310
|
+
});
|
|
284
311
|
|
|
285
312
|
// packages/shared/src/index.ts
|
|
286
313
|
var init_src2 = __esm(() => {
|
|
@@ -9662,8 +9689,8 @@ var require_pattern2 = __commonJS((exports) => {
|
|
|
9662
9689
|
}
|
|
9663
9690
|
exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
|
|
9664
9691
|
function isAffectDepthOfReadingPattern(pattern) {
|
|
9665
|
-
const
|
|
9666
|
-
return endsWithSlashGlobStar(pattern) || isStaticPattern(
|
|
9692
|
+
const basename2 = path.basename(pattern);
|
|
9693
|
+
return endsWithSlashGlobStar(pattern) || isStaticPattern(basename2);
|
|
9667
9694
|
}
|
|
9668
9695
|
exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
|
|
9669
9696
|
function expandPatternsWithBraceExpansion(patterns) {
|
|
@@ -113535,6 +113562,118 @@ function validateBashCommand(command) {
|
|
|
113535
113562
|
return { valid: true };
|
|
113536
113563
|
}
|
|
113537
113564
|
|
|
113565
|
+
// packages/core/src/security/network-validator.ts
|
|
113566
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
113567
|
+
var lookupFn = dnsLookup;
|
|
113568
|
+
async function isPrivateHostOrResolved(hostname) {
|
|
113569
|
+
if (isPrivateHost(hostname)) {
|
|
113570
|
+
return true;
|
|
113571
|
+
}
|
|
113572
|
+
const host = normalizeHostname(hostname);
|
|
113573
|
+
if (host === "" || isIpLiteral(host)) {
|
|
113574
|
+
return isPrivateHost(host);
|
|
113575
|
+
}
|
|
113576
|
+
try {
|
|
113577
|
+
const results = await lookupFn(host, { all: true });
|
|
113578
|
+
for (const result of results) {
|
|
113579
|
+
if (isPrivateHost(result.address)) {
|
|
113580
|
+
return true;
|
|
113581
|
+
}
|
|
113582
|
+
}
|
|
113583
|
+
return false;
|
|
113584
|
+
} catch {
|
|
113585
|
+
return true;
|
|
113586
|
+
}
|
|
113587
|
+
}
|
|
113588
|
+
function isIpLiteral(host) {
|
|
113589
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host))
|
|
113590
|
+
return true;
|
|
113591
|
+
if (host.includes(":"))
|
|
113592
|
+
return true;
|
|
113593
|
+
return false;
|
|
113594
|
+
}
|
|
113595
|
+
function normalizeHostname(hostname) {
|
|
113596
|
+
let host = hostname.toLowerCase().trim();
|
|
113597
|
+
if (host.startsWith("[")) {
|
|
113598
|
+
const idx = host.indexOf("]:");
|
|
113599
|
+
if (idx > 0)
|
|
113600
|
+
host = host.slice(1, idx);
|
|
113601
|
+
else if (host.endsWith("]"))
|
|
113602
|
+
host = host.slice(1, -1);
|
|
113603
|
+
} else {
|
|
113604
|
+
const idx = host.lastIndexOf(":");
|
|
113605
|
+
const beforeColon = host.slice(0, idx);
|
|
113606
|
+
if (idx > 0 && !beforeColon.includes(":")) {
|
|
113607
|
+
host = beforeColon;
|
|
113608
|
+
}
|
|
113609
|
+
}
|
|
113610
|
+
host = host.replace(/\.$/, "");
|
|
113611
|
+
return host;
|
|
113612
|
+
}
|
|
113613
|
+
function isPrivateHost(hostname) {
|
|
113614
|
+
let host = normalizeHostname(hostname);
|
|
113615
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local"))
|
|
113616
|
+
return true;
|
|
113617
|
+
if (host === "127.0.0.1" || host === "::1" || host === "::" || host === "0:0:0:0:0:0:0:0")
|
|
113618
|
+
return true;
|
|
113619
|
+
if (/^\d+$/.test(host))
|
|
113620
|
+
return true;
|
|
113621
|
+
if (host.startsWith("::ffff:")) {
|
|
113622
|
+
const mapped = host.slice("::ffff:".length);
|
|
113623
|
+
if (mapped.includes(".")) {
|
|
113624
|
+
return isPrivateHost(mapped);
|
|
113625
|
+
}
|
|
113626
|
+
const hexMatch = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
113627
|
+
if (hexMatch) {
|
|
113628
|
+
const high = Number.parseInt(hexMatch[1], 16);
|
|
113629
|
+
const low = Number.parseInt(hexMatch[2], 16);
|
|
113630
|
+
const octets2 = [
|
|
113631
|
+
high >> 8 & 255,
|
|
113632
|
+
high & 255,
|
|
113633
|
+
low >> 8 & 255,
|
|
113634
|
+
low & 255
|
|
113635
|
+
];
|
|
113636
|
+
return isPrivateIPv4(octets2);
|
|
113637
|
+
}
|
|
113638
|
+
return false;
|
|
113639
|
+
}
|
|
113640
|
+
if (host.includes(":")) {
|
|
113641
|
+
return host === "::1" || host === "::" || host === "0:0:0:0:0:0:0:0";
|
|
113642
|
+
}
|
|
113643
|
+
const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
113644
|
+
if (!match)
|
|
113645
|
+
return false;
|
|
113646
|
+
const octets = [];
|
|
113647
|
+
for (let i = 1;i <= 4; i++) {
|
|
113648
|
+
const value = Number.parseInt(match[i], 10);
|
|
113649
|
+
if (Number.isNaN(value))
|
|
113650
|
+
return false;
|
|
113651
|
+
octets.push(value);
|
|
113652
|
+
}
|
|
113653
|
+
return isPrivateIPv4(octets);
|
|
113654
|
+
}
|
|
113655
|
+
function isPrivateIPv4(octets) {
|
|
113656
|
+
if (octets[0] === 0)
|
|
113657
|
+
return true;
|
|
113658
|
+
if (octets[0] === 10)
|
|
113659
|
+
return true;
|
|
113660
|
+
if (octets[0] === 100 && octets[1] >= 64 && octets[1] <= 127)
|
|
113661
|
+
return true;
|
|
113662
|
+
if (octets[0] === 169 && octets[1] === 254)
|
|
113663
|
+
return true;
|
|
113664
|
+
if (octets[0] === 192 && octets[1] === 168)
|
|
113665
|
+
return true;
|
|
113666
|
+
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
|
|
113667
|
+
return true;
|
|
113668
|
+
if (octets[0] === 127)
|
|
113669
|
+
return true;
|
|
113670
|
+
if (octets[0] >= 224 && octets[0] <= 239)
|
|
113671
|
+
return true;
|
|
113672
|
+
if (octets[0] >= 240)
|
|
113673
|
+
return true;
|
|
113674
|
+
return false;
|
|
113675
|
+
}
|
|
113676
|
+
|
|
113538
113677
|
// packages/core/src/tools/bash.ts
|
|
113539
113678
|
function killProcess(proc) {
|
|
113540
113679
|
proc.kill();
|
|
@@ -113664,7 +113803,8 @@ class BashTool {
|
|
|
113664
113803
|
"node --version",
|
|
113665
113804
|
"bun --version",
|
|
113666
113805
|
"npm --version",
|
|
113667
|
-
"pnpm --version"
|
|
113806
|
+
"pnpm --version",
|
|
113807
|
+
"jq"
|
|
113668
113808
|
];
|
|
113669
113809
|
static BLOCKED_PATTERNS = [
|
|
113670
113810
|
/\brm\b/,
|
|
@@ -113695,8 +113835,7 @@ class BashTool {
|
|
|
113695
113835
|
/\|\s*(bash|sh|zsh|fish)\b/,
|
|
113696
113836
|
/curl.*\|\s*(bash|sh)/,
|
|
113697
113837
|
/wget.*\|\s*(bash|sh)/,
|
|
113698
|
-
|
|
113699
|
-
/[|]/,
|
|
113838
|
+
/;/,
|
|
113700
113839
|
/[\r\n]/,
|
|
113701
113840
|
/>\s*[^|]/,
|
|
113702
113841
|
/>>/,
|
|
@@ -113716,6 +113855,110 @@ class BashTool {
|
|
|
113716
113855
|
/\bcmake\b/,
|
|
113717
113856
|
/\bdocker\s+(run|exec|build|push)\b/
|
|
113718
113857
|
];
|
|
113858
|
+
static isCommandPartAllowed(commandPart, allowlist) {
|
|
113859
|
+
const trimmed = commandPart.trim().toLowerCase();
|
|
113860
|
+
for (const allowed of allowlist) {
|
|
113861
|
+
if (trimmed.startsWith(allowed.toLowerCase())) {
|
|
113862
|
+
return true;
|
|
113863
|
+
}
|
|
113864
|
+
}
|
|
113865
|
+
return false;
|
|
113866
|
+
}
|
|
113867
|
+
static areAllCommandPartsAllowed(command, allowlist) {
|
|
113868
|
+
const parts = this.splitCommandByOperators(command);
|
|
113869
|
+
if (parts.length === 0)
|
|
113870
|
+
return false;
|
|
113871
|
+
for (const part of parts) {
|
|
113872
|
+
if (!this.isCommandPartAllowed(part, allowlist)) {
|
|
113873
|
+
return false;
|
|
113874
|
+
}
|
|
113875
|
+
}
|
|
113876
|
+
return true;
|
|
113877
|
+
}
|
|
113878
|
+
static extractCurlUrls(command) {
|
|
113879
|
+
const urls = [];
|
|
113880
|
+
const curlMatch = command.match(/^curl\s+(.+)$/i);
|
|
113881
|
+
if (!curlMatch)
|
|
113882
|
+
return urls;
|
|
113883
|
+
const args = curlMatch[1];
|
|
113884
|
+
const urlMatches = args.match(/https?:\/\/[^\s'"]+/gi);
|
|
113885
|
+
if (urlMatches) {
|
|
113886
|
+
for (const url of urlMatches) {
|
|
113887
|
+
const cleanUrl = url.replace(/[;|&<>]+$/, "");
|
|
113888
|
+
if (cleanUrl)
|
|
113889
|
+
urls.push(cleanUrl);
|
|
113890
|
+
}
|
|
113891
|
+
}
|
|
113892
|
+
return urls;
|
|
113893
|
+
}
|
|
113894
|
+
static async validateCurlSsrf(command) {
|
|
113895
|
+
const trimmed = command.trim().toLowerCase();
|
|
113896
|
+
if (!trimmed.startsWith("curl ")) {
|
|
113897
|
+
return { valid: true };
|
|
113898
|
+
}
|
|
113899
|
+
const urls = this.extractCurlUrls(command.trim());
|
|
113900
|
+
for (const urlStr of urls) {
|
|
113901
|
+
try {
|
|
113902
|
+
const url = new URL(urlStr);
|
|
113903
|
+
if (await isPrivateHostOrResolved(url.hostname)) {
|
|
113904
|
+
return { valid: false, blockedUrl: urlStr };
|
|
113905
|
+
}
|
|
113906
|
+
} catch {
|
|
113907
|
+
continue;
|
|
113908
|
+
}
|
|
113909
|
+
}
|
|
113910
|
+
return { valid: true };
|
|
113911
|
+
}
|
|
113912
|
+
static splitCommandByOperators(command) {
|
|
113913
|
+
const parts = [];
|
|
113914
|
+
let current = "";
|
|
113915
|
+
let quote = null;
|
|
113916
|
+
let escaped = false;
|
|
113917
|
+
let i = 0;
|
|
113918
|
+
while (i < command.length) {
|
|
113919
|
+
const char = command[i];
|
|
113920
|
+
if (quote === '"' && !escaped && char === "\\") {
|
|
113921
|
+
escaped = true;
|
|
113922
|
+
current += char;
|
|
113923
|
+
i++;
|
|
113924
|
+
continue;
|
|
113925
|
+
}
|
|
113926
|
+
if (!quote && (char === '"' || char === "'")) {
|
|
113927
|
+
quote = char;
|
|
113928
|
+
current += char;
|
|
113929
|
+
i++;
|
|
113930
|
+
continue;
|
|
113931
|
+
}
|
|
113932
|
+
if (quote && !escaped && char === quote) {
|
|
113933
|
+
quote = null;
|
|
113934
|
+
current += char;
|
|
113935
|
+
i++;
|
|
113936
|
+
continue;
|
|
113937
|
+
}
|
|
113938
|
+
escaped = false;
|
|
113939
|
+
if (!quote) {
|
|
113940
|
+
if (char === "&" && command[i + 1] === "&" || char === "|" && command[i + 1] === "|") {
|
|
113941
|
+
if (current.trim())
|
|
113942
|
+
parts.push(current.trim());
|
|
113943
|
+
current = "";
|
|
113944
|
+
i += 2;
|
|
113945
|
+
continue;
|
|
113946
|
+
}
|
|
113947
|
+
if (char === "|") {
|
|
113948
|
+
if (current.trim())
|
|
113949
|
+
parts.push(current.trim());
|
|
113950
|
+
current = "";
|
|
113951
|
+
i++;
|
|
113952
|
+
continue;
|
|
113953
|
+
}
|
|
113954
|
+
}
|
|
113955
|
+
current += char;
|
|
113956
|
+
i++;
|
|
113957
|
+
}
|
|
113958
|
+
if (current.trim())
|
|
113959
|
+
parts.push(current.trim());
|
|
113960
|
+
return parts;
|
|
113961
|
+
}
|
|
113719
113962
|
static executor = async (input) => {
|
|
113720
113963
|
const command = input.command;
|
|
113721
113964
|
const cwd = input.cwd || process.cwd();
|
|
@@ -113799,14 +114042,8 @@ class BashTool {
|
|
|
113799
114042
|
suggestion: "Enable validation.perTool.bash.allowEnv to allow env/printenv."
|
|
113800
114043
|
});
|
|
113801
114044
|
}
|
|
113802
|
-
let isAllowed = false;
|
|
113803
114045
|
const allowlist = allowEnv ? this.ALLOWED_COMMANDS : this.ALLOWED_COMMANDS.filter((allowed) => allowed !== "env" && allowed !== "printenv");
|
|
113804
|
-
|
|
113805
|
-
if (commandTrimmed.startsWith(allowed.toLowerCase())) {
|
|
113806
|
-
isAllowed = true;
|
|
113807
|
-
break;
|
|
113808
|
-
}
|
|
113809
|
-
}
|
|
114046
|
+
const isAllowed = this.areAllCommandPartsAllowed(commandForChecks, allowlist);
|
|
113810
114047
|
if (!isAllowed) {
|
|
113811
114048
|
getSecurityLogger().log({
|
|
113812
114049
|
eventType: "blocked_command",
|
|
@@ -113827,6 +114064,27 @@ class BashTool {
|
|
|
113827
114064
|
suggestion: "Use a permitted read-only command."
|
|
113828
114065
|
});
|
|
113829
114066
|
}
|
|
114067
|
+
const ssrfCheck = await this.validateCurlSsrf(commandForChecks);
|
|
114068
|
+
if (!ssrfCheck.valid) {
|
|
114069
|
+
getSecurityLogger().log({
|
|
114070
|
+
eventType: "blocked_command",
|
|
114071
|
+
severity: "high",
|
|
114072
|
+
details: {
|
|
114073
|
+
tool: "bash",
|
|
114074
|
+
command,
|
|
114075
|
+
reason: `SSRF protection: curl to private/internal network blocked (${ssrfCheck.blockedUrl})`
|
|
114076
|
+
},
|
|
114077
|
+
sessionId: input.sessionId || "unknown"
|
|
114078
|
+
});
|
|
114079
|
+
throw new ToolExecutionError(`Cannot fetch from local/private network addresses for security reasons: ${ssrfCheck.blockedUrl}`, {
|
|
114080
|
+
toolName: "bash",
|
|
114081
|
+
toolInput: input,
|
|
114082
|
+
code: ErrorCodes.TOOL_PERMISSION_DENIED,
|
|
114083
|
+
recoverable: false,
|
|
114084
|
+
retryable: false,
|
|
114085
|
+
suggestion: "Use a public URL instead of localhost or internal network addresses."
|
|
114086
|
+
});
|
|
114087
|
+
}
|
|
113830
114088
|
try {
|
|
113831
114089
|
const runtime = getRuntime();
|
|
113832
114090
|
const isWindows = process.platform === "win32";
|
|
@@ -113937,22 +114195,86 @@ function isWithinPath(target, base) {
|
|
|
113937
114195
|
|
|
113938
114196
|
// packages/core/src/security/path-validator.ts
|
|
113939
114197
|
import { homedir as homedir4 } from "os";
|
|
113940
|
-
import { resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2 } from "path";
|
|
114198
|
+
import { resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2, basename } from "path";
|
|
113941
114199
|
import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
|
|
113942
114200
|
var PROTECTED_PATHS = [
|
|
113943
114201
|
"/etc/passwd",
|
|
113944
114202
|
"/etc/shadow",
|
|
113945
114203
|
"/etc/sudoers",
|
|
114204
|
+
"/etc/master.passwd",
|
|
114205
|
+
"/etc/security",
|
|
113946
114206
|
"~/.secrets",
|
|
113947
114207
|
"~/.ssh",
|
|
113948
114208
|
"~/.gnupg",
|
|
114209
|
+
"~/.pgp",
|
|
114210
|
+
"~/.gpg",
|
|
113949
114211
|
"~/.aws/credentials",
|
|
113950
|
-
"~/.
|
|
113951
|
-
|
|
114212
|
+
"~/.aws/config",
|
|
114213
|
+
"~/.azure/credentials",
|
|
114214
|
+
"~/.config/gcloud",
|
|
114215
|
+
"~/.kube/config",
|
|
114216
|
+
"~/.docker/config.json",
|
|
114217
|
+
"~/.npmrc",
|
|
114218
|
+
"~/.yarnrc",
|
|
114219
|
+
"~/.pypirc",
|
|
114220
|
+
"~/.gem/credentials",
|
|
114221
|
+
"~/.bash_history",
|
|
114222
|
+
"~/.zsh_history",
|
|
114223
|
+
"~/.history",
|
|
114224
|
+
"~/.pgpass",
|
|
114225
|
+
"~/.my.cnf",
|
|
114226
|
+
"~/.mongocli.yaml",
|
|
114227
|
+
"~/.git-credentials",
|
|
114228
|
+
"~/.gitconfig",
|
|
114229
|
+
"~/.netrc",
|
|
114230
|
+
"~/.authinfo",
|
|
114231
|
+
"~/.config/gh/hosts.yml",
|
|
114232
|
+
"~/.config/hub",
|
|
114233
|
+
"~/.password-store",
|
|
114234
|
+
"~/.vault-token",
|
|
114235
|
+
"~/.anthropic",
|
|
114236
|
+
"~/.openai"
|
|
114237
|
+
];
|
|
114238
|
+
var PROTECTED_FILENAME_PATTERNS = [
|
|
114239
|
+
/^\.env($|\..*)/,
|
|
114240
|
+
/^\.secret(s)?($|\..*)/,
|
|
114241
|
+
/^credentials(\..*)?$/i,
|
|
114242
|
+
/^secrets?(\..*)?$/i,
|
|
114243
|
+
/^.*_credentials(\..*)?$/i,
|
|
114244
|
+
/^.*_secret(s)?(\..*)?$/i,
|
|
114245
|
+
/^\.?id_rsa(\.pub)?$/,
|
|
114246
|
+
/^\.?id_ed25519(\.pub)?$/,
|
|
114247
|
+
/^\.?id_ecdsa(\.pub)?$/,
|
|
114248
|
+
/^\.?id_dsa(\.pub)?$/,
|
|
114249
|
+
/^authorized_keys$/,
|
|
114250
|
+
/^known_hosts$/,
|
|
114251
|
+
/^.*\.pem$/,
|
|
114252
|
+
/^.*\.key$/,
|
|
114253
|
+
/^.*\.p12$/,
|
|
114254
|
+
/^.*\.pfx$/,
|
|
114255
|
+
/^.*\.keystore$/
|
|
114256
|
+
];
|
|
114257
|
+
function isProtectedFilename(filename) {
|
|
114258
|
+
for (const pattern of PROTECTED_FILENAME_PATTERNS) {
|
|
114259
|
+
if (pattern.test(filename)) {
|
|
114260
|
+
return true;
|
|
114261
|
+
}
|
|
114262
|
+
}
|
|
114263
|
+
return false;
|
|
114264
|
+
}
|
|
114265
|
+
function isInAllowedPaths(resolved, allowedPaths) {
|
|
114266
|
+
for (const allowedPath of allowedPaths) {
|
|
114267
|
+
if (isWithinPath2(resolved, allowedPath)) {
|
|
114268
|
+
return true;
|
|
114269
|
+
}
|
|
114270
|
+
}
|
|
114271
|
+
return false;
|
|
114272
|
+
}
|
|
113952
114273
|
async function isPathSafe(targetPath, operation, options = {}) {
|
|
113953
114274
|
const expandedTarget = expandHome2(targetPath);
|
|
113954
114275
|
const resolved = resolve2(expandedTarget);
|
|
113955
114276
|
const home = homedir4();
|
|
114277
|
+
const filename = basename(resolved);
|
|
113956
114278
|
for (const protectedPath of PROTECTED_PATHS) {
|
|
113957
114279
|
const expanded = protectedPath.replace("~", home);
|
|
113958
114280
|
if (isWithinPath2(resolved, expanded)) {
|
|
@@ -113962,15 +114284,48 @@ async function isPathSafe(targetPath, operation, options = {}) {
|
|
|
113962
114284
|
};
|
|
113963
114285
|
}
|
|
113964
114286
|
}
|
|
114287
|
+
if (isProtectedFilename(filename)) {
|
|
114288
|
+
return {
|
|
114289
|
+
safe: false,
|
|
114290
|
+
reason: `Cannot ${operation} file with protected name pattern: ${filename}`
|
|
114291
|
+
};
|
|
114292
|
+
}
|
|
114293
|
+
const enforceAllowlist = options.enforceAllowlist ?? operation === "read";
|
|
114294
|
+
if (enforceAllowlist) {
|
|
114295
|
+
const cwd = options.cwd ? resolve2(options.cwd) : process.cwd();
|
|
114296
|
+
const allowedPaths = [cwd, ...(options.allowedPaths || []).map((p) => resolve2(p))];
|
|
114297
|
+
if (!isInAllowedPaths(resolved, allowedPaths)) {
|
|
114298
|
+
return {
|
|
114299
|
+
safe: false,
|
|
114300
|
+
reason: `Cannot ${operation} files outside project directory. Path must be within: ${cwd}`
|
|
114301
|
+
};
|
|
114302
|
+
}
|
|
114303
|
+
}
|
|
113965
114304
|
try {
|
|
113966
114305
|
const stat = await lstat2(resolved);
|
|
113967
114306
|
if (stat.isSymbolicLink()) {
|
|
113968
114307
|
const target = await realpath2(resolved);
|
|
113969
114308
|
const root = options.cwd ? resolve2(options.cwd) : process.cwd();
|
|
113970
|
-
|
|
114309
|
+
const allowedTargets = [root, ...(options.allowedPaths || []).map((p) => resolve2(p))];
|
|
114310
|
+
if (!isInAllowedPaths(target, allowedTargets)) {
|
|
113971
114311
|
return {
|
|
113972
114312
|
safe: false,
|
|
113973
|
-
reason: "Symlink points outside
|
|
114313
|
+
reason: "Symlink points outside allowed directories"
|
|
114314
|
+
};
|
|
114315
|
+
}
|
|
114316
|
+
for (const protectedPath of PROTECTED_PATHS) {
|
|
114317
|
+
const expanded = protectedPath.replace("~", home);
|
|
114318
|
+
if (isWithinPath2(target, expanded)) {
|
|
114319
|
+
return {
|
|
114320
|
+
safe: false,
|
|
114321
|
+
reason: `Symlink points to protected path: ${protectedPath}`
|
|
114322
|
+
};
|
|
114323
|
+
}
|
|
114324
|
+
}
|
|
114325
|
+
if (isProtectedFilename(basename(target))) {
|
|
114326
|
+
return {
|
|
114327
|
+
safe: false,
|
|
114328
|
+
reason: `Symlink points to file with protected name pattern`
|
|
113974
114329
|
};
|
|
113975
114330
|
}
|
|
113976
114331
|
}
|
|
@@ -114611,10 +114966,48 @@ class FilesystemTools {
|
|
|
114611
114966
|
|
|
114612
114967
|
// packages/core/src/tools/web.ts
|
|
114613
114968
|
init_errors();
|
|
114614
|
-
import { lookup as
|
|
114969
|
+
import { lookup as dnsLookup2 } from "dns/promises";
|
|
114615
114970
|
function abortController(controller) {
|
|
114616
114971
|
controller.abort();
|
|
114617
114972
|
}
|
|
114973
|
+
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
114974
|
+
async function readResponseWithLimit(response, maxBytes = MAX_RESPONSE_BYTES) {
|
|
114975
|
+
const reader = response.body?.getReader();
|
|
114976
|
+
if (!reader) {
|
|
114977
|
+
return { text: "", truncated: false };
|
|
114978
|
+
}
|
|
114979
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
114980
|
+
const chunks = [];
|
|
114981
|
+
let totalBytes = 0;
|
|
114982
|
+
let truncated = false;
|
|
114983
|
+
try {
|
|
114984
|
+
while (true) {
|
|
114985
|
+
const { done, value } = await reader.read();
|
|
114986
|
+
if (done)
|
|
114987
|
+
break;
|
|
114988
|
+
totalBytes += value.byteLength;
|
|
114989
|
+
if (totalBytes > maxBytes) {
|
|
114990
|
+
const excess = totalBytes - maxBytes;
|
|
114991
|
+
const trimmed = value.slice(0, value.byteLength - excess);
|
|
114992
|
+
chunks.push(decoder.decode(trimmed, { stream: false }));
|
|
114993
|
+
truncated = true;
|
|
114994
|
+
break;
|
|
114995
|
+
}
|
|
114996
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
114997
|
+
}
|
|
114998
|
+
if (!truncated) {
|
|
114999
|
+
chunks.push(decoder.decode());
|
|
115000
|
+
}
|
|
115001
|
+
} finally {
|
|
115002
|
+
reader.releaseLock();
|
|
115003
|
+
if (truncated && response.body) {
|
|
115004
|
+
try {
|
|
115005
|
+
await response.body.cancel();
|
|
115006
|
+
} catch {}
|
|
115007
|
+
}
|
|
115008
|
+
}
|
|
115009
|
+
return { text: chunks.join(""), truncated };
|
|
115010
|
+
}
|
|
114618
115011
|
|
|
114619
115012
|
class WebFetchTool {
|
|
114620
115013
|
static tool = {
|
|
@@ -114663,7 +115056,7 @@ class WebFetchTool {
|
|
|
114663
115056
|
});
|
|
114664
115057
|
}
|
|
114665
115058
|
const hostname = parsedUrl.hostname;
|
|
114666
|
-
if (await
|
|
115059
|
+
if (await isPrivateHostOrResolved2(hostname)) {
|
|
114667
115060
|
throw new ToolExecutionError("Cannot fetch from local/private network addresses for security reasons", {
|
|
114668
115061
|
toolName: "web_fetch",
|
|
114669
115062
|
toolInput: input,
|
|
@@ -114727,9 +115120,21 @@ class WebFetchTool {
|
|
|
114727
115120
|
const contentType = response.headers.get("content-type") || "";
|
|
114728
115121
|
if (extractType === "json") {
|
|
114729
115122
|
try {
|
|
114730
|
-
const
|
|
115123
|
+
const { text: jsonText, truncated } = await readResponseWithLimit(response);
|
|
115124
|
+
if (truncated) {
|
|
115125
|
+
throw new ToolExecutionError("JSON response exceeds size limit", {
|
|
115126
|
+
toolName: "web_fetch",
|
|
115127
|
+
toolInput: input,
|
|
115128
|
+
code: ErrorCodes.TOOL_EXECUTION_FAILED,
|
|
115129
|
+
recoverable: false,
|
|
115130
|
+
retryable: false
|
|
115131
|
+
});
|
|
115132
|
+
}
|
|
115133
|
+
const json = JSON.parse(jsonText);
|
|
114731
115134
|
return JSON.stringify(json, null, 2);
|
|
114732
|
-
} catch {
|
|
115135
|
+
} catch (e) {
|
|
115136
|
+
if (e instanceof ToolExecutionError)
|
|
115137
|
+
throw e;
|
|
114733
115138
|
throw new ToolExecutionError("Response is not valid JSON", {
|
|
114734
115139
|
toolName: "web_fetch",
|
|
114735
115140
|
toolInput: input,
|
|
@@ -114739,13 +115144,18 @@ class WebFetchTool {
|
|
|
114739
115144
|
});
|
|
114740
115145
|
}
|
|
114741
115146
|
}
|
|
114742
|
-
const html = await response
|
|
115147
|
+
const { text: html, truncated: htmlTruncated } = await readResponseWithLimit(response);
|
|
114743
115148
|
if (extractType === "html") {
|
|
114744
115149
|
const maxLength2 = 50000;
|
|
114745
115150
|
if (html.length > maxLength2) {
|
|
114746
115151
|
return html.slice(0, maxLength2) + `
|
|
114747
115152
|
|
|
114748
115153
|
[Content truncated...]`;
|
|
115154
|
+
}
|
|
115155
|
+
if (htmlTruncated) {
|
|
115156
|
+
return html + `
|
|
115157
|
+
|
|
115158
|
+
[Content truncated due to size limit...]`;
|
|
114749
115159
|
}
|
|
114750
115160
|
return html;
|
|
114751
115161
|
}
|
|
@@ -114755,6 +115165,11 @@ class WebFetchTool {
|
|
|
114755
115165
|
return text.slice(0, maxLength) + `
|
|
114756
115166
|
|
|
114757
115167
|
[Content truncated...]`;
|
|
115168
|
+
}
|
|
115169
|
+
if (htmlTruncated) {
|
|
115170
|
+
return (text || "No readable content found on page") + `
|
|
115171
|
+
|
|
115172
|
+
[Content truncated due to size limit...]`;
|
|
114758
115173
|
}
|
|
114759
115174
|
return text || "No readable content found on page";
|
|
114760
115175
|
} catch (error) {
|
|
@@ -114848,7 +115263,7 @@ class WebSearchTool {
|
|
|
114848
115263
|
retryable: false
|
|
114849
115264
|
});
|
|
114850
115265
|
}
|
|
114851
|
-
const html = await response
|
|
115266
|
+
const { text: html } = await readResponseWithLimit(response, 2 * 1024 * 1024);
|
|
114852
115267
|
const results = parseDuckDuckGoResults(html, maxResults);
|
|
114853
115268
|
if (results.length === 0) {
|
|
114854
115269
|
return `No results found for "${query}"`;
|
|
@@ -114998,7 +115413,7 @@ class CurlTool {
|
|
|
114998
115413
|
});
|
|
114999
115414
|
}
|
|
115000
115415
|
const hostname = parsedUrl.hostname;
|
|
115001
|
-
if (await
|
|
115416
|
+
if (await isPrivateHostOrResolved2(hostname)) {
|
|
115002
115417
|
throw new ToolExecutionError("Cannot fetch from local/private network addresses for security reasons", {
|
|
115003
115418
|
toolName: "curl",
|
|
115004
115419
|
toolInput: input,
|
|
@@ -115063,15 +115478,18 @@ class CurlTool {
|
|
|
115063
115478
|
}
|
|
115064
115479
|
const contentType = response.headers.get("content-type") || "";
|
|
115065
115480
|
let responseBody;
|
|
115481
|
+
let truncatedBySize = false;
|
|
115482
|
+
const { text: rawBody, truncated } = await readResponseWithLimit(response);
|
|
115483
|
+
truncatedBySize = truncated;
|
|
115066
115484
|
if (contentType.includes("application/json")) {
|
|
115067
115485
|
try {
|
|
115068
|
-
const json =
|
|
115486
|
+
const json = JSON.parse(rawBody);
|
|
115069
115487
|
responseBody = JSON.stringify(json, null, 2);
|
|
115070
115488
|
} catch {
|
|
115071
|
-
responseBody =
|
|
115489
|
+
responseBody = rawBody;
|
|
115072
115490
|
}
|
|
115073
115491
|
} else {
|
|
115074
|
-
responseBody =
|
|
115492
|
+
responseBody = rawBody;
|
|
115075
115493
|
if (contentType.includes("text/html")) {
|
|
115076
115494
|
responseBody = extractReadableText(responseBody);
|
|
115077
115495
|
}
|
|
@@ -115081,6 +115499,10 @@ class CurlTool {
|
|
|
115081
115499
|
responseBody = responseBody.slice(0, maxLength) + `
|
|
115082
115500
|
|
|
115083
115501
|
[Content truncated...]`;
|
|
115502
|
+
} else if (truncatedBySize) {
|
|
115503
|
+
responseBody = responseBody + `
|
|
115504
|
+
|
|
115505
|
+
[Content truncated due to size limit...]`;
|
|
115084
115506
|
}
|
|
115085
115507
|
const statusLine = `HTTP ${response.status} ${response.statusText}`;
|
|
115086
115508
|
return `${statusLine}
|
|
@@ -115126,33 +115548,35 @@ class WebTools {
|
|
|
115126
115548
|
registry.register(CurlTool.tool, CurlTool.executor);
|
|
115127
115549
|
}
|
|
115128
115550
|
}
|
|
115129
|
-
var
|
|
115130
|
-
async function
|
|
115131
|
-
if (
|
|
115551
|
+
var lookupFn2 = dnsLookup2;
|
|
115552
|
+
async function isPrivateHostOrResolved2(hostname) {
|
|
115553
|
+
if (isPrivateHost2(hostname)) {
|
|
115132
115554
|
return true;
|
|
115133
115555
|
}
|
|
115134
|
-
const host =
|
|
115135
|
-
if (host === "" ||
|
|
115556
|
+
const host = normalizeHostname2(hostname);
|
|
115557
|
+
if (host === "" || isIpLiteral2(host)) {
|
|
115136
115558
|
return false;
|
|
115137
115559
|
}
|
|
115138
115560
|
try {
|
|
115139
|
-
const results = await
|
|
115561
|
+
const results = await lookupFn2(host, { all: true });
|
|
115140
115562
|
for (const result of results) {
|
|
115141
|
-
if (
|
|
115563
|
+
if (isPrivateHost2(result.address)) {
|
|
115142
115564
|
return true;
|
|
115143
115565
|
}
|
|
115144
115566
|
}
|
|
115145
|
-
|
|
115146
|
-
|
|
115567
|
+
return false;
|
|
115568
|
+
} catch {
|
|
115569
|
+
return true;
|
|
115570
|
+
}
|
|
115147
115571
|
}
|
|
115148
|
-
function
|
|
115572
|
+
function isIpLiteral2(hostname) {
|
|
115149
115573
|
if (hostname.includes(":"))
|
|
115150
115574
|
return true;
|
|
115151
115575
|
if (/^\d+$/.test(hostname))
|
|
115152
115576
|
return true;
|
|
115153
115577
|
return /^\d+\.\d+\.\d+\.\d+$/.test(hostname);
|
|
115154
115578
|
}
|
|
115155
|
-
function
|
|
115579
|
+
function normalizeHostname2(hostname) {
|
|
115156
115580
|
let host = hostname.toLowerCase();
|
|
115157
115581
|
if (host.startsWith("[") && host.endsWith("]")) {
|
|
115158
115582
|
host = host.slice(1, -1);
|
|
@@ -115164,8 +115588,8 @@ function normalizeHostname(hostname) {
|
|
|
115164
115588
|
host = host.replace(/\.$/, "");
|
|
115165
115589
|
return host;
|
|
115166
115590
|
}
|
|
115167
|
-
function
|
|
115168
|
-
let host =
|
|
115591
|
+
function isPrivateHost2(hostname) {
|
|
115592
|
+
let host = normalizeHostname2(hostname);
|
|
115169
115593
|
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local"))
|
|
115170
115594
|
return true;
|
|
115171
115595
|
if (host === "127.0.0.1" || host === "::1" || host === "::" || host === "0:0:0:0:0:0:0:0")
|
|
@@ -115175,7 +115599,7 @@ function isPrivateHost(hostname) {
|
|
|
115175
115599
|
if (host.startsWith("::ffff:")) {
|
|
115176
115600
|
const mapped = host.slice("::ffff:".length);
|
|
115177
115601
|
if (mapped.includes(".")) {
|
|
115178
|
-
return
|
|
115602
|
+
return isPrivateHost2(mapped);
|
|
115179
115603
|
}
|
|
115180
115604
|
const hexMatch = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
115181
115605
|
if (hexMatch) {
|
|
@@ -115187,7 +115611,7 @@ function isPrivateHost(hostname) {
|
|
|
115187
115611
|
low >> 8 & 255,
|
|
115188
115612
|
low & 255
|
|
115189
115613
|
];
|
|
115190
|
-
return
|
|
115614
|
+
return isPrivateIPv42(octets2);
|
|
115191
115615
|
}
|
|
115192
115616
|
return false;
|
|
115193
115617
|
}
|
|
@@ -115207,9 +115631,9 @@ function isPrivateHost(hostname) {
|
|
|
115207
115631
|
return false;
|
|
115208
115632
|
octets.push(value);
|
|
115209
115633
|
}
|
|
115210
|
-
return
|
|
115634
|
+
return isPrivateIPv42(octets);
|
|
115211
115635
|
}
|
|
115212
|
-
function
|
|
115636
|
+
function isPrivateIPv42(octets) {
|
|
115213
115637
|
if (octets[0] === 0)
|
|
115214
115638
|
return true;
|
|
115215
115639
|
if (octets[0] === 10)
|
|
@@ -115561,8 +115985,37 @@ function computeNextRun(schedule, fromTime) {
|
|
|
115561
115985
|
return;
|
|
115562
115986
|
return getNextCronRun(schedule.schedule.cron, fromTime, validTimezone);
|
|
115563
115987
|
}
|
|
115988
|
+
if (schedule.schedule.kind === "random") {
|
|
115989
|
+
return computeRandomNextRun(schedule.schedule, fromTime);
|
|
115990
|
+
}
|
|
115991
|
+
if (schedule.schedule.kind === "interval") {
|
|
115992
|
+
return computeIntervalNextRun(schedule.schedule, fromTime);
|
|
115993
|
+
}
|
|
115564
115994
|
return;
|
|
115565
115995
|
}
|
|
115996
|
+
function computeRandomNextRun(schedule, fromTime) {
|
|
115997
|
+
const { minInterval, maxInterval, unit = "minutes" } = schedule;
|
|
115998
|
+
if (!minInterval || !maxInterval || minInterval <= 0 || maxInterval <= 0) {
|
|
115999
|
+
return;
|
|
116000
|
+
}
|
|
116001
|
+
if (minInterval > maxInterval) {
|
|
116002
|
+
return;
|
|
116003
|
+
}
|
|
116004
|
+
const multiplier = unit === "seconds" ? 1000 : unit === "hours" ? 3600000 : 60000;
|
|
116005
|
+
const minMs = minInterval * multiplier;
|
|
116006
|
+
const maxMs = maxInterval * multiplier;
|
|
116007
|
+
const randomDelay = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
|
|
116008
|
+
return fromTime + randomDelay;
|
|
116009
|
+
}
|
|
116010
|
+
function computeIntervalNextRun(schedule, fromTime) {
|
|
116011
|
+
const { interval, unit = "minutes" } = schedule;
|
|
116012
|
+
if (!interval || interval <= 0) {
|
|
116013
|
+
return;
|
|
116014
|
+
}
|
|
116015
|
+
const multiplier = unit === "seconds" ? 1000 : unit === "hours" ? 3600000 : 60000;
|
|
116016
|
+
const intervalMs = interval * multiplier;
|
|
116017
|
+
return fromTime + intervalMs;
|
|
116018
|
+
}
|
|
115566
116019
|
async function getDueSchedules(cwd, nowTime) {
|
|
115567
116020
|
const schedules = await listSchedules(cwd);
|
|
115568
116021
|
return schedules.filter((schedule) => {
|
|
@@ -115734,7 +116187,7 @@ function getTimeZoneOffsetMs(date, timeZone) {
|
|
|
115734
116187
|
class SchedulerTool {
|
|
115735
116188
|
static tool = {
|
|
115736
116189
|
name: "schedule",
|
|
115737
|
-
description: "Create and manage scheduled commands (once or
|
|
116190
|
+
description: "Create and manage scheduled commands (once, cron, or random interval).",
|
|
115738
116191
|
parameters: {
|
|
115739
116192
|
type: "object",
|
|
115740
116193
|
properties: {
|
|
@@ -115755,6 +116208,23 @@ class SchedulerTool {
|
|
|
115755
116208
|
type: "string",
|
|
115756
116209
|
description: "Cron expression for recurring schedule (5 fields)"
|
|
115757
116210
|
},
|
|
116211
|
+
minInterval: {
|
|
116212
|
+
type: "number",
|
|
116213
|
+
description: "Minimum interval for random scheduling (e.g., 1)"
|
|
116214
|
+
},
|
|
116215
|
+
maxInterval: {
|
|
116216
|
+
type: "number",
|
|
116217
|
+
description: "Maximum interval for random scheduling (e.g., 20)"
|
|
116218
|
+
},
|
|
116219
|
+
unit: {
|
|
116220
|
+
type: "string",
|
|
116221
|
+
description: "Time unit for interval/random scheduling: seconds, minutes, or hours",
|
|
116222
|
+
enum: ["seconds", "minutes", "hours"]
|
|
116223
|
+
},
|
|
116224
|
+
every: {
|
|
116225
|
+
type: "number",
|
|
116226
|
+
description: 'Fixed interval for recurring schedule (e.g., every: 15 with unit: "seconds" = every 15 seconds). Minimum 1 second.'
|
|
116227
|
+
},
|
|
115758
116228
|
timezone: {
|
|
115759
116229
|
type: "string",
|
|
115760
116230
|
description: "IANA timezone name (optional, best-effort)"
|
|
@@ -115763,6 +116233,15 @@ class SchedulerTool {
|
|
|
115763
116233
|
type: "string",
|
|
115764
116234
|
description: "Optional description for this schedule"
|
|
115765
116235
|
},
|
|
116236
|
+
actionType: {
|
|
116237
|
+
type: "string",
|
|
116238
|
+
description: 'Type of action: "command" runs the command, "message" injects custom message into agent session. Default: "command"',
|
|
116239
|
+
enum: ["command", "message"]
|
|
116240
|
+
},
|
|
116241
|
+
message: {
|
|
116242
|
+
type: "string",
|
|
116243
|
+
description: 'Custom message to inject when actionType is "message"'
|
|
116244
|
+
},
|
|
115766
116245
|
sessionId: {
|
|
115767
116246
|
type: "string",
|
|
115768
116247
|
description: "Session id to scope the schedule to"
|
|
@@ -115789,7 +116268,15 @@ class SchedulerTool {
|
|
|
115789
116268
|
return "No schedules found.";
|
|
115790
116269
|
const rows = schedules.sort((a, b) => (a.nextRunAt || 0) - (b.nextRunAt || 0)).map((s) => {
|
|
115791
116270
|
const next = s.nextRunAt ? new Date(s.nextRunAt).toISOString() : "n/a";
|
|
115792
|
-
|
|
116271
|
+
let scheduleInfo = "";
|
|
116272
|
+
if (s.schedule.kind === "interval" && s.schedule.interval) {
|
|
116273
|
+
scheduleInfo = ` (every ${s.schedule.interval} ${s.schedule.unit || "minutes"})`;
|
|
116274
|
+
} else if (s.schedule.kind === "random" && s.schedule.minInterval && s.schedule.maxInterval) {
|
|
116275
|
+
scheduleInfo = ` (random: ${s.schedule.minInterval}-${s.schedule.maxInterval} ${s.schedule.unit || "minutes"})`;
|
|
116276
|
+
} else if (s.schedule.kind === "cron" && s.schedule.cron) {
|
|
116277
|
+
scheduleInfo = ` (cron: ${s.schedule.cron})`;
|
|
116278
|
+
}
|
|
116279
|
+
return `- ${s.id} [${s.status}] ${s.command}${scheduleInfo} (next: ${next})`;
|
|
115793
116280
|
});
|
|
115794
116281
|
return rows.join(`
|
|
115795
116282
|
`);
|
|
@@ -115800,26 +116287,69 @@ class SchedulerTool {
|
|
|
115800
116287
|
return "Error: command is required.";
|
|
115801
116288
|
const at = input.at;
|
|
115802
116289
|
const cron = input.cron;
|
|
115803
|
-
|
|
115804
|
-
|
|
116290
|
+
const minInterval = input.minInterval;
|
|
116291
|
+
const maxInterval = input.maxInterval;
|
|
116292
|
+
const every = input.every;
|
|
116293
|
+
const unit = input.unit;
|
|
116294
|
+
const hasRandom = minInterval !== undefined && maxInterval !== undefined;
|
|
116295
|
+
const hasInterval = every !== undefined;
|
|
116296
|
+
if (!at && !cron && !hasRandom && !hasInterval) {
|
|
116297
|
+
return "Error: provide at (ISO time), cron, every (fixed interval), or minInterval+maxInterval for random scheduling.";
|
|
116298
|
+
}
|
|
116299
|
+
if (hasInterval) {
|
|
116300
|
+
if (every <= 0) {
|
|
116301
|
+
return "Error: every must be a positive number.";
|
|
116302
|
+
}
|
|
116303
|
+
const effectiveUnit = unit || "minutes";
|
|
116304
|
+
const intervalSeconds = effectiveUnit === "seconds" ? every : effectiveUnit === "hours" ? every * 3600 : every * 60;
|
|
116305
|
+
if (intervalSeconds < 1) {
|
|
116306
|
+
return "Error: minimum interval is 1 second.";
|
|
116307
|
+
}
|
|
116308
|
+
}
|
|
116309
|
+
if (hasRandom) {
|
|
116310
|
+
if (minInterval <= 0 || maxInterval <= 0) {
|
|
116311
|
+
return "Error: minInterval and maxInterval must be positive numbers.";
|
|
116312
|
+
}
|
|
116313
|
+
if (minInterval > maxInterval) {
|
|
116314
|
+
return "Error: minInterval cannot be greater than maxInterval.";
|
|
116315
|
+
}
|
|
116316
|
+
}
|
|
115805
116317
|
const timezone = input.timezone;
|
|
115806
116318
|
if (timezone && !isValidTimeZone(timezone)) {
|
|
115807
116319
|
return `Error: invalid timezone "${timezone}".`;
|
|
115808
116320
|
}
|
|
116321
|
+
const actionType = input.actionType;
|
|
116322
|
+
const message = input.message;
|
|
116323
|
+
let scheduleKind;
|
|
116324
|
+
if (hasInterval) {
|
|
116325
|
+
scheduleKind = "interval";
|
|
116326
|
+
} else if (hasRandom) {
|
|
116327
|
+
scheduleKind = "random";
|
|
116328
|
+
} else if (cron) {
|
|
116329
|
+
scheduleKind = "cron";
|
|
116330
|
+
} else {
|
|
116331
|
+
scheduleKind = "once";
|
|
116332
|
+
}
|
|
115809
116333
|
const schedule = {
|
|
115810
116334
|
id: generateId(),
|
|
115811
116335
|
createdAt: now2,
|
|
115812
116336
|
updatedAt: now2,
|
|
115813
116337
|
createdBy: "agent",
|
|
115814
116338
|
sessionId: typeof input.sessionId === "string" ? input.sessionId : undefined,
|
|
116339
|
+
actionType: actionType || "command",
|
|
115815
116340
|
command,
|
|
116341
|
+
message: actionType === "message" ? message : undefined,
|
|
115816
116342
|
description: input.description,
|
|
115817
116343
|
status: "active",
|
|
115818
116344
|
schedule: {
|
|
115819
|
-
kind:
|
|
116345
|
+
kind: scheduleKind,
|
|
115820
116346
|
at,
|
|
115821
116347
|
cron,
|
|
115822
|
-
timezone
|
|
116348
|
+
timezone,
|
|
116349
|
+
minInterval: hasRandom ? minInterval : undefined,
|
|
116350
|
+
maxInterval: hasRandom ? maxInterval : undefined,
|
|
116351
|
+
unit: hasRandom || hasInterval ? unit || "minutes" : undefined,
|
|
116352
|
+
interval: hasInterval ? every : undefined
|
|
115823
116353
|
}
|
|
115824
116354
|
};
|
|
115825
116355
|
schedule.nextRunAt = computeNextRun(schedule, now2);
|
|
@@ -115827,6 +116357,12 @@ class SchedulerTool {
|
|
|
115827
116357
|
return "Error: unable to compute next run for schedule.";
|
|
115828
116358
|
}
|
|
115829
116359
|
await saveSchedule(cwd, schedule);
|
|
116360
|
+
if (scheduleKind === "interval") {
|
|
116361
|
+
return `Scheduled ${schedule.command} (${schedule.id}) every ${every} ${unit || "minutes"}, next run: ${new Date(schedule.nextRunAt).toISOString()}`;
|
|
116362
|
+
}
|
|
116363
|
+
if (scheduleKind === "random") {
|
|
116364
|
+
return `Scheduled ${schedule.command} (${schedule.id}) randomly every ${minInterval}-${maxInterval} ${unit || "minutes"}, next run: ${new Date(schedule.nextRunAt).toISOString()}`;
|
|
116365
|
+
}
|
|
115830
116366
|
return `Scheduled ${schedule.command} (${schedule.id}) for ${new Date(schedule.nextRunAt).toISOString()}`;
|
|
115831
116367
|
}
|
|
115832
116368
|
if (action === "delete") {
|
|
@@ -115870,6 +116406,8 @@ import { existsSync as existsSync3, writeFileSync as writeFileSync3, unlinkSync
|
|
|
115870
116406
|
import { tmpdir } from "os";
|
|
115871
116407
|
import { join as join7 } from "path";
|
|
115872
116408
|
import { homedir as homedir6 } from "os";
|
|
116409
|
+
var FETCH_TIMEOUT_MS = 30000;
|
|
116410
|
+
var MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
115873
116411
|
async function getViuPath() {
|
|
115874
116412
|
const runtime = getRuntime();
|
|
115875
116413
|
const explicitPath = process.env.ASSISTANTS_VIU_PATH || process.env.VIU_PATH;
|
|
@@ -115937,7 +116475,18 @@ class ImageDisplayTool {
|
|
|
115937
116475
|
let tempFile = null;
|
|
115938
116476
|
if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
|
|
115939
116477
|
try {
|
|
115940
|
-
const
|
|
116478
|
+
const url = new URL(imagePath);
|
|
116479
|
+
if (await isPrivateHostOrResolved(url.hostname)) {
|
|
116480
|
+
return "Error: Cannot fetch from local/private network addresses for security reasons";
|
|
116481
|
+
}
|
|
116482
|
+
const controller = new AbortController;
|
|
116483
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
116484
|
+
let response;
|
|
116485
|
+
try {
|
|
116486
|
+
response = await fetch(imagePath, { signal: controller.signal });
|
|
116487
|
+
} finally {
|
|
116488
|
+
clearTimeout(timeoutId);
|
|
116489
|
+
}
|
|
115941
116490
|
if (!response.ok) {
|
|
115942
116491
|
return `Error: Failed to fetch image: HTTP ${response.status}`;
|
|
115943
116492
|
}
|
|
@@ -115945,12 +116494,47 @@ class ImageDisplayTool {
|
|
|
115945
116494
|
if (!contentType.startsWith("image/")) {
|
|
115946
116495
|
return `Error: URL does not point to an image (content-type: ${contentType})`;
|
|
115947
116496
|
}
|
|
115948
|
-
const
|
|
116497
|
+
const contentLength = response.headers.get("content-length");
|
|
116498
|
+
if (contentLength) {
|
|
116499
|
+
const size = parseInt(contentLength, 10);
|
|
116500
|
+
if (!isNaN(size) && size > MAX_IMAGE_SIZE_BYTES) {
|
|
116501
|
+
return `Error: Image too large (${Math.round(size / 1024 / 1024)}MB exceeds ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB limit)`;
|
|
116502
|
+
}
|
|
116503
|
+
}
|
|
116504
|
+
const chunks = [];
|
|
116505
|
+
let totalSize = 0;
|
|
116506
|
+
const reader = response.body?.getReader();
|
|
116507
|
+
if (!reader) {
|
|
116508
|
+
return "Error: Failed to read image response";
|
|
116509
|
+
}
|
|
116510
|
+
try {
|
|
116511
|
+
while (true) {
|
|
116512
|
+
const { done, value } = await reader.read();
|
|
116513
|
+
if (done)
|
|
116514
|
+
break;
|
|
116515
|
+
totalSize += value.length;
|
|
116516
|
+
if (totalSize > MAX_IMAGE_SIZE_BYTES) {
|
|
116517
|
+
return `Error: Image too large (exceeds ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB limit)`;
|
|
116518
|
+
}
|
|
116519
|
+
chunks.push(value);
|
|
116520
|
+
}
|
|
116521
|
+
} finally {
|
|
116522
|
+
reader.releaseLock();
|
|
116523
|
+
}
|
|
116524
|
+
const buffer = new Uint8Array(totalSize);
|
|
116525
|
+
let offset = 0;
|
|
116526
|
+
for (const chunk of chunks) {
|
|
116527
|
+
buffer.set(chunk, offset);
|
|
116528
|
+
offset += chunk.length;
|
|
116529
|
+
}
|
|
115949
116530
|
const ext = contentType.split("/")[1]?.split(";")[0] || "png";
|
|
115950
116531
|
tempFile = join7(tmpdir(), `assistants-image-${generateId()}.${ext}`);
|
|
115951
|
-
writeFileSync3(tempFile,
|
|
116532
|
+
writeFileSync3(tempFile, buffer);
|
|
115952
116533
|
localPath = tempFile;
|
|
115953
116534
|
} catch (error) {
|
|
116535
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
116536
|
+
return `Error: Image fetch timed out after ${FETCH_TIMEOUT_MS / 1000} seconds`;
|
|
116537
|
+
}
|
|
115954
116538
|
return `Error: Failed to fetch image: ${error instanceof Error ? error.message : String(error)}`;
|
|
115955
116539
|
}
|
|
115956
116540
|
}
|
|
@@ -116592,7 +117176,7 @@ Respond with ALLOW or DENY on the first line, followed by a short reason.`,
|
|
|
116592
117176
|
// packages/core/src/skills/loader.ts
|
|
116593
117177
|
init_src2();
|
|
116594
117178
|
var import_fast_glob = __toESM(require_out4(), 1);
|
|
116595
|
-
import { join as join9, basename, dirname as dirname5 } from "path";
|
|
117179
|
+
import { join as join9, basename as basename2, dirname as dirname5 } from "path";
|
|
116596
117180
|
import { homedir as homedir7 } from "os";
|
|
116597
117181
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
116598
117182
|
|
|
@@ -116649,7 +117233,7 @@ class SkillLoader {
|
|
|
116649
117233
|
const content = await readFile2(filePath, "utf-8");
|
|
116650
117234
|
const { frontmatter, content: markdownContent } = parseFrontmatter(content);
|
|
116651
117235
|
const includeContent = options.includeContent ?? true;
|
|
116652
|
-
const dirName =
|
|
117236
|
+
const dirName = basename2(dirname5(filePath));
|
|
116653
117237
|
const name = frontmatter.name || dirName;
|
|
116654
117238
|
let description = frontmatter.description || "";
|
|
116655
117239
|
if (!description && markdownContent) {
|
|
@@ -117590,7 +118174,7 @@ function createScopeVerificationHook() {
|
|
|
117590
118174
|
}
|
|
117591
118175
|
// packages/core/src/commands/loader.ts
|
|
117592
118176
|
import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
117593
|
-
import { join as join11, basename as
|
|
118177
|
+
import { join as join11, basename as basename3, extname as extname2 } from "path";
|
|
117594
118178
|
import { homedir as homedir8 } from "os";
|
|
117595
118179
|
class CommandLoader {
|
|
117596
118180
|
commands = new Map;
|
|
@@ -117630,7 +118214,7 @@ class CommandLoader {
|
|
|
117630
118214
|
const runtime = getRuntime();
|
|
117631
118215
|
const content = await runtime.file(filePath).text();
|
|
117632
118216
|
const { frontmatter, body } = this.parseFrontmatter(content);
|
|
117633
|
-
const fileName =
|
|
118217
|
+
const fileName = basename3(filePath, ".md");
|
|
117634
118218
|
const name = frontmatter.name || (prefix ? `${prefix}:${fileName}` : fileName);
|
|
117635
118219
|
const allowedToolsRaw = frontmatter["allowed-tools"];
|
|
117636
118220
|
const allowedTools = Array.isArray(allowedToolsRaw) ? allowedToolsRaw.map((t) => String(t).trim()).filter(Boolean) : typeof allowedToolsRaw === "string" ? allowedToolsRaw.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
|
|
@@ -117723,6 +118307,8 @@ class CommandLoader {
|
|
|
117723
118307
|
}
|
|
117724
118308
|
}
|
|
117725
118309
|
// packages/core/src/commands/executor.ts
|
|
118310
|
+
var MAX_SHELL_OUTPUT_LENGTH = 64 * 1024;
|
|
118311
|
+
|
|
117726
118312
|
class CommandExecutor {
|
|
117727
118313
|
loader;
|
|
117728
118314
|
constructor(loader) {
|
|
@@ -117806,6 +118392,20 @@ Use /help to see available commands.
|
|
|
117806
118392
|
`);
|
|
117807
118393
|
}
|
|
117808
118394
|
async executeShell(command, cwd) {
|
|
118395
|
+
const validation = validateBashCommand(command);
|
|
118396
|
+
if (!validation.valid) {
|
|
118397
|
+
getSecurityLogger().log({
|
|
118398
|
+
eventType: "blocked_command",
|
|
118399
|
+
severity: validation.severity || "high",
|
|
118400
|
+
details: {
|
|
118401
|
+
tool: "custom_command",
|
|
118402
|
+
command,
|
|
118403
|
+
reason: validation.reason || "Command blocked by security policy"
|
|
118404
|
+
},
|
|
118405
|
+
sessionId: "custom-command"
|
|
118406
|
+
});
|
|
118407
|
+
return `Error: ${validation.reason || "Command blocked by security policy"}`;
|
|
118408
|
+
}
|
|
117809
118409
|
try {
|
|
117810
118410
|
const runtime = getRuntime();
|
|
117811
118411
|
const timeoutMs = 5000;
|
|
@@ -117833,10 +118433,15 @@ Use /help to see available commands.
|
|
|
117833
118433
|
return `Error: command timed out after ${Math.round(timeoutMs / 1000)}s.`;
|
|
117834
118434
|
}
|
|
117835
118435
|
if (exitCode !== 0 && stderr) {
|
|
118436
|
+
const truncatedStderr = stderr.length > MAX_SHELL_OUTPUT_LENGTH ? `${stderr.slice(0, MAX_SHELL_OUTPUT_LENGTH)}...(truncated)` : stderr;
|
|
117836
118437
|
return `Error (exit ${exitCode}):
|
|
117837
|
-
${
|
|
118438
|
+
${truncatedStderr}`;
|
|
118439
|
+
}
|
|
118440
|
+
const trimmedOutput = stdout.trim();
|
|
118441
|
+
if (trimmedOutput.length > MAX_SHELL_OUTPUT_LENGTH) {
|
|
118442
|
+
return `${trimmedOutput.slice(0, MAX_SHELL_OUTPUT_LENGTH)}...(output truncated at ${MAX_SHELL_OUTPUT_LENGTH} bytes)`;
|
|
117838
118443
|
}
|
|
117839
|
-
return
|
|
118444
|
+
return trimmedOutput || "(no output)";
|
|
117840
118445
|
} catch (error) {
|
|
117841
118446
|
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
117842
118447
|
}
|
|
@@ -118690,7 +119295,7 @@ function formatAge(ms) {
|
|
|
118690
119295
|
return `${days}d`;
|
|
118691
119296
|
}
|
|
118692
119297
|
// packages/core/src/commands/builtin.ts
|
|
118693
|
-
var VERSION = "0.6.
|
|
119298
|
+
var VERSION = "0.6.55";
|
|
118694
119299
|
function resolveAuthTimeout(resolve5) {
|
|
118695
119300
|
resolve5({ exitCode: 1, stdout: { toString: () => "{}" } });
|
|
118696
119301
|
}
|
|
@@ -123551,6 +124156,8 @@ import { join as join20 } from "path";
|
|
|
123551
124156
|
import { readFileSync as readFileSync6, unlink as unlink6 } from "fs";
|
|
123552
124157
|
class AudioRecorder {
|
|
123553
124158
|
currentProcess = null;
|
|
124159
|
+
stoppedIntentionally = false;
|
|
124160
|
+
currentOutputPath = null;
|
|
123554
124161
|
async record(options = {}) {
|
|
123555
124162
|
if (this.currentProcess) {
|
|
123556
124163
|
throw new Error("Audio recorder is already running.");
|
|
@@ -123559,6 +124166,8 @@ class AudioRecorder {
|
|
|
123559
124166
|
const sampleRate = options.sampleRate ?? 16000;
|
|
123560
124167
|
const channels = options.channels ?? 1;
|
|
123561
124168
|
const output = join20(tmpdir4(), `assistants-record-${Date.now()}.wav`);
|
|
124169
|
+
this.currentOutputPath = output;
|
|
124170
|
+
this.stoppedIntentionally = false;
|
|
123562
124171
|
const recorder = this.resolveRecorder(sampleRate, channels, duration, output);
|
|
123563
124172
|
if (!recorder) {
|
|
123564
124173
|
throw new Error("No supported audio recorder found. Install sox or ffmpeg.");
|
|
@@ -123567,7 +124176,7 @@ class AudioRecorder {
|
|
|
123567
124176
|
this.currentProcess = spawn2(recorder.command, recorder.args, { stdio: "ignore" });
|
|
123568
124177
|
this.currentProcess.on("close", (code) => {
|
|
123569
124178
|
this.currentProcess = null;
|
|
123570
|
-
if (code === 0) {
|
|
124179
|
+
if (code === 0 || this.stoppedIntentionally) {
|
|
123571
124180
|
resolve5();
|
|
123572
124181
|
} else {
|
|
123573
124182
|
reject(new Error("Audio recording failed."));
|
|
@@ -123580,12 +124189,13 @@ class AudioRecorder {
|
|
|
123580
124189
|
});
|
|
123581
124190
|
const data = readFileSync6(output);
|
|
123582
124191
|
unlink6(output, () => {});
|
|
124192
|
+
this.currentOutputPath = null;
|
|
123583
124193
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
123584
124194
|
}
|
|
123585
124195
|
stop() {
|
|
123586
124196
|
if (this.currentProcess) {
|
|
123587
|
-
this.
|
|
123588
|
-
this.currentProcess
|
|
124197
|
+
this.stoppedIntentionally = true;
|
|
124198
|
+
this.currentProcess.kill("SIGINT");
|
|
123589
124199
|
}
|
|
123590
124200
|
}
|
|
123591
124201
|
resolveRecorder(sampleRate, channels, duration, output) {
|
|
@@ -123759,6 +124369,15 @@ init_src2();
|
|
|
123759
124369
|
import { existsSync as existsSync10 } from "fs";
|
|
123760
124370
|
import { mkdir as mkdir8, readFile as readFile9, writeFile as writeFile8, rm } from "fs/promises";
|
|
123761
124371
|
import { join as join21 } from "path";
|
|
124372
|
+
var SAFE_ID_PATTERN4 = /^[a-zA-Z0-9_-]+$/;
|
|
124373
|
+
function isValidId(id) {
|
|
124374
|
+
return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN4.test(id);
|
|
124375
|
+
}
|
|
124376
|
+
function validateId(id, idType) {
|
|
124377
|
+
if (!isValidId(id)) {
|
|
124378
|
+
throw new Error(`Invalid ${idType}: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
|
|
124379
|
+
}
|
|
124380
|
+
}
|
|
123762
124381
|
var DEFAULT_PROFILE = {
|
|
123763
124382
|
displayName: "Assistant",
|
|
123764
124383
|
timezone: "UTC",
|
|
@@ -123784,6 +124403,7 @@ class IdentityManager {
|
|
|
123784
124403
|
identities = new Map;
|
|
123785
124404
|
activeId = null;
|
|
123786
124405
|
constructor(assistantId, basePath) {
|
|
124406
|
+
validateId(assistantId, "assistantId");
|
|
123787
124407
|
this.assistantId = assistantId;
|
|
123788
124408
|
this.basePath = basePath;
|
|
123789
124409
|
}
|
|
@@ -123797,6 +124417,7 @@ class IdentityManager {
|
|
|
123797
124417
|
return join21(this.identitiesRoot, "active.json");
|
|
123798
124418
|
}
|
|
123799
124419
|
identityPath(id) {
|
|
124420
|
+
validateId(id, "identityId");
|
|
123800
124421
|
return join21(this.identitiesRoot, `${id}.json`);
|
|
123801
124422
|
}
|
|
123802
124423
|
assistantConfigPath() {
|
|
@@ -123912,7 +124533,8 @@ class IdentityManager {
|
|
|
123912
124533
|
try {
|
|
123913
124534
|
const raw = await readFile9(this.indexPath, "utf-8");
|
|
123914
124535
|
const data = JSON.parse(raw);
|
|
123915
|
-
|
|
124536
|
+
const identities = Array.isArray(data.identities) ? data.identities : [];
|
|
124537
|
+
return { identities: identities.filter(isValidId) };
|
|
123916
124538
|
} catch {
|
|
123917
124539
|
return { identities: [] };
|
|
123918
124540
|
}
|
|
@@ -123950,7 +124572,11 @@ class IdentityManager {
|
|
|
123950
124572
|
try {
|
|
123951
124573
|
const raw = await readFile9(this.activePath, "utf-8");
|
|
123952
124574
|
const data = JSON.parse(raw);
|
|
123953
|
-
|
|
124575
|
+
const id = data.id || null;
|
|
124576
|
+
if (id && !isValidId(id)) {
|
|
124577
|
+
return null;
|
|
124578
|
+
}
|
|
124579
|
+
return id;
|
|
123954
124580
|
} catch {
|
|
123955
124581
|
return null;
|
|
123956
124582
|
}
|
|
@@ -123972,6 +124598,15 @@ class IdentityManager {
|
|
|
123972
124598
|
}
|
|
123973
124599
|
|
|
123974
124600
|
// packages/core/src/identity/assistant-manager.ts
|
|
124601
|
+
var SAFE_ID_PATTERN5 = /^[a-zA-Z0-9_-]+$/;
|
|
124602
|
+
function isValidId2(id) {
|
|
124603
|
+
return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN5.test(id);
|
|
124604
|
+
}
|
|
124605
|
+
function validateId2(id, idType) {
|
|
124606
|
+
if (!isValidId2(id)) {
|
|
124607
|
+
throw new Error(`Invalid ${idType}: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
|
|
124608
|
+
}
|
|
124609
|
+
}
|
|
123975
124610
|
var DEFAULT_SETTINGS = {
|
|
123976
124611
|
model: "claude-opus-4-5"
|
|
123977
124612
|
};
|
|
@@ -123993,6 +124628,7 @@ class AssistantManager {
|
|
|
123993
124628
|
return join22(this.basePath, "active.json");
|
|
123994
124629
|
}
|
|
123995
124630
|
assistantConfigPath(id) {
|
|
124631
|
+
validateId2(id, "assistantId");
|
|
123996
124632
|
return join22(this.assistantsRoot, id, "config.json");
|
|
123997
124633
|
}
|
|
123998
124634
|
async initialize() {
|
|
@@ -124044,6 +124680,7 @@ class AssistantManager {
|
|
|
124044
124680
|
return updated;
|
|
124045
124681
|
}
|
|
124046
124682
|
async deleteAssistant(id) {
|
|
124683
|
+
validateId2(id, "assistantId");
|
|
124047
124684
|
if (!this.assistants.has(id)) {
|
|
124048
124685
|
throw new Error(`Assistant ${id} not found`);
|
|
124049
124686
|
}
|
|
@@ -124084,7 +124721,8 @@ class AssistantManager {
|
|
|
124084
124721
|
try {
|
|
124085
124722
|
const raw = await readFile10(this.indexPath, "utf-8");
|
|
124086
124723
|
const data = JSON.parse(raw);
|
|
124087
|
-
|
|
124724
|
+
const assistants = Array.isArray(data.assistants) ? data.assistants : [];
|
|
124725
|
+
return { assistants: assistants.filter(isValidId2) };
|
|
124088
124726
|
} catch {
|
|
124089
124727
|
return { assistants: [] };
|
|
124090
124728
|
}
|
|
@@ -124113,6 +124751,7 @@ class AssistantManager {
|
|
|
124113
124751
|
}
|
|
124114
124752
|
}
|
|
124115
124753
|
async persistAssistant(assistant) {
|
|
124754
|
+
validateId2(assistant.id, "assistantId");
|
|
124116
124755
|
const dir = join22(this.assistantsRoot, assistant.id);
|
|
124117
124756
|
await mkdir9(dir, { recursive: true });
|
|
124118
124757
|
await writeFile9(this.assistantConfigPath(assistant.id), JSON.stringify(assistant, null, 2));
|
|
@@ -124123,7 +124762,11 @@ class AssistantManager {
|
|
|
124123
124762
|
try {
|
|
124124
124763
|
const raw = await readFile10(this.activePath, "utf-8");
|
|
124125
124764
|
const data = JSON.parse(raw);
|
|
124126
|
-
|
|
124765
|
+
const id = data.id || null;
|
|
124766
|
+
if (id && !isValidId2(id)) {
|
|
124767
|
+
return null;
|
|
124768
|
+
}
|
|
124769
|
+
return id;
|
|
124127
124770
|
} catch {
|
|
124128
124771
|
return null;
|
|
124129
124772
|
}
|
|
@@ -132992,8 +133635,34 @@ class S3InboxClient {
|
|
|
132992
133635
|
}
|
|
132993
133636
|
|
|
132994
133637
|
// packages/core/src/inbox/storage/local-cache.ts
|
|
132995
|
-
import { join as join24 } from "path";
|
|
133638
|
+
import { join as join24, basename as basename4 } from "path";
|
|
132996
133639
|
import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile11, rm as rm3, readdir as readdir4, stat as stat3 } from "fs/promises";
|
|
133640
|
+
import { createHash as createHash2 } from "crypto";
|
|
133641
|
+
var STRICT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
133642
|
+
var SAFE_FILENAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
133643
|
+
function isValidAgentId(id) {
|
|
133644
|
+
return typeof id === "string" && id.length > 0 && STRICT_ID_PATTERN.test(id);
|
|
133645
|
+
}
|
|
133646
|
+
function validateAgentId(id) {
|
|
133647
|
+
if (!isValidAgentId(id)) {
|
|
133648
|
+
throw new Error(`Invalid agentId: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
|
|
133649
|
+
}
|
|
133650
|
+
}
|
|
133651
|
+
function emailIdToFilename(emailId) {
|
|
133652
|
+
if (SAFE_FILENAME_PATTERN.test(emailId) && emailId.length <= 100) {
|
|
133653
|
+
return emailId;
|
|
133654
|
+
}
|
|
133655
|
+
const hash = createHash2("sha256").update(emailId).digest("base64url").slice(0, 16);
|
|
133656
|
+
const readable = emailId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
|
|
133657
|
+
return readable ? `${readable}_${hash}` : hash;
|
|
133658
|
+
}
|
|
133659
|
+
function isValidMappedFilename(filename) {
|
|
133660
|
+
return typeof filename === "string" && filename.length > 0 && SAFE_FILENAME_PATTERN.test(filename);
|
|
133661
|
+
}
|
|
133662
|
+
function sanitizeFilename(filename) {
|
|
133663
|
+
const base = basename4(filename);
|
|
133664
|
+
return base.replace(/[/\\]/g, "_");
|
|
133665
|
+
}
|
|
132997
133666
|
|
|
132998
133667
|
class LocalInboxCache {
|
|
132999
133668
|
agentId;
|
|
@@ -133001,6 +133670,7 @@ class LocalInboxCache {
|
|
|
133001
133670
|
cacheDir;
|
|
133002
133671
|
index = null;
|
|
133003
133672
|
constructor(options) {
|
|
133673
|
+
validateAgentId(options.agentId);
|
|
133004
133674
|
this.agentId = options.agentId;
|
|
133005
133675
|
this.basePath = options.basePath;
|
|
133006
133676
|
this.cacheDir = join24(this.basePath, this.agentId);
|
|
@@ -133030,13 +133700,18 @@ class LocalInboxCache {
|
|
|
133030
133700
|
await writeFile11(indexPath, JSON.stringify(this.index, null, 2));
|
|
133031
133701
|
}
|
|
133032
133702
|
async saveEmail(email) {
|
|
133703
|
+
const filename = emailIdToFilename(email.id);
|
|
133704
|
+
if (!isValidMappedFilename(filename)) {
|
|
133705
|
+
throw new Error(`Failed to create safe filename for email ID: "${email.id}"`);
|
|
133706
|
+
}
|
|
133033
133707
|
await this.ensureDirectories();
|
|
133034
|
-
const emailPath = join24(this.cacheDir, "emails", `${
|
|
133708
|
+
const emailPath = join24(this.cacheDir, "emails", `${filename}.json`);
|
|
133035
133709
|
await writeFile11(emailPath, JSON.stringify(email, null, 2));
|
|
133036
133710
|
const index = await this.loadIndex();
|
|
133037
133711
|
const existingIdx = index.emails.findIndex((e3) => e3.id === email.id);
|
|
133038
133712
|
const entry = {
|
|
133039
133713
|
id: email.id,
|
|
133714
|
+
filename,
|
|
133040
133715
|
messageId: email.messageId,
|
|
133041
133716
|
from: email.from.name || email.from.address,
|
|
133042
133717
|
subject: email.subject,
|
|
@@ -133047,6 +133722,7 @@ class LocalInboxCache {
|
|
|
133047
133722
|
};
|
|
133048
133723
|
if (existingIdx >= 0) {
|
|
133049
133724
|
entry.isRead = index.emails[existingIdx].isRead;
|
|
133725
|
+
entry.filename = index.emails[existingIdx].filename || filename;
|
|
133050
133726
|
index.emails[existingIdx] = entry;
|
|
133051
133727
|
} else {
|
|
133052
133728
|
index.emails.unshift(entry);
|
|
@@ -133054,8 +133730,17 @@ class LocalInboxCache {
|
|
|
133054
133730
|
await this.saveIndex();
|
|
133055
133731
|
}
|
|
133056
133732
|
async loadEmail(id) {
|
|
133733
|
+
const index = await this.loadIndex();
|
|
133734
|
+
const entry = index.emails.find((e3) => e3.id === id);
|
|
133735
|
+
if (!entry) {
|
|
133736
|
+
return null;
|
|
133737
|
+
}
|
|
133738
|
+
const filename = entry.filename || emailIdToFilename(id);
|
|
133739
|
+
if (!isValidMappedFilename(filename)) {
|
|
133740
|
+
return null;
|
|
133741
|
+
}
|
|
133057
133742
|
try {
|
|
133058
|
-
const emailPath = join24(this.cacheDir, "emails", `${
|
|
133743
|
+
const emailPath = join24(this.cacheDir, "emails", `${filename}.json`);
|
|
133059
133744
|
const content = await readFile12(emailPath, "utf-8");
|
|
133060
133745
|
return JSON.parse(content);
|
|
133061
133746
|
} catch {
|
|
@@ -133107,15 +133792,31 @@ class LocalInboxCache {
|
|
|
133107
133792
|
return new Set(index.emails.map((e3) => e3.id));
|
|
133108
133793
|
}
|
|
133109
133794
|
async saveAttachment(emailId, filename, content) {
|
|
133110
|
-
const
|
|
133795
|
+
const emailFilename = emailIdToFilename(emailId);
|
|
133796
|
+
if (!isValidMappedFilename(emailFilename)) {
|
|
133797
|
+
throw new Error(`Failed to create safe directory name for email ID: "${emailId}"`);
|
|
133798
|
+
}
|
|
133799
|
+
const safeFilename = sanitizeFilename(filename);
|
|
133800
|
+
if (!safeFilename) {
|
|
133801
|
+
throw new Error("Invalid attachment filename");
|
|
133802
|
+
}
|
|
133803
|
+
const attachmentDir = join24(this.cacheDir, "attachments", emailFilename);
|
|
133111
133804
|
await mkdir10(attachmentDir, { recursive: true });
|
|
133112
|
-
const attachmentPath = join24(attachmentDir,
|
|
133805
|
+
const attachmentPath = join24(attachmentDir, safeFilename);
|
|
133113
133806
|
await writeFile11(attachmentPath, content);
|
|
133114
133807
|
return attachmentPath;
|
|
133115
133808
|
}
|
|
133116
133809
|
async getAttachmentPath(emailId, filename) {
|
|
133810
|
+
const emailFilename = emailIdToFilename(emailId);
|
|
133811
|
+
if (!isValidMappedFilename(emailFilename)) {
|
|
133812
|
+
return null;
|
|
133813
|
+
}
|
|
133814
|
+
const safeFilename = sanitizeFilename(filename);
|
|
133815
|
+
if (!safeFilename) {
|
|
133816
|
+
return null;
|
|
133817
|
+
}
|
|
133117
133818
|
try {
|
|
133118
|
-
const attachmentPath = join24(this.cacheDir, "attachments",
|
|
133819
|
+
const attachmentPath = join24(this.cacheDir, "attachments", emailFilename, safeFilename);
|
|
133119
133820
|
await stat3(attachmentPath);
|
|
133120
133821
|
return attachmentPath;
|
|
133121
133822
|
} catch {
|
|
@@ -133134,26 +133835,29 @@ class LocalInboxCache {
|
|
|
133134
133835
|
async cleanup(maxAgeDays = 30) {
|
|
133135
133836
|
const index = await this.loadIndex();
|
|
133136
133837
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
133137
|
-
const
|
|
133838
|
+
const toRemove = [];
|
|
133138
133839
|
for (const entry of index.emails) {
|
|
133139
133840
|
const cachedAt = new Date(entry.cachedAt).getTime();
|
|
133140
133841
|
if (cachedAt < cutoff) {
|
|
133141
|
-
|
|
133842
|
+
const filename = entry.filename || emailIdToFilename(entry.id);
|
|
133843
|
+
if (isValidMappedFilename(filename)) {
|
|
133844
|
+
toRemove.push({ id: entry.id, filename });
|
|
133845
|
+
}
|
|
133142
133846
|
}
|
|
133143
133847
|
}
|
|
133144
|
-
for (const id of
|
|
133848
|
+
for (const { id, filename } of toRemove) {
|
|
133145
133849
|
index.emails = index.emails.filter((e3) => e3.id !== id);
|
|
133146
133850
|
try {
|
|
133147
|
-
await rm3(join24(this.cacheDir, "emails", `${
|
|
133851
|
+
await rm3(join24(this.cacheDir, "emails", `${filename}.json`));
|
|
133148
133852
|
} catch {}
|
|
133149
133853
|
try {
|
|
133150
|
-
await rm3(join24(this.cacheDir, "attachments",
|
|
133854
|
+
await rm3(join24(this.cacheDir, "attachments", filename), { recursive: true });
|
|
133151
133855
|
} catch {}
|
|
133152
133856
|
}
|
|
133153
|
-
if (
|
|
133857
|
+
if (toRemove.length > 0) {
|
|
133154
133858
|
await this.saveIndex();
|
|
133155
133859
|
}
|
|
133156
|
-
return
|
|
133860
|
+
return toRemove.length;
|
|
133157
133861
|
}
|
|
133158
133862
|
async getCacheSize() {
|
|
133159
133863
|
let totalSize = 0;
|
|
@@ -138527,12 +139231,21 @@ function getMessagesBasePath() {
|
|
|
138527
139231
|
const home = envOverride && envOverride.trim() ? envOverride : homedir14();
|
|
138528
139232
|
return join26(home, ".assistants", "messages");
|
|
138529
139233
|
}
|
|
139234
|
+
var SAFE_ID_PATTERN6 = /^[a-zA-Z0-9_-]+$/;
|
|
138530
139235
|
|
|
138531
139236
|
class LocalMessagesStorage {
|
|
138532
139237
|
basePath;
|
|
138533
139238
|
constructor(options = {}) {
|
|
138534
139239
|
this.basePath = options.basePath || getMessagesBasePath();
|
|
138535
139240
|
}
|
|
139241
|
+
validateSafeId(id, idType) {
|
|
139242
|
+
if (!id || typeof id !== "string") {
|
|
139243
|
+
throw new Error(`Invalid ${idType}: must be a non-empty string`);
|
|
139244
|
+
}
|
|
139245
|
+
if (!SAFE_ID_PATTERN6.test(id)) {
|
|
139246
|
+
throw new Error(`Invalid ${idType}: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
|
|
139247
|
+
}
|
|
139248
|
+
}
|
|
138536
139249
|
async ensureDirectories(agentId) {
|
|
138537
139250
|
const agentPath = this.getAgentPath(agentId);
|
|
138538
139251
|
await Promise.all([
|
|
@@ -138542,15 +139255,18 @@ class LocalMessagesStorage {
|
|
|
138542
139255
|
]);
|
|
138543
139256
|
}
|
|
138544
139257
|
getAgentPath(agentId) {
|
|
139258
|
+
this.validateSafeId(agentId, "agentId");
|
|
138545
139259
|
return join26(this.basePath, agentId);
|
|
138546
139260
|
}
|
|
138547
139261
|
getIndexPath(agentId) {
|
|
138548
139262
|
return join26(this.getAgentPath(agentId), "index.json");
|
|
138549
139263
|
}
|
|
138550
139264
|
getMessagePath(agentId, messageId) {
|
|
139265
|
+
this.validateSafeId(messageId, "messageId");
|
|
138551
139266
|
return join26(this.getAgentPath(agentId), "messages", `${messageId}.json`);
|
|
138552
139267
|
}
|
|
138553
139268
|
getThreadPath(agentId, threadId) {
|
|
139269
|
+
this.validateSafeId(threadId, "threadId");
|
|
138554
139270
|
return join26(this.getAgentPath(agentId), "threads", `${threadId}.json`);
|
|
138555
139271
|
}
|
|
138556
139272
|
getRegistryPath() {
|
|
@@ -140479,7 +141195,9 @@ ${effects.message}
|
|
|
140479
141195
|
}
|
|
140480
141196
|
}
|
|
140481
141197
|
startHeartbeat() {
|
|
140482
|
-
if (!this.config
|
|
141198
|
+
if (!this.config)
|
|
141199
|
+
return;
|
|
141200
|
+
if (this.config.scheduler?.enabled === false)
|
|
140483
141201
|
return;
|
|
140484
141202
|
const interval = this.config.scheduler?.heartbeatIntervalMs ?? 30000;
|
|
140485
141203
|
if (this.heartbeatTimer)
|
|
@@ -140557,7 +141275,8 @@ ${effects.message}
|
|
|
140557
141275
|
leaseTimer.unref();
|
|
140558
141276
|
}
|
|
140559
141277
|
try {
|
|
140560
|
-
const
|
|
141278
|
+
const contentToRun = current.actionType === "message" ? current.message || current.command : current.command;
|
|
141279
|
+
const result = await this.runMessage(contentToRun, "schedule");
|
|
140561
141280
|
const now2 = Date.now();
|
|
140562
141281
|
await updateSchedule(this.cwd, schedule.id, (live) => {
|
|
140563
141282
|
const updated = {
|
|
@@ -140801,6 +141520,11 @@ init_src2();
|
|
|
140801
141520
|
// packages/core/src/logger.ts
|
|
140802
141521
|
import { existsSync as existsSync12, mkdirSync as mkdirSync8, appendFileSync, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
140803
141522
|
import { join as join28 } from "path";
|
|
141523
|
+
var SAFE_ID_PATTERN7 = /^[a-zA-Z0-9_-]+$/;
|
|
141524
|
+
function isValidId3(id) {
|
|
141525
|
+
return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN7.test(id);
|
|
141526
|
+
}
|
|
141527
|
+
|
|
140804
141528
|
class Logger {
|
|
140805
141529
|
logDir;
|
|
140806
141530
|
logFile;
|
|
@@ -140852,9 +141576,13 @@ class SessionStorage {
|
|
|
140852
141576
|
sessionFile;
|
|
140853
141577
|
sessionId;
|
|
140854
141578
|
constructor(sessionId, basePath, assistantId) {
|
|
141579
|
+
if (!isValidId3(sessionId)) {
|
|
141580
|
+
throw new Error(`Invalid sessionId: "${sessionId}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
|
|
141581
|
+
}
|
|
140855
141582
|
this.sessionId = sessionId;
|
|
140856
141583
|
const root = basePath || getConfigDir();
|
|
140857
|
-
|
|
141584
|
+
const safeAssistantId = isValidId3(assistantId) ? assistantId : null;
|
|
141585
|
+
this.sessionsDir = safeAssistantId ? join28(root, "assistants", safeAssistantId, "sessions") : join28(root, "sessions");
|
|
140858
141586
|
this.ensureDir(this.sessionsDir);
|
|
140859
141587
|
this.sessionFile = join28(this.sessionsDir, `${sessionId}.json`);
|
|
140860
141588
|
}
|
|
@@ -140887,15 +141615,20 @@ class SessionStorage {
|
|
|
140887
141615
|
return null;
|
|
140888
141616
|
const raw = readFileSync8(activePath, "utf-8");
|
|
140889
141617
|
const data = JSON.parse(raw);
|
|
140890
|
-
|
|
141618
|
+
const id = data.id || null;
|
|
141619
|
+
if (id && !isValidId3(id)) {
|
|
141620
|
+
return null;
|
|
141621
|
+
}
|
|
141622
|
+
return id;
|
|
140891
141623
|
} catch {
|
|
140892
141624
|
return null;
|
|
140893
141625
|
}
|
|
140894
141626
|
}
|
|
140895
141627
|
static resolveSessionsDir(assistantId) {
|
|
140896
141628
|
const root = getConfigDir();
|
|
140897
|
-
const
|
|
140898
|
-
|
|
141629
|
+
const safeAssistantId = isValidId3(assistantId) ? assistantId : null;
|
|
141630
|
+
const resolvedId = safeAssistantId ?? SessionStorage.getActiveAssistantId();
|
|
141631
|
+
if (resolvedId && isValidId3(resolvedId)) {
|
|
140899
141632
|
const assistantDir = join28(root, "assistants", resolvedId, "sessions");
|
|
140900
141633
|
if (existsSync12(assistantDir)) {
|
|
140901
141634
|
return assistantDir;
|
|
@@ -140931,6 +141664,9 @@ class SessionStorage {
|
|
|
140931
141664
|
return sessions[0] || null;
|
|
140932
141665
|
}
|
|
140933
141666
|
static loadSession(sessionId, assistantId) {
|
|
141667
|
+
if (!isValidId3(sessionId)) {
|
|
141668
|
+
return null;
|
|
141669
|
+
}
|
|
140934
141670
|
const sessionsDir = SessionStorage.resolveSessionsDir(assistantId);
|
|
140935
141671
|
const sessionFile = join28(sessionsDir, `${sessionId}.json`);
|
|
140936
141672
|
try {
|
|
@@ -147338,7 +148074,7 @@ var COMMANDS = [
|
|
|
147338
148074
|
{ name: "/help", description: "show available commands" },
|
|
147339
148075
|
{ name: "/clear", description: "clear the conversation" },
|
|
147340
148076
|
{ name: "/new", description: "start a new conversation" },
|
|
147341
|
-
{ name: "/session", description: "list/switch sessions (Ctrl+
|
|
148077
|
+
{ name: "/session", description: "list/switch sessions (Ctrl+])" },
|
|
147342
148078
|
{ name: "/status", description: "show session status" },
|
|
147343
148079
|
{ name: "/tokens", description: "show token usage" },
|
|
147344
148080
|
{ name: "/cost", description: "show estimated API cost" },
|
|
@@ -147368,10 +148104,23 @@ function Input({
|
|
|
147368
148104
|
commands: commands3,
|
|
147369
148105
|
skills = [],
|
|
147370
148106
|
isAskingUser = false,
|
|
147371
|
-
askPlaceholder
|
|
148107
|
+
askPlaceholder,
|
|
148108
|
+
allowBlankAnswer = false
|
|
147372
148109
|
}) {
|
|
147373
|
-
const [
|
|
147374
|
-
const
|
|
148110
|
+
const [inputState, setInputState] = import_react27.useState({ value: "", cursor: 0 });
|
|
148111
|
+
const { value, cursor } = inputState;
|
|
148112
|
+
const setValue = (newValue) => {
|
|
148113
|
+
setInputState((prev) => ({
|
|
148114
|
+
...prev,
|
|
148115
|
+
value: typeof newValue === "function" ? newValue(prev.value) : newValue
|
|
148116
|
+
}));
|
|
148117
|
+
};
|
|
148118
|
+
const setCursor = (newCursor) => {
|
|
148119
|
+
setInputState((prev) => ({
|
|
148120
|
+
...prev,
|
|
148121
|
+
cursor: typeof newCursor === "function" ? newCursor(prev.cursor) : newCursor
|
|
148122
|
+
}));
|
|
148123
|
+
};
|
|
147375
148124
|
const [preferredColumn, setPreferredColumn] = import_react27.useState(null);
|
|
147376
148125
|
const [selectedIndex, setSelectedIndex] = import_react27.useState(0);
|
|
147377
148126
|
const { stdout } = use_stdout_default();
|
|
@@ -147399,7 +148148,7 @@ function Input({
|
|
|
147399
148148
|
return "command";
|
|
147400
148149
|
}
|
|
147401
148150
|
return null;
|
|
147402
|
-
}, [value]);
|
|
148151
|
+
}, [value, isAskingUser]);
|
|
147403
148152
|
const filteredCommands = import_react27.useMemo(() => {
|
|
147404
148153
|
if (autocompleteMode !== "command")
|
|
147405
148154
|
return [];
|
|
@@ -147421,14 +148170,13 @@ function Input({
|
|
|
147421
148170
|
setSelectedIndex((prev) => Math.min(prev, autocompleteItems.length - 1));
|
|
147422
148171
|
}, [autocompleteItems.length]);
|
|
147423
148172
|
const setValueAndCursor = (nextValue, nextCursor = nextValue.length) => {
|
|
147424
|
-
setValue(nextValue);
|
|
147425
148173
|
const clamped = Math.max(0, Math.min(nextCursor, nextValue.length));
|
|
147426
|
-
|
|
148174
|
+
setInputState({ value: nextValue, cursor: clamped });
|
|
147427
148175
|
setPreferredColumn(null);
|
|
147428
148176
|
setSelectedIndex(0);
|
|
147429
148177
|
};
|
|
147430
148178
|
const handleSubmit = (submittedValue) => {
|
|
147431
|
-
if (!submittedValue.trim())
|
|
148179
|
+
if (!submittedValue.trim() && !allowBlankAnswer)
|
|
147432
148180
|
return;
|
|
147433
148181
|
if (autocompleteMode === "command" && filteredCommands.length > 0 && !submittedValue.includes(" ")) {
|
|
147434
148182
|
const selected = filteredCommands[selectedIndex] || filteredCommands[0];
|
|
@@ -147465,26 +148213,53 @@ function Input({
|
|
|
147465
148213
|
const insertText = (text) => {
|
|
147466
148214
|
if (!text)
|
|
147467
148215
|
return;
|
|
147468
|
-
|
|
147469
|
-
|
|
147470
|
-
|
|
148216
|
+
setInputState((prev) => ({
|
|
148217
|
+
value: prev.value.slice(0, prev.cursor) + text + prev.value.slice(prev.cursor),
|
|
148218
|
+
cursor: prev.cursor + text.length
|
|
148219
|
+
}));
|
|
147471
148220
|
setPreferredColumn(null);
|
|
147472
148221
|
setSelectedIndex(0);
|
|
147473
148222
|
};
|
|
147474
148223
|
const deleteBackward = () => {
|
|
147475
|
-
|
|
147476
|
-
|
|
147477
|
-
|
|
147478
|
-
|
|
147479
|
-
|
|
148224
|
+
setInputState((prev) => {
|
|
148225
|
+
if (prev.cursor === 0)
|
|
148226
|
+
return prev;
|
|
148227
|
+
return {
|
|
148228
|
+
value: prev.value.slice(0, prev.cursor - 1) + prev.value.slice(prev.cursor),
|
|
148229
|
+
cursor: prev.cursor - 1
|
|
148230
|
+
};
|
|
148231
|
+
});
|
|
147480
148232
|
setPreferredColumn(null);
|
|
147481
148233
|
setSelectedIndex(0);
|
|
147482
148234
|
};
|
|
147483
148235
|
const deleteForward = () => {
|
|
147484
|
-
|
|
147485
|
-
|
|
147486
|
-
|
|
147487
|
-
|
|
148236
|
+
setInputState((prev) => {
|
|
148237
|
+
if (prev.cursor >= prev.value.length)
|
|
148238
|
+
return prev;
|
|
148239
|
+
return {
|
|
148240
|
+
value: prev.value.slice(0, prev.cursor) + prev.value.slice(prev.cursor + 1),
|
|
148241
|
+
cursor: prev.cursor
|
|
148242
|
+
};
|
|
148243
|
+
});
|
|
148244
|
+
setPreferredColumn(null);
|
|
148245
|
+
setSelectedIndex(0);
|
|
148246
|
+
};
|
|
148247
|
+
const deleteWordBackward = () => {
|
|
148248
|
+
setInputState((prev) => {
|
|
148249
|
+
if (prev.cursor === 0)
|
|
148250
|
+
return prev;
|
|
148251
|
+
let start = prev.cursor - 1;
|
|
148252
|
+
while (start >= 0 && /\s/.test(prev.value[start])) {
|
|
148253
|
+
start--;
|
|
148254
|
+
}
|
|
148255
|
+
while (start >= 0 && !/\s/.test(prev.value[start])) {
|
|
148256
|
+
start--;
|
|
148257
|
+
}
|
|
148258
|
+
return {
|
|
148259
|
+
value: prev.value.slice(0, start + 1) + prev.value.slice(prev.cursor),
|
|
148260
|
+
cursor: start + 1
|
|
148261
|
+
};
|
|
148262
|
+
});
|
|
147488
148263
|
setPreferredColumn(null);
|
|
147489
148264
|
setSelectedIndex(0);
|
|
147490
148265
|
};
|
|
@@ -147495,6 +148270,10 @@ function Input({
|
|
|
147495
148270
|
}
|
|
147496
148271
|
return;
|
|
147497
148272
|
}
|
|
148273
|
+
if (key.ctrl && input === "w") {
|
|
148274
|
+
deleteWordBackward();
|
|
148275
|
+
return;
|
|
148276
|
+
}
|
|
147498
148277
|
if (!isAskingUser) {
|
|
147499
148278
|
if (key.tab) {
|
|
147500
148279
|
if (autocompleteItems.length > 0) {
|
|
@@ -147547,14 +148326,20 @@ function Input({
|
|
|
147547
148326
|
moveCursorTo(line.start + line.text.length, false);
|
|
147548
148327
|
return;
|
|
147549
148328
|
}
|
|
147550
|
-
if (
|
|
148329
|
+
if (input === "\x7F" || input === "\b" || key.backspace || key.delete) {
|
|
147551
148330
|
deleteBackward();
|
|
147552
148331
|
return;
|
|
147553
148332
|
}
|
|
147554
|
-
if (
|
|
148333
|
+
if (input === "\x1B[3~") {
|
|
147555
148334
|
deleteForward();
|
|
147556
148335
|
return;
|
|
147557
148336
|
}
|
|
148337
|
+
if (input === "\x1B\r" || input === `\x1B
|
|
148338
|
+
`) {
|
|
148339
|
+
insertText(`
|
|
148340
|
+
`);
|
|
148341
|
+
return;
|
|
148342
|
+
}
|
|
147558
148343
|
if (key.return) {
|
|
147559
148344
|
if ((key.shift || key.ctrl) && value.trim()) {
|
|
147560
148345
|
onSubmit(value, "interrupt");
|
|
@@ -148920,7 +149705,7 @@ function groupConsecutiveToolMessages(messages2) {
|
|
|
148920
149705
|
|
|
148921
149706
|
// packages/terminal/src/components/Messages.tsx
|
|
148922
149707
|
var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
|
|
148923
|
-
import { basename as
|
|
149708
|
+
import { basename as basename5 } from "path";
|
|
148924
149709
|
function Messages3({
|
|
148925
149710
|
messages: messages2,
|
|
148926
149711
|
currentResponse,
|
|
@@ -149431,10 +150216,10 @@ function getToolContext(toolCall) {
|
|
|
149431
150216
|
return truncate(String(input.command || ""), 20);
|
|
149432
150217
|
case "read":
|
|
149433
150218
|
const path2 = String(input.path || input.file_path || "");
|
|
149434
|
-
return
|
|
150219
|
+
return basename5(path2) || path2;
|
|
149435
150220
|
case "write":
|
|
149436
150221
|
const writePath = String(input.filename || input.path || input.file_path || "");
|
|
149437
|
-
return
|
|
150222
|
+
return basename5(writePath) || writePath;
|
|
149438
150223
|
case "glob":
|
|
149439
150224
|
return truncate(String(input.pattern || ""), 20);
|
|
149440
150225
|
case "grep":
|
|
@@ -149778,7 +150563,7 @@ function Status({
|
|
|
149778
150563
|
dimColor: true,
|
|
149779
150564
|
children: [
|
|
149780
150565
|
"/help",
|
|
149781
|
-
sessionCount && sessionCount > 1 ? " \xB7 Ctrl+
|
|
150566
|
+
sessionCount && sessionCount > 1 ? " \xB7 Ctrl+]" : ""
|
|
149782
150567
|
]
|
|
149783
150568
|
}, undefined, true, undefined, this),
|
|
149784
150569
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
@@ -149885,6 +150670,7 @@ function Spinner2({ label }) {
|
|
|
149885
150670
|
|
|
149886
150671
|
// packages/terminal/src/components/ProcessingIndicator.tsx
|
|
149887
150672
|
var import_react31 = __toESM(require_react(), 1);
|
|
150673
|
+
init_src2();
|
|
149888
150674
|
var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
|
|
149889
150675
|
function ProcessingIndicator({
|
|
149890
150676
|
isProcessing,
|
|
@@ -149893,6 +150679,7 @@ function ProcessingIndicator({
|
|
|
149893
150679
|
isThinking = false
|
|
149894
150680
|
}) {
|
|
149895
150681
|
const [elapsed, setElapsed] = import_react31.useState(0);
|
|
150682
|
+
const loadingWord = import_react31.useMemo(() => getRandomLoadingWord(), [isProcessing]);
|
|
149896
150683
|
import_react31.useEffect(() => {
|
|
149897
150684
|
if (!isProcessing || !startTime) {
|
|
149898
150685
|
setElapsed(0);
|
|
@@ -149930,7 +150717,7 @@ function ProcessingIndicator({
|
|
|
149930
150717
|
if (isThinking) {
|
|
149931
150718
|
parts.push("thinking");
|
|
149932
150719
|
}
|
|
149933
|
-
const label =
|
|
150720
|
+
const label = loadingWord;
|
|
149934
150721
|
return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
|
|
149935
150722
|
marginY: 1,
|
|
149936
150723
|
children: [
|
|
@@ -150319,7 +151106,7 @@ function AskUserPanel({
|
|
|
150319
151106
|
marginTop: 1,
|
|
150320
151107
|
children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text3, {
|
|
150321
151108
|
dimColor: true,
|
|
150322
|
-
children: "Multi-line answer allowed.
|
|
151109
|
+
children: "Multi-line answer allowed. Use Alt+Enter to insert newlines."
|
|
150323
151110
|
}, undefined, false, undefined, this)
|
|
150324
151111
|
}, undefined, false, undefined, this),
|
|
150325
151112
|
/* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
|
|
@@ -150376,6 +151163,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150376
151163
|
const [askUserState, setAskUserState] = import_react33.useState(null);
|
|
150377
151164
|
const [processingStartTime, setProcessingStartTime] = import_react33.useState();
|
|
150378
151165
|
const [currentTurnTokens, setCurrentTurnTokens] = import_react33.useState(0);
|
|
151166
|
+
const [lastWorkedFor, setLastWorkedFor] = import_react33.useState();
|
|
150379
151167
|
const [skills, setSkills] = import_react33.useState([]);
|
|
150380
151168
|
const [commands3, setCommands] = import_react33.useState([]);
|
|
150381
151169
|
const responseRef = import_react33.useRef("");
|
|
@@ -150448,6 +151236,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150448
151236
|
const turnIdRef = import_react33.useRef(0);
|
|
150449
151237
|
const initStateRef = import_react33.useRef("idle");
|
|
150450
151238
|
const isMountedRef = import_react33.useRef(true);
|
|
151239
|
+
const handlersRegisteredRef = import_react33.useRef(false);
|
|
150451
151240
|
import_react33.useEffect(() => {
|
|
150452
151241
|
isProcessingRef.current = isProcessing;
|
|
150453
151242
|
}, [isProcessing]);
|
|
@@ -150527,9 +151316,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150527
151316
|
}
|
|
150528
151317
|
if (processingStartTimeRef.current) {
|
|
150529
151318
|
const workedFor = formatElapsedDuration(Date.now() - processingStartTimeRef.current);
|
|
150530
|
-
|
|
150531
|
-
|
|
150532
|
-
\u273B Worked for ${workedFor}` : `\u273B Worked for ${workedFor}`;
|
|
151319
|
+
setLastWorkedFor(workedFor);
|
|
150533
151320
|
}
|
|
150534
151321
|
setMessages((prev) => [
|
|
150535
151322
|
...prev,
|
|
@@ -150569,10 +151356,11 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150569
151356
|
identityInfo,
|
|
150570
151357
|
processingStartTime,
|
|
150571
151358
|
currentTurnTokens,
|
|
150572
|
-
error: error2
|
|
151359
|
+
error: error2,
|
|
151360
|
+
lastWorkedFor
|
|
150573
151361
|
});
|
|
150574
151362
|
}
|
|
150575
|
-
}, [activeSessionId, messages2, tokenUsage, energyState, voiceState, identityInfo, processingStartTime, currentTurnTokens, error2]);
|
|
151363
|
+
}, [activeSessionId, messages2, tokenUsage, energyState, voiceState, identityInfo, processingStartTime, currentTurnTokens, error2, lastWorkedFor]);
|
|
150576
151364
|
const loadSessionState = import_react33.useCallback((sessionId) => {
|
|
150577
151365
|
const state = sessionUIStates.current.get(sessionId);
|
|
150578
151366
|
const askState = askUserStateRef.current.get(sessionId) || null;
|
|
@@ -150592,6 +151380,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150592
151380
|
setProcessingStartTime(state.processingStartTime);
|
|
150593
151381
|
setCurrentTurnTokens(state.currentTurnTokens);
|
|
150594
151382
|
setError(state.error);
|
|
151383
|
+
setLastWorkedFor(state.lastWorkedFor);
|
|
150595
151384
|
setAskUserState(askState);
|
|
150596
151385
|
} else {
|
|
150597
151386
|
setMessages([]);
|
|
@@ -150609,6 +151398,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150609
151398
|
setProcessingStartTime(undefined);
|
|
150610
151399
|
setCurrentTurnTokens(0);
|
|
150611
151400
|
setError(null);
|
|
151401
|
+
setLastWorkedFor(undefined);
|
|
150612
151402
|
setAskUserState(askState);
|
|
150613
151403
|
}
|
|
150614
151404
|
}, []);
|
|
@@ -150751,21 +151541,24 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150751
151541
|
let cancelled = false;
|
|
150752
151542
|
const initSession = async () => {
|
|
150753
151543
|
try {
|
|
150754
|
-
|
|
150755
|
-
|
|
150756
|
-
|
|
150757
|
-
|
|
150758
|
-
|
|
150759
|
-
|
|
150760
|
-
|
|
150761
|
-
|
|
150762
|
-
|
|
150763
|
-
|
|
150764
|
-
|
|
150765
|
-
|
|
150766
|
-
registryRef.current.
|
|
150767
|
-
|
|
150768
|
-
|
|
151544
|
+
if (!handlersRegisteredRef.current) {
|
|
151545
|
+
handlersRegisteredRef.current = true;
|
|
151546
|
+
registry.onChunk(handleChunk);
|
|
151547
|
+
registry.onError((err) => {
|
|
151548
|
+
const finalized = finalizeResponse("error");
|
|
151549
|
+
if (finalized) {
|
|
151550
|
+
skipNextDoneRef.current = true;
|
|
151551
|
+
}
|
|
151552
|
+
resetTurnState();
|
|
151553
|
+
setError(err.message);
|
|
151554
|
+
setIsProcessing(false);
|
|
151555
|
+
isProcessingRef.current = false;
|
|
151556
|
+
const active = registryRef.current.getActiveSession();
|
|
151557
|
+
if (active) {
|
|
151558
|
+
registryRef.current.setProcessing(active.id, false);
|
|
151559
|
+
}
|
|
151560
|
+
});
|
|
151561
|
+
}
|
|
150769
151562
|
const session = await registry.createSession(cwd2);
|
|
150770
151563
|
setActiveSessionId(session.id);
|
|
150771
151564
|
session.client.setAskUserHandler((request2) => beginAskUser(session.id, request2));
|
|
@@ -150950,7 +151743,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
150950
151743
|
}
|
|
150951
151744
|
}, [cwd2, registry, saveCurrentSessionState, loadSessionState, beginAskUser]);
|
|
150952
151745
|
use_input_default((input, key) => {
|
|
150953
|
-
if (key.ctrl && input === "
|
|
151746
|
+
if (key.ctrl && input === "]") {
|
|
150954
151747
|
if (sessions.length > 0) {
|
|
150955
151748
|
setShowSessionSelector(true);
|
|
150956
151749
|
}
|
|
@@ -151113,6 +151906,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
151113
151906
|
pendingSendsRef.current = pendingSendsRef.current.filter((entry) => entry.sessionId !== activeSession.id);
|
|
151114
151907
|
setActivityLog([]);
|
|
151115
151908
|
activityLogRef.current = [];
|
|
151909
|
+
setLastWorkedFor(undefined);
|
|
151116
151910
|
sessionUIStates.current.set(activeSession.id, {
|
|
151117
151911
|
messages: [],
|
|
151118
151912
|
currentResponse: "",
|
|
@@ -151125,7 +151919,8 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
151125
151919
|
identityInfo,
|
|
151126
151920
|
processingStartTime: undefined,
|
|
151127
151921
|
currentTurnTokens: 0,
|
|
151128
|
-
error: null
|
|
151922
|
+
error: null,
|
|
151923
|
+
lastWorkedFor: undefined
|
|
151129
151924
|
});
|
|
151130
151925
|
} else {
|
|
151131
151926
|
const userMessage = {
|
|
@@ -151220,7 +152015,7 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
151220
152015
|
backgroundProcessingCount,
|
|
151221
152016
|
" session",
|
|
151222
152017
|
backgroundProcessingCount > 1 ? "s" : "",
|
|
151223
|
-
" processing in background (Ctrl+
|
|
152018
|
+
" processing in background (Ctrl+] to switch)"
|
|
151224
152019
|
]
|
|
151225
152020
|
}, undefined, true, undefined, this)
|
|
151226
152021
|
}, undefined, false, undefined, this),
|
|
@@ -151286,6 +152081,17 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
151286
152081
|
tokenCount: currentTurnTokens,
|
|
151287
152082
|
isThinking
|
|
151288
152083
|
}, undefined, false, undefined, this),
|
|
152084
|
+
!isProcessing && lastWorkedFor && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
152085
|
+
marginBottom: 0,
|
|
152086
|
+
marginLeft: 2,
|
|
152087
|
+
children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text3, {
|
|
152088
|
+
color: "gray",
|
|
152089
|
+
children: [
|
|
152090
|
+
"\u273B Worked for ",
|
|
152091
|
+
lastWorkedFor
|
|
152092
|
+
]
|
|
152093
|
+
}, undefined, true, undefined, this)
|
|
152094
|
+
}, undefined, false, undefined, this),
|
|
151289
152095
|
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Input, {
|
|
151290
152096
|
onSubmit: handleSubmit,
|
|
151291
152097
|
isProcessing: isBusy,
|
|
@@ -151293,7 +152099,8 @@ function App2({ cwd: cwd2, version: version3 }) {
|
|
|
151293
152099
|
commands: commands3,
|
|
151294
152100
|
skills,
|
|
151295
152101
|
isAskingUser: Boolean(activeAskQuestion),
|
|
151296
|
-
askPlaceholder
|
|
152102
|
+
askPlaceholder,
|
|
152103
|
+
allowBlankAnswer: activeAskQuestion?.required === false
|
|
151297
152104
|
}, undefined, false, undefined, this),
|
|
151298
152105
|
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Status, {
|
|
151299
152106
|
isProcessing: isBusy,
|
|
@@ -151492,44 +152299,9 @@ function sanitizeTerminalOutput(chunk) {
|
|
|
151492
152299
|
return chunk.replace(/\x1b\[2J\x1b\[3J\x1b\[H/g, "\x1B[H").replace(/\x1b\[3J/g, "");
|
|
151493
152300
|
}
|
|
151494
152301
|
|
|
151495
|
-
// packages/terminal/src/
|
|
151496
|
-
|
|
151497
|
-
|
|
151498
|
-
var VERSION3 = "0.6.54";
|
|
151499
|
-
var SYNC_START = "\x1B[?2026h";
|
|
151500
|
-
var SYNC_END = "\x1B[?2026l";
|
|
151501
|
-
function enableSynchronizedOutput() {
|
|
151502
|
-
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
151503
|
-
let buffer = "";
|
|
151504
|
-
let flushTimeout = null;
|
|
151505
|
-
const flush = () => {
|
|
151506
|
-
if (buffer) {
|
|
151507
|
-
const safe = sanitizeTerminalOutput(buffer);
|
|
151508
|
-
originalWrite(SYNC_START + safe + SYNC_END);
|
|
151509
|
-
buffer = "";
|
|
151510
|
-
}
|
|
151511
|
-
flushTimeout = null;
|
|
151512
|
-
};
|
|
151513
|
-
process.stdout.write = function(chunk, encodingOrCallback, callback) {
|
|
151514
|
-
const raw = typeof chunk === "string" ? chunk : chunk.toString();
|
|
151515
|
-
buffer += raw;
|
|
151516
|
-
if (flushTimeout) {
|
|
151517
|
-
clearTimeout(flushTimeout);
|
|
151518
|
-
}
|
|
151519
|
-
flushTimeout = setTimeout(flush, 0);
|
|
151520
|
-
const cb2 = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
151521
|
-
if (cb2) {
|
|
151522
|
-
setImmediate(() => cb2());
|
|
151523
|
-
}
|
|
151524
|
-
return true;
|
|
151525
|
-
};
|
|
151526
|
-
return () => {
|
|
151527
|
-
if (flushTimeout) {
|
|
151528
|
-
clearTimeout(flushTimeout);
|
|
151529
|
-
}
|
|
151530
|
-
flush();
|
|
151531
|
-
process.stdout.write = originalWrite;
|
|
151532
|
-
};
|
|
152302
|
+
// packages/terminal/src/cli/main.ts
|
|
152303
|
+
function isFlag(arg) {
|
|
152304
|
+
return arg !== undefined && arg.startsWith("-") && arg !== "--";
|
|
151533
152305
|
}
|
|
151534
152306
|
function parseArgs(argv) {
|
|
151535
152307
|
const args = argv.slice(2);
|
|
@@ -151544,10 +152316,21 @@ function parseArgs(argv) {
|
|
|
151544
152316
|
jsonSchema: null,
|
|
151545
152317
|
continue: false,
|
|
151546
152318
|
resume: null,
|
|
151547
|
-
cwdProvided: false
|
|
152319
|
+
cwdProvided: false,
|
|
152320
|
+
errors: []
|
|
151548
152321
|
};
|
|
152322
|
+
let endOfOptions = false;
|
|
152323
|
+
const positionalArgs = [];
|
|
151549
152324
|
for (let i5 = 0;i5 < args.length; i5++) {
|
|
151550
152325
|
const arg = args[i5];
|
|
152326
|
+
if (arg === "--") {
|
|
152327
|
+
endOfOptions = true;
|
|
152328
|
+
continue;
|
|
152329
|
+
}
|
|
152330
|
+
if (endOfOptions) {
|
|
152331
|
+
positionalArgs.push(arg);
|
|
152332
|
+
continue;
|
|
152333
|
+
}
|
|
151551
152334
|
if (arg === "--version" || arg === "-v") {
|
|
151552
152335
|
options.version = true;
|
|
151553
152336
|
continue;
|
|
@@ -151557,29 +152340,62 @@ function parseArgs(argv) {
|
|
|
151557
152340
|
continue;
|
|
151558
152341
|
}
|
|
151559
152342
|
if (arg === "--print" || arg === "-p") {
|
|
151560
|
-
|
|
152343
|
+
const nextArg = args[i5 + 1];
|
|
152344
|
+
if (nextArg === undefined || isFlag(nextArg) || nextArg === "--") {
|
|
152345
|
+
options.print = "";
|
|
152346
|
+
} else {
|
|
152347
|
+
options.print = nextArg;
|
|
152348
|
+
i5++;
|
|
152349
|
+
}
|
|
151561
152350
|
continue;
|
|
151562
152351
|
}
|
|
151563
152352
|
if (arg === "--output-format") {
|
|
151564
|
-
const
|
|
151565
|
-
if (
|
|
151566
|
-
options.
|
|
152353
|
+
const nextArg = args[i5 + 1];
|
|
152354
|
+
if (nextArg === undefined || isFlag(nextArg)) {
|
|
152355
|
+
options.errors.push("--output-format requires a value (text, json, or stream-json)");
|
|
152356
|
+
} else if (nextArg === "text" || nextArg === "json" || nextArg === "stream-json") {
|
|
152357
|
+
options.outputFormat = nextArg;
|
|
152358
|
+
i5++;
|
|
152359
|
+
} else {
|
|
152360
|
+
options.errors.push(`Invalid output format "${nextArg}". Valid options: text, json, stream-json`);
|
|
152361
|
+
i5++;
|
|
151567
152362
|
}
|
|
151568
152363
|
continue;
|
|
151569
152364
|
}
|
|
151570
152365
|
if (arg === "--allowed-tools" || arg === "--allowedTools") {
|
|
151571
|
-
const
|
|
151572
|
-
if (
|
|
151573
|
-
options.
|
|
152366
|
+
const nextArg = args[i5 + 1];
|
|
152367
|
+
if (nextArg === undefined || isFlag(nextArg)) {
|
|
152368
|
+
options.errors.push(`${arg} requires a comma-separated list of tool names`);
|
|
152369
|
+
} else {
|
|
152370
|
+
const seen = new Set;
|
|
152371
|
+
options.allowedTools = nextArg.split(",").map((t9) => t9.trim()).filter((t9) => {
|
|
152372
|
+
if (!t9 || seen.has(t9))
|
|
152373
|
+
return false;
|
|
152374
|
+
seen.add(t9);
|
|
152375
|
+
return true;
|
|
152376
|
+
});
|
|
152377
|
+
i5++;
|
|
151574
152378
|
}
|
|
151575
152379
|
continue;
|
|
151576
152380
|
}
|
|
151577
152381
|
if (arg === "--system-prompt") {
|
|
151578
|
-
|
|
152382
|
+
const nextArg = args[i5 + 1];
|
|
152383
|
+
if (nextArg === undefined || isFlag(nextArg)) {
|
|
152384
|
+
options.errors.push("--system-prompt requires a value");
|
|
152385
|
+
} else {
|
|
152386
|
+
options.systemPrompt = nextArg;
|
|
152387
|
+
i5++;
|
|
152388
|
+
}
|
|
151579
152389
|
continue;
|
|
151580
152390
|
}
|
|
151581
152391
|
if (arg === "--json-schema") {
|
|
151582
|
-
|
|
152392
|
+
const nextArg = args[i5 + 1];
|
|
152393
|
+
if (nextArg === undefined || isFlag(nextArg)) {
|
|
152394
|
+
options.errors.push("--json-schema requires a JSON schema string");
|
|
152395
|
+
} else {
|
|
152396
|
+
options.jsonSchema = nextArg;
|
|
152397
|
+
i5++;
|
|
152398
|
+
}
|
|
151583
152399
|
continue;
|
|
151584
152400
|
}
|
|
151585
152401
|
if (arg === "--continue" || arg === "-c") {
|
|
@@ -151587,20 +152403,163 @@ function parseArgs(argv) {
|
|
|
151587
152403
|
continue;
|
|
151588
152404
|
}
|
|
151589
152405
|
if (arg === "--resume" || arg === "-r") {
|
|
151590
|
-
|
|
152406
|
+
const nextArg = args[i5 + 1];
|
|
152407
|
+
if (nextArg === undefined || isFlag(nextArg)) {
|
|
152408
|
+
options.errors.push(`${arg} requires a session ID`);
|
|
152409
|
+
} else {
|
|
152410
|
+
options.resume = nextArg;
|
|
152411
|
+
i5++;
|
|
152412
|
+
}
|
|
151591
152413
|
continue;
|
|
151592
152414
|
}
|
|
151593
152415
|
if (arg === "--cwd") {
|
|
151594
|
-
|
|
151595
|
-
|
|
152416
|
+
const nextArg = args[i5 + 1];
|
|
152417
|
+
if (nextArg === undefined || isFlag(nextArg)) {
|
|
152418
|
+
options.errors.push("--cwd requires a path");
|
|
152419
|
+
} else {
|
|
152420
|
+
options.cwd = nextArg;
|
|
152421
|
+
options.cwdProvided = true;
|
|
152422
|
+
i5++;
|
|
152423
|
+
}
|
|
151596
152424
|
continue;
|
|
151597
152425
|
}
|
|
151598
|
-
if (
|
|
151599
|
-
|
|
152426
|
+
if (!arg.startsWith("-")) {
|
|
152427
|
+
positionalArgs.push(arg);
|
|
151600
152428
|
}
|
|
151601
152429
|
}
|
|
152430
|
+
if (options.print === "" && positionalArgs.length > 0) {
|
|
152431
|
+
options.print = positionalArgs.join(" ");
|
|
152432
|
+
}
|
|
152433
|
+
if (options.print === null && positionalArgs.length > 0) {
|
|
152434
|
+
options.print = positionalArgs.join(" ");
|
|
152435
|
+
}
|
|
151602
152436
|
return options;
|
|
151603
152437
|
}
|
|
152438
|
+
async function main(argv, deps) {
|
|
152439
|
+
const options = parseArgs(argv);
|
|
152440
|
+
const { runHeadless: runHeadless2, print, exit, VERSION: VERSION3 } = deps;
|
|
152441
|
+
if (options.errors.length > 0) {
|
|
152442
|
+
for (const error2 of options.errors) {
|
|
152443
|
+
print(`Error: ${error2}`);
|
|
152444
|
+
}
|
|
152445
|
+
exit(1);
|
|
152446
|
+
return;
|
|
152447
|
+
}
|
|
152448
|
+
if (options.version) {
|
|
152449
|
+
print(`assistants v${VERSION3}`);
|
|
152450
|
+
exit(0);
|
|
152451
|
+
return;
|
|
152452
|
+
}
|
|
152453
|
+
if (options.help) {
|
|
152454
|
+
print(`
|
|
152455
|
+
assistants - Your personal AI assistant
|
|
152456
|
+
|
|
152457
|
+
Usage:
|
|
152458
|
+
assistants [options] Start interactive mode
|
|
152459
|
+
assistants -p "<prompt>" [options] Run in headless mode
|
|
152460
|
+
|
|
152461
|
+
Options:
|
|
152462
|
+
-h, --help Show this help message
|
|
152463
|
+
-v, --version Show version number
|
|
152464
|
+
|
|
152465
|
+
Headless Mode:
|
|
152466
|
+
-p, --print <prompt> Run non-interactively with the given prompt
|
|
152467
|
+
--output-format <format> Output format: text (default), json, stream-json
|
|
152468
|
+
--allowed-tools <tools> Comma-separated tools to auto-approve (e.g., "Read,Edit,Bash")
|
|
152469
|
+
--system-prompt <prompt> Custom system prompt
|
|
152470
|
+
--json-schema <schema> JSON Schema for structured output (use with --output-format json)
|
|
152471
|
+
-c, --continue Continue the most recent conversation
|
|
152472
|
+
-r, --resume <session_id> Resume a specific session by ID
|
|
152473
|
+
--cwd <path> Set working directory
|
|
152474
|
+
|
|
152475
|
+
Examples:
|
|
152476
|
+
# Ask a question
|
|
152477
|
+
assistants -p "What does the auth module do?"
|
|
152478
|
+
|
|
152479
|
+
# Run with JSON output
|
|
152480
|
+
assistants -p "Summarize this project" --output-format json
|
|
152481
|
+
|
|
152482
|
+
# Stream JSON events
|
|
152483
|
+
assistants -p "Explain this code" --output-format stream-json
|
|
152484
|
+
|
|
152485
|
+
# Auto-approve tools
|
|
152486
|
+
assistants -p "Fix the bug in auth.py" --allowed-tools "Read,Edit,Bash"
|
|
152487
|
+
|
|
152488
|
+
# Get structured output
|
|
152489
|
+
assistants -p "List all functions" --output-format json --json-schema '{"type":"array","items":{"type":"string"}}'
|
|
152490
|
+
|
|
152491
|
+
# Continue conversation
|
|
152492
|
+
assistants -p "What else can you tell me?" --continue
|
|
152493
|
+
|
|
152494
|
+
Interactive Mode:
|
|
152495
|
+
- Type your message and press Enter to send
|
|
152496
|
+
- Use $skill-name to invoke a skill
|
|
152497
|
+
- Use /command for built-in commands
|
|
152498
|
+
- Press Ctrl+] to switch sessions
|
|
152499
|
+
- Press Ctrl+C to exit
|
|
152500
|
+
`);
|
|
152501
|
+
exit(0);
|
|
152502
|
+
return;
|
|
152503
|
+
}
|
|
152504
|
+
if (options.print !== null) {
|
|
152505
|
+
if (!options.print.trim()) {
|
|
152506
|
+
print("Error: Prompt is required with -p/--print flag");
|
|
152507
|
+
exit(1);
|
|
152508
|
+
return;
|
|
152509
|
+
}
|
|
152510
|
+
await runHeadless2({
|
|
152511
|
+
prompt: options.print,
|
|
152512
|
+
cwd: options.cwd,
|
|
152513
|
+
outputFormat: options.outputFormat,
|
|
152514
|
+
allowedTools: options.allowedTools.length > 0 ? options.allowedTools : undefined,
|
|
152515
|
+
systemPrompt: options.systemPrompt || undefined,
|
|
152516
|
+
jsonSchema: options.jsonSchema || undefined,
|
|
152517
|
+
continue: options.continue,
|
|
152518
|
+
resume: options.resume,
|
|
152519
|
+
cwdProvided: options.cwdProvided
|
|
152520
|
+
});
|
|
152521
|
+
}
|
|
152522
|
+
}
|
|
152523
|
+
|
|
152524
|
+
// packages/terminal/src/index.tsx
|
|
152525
|
+
var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
|
|
152526
|
+
setRuntime(bunRuntime);
|
|
152527
|
+
var VERSION3 = "0.6.55";
|
|
152528
|
+
var SYNC_START = "\x1B[?2026h";
|
|
152529
|
+
var SYNC_END = "\x1B[?2026l";
|
|
152530
|
+
function enableSynchronizedOutput() {
|
|
152531
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
152532
|
+
let buffer = "";
|
|
152533
|
+
let flushTimeout = null;
|
|
152534
|
+
const flush = () => {
|
|
152535
|
+
if (buffer) {
|
|
152536
|
+
const safe = sanitizeTerminalOutput(buffer);
|
|
152537
|
+
originalWrite(SYNC_START + safe + SYNC_END);
|
|
152538
|
+
buffer = "";
|
|
152539
|
+
}
|
|
152540
|
+
flushTimeout = null;
|
|
152541
|
+
};
|
|
152542
|
+
process.stdout.write = function(chunk, encodingOrCallback, callback) {
|
|
152543
|
+
const raw = typeof chunk === "string" ? chunk : chunk.toString();
|
|
152544
|
+
buffer += raw;
|
|
152545
|
+
if (flushTimeout) {
|
|
152546
|
+
clearTimeout(flushTimeout);
|
|
152547
|
+
}
|
|
152548
|
+
flushTimeout = setTimeout(flush, 0);
|
|
152549
|
+
const cb2 = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
152550
|
+
if (cb2) {
|
|
152551
|
+
setImmediate(() => cb2());
|
|
152552
|
+
}
|
|
152553
|
+
return true;
|
|
152554
|
+
};
|
|
152555
|
+
return () => {
|
|
152556
|
+
if (flushTimeout) {
|
|
152557
|
+
clearTimeout(flushTimeout);
|
|
152558
|
+
}
|
|
152559
|
+
flush();
|
|
152560
|
+
process.stdout.write = originalWrite;
|
|
152561
|
+
};
|
|
152562
|
+
}
|
|
151604
152563
|
var options = parseArgs(process.argv);
|
|
151605
152564
|
if (options.version) {
|
|
151606
152565
|
console.log(`assistants v${VERSION3}`);
|
|
@@ -151651,7 +152610,7 @@ Interactive Mode:
|
|
|
151651
152610
|
- Type your message and press Enter to send
|
|
151652
152611
|
- Use $skill-name to invoke a skill
|
|
151653
152612
|
- Use /command for built-in commands
|
|
151654
|
-
- Press Ctrl+
|
|
152613
|
+
- Press Ctrl+] to switch sessions
|
|
151655
152614
|
- Press Ctrl+C to exit
|
|
151656
152615
|
`);
|
|
151657
152616
|
process.exit(0);
|
|
@@ -151690,5 +152649,9 @@ if (options.print !== null) {
|
|
|
151690
152649
|
process.exit(0);
|
|
151691
152650
|
});
|
|
151692
152651
|
}
|
|
152652
|
+
export {
|
|
152653
|
+
parseArgs,
|
|
152654
|
+
main
|
|
152655
|
+
};
|
|
151693
152656
|
|
|
151694
|
-
//# debugId=
|
|
152657
|
+
//# debugId=C7451B3CD4B5E92E64756E2164756E21
|