@zaly/agent 0.0.12 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-Dp4LZDSo.mjs → agent-BRENvuvx.mjs} +8 -11
- package/dist/{agent-Dp4LZDSo.mjs.map → agent-BRENvuvx.mjs.map} +1 -1
- package/dist/{agent-B7cQOuMZ.mjs → agent-sHtwQDAk.mjs} +2 -3
- package/dist/{agent-B7cQOuMZ.mjs.map → agent-sHtwQDAk.mjs.map} +1 -1
- package/dist/{bash-COgq4W7U.mjs → bash-DSy03sAj.mjs} +8 -9
- package/dist/bash-DSy03sAj.mjs.map +1 -0
- package/dist/compactions-HFjEgrv3.mjs +150 -0
- package/dist/compactions-HFjEgrv3.mjs.map +1 -0
- package/dist/{edit-D4yaBK1K.mjs → edit-DT672zIy.mjs} +2 -3
- package/dist/edit-DT672zIy.mjs.map +1 -0
- package/dist/{env-UUFv5Mye.mjs → env-C8p8i1E0.mjs} +2 -3
- package/dist/{env-UUFv5Mye.mjs.map → env-C8p8i1E0.mjs.map} +1 -1
- package/dist/fetch-BrtIqrid.mjs +158 -0
- package/dist/fetch-BrtIqrid.mjs.map +1 -0
- package/dist/{find-CimUGn-L.mjs → find-B-vvdlHu.mjs} +30 -13
- package/dist/find-B-vvdlHu.mjs.map +1 -0
- package/dist/{grep-6O3mjSvV.mjs → grep-rvZ5S319.mjs} +19 -10
- package/dist/grep-rvZ5S319.mjs.map +1 -0
- package/dist/html-VrAsoGBK.mjs +32950 -0
- package/dist/html-VrAsoGBK.mjs.map +1 -0
- package/dist/{index-node-esm-CJsN8G51.mjs → index-node-esm-BwaOzhXB.mjs} +2 -3
- package/dist/{index-node-esm-CJsN8G51.mjs.map → index-node-esm-BwaOzhXB.mjs.map} +1 -1
- package/dist/index.d.mts +72 -100
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/{jsonl-CvgEOakL.mjs → jsonl-BGt7eqPK.mjs} +2 -3
- package/dist/{jsonl-CvgEOakL.mjs.map → jsonl-BGt7eqPK.mjs.map} +1 -1
- package/dist/{manager-BqlbCGYV.mjs → manager-1zEE5WiA.mjs} +4 -6
- package/dist/{manager-BqlbCGYV.mjs.map → manager-1zEE5WiA.mjs.map} +1 -1
- package/dist/{markdown-Bv4J8cg3.mjs → markdown-8ICz5aFw.mjs} +2 -3
- package/dist/{markdown-Bv4J8cg3.mjs.map → markdown-8ICz5aFw.mjs.map} +1 -1
- package/dist/{masker-ZoP381jN.mjs → masker-DEgR_ntk.mjs} +4 -6
- package/dist/{masker-ZoP381jN.mjs.map → masker-DEgR_ntk.mjs.map} +1 -1
- package/dist/{model-_3ZA2xqm.mjs → model-DqGqxfAc.mjs} +2 -3
- package/dist/{model-_3ZA2xqm.mjs.map → model-DqGqxfAc.mjs.map} +1 -1
- package/dist/{notify-DpG_m9iP.mjs → notify-Ds0-4BeI.mjs} +2 -3
- package/dist/{notify-DpG_m9iP.mjs.map → notify-Ds0-4BeI.mjs.map} +1 -1
- package/dist/{read-B-hwcc2Q.mjs → read-C5keKntU.mjs} +4 -44
- package/dist/read-C5keKntU.mjs.map +1 -0
- package/dist/{registry-L9GDccS0.mjs → registry-CntkeXlo.mjs} +45 -45
- package/dist/registry-CntkeXlo.mjs.map +1 -0
- package/dist/rolldown-runtime-DYf_mQCB.mjs +34 -0
- package/dist/{search-CRw247ES.mjs → search-B-89W3SM.mjs} +20 -17
- package/dist/search-B-89W3SM.mjs.map +1 -0
- package/dist/session/claude.d.mts +0 -2
- package/dist/session/claude.d.mts.map +1 -1
- package/dist/session/claude.mjs +4 -6
- package/dist/session/claude.mjs.map +1 -1
- package/dist/{session-BSvUrr12.mjs → session-C8zqqbPE.mjs} +6 -8
- package/dist/{session-BSvUrr12.mjs.map → session-C8zqqbPE.mjs.map} +1 -1
- package/dist/{session-Ds5nJz5A.d.mts → session-Cq52cam5.d.mts} +1 -11
- package/dist/session-Cq52cam5.d.mts.map +1 -0
- package/dist/session.d.mts +1 -3
- package/dist/session.d.mts.map +1 -1
- package/dist/session.mjs +4 -5
- package/dist/{src-C852Q6gy.mjs → src-C04Qk9KE.mjs} +35 -39
- package/dist/{src-C852Q6gy.mjs.map → src-C04Qk9KE.mjs.map} +1 -1
- package/dist/{subagent-C8XCEecM.mjs → subagent-BbcxLzgC.mjs} +4 -5
- package/dist/{subagent-C8XCEecM.mjs.map → subagent-BbcxLzgC.mjs.map} +1 -1
- package/dist/{swarm-BJ3gQA1Z.mjs → swarm-B_s9Qpbt.mjs} +2 -3
- package/dist/{swarm-BJ3gQA1Z.mjs.map → swarm-B_s9Qpbt.mjs.map} +1 -1
- package/dist/{swarm-BuFx_xeY.mjs → swarm-D823Yumk.mjs} +2 -3
- package/dist/{swarm-BuFx_xeY.mjs.map → swarm-D823Yumk.mjs.map} +1 -1
- package/dist/{tasks-WdRAHsOL.mjs → tasks-D76WXWr2.mjs} +3 -4
- package/dist/{tasks-WdRAHsOL.mjs.map → tasks-D76WXWr2.mjs.map} +1 -1
- package/dist/{tasks-BA-PCeGa.mjs → tasks-Dgsiuh0Q.mjs} +3 -4
- package/dist/{tasks-BA-PCeGa.mjs.map → tasks-Dgsiuh0Q.mjs.map} +1 -1
- package/dist/{tools-wvkkuXWH.mjs → tools-DmoUYORG.mjs} +7 -42
- package/dist/{tools-wvkkuXWH.mjs.map → tools-DmoUYORG.mjs.map} +1 -1
- package/dist/{truncate-BSMXgiXj.mjs → truncate-CChlZ113.mjs} +2 -3
- package/dist/{truncate-BSMXgiXj.mjs.map → truncate-CChlZ113.mjs.map} +1 -1
- package/dist/{uuid-CAdJg4l8.mjs → uuid-rfbQ9ad2.mjs} +2 -3
- package/dist/{uuid-CAdJg4l8.mjs.map → uuid-rfbQ9ad2.mjs.map} +1 -1
- package/dist/{wakeup-BYqTnfSp.mjs → wakeup-CFr1nu_L.mjs} +2 -3
- package/dist/{wakeup-BYqTnfSp.mjs.map → wakeup-CFr1nu_L.mjs.map} +1 -1
- package/dist/{write-DwGiDNeG.mjs → write-BRQFfWAn.mjs} +2 -3
- package/dist/write-BRQFfWAn.mjs.map +1 -0
- package/package.json +46 -8
- package/dist/bash-COgq4W7U.mjs.map +0 -1
- package/dist/compactions-CvQs64fS.mjs +0 -191
- package/dist/compactions-CvQs64fS.mjs.map +0 -1
- package/dist/edit-D4yaBK1K.mjs.map +0 -1
- package/dist/fetch-VKhSWdEh.mjs +0 -111
- package/dist/fetch-VKhSWdEh.mjs.map +0 -1
- package/dist/find-CimUGn-L.mjs.map +0 -1
- package/dist/grep-6O3mjSvV.mjs.map +0 -1
- package/dist/read-B-hwcc2Q.mjs.map +0 -1
- package/dist/registry-L9GDccS0.mjs.map +0 -1
- package/dist/search-CRw247ES.mjs.map +0 -1
- package/dist/session-Ds5nJz5A.d.mts.map +0 -1
- package/dist/write-DwGiDNeG.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"masker-ZoP381jN.mjs","names":["defaults"],"sources":["../src/context/scoring.ts","../src/masker.ts"],"sourcesContent":["// ── Frecency tuning ───────────────────────────────────────────────────\n// Half-life measured in assistant turns, not wall time — what matters for\n// compaction context is what's been hot in the agent's *recent\n// experience*, regardless of how long the user spent between turns.\n// 60 turns is a middle ground: actively-iterated work stays at the top\n// while recurring workflow patterns (git stash, test-runner flags) still\n// clear the minScore filter instead of getting buried under one-shot\n\nimport type {\n AnyPart,\n Message,\n MetaPart,\n ParamsOf,\n Role,\n SafeParamsOf,\n Tool,\n ToolCallPart,\n ToolResultPart,\n} from \"@zaly/ai\"\nimport type { BashTool } from \"../tools/bash.ts\"\nimport type { EditTool, EditToolMeta } from \"../tools/edit.ts\"\nimport type { ReadTool, ReadToolMeta } from \"../tools/read.ts\"\nimport type { AnyTool } from \"../tools/registry.ts\"\nimport type { WriteTool, WriteToolMeta } from \"../tools/write.ts\"\n\nimport { isAttachment, safeParseToolParams } from \"@zaly/ai\"\nimport { safeStringify } from \"@zaly/shared\"\n\nconst DEFAULT_HALF_LIFE = 40\nconst DEFAULT_WEIGHT = 1\nconst DEFAULT_GAMMA = 0.5\n\nconst maskedMeta = (result = \"result\", action = \"call\"): MetaPart => ({\n content: `Masked ${result}. Re-${action} to refresh`,\n tag: \"masked\",\n type: \"meta\",\n})\n\nexport type MsgPart<R extends Role = Role, P extends AnyPart = AnyPart> = {\n message: Message<R>\n part: P\n score: number\n turn: number\n $m: number\n $p: number\n}\n\nexport type MaskGroup<P extends MsgPart = MsgPart> = {\n id: string\n key: string\n policy: MaskPolicy<P, MaskGroup<P>>\n parts: P[]\n}\n\nexport type MaskRule<P extends MsgPart = MsgPart, G extends MaskGroup<P> = MaskGroup<P>> = {\n filter?: (part: MsgPart) => boolean\n id?: (part: P) => string | undefined\n update?: (group: G, part: P) => void\n gamma?: number | ((group: G) => number | undefined)\n halfLife?: number | ((group: G) => number | undefined)\n key?: \"id\" | ((group: G) => string | undefined)\n weight?: number | ((group: G) => number | undefined)\n mask: (part: P, group: G) => AnyPart\n}\n\nexport type ToolPart<T extends Tool = Tool, M extends object = object> = MsgPart<\n \"assistant\" | \"tool\" | \"system\",\n ToolCallPart<T[\"name\"], ParamsOf<T>> | ToolResultPart<T[\"name\"], M>\n>\n\nexport type ToolGroup<T extends Tool = Tool, M extends object = object> = MaskGroup<\n ToolPart<T, M>\n> & {\n name: T[\"name\"]\n params: SafeParamsOf<T>\n call?: ToolCallPart<T[\"name\"], ParamsOf<T>>\n result?: ToolResultPart<T[\"name\"], M>\n}\n\nexport type ToolRule<T extends Tool = Tool, M extends object = object> =\n | MaskRule<ToolPart<T, M>, ToolGroup<T, M>>\n | {\n key?: \"name\" | \"params\" | \"id\" | ((tool: ToolGroup<T, M>) => string | undefined)\n }\n\nclass MaskPolicy<\n P extends MsgPart = MsgPart,\n G extends MaskGroup<P> = MaskGroup<P>,\n> implements Required<MaskRule<P, G>> {\n #rule: MaskRule<P, G>\n\n constructor(rule: MaskRule<P, G>) {\n this.#rule = rule\n }\n\n update(group: G, part: P): void {\n group.parts.push(part)\n this.#rule.update?.(group, part)\n }\n\n mask(part: P, group: G): AnyPart {\n return this.#rule.mask(part, group)\n }\n\n filter(part: MsgPart): boolean {\n return this.#rule.filter?.(part) ?? true\n }\n\n id(part: P): string {\n return this.#rule.id?.(part) ?? `${part.$m}:${part.$p}`\n }\n\n halfLife(group: G): number {\n const halfLife = this.#rule.halfLife ?? DEFAULT_HALF_LIFE\n return typeof halfLife === \"number\" ? halfLife : (halfLife(group) ?? DEFAULT_HALF_LIFE)\n }\n\n lambda(group: G): number {\n return Math.LN2 / this.halfLife(group)\n }\n\n weight(group: G): number {\n const weight = this.#rule.weight ?? DEFAULT_WEIGHT\n return typeof weight === \"number\" ? weight : (weight(group) ?? DEFAULT_WEIGHT)\n }\n\n gamma(group: G): number {\n const gamma = this.#rule.gamma ?? DEFAULT_GAMMA\n return typeof gamma === \"number\" ? gamma : (gamma(group) ?? DEFAULT_GAMMA)\n }\n\n key(group: G): string {\n const key = this.#rule.key ?? \"id\"\n if (key === \"id\") return group.id\n else if (typeof key === \"function\") return key(group) ?? group.id\n return group.id\n }\n}\n\nclass ToolPolicy<T extends Tool = Tool, M extends object = object> extends MaskPolicy<\n ToolPart<T, M>,\n ToolGroup<T, M>\n> {\n constructor(name: string, rule: ToolRule<T, M>) {\n super({\n filter: (part) =>\n (part.part.type === \"tool-call\" || part.part.type === \"tool-result\") &&\n (part.part.name === name || name === \"*\"),\n id: (part) => part.part.id,\n mask: (part, group) => {\n if (part.part.type === \"tool-call\")\n return {\n ...part.part,\n params: { masked: truncate(safeStringify(group.params), 100) },\n }\n return { ...part.part, content: [maskedMeta()] }\n },\n update: (group, part) => {\n group.name = name\n if (part.part.type === \"tool-call\") {\n group.call = part.part\n group.params = safeParseToolParams(part.part.params)\n }\n if (part.part.type === \"tool-result\") group.result = part.part\n },\n ...rule,\n key: (group) => {\n const key = rule.key ?? \"params\"\n if (key === \"name\") return group.name\n if (key === \"params\" && group.params !== undefined) return safeStringify(group.params)\n if (key === \"id\") return group.id\n if (typeof key === \"function\") return key(group)\n return group.id\n },\n })\n }\n}\n\nfunction truncate(text: string, len: number): string {\n return text.length <= len ? text : `${text.slice(0, len)}…`\n}\n\nconst fileScore: ToolRule<\n ReadTool | WriteTool | EditTool,\n ReadToolMeta | WriteToolMeta | EditToolMeta\n> = {\n gamma: (t) => {\n if (t.name === \"write\") return 0.45\n if (t.name === \"edit\") return 0.65\n if (t.result?.meta?.full) return 0.75\n return 0.95\n },\n halfLife: (t) => {\n if (t.name === \"write\") return 90\n if (t.name === \"edit\") return 75\n if (t.result?.meta?.full) return 60\n return 50 // partial read\n },\n key: (t) => `file:${t.result?.meta?.path ?? t.id}`,\n mask: (part) => {\n if (part.part.type === \"tool-call\")\n return {\n ...part.part,\n params: { masked: true, path: part.part.params.path },\n }\n return part.part.isError ? part.part : { ...part.part, content: [maskedMeta()] }\n },\n weight: (t) => {\n if (t.name === \"write\") return 1.25\n if (t.name === \"edit\") return 1.2\n if (t.result?.meta?.full) return 1.1\n return 1\n },\n}\n\nexport type ContextScoringOptions = {\n tools?: Record<AnyTool | \"*\", ToolRule>\n parts?: MaskRule[]\n}\n\nconst defaults: ContextScoringOptions = {\n parts: [\n {\n filter: (part) => isAttachment(part.part),\n mask: (part) => maskedMeta(part.part.type, \"attach\"),\n },\n {\n filter: (part) => part.message.role === \"system\" && part.message.meta?.kind === \"task\",\n halfLife: 25,\n mask: (part) => {\n if (part.part.type === \"meta\") return part.part\n return maskedMeta(\"task\")\n },\n },\n ],\n // oxlint-disable-next-line sort-keys\n tools: {\n bash: {\n gamma: 0.35,\n halfLife: 25,\n key: (t: ToolGroup<BashTool>) => `bash:${t.params?.command?.replace(/\\s+/g, \" \") ?? t.id}`,\n },\n find: { gamma: 0.7, halfLife: 35 },\n grep: { gamma: 0.7, halfLife: 35 },\n search: { gamma: 0.5, halfLife: 40 },\n\n read: fileScore,\n edit: fileScore,\n write: fileScore,\n\n \"*\": { gamma: 0.5, halfLife: 40 },\n } as Record<AnyTool | \"*\", ToolRule>,\n}\n\nexport class ContextScoring {\n #groups = new Map<string, MaskGroup>()\n #messages: readonly Message[] = []\n #policies: MaskPolicy[] = []\n\n constructor(opts: ContextScoringOptions = {}) {\n const tools = { ...defaults.tools, ...opts.tools }\n for (const [name, tool] of Object.entries(tools)) {\n this.#policies.push(new ToolPolicy(name, tool) as MaskPolicy)\n }\n for (const rule of [...(defaults.parts ?? []), ...(opts.parts ?? [])]) {\n this.#policies.push(new MaskPolicy(rule))\n }\n }\n\n #reset() {\n this.#groups.clear()\n }\n\n #index() {\n let turn = 0\n for (let $m = this.#messages.length - 1; $m >= 0; $m--) {\n const m = this.#messages[$m]\n if (m.role === \"assistant\") turn++\n\n if (!Array.isArray(m.content)) continue\n\n for (let $p = m.content.length - 1; $p >= 0; $p--) {\n const part: MsgPart = { $m, $p, message: m, part: m.content[$p], score: 0, turn }\n const policy = this.#policy(part)\n if (!policy) continue\n const id = policy.id(part)\n const group = this.#groups.get(id) ?? { id, key: \"\", parts: [], policy }\n policy.update(group, part)\n this.#groups.set(id, group)\n }\n }\n }\n\n #policy(part: MsgPart): MaskPolicy | undefined {\n return this.#policies.find((policy) => policy.filter(part))\n }\n\n score(messages: readonly Message[]): MaskGroup[] {\n this.#reset()\n this.#messages = messages\n this.#index()\n // tools sorted by recency\n const groups = [...this.#groups.values()]\n const gammas = new Map<string, number>()\n for (const group of groups) {\n const key = group.policy.key(group)\n group.key = key\n const weight = group.policy.weight(group)\n const shadow = gammas.get(key) ?? 1\n const lambda = group.policy.lambda(group)\n for (const part of group.parts) {\n const decay = Math.exp(-lambda * part.turn)\n part.score = decay * weight * shadow\n }\n gammas.set(key, shadow * group.policy.gamma(group))\n }\n return groups\n }\n}\n","import type { AnyPart, Message, Role, Tool } from \"@zaly/ai\"\nimport type { MaybeGetter } from \"@zaly/shared\"\nimport type { Agent } from \"./agent.ts\"\nimport type { MsgPart } from \"./context/scoring.ts\"\nimport type { Session } from \"./session/session.ts\"\n\nimport { toValue } from \"@zaly/shared\"\nimport { ContextScoring } from \"./context/scoring.ts\"\nimport { estimatePart, tokenStats } from \"./context/tokens.ts\"\n\n/** Top-level masking config. */\nexport type MaskerOptions = {\n enabled?: boolean\n /** Don't mask tool-result parts whose original content is shorter\n * than this (estimated tokens). Skips tiny \"ok\"-style success\n * messages where the stub would be larger than the original.\n * Doesn't apply to attachments (always worth masking). */\n minTokens?: number\n /** How many turns to keep in the tail of the conversation, regardless\n * of score. Defaults to 20. */\n keepTurns?: number\n /** How far above the target ratio to trigger a new masking pass.\n * Defaults to 0.25 (25%). */\n delta?: number\n /** Target ratio of used/limit tokens to reach by masking. Defaults to 0.5 (50%). */\n target?: number\n}\n\nexport type MaskOpts = {\n force?: boolean\n limit?: number\n ratio?: number\n tools?: Tool[]\n prompt?: string[]\n}\n\nconst defaults = {\n delta: 0.25,\n enabled: true,\n keepTurns: 40,\n minTokens: 50,\n target: 0.5,\n} as const satisfies Required<MaskerOptions>\n\n/** In-place mask projection for the request stream.\n *\n * The masker replaces low-value parts with stable stubs to keep the\n * projected request within budget. A scoring pass ranks maskable parts\n * by recency, repeated-use shadowing, and per-policy weights; the budget\n * pass masks from lowest score upward until the target is reached or no\n * useful candidates remain.\n *\n * Masking intentionally happens rarely because it changes historical\n * bytes and busts prefix cache. The first pass after startup/session\n * reset always rebuilds the projection. Later passes use a hysteresis\n * threshold: mask toward `target`, then trigger again at roughly\n * `actual + delta`. If the target cannot be reached, the next threshold\n * rises automatically to avoid repeated no-op cache busts.\n */\nexport class Masker {\n #o: MaybeGetter<MaskerOptions>\n #stats = new Map<Role, Record<string, number>>()\n #masked = new Map<string, Map<number, AnyPart>>()\n #threshold?: number\n #agent: Agent\n\n constructor(agent: Agent, opts: MaybeGetter<MaskerOptions> = {}) {\n this.#o = opts\n this.#agent = agent\n agent.on(\"context\", async (ctx) => (ctx.messages = await this.mask(ctx.messages)))\n agent.ctx.on(\"session\", () => this.reset())\n }\n\n get #opts(): Required<MaskerOptions> {\n return { ...defaults, ...toValue(this.#o) }\n }\n\n get enabled(): boolean {\n return this.#opts.enabled\n }\n\n get #session(): Session {\n return this.#agent.session\n }\n\n #maskOpts(opts?: MaskOpts): Required<MaskOpts> {\n const pressure = this.#agent.pressure\n return {\n force: false,\n limit: pressure.limit,\n prompt: this.#agent.prompt,\n ratio: pressure.ratio,\n tools: this.#agent.tools,\n ...opts,\n }\n }\n\n reset(): void {\n this.#masked.clear()\n this.#stats.clear()\n this.#threshold = undefined\n }\n\n get stats(): Map<Role, Record<string, number>> {\n return this.#stats\n }\n\n /** Number of messages with at least one masked part. */\n get masked(): number {\n return this.#masked.size\n }\n\n isMasked(msgId: string, partIdx?: number): boolean {\n if (!this.enabled) return false\n const parts = this.#masked.get(msgId)\n if (parts === undefined) return false\n return partIdx === undefined ? parts.size > 0 : parts.has(partIdx)\n }\n\n /** Apply masks and return the projected message array.\n *\n * Always rebuild once after startup/session reset: `pressure.ratio`\n * may describe a previously masked projection, while this instance has\n * no mask decisions yet. After that, rebuild only when projected\n * pressure crosses the hysteresis threshold. */\n async mask(messages: readonly Message[], opts?: MaskOpts): Promise<Message[]> {\n if (!this.enabled || messages.length === 0) return [...messages]\n\n const resolved = this.#maskOpts(opts)\n\n // Masker has not yet been initialized for this session.\n // Use the session's mask checkpoint to restore the last threshold\n // and rebuild the mask decisions up to that point.\n if (this.#threshold === undefined) {\n const cp = this.#session.maskCheckpoint\n if (!cp) {\n // No checkpoint means the session has never been masked before, so\n // just set to the default threshold for the first pass, so that the next pass will\n // trigger only if the pressure exceeds the target + delta.\n this.#threshold = this.#opts.target + this.#opts.delta\n } else {\n this.#threshold = cp.threshold\n const idx = messages.findIndex((m) => m.id === cp.messageId)\n // NOTE: the checkpoint message should exists. If not mask all, just in case\n // Rebuild the mask decisions\n this.#update(idx === -1 ? messages : messages.slice(0, idx + 1), resolved)\n }\n }\n\n // If the current pressure ratio exceeds the threshold, rebuild the mask decisions.\n if (resolved.ratio >= this.#threshold || resolved.force) {\n const messageId = messages.at(-1)?.id\n if (!messageId) throw new Error(\"Message in masker without ID. Should never happen.\")\n // Create a checkpoint first, with the CURRENT threshold, not the new one.\n await this.#session.addMaskCheckpoint({\n messageId,\n threshold: this.#threshold,\n })\n this.#update(messages, resolved)\n }\n return this.#mask(messages)\n }\n\n #stat(role: Role, key: string, n = 1): void {\n const r = this.#stats.get(role) ?? {}\n r[key] = (r[key] ?? 0) + n\n this.#stats.set(role, r)\n }\n\n /** Recompute mask decisions for the current message history. */\n #update(messages: readonly Message[], opts: Required<MaskOpts>): void {\n this.#masked.clear()\n this.#stats.clear()\n this.#threshold = this.#opts.target + this.#opts.delta\n const usage = tokenStats(messages, { prompt: opts.prompt, tools: opts.tools })\n // `pressure.ratio` is based on the current projected/masked request,\n // not the raw session. Raw history may exceed model context; masking\n // only needs to keep the projected request in a safe band.\n const targetRatio = Math.max(this.#opts.target, this.#threshold - this.#opts.delta)\n const target = opts.limit * targetRatio\n\n // Return when already under target.\n if (usage.tokens <= target) return\n\n let mask = usage.tokens - target\n\n if (mask === 0) return\n\n const scorer = new ContextScoring()\n const scores = scorer.score(messages)\n const parts: (MsgPart & { mask: () => AnyPart })[] = []\n for (const s of scores) {\n for (const p of s.parts) {\n parts.push({\n ...p,\n mask: () => s.policy.mask(p, s),\n })\n }\n }\n parts.sort((a, b) => a.score - b.score)\n\n let masked = 0\n for (const p of parts) {\n if (mask <= 0) break\n const id = p.message.id\n if (!id) continue\n\n if (p.turn <= this.#opts.keepTurns) continue\n\n const tokens = estimatePart(p.part).tokens\n if (tokens < this.#opts.minTokens) continue\n\n const part = p.mask()\n if (part === p.part) continue\n\n const msgParts = this.#masked.get(id) ?? new Map()\n msgParts.set(p.$p, part)\n this.#stat(\n p.message.role,\n `${p.part.type}${p.part.type === \"tool-result\" || p.part.type === \"tool-call\" ? `:${p.part.name}` : \"\"}`\n )\n this.#masked.set(id, msgParts)\n const delta = tokens - estimatePart(part).tokens\n masked += delta\n mask -= delta\n }\n\n this.#threshold = Math.max(\n (usage.tokens - masked) / opts.limit + this.#opts.delta,\n this.#opts.target + this.#opts.delta\n )\n }\n\n /** Render the current projection using the last computed mask decisions. */\n #mask(messages: readonly Message[]): Message[] {\n return messages.map((m) => {\n if (m.id === undefined || typeof m.content === \"string\") return m\n const masked = m.id ? this.#masked.get(m.id) : undefined\n return masked && masked.size > 0\n ? ({\n ...m,\n content: m.content.map((p, i) => masked.get(i) ?? p),\n } as Message)\n : m\n })\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"masker-DEgR_ntk.mjs","names":["defaults"],"sources":["../src/context/scoring.ts","../src/masker.ts"],"sourcesContent":["// ── Frecency tuning ───────────────────────────────────────────────────\n// Half-life measured in assistant turns, not wall time — what matters for\n// compaction context is what's been hot in the agent's *recent\n// experience*, regardless of how long the user spent between turns.\n// 60 turns is a middle ground: actively-iterated work stays at the top\n// while recurring workflow patterns (git stash, test-runner flags) still\n// clear the minScore filter instead of getting buried under one-shot\n\nimport type {\n AnyPart,\n Message,\n MetaPart,\n ParamsOf,\n Role,\n SafeParamsOf,\n Tool,\n ToolCallPart,\n ToolResultPart,\n} from \"@zaly/ai\"\nimport type { BashTool } from \"../tools/bash.ts\"\nimport type { EditTool, EditToolMeta } from \"../tools/edit.ts\"\nimport type { ReadTool, ReadToolMeta } from \"../tools/read.ts\"\nimport type { AnyTool } from \"../tools/registry.ts\"\nimport type { WriteTool, WriteToolMeta } from \"../tools/write.ts\"\n\nimport { isAttachment, safeParseToolParams } from \"@zaly/ai\"\nimport { safeStringify } from \"@zaly/shared\"\n\nconst DEFAULT_HALF_LIFE = 40\nconst DEFAULT_WEIGHT = 1\nconst DEFAULT_GAMMA = 0.5\n\nconst maskedMeta = (result = \"result\", action = \"call\"): MetaPart => ({\n content: `Masked ${result}. Re-${action} to refresh`,\n tag: \"masked\",\n type: \"meta\",\n})\n\nexport type MsgPart<R extends Role = Role, P extends AnyPart = AnyPart> = {\n message: Message<R>\n part: P\n score: number\n turn: number\n $m: number\n $p: number\n}\n\nexport type MaskGroup<P extends MsgPart = MsgPart> = {\n id: string\n key: string\n policy: MaskPolicy<P, MaskGroup<P>>\n parts: P[]\n}\n\nexport type MaskRule<P extends MsgPart = MsgPart, G extends MaskGroup<P> = MaskGroup<P>> = {\n filter?: (part: MsgPart) => boolean\n id?: (part: P) => string | undefined\n update?: (group: G, part: P) => void\n gamma?: number | ((group: G) => number | undefined)\n halfLife?: number | ((group: G) => number | undefined)\n key?: \"id\" | ((group: G) => string | undefined)\n weight?: number | ((group: G) => number | undefined)\n mask: (part: P, group: G) => AnyPart\n}\n\nexport type ToolPart<T extends Tool = Tool, M extends object = object> = MsgPart<\n \"assistant\" | \"tool\" | \"system\",\n ToolCallPart<T[\"name\"], ParamsOf<T>> | ToolResultPart<T[\"name\"], M>\n>\n\nexport type ToolGroup<T extends Tool = Tool, M extends object = object> = MaskGroup<\n ToolPart<T, M>\n> & {\n name: T[\"name\"]\n params: SafeParamsOf<T>\n call?: ToolCallPart<T[\"name\"], ParamsOf<T>>\n result?: ToolResultPart<T[\"name\"], M>\n}\n\nexport type ToolRule<T extends Tool = Tool, M extends object = object> =\n | MaskRule<ToolPart<T, M>, ToolGroup<T, M>>\n | {\n key?: \"name\" | \"params\" | \"id\" | ((tool: ToolGroup<T, M>) => string | undefined)\n }\n\nclass MaskPolicy<\n P extends MsgPart = MsgPart,\n G extends MaskGroup<P> = MaskGroup<P>,\n> implements Required<MaskRule<P, G>> {\n #rule: MaskRule<P, G>\n\n constructor(rule: MaskRule<P, G>) {\n this.#rule = rule\n }\n\n update(group: G, part: P): void {\n group.parts.push(part)\n this.#rule.update?.(group, part)\n }\n\n mask(part: P, group: G): AnyPart {\n return this.#rule.mask(part, group)\n }\n\n filter(part: MsgPart): boolean {\n return this.#rule.filter?.(part) ?? true\n }\n\n id(part: P): string {\n return this.#rule.id?.(part) ?? `${part.$m}:${part.$p}`\n }\n\n halfLife(group: G): number {\n const halfLife = this.#rule.halfLife ?? DEFAULT_HALF_LIFE\n return typeof halfLife === \"number\" ? halfLife : (halfLife(group) ?? DEFAULT_HALF_LIFE)\n }\n\n lambda(group: G): number {\n return Math.LN2 / this.halfLife(group)\n }\n\n weight(group: G): number {\n const weight = this.#rule.weight ?? DEFAULT_WEIGHT\n return typeof weight === \"number\" ? weight : (weight(group) ?? DEFAULT_WEIGHT)\n }\n\n gamma(group: G): number {\n const gamma = this.#rule.gamma ?? DEFAULT_GAMMA\n return typeof gamma === \"number\" ? gamma : (gamma(group) ?? DEFAULT_GAMMA)\n }\n\n key(group: G): string {\n const key = this.#rule.key ?? \"id\"\n if (key === \"id\") return group.id\n else if (typeof key === \"function\") return key(group) ?? group.id\n return group.id\n }\n}\n\nclass ToolPolicy<T extends Tool = Tool, M extends object = object> extends MaskPolicy<\n ToolPart<T, M>,\n ToolGroup<T, M>\n> {\n constructor(name: string, rule: ToolRule<T, M>) {\n super({\n filter: (part) =>\n (part.part.type === \"tool-call\" || part.part.type === \"tool-result\") &&\n (part.part.name === name || name === \"*\"),\n id: (part) => part.part.id,\n mask: (part, group) => {\n if (part.part.type === \"tool-call\")\n return {\n ...part.part,\n params: { masked: truncate(safeStringify(group.params), 100) },\n }\n return { ...part.part, content: [maskedMeta()] }\n },\n update: (group, part) => {\n group.name = name\n if (part.part.type === \"tool-call\") {\n group.call = part.part\n group.params = safeParseToolParams(part.part.params)\n }\n if (part.part.type === \"tool-result\") group.result = part.part\n },\n ...rule,\n key: (group) => {\n const key = rule.key ?? \"params\"\n if (key === \"name\") return group.name\n if (key === \"params\" && group.params !== undefined) return safeStringify(group.params)\n if (key === \"id\") return group.id\n if (typeof key === \"function\") return key(group)\n return group.id\n },\n })\n }\n}\n\nfunction truncate(text: string, len: number): string {\n return text.length <= len ? text : `${text.slice(0, len)}…`\n}\n\nconst fileScore: ToolRule<\n ReadTool | WriteTool | EditTool,\n ReadToolMeta | WriteToolMeta | EditToolMeta\n> = {\n gamma: (t) => {\n if (t.name === \"write\") return 0.45\n if (t.name === \"edit\") return 0.65\n if (t.result?.meta?.full) return 0.75\n return 0.95\n },\n halfLife: (t) => {\n if (t.name === \"write\") return 90\n if (t.name === \"edit\") return 75\n if (t.result?.meta?.full) return 60\n return 50 // partial read\n },\n key: (t) => `file:${t.result?.meta?.path ?? t.id}`,\n mask: (part) => {\n if (part.part.type === \"tool-call\")\n return {\n ...part.part,\n params: { masked: true, path: part.part.params.path },\n }\n return part.part.isError ? part.part : { ...part.part, content: [maskedMeta()] }\n },\n weight: (t) => {\n if (t.name === \"write\") return 1.25\n if (t.name === \"edit\") return 1.2\n if (t.result?.meta?.full) return 1.1\n return 1\n },\n}\n\nexport type ContextScoringOptions = {\n tools?: Record<AnyTool | \"*\", ToolRule>\n parts?: MaskRule[]\n}\n\nconst defaults: ContextScoringOptions = {\n parts: [\n {\n filter: (part) => isAttachment(part.part),\n mask: (part) => maskedMeta(part.part.type, \"attach\"),\n },\n {\n filter: (part) => part.message.role === \"system\" && part.message.meta?.kind === \"task\",\n halfLife: 25,\n mask: (part) => {\n if (part.part.type === \"meta\") return part.part\n return maskedMeta(\"task\")\n },\n },\n ],\n // oxlint-disable-next-line sort-keys\n tools: {\n bash: {\n gamma: 0.35,\n halfLife: 25,\n key: (t: ToolGroup<BashTool>) => `bash:${t.params?.command?.replace(/\\s+/g, \" \") ?? t.id}`,\n },\n find: { gamma: 0.7, halfLife: 35 },\n grep: { gamma: 0.7, halfLife: 35 },\n search: { gamma: 0.5, halfLife: 40 },\n\n read: fileScore,\n edit: fileScore,\n write: fileScore,\n\n \"*\": { gamma: 0.5, halfLife: 40 },\n } as Record<AnyTool | \"*\", ToolRule>,\n}\n\nexport class ContextScoring {\n #groups = new Map<string, MaskGroup>()\n #messages: readonly Message[] = []\n #policies: MaskPolicy[] = []\n\n constructor(opts: ContextScoringOptions = {}) {\n const tools = { ...defaults.tools, ...opts.tools }\n for (const [name, tool] of Object.entries(tools)) {\n this.#policies.push(new ToolPolicy(name, tool) as MaskPolicy)\n }\n for (const rule of [...(defaults.parts ?? []), ...(opts.parts ?? [])]) {\n this.#policies.push(new MaskPolicy(rule))\n }\n }\n\n #reset() {\n this.#groups.clear()\n }\n\n #index() {\n let turn = 0\n for (let $m = this.#messages.length - 1; $m >= 0; $m--) {\n const m = this.#messages[$m]\n if (m.role === \"assistant\") turn++\n\n if (!Array.isArray(m.content)) continue\n\n for (let $p = m.content.length - 1; $p >= 0; $p--) {\n const part: MsgPart = { $m, $p, message: m, part: m.content[$p], score: 0, turn }\n const policy = this.#policy(part)\n if (!policy) continue\n const id = policy.id(part)\n const group = this.#groups.get(id) ?? { id, key: \"\", parts: [], policy }\n policy.update(group, part)\n this.#groups.set(id, group)\n }\n }\n }\n\n #policy(part: MsgPart): MaskPolicy | undefined {\n return this.#policies.find((policy) => policy.filter(part))\n }\n\n score(messages: readonly Message[]): MaskGroup[] {\n this.#reset()\n this.#messages = messages\n this.#index()\n // tools sorted by recency\n const groups = [...this.#groups.values()]\n const gammas = new Map<string, number>()\n for (const group of groups) {\n const key = group.policy.key(group)\n group.key = key\n const weight = group.policy.weight(group)\n const shadow = gammas.get(key) ?? 1\n const lambda = group.policy.lambda(group)\n for (const part of group.parts) {\n const decay = Math.exp(-lambda * part.turn)\n part.score = decay * weight * shadow\n }\n gammas.set(key, shadow * group.policy.gamma(group))\n }\n return groups\n }\n}\n","import type { AnyPart, Message, Role, Tool } from \"@zaly/ai\"\nimport type { MaybeGetter } from \"@zaly/shared\"\nimport type { Agent } from \"./agent.ts\"\nimport type { MsgPart } from \"./context/scoring.ts\"\nimport type { Session } from \"./session/session.ts\"\n\nimport { toValue } from \"@zaly/shared\"\nimport { ContextScoring } from \"./context/scoring.ts\"\nimport { estimatePart, tokenStats } from \"./context/tokens.ts\"\n\n/** Top-level masking config. */\nexport type MaskerOptions = {\n enabled?: boolean\n /** Don't mask tool-result parts whose original content is shorter\n * than this (estimated tokens). Skips tiny \"ok\"-style success\n * messages where the stub would be larger than the original.\n * Doesn't apply to attachments (always worth masking). */\n minTokens?: number\n /** How many turns to keep in the tail of the conversation, regardless\n * of score. Defaults to 20. */\n keepTurns?: number\n /** How far above the target ratio to trigger a new masking pass.\n * Defaults to 0.25 (25%). */\n delta?: number\n /** Target ratio of used/limit tokens to reach by masking. Defaults to 0.5 (50%). */\n target?: number\n}\n\nexport type MaskOpts = {\n force?: boolean\n limit?: number\n ratio?: number\n tools?: Tool[]\n prompt?: string[]\n}\n\nconst defaults = {\n delta: 0.25,\n enabled: true,\n keepTurns: 40,\n minTokens: 50,\n target: 0.5,\n} as const satisfies Required<MaskerOptions>\n\n/** In-place mask projection for the request stream.\n *\n * The masker replaces low-value parts with stable stubs to keep the\n * projected request within budget. A scoring pass ranks maskable parts\n * by recency, repeated-use shadowing, and per-policy weights; the budget\n * pass masks from lowest score upward until the target is reached or no\n * useful candidates remain.\n *\n * Masking intentionally happens rarely because it changes historical\n * bytes and busts prefix cache. The first pass after startup/session\n * reset always rebuilds the projection. Later passes use a hysteresis\n * threshold: mask toward `target`, then trigger again at roughly\n * `actual + delta`. If the target cannot be reached, the next threshold\n * rises automatically to avoid repeated no-op cache busts.\n */\nexport class Masker {\n #o: MaybeGetter<MaskerOptions>\n #stats = new Map<Role, Record<string, number>>()\n #masked = new Map<string, Map<number, AnyPart>>()\n #threshold?: number\n #agent: Agent\n\n constructor(agent: Agent, opts: MaybeGetter<MaskerOptions> = {}) {\n this.#o = opts\n this.#agent = agent\n agent.on(\"context\", async (ctx) => (ctx.messages = await this.mask(ctx.messages)))\n agent.ctx.on(\"session\", () => this.reset())\n }\n\n get #opts(): Required<MaskerOptions> {\n return { ...defaults, ...toValue(this.#o) }\n }\n\n get enabled(): boolean {\n return this.#opts.enabled\n }\n\n get #session(): Session {\n return this.#agent.session\n }\n\n #maskOpts(opts?: MaskOpts): Required<MaskOpts> {\n const pressure = this.#agent.pressure\n return {\n force: false,\n limit: pressure.limit,\n prompt: this.#agent.prompt,\n ratio: pressure.ratio,\n tools: this.#agent.tools,\n ...opts,\n }\n }\n\n reset(): void {\n this.#masked.clear()\n this.#stats.clear()\n this.#threshold = undefined\n }\n\n get stats(): Map<Role, Record<string, number>> {\n return this.#stats\n }\n\n /** Number of messages with at least one masked part. */\n get masked(): number {\n return this.#masked.size\n }\n\n isMasked(msgId: string, partIdx?: number): boolean {\n if (!this.enabled) return false\n const parts = this.#masked.get(msgId)\n if (parts === undefined) return false\n return partIdx === undefined ? parts.size > 0 : parts.has(partIdx)\n }\n\n /** Apply masks and return the projected message array.\n *\n * Always rebuild once after startup/session reset: `pressure.ratio`\n * may describe a previously masked projection, while this instance has\n * no mask decisions yet. After that, rebuild only when projected\n * pressure crosses the hysteresis threshold. */\n async mask(messages: readonly Message[], opts?: MaskOpts): Promise<Message[]> {\n if (!this.enabled || messages.length === 0) return [...messages]\n\n const resolved = this.#maskOpts(opts)\n\n // Masker has not yet been initialized for this session.\n // Use the session's mask checkpoint to restore the last threshold\n // and rebuild the mask decisions up to that point.\n if (this.#threshold === undefined) {\n const cp = this.#session.maskCheckpoint\n if (!cp) {\n // No checkpoint means the session has never been masked before, so\n // just set to the default threshold for the first pass, so that the next pass will\n // trigger only if the pressure exceeds the target + delta.\n this.#threshold = this.#opts.target + this.#opts.delta\n } else {\n this.#threshold = cp.threshold\n const idx = messages.findIndex((m) => m.id === cp.messageId)\n // NOTE: the checkpoint message should exists. If not mask all, just in case\n // Rebuild the mask decisions\n this.#update(idx === -1 ? messages : messages.slice(0, idx + 1), resolved)\n }\n }\n\n // If the current pressure ratio exceeds the threshold, rebuild the mask decisions.\n if (resolved.ratio >= this.#threshold || resolved.force) {\n const messageId = messages.at(-1)?.id\n if (!messageId) throw new Error(\"Message in masker without ID. Should never happen.\")\n // Create a checkpoint first, with the CURRENT threshold, not the new one.\n await this.#session.addMaskCheckpoint({\n messageId,\n threshold: this.#threshold,\n })\n this.#update(messages, resolved)\n }\n return this.#mask(messages)\n }\n\n #stat(role: Role, key: string, n = 1): void {\n const r = this.#stats.get(role) ?? {}\n r[key] = (r[key] ?? 0) + n\n this.#stats.set(role, r)\n }\n\n /** Recompute mask decisions for the current message history. */\n #update(messages: readonly Message[], opts: Required<MaskOpts>): void {\n this.#masked.clear()\n this.#stats.clear()\n this.#threshold = this.#opts.target + this.#opts.delta\n const usage = tokenStats(messages, { prompt: opts.prompt, tools: opts.tools })\n // `pressure.ratio` is based on the current projected/masked request,\n // not the raw session. Raw history may exceed model context; masking\n // only needs to keep the projected request in a safe band.\n const targetRatio = Math.max(this.#opts.target, this.#threshold - this.#opts.delta)\n const target = opts.limit * targetRatio\n\n // Return when already under target.\n if (usage.tokens <= target) return\n\n let mask = usage.tokens - target\n\n if (mask === 0) return\n\n const scorer = new ContextScoring()\n const scores = scorer.score(messages)\n const parts: (MsgPart & { mask: () => AnyPart })[] = []\n for (const s of scores) {\n for (const p of s.parts) {\n parts.push({\n ...p,\n mask: () => s.policy.mask(p, s),\n })\n }\n }\n parts.sort((a, b) => a.score - b.score)\n\n let masked = 0\n for (const p of parts) {\n if (mask <= 0) break\n const id = p.message.id\n if (!id) continue\n\n if (p.turn <= this.#opts.keepTurns) continue\n\n const tokens = estimatePart(p.part).tokens\n if (tokens < this.#opts.minTokens) continue\n\n const part = p.mask()\n if (part === p.part) continue\n\n const msgParts = this.#masked.get(id) ?? new Map()\n msgParts.set(p.$p, part)\n this.#stat(\n p.message.role,\n `${p.part.type}${p.part.type === \"tool-result\" || p.part.type === \"tool-call\" ? `:${p.part.name}` : \"\"}`\n )\n this.#masked.set(id, msgParts)\n const delta = tokens - estimatePart(part).tokens\n masked += delta\n mask -= delta\n }\n\n this.#threshold = Math.max(\n (usage.tokens - masked) / opts.limit + this.#opts.delta,\n this.#opts.target + this.#opts.delta\n )\n }\n\n /** Render the current projection using the last computed mask decisions. */\n #mask(messages: readonly Message[]): Message[] {\n return messages.map((m) => {\n if (m.id === undefined || typeof m.content === \"string\") return m\n const masked = m.id ? this.#masked.get(m.id) : undefined\n return masked && masked.size > 0\n ? ({\n ...m,\n content: m.content.map((p, i) => masked.get(i) ?? p),\n } as Message)\n : m\n })\n }\n}\n"],"mappings":";;;;AA4BA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AACvB,MAAM,gBAAgB;AAEtB,MAAM,cAAc,SAAS,UAAU,SAAS,YAAsB;CACpE,SAAS,UAAU,OAAO,OAAO,OAAO;CACxC,KAAK;CACL,MAAM;AACR;AAiDA,IAAM,aAAN,MAGsC;CACpC;CAEA,YAAY,MAAsB;EAChC,KAAK,QAAQ;CACf;CAEA,OAAO,OAAU,MAAe;EAC9B,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,MAAM,SAAS,OAAO,IAAI;CACjC;CAEA,KAAK,MAAS,OAAmB;EAC/B,OAAO,KAAK,MAAM,KAAK,MAAM,KAAK;CACpC;CAEA,OAAO,MAAwB;EAC7B,OAAO,KAAK,MAAM,SAAS,IAAI,KAAK;CACtC;CAEA,GAAG,MAAiB;EAClB,OAAO,KAAK,MAAM,KAAK,IAAI,KAAK,GAAG,KAAK,GAAG,GAAG,KAAK;CACrD;CAEA,SAAS,OAAkB;EACzB,MAAM,WAAW,KAAK,MAAM,YAAY;EACxC,OAAO,OAAO,aAAa,WAAW,WAAY,SAAS,KAAK,KAAK;CACvE;CAEA,OAAO,OAAkB;EACvB,OAAO,KAAK,MAAM,KAAK,SAAS,KAAK;CACvC;CAEA,OAAO,OAAkB;EACvB,MAAM,SAAS,KAAK,MAAM,UAAU;EACpC,OAAO,OAAO,WAAW,WAAW,SAAU,OAAO,KAAK,KAAK;CACjE;CAEA,MAAM,OAAkB;EACtB,MAAM,QAAQ,KAAK,MAAM,SAAS;EAClC,OAAO,OAAO,UAAU,WAAW,QAAS,MAAM,KAAK,KAAK;CAC9D;CAEA,IAAI,OAAkB;EACpB,MAAM,MAAM,KAAK,MAAM,OAAO;EAC9B,IAAI,QAAQ,MAAM,OAAO,MAAM;OAC1B,IAAI,OAAO,QAAQ,YAAY,OAAO,IAAI,KAAK,KAAK,MAAM;EAC/D,OAAO,MAAM;CACf;AACF;AAEA,IAAM,aAAN,cAA2E,WAGzE;CACA,YAAY,MAAc,MAAsB;EAC9C,MAAM;GACJ,SAAS,UACN,KAAK,KAAK,SAAS,eAAe,KAAK,KAAK,SAAS,mBACrD,KAAK,KAAK,SAAS,QAAQ,SAAS;GACvC,KAAK,SAAS,KAAK,KAAK;GACxB,OAAO,MAAM,UAAU;IACrB,IAAI,KAAK,KAAK,SAAS,aACrB,OAAO;KACL,GAAG,KAAK;KACR,QAAQ,EAAE,QAAQ,SAAS,cAAc,MAAM,MAAM,GAAG,GAAG,EAAE;IAC/D;IACF,OAAO;KAAE,GAAG,KAAK;KAAM,SAAS,CAAC,WAAW,CAAC;IAAE;GACjD;GACA,SAAS,OAAO,SAAS;IACvB,MAAM,OAAO;IACb,IAAI,KAAK,KAAK,SAAS,aAAa;KAClC,MAAM,OAAO,KAAK;KAClB,MAAM,SAAS,oBAAoB,KAAK,KAAK,MAAM;IACrD;IACA,IAAI,KAAK,KAAK,SAAS,eAAe,MAAM,SAAS,KAAK;GAC5D;GACA,GAAG;GACH,MAAM,UAAU;IACd,MAAM,MAAM,KAAK,OAAO;IACxB,IAAI,QAAQ,QAAQ,OAAO,MAAM;IACjC,IAAI,QAAQ,YAAY,MAAM,WAAW,KAAA,GAAW,OAAO,cAAc,MAAM,MAAM;IACrF,IAAI,QAAQ,MAAM,OAAO,MAAM;IAC/B,IAAI,OAAO,QAAQ,YAAY,OAAO,IAAI,KAAK;IAC/C,OAAO,MAAM;GACf;EACF,CAAC;CACH;AACF;AAEA,SAAS,SAAS,MAAc,KAAqB;CACnD,OAAO,KAAK,UAAU,MAAM,OAAO,GAAG,KAAK,MAAM,GAAG,GAAG,EAAE;AAC3D;AAEA,MAAM,YAGF;CACF,QAAQ,MAAM;EACZ,IAAI,EAAE,SAAS,SAAS,OAAO;EAC/B,IAAI,EAAE,SAAS,QAAQ,OAAO;EAC9B,IAAI,EAAE,QAAQ,MAAM,MAAM,OAAO;EACjC,OAAO;CACT;CACA,WAAW,MAAM;EACf,IAAI,EAAE,SAAS,SAAS,OAAO;EAC/B,IAAI,EAAE,SAAS,QAAQ,OAAO;EAC9B,IAAI,EAAE,QAAQ,MAAM,MAAM,OAAO;EACjC,OAAO;CACT;CACA,MAAM,MAAM,QAAQ,EAAE,QAAQ,MAAM,QAAQ,EAAE;CAC9C,OAAO,SAAS;EACd,IAAI,KAAK,KAAK,SAAS,aACrB,OAAO;GACL,GAAG,KAAK;GACR,QAAQ;IAAE,QAAQ;IAAM,MAAM,KAAK,KAAK,OAAO;GAAK;EACtD;EACF,OAAO,KAAK,KAAK,UAAU,KAAK,OAAO;GAAE,GAAG,KAAK;GAAM,SAAS,CAAC,WAAW,CAAC;EAAE;CACjF;CACA,SAAS,MAAM;EACb,IAAI,EAAE,SAAS,SAAS,OAAO;EAC/B,IAAI,EAAE,SAAS,QAAQ,OAAO;EAC9B,IAAI,EAAE,QAAQ,MAAM,MAAM,OAAO;EACjC,OAAO;CACT;AACF;AAOA,MAAMA,aAAkC;CACtC,OAAO,CACL;EACE,SAAS,SAAS,aAAa,KAAK,IAAI;EACxC,OAAO,SAAS,WAAW,KAAK,KAAK,MAAM,QAAQ;CACrD,GACA;EACE,SAAS,SAAS,KAAK,QAAQ,SAAS,YAAY,KAAK,QAAQ,MAAM,SAAS;EAChF,UAAU;EACV,OAAO,SAAS;GACd,IAAI,KAAK,KAAK,SAAS,QAAQ,OAAO,KAAK;GAC3C,OAAO,WAAW,MAAM;EAC1B;CACF,CACF;CAEA,OAAO;EACL,MAAM;GACJ,OAAO;GACP,UAAU;GACV,MAAM,MAA2B,QAAQ,EAAE,QAAQ,SAAS,QAAQ,QAAQ,GAAG,KAAK,EAAE;EACxF;EACA,MAAM;GAAE,OAAO;GAAK,UAAU;EAAG;EACjC,MAAM;GAAE,OAAO;GAAK,UAAU;EAAG;EACjC,QAAQ;GAAE,OAAO;GAAK,UAAU;EAAG;EAEnC,MAAM;EACN,MAAM;EACN,OAAO;EAEP,KAAK;GAAE,OAAO;GAAK,UAAU;EAAG;CAClC;AACF;AAEA,IAAa,iBAAb,MAA4B;CAC1B,0BAAU,IAAI,IAAuB;CACrC,YAAgC,CAAC;CACjC,YAA0B,CAAC;CAE3B,YAAY,OAA8B,CAAC,GAAG;EAC5C,MAAM,QAAQ;GAAE,GAAGA,WAAS;GAAO,GAAG,KAAK;EAAM;EACjD,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,KAAK,GAC7C,KAAK,UAAU,KAAK,IAAI,WAAW,MAAM,IAAI,CAAe;EAE9D,KAAK,MAAM,QAAQ,CAAC,GAAIA,WAAS,SAAS,CAAC,GAAI,GAAI,KAAK,SAAS,CAAC,CAAE,GAClE,KAAK,UAAU,KAAK,IAAI,WAAW,IAAI,CAAC;CAE5C;CAEA,SAAS;EACP,KAAK,QAAQ,MAAM;CACrB;CAEA,SAAS;EACP,IAAI,OAAO;EACX,KAAK,IAAI,KAAK,KAAK,UAAU,SAAS,GAAG,MAAM,GAAG,MAAM;GACtD,MAAM,IAAI,KAAK,UAAU;GACzB,IAAI,EAAE,SAAS,aAAa;GAE5B,IAAI,CAAC,MAAM,QAAQ,EAAE,OAAO,GAAG;GAE/B,KAAK,IAAI,KAAK,EAAE,QAAQ,SAAS,GAAG,MAAM,GAAG,MAAM;IACjD,MAAM,OAAgB;KAAE;KAAI;KAAI,SAAS;KAAG,MAAM,EAAE,QAAQ;KAAK,OAAO;KAAG;IAAK;IAChF,MAAM,SAAS,KAAK,QAAQ,IAAI;IAChC,IAAI,CAAC,QAAQ;IACb,MAAM,KAAK,OAAO,GAAG,IAAI;IACzB,MAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE,KAAK;KAAE;KAAI,KAAK;KAAI,OAAO,CAAC;KAAG;IAAO;IACvE,OAAO,OAAO,OAAO,IAAI;IACzB,KAAK,QAAQ,IAAI,IAAI,KAAK;GAC5B;EACF;CACF;CAEA,QAAQ,MAAuC;EAC7C,OAAO,KAAK,UAAU,MAAM,WAAW,OAAO,OAAO,IAAI,CAAC;CAC5D;CAEA,MAAM,UAA2C;EAC/C,KAAK,OAAO;EACZ,KAAK,YAAY;EACjB,KAAK,OAAO;EAEZ,MAAM,SAAS,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC;EACxC,MAAM,yBAAS,IAAI,IAAoB;EACvC,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,MAAM,MAAM,OAAO,IAAI,KAAK;GAClC,MAAM,MAAM;GACZ,MAAM,SAAS,MAAM,OAAO,OAAO,KAAK;GACxC,MAAM,SAAS,OAAO,IAAI,GAAG,KAAK;GAClC,MAAM,SAAS,MAAM,OAAO,OAAO,KAAK;GACxC,KAAK,MAAM,QAAQ,MAAM,OAEvB,KAAK,QADS,KAAK,IAAI,CAAC,SAAS,KAAK,IACrB,IAAI,SAAS;GAEhC,OAAO,IAAI,KAAK,SAAS,MAAM,OAAO,MAAM,KAAK,CAAC;EACpD;EACA,OAAO;CACT;AACF;;AC1RA,MAAM,WAAW;CACf,OAAO;CACP,SAAS;CACT,WAAW;CACX,WAAW;CACX,QAAQ;AACV;;;;;;;;;;;;;;;;AAiBA,IAAa,SAAb,MAAoB;CAClB;CACA,yBAAS,IAAI,IAAkC;CAC/C,0BAAU,IAAI,IAAkC;CAChD;CACA;CAEA,YAAY,OAAc,OAAmC,CAAC,GAAG;EAC/D,KAAK,KAAK;EACV,KAAK,SAAS;EACd,MAAM,GAAG,WAAW,OAAO,QAAS,IAAI,WAAW,MAAM,KAAK,KAAK,IAAI,QAAQ,CAAE;EACjF,MAAM,IAAI,GAAG,iBAAiB,KAAK,MAAM,CAAC;CAC5C;CAEA,IAAI,QAAiC;EACnC,OAAO;GAAE,GAAG;GAAU,GAAG,QAAQ,KAAK,EAAE;EAAE;CAC5C;CAEA,IAAI,UAAmB;EACrB,OAAO,KAAK,MAAM;CACpB;CAEA,IAAI,WAAoB;EACtB,OAAO,KAAK,OAAO;CACrB;CAEA,UAAU,MAAqC;EAC7C,MAAM,WAAW,KAAK,OAAO;EAC7B,OAAO;GACL,OAAO;GACP,OAAO,SAAS;GAChB,QAAQ,KAAK,OAAO;GACpB,OAAO,SAAS;GAChB,OAAO,KAAK,OAAO;GACnB,GAAG;EACL;CACF;CAEA,QAAc;EACZ,KAAK,QAAQ,MAAM;EACnB,KAAK,OAAO,MAAM;EAClB,KAAK,aAAa,KAAA;CACpB;CAEA,IAAI,QAA2C;EAC7C,OAAO,KAAK;CACd;;CAGA,IAAI,SAAiB;EACnB,OAAO,KAAK,QAAQ;CACtB;CAEA,SAAS,OAAe,SAA2B;EACjD,IAAI,CAAC,KAAK,SAAS,OAAO;EAC1B,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK;EACpC,IAAI,UAAU,KAAA,GAAW,OAAO;EAChC,OAAO,YAAY,KAAA,IAAY,MAAM,OAAO,IAAI,MAAM,IAAI,OAAO;CACnE;;;;;;;CAQA,MAAM,KAAK,UAA8B,MAAqC;EAC5E,IAAI,CAAC,KAAK,WAAW,SAAS,WAAW,GAAG,OAAO,CAAC,GAAG,QAAQ;EAE/D,MAAM,WAAW,KAAK,UAAU,IAAI;EAKpC,IAAI,KAAK,eAAe,KAAA,GAAW;GACjC,MAAM,KAAK,KAAK,SAAS;GACzB,IAAI,CAAC,IAIH,KAAK,aAAa,KAAK,MAAM,SAAS,KAAK,MAAM;QAC5C;IACL,KAAK,aAAa,GAAG;IACrB,MAAM,MAAM,SAAS,WAAW,MAAM,EAAE,OAAO,GAAG,SAAS;IAG3D,KAAK,QAAQ,QAAQ,KAAK,WAAW,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,QAAQ;GAC3E;EACF;EAGA,IAAI,SAAS,SAAS,KAAK,cAAc,SAAS,OAAO;GACvD,MAAM,YAAY,SAAS,GAAG,EAAE,CAAC,EAAE;GACnC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,oDAAoD;GAEpF,MAAM,KAAK,SAAS,kBAAkB;IACpC;IACA,WAAW,KAAK;GAClB,CAAC;GACD,KAAK,QAAQ,UAAU,QAAQ;EACjC;EACA,OAAO,KAAK,MAAM,QAAQ;CAC5B;CAEA,MAAM,MAAY,KAAa,IAAI,GAAS;EAC1C,MAAM,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,CAAC;EACpC,EAAE,QAAQ,EAAE,QAAQ,KAAK;EACzB,KAAK,OAAO,IAAI,MAAM,CAAC;CACzB;;CAGA,QAAQ,UAA8B,MAAgC;EACpE,KAAK,QAAQ,MAAM;EACnB,KAAK,OAAO,MAAM;EAClB,KAAK,aAAa,KAAK,MAAM,SAAS,KAAK,MAAM;EACjD,MAAM,QAAQ,WAAW,UAAU;GAAE,QAAQ,KAAK;GAAQ,OAAO,KAAK;EAAM,CAAC;EAI7E,MAAM,cAAc,KAAK,IAAI,KAAK,MAAM,QAAQ,KAAK,aAAa,KAAK,MAAM,KAAK;EAClF,MAAM,SAAS,KAAK,QAAQ;EAG5B,IAAI,MAAM,UAAU,QAAQ;EAE5B,IAAI,OAAO,MAAM,SAAS;EAE1B,IAAI,SAAS,GAAG;EAGhB,MAAM,SAAS,IADI,eACC,CAAC,CAAC,MAAM,QAAQ;EACpC,MAAM,QAA+C,CAAC;EACtD,KAAK,MAAM,KAAK,QACd,KAAK,MAAM,KAAK,EAAE,OAChB,MAAM,KAAK;GACT,GAAG;GACH,YAAY,EAAE,OAAO,KAAK,GAAG,CAAC;EAChC,CAAC;EAGL,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;EAEtC,IAAI,SAAS;EACb,KAAK,MAAM,KAAK,OAAO;GACrB,IAAI,QAAQ,GAAG;GACf,MAAM,KAAK,EAAE,QAAQ;GACrB,IAAI,CAAC,IAAI;GAET,IAAI,EAAE,QAAQ,KAAK,MAAM,WAAW;GAEpC,MAAM,SAAS,aAAa,EAAE,IAAI,CAAC,CAAC;GACpC,IAAI,SAAS,KAAK,MAAM,WAAW;GAEnC,MAAM,OAAO,EAAE,KAAK;GACpB,IAAI,SAAS,EAAE,MAAM;GAErB,MAAM,WAAW,KAAK,QAAQ,IAAI,EAAE,qBAAK,IAAI,IAAI;GACjD,SAAS,IAAI,EAAE,IAAI,IAAI;GACvB,KAAK,MACH,EAAE,QAAQ,MACV,GAAG,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,iBAAiB,EAAE,KAAK,SAAS,cAAc,IAAI,EAAE,KAAK,SAAS,IACtG;GACA,KAAK,QAAQ,IAAI,IAAI,QAAQ;GAC7B,MAAM,QAAQ,SAAS,aAAa,IAAI,CAAC,CAAC;GAC1C,UAAU;GACV,QAAQ;EACV;EAEA,KAAK,aAAa,KAAK,KACpB,MAAM,SAAS,UAAU,KAAK,QAAQ,KAAK,MAAM,OAClD,KAAK,MAAM,SAAS,KAAK,MAAM,KACjC;CACF;;CAGA,MAAM,UAAyC;EAC7C,OAAO,SAAS,KAAK,MAAM;GACzB,IAAI,EAAE,OAAO,KAAA,KAAa,OAAO,EAAE,YAAY,UAAU,OAAO;GAChE,MAAM,SAAS,EAAE,KAAK,KAAK,QAAQ,IAAI,EAAE,EAAE,IAAI,KAAA;GAC/C,OAAO,UAAU,OAAO,OAAO,IAC1B;IACC,GAAG;IACH,SAAS,EAAE,QAAQ,KAAK,GAAG,MAAM,OAAO,IAAI,CAAC,KAAK,CAAC;GACrD,IACA;EACN,CAAC;CACH;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/prompt/model.ts
|
|
2
2
|
async function prompt(ctx) {
|
|
3
3
|
const model = ctx.model.spec;
|
|
4
4
|
const provider = model.provider.id;
|
|
@@ -21,7 +21,6 @@ async function prompt(ctx) {
|
|
|
21
21
|
] : void 0
|
|
22
22
|
].flat().filter(Boolean).join("\n");
|
|
23
23
|
}
|
|
24
|
-
//#endregion
|
|
25
24
|
export { prompt };
|
|
26
25
|
|
|
27
|
-
//# sourceMappingURL=model-
|
|
26
|
+
//# sourceMappingURL=model-DqGqxfAc.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-
|
|
1
|
+
{"version":3,"file":"model-DqGqxfAc.mjs","names":[],"sources":["../src/prompt/model.ts"],"sourcesContent":["import type { PromptCtx } from \"./registry.ts\"\n\nexport async function prompt(ctx: PromptCtx): Promise<string> {\n const model = ctx.model.spec\n const provider = model.provider.id\n const modelId = model.model\n const modsIn = model.input\n const modsOut = model.output ?? []\n\n const lines = [\n \"## Model\",\n `- Id: ${modelId}`,\n `- Name: ${model.name}`,\n `- Provider: ${provider}`,\n `- Max tokens: ${model.maxTokens}`,\n model.knowledge ? `- Knowledge cutoff: ${model.knowledge}` : undefined,\n model.release_date ? `- Release date: ${model.release_date}` : undefined,\n model.last_updated ? `- Last updated: ${model.last_updated}` : undefined,\n modsIn.length > 0 || modsOut.length > 0\n ? [\n \"\\n### Modalities\",\n modsIn.length ? `- Input modalities: ${modsIn.join(\", \")}` : undefined,\n modsOut.length ? `- Output modalities: ${modsOut.join(\", \")}` : undefined,\n ]\n : undefined,\n ]\n\n return lines.flat().filter(Boolean).join(\"\\n\")\n}\n"],"mappings":";AAEA,eAAsB,OAAO,KAAiC;CAC5D,MAAM,QAAQ,IAAI,MAAM;CACxB,MAAM,WAAW,MAAM,SAAS;CAChC,MAAM,UAAU,MAAM;CACtB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,MAAM,UAAU,CAAC;CAoBjC,OAAO;EAjBL;EACA,SAAS;EACT,WAAW,MAAM;EACjB,eAAe;EACf,iBAAiB,MAAM;EACvB,MAAM,YAAY,uBAAuB,MAAM,cAAc,KAAA;EAC7D,MAAM,eAAe,mBAAmB,MAAM,iBAAiB,KAAA;EAC/D,MAAM,eAAe,mBAAmB,MAAM,iBAAiB,KAAA;EAC/D,OAAO,SAAS,KAAK,QAAQ,SAAS,IAClC;GACE;GACA,OAAO,SAAS,uBAAuB,OAAO,KAAK,IAAI,MAAM,KAAA;GAC7D,QAAQ,SAAS,wBAAwB,QAAQ,KAAK,IAAI,MAAM,KAAA;EAClE,IACA,KAAA;CAGK,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;AAC/C"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatDuration } from "@zaly/shared";
|
|
2
|
-
|
|
2
|
+
// src/notify.ts
|
|
3
3
|
var Notifier = class {
|
|
4
4
|
#opts;
|
|
5
5
|
#lastTime;
|
|
@@ -90,7 +90,6 @@ function timeInfo(t = Date.now()) {
|
|
|
90
90
|
tz
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
-
//#endregion
|
|
94
93
|
export { Notifier };
|
|
95
94
|
|
|
96
|
-
//# sourceMappingURL=notify-
|
|
95
|
+
//# sourceMappingURL=notify-Ds0-4BeI.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notify-
|
|
1
|
+
{"version":3,"file":"notify-Ds0-4BeI.mjs","names":[],"sources":["../src/notify.ts"],"sourcesContent":["import type { Agent } from \"./agent.ts\"\n\nimport { formatDuration } from \"@zaly/shared\"\n\nexport type NotifyContext = {\n agent: Agent\n}\n\nexport type NotifyOptions = {\n idle?: number // seconds of idle time (1800 = 30m)\n periodic?: number // seconds of periodic time notifications (3600 = 1h)\n}\n\nexport class Notifier {\n #opts: Required<NotifyOptions>\n #lastTime?: number\n #lastStep?: number\n /** Highest pressure threshold (from `#pressureLevels`) we've already\n * notified for in this session. Reset to 0 when pressure drops back\n * below the lowest threshold (e.g. after compaction), so a later\n * refill can fire the same level again. */\n #pressureLevel = 0\n #ac?: AbortController\n\n constructor(opts: NotifyOptions = {}) {\n this.#opts = { idle: 30 * 60, periodic: 60 * 60, ...opts }\n }\n\n attach(agent: Agent) {\n agent.on(\"step-start\", () => this.check(agent))\n agent.ctx.on(\"session\", () => this.attachSession(agent))\n this.attachSession(agent)\n }\n\n attachSession(agent: Agent) {\n this.#ac?.abort()\n this.#ac = new AbortController()\n const opts = { signal: this.#ac.signal }\n agent.session\n .on(\n \"compact\",\n ({ node }) => {\n this.#pressureLevel = 0\n agent.notify(\"compacted\", {\n ...this.time(),\n messages_preserved: node.tail,\n trigger: node.trigger,\n })\n },\n opts\n )\n .on(\n \"session-resume\",\n () => {\n agent.notify(\"session-resume\", this.time())\n },\n opts\n )\n .on(\n \"session-start\",\n () => {\n agent.notify(\"session-start\", this.time())\n },\n opts\n )\n .on(\n \"cwd\",\n ({ cwd }) => {\n agent.notify(\"cwd-changed\", { cwd })\n },\n opts\n )\n .on(\n \"model\",\n ({ model, prev }) => {\n agent.notify(\"model-changed\", { current: model, prev })\n },\n opts\n )\n }\n\n time(now = Date.now()) {\n this.#lastTime = now\n return timeInfo(now)\n }\n\n check(agent: Agent) {\n const now = Date.now()\n const lastInfo = this.#lastTime ? timeInfo(this.#lastTime) : undefined\n\n this.#lastStep ??= now\n this.#lastTime ??= now\n\n if (lastInfo && timeInfo(now).date !== lastInfo.date) {\n agent.notify(\"new-day\", this.time(now))\n } else if (now - this.#lastStep > this.#opts.idle * 1000) {\n agent.notify(\"user-returned\", {\n idle: formatDuration(this.#lastStep, { to: now }),\n ...this.time(now),\n })\n } else if (now - this.#lastTime > this.#opts.periodic * 1000) {\n agent.notify(\"time\", this.time(now))\n }\n this.#lastStep = now\n\n // Context-window pressure — denominator is `limit.context` (full\n // window), NOT `maxTokens` (per-request output cap). Notification\n // fires once per discrete level crossing (75% / 85% / 95%) so the\n // model gets at most a few escalating signals per session, not one\n // per step. Resets if pressure drops below the lowest level (e.g.\n // after compaction) so the cycle can fire again later.\n const pressure = agent.pressure\n if (pressure.level > this.#pressureLevel) {\n agent.notify(\"context-pressure\", {\n limit: pressure.limit,\n pct: Math.round(pressure.ratio * 100),\n used: pressure.used,\n })\n this.#pressureLevel = pressure.level\n } else if (pressure.level === 0) this.#pressureLevel = 0\n }\n}\n\nfunction timeInfo(t = Date.now()): {\n day: string\n date: string\n time: string\n tz: string\n} {\n const now = new Date(t)\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone\n return {\n date: now.toLocaleDateString(\"sv-SE\", { timeZone: tz }),\n day: now.toLocaleDateString(\"en-US\", { timeZone: tz, weekday: \"long\" }),\n time: now.toLocaleTimeString(\"sv-SE\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: tz,\n }),\n tz,\n }\n}\n"],"mappings":";;AAaA,IAAa,WAAb,MAAsB;CACpB;CACA;CACA;;;;;CAKA,iBAAiB;CACjB;CAEA,YAAY,OAAsB,CAAC,GAAG;EACpC,KAAK,QAAQ;GAAE,MAAM;GAAS,UAAU;GAAS,GAAG;EAAK;CAC3D;CAEA,OAAO,OAAc;EACnB,MAAM,GAAG,oBAAoB,KAAK,MAAM,KAAK,CAAC;EAC9C,MAAM,IAAI,GAAG,iBAAiB,KAAK,cAAc,KAAK,CAAC;EACvD,KAAK,cAAc,KAAK;CAC1B;CAEA,cAAc,OAAc;EAC1B,KAAK,KAAK,MAAM;EAChB,KAAK,MAAM,IAAI,gBAAgB;EAC/B,MAAM,OAAO,EAAE,QAAQ,KAAK,IAAI,OAAO;EACvC,MAAM,QACH,GACC,YACC,EAAE,WAAW;GACZ,KAAK,iBAAiB;GACtB,MAAM,OAAO,aAAa;IACxB,GAAG,KAAK,KAAK;IACb,oBAAoB,KAAK;IACzB,SAAS,KAAK;GAChB,CAAC;EACH,GACA,IACF,CAAC,CACA,GACC,wBACM;GACJ,MAAM,OAAO,kBAAkB,KAAK,KAAK,CAAC;EAC5C,GACA,IACF,CAAC,CACA,GACC,uBACM;GACJ,MAAM,OAAO,iBAAiB,KAAK,KAAK,CAAC;EAC3C,GACA,IACF,CAAC,CACA,GACC,QACC,EAAE,UAAU;GACX,MAAM,OAAO,eAAe,EAAE,IAAI,CAAC;EACrC,GACA,IACF,CAAC,CACA,GACC,UACC,EAAE,OAAO,WAAW;GACnB,MAAM,OAAO,iBAAiB;IAAE,SAAS;IAAO;GAAK,CAAC;EACxD,GACA,IACF;CACJ;CAEA,KAAK,MAAM,KAAK,IAAI,GAAG;EACrB,KAAK,YAAY;EACjB,OAAO,SAAS,GAAG;CACrB;CAEA,MAAM,OAAc;EAClB,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,WAAW,KAAK,YAAY,SAAS,KAAK,SAAS,IAAI,KAAA;EAE7D,KAAK,cAAc;EACnB,KAAK,cAAc;EAEnB,IAAI,YAAY,SAAS,GAAG,CAAC,CAAC,SAAS,SAAS,MAC9C,MAAM,OAAO,WAAW,KAAK,KAAK,GAAG,CAAC;OACjC,IAAI,MAAM,KAAK,YAAY,KAAK,MAAM,OAAO,KAClD,MAAM,OAAO,iBAAiB;GAC5B,MAAM,eAAe,KAAK,WAAW,EAAE,IAAI,IAAI,CAAC;GAChD,GAAG,KAAK,KAAK,GAAG;EAClB,CAAC;OACI,IAAI,MAAM,KAAK,YAAY,KAAK,MAAM,WAAW,KACtD,MAAM,OAAO,QAAQ,KAAK,KAAK,GAAG,CAAC;EAErC,KAAK,YAAY;EAQjB,MAAM,WAAW,MAAM;EACvB,IAAI,SAAS,QAAQ,KAAK,gBAAgB;GACxC,MAAM,OAAO,oBAAoB;IAC/B,OAAO,SAAS;IAChB,KAAK,KAAK,MAAM,SAAS,QAAQ,GAAG;IACpC,MAAM,SAAS;GACjB,CAAC;GACD,KAAK,iBAAiB,SAAS;EACjC,OAAO,IAAI,SAAS,UAAU,GAAG,KAAK,iBAAiB;CACzD;AACF;AAEA,SAAS,SAAS,IAAI,KAAK,IAAI,GAK7B;CACA,MAAM,MAAM,IAAI,KAAK,CAAC;CACtB,MAAM,KAAK,KAAK,eAAe,CAAC,CAAC,gBAAgB,CAAC,CAAC;CACnD,OAAO;EACL,MAAM,IAAI,mBAAmB,SAAS,EAAE,UAAU,GAAG,CAAC;EACtD,KAAK,IAAI,mBAAmB,SAAS;GAAE,UAAU;GAAI,SAAS;EAAO,CAAC;EACtE,MAAM,IAAI,mBAAmB,SAAS;GACpC,MAAM;GACN,QAAQ;GACR,UAAU;EACZ,CAAC;EACD;CACF;AACF"}
|
|
@@ -1,49 +1,10 @@
|
|
|
1
|
-
import { normPath
|
|
1
|
+
import { normPath } from "@zaly/shared";
|
|
2
2
|
import { AiError, defineTool, extractToolResults, toAttachment } from "@zaly/ai";
|
|
3
3
|
import { stat } from "node:fs/promises";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { normalizeEol } from "@zaly/shared/text";
|
|
6
6
|
import { fileDetect } from "@zaly/shared/detect";
|
|
7
|
-
|
|
8
|
-
function isFileMeta(meta) {
|
|
9
|
-
const m = meta;
|
|
10
|
-
return m !== void 0 && m !== null && typeof m === "object" && typeof m.path === "string" && typeof m.mtime === "number" && (m.kind === "read" || m.kind === "write" || m.kind === "edit");
|
|
11
|
-
}
|
|
12
|
-
function isUnchanged(path, ctx) {
|
|
13
|
-
return checkFresh(path, ctx, { full: true }) === true;
|
|
14
|
-
}
|
|
15
|
-
function checkFresh(path, ctx, opts = {}) {
|
|
16
|
-
path = normPath(ctx.cwd, path);
|
|
17
|
-
const mtime = safeStat(path)?.mtimeMs;
|
|
18
|
-
if (mtime === void 0) return new AiError({
|
|
19
|
-
code: "NOT_FOUND",
|
|
20
|
-
message: `${path}: file not found`
|
|
21
|
-
});
|
|
22
|
-
const messages = ctx.messages ?? [];
|
|
23
|
-
let ret = freshnessError(path, "NOT_READ");
|
|
24
|
-
for (const { m, $p, p } of extractToolResults(messages)) {
|
|
25
|
-
const id = m.id;
|
|
26
|
-
if (!id || !isFileMeta(p.meta) || p.meta.path !== path || ctx.isMasked?.(id, $p)) continue;
|
|
27
|
-
if (opts.full && !p.meta.full) continue;
|
|
28
|
-
if (p.meta.mtime === mtime) return true;
|
|
29
|
-
ret = freshnessError(path, "STALE");
|
|
30
|
-
}
|
|
31
|
-
return ret;
|
|
32
|
-
}
|
|
33
|
-
/** Build the canonical freshness error. `data.reason` distinguishes
|
|
34
|
-
* never-read from changed-since-read for callers and renderers. */
|
|
35
|
-
function freshnessError(path, reason) {
|
|
36
|
-
return new AiError({
|
|
37
|
-
code: "FILE_NOT_FRESH",
|
|
38
|
-
data: {
|
|
39
|
-
path,
|
|
40
|
-
reason
|
|
41
|
-
},
|
|
42
|
-
message: reason === "NOT_READ" ? `${path}: read this file before mutating it.` : `${path}: file changed since last read. Re-read before mutating.`
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
|
-
//#region src/tools/read.ts
|
|
7
|
+
// src/tools/read.ts
|
|
47
8
|
/**
|
|
48
9
|
* Read a file from disk.
|
|
49
10
|
*
|
|
@@ -99,7 +60,7 @@ const readTool = defineTool({
|
|
|
99
60
|
code: "NOT_A_FILE",
|
|
100
61
|
message: `${path} is not a regular file`
|
|
101
62
|
});
|
|
102
|
-
if (
|
|
63
|
+
if (extractToolResults(ctx.messages ?? [], ["read"]).find(({ m, $p, p }) => m.id && p.meta?.path === path && p.meta.full && p.meta.mtime === fileStat.mtimeMs && !ctx.isMasked?.(m.id, $p))) {
|
|
103
64
|
ctx.meta = {
|
|
104
65
|
full: false,
|
|
105
66
|
kind: "read",
|
|
@@ -186,7 +147,6 @@ function formatTextSlice(content, { offset, limit }) {
|
|
|
186
147
|
offset: start + 1
|
|
187
148
|
};
|
|
188
149
|
}
|
|
189
|
-
//#endregion
|
|
190
150
|
export { readTool };
|
|
191
151
|
|
|
192
|
-
//# sourceMappingURL=read-
|
|
152
|
+
//# sourceMappingURL=read-C5keKntU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-C5keKntU.mjs","names":[],"sources":["../src/tools/read.ts"],"sourcesContent":["import type { Content, Tool, ToolContext } from \"@zaly/ai\"\n\nimport { AiError, defineTool, extractToolResults, toAttachment } from \"@zaly/ai\"\nimport { normPath } from \"@zaly/shared\"\nimport { fileDetect } from \"@zaly/shared/detect\"\nimport { normalizeEol } from \"@zaly/shared/text\"\nimport { stat } from \"node:fs/promises\"\nimport { Type } from \"typebox\"\n\n/**\n * Read a file from disk.\n *\n * Output shape:\n * - Text files → plain text plus a `<slice>` metadata part describing\n * the returned line range, including when reading the whole file.\n * - Image files (png, jpeg, webp, gif, avif, …) → `ImagePart` attachment.\n * The model sees the image natively if its modality supports it.\n * - Other binary files → `BINARY_FILE` error with a hint.\n *\n * Path resolution: relative paths resolve against the agent's session\n * cwd via `process.cwd()` for now. When the permission system gates this\n * via a tool-side hook, the cwd from `PermissionContext` will be used\n * instead so resolution stays consistent across the loop.\n */\nconst DEFAULT_LIMIT = 2000\nconst MAX_LINE_LENGTH = 2000\n\nexport type ReadTool = typeof readTool\nexport type FileTool = Tool<{ path: string }, unknown, FileMeta>\n\nexport type FileMeta = {\n path: string\n mtime: number\n kind: \"read\" | \"write\" | \"edit\"\n /** True when the result reflects the whole-file content — set by\n * un-sliced `read` and by `write` (which always replaces the\n * full file). Used by the masker to know whether this result\n * subsumes earlier reads of the same path. `edit` never sets it\n * because the result is patch-relative. */\n full?: boolean\n unchanged?: boolean\n}\n\nexport type ReadToolMeta = FileMeta & {\n offset: number\n limit: number\n}\n\n/** Build the `read` tool with model-aware schema description. The\n * attachment-shape blurb is included only when the loaded model\n * actually accepts the relevant modality — text-only models see a\n * description that promises only text output, so they don't reach\n * for the tool expecting images they can't process. */\n// oxlint-disable-next-line sort-keys -- semantic field order: name, desc, params, call\nexport const readTool = defineTool({\n name: \"read\",\n desc: `Read a file. Text files return plain text with line-range metadata. Image & PDF files are sent as attachments. Use \\`offset\\`/\\`limit\\` to slice large files.`,\n parallel: true,\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 offset: Type.Optional(\n Type.Integer({\n default: 1,\n description:\n \"1-based line number to start at. Negative values count from \" +\n \"the end: -50 starts 50 lines before EOF (so `offset: -50` \" +\n \"with the default limit returns the last 50 lines, like \" +\n \"`tail -n 50`). `offset: -50, limit: 20` reads 20 lines \" +\n \"starting 50 from the end.\",\n })\n ),\n limit: Type.Optional(\n Type.Integer({\n default: DEFAULT_LIMIT,\n description: \"Maximum number of lines to return.\",\n minimum: 1,\n })\n ),\n }),\n\n async preflight(args, ctx: ToolContext<ReadToolMeta>) {\n const path = normPath(ctx.cwd, args.path)\n await ctx.need?.(\"read\", path)\n },\n\n async call(args, ctx: ToolContext<ReadToolMeta>): Promise<Content> {\n const path = normPath(ctx.cwd, args.path)\n\n const fileStat = await stat(path).catch((error: unknown) => {\n throw new AiError({\n cause: error,\n code: \"NOT_FOUND\",\n message: `cannot read ${path}: file not found`,\n })\n })\n if (!fileStat.isFile()) {\n throw new AiError({ code: \"NOT_A_FILE\", message: `${path} is not a regular file` })\n }\n\n // Check whether we've already read this file's current bytes. If so, we can skip\n // returning the content again. This is a context optimization for large files\n // that are read multiple times without changes.\n const existing = extractToolResults<Partial<ReadToolMeta>>(ctx.messages ?? [], [\"read\"]).find(\n ({ m, $p, p }) =>\n m.id &&\n p.meta?.path === path &&\n p.meta.full &&\n p.meta.mtime === fileStat.mtimeMs &&\n !ctx.isMasked?.(m.id, $p)\n )\n\n if (existing) {\n // The exact file is still fully read in context, so we can skip returning it again.\n ctx.meta = {\n full: false,\n kind: \"read\",\n limit: 0,\n mtime: fileStat.mtimeMs,\n offset: 0,\n path,\n unchanged: true,\n }\n return [\n { content: `file unchanged since last read: ${path}`, tag: \"unchanged\", type: \"meta\" },\n ]\n }\n\n const file = await fileDetect(path)\n if (!file) {\n throw new AiError({\n code: \"READ_ERROR\",\n message: `${path}: could not read file`,\n })\n }\n\n // Threshold-based binary check — files with sporadic control bytes\n // (logs with ANSI styling, source with form feeds) still read as\n // text; only when binary content dominates the sample do we bail.\n if (file.type === \"binary\") {\n throw new AiError({\n code: \"BINARY_FILE\",\n data: { bytes: file.data.length, path },\n message: `${path}: binary file (${file.data.length} bytes); not displayable as text`,\n })\n }\n\n const att = await toAttachment(file)\n if (att) return [att]\n\n if (file.type !== \"text\") {\n throw new AiError({\n code: \"UNSUPPORTED_FILE\",\n data: { path, type: file.type },\n message: `${path}: unsupported file type ${file.type}`,\n })\n }\n\n const text = new TextDecoder().decode(file.data)\n const slice = formatTextSlice(text, {\n limit: args.limit ?? DEFAULT_LIMIT,\n offset: args.offset ?? 1,\n })\n\n // Record the observed mtime for unchanged-read detection. The masker\n // uses `full` to know whether this read subsumes earlier reads.\n ctx.meta = {\n full: slice.full,\n kind: \"read\",\n limit: slice.limit,\n mtime: fileStat.mtimeMs,\n offset: slice.offset,\n path,\n }\n\n return slice.content\n },\n})\n\n/** Return plain text and line-range metadata for both full and sliced reads. */\nfunction formatTextSlice(\n content: string,\n { offset, limit }: { offset: number; limit: number }\n): { content: Content; full?: boolean; offset: number; limit: number } {\n // Normalize line endings to LF for display. The model always sees LF;\n // edit/write detect the file's actual style and re-apply it on disk.\n content = normalizeEol(content)\n const lines = content.split(\"\\n\")\n // `split` on a final newline produces a trailing empty string — drop\n // it so a 3-line file with trailing LF reads as 3 lines, not 4.\n if (lines.at(-1) === \"\") lines.pop()\n\n // Negative offset = \"from the end\" — Python-slice / `tail -n` semantic.\n // `-1000` on a 30-line file clamps to 0 (read whole file) rather than\n // erroring; `0` is treated as `1` (head, off-by-one tolerance).\n let start = offset < 0 ? Math.max(0, lines.length + offset) : Math.max(0, offset - 1)\n // Overshoot clamps to `lines.length` so the slice loop emits nothing.\n // The agent gets an empty text + a `<slice>` meta that reports the\n // real file size, then can re-issue with a sensible offset.\n if (start >= lines.length) start = lines.length\n const end = Math.min(lines.length, start + limit)\n\n const out: string[] = []\n let full = start === 0 && end === lines.length\n for (let i = start; i < end; i++) {\n let line = lines[i]\n if (line.length > MAX_LINE_LENGTH) {\n full = false\n line = `${line.slice(0, MAX_LINE_LENGTH)}… [line truncated, ${line.length} chars]`\n }\n out.push(line)\n }\n\n const text = out.join(\"\\n\")\n\n // Empty slice (overshot offset): tell the agent the offset they asked\n // for and the real file size, rather than emitting a nonsensical\n // \"showing 6-5 of 5\"-style range.\n const sliceMsg =\n start === end && lines.length > 1\n ? `offset ${offset} past end of file (${lines.length} lines)`\n : `showing ${start + 1}-${end} of ${lines.length}`\n\n return {\n content: [\n { content: sliceMsg, tag: \"slice\", type: \"meta\" },\n { text, type: \"text\" },\n ],\n full,\n limit: end - start,\n offset: start + 1,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as __toESM, t as __commonJSMin } from "./rolldown-runtime-DYf_mQCB.mjs";
|
|
2
|
+
import { n as parseBash, t as TOOLS } from "./tools-DmoUYORG.mjs";
|
|
2
3
|
import { normPath, prettyPath } from "@zaly/shared";
|
|
3
4
|
import { createRegistry } from "@zaly/shared/registry";
|
|
4
5
|
import { dirname, isAbsolute, relative } from "pathe";
|
|
5
|
-
|
|
6
|
+
// src/permissions/handlers/bash.ts
|
|
6
7
|
/**
|
|
7
8
|
* Handler for `Rule<"bash">` — evaluates a bash command line against
|
|
8
9
|
* pattern rules, then delegates each segment's inferred and explicit
|
|
@@ -153,8 +154,7 @@ function matchRule(rule, seg) {
|
|
|
153
154
|
function resolveRules(rules, seg) {
|
|
154
155
|
for (const r of rules) if (matchRule(r, seg)) return r.policy;
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
-
//#region src/permissions/handlers/files.ts
|
|
157
|
+
// src/permissions/handlers/files.ts
|
|
158
158
|
var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
159
159
|
function makeArray(subject) {
|
|
160
160
|
return Array.isArray(subject) ? subject : [subject];
|
|
@@ -166,6 +166,7 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
166
166
|
const REGEX_LITERAL_SPECIAL = /[.*+?()[\]{}^$|\\/]/;
|
|
167
167
|
const BOM = "";
|
|
168
168
|
const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
169
|
+
const REGEX_ANCHORED_GLOBSTAR = /^\/\*{2,}$/;
|
|
169
170
|
const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
170
171
|
const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
171
172
|
const REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
@@ -182,10 +183,6 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
182
183
|
return value;
|
|
183
184
|
};
|
|
184
185
|
const RETURN_FALSE = () => false;
|
|
185
|
-
const cleanRangeBackSlash = (slashes) => {
|
|
186
|
-
const { length } = slashes;
|
|
187
|
-
return slashes.slice(0, length - length % 2);
|
|
188
|
-
};
|
|
189
186
|
const POSIX_CLASSES = {
|
|
190
187
|
alnum: "0-9A-Za-z",
|
|
191
188
|
alpha: "A-Za-z",
|
|
@@ -240,9 +237,9 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
240
237
|
while (end < length && pattern[end] !== "]") end++;
|
|
241
238
|
if (end === length) return null;
|
|
242
239
|
if (end > nameStart && pattern[end - 1] === ":") {
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
245
|
-
body +=
|
|
240
|
+
const name = pattern.slice(nameStart, end - 1);
|
|
241
|
+
if (!Object.prototype.hasOwnProperty.call(POSIX_CLASSES, name)) return null;
|
|
242
|
+
body += POSIX_CLASSES[name];
|
|
246
243
|
prev = EMPTY;
|
|
247
244
|
index = end;
|
|
248
245
|
} else {
|
|
@@ -266,6 +263,7 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
266
263
|
const REGEX_RESTORE_PLACEHOLDER = new RegExp(`${PLACEHOLDER}(\\d+)${PLACEHOLDER}`, "g");
|
|
267
264
|
const TRAILING_WILDCARD = "";
|
|
268
265
|
const TRAILING_DOUBLESTAR = "";
|
|
266
|
+
const isReserved = (char) => char === PLACEHOLDER || char === TRAILING_WILDCARD || char === TRAILING_DOUBLESTAR;
|
|
269
267
|
const extractBrackets = (pattern) => {
|
|
270
268
|
const sources = [];
|
|
271
269
|
const hold = (source) => `${PLACEHOLDER}${sources.push(source) - 1}${PLACEHOLDER}`;
|
|
@@ -277,11 +275,11 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
277
275
|
if (char === ESCAPE) {
|
|
278
276
|
const escaped = pattern[index + 1];
|
|
279
277
|
if (escaped === UNDEFINED || escaped === SLASH && index + 2 === length) out += hold(NEVER_MATCH);
|
|
280
|
-
else if (escaped === "*" || escaped === "[" || escaped === SPACE
|
|
281
|
-
else out += hold(REGEX_LITERAL_SPECIAL.test(escaped) ? ESCAPE + escaped : escaped);
|
|
278
|
+
else if (escaped === "*" || escaped === "[" || escaped === SPACE) out += pattern.slice(index, index + 2);
|
|
279
|
+
else out += hold(REGEX_LITERAL_SPECIAL.test(escaped) ? ESCAPE + escaped : isReserved(escaped) ? `[${escaped}]` : escaped);
|
|
282
280
|
index += 2;
|
|
283
|
-
} else if (char
|
|
284
|
-
out += hold(`[${
|
|
281
|
+
} else if (isReserved(char)) {
|
|
282
|
+
out += hold(`[${char}]`);
|
|
285
283
|
index++;
|
|
286
284
|
} else if (char === "[") {
|
|
287
285
|
const scanned = scanBracket(pattern, index);
|
|
@@ -310,6 +308,11 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
310
308
|
(_, run, space) => space ? run.slice(0, run.length - run.length % 2) + SPACE : run,
|
|
311
309
|
"\\ "
|
|
312
310
|
],
|
|
311
|
+
[
|
|
312
|
+
/(^|\/)\*{3,}(?=\/|$)/g,
|
|
313
|
+
(_, start) => `${start}**`,
|
|
314
|
+
"*"
|
|
315
|
+
],
|
|
313
316
|
[/[\\$.|*+(){^]/g, (match) => `\\${match}`],
|
|
314
317
|
[
|
|
315
318
|
/(?!\\)\?/g,
|
|
@@ -349,7 +352,7 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
349
352
|
],
|
|
350
353
|
[
|
|
351
354
|
/(^|[^\\])((?:\\\\)*)\\\*$/,
|
|
352
|
-
(match, p1, p2) => p2
|
|
355
|
+
(match, p1, p2) => p2 === EMPTY ? p1 + TRAILING_WILDCARD : match,
|
|
353
356
|
"*"
|
|
354
357
|
],
|
|
355
358
|
[
|
|
@@ -363,8 +366,8 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
363
366
|
"\\\\"
|
|
364
367
|
],
|
|
365
368
|
[
|
|
366
|
-
/\\\[([^\]/]*?)(
|
|
367
|
-
(match, range,
|
|
369
|
+
/\\\[([^\]/]*?)($|\])/g,
|
|
370
|
+
(match, range, close) => `\\[${range}${close}`,
|
|
368
371
|
"["
|
|
369
372
|
],
|
|
370
373
|
[DIRECT, (source) => {
|
|
@@ -456,6 +459,7 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
456
459
|
return out;
|
|
457
460
|
};
|
|
458
461
|
const makeRegexPrefix = (pattern) => {
|
|
462
|
+
if (REGEX_ANCHORED_GLOBSTAR.test(pattern)) pattern = "**";
|
|
459
463
|
const { source, sources } = extractBrackets(pattern);
|
|
460
464
|
const replaced = REPLACERS.reduce((prev, [matcher, replacer, required]) => {
|
|
461
465
|
if (matcher === DIRECT) return replacer(prev, pattern);
|
|
@@ -594,29 +598,27 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
594
598
|
if (matchedRule) ret.rule = matchedRule;
|
|
595
599
|
return ret;
|
|
596
600
|
}
|
|
597
|
-
test(path,
|
|
598
|
-
let ignored = false;
|
|
599
|
-
let unignored = false;
|
|
600
|
-
let matchedRule;
|
|
601
|
+
test(path, mode) {
|
|
601
602
|
const rules = this._rules;
|
|
602
603
|
const { length } = rules;
|
|
603
604
|
const shortcut = this._basenameCount * 2 >= length;
|
|
604
605
|
const basename = shortcut ? basenameOf(path) : path;
|
|
605
|
-
for (let index =
|
|
606
|
+
for (let index = length - 1; index >= 0; index--) {
|
|
606
607
|
const rule = rules[index];
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
608
|
+
if (rule[mode].test(shortcut && rule._basenameOnly ? basename : path)) {
|
|
609
|
+
const { negative } = rule;
|
|
610
|
+
const ret = {
|
|
611
|
+
ignored: !negative,
|
|
612
|
+
unignored: negative
|
|
613
|
+
};
|
|
614
|
+
if (!negative) ret.rule = rule;
|
|
615
|
+
return ret;
|
|
612
616
|
}
|
|
613
617
|
}
|
|
614
|
-
|
|
615
|
-
ignored,
|
|
616
|
-
unignored
|
|
618
|
+
return {
|
|
619
|
+
ignored: false,
|
|
620
|
+
unignored: false
|
|
617
621
|
};
|
|
618
|
-
if (matchedRule) ret.rule = matchedRule;
|
|
619
|
-
return ret;
|
|
620
622
|
}
|
|
621
623
|
};
|
|
622
624
|
const throwError = (message, Ctor) => {
|
|
@@ -659,26 +661,26 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
659
661
|
addPattern(pattern) {
|
|
660
662
|
return this.add(pattern);
|
|
661
663
|
}
|
|
662
|
-
_test(originalPath, cache
|
|
664
|
+
_test(originalPath, cache) {
|
|
663
665
|
const path = originalPath && checkPath.convert(originalPath);
|
|
664
666
|
checkPath(path, originalPath, this._strictPathCheck ? throwError : RETURN_FALSE);
|
|
665
|
-
return this._t(path, cache
|
|
667
|
+
return this._t(path, cache);
|
|
666
668
|
}
|
|
667
669
|
checkIgnore(path) {
|
|
668
670
|
if (path.charCodeAt(path.length - 1) !== SLASH_CODE) return this.test(path);
|
|
669
|
-
const dir = this._t(path, this._testCache
|
|
671
|
+
const dir = this._t(path, this._testCache);
|
|
670
672
|
if (dir.ignored) return dir;
|
|
671
673
|
const literal = this._rules.testLiteral(path);
|
|
672
674
|
return literal.ignored || literal.unignored ? literal : dir;
|
|
673
675
|
}
|
|
674
|
-
_t(path, cache
|
|
676
|
+
_t(path, cache) {
|
|
675
677
|
if (path in cache) return cache[path];
|
|
676
678
|
const parentPath = parentOf(path);
|
|
677
|
-
const parent = parentPath ? this._t(parentPath, cache
|
|
678
|
-
return cache[path] = parent && parent.ignored ? parent : this._rules.test(path,
|
|
679
|
+
const parent = parentPath ? this._t(parentPath, cache) : UNDEFINED;
|
|
680
|
+
return cache[path] = parent && parent.ignored ? parent : this._rules.test(path, MODE_IGNORE);
|
|
679
681
|
}
|
|
680
682
|
ignores(path) {
|
|
681
|
-
return this._test(path, this._ignoreCache
|
|
683
|
+
return this._test(path, this._ignoreCache).ignored;
|
|
682
684
|
}
|
|
683
685
|
createFilter() {
|
|
684
686
|
return (path) => !this.ignores(path);
|
|
@@ -687,7 +689,7 @@ var import_ignore = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
687
689
|
return makeArray(paths).filter(this.createFilter());
|
|
688
690
|
}
|
|
689
691
|
test(path) {
|
|
690
|
-
return this._test(path, this._testCache
|
|
692
|
+
return this._test(path, this._testCache);
|
|
691
693
|
}
|
|
692
694
|
};
|
|
693
695
|
const factory = (options) => new Ignore(options);
|
|
@@ -836,8 +838,7 @@ function relativizeToWorkspace(absPattern, workspace) {
|
|
|
836
838
|
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return void 0;
|
|
837
839
|
return `/${rel}`;
|
|
838
840
|
}
|
|
839
|
-
|
|
840
|
-
//#region src/permissions/handlers/tool.ts
|
|
841
|
+
// src/permissions/handlers/tool.ts
|
|
841
842
|
/**
|
|
842
843
|
* Generic tool-scope handler.
|
|
843
844
|
*
|
|
@@ -884,7 +885,6 @@ const handlerRegistry = createRegistry("scope").from({
|
|
|
884
885
|
tool: () => toolHandler,
|
|
885
886
|
write: () => fileHandler
|
|
886
887
|
});
|
|
887
|
-
//#endregion
|
|
888
888
|
export { handlerRegistry as t };
|
|
889
889
|
|
|
890
|
-
//# sourceMappingURL=registry-
|
|
890
|
+
//# sourceMappingURL=registry-CntkeXlo.mjs.map
|