@zaly/agent 0.0.3

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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/agent-BaD2kax9.mjs +68 -0
  4. package/dist/agent-BaD2kax9.mjs.map +1 -0
  5. package/dist/agent-GDLdDc2w.mjs +902 -0
  6. package/dist/agent-GDLdDc2w.mjs.map +1 -0
  7. package/dist/bash-QNMTQ1PD.mjs +142 -0
  8. package/dist/bash-QNMTQ1PD.mjs.map +1 -0
  9. package/dist/compactions-3AjPqjeZ.mjs +190 -0
  10. package/dist/compactions-3AjPqjeZ.mjs.map +1 -0
  11. package/dist/edit-Qxl4w1C2.mjs +170 -0
  12. package/dist/edit-Qxl4w1C2.mjs.map +1 -0
  13. package/dist/env-UUFv5Mye.mjs +30 -0
  14. package/dist/env-UUFv5Mye.mjs.map +1 -0
  15. package/dist/fetch-D8cmmA3E.mjs +111 -0
  16. package/dist/fetch-D8cmmA3E.mjs.map +1 -0
  17. package/dist/find-Bah9SKWX.mjs +87 -0
  18. package/dist/find-Bah9SKWX.mjs.map +1 -0
  19. package/dist/grep-B2Ax7ewy.mjs +244 -0
  20. package/dist/grep-B2Ax7ewy.mjs.map +1 -0
  21. package/dist/index-node-esm-D6YQRKEz.mjs +1537 -0
  22. package/dist/index-node-esm-D6YQRKEz.mjs.map +1 -0
  23. package/dist/index.d.mts +1605 -0
  24. package/dist/index.d.mts.map +1 -0
  25. package/dist/index.mjs +948 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/jsonl-Dw_VBp46.mjs +134 -0
  28. package/dist/jsonl-Dw_VBp46.mjs.map +1 -0
  29. package/dist/manager-D-lWBTHG.mjs +271 -0
  30. package/dist/manager-D-lWBTHG.mjs.map +1 -0
  31. package/dist/markdown-Bv4J8cg3.mjs +53 -0
  32. package/dist/markdown-Bv4J8cg3.mjs.map +1 -0
  33. package/dist/masker-Bv9KjkDF.mjs +396 -0
  34. package/dist/masker-Bv9KjkDF.mjs.map +1 -0
  35. package/dist/model-_3ZA2xqm.mjs +27 -0
  36. package/dist/model-_3ZA2xqm.mjs.map +1 -0
  37. package/dist/notify-DpG_m9iP.mjs +96 -0
  38. package/dist/notify-DpG_m9iP.mjs.map +1 -0
  39. package/dist/read-Djsx2NZl.mjs +205 -0
  40. package/dist/read-Djsx2NZl.mjs.map +1 -0
  41. package/dist/registry-CFmR5G3J.mjs +564 -0
  42. package/dist/registry-CFmR5G3J.mjs.map +1 -0
  43. package/dist/search-CRw247ES.mjs +121 -0
  44. package/dist/search-CRw247ES.mjs.map +1 -0
  45. package/dist/session/claude.d.mts +59 -0
  46. package/dist/session/claude.d.mts.map +1 -0
  47. package/dist/session/claude.mjs +455 -0
  48. package/dist/session/claude.mjs.map +1 -0
  49. package/dist/session-CUUdWFC5.mjs +450 -0
  50. package/dist/session-CUUdWFC5.mjs.map +1 -0
  51. package/dist/session-Ds5nJz5A.d.mts +346 -0
  52. package/dist/session-Ds5nJz5A.d.mts.map +1 -0
  53. package/dist/session.d.mts +37 -0
  54. package/dist/session.d.mts.map +1 -0
  55. package/dist/session.mjs +106 -0
  56. package/dist/session.mjs.map +1 -0
  57. package/dist/subagent-baqzjRMc.mjs +136 -0
  58. package/dist/subagent-baqzjRMc.mjs.map +1 -0
  59. package/dist/swarm-BJ3gQA1Z.mjs +194 -0
  60. package/dist/swarm-BJ3gQA1Z.mjs.map +1 -0
  61. package/dist/swarm-BuFx_xeY.mjs +109 -0
  62. package/dist/swarm-BuFx_xeY.mjs.map +1 -0
  63. package/dist/tasks-D8rARHZ1.mjs +65 -0
  64. package/dist/tasks-D8rARHZ1.mjs.map +1 -0
  65. package/dist/tasks-WmC270X2.mjs +615 -0
  66. package/dist/tasks-WmC270X2.mjs.map +1 -0
  67. package/dist/tools-DW1dbcCz.mjs +695 -0
  68. package/dist/tools-DW1dbcCz.mjs.map +1 -0
  69. package/dist/truncate-BSMXgiXj.mjs +102 -0
  70. package/dist/truncate-BSMXgiXj.mjs.map +1 -0
  71. package/dist/uuid-ByXP5SO5.mjs +59 -0
  72. package/dist/uuid-ByXP5SO5.mjs.map +1 -0
  73. package/dist/wakeup-BYqTnfSp.mjs +29 -0
  74. package/dist/wakeup-BYqTnfSp.mjs.map +1 -0
  75. package/dist/write-D3L-92vj.mjs +89 -0
  76. package/dist/write-D3L-92vj.mjs.map +1 -0
  77. package/package.json +63 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-DW1dbcCz.mjs","names":[],"sources":["../../../node_modules/.bun/shell-quote@1.9.0/node_modules/shell-quote/quote.js","../../../node_modules/.bun/shell-quote@1.9.0/node_modules/shell-quote/parse.js","../../../node_modules/.bun/shell-quote@1.9.0/node_modules/shell-quote/index.js","../src/utils/bash/parser.ts","../src/utils/bash/tools.ts"],"sourcesContent":["'use strict';\n\n/** @import { ControlOperator } from './parse' */\n\n/** @type {ControlOperator['op'][]} */\nvar OPS = /** @type {const} */ ([\n\t'||',\n\t'&&',\n\t';;',\n\t'|&',\n\t'<(',\n\t'<<<',\n\t'>>',\n\t'>&',\n\t'<&',\n\t'&',\n\t';',\n\t'(',\n\t')',\n\t'|',\n\t'<',\n\t'>'\n]);\nvar LINE_TERMINATORS = /[\\n\\r\\u2028\\u2029]/;\nvar GLOB_SHELL_SPECIAL = /[\\s#!\"$&'():;<=>@\\\\^`|]/g;\n\n/** @type {import('./quote')} */\nmodule.exports = function quote(xs) {\n\treturn xs.map(function (s) {\n\t\tif (s === '') {\n\t\t\treturn /** @type {const} */ ('\\'\\'');\n\t\t}\n\t\tif (s && typeof s === 'object') {\n\t\t\tif ('op' in s && s.op === 'glob') {\n\t\t\t\tif (typeof s.pattern !== 'string') {\n\t\t\t\t\tthrow new TypeError('glob token requires a string `pattern`');\n\t\t\t\t}\n\t\t\t\tif (LINE_TERMINATORS.test(s.pattern)) {\n\t\t\t\t\tthrow new TypeError('glob `pattern` must not contain line terminators');\n\t\t\t\t}\n\t\t\t\treturn s.pattern.replace(GLOB_SHELL_SPECIAL, '\\\\$&');\n\t\t\t}\n\t\t\tif ('op' in s && typeof s.op === 'string') {\n\t\t\t\tif (OPS.indexOf(s.op) < 0) {\n\t\t\t\t\tthrow new TypeError('invalid `op` value: ' + JSON.stringify(s.op));\n\t\t\t\t}\n\t\t\t\treturn s.op.replace(/[\\s\\S]/g, '\\\\$&');\n\t\t\t}\n\t\t\tif ('comment' in s && typeof s.comment === 'string') {\n\t\t\t\tif (LINE_TERMINATORS.test(s.comment)) {\n\t\t\t\t\tthrow new TypeError('`comment` must not contain line terminators');\n\t\t\t\t}\n\t\t\t\treturn '#' + s.comment;\n\t\t\t}\n\t\t\tthrow new TypeError('unrecognized object token shape');\n\t\t}\n\t\tif ((/[\"\\s\\\\]/).test(s) && !(/'/).test(s)) {\n\t\t\treturn \"'\" + s.replace(/(['])/g, '\\\\$1') + \"'\";\n\t\t}\n\t\tif ((/[\"'\\s]/).test(s)) {\n\t\t\treturn '\"' + s.replace(/([\"\\\\$`!])/g, '\\\\$1') + '\"';\n\t\t}\n\t\treturn String(s).replace(/([A-Za-z]:)?([#!\"$&'()*,:;<=>?@[\\\\\\]^`{|}~])/g, '$1\\\\$2');\n\t}).join(' ');\n};\n","'use strict';\n\n/**\n * @import {\n * \tControlOperator,\n * \tEnv,\n * \tGlobPattern,\n * \tParseEntry,\n * } from './parse' */\n\n// '<(' is process substitution operator and\n// can be parsed the same as control operator\nvar CONTROL = /** @type {const} */ ('(?:') + /** @type {const} */ ([\n\t'\\\\|\\\\|',\n\t'\\\\&\\\\&',\n\t';;',\n\t'\\\\|\\\\&',\n\t'\\\\<\\\\(',\n\t'\\\\<\\\\<\\\\<',\n\t'>>',\n\t'>\\\\&',\n\t'<\\\\&',\n\t'[&;()|<>]'\n]).join(/** @type {const} */ ('|')) + /** @type {const} */ (')');\nvar controlRE = new RegExp('^' + CONTROL + '$');\nvar META = /** @type {const} */ ('|&;()<> \\\\t');\nvar SINGLE_QUOTE = /** @type {const} */ ('\"((\\\\\\\\\"|[^\"])*?)\"');\nvar DOUBLE_QUOTE = /** @type {const} */ ('\\'((\\\\\\\\\\'|[^\\'])*?)\\'');\nvar hash = /^#$/;\n\nvar SQ = /** @type {const} */ (\"'\");\nvar DQ = /** @type {const} */ ('\"');\nvar DS = /** @type {const} */ ('$');\n\nvar TOKEN = '';\nvar mult = /** @type {const} */ (0x100000000); // Math.pow(16, 8);\nfor (var i = 0; i < 4; i++) {\n\tTOKEN += (mult * Math.random()).toString(16);\n}\nvar startsWithToken = new RegExp('^' + TOKEN);\n\n/**\n * @param {string} s\n * @param {RegExp} r\n */\nfunction matchAll(s, r) {\n\tvar origIndex = r.lastIndex;\n\n\tvar matches = [];\n\tvar matchObj;\n\n\twhile ((matchObj = r.exec(s))) {\n\t\tmatches[matches.length] = matchObj;\n\t\tif (r.lastIndex === matchObj.index) {\n\t\t\tr.lastIndex += 1;\n\t\t}\n\t}\n\n\tr.lastIndex = origIndex;\n\n\treturn matches;\n}\n\n/**\n * @param {Env} env\n * @param {string} pre\n * @param {string} key\n */\nfunction getVar(env, pre, key) {\n\tvar r = typeof env === 'function' ? env(key) : env[key];\n\tif (typeof r === 'undefined' && key != '') {\n\t\tr = '';\n\t} else if (typeof r === 'undefined') {\n\t\tr = '$';\n\t}\n\n\tif (typeof r === 'object') {\n\t\treturn pre + TOKEN + JSON.stringify(r) + TOKEN;\n\t}\n\treturn pre + r;\n}\n\n/**\n * @param {string} string\n * @param {Env} [env]\n * @param {{ escape?: string }} [opts]\n * @returns {ParseEntry[]}\n */\nfunction parseInternal(string, env, opts) {\n\tif (!opts) {\n\t\topts = {};\n\t}\n\tvar BS = opts.escape || '\\\\';\n\tvar BAREWORD = '(\\\\' + BS + '[\\'\"' + META + ']|[^\\\\s\\'\"' + META + '])+';\n\n\tvar chunker = new RegExp([\n\t\t'(' + CONTROL + ')', // control chars\n\t\t'(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')+'\n\t].join('|'), 'g');\n\n\tvar matches = matchAll(string, chunker);\n\n\tif (matches.length === 0) {\n\t\treturn [];\n\t}\n\tif (!env) {\n\t\tenv = {};\n\t}\n\n\tvar commented = false;\n\n\treturn matches.map(function (match) {\n\t\tvar s = match[0];\n\t\tif (!s || commented) {\n\t\t\treturn void undefined;\n\t\t}\n\t\tif (controlRE.test(s)) {\n\t\t\treturn /** @type {ControlOperator} */ ({ op: s });\n\t\t}\n\n\t\t// Hand-written scanner/parser for Bash quoting rules:\n\t\t//\n\t\t// 1. inside single quotes, all characters are printed literally.\n\t\t// 2. inside double quotes, all characters are printed literally\n\t\t// except variables prefixed by '$' and backslashes followed by\n\t\t// either a double quote or another backslash.\n\t\t// 3. outside of any quotes, backslashes are treated as escape\n\t\t// characters and not printed (unless they are themselves escaped)\n\t\t// 4. quote context can switch mid-token if there is no whitespace\n\t\t// between the two quote contexts (e.g. all'one'\"token\" parses as\n\t\t// \"allonetoken\")\n\t\t/** @type {string | boolean} */\n\t\tvar quote = false;\n\t\tvar esc = false;\n\t\tvar out = '';\n\t\tvar isGlob = false;\n\t\t/** @type {number} */\n\t\tvar i;\n\n\t\tfunction parseEnvVar() {\n\t\t\ti += 1;\n\t\t\t/** @type {number | RegExpMatchArray | null} */\n\t\t\tvar varend;\n\t\t\t/** @type {string} */\n\t\t\tvar varname;\n\t\t\tvar char = s.charAt(i);\n\n\t\t\tif (char === '{') {\n\t\t\t\ti += 1;\n\t\t\t\tif (s.charAt(i) === '}') {\n\t\t\t\t\tthrow new Error('Bad substitution: ' + s.slice(i - 2, i + 1));\n\t\t\t\t}\n\t\t\t\tvarend = s.indexOf('}', i);\n\t\t\t\tif (varend < 0) {\n\t\t\t\t\tthrow new Error('Bad substitution: ' + s.slice(i));\n\t\t\t\t}\n\t\t\t\tvarname = s.slice(i, varend);\n\t\t\t\ti = varend;\n\t\t\t} else if ((/[*@#?$!_-]/).test(char)) {\n\t\t\t\tvarname = char;\n\t\t\t\ti += 1;\n\t\t\t} else {\n\t\t\t\tvar slicedFromI = s.slice(i);\n\t\t\t\tvarend = slicedFromI.match(/[^\\w\\d_]/);\n\t\t\t\tif (!varend) {\n\t\t\t\t\tvarname = slicedFromI;\n\t\t\t\t\ti = s.length;\n\t\t\t\t} else {\n\t\t\t\t\tvarname = slicedFromI.slice(0, varend.index);\n\t\t\t\t\ti += /** @type {number} */ (varend.index) - 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn getVar(/** @type {NonNullable<typeof env>} */ (env), '', varname);\n\t\t}\n\n\t\tfor (i = 0; i < s.length; i++) {\n\t\t\tvar c = s.charAt(i);\n\t\t\tisGlob = isGlob || (!quote && (c === '*' || c === '?'));\n\t\t\tif (esc) {\n\t\t\t\tout += c;\n\t\t\t\tesc = false;\n\t\t\t} else if (quote) {\n\t\t\t\tif (c === quote) {\n\t\t\t\t\tquote = false;\n\t\t\t\t} else if (quote == SQ) {\n\t\t\t\t\tout += c;\n\t\t\t\t} else { // Double quote\n\t\t\t\t\tif (c === BS) {\n\t\t\t\t\t\ti += 1;\n\t\t\t\t\t\tc = s.charAt(i);\n\t\t\t\t\t\tif (c === DQ || c === BS || c === DS) {\n\t\t\t\t\t\t\tout += c;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tout += BS + c;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (c === DS) {\n\t\t\t\t\t\tout += parseEnvVar();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tout += c;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (c === DQ || c === SQ) {\n\t\t\t\tquote = c;\n\t\t\t} else if (controlRE.test(c)) {\n\t\t\t\treturn /** @type {ControlOperator} */ ({ op: s });\n\t\t\t} else if (hash.test(c)) {\n\t\t\t\tcommented = true;\n\t\t\t\tvar commentObj = { comment: string.slice(match.index + i + 1) };\n\t\t\t\tif (out.length) {\n\t\t\t\t\treturn /** @type {const} */ ([out, commentObj]);\n\t\t\t\t}\n\t\t\t\treturn /** @type {const} */ ([commentObj]);\n\t\t\t} else if (c === BS) {\n\t\t\t\tesc = true;\n\t\t\t} else if (c === DS) {\n\t\t\t\tout += parseEnvVar();\n\t\t\t} else {\n\t\t\t\tout += c;\n\t\t\t}\n\t\t}\n\n\t\tif (isGlob) {\n\t\t\treturn /** @type {GlobPattern} */ ({ op: 'glob', pattern: out });\n\t\t}\n\n\t\treturn out;\n\t}).reduce(function (prev, arg) { // finalize parsed arguments\n\t\tif (typeof arg === 'undefined') {\n\t\t\treturn prev;\n\t\t}\n\t\t/** @type {ParseEntry[]} */ ([]).concat(arg).forEach(function (entry) {\n\t\t\tprev[prev.length] = entry;\n\t\t});\n\t\treturn prev;\n\t}, /** @type {ParseEntry[]} */ ([]));\n}\n\n/** @type {import('./parse')} */\nmodule.exports = function parse(s, env, opts) {\n\tvar mapped = parseInternal(s, env, opts);\n\tif (typeof env !== 'function') {\n\t\treturn mapped;\n\t}\n\treturn mapped.reduce(function (acc, s) {\n\t\tif (typeof s === 'object') {\n\t\t\tacc[acc.length] = s;\n\t\t\treturn acc;\n\t\t}\n\t\tvar xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')', 'g'));\n\t\tif (xs.length === 1) {\n\t\t\tacc[acc.length] = xs[0];\n\t\t\treturn acc;\n\t\t}\n\t\txs.filter(Boolean).forEach(function (x) {\n\t\t\tacc[acc.length] = startsWithToken.test(x)\n\t\t\t\t? JSON.parse(x.split(TOKEN)[1])\n\t\t\t\t: x;\n\t\t});\n\t\treturn acc;\n\t}, /** @type {ParseEntry[]} */ ([]));\n};\n","'use strict';\n\nexports.quote = require('./quote');\nexports.parse = require('./parse');\n","import { safeStringify } from \"@zaly/shared\"\nimport { parse as shellParse } from \"shell-quote\"\n\n/** A shell command broken down into its structural parts. One bash\n * input may parse to multiple segments (joined by `|`, `&&`, `||`, `;`).\n * Each segment is evaluated independently by the policy. */\nexport interface Segment {\n cmd: string\n args: string[]\n dyn?: Set<string>\n /** File paths the segment reads from (`< file` redirects). Built-in\n * tools may add inferred reads in the policy layer. */\n reads: string[]\n /** File paths the segment writes to (`> file` / `>> file`). */\n writes: { path: string; mode: \"trunc\" | \"append\" }[]\n /** True if the input contains command substitution (`$()` or backticks)\n * or any other dynamic-evaluation construct. Forces ask. */\n hasCommandSubst: boolean\n}\n\nexport type ParseResult = { ok: true; segments: Segment[] } | { ok: false; reason: string }\n\n/** Targets a redirect can write to without filesystem implications.\n * Anything else is treated as a file write and goes through fileWrite\n * policy. */\nconst SAFE_REDIRECT_TARGETS = new Set([\"/dev/null\", \"/dev/stderr\", \"/dev/stdout\"])\n\n/** A redirect target like `&1`, `&2`, `&-` — duplicates an existing fd,\n * no filesystem effect. */\nfunction isFdRedirect(target: string): boolean {\n return /^&(?:\\d+|-)$/.test(target)\n}\n\ntype Op = { op: string }\ntype Glob = { op: \"glob\"; pattern: string }\ntype Comment = { comment: string }\ntype Token = string | Op | Glob | Comment\n\nconst COMMAND_SEPS = new Set([\"|\", \"&&\", \"||\", \";\"])\nconst REDIRECT_OPS = new Set([\">\", \">>\", \"<\", \">&\", \"<&\"])\nconst DYN_SENTINEL = \"\\u{E000}\"\n\n/** Parse a bash command line. Returns segments with a flag for\n * unsupported constructs that force \"ask\" downstream. Heredocs,\n * subshells, command substitution, and backticks all surface as\n * `hasCommandSubst: true` (the umbrella \"we can't reason about this\"\n * signal) rather than parse failures, so the policy can still decide. */\nexport function parseBash(input: string): ParseResult {\n // Heredocs derail shell-quote (the body bleeds into surrounding tokens),\n // so we bail to ok:false here. checkBash maps that to ask.\n if (/<<-?\\s*['\"]?\\w+/.test(input)) {\n return { ok: false, reason: \"heredoc not supported\" }\n }\n // Backticks command-substitute in bash unless inside single quotes\n // (single quotes preserve everything literally; double quotes don't\n // suppress substitution). Flag only the unquoted form so embedded\n // template literals in `bun -e '... ${x} `tpl` ...'` don't trip us.\n const hasBackticks = hasUnquotedBacktick(input)\n\n let rawTokens: Token[]\n try {\n rawTokens = shellParse(input, (e) =>\n e === \"\" ? undefined : `${DYN_SENTINEL}\\${${e}}`\n ) as Token[]\n } catch (error) {\n return { ok: false, reason: `tokenize: ${(error as Error).message}` }\n }\n\n // Inline plain `(subshell)` contents as separate segments; strip\n // `$(...)` regions and flag as command substitution. Backticks\n // already detected via the pre-scan above.\n const flat = flattenParens(rawTokens)\n const hasCommandSubst = hasBackticks || flat.hasCommandSubst\n\n const segmentTokens: Token[][] = [[]]\n for (const t of flat.tokens) {\n if (typeof t === \"object\" && \"comment\" in t) continue\n\n if (typeof t === \"object\" && \"op\" in t && COMMAND_SEPS.has(t.op)) {\n if (segmentTokens.at(-1)!.length > 0) segmentTokens.push([])\n continue\n }\n\n segmentTokens.at(-1)!.push(t)\n }\n\n if (segmentTokens.at(-1)!.length === 0) segmentTokens.pop()\n\n const segments: Segment[] = []\n for (const segToks of segmentTokens) {\n const seg = parseSegment(segToks)\n if (!seg.ok) return seg\n if (seg.segment) segments.push({ ...seg.segment, hasCommandSubst })\n }\n\n if (segments.length === 0) {\n return { ok: false, reason: \"empty input\" }\n }\n return { ok: true, segments }\n}\n\nfunction parseSegment(\n tokens: Token[]\n):\n | { ok: true; segment: Omit<Segment, \"hasCommandSubst\"> | undefined }\n | { ok: false; reason: string } {\n let args: string[] = []\n let reads: string[] = []\n let writes: { path: string; mode: \"trunc\" | \"append\" }[] = []\n const dyn = new Set<string>()\n\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i]\n\n if (typeof t === \"string\") {\n // FD prefix on a redirect (`2>&1`, `2>file`, `1<&-`) — shell-quote\n // splits the leading digit off as its own token. Don't treat it\n // as a regular arg; the redirect handler below picks up the op.\n if (/^\\d+$/.test(t)) {\n const next = tokens[i + 1]\n if (typeof next === \"object\" && \"op\" in next && REDIRECT_OPS.has(next.op)) {\n continue\n }\n }\n args.push(t)\n continue\n }\n\n if (\"comment\" in t) continue\n\n if (\"pattern\" in t) {\n // Pass globs through as their pattern — policy layer decides.\n args.push(t.pattern)\n dyn.add(t.pattern)\n continue\n }\n\n if (!(\"op\" in t)) {\n return { ok: false, reason: `unsupported token: ${safeStringify(t)}` }\n }\n\n if (REDIRECT_OPS.has(t.op)) {\n const next = tokens[i + 1]\n if (typeof next !== \"string\") {\n return { ok: false, reason: `redirect target missing after ${t.op}` }\n }\n i++ // consume target\n\n if (t.op === \">\" || t.op === \">>\") {\n // Skip safe targets (/dev/null etc.) and fd redirects entirely.\n if (SAFE_REDIRECT_TARGETS.has(next) || isFdRedirect(next)) continue\n writes.push({ mode: t.op === \">>\" ? \"append\" : \"trunc\", path: next })\n } else if (t.op === \"<\") {\n if (SAFE_REDIRECT_TARGETS.has(next) || isFdRedirect(next)) continue\n reads.push(next)\n }\n // `>&` / `<&` are fd-duplicating (`2>&1`-style) — no fs impact.\n continue\n }\n\n return { ok: false, reason: `unsupported op: ${t.op}` }\n }\n\n const dynPath = (p: string) => {\n if (!p.includes(DYN_SENTINEL)) return p\n p = p.replaceAll(DYN_SENTINEL, \"\")\n dyn.add(p)\n return p\n }\n\n args = args.map((a) => dynPath(a))\n writes = writes.map((w) => ({ ...w, path: dynPath(w.path) }))\n reads = reads.map(dynPath)\n\n // Strip leading wrapper commands (`time`, `nohup`, `nice`, …) so the\n // policy sees the real command being run. `time bun test` evaluates\n // as `bun test`; a bare `time` (or `time (subshell)` after flatten)\n // leaves nothing and signals the caller to drop the segment.\n const unwrapped = unwrapWrappers(args)\n if (unwrapped.length === 0) {\n return { ok: true, segment: undefined }\n }\n\n return {\n ok: true,\n segment: {\n args: unwrapped.slice(1),\n cmd: unwrapped[0],\n dyn,\n reads,\n writes,\n },\n }\n}\n\ninterface WrapperSpec {\n /** Flags that consume the next arg as their value. */\n valueFlags?: Set<string>\n}\n\n/** Commands that wrap another command — they're transparent for\n * permission purposes, since the security-relevant action is whatever\n * they run. `sudo` is intentionally absent: it changes the effective\n * user and must always be flagged. */\nconst WRAPPERS: Partial<Record<string, WrapperSpec>> = {\n command: {}, // bash builtin, bypasses aliases\n ionice: { valueFlags: new Set([\"-c\", \"-n\", \"-p\", \"-P\", \"-u\"]) },\n nice: { valueFlags: new Set([\"-n\", \"--adjustment\"]) },\n nohup: {},\n time: {}, // bash reserved word, also GNU /usr/bin/time\n}\n\n/** Detect backticks outside single-quoted regions. Single quotes in\n * bash can't be escaped from within, so the toggle is a simple state\n * flip on every `'`. Double quotes do not suppress backtick\n * substitution and are ignored here. */\nfunction hasUnquotedBacktick(input: string): boolean {\n let inSingle = false\n for (const c of input) {\n if (c === \"'\") {\n inSingle = !inSingle\n continue\n }\n if (c === \"`\" && !inSingle) return true\n }\n return false\n}\n\n/** Strip leading wrapper-command prefixes from an arg list. Repeats\n * while the head is a wrapper (so `time nice -n 5 cmd` → `cmd`).\n * Wrapper-flags (and their values, where applicable) are consumed\n * along with the wrapper itself. */\nfunction unwrapWrappers(args: string[]): string[] {\n let current = args\n while (current.length > 0) {\n const spec = WRAPPERS[current[0]]\n if (!spec) break\n const valueFlags = spec.valueFlags ?? new Set()\n let i = 1\n while (i < current.length && current[i].startsWith(\"-\")) {\n const flag = current[i]\n i++\n if (valueFlags.has(flag) && i < current.length) i++ // consume flag value\n }\n current = current.slice(i)\n }\n return current\n}\n\n/** Walk a token list and resolve paren constructs:\n * - Plain `(subshell)` → tokens inlined with `;` boundaries on each\n * side, so the contents become independent segments evaluated by\n * the same policy. The subshell's own scope (cwd changes etc.) is\n * irrelevant to permissions — each command is checked individually.\n * - `$(...)` (command substitution) → stripped entirely, with the\n * `hasCommandSubst` flag raised. The dynamic value can't be\n * statically reasoned about. Detected by the leading `$` token\n * that shell-quote emits before the `(`.\n *\n * Recurses into subshell contents to handle nesting and to catch\n * `$(...)` constructs nested inside subshells. */\nfunction flattenParens(tokens: Token[]): { tokens: Token[]; hasCommandSubst: boolean } {\n const out: Token[] = []\n let hasCommandSubst = false\n let i = 0\n while (i < tokens.length) {\n const t = tokens[i]\n if (typeof t === \"object\" && \"op\" in t && t.op === \"(\") {\n const isSubst = out.at(-1) === \"$\"\n if (isSubst) out.pop()\n\n // Find matching `)`, accounting for nesting.\n let depth = 1\n let j = i + 1\n while (j < tokens.length && depth > 0) {\n const tj = tokens[j]\n if (typeof tj === \"object\" && \"op\" in tj) {\n if (tj.op === \"(\") depth++\n else if (tj.op === \")\") depth--\n }\n if (depth > 0) j++\n }\n const inner = tokens.slice(i + 1, j)\n\n if (isSubst) {\n hasCommandSubst = true\n // Drop the entire $(...) — its value is dynamic.\n } else {\n const sub = flattenParens(inner)\n if (sub.hasCommandSubst) hasCommandSubst = true\n // Surround with separators so subshell contents form their own\n // segments regardless of what's adjacent in the outer chain.\n out.push({ op: \";\" }, ...sub.tokens, { op: \";\" })\n }\n i = j + 1\n continue\n }\n\n if (typeof t === \"object\" && \"op\" in t && t.op === \")\") {\n // Stray closing paren (unbalanced input). Skip — the parser's\n // overall result will likely fail downstream on a malformed segment.\n i++\n continue\n }\n\n out.push(t)\n i++\n }\n return { hasCommandSubst, tokens: out }\n}\n","/**\n * Built-in tool registry — per-command knowledge of which args are\n * input files, output files, and which invocation modes are too\n * complex to safely model.\n *\n * The policy engine consults this registry alongside explicit redirect\n * info from the parser to determine per-command file I/O for the\n * fileRead/fileWrite policy hooks.\n */\nexport interface ToolSpec {\n /** File paths the command will read from, derived from args. */\n reads?: (args: string[]) => string[]\n /** File paths the command will write to. */\n writes?: (args: string[]) => string[]\n /** Returns true if the args invoke a mode the parser can't safely\n * reason about (e.g. `sed -e 'w out'`, `find -exec`). Forces ask. */\n unsafe?: (args: string[]) => boolean\n}\n\n/** All bare positional args (no leading dash). Default for many\n * read-only utilities. */\nfunction positionals(args: string[]): string[] {\n return args.filter((a) => !a.startsWith(\"-\") && a !== \"\")\n}\n\n/** All positionals after the first one (e.g. `grep PATTERN file...`). */\nfunction positionalsAfterFirst(args: string[]): string[] {\n const out: string[] = []\n let seenFirst = false\n for (const a of args) {\n if (a.startsWith(\"-\") || a === \"\") continue\n if (!seenFirst) {\n seenFirst = true\n continue\n }\n out.push(a)\n }\n return out\n}\n\n// ── sed ──────────────────────────────────────────────────────────────────\n\n/** Flags whose value is the *next* arg, not part of the script. */\nconst SED_VALUE_FLAGS = new Set([\"-e\", \"--expression\", \"-f\", \"--file\"])\n\n/** Walk sed args, returning [scripts, positionalsAfterScript]. The\n * \"script\" is everything passed via `-e` plus the first bare arg\n * (the implicit script when no `-e` is given). Positionals after\n * that are input files. */\nfunction sedSplit(args: string[]): { scripts: string[]; files: string[] } {\n const scripts: string[] = []\n const files: string[] = []\n let needsImplicitScript = !args.some(\n (a) => a === \"-e\" || a === \"--expression\" || a === \"-f\" || a === \"--file\"\n )\n\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a.startsWith(\"-\")) {\n if (SED_VALUE_FLAGS.has(a) && i + 1 < args.length) {\n if (a === \"-e\" || a === \"--expression\") scripts.push(args[i + 1])\n i++ // consume value\n }\n continue\n }\n if (needsImplicitScript) {\n scripts.push(a)\n needsImplicitScript = false\n continue\n }\n files.push(a)\n }\n return { files, scripts }\n}\n\nconst SED_INPLACE = new Set([\"-i\", \"--in-place\"])\n\nfunction sedReads(args: string[]): string[] {\n return sedSplit(args).files\n}\n\nfunction sedWrites(args: string[]): string[] {\n // `-i` rewrites positional input files; also supports `-i.bak` form.\n const inPlace = args.some((a) => SED_INPLACE.has(a) || a.startsWith(\"-i.\"))\n return inPlace ? sedSplit(args).files : []\n}\n\nfunction sedUnsafe(args: string[]): boolean {\n // `w` / `W` write commands and `r` / `R` read commands inside the\n // script can do file I/O the parser can't see. Bail to ask.\n for (const script of sedSplit(args).scripts) {\n if (/(?:^|;|\\n)\\s*[wWrR]\\s+\\S/.test(script)) return true\n }\n return false\n}\n\n// ── awk ──────────────────────────────────────────────────────────────────\n\nfunction awkReads(args: string[]): string[] {\n // awk usage: `awk [opts] 'script' [files...]` or `awk [opts] -f file [files...]`\n // Files come after the script. Skip flags + their values.\n let sawScript = false\n let usingFFlag = false\n const files: string[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a === \"-f\" || a === \"--file\") {\n if (i + 1 < args.length) files.push(args[i + 1]) // script file is read too\n i++\n sawScript = true\n usingFFlag = true\n continue\n }\n if (a.startsWith(\"-\")) {\n // Some awk flags take a value (-v, -F)\n if (a === \"-v\" || a === \"-F\") i++\n continue\n }\n if (!sawScript) {\n sawScript = true\n continue\n }\n files.push(a)\n }\n return usingFFlag ? files : files\n}\n\nfunction awkUnsafe(args: string[]): boolean {\n // awk's `print > file` and `printf > file` inside a script write to\n // files. Detecting reliably without parsing the script is hard, so\n // bail to ask if the script (or any -e value) contains a `>` operator.\n // Conservative — false positives are fine for \"ask\".\n let sawScript = false\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a === \"-f\" || a === \"--file\" || a === \"-v\" || a === \"-F\") {\n i++\n continue\n }\n if (a.startsWith(\"-\")) continue\n if (!sawScript) {\n sawScript = true\n if (/[>|]/.test(a)) return true\n }\n }\n return false\n}\n\n// ── find ─────────────────────────────────────────────────────────────────\n\nfunction findReads(args: string[]): string[] {\n // First positional (or `.`) is the path. Multiple paths possible\n // before the first `-action` flag.\n const out: string[] = []\n for (const a of args) {\n if (a.startsWith(\"-\")) break\n out.push(a)\n }\n return out.length > 0 ? out : [\".\"]\n}\n\nfunction findUnsafe(args: string[]): boolean {\n // `-delete`, `-exec`, `-execdir`, `-ok`, `-okdir` all do work we\n // can't trace.\n return args.some(\n (a) => a === \"-delete\" || a === \"-exec\" || a === \"-execdir\" || a === \"-ok\" || a === \"-okdir\"\n )\n}\n\n// ── dynamic execution ────────────────────────────────────────────────────\n//\n// These commands run arbitrary code passed as arguments or sourced from\n// files. We can't reason about their effects from the parser; force ask.\n\nconst dynamicExec = (): boolean => true\n\n// ── registry ─────────────────────────────────────────────────────────────\n\nexport const TOOLS: Partial<Record<string, ToolSpec>> = {\n \".\": { unsafe: dynamicExec }, // sh: source alias\n awk: { reads: awkReads, unsafe: awkUnsafe },\n basename: {},\n cat: { reads: positionals },\n cd: {},\n cut: { reads: positionals },\n diff: { reads: positionals },\n dirname: {},\n echo: {},\n eval: { unsafe: dynamicExec }, // runs its argument as shell input\n exec: { unsafe: dynamicExec }, // replaces the shell process\n file: { reads: positionals },\n find: { reads: findReads, unsafe: findUnsafe },\n grep: { reads: positionalsAfterFirst },\n head: { reads: positionals },\n hexdump: { reads: positionals },\n less: { reads: positionals },\n ls: { reads: positionals },\n more: { reads: positionals },\n od: { reads: positionals },\n printf: {},\n pwd: {},\n readlink: { reads: positionals },\n realpath: { reads: positionals },\n rg: { reads: positionalsAfterFirst },\n sed: { reads: sedReads, unsafe: sedUnsafe, writes: sedWrites },\n sort: { reads: positionals },\n source: { unsafe: dynamicExec }, // bash: runs the file in current shell\n stat: { reads: positionals },\n tail: { reads: positionals },\n tree: { reads: positionals },\n uniq: { reads: positionals },\n wc: { reads: positionals },\n xxd: { reads: positionals },\n}\n"],"x_google_ignoreList":[0,1,2],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import { sliceAnsi, splitAnsi, stringWidth } from "@zaly/shared/ansi";
2
+ //#region src/utils/truncate.ts
3
+ const MAX_CHARS = 5e4;
4
+ const MAX_LINES = 2e3;
5
+ const MAX_LINE_CHARS = 1e3;
6
+ const isJsonish = (lines) => lines.length === 1 && /^[\s]*[[{]/.test(lines[0]);
7
+ function truncateOpts(opts = {}) {
8
+ const maxLines = Math.max(1, opts.maxLines ?? MAX_LINES);
9
+ let head = opts.head ?? Math.floor(maxLines / 2);
10
+ if (head < 1) head = Math.floor(maxLines * head);
11
+ head = Math.max(0, Math.min(head, maxLines));
12
+ return {
13
+ ...opts,
14
+ head,
15
+ maxChars: Math.max(1, opts.maxChars ?? MAX_CHARS),
16
+ maxLineChars: Math.max(1, opts.maxLineChars ?? MAX_LINE_CHARS),
17
+ maxLines,
18
+ strategy: opts.strategy ?? "head+tail"
19
+ };
20
+ }
21
+ function truncate(data, opts = {}) {
22
+ const text = typeof data === "string" ? data : data.toString("utf8");
23
+ const o = truncateOpts(opts);
24
+ let lines = splitAnsi(text);
25
+ while (lines.at(-1) === "") lines.pop();
26
+ const ret = {
27
+ opts: o,
28
+ origBytes: typeof data === "string" ? Buffer.byteLength(data, "utf8") : data.length,
29
+ origChars: lines.reduce((sum, l) => sum + stringWidth(l) + 1, 0) - 1,
30
+ origLines: lines.length,
31
+ origLongestLine: lines.reduce((max, l) => Math.max(max, stringWidth(l)), 0),
32
+ text,
33
+ truncated: false
34
+ };
35
+ if (lines.length === 0) return ret;
36
+ const jsonish = isJsonish(lines);
37
+ const truncLineChars = !jsonish && ret.origLongestLine > o.maxLineChars;
38
+ if (lines.length <= o.maxLines && ret.origChars <= o.maxChars && !truncLineChars) return ret;
39
+ ret.truncated = true;
40
+ if (jsonish && ret.origChars > o.maxChars) {
41
+ const marker = ` … [truncated ${ret.origChars - o.maxChars} chars]`;
42
+ const len = Math.max(0, o.maxChars - marker.length);
43
+ ret.text = sliceAnsi(lines[0], 0, len);
44
+ ret.text += marker;
45
+ ret.truncatedChars = true;
46
+ return ret;
47
+ }
48
+ if (truncLineChars) lines = lines.map((l) => {
49
+ const len = stringWidth(l);
50
+ if (len <= o.maxLineChars) return l;
51
+ ret.truncatedLineChars = true;
52
+ const trunc = len - o.maxLineChars;
53
+ return `${sliceAnsi(l, 0, o.maxLineChars)} [ … truncated ${trunc} chars]`;
54
+ });
55
+ const truncateLines = (ll, maxLines, maxChars, reverse = false) => {
56
+ ll = reverse ? ll.toReversed() : ll;
57
+ if (ll.length > maxLines) {
58
+ ret.truncatedLines = true;
59
+ ll = ll.slice(0, maxLines);
60
+ }
61
+ let c = 0;
62
+ ll = ll.filter((l) => {
63
+ c += stringWidth(l) + 1;
64
+ if (c <= maxChars) return true;
65
+ ret.truncatedChars = true;
66
+ ret.truncatedLines = true;
67
+ return false;
68
+ });
69
+ return reverse ? ll.toReversed() : ll;
70
+ };
71
+ let strategy = o.strategy;
72
+ if (strategy === "head+tail") {
73
+ if (o.head === 0) strategy = "tail";
74
+ else if (o.head >= o.maxLines) strategy = "head";
75
+ }
76
+ if (strategy === "head") {
77
+ lines = truncateLines(lines, o.maxLines, o.maxChars);
78
+ const truncated = ret.origLines - lines.length;
79
+ if (truncated) lines.push(` [ … truncated ${truncated} lines]`);
80
+ } else if (strategy === "tail") {
81
+ lines = truncateLines(lines, o.maxLines, o.maxChars, true);
82
+ const truncated = ret.origLines - lines.length;
83
+ if (truncated) lines.unshift(` [ … truncated ${truncated} lines]`);
84
+ } else {
85
+ const headMaxChars = Math.floor(o.head / o.maxLines * o.maxChars);
86
+ const tailMaxChars = o.maxChars - headMaxChars;
87
+ const head = truncateLines(lines.splice(0, o.head), o.head, headMaxChars);
88
+ const tail = truncateLines(lines, o.maxLines - o.head, tailMaxChars, true);
89
+ const truncated = ret.origLines - head.length - tail.length;
90
+ lines = truncated ? [
91
+ ...head,
92
+ ` [ … truncated ${truncated} lines]`,
93
+ ...tail
94
+ ] : [...head, ...tail];
95
+ }
96
+ ret.text = lines.join("\n");
97
+ return ret;
98
+ }
99
+ //#endregion
100
+ export { truncate as t };
101
+
102
+ //# sourceMappingURL=truncate-BSMXgiXj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truncate-BSMXgiXj.mjs","names":["sw"],"sources":["../src/utils/truncate.ts"],"sourcesContent":["import { sliceAnsi, splitAnsi, stringWidth as sw } from \"@zaly/shared/ansi\"\n\nconst MAX_CHARS = 50_000\nconst MAX_LINES = 2000\nconst MAX_LINE_CHARS = 1000\n\nexport type TruncateOps = {\n strategy?: \"head\" | \"tail\" | \"head+tail\"\n /** Max chars to keep (default: 50k). */\n maxChars?: number\n /** Max lines to keep (default: 2000). */\n maxLines?: number\n /** Max chars per line (default: 1000). */\n maxLineChars?: number\n /** When `strategy` is \"head+tail\", how many lines to keep in the head.\n * When < 1, treated as a fraction of `maxLines`. Default: maxLines/2. */\n head?: number\n}\n\nexport type TruncateResult = {\n text: string\n truncated: boolean\n truncatedLines?: boolean\n truncatedChars?: boolean\n truncatedLineChars?: boolean\n opts: Required<TruncateOps>\n origLines: number\n origChars: number\n origLongestLine: number\n origBytes: number\n}\n\nconst isJsonish = (lines: string[]) => lines.length === 1 && /^[\\s]*[[{]/.test(lines[0])\n\nfunction truncateOpts(opts: TruncateOps = {}): Required<TruncateOps> {\n const maxLines = Math.max(1, opts.maxLines ?? MAX_LINES)\n let head = opts.head ?? Math.floor(maxLines / 2)\n if (head < 1) head = Math.floor(maxLines * head)\n head = Math.max(0, Math.min(head, maxLines))\n return {\n ...opts,\n head,\n maxChars: Math.max(1, opts.maxChars ?? MAX_CHARS),\n maxLineChars: Math.max(1, opts.maxLineChars ?? MAX_LINE_CHARS),\n maxLines,\n strategy: opts.strategy ?? \"head+tail\",\n }\n}\n\nexport function truncate(data: string | Buffer, opts: TruncateOps = {}) {\n const text = typeof data === \"string\" ? data : data.toString(\"utf8\")\n const o = truncateOpts(opts)\n\n let lines = splitAnsi(text)\n while (lines.at(-1) === \"\") lines.pop()\n const bytes = typeof data === \"string\" ? Buffer.byteLength(data, \"utf8\") : data.length\n\n const ret: TruncateResult = {\n opts: o,\n origBytes: bytes,\n origChars: lines.reduce((sum, l) => sum + sw(l) + 1, 0) - 1,\n origLines: lines.length,\n origLongestLine: lines.reduce((max, l) => Math.max(max, sw(l)), 0),\n text,\n truncated: false,\n }\n\n if (lines.length === 0) return ret\n\n const jsonish = isJsonish(lines)\n const truncLineChars = !jsonish && ret.origLongestLine > o.maxLineChars\n\n if (lines.length <= o.maxLines && ret.origChars <= o.maxChars && !truncLineChars) return ret\n\n ret.truncated = true\n\n if (jsonish && ret.origChars > o.maxChars) {\n const marker = ` … [truncated ${ret.origChars - o.maxChars} chars]`\n const len = Math.max(0, o.maxChars - marker.length)\n ret.text = sliceAnsi(lines[0], 0, len)\n ret.text += marker\n ret.truncatedChars = true\n return ret\n }\n\n if (truncLineChars) {\n lines = lines.map((l) => {\n const len = sw(l)\n if (len <= o.maxLineChars) return l\n ret.truncatedLineChars = true\n const trunc = len - o.maxLineChars\n return `${sliceAnsi(l, 0, o.maxLineChars)} [ … truncated ${trunc} chars]`\n })\n }\n\n const truncateLines = (ll: string[], maxLines: number, maxChars: number, reverse = false) => {\n ll = reverse ? ll.toReversed() : ll\n if (ll.length > maxLines) {\n ret.truncatedLines = true\n ll = ll.slice(0, maxLines)\n }\n let c = 0\n ll = ll.filter((l) => {\n c += sw(l) + 1 // +1 for the dropped \\n\n if (c <= maxChars) return true\n ret.truncatedChars = true\n ret.truncatedLines = true\n return false\n })\n return reverse ? ll.toReversed() : ll\n }\n\n let strategy: \"head\" | \"tail\" | \"head+tail\" = o.strategy\n if (strategy === \"head+tail\") {\n if (o.head === 0) strategy = \"tail\"\n else if (o.head >= o.maxLines) strategy = \"head\"\n }\n\n if (strategy === \"head\") {\n lines = truncateLines(lines, o.maxLines, o.maxChars)\n const truncated = ret.origLines - lines.length\n if (truncated) lines.push(` [ … truncated ${truncated} lines]`)\n } else if (strategy === \"tail\") {\n lines = truncateLines(lines, o.maxLines, o.maxChars, true)\n const truncated = ret.origLines - lines.length\n if (truncated) lines.unshift(` [ … truncated ${truncated} lines]`)\n } else {\n const headMaxChars = Math.floor((o.head / o.maxLines) * o.maxChars)\n const tailMaxChars = o.maxChars - headMaxChars\n const headLines = lines.splice(0, o.head)\n const head = truncateLines(headLines, o.head, headMaxChars)\n const tail = truncateLines(lines, o.maxLines - o.head, tailMaxChars, true)\n const truncated = ret.origLines - head.length - tail.length\n lines = truncated\n ? [...head, ` [ … truncated ${truncated} lines]`, ...tail]\n : [...head, ...tail]\n }\n\n ret.text = lines.join(\"\\n\")\n return ret\n}\n"],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ //#region src/utils/uuid.ts
2
+ /**
3
+ * RFC 9562 UUIDv7 — timestamp-prefixed (48-bit ms since epoch),
4
+ * monotonic within a millisecond via a 32-bit sequence counter,
5
+ * random tail otherwise. Sortable lexicographically by creation time,
6
+ * which is handy for debugging ("show me the latest few records by
7
+ * uuid") and for any future DB index.
8
+ *
9
+ * Hand-rolled to keep `@zaly/agent` dep-free. Same algorithm as the
10
+ * `uuid` package's v7 implementation, just without the buffer/offset
11
+ * ceremony — we only ever want the canonical string form.
12
+ */
13
+ let lastMs = 0;
14
+ let seq = 0;
15
+ function uuidv7() {
16
+ let ms = Date.now();
17
+ if (ms > lastMs) {
18
+ lastMs = ms;
19
+ seq = Math.floor(Math.random() * 4294967296);
20
+ } else {
21
+ seq = seq + 1 >>> 0;
22
+ if (seq === 0) lastMs++;
23
+ ms = lastMs;
24
+ }
25
+ const r = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(6));
26
+ const b = /* @__PURE__ */ new Uint8Array(16);
27
+ b[0] = ms / 1099511627776 & 255;
28
+ b[1] = ms / 4294967296 & 255;
29
+ b[2] = ms / 16777216 & 255;
30
+ b[3] = ms / 65536 & 255;
31
+ b[4] = ms / 256 & 255;
32
+ b[5] = ms & 255;
33
+ b[6] = 112 | seq >>> 28 & 15;
34
+ b[7] = seq >>> 20 & 255;
35
+ b[8] = 128 | seq >>> 14 & 63;
36
+ b[9] = seq >>> 6 & 255;
37
+ b[10] = seq << 2 & 255 | r[0] & 3;
38
+ b[11] = r[1];
39
+ b[12] = r[2];
40
+ b[13] = r[3];
41
+ b[14] = r[4];
42
+ b[15] = r[5];
43
+ const hex = [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
44
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
45
+ }
46
+ function isUuidv7(s) {
47
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s);
48
+ }
49
+ function isUuidv7Like(s) {
50
+ if (s.length < 7) return false;
51
+ s = s.replace(/-/g, "").slice(0, 12).padEnd(12, "0").toLowerCase();
52
+ if (!/^[0-9a-f]+$/.test(s)) return false;
53
+ const ts = parseInt(s, 16);
54
+ return ts > Date.parse("2026-01-01T00:00:00Z") && ts < Date.now() + 3600 * 1e3;
55
+ }
56
+ //#endregion
57
+ export { isUuidv7Like as n, uuidv7 as r, isUuidv7 as t };
58
+
59
+ //# sourceMappingURL=uuid-ByXP5SO5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid-ByXP5SO5.mjs","names":[],"sources":["../src/utils/uuid.ts"],"sourcesContent":["/**\n * RFC 9562 UUIDv7 — timestamp-prefixed (48-bit ms since epoch),\n * monotonic within a millisecond via a 32-bit sequence counter,\n * random tail otherwise. Sortable lexicographically by creation time,\n * which is handy for debugging (\"show me the latest few records by\n * uuid\") and for any future DB index.\n *\n * Hand-rolled to keep `@zaly/agent` dep-free. Same algorithm as the\n * `uuid` package's v7 implementation, just without the buffer/offset\n * ceremony — we only ever want the canonical string form.\n */\n\nlet lastMs = 0\nlet seq = 0\n\nexport function uuidv7(): string {\n let ms = Date.now()\n if (ms > lastMs) {\n // Time moved on — fresh sequence base.\n lastMs = ms\n seq = Math.floor(Math.random() * 0x1_00_00_00_00)\n } else {\n // Same (or earlier) ms — bump sequence with 32-bit rollover. On\n // rollover, advance the timestamp by one ms to preserve\n // monotonicity (allowed by RFC 9562 §6.2; self-corrects as the\n // wall clock catches up).\n seq = (seq + 1) >>> 0\n if (seq === 0) lastMs++\n ms = lastMs\n }\n\n const r = crypto.getRandomValues(new Uint8Array(6))\n const b = new Uint8Array(16)\n\n // bytes 0-5: 48-bit big-endian millisecond timestamp\n b[0] = (ms / 0x1_00_00_00_00_00) & 0xff\n b[1] = (ms / 0x1_00_00_00_00) & 0xff\n b[2] = (ms / 0x1_00_00_00) & 0xff\n b[3] = (ms / 0x1_00_00) & 0xff\n b[4] = (ms / 0x1_00) & 0xff\n b[5] = ms & 0xff\n // byte 6: version (0x70) + top 4 bits of sequence\n b[6] = 0x70 | ((seq >>> 28) & 0x0f)\n // byte 7: next 8 bits of sequence\n b[7] = (seq >>> 20) & 0xff\n // byte 8: variant (0x80) + next 6 bits of sequence\n b[8] = 0x80 | ((seq >>> 14) & 0x3f)\n // byte 9: next 8 bits of sequence\n b[9] = (seq >>> 6) & 0xff\n // byte 10: low 6 bits of sequence + 2 random bits\n b[10] = ((seq << 2) & 0xff) | (r[0] & 0x03)\n // bytes 11-15: 40 random bits\n b[11] = r[1]\n b[12] = r[2]\n b[13] = r[3]\n b[14] = r[4]\n b[15] = r[5]\n\n const hex = [...b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\")\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`\n}\n\nexport function isUuidv7(s: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s)\n}\n\nexport function isUuidv7Like(s: string): boolean {\n if (s.length < 7) return false\n // First 12 chars of a v7 UUID is a timestamp\n s = s.replace(/-/g, \"\").slice(0, 12).padEnd(12, \"0\").toLowerCase()\n if (!/^[0-9a-f]+$/.test(s)) return false\n const ts = parseInt(s, 16)\n // Reject timestamps before 2026, and more than an hour in the future to allow for clock drift\n return ts > Date.parse(\"2026-01-01T00:00:00Z\") && ts < Date.now() + 3600 * 1000\n}\n"],"mappings":";;;;;;;;;;;;AAYA,IAAI,SAAS;AACb,IAAI,MAAM;AAEV,SAAgB,SAAiB;CAC/B,IAAI,KAAK,KAAK,IAAI;CAClB,IAAI,KAAK,QAAQ;EAEf,SAAS;EACT,MAAM,KAAK,MAAM,KAAK,OAAO,IAAI,UAAe;CAClD,OAAO;EAKL,MAAO,MAAM,MAAO;EACpB,IAAI,QAAQ,GAAG;EACf,KAAK;CACP;CAEA,MAAM,IAAI,OAAO,gCAAgB,IAAI,WAAW,CAAC,CAAC;CAClD,MAAM,oBAAI,IAAI,WAAW,EAAE;CAG3B,EAAE,KAAM,KAAK,gBAAsB;CACnC,EAAE,KAAM,KAAK,aAAmB;CAChC,EAAE,KAAM,KAAK,WAAgB;CAC7B,EAAE,KAAM,KAAK,QAAa;CAC1B,EAAE,KAAM,KAAK,MAAU;CACvB,EAAE,KAAK,KAAK;CAEZ,EAAE,KAAK,MAAS,QAAQ,KAAM;CAE9B,EAAE,KAAM,QAAQ,KAAM;CAEtB,EAAE,KAAK,MAAS,QAAQ,KAAM;CAE9B,EAAE,KAAM,QAAQ,IAAK;CAErB,EAAE,MAAQ,OAAO,IAAK,MAAS,EAAE,KAAK;CAEtC,EAAE,MAAM,EAAE;CACV,EAAE,MAAM,EAAE;CACV,EAAE,MAAM,EAAE;CACV,EAAE,MAAM,EAAE;CACV,EAAE,MAAM,EAAE;CAEV,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;CACtE,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE;AAC7G;AAEA,SAAgB,SAAS,GAAoB;CAC3C,OAAO,yEAAyE,KAAK,CAAC;AACxF;AAEA,SAAgB,aAAa,GAAoB;CAC/C,IAAI,EAAE,SAAS,GAAG,OAAO;CAEzB,IAAI,EAAE,QAAQ,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC,YAAY;CACjE,IAAI,CAAC,cAAc,KAAK,CAAC,GAAG,OAAO;CACnC,MAAM,KAAK,SAAS,GAAG,EAAE;CAEzB,OAAO,KAAK,KAAK,MAAM,sBAAsB,KAAK,KAAK,KAAK,IAAI,IAAI,OAAO;AAC7E"}
@@ -0,0 +1,29 @@
1
+ import { AiError, defineTool } from "@zaly/ai";
2
+ import { Type } from "typebox";
3
+ //#region src/tools/wakeup.ts
4
+ const wakeupTool = defineTool({
5
+ name: "wakeup",
6
+ desc: "Schedule a one-shot wake-up of the agent loop. If `delayMs` elapses without anything else waking the loop (task completion, heartbeat, user message), you'll receive a system message with the optional `hint`. If something else wakes the loop first, the wakeup is cancelled — its hint still surfaces (with `status=\"cancelled\"`) so your reminder doesn't evaporate. Useful for polling external state or scheduling a return to a thought.",
7
+ parallel: true,
8
+ params: Type.Object({
9
+ delayMs: Type.Integer({
10
+ description: "How long to wait before waking up, in milliseconds. Minimum 1.",
11
+ minimum: 1
12
+ }),
13
+ hint: Type.Optional(Type.String({ description: "Short text the wakeup carries — what you wanted to remember to do at this point. Surfaces in the system message whether the wakeup fires on its own timer or gets cancelled by another event. Keep it concise; treat it as a TODO line, not prose." }))
14
+ }),
15
+ call(args, ctx) {
16
+ if (!ctx.agent) throw new AiError({
17
+ code: "MISSING_TOOL_CONTEXT",
18
+ message: "wakeup requires an Agent reference on the context (set up by the agent harness)."
19
+ });
20
+ return `wakeup ${ctx.agent.scheduleWakeup({
21
+ delayMs: args.delayMs,
22
+ hint: args.hint
23
+ })} scheduled for ${new Date(Date.now() + args.delayMs).toISOString()}`;
24
+ }
25
+ });
26
+ //#endregion
27
+ export { wakeupTool };
28
+
29
+ //# sourceMappingURL=wakeup-BYqTnfSp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wakeup-BYqTnfSp.mjs","names":[],"sources":["../src/tools/wakeup.ts"],"sourcesContent":["import type { Agent } from \"../agent.ts\"\n\nimport { defineTool, AiError } from \"@zaly/ai\"\nimport { Type } from \"typebox\"\n\n/**\n * Schedule a future wake-up of the agent loop.\n *\n * Behavior:\n * - If `delayMs` elapses without anything else waking the loop, the\n * agent receives a system message containing the wakeup id and the\n * optional `hint`. The loop ticks; the model decides what to do next.\n * - If task completion / heartbeat / user message wakes the loop first,\n * the wakeup is auto-cancelled. Any cancelled wakeups carry their\n * `hint`s forward as a system message tagged `status=\"cancelled\"` so\n * the model still sees what it scheduled — the timing context is just\n * explicit (\"I scheduled this; it didn't fire on its own\").\n *\n * Use cases:\n * - Watch an external (non-task) process: \"wake me in 60s to re-fetch\n * the build status URL.\"\n * - Poll-style fallback for long-running tasks the model cares about\n * finishing: \"wake me in 30s in case the build hasn't completed yet.\"\n * - \"Take a moment\" patterns: \"wake me in 5min to reflect on whether\n * this approach is right.\"\n *\n * No companion `wakeup_cancel` tool exists — by the time the model has\n * a turn to call cancel, the wakeup has either fired or been auto-\n * cancelled for it. The only useful state transition is \"fire,\" and that\n * the harness handles automatically.\n */\nexport interface WakeupContext {\n /** The agent that scheduled the wakeup — provided via ToolContext\n * augmentation so the tool can call `scheduleWakeup` without a\n * cross-package dep on `Agent`. */\n agent?: Agent\n}\n\n// oxlint-disable-next-line sort-keys -- semantic field order: name, desc, params, call\nexport const wakeupTool = defineTool({\n name: \"wakeup\",\n desc:\n \"Schedule a one-shot wake-up of the agent loop. If `delayMs` elapses \" +\n \"without anything else waking the loop (task completion, heartbeat, \" +\n \"user message), you'll receive a system message with the optional \" +\n \"`hint`. If something else wakes the loop first, the wakeup is \" +\n 'cancelled — its hint still surfaces (with `status=\"cancelled\"`) so ' +\n \"your reminder doesn't evaporate. Useful for polling external state \" +\n \"or scheduling a return to a thought.\",\n parallel: true,\n params: Type.Object({\n delayMs: Type.Integer({\n description: \"How long to wait before waking up, in milliseconds. Minimum 1.\",\n minimum: 1,\n }),\n hint: Type.Optional(\n Type.String({\n description:\n \"Short text the wakeup carries — what you wanted to remember \" +\n \"to do at this point. Surfaces in the system message whether \" +\n \"the wakeup fires on its own timer or gets cancelled by another \" +\n \"event. Keep it concise; treat it as a TODO line, not prose.\",\n })\n ),\n }),\n\n call(args, ctx) {\n if (!ctx.agent) {\n throw new AiError({\n code: \"MISSING_TOOL_CONTEXT\",\n message: \"wakeup requires an Agent reference on the context (set up by the agent harness).\",\n })\n }\n const id = ctx.agent.scheduleWakeup({ delayMs: args.delayMs, hint: args.hint })\n const when = new Date(Date.now() + args.delayMs).toISOString()\n return `wakeup ${id} scheduled for ${when}`\n },\n})\n"],"mappings":";;;AAuCA,MAAa,aAAa,WAAW;CACnC,MAAM;CACN,MACE;CAOF,UAAU;CACV,QAAQ,KAAK,OAAO;EAClB,SAAS,KAAK,QAAQ;GACpB,aAAa;GACb,SAAS;EACX,CAAC;EACD,MAAM,KAAK,SACT,KAAK,OAAO,EACV,aACE,qPAIJ,CAAC,CACH;CACF,CAAC;CAED,KAAK,MAAM,KAAK;EACd,IAAI,CAAC,IAAI,OACP,MAAM,IAAI,QAAQ;GAChB,MAAM;GACN,SAAS;EACX,CAAC;EAIH,OAAO,UAFI,IAAI,MAAM,eAAe;GAAE,SAAS,KAAK;GAAS,MAAM,KAAK;EAAK,CAE3D,EAAE,iBADP,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC,YACT;CAC1C;AACF,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { assertFresh } from "./read-Djsx2NZl.mjs";
2
+ import { normPath, safeReadFile, safeStat } from "@zaly/shared";
3
+ import { dirname } from "pathe";
4
+ import { AiError, defineTool } from "@zaly/ai";
5
+ import { mkdir, stat, writeFile } from "node:fs/promises";
6
+ import { Type } from "typebox";
7
+ import { detectEol, normalizeEol } from "@zaly/shared/text";
8
+ //#region src/tools/write.ts
9
+ /**
10
+ * Write a file to disk. Overwrites if the file exists; creates parent
11
+ * directories as needed.
12
+ *
13
+ * Content is written *verbatim* in everything but line endings. The
14
+ * model always emits LF; this tool detects the existing file's
15
+ * line-ending style (or the platform default for new files via
16
+ * `detectEol`) and re-applies it on write so a CRLF file stays CRLF
17
+ * after an LF-from-the-model overwrite. No trailing-newline injection,
18
+ * no whitespace stripping.
19
+ *
20
+ * Freshness: when the target file already exists, the model must have
21
+ * read it in this session and the on-disk mtime must be unchanged
22
+ * since that read. Otherwise the tool refuses with `FILE_NOT_FRESH` —
23
+ * "read this file before overwriting it." New files (path doesn't
24
+ * exist) bypass this check; nothing to be fresh against.
25
+ *
26
+ * Returns a small acknowledgement (`{ ok, bytes, lines }`) rather than
27
+ * echoing the content back — the model already has it. `bytes` reflects
28
+ * the on-disk byte count, including any extra bytes from CRLF expansion.
29
+ */
30
+ const writeTool = defineTool({
31
+ name: "write",
32
+ desc: "Write content to a file. Overwrites existing files (must be read first); creates parent directories as needed. Returns the byte count and line count of the written file.",
33
+ params: Type.Object({
34
+ path: Type.String({ description: "Path to the file. Absolute or cwd-relative." }),
35
+ content: Type.String({ description: "File contents to write, verbatim." })
36
+ }),
37
+ async preflight(args, ctx) {
38
+ const path = normPath(ctx.cwd, args.path);
39
+ await ctx.need?.("write", path);
40
+ },
41
+ async call(args, ctx) {
42
+ const path = normPath(ctx.cwd, args.path);
43
+ if (safeStat(path)?.isFile()) assertFresh(path, ctx);
44
+ let text;
45
+ let original;
46
+ try {
47
+ original = await safeReadFile(path);
48
+ const eol = detectEol({
49
+ path,
50
+ text: original
51
+ });
52
+ text = normalizeEol(args.content, { eol });
53
+ await mkdir(dirname(path), { recursive: true });
54
+ await writeFile(path, text, "utf8");
55
+ } catch (error) {
56
+ throw new AiError({
57
+ cause: error,
58
+ code: "WRITE_FAILED",
59
+ message: `failed to write ${path}: ${error instanceof Error ? error.message : String(error)}`
60
+ });
61
+ }
62
+ ctx.meta = {
63
+ full: true,
64
+ kind: "write",
65
+ mtime: (await stat(path)).mtimeMs,
66
+ original,
67
+ path
68
+ };
69
+ return {
70
+ bytes: Buffer.byteLength(text, "utf8"),
71
+ lines: countLines(text),
72
+ ok: true,
73
+ path
74
+ };
75
+ }
76
+ });
77
+ /** Count lines the way an editor would — non-empty trailing line counts;
78
+ * trailing newline doesn't add a phantom blank line. */
79
+ function countLines(content) {
80
+ if (content === "") return 0;
81
+ let n = 1;
82
+ for (const c of content) if (c === "\n") n++;
83
+ if (content.endsWith("\n")) n--;
84
+ return n;
85
+ }
86
+ //#endregion
87
+ export { writeTool };
88
+
89
+ //# sourceMappingURL=write-D3L-92vj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-D3L-92vj.mjs","names":[],"sources":["../src/tools/write.ts"],"sourcesContent":["import type { ToolContext } from \"@zaly/ai\"\nimport type { FileMeta } from \"./read.ts\"\n\nimport { AiError, defineTool } from \"@zaly/ai\"\nimport { normPath, safeReadFile, safeStat } from \"@zaly/shared\"\nimport { detectEol, normalizeEol } from \"@zaly/shared/text\"\nimport { mkdir, stat, writeFile } from \"node:fs/promises\"\nimport { dirname } from \"pathe\"\nimport { Type } from \"typebox\"\nimport { assertFresh } from \"./read.ts\"\n\nexport type WriteTool = typeof writeTool\n\nexport type WriteToolMeta = FileMeta & {\n /** File content before the write. Undefined for new files. The\n * post-write content is recoverable from the original call's\n * `params.content`, so we don't redundantly stash it on meta. */\n original?: string\n}\n\n/**\n * Write a file to disk. Overwrites if the file exists; creates parent\n * directories as needed.\n *\n * Content is written *verbatim* in everything but line endings. The\n * model always emits LF; this tool detects the existing file's\n * line-ending style (or the platform default for new files via\n * `detectEol`) and re-applies it on write so a CRLF file stays CRLF\n * after an LF-from-the-model overwrite. No trailing-newline injection,\n * no whitespace stripping.\n *\n * Freshness: when the target file already exists, the model must have\n * read it in this session and the on-disk mtime must be unchanged\n * since that read. Otherwise the tool refuses with `FILE_NOT_FRESH` —\n * \"read this file before overwriting it.\" New files (path doesn't\n * exist) bypass this check; nothing to be fresh against.\n *\n * Returns a small acknowledgement (`{ ok, bytes, lines }`) rather than\n * echoing the content back — the model already has it. `bytes` reflects\n * the on-disk byte count, including any extra bytes from CRLF expansion.\n */\n// oxlint-disable-next-line sort-keys -- semantic field order: name, desc, params, call\nexport const writeTool = defineTool({\n name: \"write\",\n desc:\n \"Write content to a file. Overwrites existing files (must be read first); \" +\n \"creates parent directories as needed. Returns the byte count and line \" +\n \"count of the written file.\",\n // oxlint-disable-next-line sort-keys -- semantic param order\n params: Type.Object({\n path: Type.String({ description: \"Path to the file. Absolute or cwd-relative.\" }),\n content: Type.String({ description: \"File contents to write, verbatim.\" }),\n }),\n\n async preflight(args, ctx: ToolContext<WriteToolMeta>) {\n const path = normPath(ctx.cwd, args.path)\n await ctx.need?.(\"write\", path)\n },\n\n async call(\n args,\n ctx: ToolContext<WriteToolMeta>\n ): Promise<{ ok: true; path: string; bytes: number; lines: number }> {\n const path = normPath(ctx.cwd, args.path)\n\n // Existing file → freshness required. New file → no requirement.\n if (safeStat(path)?.isFile()) assertFresh(path, ctx)\n\n let text: string\n let original: string | undefined\n try {\n original = await safeReadFile(path)\n const eol = detectEol({ path, text: original })\n text = normalizeEol(args.content, { eol })\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, text, \"utf8\")\n } catch (error) {\n throw new AiError({\n cause: error,\n code: \"WRITE_FAILED\",\n message: `failed to write ${path}: ${error instanceof Error ? error.message : String(error)}`,\n })\n }\n\n // Record post-write so the model can re-edit without an immediate\n // re-read. Captures the new mtime.\n const fstat = await stat(path)\n ctx.meta = { full: true, kind: \"write\", mtime: fstat.mtimeMs, original, path }\n\n const bytes = Buffer.byteLength(text, \"utf8\")\n const lines = countLines(text)\n return { bytes, lines, ok: true, path }\n },\n})\n\n/** Count lines the way an editor would — non-empty trailing line counts;\n * trailing newline doesn't add a phantom blank line. */\nfunction countLines(content: string): number {\n if (content === \"\") return 0\n let n = 1\n for (const c of content) if (c === \"\\n\") n++\n // A trailing newline means the count is one too high (no real line follows).\n if (content.endsWith(\"\\n\")) n--\n return n\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,YAAY,WAAW;CAClC,MAAM;CACN,MACE;CAIF,QAAQ,KAAK,OAAO;EAClB,MAAM,KAAK,OAAO,EAAE,aAAa,8CAA8C,CAAC;EAChF,SAAS,KAAK,OAAO,EAAE,aAAa,oCAAoC,CAAC;CAC3E,CAAC;CAED,MAAM,UAAU,MAAM,KAAiC;EACrD,MAAM,OAAO,SAAS,IAAI,KAAK,KAAK,IAAI;EACxC,MAAM,IAAI,OAAO,SAAS,IAAI;CAChC;CAEA,MAAM,KACJ,MACA,KACmE;EACnE,MAAM,OAAO,SAAS,IAAI,KAAK,KAAK,IAAI;EAGxC,IAAI,SAAS,IAAI,CAAC,EAAE,OAAO,GAAG,YAAY,MAAM,GAAG;EAEnD,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,aAAa,IAAI;GAClC,MAAM,MAAM,UAAU;IAAE;IAAM,MAAM;GAAS,CAAC;GAC9C,OAAO,aAAa,KAAK,SAAS,EAAE,IAAI,CAAC;GACzC,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;GAC9C,MAAM,UAAU,MAAM,MAAM,MAAM;EACpC,SAAS,OAAO;GACd,MAAM,IAAI,QAAQ;IAChB,OAAO;IACP,MAAM;IACN,SAAS,mBAAmB,KAAK,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC5F,CAAC;EACH;EAKA,IAAI,OAAO;GAAE,MAAM;GAAM,MAAM;GAAS,QAAO,MAD3B,KAAK,IAAI,EAAA,CACwB;GAAS;GAAU;EAAK;EAI7E,OAAO;GAAE,OAFK,OAAO,WAAW,MAAM,MAEzB;GAAG,OADF,WAAW,IACL;GAAG,IAAI;GAAM;EAAK;CACxC;AACF,CAAC;;;AAID,SAAS,WAAW,SAAyB;CAC3C,IAAI,YAAY,IAAI,OAAO;CAC3B,IAAI,IAAI;CACR,KAAK,MAAM,KAAK,SAAS,IAAI,MAAM,MAAM;CAEzC,IAAI,QAAQ,SAAS,IAAI,GAAG;CAC5B,OAAO;AACT"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@zaly/agent",
3
+ "version": "0.0.3",
4
+ "description": "Agent runtime, sessions, tools, permissions, compaction, and tasks for zaly.",
5
+ "keywords": [
6
+ "agent",
7
+ "ai",
8
+ "runtime",
9
+ "sessions",
10
+ "tools"
11
+ ],
12
+ "homepage": "https://github.com/folke/zaly/tree/main/packages/agent#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/folke/zaly/issues"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/folke/zaly.git",
20
+ "directory": "packages/agent"
21
+ },
22
+ "files": [
23
+ "dist/**/*"
24
+ ],
25
+ "type": "module",
26
+ "exports": {
27
+ ".": "./dist/index.mjs",
28
+ "./session": "./dist/session.mjs",
29
+ "./session/claude": "./dist/session/claude.mjs",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "pathe": "^2.0.3",
37
+ "@zaly/ai": "0.0.3",
38
+ "@zaly/shared": "0.0.3"
39
+ },
40
+ "devDependencies": {
41
+ "@types/shell-quote": "^1.7.5",
42
+ "ignore": "^7.0.5",
43
+ "jsonpath-plus": "^10.4.0",
44
+ "shell-quote": "^1.9.0"
45
+ },
46
+ "peerDependencies": {
47
+ "typebox": "^1.3.6"
48
+ },
49
+ "engines": {
50
+ "bun": ">=1.2",
51
+ "node": ">=24"
52
+ },
53
+ "inlinedDependencies": {
54
+ "ignore": "7.0.5",
55
+ "jsonpath-plus": "10.4.0",
56
+ "shell-quote": "1.9.0"
57
+ },
58
+ "scripts": {
59
+ "build": "z build",
60
+ "lint": "z lint",
61
+ "test": "z test"
62
+ }
63
+ }