@zaly/agent 0.0.4 → 0.0.5

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 (50) hide show
  1. package/dist/{agent-GDLdDc2w.mjs → agent-EkPynZRJ.mjs} +7 -9
  2. package/dist/agent-EkPynZRJ.mjs.map +1 -0
  3. package/dist/{bash-QNMTQ1PD.mjs → bash-COgq4W7U.mjs} +2 -2
  4. package/dist/{bash-QNMTQ1PD.mjs.map → bash-COgq4W7U.mjs.map} +1 -1
  5. package/dist/compactions-3AjPqjeZ.mjs.map +1 -1
  6. package/dist/{fetch-D8cmmA3E.mjs → fetch-DknuSrSX.mjs} +2 -2
  7. package/dist/{fetch-D8cmmA3E.mjs.map → fetch-DknuSrSX.mjs.map} +1 -1
  8. package/dist/{grep-B2Ax7ewy.mjs → grep-B4GF0svc.mjs} +2 -2
  9. package/dist/{grep-B2Ax7ewy.mjs.map → grep-B4GF0svc.mjs.map} +1 -1
  10. package/dist/index-node-esm-D6YQRKEz.mjs.map +1 -1
  11. package/dist/index.d.mts +42 -42
  12. package/dist/index.d.mts.map +1 -1
  13. package/dist/index.mjs +28 -22
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/{jsonl-Dw_VBp46.mjs → jsonl-CvgEOakL.mjs} +2 -2
  16. package/dist/jsonl-CvgEOakL.mjs.map +1 -0
  17. package/dist/{manager-D-lWBTHG.mjs → manager-f-WjFTMX.mjs} +2 -2
  18. package/dist/manager-f-WjFTMX.mjs.map +1 -0
  19. package/dist/masker-Bv9KjkDF.mjs.map +1 -1
  20. package/dist/notify-DpG_m9iP.mjs.map +1 -1
  21. package/dist/{registry-CFmR5G3J.mjs → registry-BVf0aZEA.mjs} +328 -48
  22. package/dist/registry-BVf0aZEA.mjs.map +1 -0
  23. package/dist/session/claude.d.mts +2 -3
  24. package/dist/session/claude.d.mts.map +1 -1
  25. package/dist/session/claude.mjs +1 -1
  26. package/dist/{session-CUUdWFC5.mjs → session-Bv8JiT8P.mjs} +4 -4
  27. package/dist/session-Bv8JiT8P.mjs.map +1 -0
  28. package/dist/session.d.mts +8 -8
  29. package/dist/session.d.mts.map +1 -1
  30. package/dist/session.mjs +3 -3
  31. package/dist/{subagent-baqzjRMc.mjs → subagent-CX7Zjcws.mjs} +3 -3
  32. package/dist/{subagent-baqzjRMc.mjs.map → subagent-CX7Zjcws.mjs.map} +1 -1
  33. package/dist/swarm-BJ3gQA1Z.mjs.map +1 -1
  34. package/dist/{tasks-D8rARHZ1.mjs → tasks-BA-PCeGa.mjs} +4 -3
  35. package/dist/{tasks-D8rARHZ1.mjs.map → tasks-BA-PCeGa.mjs.map} +1 -1
  36. package/dist/{tasks-WmC270X2.mjs → tasks-WdRAHsOL.mjs} +5 -4
  37. package/dist/tasks-WdRAHsOL.mjs.map +1 -0
  38. package/dist/{tools-DW1dbcCz.mjs → tools-Bl-YI890.mjs} +75 -25
  39. package/dist/tools-Bl-YI890.mjs.map +1 -0
  40. package/dist/truncate-BSMXgiXj.mjs.map +1 -1
  41. package/dist/{uuid-ByXP5SO5.mjs → uuid-CAdJg4l8.mjs} +2 -2
  42. package/dist/{uuid-ByXP5SO5.mjs.map → uuid-CAdJg4l8.mjs.map} +1 -1
  43. package/package.json +8 -8
  44. package/dist/agent-GDLdDc2w.mjs.map +0 -1
  45. package/dist/jsonl-Dw_VBp46.mjs.map +0 -1
  46. package/dist/manager-D-lWBTHG.mjs.map +0 -1
  47. package/dist/registry-CFmR5G3J.mjs.map +0 -1
  48. package/dist/session-CUUdWFC5.mjs.map +0 -1
  49. package/dist/tasks-WmC270X2.mjs.map +0 -1
  50. package/dist/tools-DW1dbcCz.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"agent-GDLdDc2w.mjs","names":["#last","#total","#opts","#usage","#totalUsage","#steps","#consecutiveErrors","#calls","#detectLoop","#opts","#ctx","#stopPolicy","#tasks","#wakeups","#usage","#cancelAllWakeups","#injectQueue","#appendQueue","#notifyQueue","#stop","#parent","#status","#abortController","#lastStop","#enqueue","#toolContext","#running","#pauseRequested","#loop","#collect","#streamOpts","#shouldAutoCompact","#setStatus","#parseToolCalls","#runTools","#started","#turn","#need"],"sources":["../src/utils/usage.ts","../src/stop.ts","../src/agent.ts"],"sourcesContent":["import type { Message, TokenCount, Usage } from \"@zaly/ai\"\n\n/** Sum two Usages. Optional fields are only present in the result\n * when at least one input had them set, so callers can tell \"no\n * reasoning happened this turn\" apart from \"0 reasoning tokens.\" */\nexport function addUsage(a: Usage, b: Usage): Usage {\n const out: Usage = {\n input: a.input + b.input,\n output: a.output + b.output,\n }\n if (a.cacheRead !== undefined || b.cacheRead !== undefined) {\n out.cacheRead = (a.cacheRead ?? 0) + (b.cacheRead ?? 0)\n }\n if (a.cacheWrite !== undefined || b.cacheWrite !== undefined) {\n out.cacheWrite = (a.cacheWrite ?? 0) + (b.cacheWrite ?? 0)\n }\n if (a.reasoning !== undefined || b.reasoning !== undefined) {\n out.reasoning = (a.reasoning ?? 0) + (b.reasoning ?? 0)\n }\n if (a.cost || b.cost) out.cost = addUsage(a.cost ?? empty(), b.cost ?? empty())\n return out\n}\n\nexport class TokenUsage implements Usage {\n #last: Usage = empty()\n #total: Usage = empty()\n\n constructor(messages?: readonly Message[]) {\n for (const m of messages ?? [])\n if (m.role === \"assistant\" && m.meta?.usage) this.add(m.meta.usage)\n }\n\n get cost(): Usage {\n return this.total.cost ?? empty()\n }\n\n get input(): number {\n return this.last.input\n }\n\n get output(): number {\n return this.last.output\n }\n\n get cacheRead(): number {\n return this.last.cacheRead ?? 0\n }\n\n get cacheWrite(): number {\n return this.last.cacheWrite ?? 0\n }\n\n get reasoning(): number {\n return this.last.reasoning ?? 0\n }\n\n add(count: Usage): void {\n this.#last = count\n this.#total = addUsage(this.#total, count)\n }\n\n resetLast(): void {\n this.#last = empty()\n }\n\n get last(): Usage {\n return this.#last\n }\n\n get total(): Usage {\n return this.#total\n }\n\n get contextSize(): number {\n return (\n this.last.input + this.last.output + (this.last.cacheRead ?? 0) + (this.last.cacheWrite ?? 0)\n )\n }\n}\n\nfunction empty(): TokenCount {\n return { input: 0, output: 0 }\n}\n","import type { TokenCount, ToolCallPart } from \"@zaly/ai\"\nimport type { Emitter, Envelope } from \"@zaly/shared\"\nimport type { AgentEvents, AgentStopKind } from \"./events.ts\"\n\nimport { safeStringify } from \"@zaly/shared\"\nimport { addUsage } from \"./utils/usage.ts\"\n\n/** Caps + heuristics that can end a run early. Wired into the\n * `AgentSession` loop via a single `detect()` call after each step. */\nexport interface StopOptions {\n /** Hard ceiling on provider round-trips per `run()`. Default: 50. */\n maxSteps?: number\n /** Cumulative token cap across the whole `run()`\n * (`totalUsage.input + totalUsage.output`). */\n tokenBudget?: number\n /** Bail after this many consecutive failing tool calls. A successful\n * tool result resets the streak. */\n maxToolErrors?: number\n\n // ── Loop detection ──────────────────────────────────────────────────\n // Two cheap heuristics over the running tool-call history. Hash is\n // `name + JSON.stringify(params)`; property order matters (rare false\n // negative if the model alternates key order on the same logical call,\n // accepted in exchange for a much cheaper hash). Set either limit to\n // `Infinity` to disable that arm.\n\n /** Same `(name, params)` appearing N times in a row → loop. Catches\n * the most common failure mode: re-calling `read_file` with the same\n * path expecting different output. Default 3. */\n loopConsecutive?: number\n /** Bounded window for duplicate detection. Default 10. */\n loopWindow?: number\n /** Within the window, this many duplicates of one call → loop.\n * Catches alternation patterns (`A B A B …`) the consecutive arm\n * alone won't see. Default 4. */\n loopWindowRepeats?: number\n}\n\n/**\n * Subscribes to an `AgentEvent` stream, accumulates the bookkeeping\n * the loop needs to make stop-or-continue decisions, and exposes a\n * single `detect()` to consult after each step.\n *\n * Lives outside `AgentSession` so concerns separate cleanly:\n * - the session owns conversation, queues, status, and loop control;\n * - the policy owns counters and the rules that act on them.\n *\n * Wire it up via `attach(session)` (returns an unsubscribe), or feed\n * events manually with `handle(event)` for custom drivers.\n */\nexport class StopPolicy {\n readonly #opts: StopOptions\n\n #steps = 0\n #consecutiveErrors = 0\n #calls: ToolCallPart[] = []\n #usage: TokenCount = { input: 0, output: 0 }\n #totalUsage: TokenCount = { input: 0, output: 0 }\n\n constructor(opts: StopOptions = {}) {\n this.#opts = opts\n }\n\n // ── Read ──────────────────────────────────────────────────────────────\n\n /** Token usage from the most recent step's response. */\n get usage(): TokenCount {\n return this.#usage\n }\n /** Cumulative token usage across every step in the current run. */\n get totalUsage(): TokenCount {\n return this.#totalUsage\n }\n get steps(): number {\n return this.#steps\n }\n get consecutiveErrors(): number {\n return this.#consecutiveErrors\n }\n /** Tool-call history fed to the loop detector. Read-only. */\n get calls(): readonly ToolCallPart[] {\n return this.#calls\n }\n\n // ── Wiring ────────────────────────────────────────────────────────────\n\n /** Subscribe to an emitter. To detach later, register the listener\n * yourself and pass the same fn to `emitter.off(fn)`:\n *\n * const handler = (e) => policy.handle(e)\n * emitter.all(handler)\n * // later:\n * emitter.off(handler) */\n attach(emitter: Emitter<AgentEvents>): void {\n emitter.onAny((event) => this.handle(event))\n }\n\n /** Feed a single event into the policy. Public so custom drivers\n * can drive it without going through `attach`. */\n handle(event: Envelope<AgentEvents>): void {\n switch (event.type) {\n case \"step-end\": {\n this.#steps++\n break\n }\n case \"tool-call\": {\n this.#calls.push(event.call)\n break\n }\n case \"tool-result\": {\n this.#consecutiveErrors = event.result.isError ? this.#consecutiveErrors + 1 : 0\n break\n }\n case \"stream-event\": {\n if (event.event.type === \"finish\") {\n this.#usage = event.event.usage\n this.#totalUsage = addUsage(this.#totalUsage, event.event.usage)\n }\n break\n }\n // status / stop — no policy state to update.\n }\n }\n\n /** Reset per-run counters. Token totals (`usage` / `totalUsage`)\n * persist unless `keepUsage: false` is passed — billing-style\n * displays usually want them sticky across resets. */\n reset(opts: { keepUsage?: boolean } = {}): void {\n this.#steps = 0\n this.#consecutiveErrors = 0\n this.#calls = []\n if (opts.keepUsage === false) {\n this.#usage = { input: 0, output: 0 }\n this.#totalUsage = { input: 0, output: 0 }\n }\n }\n\n /** Decide whether the loop should stop. Returns the stop reason or\n * `undefined` to continue. Order matters — `loop-detected` wins\n * over `max-tool-errors` if both fire, since a model in a tight\n * loop is the more pressing thing to surface. */\n detect(): AgentStopKind | undefined {\n if (this.#opts.maxSteps !== undefined && this.#steps >= this.#opts.maxSteps) {\n return \"max-steps\"\n }\n if (this.#detectLoop()) {\n return \"loop-detected\"\n }\n if (\n this.#opts.maxToolErrors !== undefined &&\n this.#consecutiveErrors >= this.#opts.maxToolErrors\n ) {\n return \"max-tool-errors\"\n }\n if (\n this.#opts.tokenBudget !== undefined &&\n this.#totalUsage.input + this.#totalUsage.output > this.#opts.tokenBudget\n ) {\n return \"token-budget\"\n }\n return undefined\n }\n\n // ── Loop detection internals ─────────────────────────────────────────\n\n #detectLoop(): boolean {\n const calls = this.#calls\n if (calls.length === 0) return false\n\n const consecutive = this.#opts.loopConsecutive ?? 3\n if (Number.isFinite(consecutive) && calls.length >= consecutive) {\n const last = hashCall(calls[calls.length - 1])\n let run = 1\n for (let i = calls.length - 2; i >= 0 && run < consecutive; i--) {\n if (hashCall(calls[i]) === last) run++\n else break\n }\n if (run >= consecutive) return true\n }\n\n const window = this.#opts.loopWindow ?? 10\n const windowRepeats = this.#opts.loopWindowRepeats ?? 4\n if (Number.isFinite(windowRepeats) && calls.length >= windowRepeats) {\n const slice = calls.slice(Math.max(0, calls.length - window))\n const counts = new Map<string, number>()\n for (const call of slice) {\n const h = hashCall(call)\n const next = (counts.get(h) ?? 0) + 1\n if (next >= windowRepeats) return true\n counts.set(h, next)\n }\n }\n\n return false\n }\n}\n\nfunction hashCall(call: ToolCallPart): string {\n return `${call.name}\\0${safeStringify(call.params)}`\n}\n","// oxlint-disable no-await-in-loop\nimport type {\n AssistantMessage,\n Content,\n Context,\n Message,\n MetaOf,\n MetaPart,\n Model,\n ModelStreamOptions,\n ParamsOf,\n TokenCount,\n Tool,\n ToolCallPart,\n ToolContext,\n ToolResult,\n} from \"@zaly/ai\"\nimport type { CompactionOptions } from \"./compaction/compactions.ts\"\nimport type { AgentContext } from \"./ctx.ts\"\nimport type { AgentEvents, AgentStatus, AgentStop, AgentStopKind } from \"./events.ts\"\nimport type { Session } from \"./session/session.ts\"\nimport type { AgentOptions, ContextPressure, SendMode, StepResult, TurnResult } from \"./types.ts\"\n\nimport { AiError, isContextOverflow, runTool } from \"@zaly/ai\"\nimport { Emitter, toValue, toError } from \"@zaly/shared\"\nimport { StopPolicy } from \"./stop.ts\"\nimport { Tasks, taskCompletionMessage, taskInfoPart } from \"./tasks.ts\"\nimport { TokenUsage } from \"./utils/usage.ts\"\nimport { uuidv7 } from \"./utils/uuid.ts\"\n\nconst PRESSURE_LEVELS = [0.75, 0.85, 0.95] as const\n\n/**\n * Long-lived agent — drives the multi-turn loop, owns the run-time\n * status / queues, and delegates conversation state to a `Session`.\n *\n * Typical interactive use:\n *\n * ```ts\n * const agent = await Agent.load({ model, tools })\n * agent.session.on(\"node\", (e) => render(e))\n * agent.send({ role: \"user\", content: \"hi\" }) // auto-runs\n * // …user types again later…\n * agent.send({ role: \"user\", content: \"follow-up\" }) // queues if running\n * ```\n *\n * Headless / one-shot use is just a thin wrapper on top — see\n * `runAgent` in the test helpers.\n */\nexport class Agent extends Emitter<AgentEvents> {\n readonly #opts: AgentOptions\n readonly #ctx: AgentContext\n readonly #stopPolicy: StopPolicy\n readonly #tasks: Tasks\n /** Nesting depth — see `AgentOptions.depth`. Read-only; subagents pass\n * `parent.depth + 1` when constructing their child. */\n readonly depth: number\n /** Cap on `depth` — see `AgentOptions.maxDepth`. */\n readonly maxDepth: number\n #started = false\n\n #parent?: Agent\n\n #injectQueue: Message[] = []\n #appendQueue: Message[] = []\n #notifyQueue: MetaPart[] = []\n\n #status: AgentStatus = \"idle\"\n #abortController?: AbortController\n #pauseRequested?: string\n #running?: Promise<AgentStopKind>\n #usage: TokenUsage\n\n /** Pending future-injects scheduled via `scheduleWakeup`. Cleared\n * whenever the loop becomes active for any reason — the scheduled\n * inject was a fallback \"wake me up at time T if nothing else does,\"\n * and once something else has, the timer is moot. Hints from cancelled\n * wakeups carry over as a system message so the intent doesn't\n * evaporate. */\n readonly #wakeups = new Map<string, { timer: ReturnType<typeof setTimeout>; hint?: string }>()\n\n #lastStop?: AgentStop\n\n /** Synchronous, low-level constructor. Prefer `Agent.load(opts)` —\n * it runs the same construction *plus* any async setup (resolving\n * `session: SessionOptions` to a built `Session`, skills discovery,\n * future warm-ups). The constructor is `protected` so test doubles /\n * subclasses can still call `super(init)` directly; they're\n * responsible for providing a pre-built `Session` on `init.session`. */\n public constructor(ctx: AgentContext) {\n super()\n const opts = ctx.opts\n this.#ctx = ctx\n this.#opts = ctx.opts\n this.depth = opts.depth ?? 0\n this.maxDepth = opts.maxDepth ?? 2\n\n // Note: session.update() + initial messages are committed\n // asynchronously from `Agent.load`, NOT here. Constructors can't\n // be async; deferring those writes until after construction also\n // gives consumers a window to subscribe to session events before\n // the first node fires.\n\n this.#tasks = new Tasks()\n this.#tasks.$tools = async () => this.tools\n this.#tasks.heartbeatMs = opts.heartbeatMs\n this.#tasks.onEmitError = (error) => opts.logger?.child(\"tasks\").error(error)\n // Post-round task completions inject a system message into the next\n // step, surfacing the result to the model. Round-internal completions\n // are folded into the round's returned parts and don't fire here.\n this.#tasks.on(\"task-done\", ({ task }) => {\n this.send(taskCompletionMessage(task))\n })\n // Heartbeats keep the agent loop alive while long-running tasks are\n // in flight. Each pulse injects a small system note listing what's\n // still going. Tasks with incremental output ready to read get a\n // `*new*` marker so the model knows to call `task_poll` if it cares.\n this.#tasks.on(\"heartbeat\", ({ running }) => {\n this.send({\n content: [{ content: [taskInfoPart(running)], tag: \"heartbeat\", type: \"meta\" }],\n role: \"system\",\n })\n })\n\n this.#stopPolicy = new StopPolicy(opts.stop)\n this.#stopPolicy.attach(this)\n this.onEmitError = (error) => opts.logger?.child(\"events\").error(error)\n this.#ctx.attach(this)\n\n // Seed usage\n this.#usage = new TokenUsage(this.messages)\n this.#ctx.on(\"session\", () => this.reset())\n }\n\n async reset(): Promise<void> {\n this.stop({ abort: true, reason: \"reset\" })\n await this.waitIdle()\n await this.#tasks.killAll()\n this.#cancelAllWakeups()\n this.#usage = new TokenUsage(this.messages)\n this.#stopPolicy.reset({ keepUsage: false })\n this.#injectQueue = []\n this.#appendQueue = []\n this.#notifyQueue = []\n this.#wakeups.clear()\n this.#stop(\"natural\")\n }\n\n /** Spawn a child agent that inherits this agent's runtime defaults —\n * cwd, model, permissions, full effective tool list (user tools +\n * the loaded skill tool), `depth + 1`, and `maxDepth`. Pass\n * `overrides` to specialize: typical ones are `prompt` (the child\n * has its own role), `session` (the child writes to its own JSONL),\n * and `tools` (curated subset).\n *\n * Used by the `subagent` tool for delegation; equally useful for\n * ad-hoc parallel-agent patterns (multiple children working on\n * pieces of a task in parallel).\n *\n * Depth cap: when the child would be at `maxDepth`, the `subagent`\n * tool is filtered out of its inherited tool list so recursion\n * bottoms out cleanly. The model just doesn't see the tool — no\n * error path. */\n async child(overrides: Partial<AgentOptions> = {}): Promise<Agent> {\n const childDepth = overrides.depth ?? this.depth + 1\n // Inherit the *effective* step tool list (includes the loaded\n // `skill` tool). The child opts out of its own skill scan since\n // the catalog is shared.\n\n let tools = [...this.tools].filter((t) => t.name !== \"skill\")\n if (childDepth >= this.maxDepth) tools = tools.filter((t) => t.name !== \"subagent\")\n const { createAgent } = await import(\"./ctx.ts\")\n\n const ret = await createAgent({\n cwd: this.cwd,\n depth: childDepth,\n mask: this.#opts.mask,\n maxDepth: this.maxDepth,\n model: this.model,\n // Inherit the parent's `notify` setting so test roots that\n // disable the notifier (`notify: false`) propagate that to\n // children — otherwise spawning a child would silently re-enable\n // session-started / time / etc. injections that the harness\n // explicitly opted out of.\n notify: this.#opts.notify,\n permissions: await this.ctx.permissions(),\n skills: this.#ctx.skills, // shared catalog; child doesn't reload\n // Propagate the swarm so the child + every grandchild address\n // each other through the same registry. Override-able via\n // `overrides.swarm` if a caller wants the child outside the\n // tree (rare).\n swarm: await this.#ctx.swarm(),\n tools,\n ...overrides,\n })\n ret.#parent = this\n // Children are spawned ready — subagent dispatch typically calls\n // `.send()` immediately after, and inspecting `child.tools` /\n // `child.prompt` before sending should just work.\n await ret.start()\n return ret\n }\n\n // ── Read ──────────────────────────────────────────────────────────────\n\n /** Active conversation — delegates to the underlying `Session`. */\n get messages(): readonly Message[] {\n return this.session.messages\n }\n\n get status(): AgentStatus {\n return this.#status\n }\n\n get cwd(): string {\n return this.#ctx.cwd\n }\n\n get model(): Model | undefined {\n return this.#ctx.model\n }\n\n get session(): Session {\n return this.#ctx.session\n }\n\n get signal(): AbortSignal | undefined {\n return this.#abortController?.signal\n }\n\n /** Token usage from the most recent step's response. Drives\n * `contextSize` and any \"this turn used N tokens\" UI. */\n get usage(): TokenUsage {\n return this.#usage\n }\n\n /** Cumulative token usage across every step in the current run.\n * Useful for billing-style displays. */\n get totalUsage(): TokenCount {\n return this.#usage.total\n }\n /** Logical size of the current conversation in tokens — what the\n * next request will send as input. Equals the last step's\n * `input + output` (since the model's reply becomes part of the\n * next prompt). Returns 0 before any step has run. */\n get contextSize(): number {\n return this.#usage.contextSize\n }\n\n get pressure(): ContextPressure {\n const used = this.contextSize\n const limit = this.model?.spec.contextSize ?? 0\n const ratio = limit > 0 ? used / limit : 0\n const level = PRESSURE_LEVELS.findLast((t) => ratio >= t) ?? 0\n return { level, limit, ratio, used }\n }\n\n get lastStop(): AgentStop | undefined {\n return this.#lastStop\n }\n\n get steps(): number {\n return this.#stopPolicy.steps\n }\n\n get parent(): Agent | undefined {\n return this.#parent\n }\n\n get tools() {\n return this.#ctx.tools\n }\n\n get prompt() {\n return this.#ctx.prompt\n }\n\n get ctx(): AgentContext {\n return this.#ctx\n }\n\n /** Long-running task registry — exposed for the TUI / introspection\n * tools (`task_list`, etc.). Mutating directly is a foot-gun;\n * prefer the agent's higher-level surface. */\n get tasks(): Tasks {\n return this.#tasks\n }\n\n // ── Input ────────────────────────────────────────────────────────────\n\n #enqueue(message: Message | Message[], opts: { mode?: SendMode } = {}): void {\n const messages = Array.isArray(message) ? message : [message]\n if (!messages.length) return\n for (const m of messages) m.id ??= uuidv7()\n const queue = opts.mode === \"append\" ? this.#appendQueue : this.#injectQueue\n queue.push(...messages)\n void this.emit(\"pending\", { messages })\n }\n\n /** Append a message and (re)start the loop. The user spoke; the agent\n * responds. If a loop is currently running, the message lands on the\n * follow-up queue and is processed after the current turn naturally\n * stops. Otherwise the loop starts immediately. */\n send(message?: Message | Message[], opts: { mode?: SendMode; run?: boolean } = {}): void {\n this.#enqueue(message ?? [], opts)\n if (opts.run === false) return\n if (this.#status === \"idle\" || this.#status === \"paused\") void this.run()\n }\n\n async useTool<T extends Tool = Tool>(\n name: T[\"name\"],\n params: ParamsOf<T>,\n msg: string,\n opts: { hidden?: boolean } = {}\n ): Promise<{\n call: ToolCallPart<T[\"name\"], ParamsOf<T>>\n result: ToolResult<MetaOf<T>>\n messages: [Message<\"system\">, Message<\"assistant\">, Message<\"tool\">]\n }> {\n const tool = this.tools.find((t) => t.name === name) as T | undefined\n if (!tool) throw new Error(`cannot inject tool use: no tool named \"${name}\"`)\n\n const id = uuidv7()\n const call: ToolCallPart = { id, name, params, type: \"tool-call\" }\n const result = await runTool(tool, params, await this.#toolContext(), { preflight: false })\n const messages: [Message<\"system\">, Message<\"assistant\">, Message<\"tool\">] = [\n {\n content: [\n {\n content: msg,\n data: { tool: name },\n tag: \"user-tool-use\",\n type: \"meta\",\n },\n ],\n role: \"system\",\n },\n {\n content: [call],\n role: \"assistant\",\n },\n {\n content: [\n {\n content: result.content,\n error: result.error,\n id,\n isError: result.isError,\n meta: result.meta,\n name,\n type: \"tool-result\",\n },\n ],\n role: \"tool\",\n },\n ]\n\n if (opts.hidden) for (const m of messages) m.hidden = true\n\n return { call, messages, result: result as ToolResult<MetaOf<T>> }\n }\n\n notify(type: string, data: Content | Record<string, unknown>): void {\n const meta: MetaPart =\n typeof data === \"string\" || Array.isArray(data)\n ? { content: data, tag: type, type: \"meta\" }\n : { data, tag: type, type: \"meta\" }\n this.#notifyQueue.push(meta)\n }\n\n /** Schedule a one-shot wake-up at `delayMs` from now. The agent will\n * receive a system message at that time IF nothing else has woken\n * the loop in the meantime — task-done injects, heartbeats, and user\n * messages all auto-cancel pending wakeups (their job was to ensure\n * the agent gets a turn; if it's already getting one, the timer is\n * redundant).\n *\n * Cancelled wakeups don't silently disappear — their `hint`s, if any,\n * are folded into a system message that lands in the next step\n * alongside whatever woke the loop. The model sees the hints with a\n * `status=\"cancelled\"` marker so it knows the timer didn't fire.\n *\n * Returns the wakeup id (mostly for telemetry / TUI display — the\n * model can't usefully cancel it later, since by the time it has a\n * turn to call cancel, the wakeup has either fired or been cancelled\n * for it). */\n scheduleWakeup(opts: { delayMs: number; hint?: string }): string {\n const id = uuidv7()\n const timer = setTimeout(() => {\n this.#wakeups.delete(id)\n this.send({\n content: [{ data: { hint: opts.hint, id }, tag: \"wakeup\", type: \"meta\" }],\n role: \"system\",\n })\n }, opts.delayMs)\n timer.unref()\n this.#wakeups.set(id, { hint: opts.hint, timer })\n return id\n }\n\n /** Cancel all pending wakeups, surfacing their hints as a single\n * system message so the model can see what was queued. Called from\n * `#setStatus` whenever the agent transitions to `streaming` — that's\n * the unambiguous \"loop is active\" signal. */\n #cancelAllWakeups(): void {\n if (this.#wakeups.size === 0) return\n const carried: { id: string; hint: string }[] = []\n for (const [id, { hint, timer }] of this.#wakeups) {\n clearTimeout(timer)\n if (hint) carried.push({ hint, id })\n }\n this.#wakeups.clear()\n if (carried.length > 0) {\n const parts: MetaPart[] = carried.map((c) => ({\n data: { hint: c.hint, id: c.id, status: \"cancelled\" as const },\n tag: \"wakeup\",\n type: \"meta\",\n }))\n this.#injectQueue.push({ content: parts, role: \"system\" })\n }\n }\n\n // ── Loop control ─────────────────────────────────────────────────────\n\n /** Drive steps until the agent goes idle (no queued messages and\n * the model stopped naturally) or hits a non-recoverable stop.\n * Doubles as a resume from `paused` — drains queued messages and\n * picks the loop back up. If a run is already in flight, returns\n * the existing promise. */\n run(): Promise<AgentStopKind> {\n if (this.#running) return this.#running\n this.#pauseRequested = undefined\n this.#abortController = undefined\n this.#running = this.#loop()\n .catch((error) => {\n this.#opts.logger?.child(\"run\").error(error)\n return this.#stop(\n error instanceof Error && error.name === \"AbortError\" ? \"aborted\" : \"error\",\n error\n )\n })\n .finally(() => {\n this.#abortController = undefined\n this.#running = undefined\n })\n return this.#running\n }\n\n async #collect() {\n this.#abortController = new AbortController()\n const context = {\n messages: [...this.ctx.messages],\n prompt: this.prompt,\n tools: this.tools,\n } satisfies Context\n await this.emitSerial(\"context\", context)\n const model = this.model\n if (!model) throw new Error(\"model must be loaded to collect\")\n return model.stream(context, this.#streamOpts())\n }\n\n #shouldAutoCompact(): boolean {\n const opts = toValue(this.#opts.compaction)\n const auto = opts?.enabled ?? true\n if (!auto) return false\n const threshold = opts?.threshold ?? 0.95\n return this.pressure.ratio >= threshold\n }\n\n #streamOpts(): ModelStreamOptions {\n const base = this.#opts.request ?? {}\n return {\n ...base,\n onEvent: (event) => {\n void this.emit(\"stream-event\", { event })\n try {\n void this.#opts.onEvent?.(event)\n } catch (error) {\n this.#opts.logger?.child(\"stream\").error(error)\n }\n },\n onUpdate: this.#opts.onUpdate,\n reasoning: { ...base.reasoning, effort: this.#ctx.reasoning },\n signal: this.#abortController?.signal,\n }\n }\n\n /** Run exactly one step. Useful for tests and custom drivers\n * that want to interleave logic between steps. */\n async step(): Promise<StepResult> {\n if (this.#shouldAutoCompact()) await this.compact()\n // Drain the notify queue into the inject queue as a single system message\n if (this.#notifyQueue.length > 0) {\n this.#injectQueue.push({\n content: this.#notifyQueue.splice(0),\n role: \"system\",\n })\n }\n\n // Drain the send queue (user-submitted via `send()`) and the inject\n // queue (notifier / wakeup / hooks). Sequential await preserves\n // chronological order on the session.\n for (const m of this.#injectQueue.splice(0)) await this.session.add(m)\n for (const m of this.#appendQueue.splice(0)) await this.session.add(m)\n\n this.#setStatus(\"streaming\")\n\n let collected: AssistantMessage\n\n try {\n collected = await this.#collect()\n } catch (error) {\n const err = toError(error)\n const overflow = isContextOverflow({ message: err.message })\n if (overflow) this.#opts.logger?.warn(\"Context overflow detected:\", err)\n return {\n error: err,\n finishReason: overflow ? \"length\" : \"error\",\n kind: overflow ? \"context-overflow\" : \"error\",\n usage: { input: 0, output: 0 },\n }\n }\n\n this.#usage.add(collected.meta.usage)\n\n const result: Omit<StepResult, \"kind\"> = {\n finishReason: collected.meta.finishReason,\n message: collected,\n usage: collected.meta.usage,\n }\n\n // Silent-overflow check BEFORE committing the message — gives the\n // session a chance to drop it and retry on a compacted history.\n if (\n this.#opts.contextLimit !== undefined &&\n isContextOverflow({\n contextLimit: this.#opts.contextLimit,\n // Total prompt size = uncached input + cached reads + cached\n // writes. `usage.input` is uncached-only; the cache fields are\n // separate billing tiers that still occupy the context window.\n usageInput:\n result.usage.input + (result.usage.cacheRead ?? 0) + (result.usage.cacheWrite ?? 0),\n })\n )\n return { kind: \"context-overflow\", ...result }\n\n const calls = await this.#parseToolCalls(collected)\n await this.session.add(collected)\n\n if (calls.length === 0) return { kind: \"natural\", ...result }\n\n return {\n kind: \"tool-calls\",\n toolMessage: await this.#runTools(calls),\n ...result,\n }\n }\n\n async #parseToolCalls(message: Message): Promise<ToolCallPart[]> {\n if (typeof message.content === \"string\") return []\n const calls = message.content.filter((p): p is ToolCallPart => p.type === \"tool-call\")\n for (const call of calls) {\n const tool = this.tools.find((t) => t.name === call.name)\n if (!tool) continue\n try {\n call.params = (await tool.validator.cleanParams(call.params)) ?? call.params\n } catch {}\n }\n return calls\n }\n\n async compact(): Promise<void> {\n const prev = this.#status\n const pressure = this.pressure\n this.#setStatus(\"compacting\")\n this.#usage.resetLast()\n try {\n const { Compaction } = await import(\"./compaction/compactions.ts\")\n const opts: Partial<CompactionOptions> = {\n ...toValue(this.#opts.compaction),\n signal: this.#abortController?.signal,\n }\n const compactor = new Compaction(this, opts)\n await compactor.compact(pressure)\n } finally {\n this.#setStatus(prev)\n }\n }\n\n async #runTools(calls: ToolCallPart[]) {\n this.#setStatus(\"running-tools\")\n void this.emit(\"tool-calls\", { calls })\n for (const call of calls) void this.emit(\"tool-call\", { call })\n\n const emitted = new Set<string>()\n // The whole batch — including streamable promotion, parallel chains,\n // grace timing, and ownerRound suppression — lives in Tasks.run().\n // What lands back here is a 1:1 array of result parts ready to commit.\n // The skill tool (when active) is passed via `extraTools` so dispatch\n // can resolve `name: \"skill\"` calls without polluting `tasks.tools`.\n const resultParts = await this.#tasks.run(calls, await this.#toolContext(), {\n onResult: (call, result) => {\n emitted.add(call.id)\n void this.emit(\"tool-result\", { call, result })\n },\n })\n\n for (let i = 0; i < calls.length; i++) {\n const call = calls[i]\n const part = resultParts[i]\n if (emitted.has(call.id)) continue\n void this.emit(\"tool-result\", {\n call,\n result: {\n content: part.content,\n error: part.error,\n isError: part.isError ?? false,\n meta: part.meta,\n },\n })\n }\n const message: Message<\"tool\"> = { content: resultParts, role: \"tool\" }\n await this.session.add(message)\n return message\n }\n\n /** Resolve when the agent is in a quiescent state — `idle` or\n * `paused`. If a run is currently in flight (or was just kicked off\n * synchronously by `send` / `inject` / a wakeup), waits for it to\n * settle before returning the final status. If no run is in flight,\n * resolves immediately with the current status.\n *\n * Use this in REPL-style drivers to await the loop without racing\n * with internally-triggered runs (the wakeup timer, heartbeat, or\n * task-completion injects all spawn `void this.run()` independently\n * of caller-driven `await agent.run()`). The `#running` promise is\n * set synchronously inside `run()` before any await point, so this\n * catches every kicked-off cycle including the one `send` just fired.\n *\n * Errors from the loop are swallowed — callers read `agent.lastError`\n * / `agent.lastStopReason` for diagnostics. The point of `waitIdle`\n * is just to know \"the loop is no longer driving.\" */\n async waitIdle(timeout?: number): Promise<AgentStatus> {\n if (!this.#running) return this.#status\n const wait = [this.#running.catch(() => undefined)]\n if (timeout !== undefined) wait.push(new Promise((resolve) => setTimeout(resolve, timeout)))\n await Promise.race(wait)\n return this.#status\n }\n\n /** Stop the loop with a custom reason.\n * When `abort: true` (the default), also trigger the abort signal so any\n * in-flight stream or tool calls can react to the stop.\n * When `abort: false`, the agent still stops immediately, but any active\n * stream or tool calls are left to run to completion */\n stop(opts: { abort?: boolean; reason?: string } = {}): void {\n this.#pauseRequested ??= opts.reason ?? \"stop\"\n if (opts.abort ?? true) this.#abortController?.abort(opts.reason)\n }\n\n async start() {\n if (this.#started) return\n this.#started = true\n await this.emit(\"start\")\n }\n\n get started() {\n return this.#started\n }\n\n // ── Internals ────────────────────────────────────────────────────────\n\n async #turn(): Promise<TurnResult> {\n for (let step = 1; ; step++) {\n if (this.#abortController?.signal.aborted)\n return { kind: \"aborted\", reason: this.#abortController.signal.reason }\n if (this.#pauseRequested !== undefined)\n return { kind: \"paused\", reason: this.#pauseRequested }\n\n void this.emit(\"step-start\", { step })\n const outcome = await this.step()\n void this.emit(\"step-end\", { outcome: outcome.kind, step })\n\n if (outcome.kind === \"error\") {\n return {\n kind: outcome.error?.name === \"AbortError\" ? \"aborted\" : \"error\",\n reason: outcome.error,\n }\n }\n\n if (outcome.kind === \"natural\") {\n return { kind: \"natural\" }\n }\n\n if (outcome.kind === \"context-overflow\") {\n // Auto-compaction also gates the overflow recovery path. With\n // `auto: false`, overflow stops cleanly instead of attempting\n // a recovery the user explicitly opted out of.\n if (toValue(this.#opts.compaction)?.enabled === false) return { kind: \"context-overflow\" }\n // Compactor mutates the conversation; the rejected message is\n // not committed — next step retries on the compacted state.\n this.#opts.logger\n ?.child(\"compaction\")\n .warn(\n `context overflow detected (size ${this.contextSize}, limit ${this.pressure.limit}); running compaction`\n )\n await this.compact()\n continue\n }\n\n // outcome.kind === \"tool-calls\" — consult the policy.\n const stop = this.#stopPolicy.detect()\n if (stop) return { kind: stop }\n }\n }\n\n async #loop(): Promise<AgentStopKind> {\n if (!this.#started) await this.start()\n // Reset per-run counters (steps, consecutive errors, call history).\n // Token totals stay sticky across resets — they're billing-style\n // displays, not per-turn caps.\n this.#stopPolicy.reset()\n for (let turn = 1; ; turn++) {\n void this.emit(\"turn-start\", { turn })\n const result = await this.#turn()\n const reason = result.reason ? toError(result.reason).message : undefined\n void this.emit(\"turn-end\", { outcome: result.kind, reason, turn })\n\n if (result.kind === \"natural\") {\n // Drain follow-up queue if anything arrived during the turn.\n if (this.#appendQueue.length > 0) {\n for (const m of this.#appendQueue.splice(0)) await this.session.add(m)\n continue\n }\n // Wakeups (or any other inject) that fired while the agent was\n // streaming / running-tools land in `#injectQueue`. If we stop\n // here without giving them a turn, they get drained at the top\n // of the next unrelated user turn — surfacing the wakeup AFTER\n // the user's reply, with no `status=\"cancelled\"` marker since it\n // never went through `#cancelAllWakeups`. Continue the loop so\n // `step()` drains them next iteration.\n if (this.#injectQueue.length > 0) continue\n }\n return this.#stop(result.kind, result.reason)\n }\n }\n\n #stop(kind: AgentStopKind, reason?: unknown): AgentStopKind {\n this.#lastStop = {\n error: reason instanceof Error ? reason : undefined,\n kind,\n reason: reason === undefined ? undefined : toError(reason).message,\n }\n this.#setStatus(kind === \"natural\" ? \"idle\" : \"paused\")\n void this.emit(\"stop\", {\n status: this.#status,\n usage: this.usage,\n ...this.#lastStop,\n })\n return kind\n }\n\n /** Build the per-step `ToolContext` handed to each tool. The session\n * cwd, an abort signal scoped to the in-flight stream, and the\n * long-running spawn registry are all surfaced here. */\n async #toolContext(): Promise<ToolContext> {\n const masker = await this.#ctx.masker()\n return {\n agent: this,\n bash: this.#opts.bash,\n cwd: this.cwd,\n isMasked: (msgId: string, partIdx?: number) => masker?.isMasked(msgId, partIdx) ?? false,\n messages: this.session.messages,\n need: (scope, input) => this.#need(scope, input),\n perms: await this.ctx.permissions(),\n sessionDir: this.session.dir,\n signal: this.#abortController?.signal,\n swarm: () => this.#ctx.swarm(),\n tasks: this.#tasks,\n }\n }\n\n /** Implementation of `ctx.need(scope, input)`. Resolves on `allow`,\n * throws a `PERMISSION_DENIED` `AiError` on `deny`, and escalates\n * `ask` to `AgentOptions.allow` (treating it as `deny` when no\n * callback is configured). */\n async #need(scope: string, input: string): Promise<void> {\n const perms = await this.ctx.permissions()\n const r = perms.validate(scope, input)\n if (r.verdict === \"allow\") return\n if (r.verdict === \"ask\" && this.#opts.allow) {\n const ok = await this.#opts.allow({\n ask: r.ask,\n input,\n reason: r.reason,\n scope,\n suggestions: r.suggestions,\n })\n if (ok) return\n }\n throw new AiError({\n code: \"PERMISSION_DENIED\",\n data: { input, scope, suggestions: r.suggestions, verdict: r.verdict },\n message: r.reason,\n // `ask` verdicts that the user denied this turn are retryable —\n // they may add a rule later. Hard `deny` is not.\n retryable: r.verdict === \"ask\",\n })\n }\n\n /** Tear down session-scoped resources. Called on agent disposal — the\n * agent doesn't auto-dispose today, so callers (TUI on quit, headless\n * runner on completion) should invoke this explicitly. */\n async dispose(): Promise<void> {\n this.clearListeners()\n this.#cancelAllWakeups()\n await this.#tasks.killAll()\n }\n\n #setStatus(status: AgentStatus): void {\n const prev = this.#status\n if (prev === status) return\n // Cancel wakeups only when an EXTERNAL wake-up brings the loop back\n // to active (idle/paused → streaming). Mid-turn transitions\n // (running-tools → streaming) are the same turn continuing — a\n // wakeup the model just scheduled mid-tool-call would otherwise be\n // killed before it ever fires. The \"external wake-up\" cases that\n // *should* cancel: a `task-done` inject, a heartbeat, a user\n // message — all route through `inject` → `send` → `run` from the\n // idle/paused state, hitting this branch correctly.\n if (status === \"streaming\" && (prev === \"idle\" || prev === \"paused\")) {\n this.#cancelAllWakeups()\n }\n this.#status = status\n void this.emit(\"status\", { status })\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"jsonl-Dw_VBp46.mjs","names":["#jsonl","#fetch","#nodes","#root","#path","#it","#count","#fd","#toJson"],"sources":["../src/session/jsonl.ts"],"sourcesContent":["import type { FileHandle } from \"node:fs/promises\"\nimport type { SessionStore } from \"./store.ts\"\nimport type { SessionNode } from \"./types.ts\"\n\nimport { normPath, safeStringify, withLock } from \"@zaly/shared\"\nimport { appendFile, open } from \"node:fs/promises\"\n\nconst CHUNK_SIZE = 4 * 1024 * 1024\n\n/**\n * JSONL-backed `SessionStore` — append-only file, one record per line.\n *\n * Current implementation reads the entire file on `load()` and keeps the\n * full DAG in memory. Future optimizations (lazy reads with offset\n * index, LRU cache for huge sessions) can land behind this same\n * interface without changing `Session`.\n *\n * Crash tolerance: a truncated last line (interrupted mid-write) is\n * silently dropped; any other JSON parse error mid-file throws so real\n * corruption surfaces loudly.\n */\nexport class JsonlStore implements SessionStore {\n readonly path: string\n #nodes = new Map<string, SessionNode>()\n #root?: SessionNode\n #jsonl: JsonlReader<SessionNode>\n\n constructor(path: string) {\n this.path = path\n this.#jsonl = new JsonlReader(path)\n }\n\n /** Load + open: hydrate the DAG from `path` (when the file exists),\n * then open the writer in append mode. New nodes land on disk as\n * they're committed. */\n static async load(path: string): Promise<JsonlStore> {\n path = normPath(path)\n const ret = new JsonlStore(path)\n await ret.#fetch({ limit: 1 })\n return ret\n }\n\n async #fetch(opts?: { id?: string; limit?: number }): Promise<SessionNode | undefined> {\n let count = 0\n let value: SessionNode | undefined\n // oxlint-disable-next-line no-await-in-loop\n while ((value = await this.#jsonl.next()) !== undefined) {\n this.#nodes.set(value.uuid, value)\n this.#root ??= value\n if (opts?.limit && count++ >= opts.limit) break // safe — doesn't call .return()\n if (opts?.id && value.uuid === opts.id) return value\n }\n }\n\n get root(): SessionNode | undefined {\n return this.#root\n }\n\n async get(id: string): Promise<SessionNode | undefined> {\n return this.#nodes.get(id) ?? (await this.#fetch({ id }))\n }\n\n async write(node: SessionNode): Promise<void> {\n const line = `${safeStringify(node)}\\n`\n await withLock(this.path, async () => {\n await appendFile(this.path, line, \"utf8\")\n })\n this.#nodes.set(node.uuid, node)\n this.#root = node\n }\n\n async close(): Promise<void> {\n await this.#jsonl.close()\n }\n\n async *all(): AsyncIterable<SessionNode> {\n await this.#fetch()\n yield* this.#nodes.values()\n }\n}\n\nexport class JsonlReader<T> {\n #fd?: FileHandle\n #path: string\n #it?: AsyncIterableIterator<T>\n #count = 0\n\n constructor(readonly path: string) {\n this.#path = path\n this.#it = this.generator()\n }\n\n #toJson(line: string): T | undefined {\n if (line.trim() === \"\") return\n try {\n const ret = JSON.parse(line) as T\n this.#count++\n return ret\n } catch (error) {\n // Tolerate a truncated last line (crash mid-write); anything\n // else is real corruption.\n if (this.#count === 0) return\n throw new Error(\n `JsonlStore.load: malformed JSON at line ${this.#count + 1} of \"${this.#path}\": ${(error as Error).message}`,\n { cause: error }\n )\n }\n }\n\n private async *generator(): AsyncIterableIterator<T> {\n try {\n this.#fd = await open(this.#path, \"r\")\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw error\n }\n let { size: position } = await this.#fd.stat()\n let leftover = \"\"\n try {\n while (position > 0) {\n const length = Math.min(position, CHUNK_SIZE)\n position -= length\n // oxlint-disable-next-line no-await-in-loop\n const { buffer } = await this.#fd.read(Buffer.alloc(length), 0, length, position)\n const lines = (buffer.toString(\"utf8\") + leftover).split(/\\r?\\n/)\n leftover = lines.shift() ?? \"\"\n for (let i = lines.length - 1; i >= 0; i--) {\n const v = this.#toJson(lines[i])\n if (v) yield v\n }\n }\n if (leftover.length) {\n const v = this.#toJson(leftover)\n if (v) yield v\n }\n } finally {\n await this.#fd.close()\n this.#it = undefined\n }\n }\n\n async next(): Promise<T | undefined> {\n if (!this.#it) return\n const { value, done } = await this.#it.next()\n return done ? undefined : value\n }\n\n async close() {\n if (this.#it) {\n await this.#it.return?.() // runs the generator's finally\n this.#it = undefined\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"manager-D-lWBTHG.mjs","names":["#rules","#workspaces"],"sources":["../src/permissions/presets.ts","../src/permissions/manager.ts"],"sourcesContent":["import type { Verdict } from \"./types.ts\"\n\n/**\n * Permission presets — verdict-keyed pattern lists in the same shape\n * `parseRules` accepts. The TUI exposes these as `--preset strict`,\n * `--preset readonly`, etc.; the manager resolves the chosen preset to\n * the structured `Rule<string>[]` at construction.\n *\n * Pattern grammar:\n * \"bash(ls:*)\" → bash command pattern\n * \"read(/src/**)\" → workspace-relative gitignore pattern\n * \"read(*)\" → matches every read in any workspace\n * \"bash\" → bare scope, equivalent to `bash(*)` (match any cmd)\n *\n * Scope names are exact matches against the tool name they gate\n * (snake_case). Underscored scopes work too (`task_stop`, `task_poll`).\n */\nexport type PermissionPresetName = \"strict\" | \"readonly\" | \"permissive\" | \"yolo\"\n\nexport interface PermissionPreset {\n description: string\n rules: Partial<Record<Verdict, string[]>>\n}\n\n// ── Pattern groups ─────────────────────────────────────────────────────────\n\n/** Native tools that only inspect session state or workspace files. */\nconst READONLY_TOOLS = [\n \"tool(read)\",\n \"tool(grep)\",\n \"tool(find)\",\n \"tool(task_list)\",\n \"tool(task_poll)\",\n]\n\n/** Read-only utilities the model can run without surprises: file\n * inspection, search, metadata. None of these modify state on their own.\n * (`sed`/`awk` writes still gated by Write rules via the bash handler's\n * composition with the file handler.) */\nconst READONLY_BASH = [\n \"bash(ls:*)\",\n \"bash(cat:*)\",\n \"bash(head:*)\",\n \"bash(tail:*)\",\n \"bash(wc:*)\",\n \"bash(find:*)\",\n \"bash(grep:*)\",\n \"bash(rg:*)\",\n \"bash(fd:*)\",\n \"bash(sort:*)\",\n \"bash(uniq:*)\",\n \"bash(cut:*)\",\n \"bash(diff:*)\",\n \"bash(stat:*)\",\n \"bash(file:*)\",\n \"bash(realpath:*)\",\n \"bash(readlink:*)\",\n \"bash(basename:*)\",\n \"bash(dirname:*)\",\n \"bash(echo:*)\",\n \"bash(printf:*)\",\n \"bash(sed:*)\",\n \"bash(awk:*)\",\n \"bash(xxd:*)\",\n \"bash(hexdump:*)\",\n \"bash(od:*)\",\n \"bash(tree:*)\",\n \"bash(pwd)\",\n \"bash(true)\",\n \"bash(false)\",\n \"bash(which:*)\",\n \"bash(type:*)\",\n \"bash(command:*)\",\n \"bash(test:*)\",\n \"bash(cd:*)\",\n]\n\n/** Read-only git + GitHub CLI subcommands. */\nconst READONLY_GIT = [\n \"bash(git status:*)\",\n \"bash(git log:*)\",\n \"bash(git diff:*)\",\n \"bash(git show:*)\",\n \"bash(git show-ref:*)\",\n \"bash(git branch:*)\",\n \"bash(git remote:*)\",\n \"bash(git rev-parse:*)\",\n \"bash(git config --get:*)\",\n \"bash(gh issue list:*)\",\n \"bash(gh issue view:*)\",\n \"bash(gh pr list:*)\",\n \"bash(gh pr view:*)\",\n \"bash(gh repo view:*)\",\n]\n\n/** Common dev-workflow commands — build, test, typecheck. Excludes\n * package management (`npm install`, `bun add`) which can drag arbitrary\n * code in over the network. */\nconst DEV_BASIC = [\n \"bash(bun test:*)\",\n \"bash(bun test:node:*)\",\n \"bash(bun test:bun:*)\",\n \"bash(bun check:*)\",\n \"bash(bun run:*)\",\n \"bash(bun build:*)\",\n \"bash(bunx tsc:*)\",\n \"bash(bun tsc:*)\",\n \"bash(bun x tsc:*)\",\n \"bash(bunx vitest:*)\",\n \"bash(bun x vitest:*)\",\n \"bash(bunx oxlint:*)\",\n \"bash(bun x oxlint:*)\",\n \"bash(oxlint:*)\",\n]\n\n/** Hard denies — never auto-allowed, regardless of preset. */\nconst HARD_DENIES = [\"bash(sudo:*)\"]\n\n/** Broader rules unlocked by the permissive preset. Trades some safety\n * for speed — appropriate for frontier models with strong instruction\n * following. Includes ad-hoc bun/node evals and git mutations. */\nconst PERMISSIVE_EXTRAS = [\n \"bash(bun -e:*)\",\n \"bash(bun:*)\",\n \"bash(node:*)\",\n \"bash(node -e:*)\",\n \"bash(make:*)\",\n \"bash(mkdir:*)\",\n \"bash(cp:*)\",\n \"bash(mv:*)\",\n \"bash(touch:*)\",\n \"bash(ln:*)\",\n \"bash(chmod:*)\",\n \"bash(git add:*)\",\n \"bash(git commit:*)\",\n \"bash(git checkout:*)\",\n \"bash(git restore:*)\",\n \"bash(git stash:*)\",\n \"bash(git fetch:*)\",\n \"bash(git pull:*)\",\n \"bash(curl:*)\",\n \"bash(jq:*)\",\n \"bash(perl -e:*)\",\n \"bash(timeout:*)\",\n \"bash(for:*)\",\n \"bash(while:*)\",\n]\n\n// ── Presets ────────────────────────────────────────────────────────────────\n\n// oxlint-disable sort-keys\nexport const permissionPresets = {\n strict: {\n description: \"Ask for everything. No commands or file ops auto-allowed.\",\n rules: {\n deny: HARD_DENIES,\n // Override the tool/file handlers' default allows.\n ask: [\"tool(*)\", \"read(*)\", \"write(*)\"],\n },\n },\n readonly: {\n description:\n \"Auto-allow read-only utilities, git read-only ops, and common build/test commands. \" +\n \"Reads inside the workspace are allowed (sensitive paths denied); writes always ask.\",\n rules: {\n deny: HARD_DENIES,\n allow: [...READONLY_TOOLS, ...READONLY_BASH, ...READONLY_GIT, ...DEV_BASIC],\n ask: [\"tool(*)\", \"write(*)\"],\n },\n },\n permissive: {\n description:\n \"Readonly preset plus broader dev commands (file moves, ad-hoc bun/node evals, \" +\n \"git mutations, network fetches) and writes inside the workspace.\",\n rules: {\n deny: HARD_DENIES,\n allow: [\n \"tool(*)\",\n ...READONLY_BASH,\n ...READONLY_GIT,\n ...DEV_BASIC,\n ...PERMISSIVE_EXTRAS,\n // Allow writes inside any workspace (sensitive paths still denied\n // by the file handler before rule resolution).\n \"write(*)\",\n ],\n },\n },\n yolo: {\n description: \"Allow everything. No prompts. Use only when you trust the model and the task.\",\n rules: {\n allow: [\"bash\", \"read(*)\", \"write(*)\", \"tool(*)\"],\n },\n },\n} as const satisfies Record<PermissionPresetName, PermissionPreset>\n","import type { PermissionPresetName } from \"./presets.ts\"\nimport type { CheckResult, Rule, Verdict } from \"./types.ts\"\n\nimport { normPath } from \"@zaly/shared\"\nimport { handlerRegistry } from \"./handlers/registry.ts\"\nimport { permissionPresets } from \"./presets.ts\"\n\nexport type PermissionOptions = {\n /** Session cwd — anchor for resolving relative paths and the default\n * spawn cwd for bash subprocesses. Pinned at construction; the agent\n * never mutates `process.cwd()`, and there's no `cd` tool. Defaults\n * to `process.cwd()`. */\n cwd?: string\n /** Named preset — supplies a baseline rule set. Explicit `rules` win\n * over preset rules on conflict (first-match-wins resolution; user\n * rules are listed before preset rules at construction). */\n preset?: PermissionPresetName\n rules?: Rule[] | Partial<Record<Verdict, string[] | undefined>>\n /** Initial workspace list. `cwd` is auto-added so the user doesn't\n * have to repeat it. Pass paths absolute (or relative — they'll be\n * resolved against `cwd`). */\n workspaces?: readonly string[]\n}\n\n/**\n * Owns per-session permission state and dispatches `validate(scope, input)`\n * to the registered handler for `scope`. Handlers receive rules\n * pre-filtered to their scope plus the shared workspace list.\n *\n * Mutators (`addWorkspace`, `addRule`, …) are how the prompt UI promotes\n * an `ask` verdict — once the user picks \"allow `Bash(git push:*)` for\n * this session\", the TUI calls `manager.addRule(...)` and subsequent\n * `validate()` calls pick it up via the filter.\n */\nexport class PermissionManager {\n readonly cwd: string\n #rules: Rule[] = []\n #workspaces: string[]\n\n constructor(opts?: PermissionOptions) {\n this.cwd = normPath(opts?.cwd)\n if (opts?.rules) {\n const more = Array.isArray(opts.rules) ? opts.rules : parseRules(opts.rules)\n this.#rules.push(...more)\n }\n if (opts?.preset) this.#rules.push(...parseRules(permissionPresets[opts.preset].rules))\n this.#workspaces = (opts?.workspaces ?? []).map((p) => normPath(this.cwd, p))\n // Auto-include cwd so users don't have to repeat it. Listed first so\n // longest-prefix lookup in handlers stays predictable.\n if (!this.#workspaces.includes(this.cwd)) this.#workspaces.unshift(this.cwd)\n }\n\n // ── Workspaces ──────────────────────────────────────────────────────\n\n get workspaces(): readonly string[] {\n return this.#workspaces\n }\n\n addWorkspace(path: string): void {\n const abs = normPath(this.cwd, path)\n if (!this.#workspaces.includes(abs)) this.#workspaces.push(abs)\n }\n\n removeWorkspace(path: string): void {\n const abs = normPath(this.cwd, path)\n const i = this.#workspaces.indexOf(abs)\n if (i !== -1) this.#workspaces.splice(i, 1)\n }\n\n // ── Rules ───────────────────────────────────────────────────────────\n\n get rules(): readonly Rule[] {\n return this.#rules\n }\n\n /** Rules whose scope has no registered handler — surfaces config\n * errors (typos, unknown scopes, malformed pattern strings). The TUI\n * can render these as a startup warning. */\n get invalidRules(): readonly Rule[] {\n return this.#rules.filter((r) => !handlerRegistry.has(r.scope))\n }\n\n addRule(rule: Rule): void {\n this.#rules.unshift(rule)\n }\n\n // ── Dispatch ────────────────────────────────────────────────────────\n\n validate(scope: string, input: string): CheckResult {\n if (!handlerRegistry.has(scope)) {\n throw new Error(`no permission handler registered for scope \"${scope}\"`)\n }\n const handler = handlerRegistry.load(scope)\n const rules = this.#rules.filter((r) => r.scope === scope)\n return handler.validate(input, {\n cwd: this.cwd,\n rules,\n scope,\n validate: this.validate.bind(this),\n workspaces: this.#workspaces,\n })\n }\n}\n\n/** Parse Claude-Code-style permission config (a verdict-keyed map of\n * pattern strings) into structured `Rule<string>[]`.\n *\n * Pattern shapes accepted:\n * \"Bash(ls:*)\" → { scope: \"bash\", pattern: \"ls:*\" }\n * \"WebSearch\" → { scope: \"websearch\", pattern: \"*\" } // bare name = scope-wide\n *\n * Scopes are normalized to lower-case so config can use either\n * TitleCase (\"Bash\") or lowercase (\"bash\") interchangeably.\n *\n * Malformed entries are pushed verbatim so `manager.invalidRules`\n * surfaces them — typos in the scope (\"Bahs(...)\") are caught the same\n * way (no handler registered → flagged invalid). */\nexport function parseRules(rules: Partial<Record<Verdict, string[] | undefined>>): Rule[] {\n const ret: Rule[] = []\n for (const policy of Object.keys(rules) as Verdict[]) {\n const patterns = rules[policy]\n if (!Array.isArray(patterns)) continue\n for (const raw of patterns) {\n const m = raw.match(/^(\\w+)(?:\\((.+)\\))?$/)\n if (m) {\n const [, scope, pat = \"*\"] = m\n ret.push({ pattern: pat, policy, scope: scope.toLowerCase() })\n } else {\n // Truly malformed — preserve verbatim so invalidRules surfaces it.\n ret.push({ pattern: raw, policy, scope: \"invalid\" })\n }\n }\n }\n return ret\n}\n"],"mappings":";;;;AA2BA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;AAKA,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAM,cAAc,CAAC,cAAc;;;;AAKnC,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAKA,MAAa,oBAAoB;CAC/B,QAAQ;EACN,aAAa;EACb,OAAO;GACL,MAAM;GAEN,KAAK;IAAC;IAAW;IAAW;GAAU;EACxC;CACF;CACA,UAAU;EACR,aACE;EAEF,OAAO;GACL,MAAM;GACN,OAAO;IAAC,GAAG;IAAgB,GAAG;IAAe,GAAG;IAAc,GAAG;GAAS;GAC1E,KAAK,CAAC,WAAW,UAAU;EAC7B;CACF;CACA,YAAY;EACV,aACE;EAEF,OAAO;GACL,MAAM;GACN,OAAO;IACL;IACA,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IAGH;GACF;EACF;CACF;CACA,MAAM;EACJ,aAAa;EACb,OAAO,EACL,OAAO;GAAC;GAAQ;GAAW;GAAY;EAAS,EAClD;CACF;AACF;;;;;;;;;;;;;AChKA,IAAa,oBAAb,MAA+B;CAC7B;CACA,SAAiB,CAAC;CAClB;CAEA,YAAY,MAA0B;EACpC,KAAK,MAAM,SAAS,MAAM,GAAG;EAC7B,IAAI,MAAM,OAAO;GACf,MAAM,OAAO,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,WAAW,KAAK,KAAK;GAC3E,KAAKA,OAAO,KAAK,GAAG,IAAI;EAC1B;EACA,IAAI,MAAM,QAAQ,KAAKA,OAAO,KAAK,GAAG,WAAW,kBAAkB,KAAK,OAAO,CAAC,KAAK,CAAC;EACtF,KAAKC,eAAe,MAAM,cAAc,CAAC,EAAA,CAAG,KAAK,MAAM,SAAS,KAAK,KAAK,CAAC,CAAC;EAG5E,IAAI,CAAC,KAAKA,YAAY,SAAS,KAAK,GAAG,GAAG,KAAKA,YAAY,QAAQ,KAAK,GAAG;CAC7E;CAIA,IAAI,aAAgC;EAClC,OAAO,KAAKA;CACd;CAEA,aAAa,MAAoB;EAC/B,MAAM,MAAM,SAAS,KAAK,KAAK,IAAI;EACnC,IAAI,CAAC,KAAKA,YAAY,SAAS,GAAG,GAAG,KAAKA,YAAY,KAAK,GAAG;CAChE;CAEA,gBAAgB,MAAoB;EAClC,MAAM,MAAM,SAAS,KAAK,KAAK,IAAI;EACnC,MAAM,IAAI,KAAKA,YAAY,QAAQ,GAAG;EACtC,IAAI,MAAM,IAAI,KAAKA,YAAY,OAAO,GAAG,CAAC;CAC5C;CAIA,IAAI,QAAyB;EAC3B,OAAO,KAAKD;CACd;;;;CAKA,IAAI,eAAgC;EAClC,OAAO,KAAKA,OAAO,QAAQ,MAAM,CAAC,gBAAgB,IAAI,EAAE,KAAK,CAAC;CAChE;CAEA,QAAQ,MAAkB;EACxB,KAAKA,OAAO,QAAQ,IAAI;CAC1B;CAIA,SAAS,OAAe,OAA4B;EAClD,IAAI,CAAC,gBAAgB,IAAI,KAAK,GAC5B,MAAM,IAAI,MAAM,+CAA+C,MAAM,EAAE;EAEzE,MAAM,UAAU,gBAAgB,KAAK,KAAK;EAC1C,MAAM,QAAQ,KAAKA,OAAO,QAAQ,MAAM,EAAE,UAAU,KAAK;EACzD,OAAO,QAAQ,SAAS,OAAO;GAC7B,KAAK,KAAK;GACV;GACA;GACA,UAAU,KAAK,SAAS,KAAK,IAAI;GACjC,YAAY,KAAKC;EACnB,CAAC;CACH;AACF;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,OAA+D;CACxF,MAAM,MAAc,CAAC;CACrB,KAAK,MAAM,UAAU,OAAO,KAAK,KAAK,GAAgB;EACpD,MAAM,WAAW,MAAM;EACvB,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;EAC9B,KAAK,MAAM,OAAO,UAAU;GAC1B,MAAM,IAAI,IAAI,MAAM,sBAAsB;GAC1C,IAAI,GAAG;IACL,MAAM,GAAG,OAAO,MAAM,OAAO;IAC7B,IAAI,KAAK;KAAE,SAAS;KAAK;KAAQ,OAAO,MAAM,YAAY;IAAE,CAAC;GAC/D,OAEE,IAAI,KAAK;IAAE,SAAS;IAAK;IAAQ,OAAO;GAAU,CAAC;EAEvD;CACF;CACA,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry-CFmR5G3J.mjs","names":["resolveRule"],"sources":["../src/permissions/handlers/bash.ts","../../../node_modules/.bun/ignore@7.0.5/node_modules/ignore/index.js","../src/permissions/handlers/files.ts","../src/permissions/handlers/tool.ts","../src/permissions/handlers/registry.ts"],"sourcesContent":["import type { Segment } from \"../../utils/bash/parser.ts\"\nimport type { CheckResult, PermissionHandler, Rule, Suggestion, Verdict } from \"../types.ts\"\n\nimport { parseBash } from \"../../utils/bash/parser.ts\"\nimport { TOOLS } from \"../../utils/bash/tools.ts\"\n\n/**\n * Handler for `Rule<\"bash\">` — evaluates a bash command line against\n * pattern rules, then delegates each segment's inferred and explicit\n * file paths to the file handler via `ctx.validate(\"read\"|\"write\", …)`.\n *\n * Composition keeps file-rule logic in one place: the bash handler\n * doesn't know about workspaces, sensitive files, or path-pattern\n * matching — it just asks the registered handler for those scopes.\n *\n * Sources of file paths per segment:\n * - `seg.reads` / `seg.writes` — explicit `<` / `>` / `>>` redirects\n * extracted by the parser.\n * - `TOOLS[cmd].reads/writes(args)` — inferred from built-in tool\n * specs (e.g. `cat foo.txt` reads `foo.txt`, `tee out` writes `out`).\n *\n * Suggestions and reasons from inner `validate` calls bubble up so the\n * prompt UI can offer \"allow `Bash(cat:*)`\" alongside \"add ~/Documents\n * as workspace\" in a single ask.\n */\nexport const bashHandler: PermissionHandler<\"bash\"> = {\n validate(input, ctx): CheckResult {\n const parsed = parseBash(input)\n if (!parsed.ok) {\n return {\n ask: `Allow this bash command? (${parsed.reason})`,\n reason: parsed.reason,\n verdict: \"ask\",\n }\n }\n\n let verdict: Verdict = \"allow\"\n let reason: string | undefined\n const suggestions: Suggestion[] = []\n\n for (const seg of parsed.segments) {\n // Bash rule match for the command itself. Resolve this before the\n // conservative safety checks so explicit allow-all presets like yolo can\n // mean exactly that instead of degrading to an unimplemented ask prompt.\n const cmdVerdict = resolveRules(ctx.rules, seg) ?? \"ask\"\n if (seg.hasCommandSubst && cmdVerdict !== \"allow\") {\n return {\n ask: `Allow command substitution in \\`${seg.cmd}\\`?`,\n reason: \"command substitution not allowed\",\n verdict: \"ask\",\n }\n }\n const spec = TOOLS[seg.cmd]\n if (spec?.unsafe?.(seg.args) && cmdVerdict !== \"allow\") {\n return {\n ask: `Allow invocation of \\`${seg.cmd}\\`? (unsafe)`,\n reason: `${seg.cmd}: invocation mode not safely modelled`,\n verdict: \"ask\",\n }\n }\n\n let segVerdict: Verdict = cmdVerdict\n let segReason: string | undefined =\n cmdVerdict === \"allow\" ? undefined : `segment \"${seg.cmd}\" → ${cmdVerdict}`\n if (cmdVerdict === \"ask\") {\n suggestions.push({\n kind: \"rule\",\n pattern: seg.args.length > 0 ? `${seg.cmd}:*` : seg.cmd,\n scope: \"bash\",\n })\n }\n\n // Delegate file paths to the file handler. Reads first, then\n // writes; both contribute to this segment's verdict via combine.\n const reads = [...(spec?.reads?.(seg.args) ?? []), ...seg.reads]\n const writes = [...(spec?.writes?.(seg.args) ?? []), ...seg.writes.map((w) => w.path)]\n\n for (const path of reads) {\n if (seg.dyn?.has(path)) {\n segVerdict = combine(segVerdict, \"ask\")\n segReason ??= `${seg.cmd}: read ${path}: dynamic path`\n suggestions.push({\n kind: \"rule\",\n pattern: `${seg.cmd}:*`,\n scope: \"bash\",\n })\n continue\n }\n const r = ctx.validate(\"read\", path)\n segVerdict = combine(segVerdict, r.verdict)\n if (r.verdict !== \"allow\") {\n segReason ??= `${seg.cmd}: read ${path}: ${r.reason}`\n if (r.suggestions) suggestions.push(...r.suggestions)\n }\n if (segVerdict === \"deny\") break\n }\n if (segVerdict !== \"deny\") {\n for (const path of writes) {\n if (seg.dyn?.has(path)) {\n segVerdict = combine(segVerdict, \"ask\")\n segReason ??= `${seg.cmd}: write ${path}: dynamic path`\n suggestions.push({\n kind: \"rule\",\n pattern: `${seg.cmd}:*`,\n scope: \"bash\",\n })\n continue\n }\n const w = ctx.validate(\"write\", path)\n segVerdict = combine(segVerdict, w.verdict)\n if (w.verdict !== \"allow\") {\n segReason ??= `${seg.cmd}: write ${path}: ${w.reason}`\n if (w.suggestions) suggestions.push(...w.suggestions)\n }\n if (segVerdict === \"deny\") break\n }\n }\n\n verdict = combine(verdict, segVerdict)\n if (verdict !== \"allow\" && reason === undefined) reason = segReason\n if (verdict === \"deny\") break\n }\n\n return verdict === \"allow\"\n ? { verdict: \"allow\" }\n : {\n ask: `Allow this bash command?`,\n reason: reason ?? \"no rule matched\",\n suggestions: suggestions.length > 0 ? suggestions : undefined,\n verdict,\n }\n },\n}\n\n// ── Pattern matching ─────────────────────────────────────────────────────\n\n/** A command-pattern rule.\n *\n * Pattern syntax:\n * \"cmd\" → cmd alone, no args\n * \"cmd:*\" → cmd with any args (trailing wildcard)\n * \"cmd a\" → cmd with arg \"a\", no other args\n * \"cmd a:*\" → cmd with arg \"a\" plus any others\n * \"cmd a b\" → cmd with exact args [a, b]\n * \"cmd --flag*\" → cmd with arg starting with \"--flag\" (per-arg prefix)\n * \"bun test:node:*\" → cmd \"bun\", arg \"test:node\", any others\n *\n * Whitespace separates the cmd from its args, and args from each\n * other. The `:` character is only meaningful as the trailing `:*`\n * wildcard suffix — anywhere else (including inside an arg like\n * `test:node`) it's a literal character. */\n\ninterface ParsedPattern {\n cmd: string\n /** Arg-position match tokens. Each may end with `*` for prefix match. */\n parts: string[]\n /** Set when the pattern ends with `:*`, allowing arbitrary further args. */\n trailingWildcard: boolean\n}\n\n/** Combine two verdicts: deny dominates, ask beats allow. */\nfunction combine(a: Verdict, b: Verdict): Verdict {\n if (a === \"deny\" || b === \"deny\") return \"deny\"\n if (a === \"ask\" || b === \"ask\") return \"ask\"\n return \"allow\"\n}\n\nconst patternCache = new Map<string, ParsedPattern>()\n\nfunction parsePattern(pattern: string): ParsedPattern {\n const cached = patternCache.get(pattern)\n if (cached) return cached\n\n let body = pattern.trim()\n let trailingWildcard = false\n if (body.endsWith(\":*\")) {\n trailingWildcard = true\n body = body.slice(0, -2)\n }\n\n const tokens = body.split(/\\s+/).filter((s) => s.length > 0)\n const cmd = tokens[0] ?? \"\"\n const result: ParsedPattern = {\n cmd,\n parts: tokens.slice(1),\n trailingWildcard,\n }\n patternCache.set(pattern, result)\n return result\n}\n\nfunction matchPart(part: string, value: string): boolean {\n if (part.endsWith(\"*\")) {\n return value.startsWith(part.slice(0, -1))\n }\n return part === value\n}\n\n/** Test whether a parsed segment matches a rule pattern. */\nexport function matchRule(rule: Rule, seg: Segment): boolean {\n const p = parsePattern(rule.pattern)\n // Bare `*` (e.g. from a `Bash` rule with no parens) matches any command.\n if (p.cmd === \"*\" && p.parts.length === 0) return true\n if (p.cmd !== seg.cmd) return false\n if (seg.args.length < p.parts.length) return false\n for (let i = 0; i < p.parts.length; i++) {\n if (!matchPart(p.parts[i], seg.args[i])) return false\n }\n if (p.trailingWildcard) return true\n return seg.args.length === p.parts.length\n}\n\n/** First-match-wins resolution. Returns `undefined` if no rule matches. */\nfunction resolveRules(rules: readonly Rule[], seg: Segment): Verdict | undefined {\n for (const r of rules) {\n if (matchRule(r, seg)) return r.policy\n }\n return undefined\n}\n","// A simple implementation of make-array\nfunction makeArray (subject) {\n return Array.isArray(subject)\n ? subject\n : [subject]\n}\n\nconst UNDEFINED = undefined\nconst EMPTY = ''\nconst SPACE = ' '\nconst ESCAPE = '\\\\'\nconst REGEX_TEST_BLANK_LINE = /^\\s+$/\nconst REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\\\]|^)\\\\$/\nconst REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\\\!/\nconst REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\\\#/\nconst REGEX_SPLITALL_CRLF = /\\r?\\n/g\n\n// Invalid:\n// - /foo,\n// - ./foo,\n// - ../foo,\n// - .\n// - ..\n// Valid:\n// - .foo\nconst REGEX_TEST_INVALID_PATH = /^\\.{0,2}\\/|^\\.{1,2}$/\n\nconst REGEX_TEST_TRAILING_SLASH = /\\/$/\n\nconst SLASH = '/'\n\n// Do not use ternary expression here, since \"istanbul ignore next\" is buggy\nlet TMP_KEY_IGNORE = 'node-ignore'\n/* istanbul ignore else */\nif (typeof Symbol !== 'undefined') {\n TMP_KEY_IGNORE = Symbol.for('node-ignore')\n}\nconst KEY_IGNORE = TMP_KEY_IGNORE\n\nconst define = (object, key, value) => {\n Object.defineProperty(object, key, {value})\n return value\n}\n\nconst REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g\n\nconst RETURN_FALSE = () => false\n\n// Sanitize the range of a regular expression\n// The cases are complicated, see test cases for details\nconst sanitizeRange = range => range.replace(\n REGEX_REGEXP_RANGE,\n (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)\n ? match\n // Invalid range (out of order) which is ok for gitignore rules but\n // fatal for JavaScript regular expression, so eliminate it.\n : EMPTY\n)\n\n// See fixtures #59\nconst cleanRangeBackSlash = slashes => {\n const {length} = slashes\n return slashes.slice(0, length - length % 2)\n}\n\n// > If the pattern ends with a slash,\n// > it is removed for the purpose of the following description,\n// > but it would only find a match with a directory.\n// > In other words, foo/ will match a directory foo and paths underneath it,\n// > but will not match a regular file or a symbolic link foo\n// > (this is consistent with the way how pathspec works in general in Git).\n// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'\n// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call\n// you could use option `mark: true` with `glob`\n\n// '`foo/`' should not continue with the '`..`'\nconst REPLACERS = [\n\n [\n // Remove BOM\n // TODO:\n // Other similar zero-width characters?\n /^\\uFEFF/,\n () => EMPTY\n ],\n\n // > Trailing spaces are ignored unless they are quoted with backslash (\"\\\")\n [\n // (a\\ ) -> (a )\n // (a ) -> (a)\n // (a ) -> (a)\n // (a \\ ) -> (a )\n /((?:\\\\\\\\)*?)(\\\\?\\s+)$/,\n (_, m1, m2) => m1 + (\n m2.indexOf('\\\\') === 0\n ? SPACE\n : EMPTY\n )\n ],\n\n // Replace (\\ ) with ' '\n // (\\ ) -> ' '\n // (\\\\ ) -> '\\\\ '\n // (\\\\\\ ) -> '\\\\ '\n [\n /(\\\\+?)\\s/g,\n (_, m1) => {\n const {length} = m1\n return m1.slice(0, length - length % 2) + SPACE\n }\n ],\n\n // Escape metacharacters\n // which is written down by users but means special for regular expressions.\n\n // > There are 12 characters with special meanings:\n // > - the backslash \\,\n // > - the caret ^,\n // > - the dollar sign $,\n // > - the period or dot .,\n // > - the vertical bar or pipe symbol |,\n // > - the question mark ?,\n // > - the asterisk or star *,\n // > - the plus sign +,\n // > - the opening parenthesis (,\n // > - the closing parenthesis ),\n // > - and the opening square bracket [,\n // > - the opening curly brace {,\n // > These special characters are often called \"metacharacters\".\n [\n /[\\\\$.|*+(){^]/g,\n match => `\\\\${match}`\n ],\n\n [\n // > a question mark (?) matches a single character\n /(?!\\\\)\\?/g,\n () => '[^/]'\n ],\n\n // leading slash\n [\n\n // > A leading slash matches the beginning of the pathname.\n // > For example, \"/*.c\" matches \"cat-file.c\" but not \"mozilla-sha1/sha1.c\".\n // A leading slash matches the beginning of the pathname\n /^\\//,\n () => '^'\n ],\n\n // replace special metacharacter slash after the leading slash\n [\n /\\//g,\n () => '\\\\/'\n ],\n\n [\n // > A leading \"**\" followed by a slash means match in all directories.\n // > For example, \"**/foo\" matches file or directory \"foo\" anywhere,\n // > the same as pattern \"foo\".\n // > \"**/foo/bar\" matches file or directory \"bar\" anywhere that is directly\n // > under directory \"foo\".\n // Notice that the '*'s have been replaced as '\\\\*'\n /^\\^*\\\\\\*\\\\\\*\\\\\\//,\n\n // '**/foo' <-> 'foo'\n () => '^(?:.*\\\\/)?'\n ],\n\n // starting\n [\n // there will be no leading '/'\n // (which has been replaced by section \"leading slash\")\n // If starts with '**', adding a '^' to the regular expression also works\n /^(?=[^^])/,\n function startingReplacer () {\n // If has a slash `/` at the beginning or middle\n return !/\\/(?!$)/.test(this)\n // > Prior to 2.22.1\n // > If the pattern does not contain a slash /,\n // > Git treats it as a shell glob pattern\n // Actually, if there is only a trailing slash,\n // git also treats it as a shell glob pattern\n\n // After 2.22.1 (compatible but clearer)\n // > If there is a separator at the beginning or middle (or both)\n // > of the pattern, then the pattern is relative to the directory\n // > level of the particular .gitignore file itself.\n // > Otherwise the pattern may also match at any level below\n // > the .gitignore level.\n ? '(?:^|\\\\/)'\n\n // > Otherwise, Git treats the pattern as a shell glob suitable for\n // > consumption by fnmatch(3)\n : '^'\n }\n ],\n\n // two globstars\n [\n // Use lookahead assertions so that we could match more than one `'/**'`\n /\\\\\\/\\\\\\*\\\\\\*(?=\\\\\\/|$)/g,\n\n // Zero, one or several directories\n // should not use '*', or it will be replaced by the next replacer\n\n // Check if it is not the last `'/**'`\n (_, index, str) => index + 6 < str.length\n\n // case: /**/\n // > A slash followed by two consecutive asterisks then a slash matches\n // > zero or more directories.\n // > For example, \"a/**/b\" matches \"a/b\", \"a/x/b\", \"a/x/y/b\" and so on.\n // '/**/'\n ? '(?:\\\\/[^\\\\/]+)*'\n\n // case: /**\n // > A trailing `\"/**\"` matches everything inside.\n\n // #21: everything inside but it should not include the current folder\n : '\\\\/.+'\n ],\n\n // normal intermediate wildcards\n [\n // Never replace escaped '*'\n // ignore rule '\\*' will match the path '*'\n\n // 'abc.*/' -> go\n // 'abc.*' -> skip this rule,\n // coz trailing single wildcard will be handed by [trailing wildcard]\n /(^|[^\\\\]+)(\\\\\\*)+(?=.+)/g,\n\n // '*.js' matches '.js'\n // '*.js' doesn't match 'abc'\n (_, p1, p2) => {\n // 1.\n // > An asterisk \"*\" matches anything except a slash.\n // 2.\n // > Other consecutive asterisks are considered regular asterisks\n // > and will match according to the previous rules.\n const unescaped = p2.replace(/\\\\\\*/g, '[^\\\\/]*')\n return p1 + unescaped\n }\n ],\n\n [\n // unescape, revert step 3 except for back slash\n // For example, if a user escape a '\\\\*',\n // after step 3, the result will be '\\\\\\\\\\\\*'\n /\\\\\\\\\\\\(?=[$.|*+(){^])/g,\n () => ESCAPE\n ],\n\n [\n // '\\\\\\\\' -> '\\\\'\n /\\\\\\\\/g,\n () => ESCAPE\n ],\n\n [\n // > The range notation, e.g. [a-zA-Z],\n // > can be used to match one of the characters in a range.\n\n // `\\` is escaped by step 3\n /(\\\\)?\\[([^\\]/]*?)(\\\\*)($|\\])/g,\n (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE\n // '\\\\[bar]' -> '\\\\\\\\[bar\\\\]'\n ? `\\\\[${range}${cleanRangeBackSlash(endEscape)}${close}`\n : close === ']'\n ? endEscape.length % 2 === 0\n // A normal case, and it is a range notation\n // '[bar]'\n // '[bar\\\\\\\\]'\n ? `[${sanitizeRange(range)}${endEscape}]`\n // Invalid range notaton\n // '[bar\\\\]' -> '[bar\\\\\\\\]'\n : '[]'\n : '[]'\n ],\n\n // ending\n [\n // 'js' will not match 'js.'\n // 'ab' will not match 'abc'\n /(?:[^*])$/,\n\n // WTF!\n // https://git-scm.com/docs/gitignore\n // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)\n // which re-fixes #24, #38\n\n // > If there is a separator at the end of the pattern then the pattern\n // > will only match directories, otherwise the pattern can match both\n // > files and directories.\n\n // 'js*' will not match 'a.js'\n // 'js/' will not match 'a.js'\n // 'js' will match 'a.js' and 'a.js/'\n match => /\\/$/.test(match)\n // foo/ will not match 'foo'\n ? `${match}$`\n // foo matches 'foo' and 'foo/'\n : `${match}(?=$|\\\\/$)`\n ]\n]\n\nconst REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\\\\/)?\\\\\\*$/\nconst MODE_IGNORE = 'regex'\nconst MODE_CHECK_IGNORE = 'checkRegex'\nconst UNDERSCORE = '_'\n\nconst TRAILING_WILD_CARD_REPLACERS = {\n [MODE_IGNORE] (_, p1) {\n const prefix = p1\n // '\\^':\n // '/*' does not match EMPTY\n // '/*' does not match everything\n\n // '\\\\\\/':\n // 'abc/*' does not match 'abc/'\n ? `${p1}[^/]+`\n\n // 'a*' matches 'a'\n // 'a*' matches 'aa'\n : '[^/]*'\n\n return `${prefix}(?=$|\\\\/$)`\n },\n\n [MODE_CHECK_IGNORE] (_, p1) {\n // When doing `git check-ignore`\n const prefix = p1\n // '\\\\\\/':\n // 'abc/*' DOES match 'abc/' !\n ? `${p1}[^/]*`\n\n // 'a*' matches 'a'\n // 'a*' matches 'aa'\n : '[^/]*'\n\n return `${prefix}(?=$|\\\\/$)`\n }\n}\n\n// @param {pattern}\nconst makeRegexPrefix = pattern => REPLACERS.reduce(\n (prev, [matcher, replacer]) =>\n prev.replace(matcher, replacer.bind(pattern)),\n pattern\n)\n\nconst isString = subject => typeof subject === 'string'\n\n// > A blank line matches no files, so it can serve as a separator for readability.\nconst checkPattern = pattern => pattern\n && isString(pattern)\n && !REGEX_TEST_BLANK_LINE.test(pattern)\n && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)\n\n // > A line starting with # serves as a comment.\n && pattern.indexOf('#') !== 0\n\nconst splitPattern = pattern => pattern\n.split(REGEX_SPLITALL_CRLF)\n.filter(Boolean)\n\nclass IgnoreRule {\n constructor (\n pattern,\n mark,\n body,\n ignoreCase,\n negative,\n prefix\n ) {\n this.pattern = pattern\n this.mark = mark\n this.negative = negative\n\n define(this, 'body', body)\n define(this, 'ignoreCase', ignoreCase)\n define(this, 'regexPrefix', prefix)\n }\n\n get regex () {\n const key = UNDERSCORE + MODE_IGNORE\n\n if (this[key]) {\n return this[key]\n }\n\n return this._make(MODE_IGNORE, key)\n }\n\n get checkRegex () {\n const key = UNDERSCORE + MODE_CHECK_IGNORE\n\n if (this[key]) {\n return this[key]\n }\n\n return this._make(MODE_CHECK_IGNORE, key)\n }\n\n _make (mode, key) {\n const str = this.regexPrefix.replace(\n REGEX_REPLACE_TRAILING_WILDCARD,\n\n // It does not need to bind pattern\n TRAILING_WILD_CARD_REPLACERS[mode]\n )\n\n const regex = this.ignoreCase\n ? new RegExp(str, 'i')\n : new RegExp(str)\n\n return define(this, key, regex)\n }\n}\n\nconst createRule = ({\n pattern,\n mark\n}, ignoreCase) => {\n let negative = false\n let body = pattern\n\n // > An optional prefix \"!\" which negates the pattern;\n if (body.indexOf('!') === 0) {\n negative = true\n body = body.substr(1)\n }\n\n body = body\n // > Put a backslash (\"\\\") in front of the first \"!\" for patterns that\n // > begin with a literal \"!\", for example, `\"\\!important!.txt\"`.\n .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')\n // > Put a backslash (\"\\\") in front of the first hash for patterns that\n // > begin with a hash.\n .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#')\n\n const regexPrefix = makeRegexPrefix(body)\n\n return new IgnoreRule(\n pattern,\n mark,\n body,\n ignoreCase,\n negative,\n regexPrefix\n )\n}\n\nclass RuleManager {\n constructor (ignoreCase) {\n this._ignoreCase = ignoreCase\n this._rules = []\n }\n\n _add (pattern) {\n // #32\n if (pattern && pattern[KEY_IGNORE]) {\n this._rules = this._rules.concat(pattern._rules._rules)\n this._added = true\n return\n }\n\n if (isString(pattern)) {\n pattern = {\n pattern\n }\n }\n\n if (checkPattern(pattern.pattern)) {\n const rule = createRule(pattern, this._ignoreCase)\n this._added = true\n this._rules.push(rule)\n }\n }\n\n // @param {Array<string> | string | Ignore} pattern\n add (pattern) {\n this._added = false\n\n makeArray(\n isString(pattern)\n ? splitPattern(pattern)\n : pattern\n ).forEach(this._add, this)\n\n return this._added\n }\n\n // Test one single path without recursively checking parent directories\n //\n // - checkUnignored `boolean` whether should check if the path is unignored,\n // setting `checkUnignored` to `false` could reduce additional\n // path matching.\n // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`\n\n // @returns {TestResult} true if a file is ignored\n test (path, checkUnignored, mode) {\n let ignored = false\n let unignored = false\n let matchedRule\n\n this._rules.forEach(rule => {\n const {negative} = rule\n\n // | ignored : unignored\n // -------- | ---------------------------------------\n // negative | 0:0 | 0:1 | 1:0 | 1:1\n // -------- | ------- | ------- | ------- | --------\n // 0 | TEST | TEST | SKIP | X\n // 1 | TESTIF | SKIP | TEST | X\n\n // - SKIP: always skip\n // - TEST: always test\n // - TESTIF: only test if checkUnignored\n // - X: that never happen\n if (\n unignored === negative && ignored !== unignored\n || negative && !ignored && !unignored && !checkUnignored\n ) {\n return\n }\n\n const matched = rule[mode].test(path)\n\n if (!matched) {\n return\n }\n\n ignored = !negative\n unignored = negative\n\n matchedRule = negative\n ? UNDEFINED\n : rule\n })\n\n const ret = {\n ignored,\n unignored\n }\n\n if (matchedRule) {\n ret.rule = matchedRule\n }\n\n return ret\n }\n}\n\nconst throwError = (message, Ctor) => {\n throw new Ctor(message)\n}\n\nconst checkPath = (path, originalPath, doThrow) => {\n if (!isString(path)) {\n return doThrow(\n `path must be a string, but got \\`${originalPath}\\``,\n TypeError\n )\n }\n\n // We don't know if we should ignore EMPTY, so throw\n if (!path) {\n return doThrow(`path must not be empty`, TypeError)\n }\n\n // Check if it is a relative path\n if (checkPath.isNotRelative(path)) {\n const r = '`path.relative()`d'\n return doThrow(\n `path should be a ${r} string, but got \"${originalPath}\"`,\n RangeError\n )\n }\n\n return true\n}\n\nconst isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path)\n\ncheckPath.isNotRelative = isNotRelative\n\n// On windows, the following function will be replaced\n/* istanbul ignore next */\ncheckPath.convert = p => p\n\n\nclass Ignore {\n constructor ({\n ignorecase = true,\n ignoreCase = ignorecase,\n allowRelativePaths = false\n } = {}) {\n define(this, KEY_IGNORE, true)\n\n this._rules = new RuleManager(ignoreCase)\n this._strictPathCheck = !allowRelativePaths\n this._initCache()\n }\n\n _initCache () {\n // A cache for the result of `.ignores()`\n this._ignoreCache = Object.create(null)\n\n // A cache for the result of `.test()`\n this._testCache = Object.create(null)\n }\n\n add (pattern) {\n if (this._rules.add(pattern)) {\n // Some rules have just added to the ignore,\n // making the behavior changed,\n // so we need to re-initialize the result cache\n this._initCache()\n }\n\n return this\n }\n\n // legacy\n addPattern (pattern) {\n return this.add(pattern)\n }\n\n // @returns {TestResult}\n _test (originalPath, cache, checkUnignored, slices) {\n const path = originalPath\n // Supports nullable path\n && checkPath.convert(originalPath)\n\n checkPath(\n path,\n originalPath,\n this._strictPathCheck\n ? throwError\n : RETURN_FALSE\n )\n\n return this._t(path, cache, checkUnignored, slices)\n }\n\n checkIgnore (path) {\n // If the path doest not end with a slash, `.ignores()` is much equivalent\n // to `git check-ignore`\n if (!REGEX_TEST_TRAILING_SLASH.test(path)) {\n return this.test(path)\n }\n\n const slices = path.split(SLASH).filter(Boolean)\n slices.pop()\n\n if (slices.length) {\n const parent = this._t(\n slices.join(SLASH) + SLASH,\n this._testCache,\n true,\n slices\n )\n\n if (parent.ignored) {\n return parent\n }\n }\n\n return this._rules.test(path, false, MODE_CHECK_IGNORE)\n }\n\n _t (\n // The path to be tested\n path,\n\n // The cache for the result of a certain checking\n cache,\n\n // Whether should check if the path is unignored\n checkUnignored,\n\n // The path slices\n slices\n ) {\n if (path in cache) {\n return cache[path]\n }\n\n if (!slices) {\n // path/to/a.js\n // ['path', 'to', 'a.js']\n slices = path.split(SLASH).filter(Boolean)\n }\n\n slices.pop()\n\n // If the path has no parent directory, just test it\n if (!slices.length) {\n return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE)\n }\n\n const parent = this._t(\n slices.join(SLASH) + SLASH,\n cache,\n checkUnignored,\n slices\n )\n\n // If the path contains a parent directory, check the parent first\n return cache[path] = parent.ignored\n // > It is not possible to re-include a file if a parent directory of\n // > that file is excluded.\n ? parent\n : this._rules.test(path, checkUnignored, MODE_IGNORE)\n }\n\n ignores (path) {\n return this._test(path, this._ignoreCache, false).ignored\n }\n\n createFilter () {\n return path => !this.ignores(path)\n }\n\n filter (paths) {\n return makeArray(paths).filter(this.createFilter())\n }\n\n // @returns {TestResult}\n test (path) {\n return this._test(path, this._testCache, true)\n }\n}\n\nconst factory = options => new Ignore(options)\n\nconst isPathValid = path =>\n checkPath(path && checkPath.convert(path), path, RETURN_FALSE)\n\n/* istanbul ignore next */\nconst setupWindows = () => {\n /* eslint no-control-regex: \"off\" */\n const makePosix = str => /^\\\\\\\\\\?\\\\/.test(str)\n || /[\"<>|\\u0000-\\u001F]+/u.test(str)\n ? str\n : str.replace(/\\\\/g, '/')\n\n checkPath.convert = makePosix\n\n // 'C:\\\\foo' <- 'C:\\\\foo' has been converted to 'C:/'\n // 'd:\\\\foo'\n const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\\//i\n checkPath.isNotRelative = path =>\n REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path)\n || isNotRelative(path)\n}\n\n\n// Windows\n// --------------------------------------------------------------\n/* istanbul ignore next */\nif (\n // Detect `process` so that it can run in browsers.\n typeof process !== 'undefined'\n && process.platform === 'win32'\n) {\n setupWindows()\n}\n\n// COMMONJS_EXPORTS ////////////////////////////////////////////////////////////\n\nmodule.exports = factory\n\n// Although it is an anti-pattern,\n// it is still widely misused by a lot of libraries in github\n// Ref: https://github.com/search?q=ignore.default%28%29&type=code\nfactory.default = factory\n\nmodule.exports.isPathValid = isPathValid\n\n// For testing purposes\ndefine(module.exports, Symbol.for('setupWindows'), setupWindows)\n","import type { CheckResult, PermissionHandler, Rule } from \"../types.ts\"\n\nimport { normPath, prettyPath } from \"@zaly/shared\"\nimport ignore from \"ignore\"\nimport { dirname, isAbsolute, relative } from \"pathe\"\n\n/**\n * Handler for `Rule<\"read\" | \"write\">` — one impl serves both scopes\n * because they share the same matcher and the same workspace state.\n *\n * Algorithm:\n * 1. Normalize candidate to an absolute path.\n * 2. Sensitive-file check (env, ssh keys, credentials) → deny.\n * 3. Find the longest-prefix workspace containing the path. None →\n * ask, with a `kind: \"workspace\"` suggestion for the containing\n * directory.\n * 4. Inside a workspace: resolve the first matching rule against the\n * workspace-relative path. Earlier rules win.\n * 5. Default when no rule matches but the path is inside a workspace:\n * `allow` for reads (workspace = trust signal), `ask` for writes\n * (mutating ops escalate by default; users add `Write(...)` rules\n * to whitelist).\n *\n * Pattern grammar (per Claude-Code conventions):\n * //abs/path absolute filesystem path\n * ~/path path under home directory\n * /path or path workspace-relative (passed through to gitignore)\n */\nexport const fileHandler: PermissionHandler<\"read\" | \"write\"> = {\n validate(input, ctx): CheckResult {\n const abs = normPath(ctx.cwd, input)\n\n if (isSensitiveFile(abs)) {\n return { reason: `${abs}: sensitive file`, verdict: \"deny\" }\n }\n\n const ws = longestWorkspace(abs, ctx.workspaces)\n const inWorkspace = ws !== undefined\n\n // Inside-workspace + path === root: the workspace itself (e.g. `find .`\n // resolved to cwd). Workspace containment alone is enough to allow.\n if (inWorkspace && abs === ws) return { verdict: \"allow\" }\n\n // Outside any workspace, fall through to rule matching with `/` as\n // synthetic root. Lets blanket rules like `read(*)` (yolo preset)\n // override the default \"ask outside workspace\" verdict, while\n // narrower presets keep the safer default.\n const base = ws ?? \"/\"\n const rel = relative(base, abs)\n\n const verb = ctx.scope === \"read\" ? \"reading\" : \"writing\"\n\n const rule = resolveRule(base, rel, ctx.rules)\n if (rule?.policy === \"deny\") return { reason: `${abs}: denied by rule`, verdict: \"deny\" }\n if (rule?.policy === \"ask\") {\n return {\n ask: `Allow ${verb} ${prettyPath(abs)}?`,\n reason: `${abs}: rule requires confirmation`,\n suggestions: [{ kind: \"rule\", pattern: `/${rel}`, scope: ctx.scope }],\n verdict: \"ask\",\n }\n }\n if (rule?.policy === \"allow\") return { verdict: \"allow\" }\n\n // No rule matched. Defaults differ by location:\n // - Inside a workspace: reads ride on workspace trust → allow;\n // writes escalate → ask.\n // - Outside any workspace: always ask, and surface a\n // workspace-add suggestion so the user can promote the parent\n // dir without composing a rule by hand.\n if (!inWorkspace) {\n const parent = dirname(abs)\n return {\n ask: `Allow ${verb} ${prettyPath(abs)}? (not in any workspace)`,\n reason: `${abs}: outside any workspace`,\n suggestions: [\n { description: `add ${parent} as workspace`, kind: \"workspace\", path: parent },\n ],\n verdict: \"ask\",\n }\n }\n if (ctx.scope === \"read\") return { verdict: \"allow\" }\n return {\n ask: `Allow ${verb} ${prettyPath(abs)}?`,\n reason: `${abs}: write requires confirmation`,\n suggestions: [{ kind: \"rule\", pattern: `/${rel}`, scope: \"write\" }],\n verdict: \"ask\",\n }\n },\n}\n\n/** True if `path` looks like a credentials/keys file we shouldn't\n * expose to the model by default. Conservative — false positives are\n * fine here, and users can override via `allow` rules. */\nexport function isSensitiveFile(path: string): boolean {\n return SENSITIVE_PATTERNS.some((re) => re.test(path))\n}\n\n/** Common locations for credentials, keys, and other sensitive data.\n * Matched against the path string directly — covers both absolute and\n * relative inputs. Add to this list as new gotchas surface. */\nconst SENSITIVE_PATTERNS: RegExp[] = [\n /(?:^|\\/)\\.env(?:\\.|$)/, // .env, .env.local, .env.production, ...\n /(?:^|\\/)\\.git\\/(?!hooks\\/)/, // .git/* but not hooks (commonly inspected)\n /(?:^|\\/)\\.ssh\\//, // ssh keys + config\n /(?:^|\\/)\\.aws\\/credentials/,\n /(?:^|\\/)\\.netrc$/,\n /(?:^|\\/)id_(?:rsa|dsa|ed25519|ecdsa)(?:\\.pub)?$/,\n /(?:^|\\/)\\.npmrc$/, // can hold auth tokens\n /(?:^|\\/)\\.pypirc$/,\n /(?:^|\\/)secrets?\\//,\n /(?:^|\\/)credentials?(?:\\.|\\/)/,\n]\n\n/** Longest-prefix match against the workspace list — returns the\n * containing workspace's absolute path, or undefined if none contain\n * the candidate. Workspaces arrive pre-normalized from the manager. */\nfunction longestWorkspace(abs: string, workspaces: readonly string[]): string | undefined {\n let best: string | undefined\n for (const root of workspaces) {\n if (abs === root || abs.startsWith(root.endsWith(\"/\") ? root : `${root}/`)) {\n if (best === undefined || root.length > best.length) best = root\n }\n }\n return best\n}\n\n/** Resolve the first matching rule.\n * Patterns get normalized (`//abs`, `~/`, `/rel`, `rel`) into\n * workspace-rooted gitignore syntax; patterns that resolve outside the\n * candidate's workspace are dropped (they don't apply here). */\nfunction resolveRule(\n workspace: string,\n rel: string,\n rules: readonly Rule<\"read\" | \"write\">[]\n): Rule<\"read\" | \"write\"> | undefined {\n if (rel === \"\") return undefined\n for (const rule of rules) {\n const norm = normalizePattern(rule.pattern, workspace)\n if (norm === undefined) continue\n try {\n if (ignore().add(norm).ignores(rel)) return rule\n } catch {\n // Skip invalid rels.\n }\n }\n}\n\nfunction normalizePattern(pattern: string, workspace: string): string | undefined {\n if (pattern.startsWith(\"//\")) {\n return relativizeToWorkspace(pattern.slice(1), workspace)\n }\n if (pattern.startsWith(\"~/\")) {\n return relativizeToWorkspace(normPath(pattern), workspace)\n }\n // /pattern or pattern → already workspace-relative gitignore syntax.\n return pattern\n}\n\nfunction relativizeToWorkspace(absPattern: string, workspace: string): string | undefined {\n const rel = relative(workspace, absPattern)\n if (rel === \"\" || rel.startsWith(\"..\") || isAbsolute(rel)) return undefined\n return `/${rel}`\n}\n","import type { CheckResult, PermissionHandler, Rule } from \"../types.ts\"\n\nimport ignore from \"ignore\"\n\n/**\n * Generic tool-scope handler.\n *\n * Used by `Tasks` to gate every tool dispatch on the tool name, and\n * available to tools themselves for richer checks (e.g. fetch passing\n * `fetch:example.com` so users can ask-for-domain).\n *\n * Pattern grammar is gitignore-style — reuses the `ignore` package so\n * the syntax matches `read` / `write` patterns. Common shapes:\n *\n * \"tool\" → bare scope; matches every input (default-allow)\n * \"tool(bash)\" → exact match for `\"bash\"`\n * \"tool(fetch:*)\" → glob match `\"fetch:...\"`\n * \"tool(task_*)\" → glob match `\"task_...\"`\n *\n * Resolution is first-match-wins. Default verdict when no rule matches is\n * `allow` — an unconfigured `tool` scope should not surprise users by\n * gating ordinary tool calls.\n */\nexport const toolHandler: PermissionHandler<\"tool\"> = {\n validate(input, ctx): CheckResult {\n const rule = resolveRule(input, ctx.rules)\n if (rule?.policy === \"deny\") {\n return { reason: `tool ${input}: denied by rule`, verdict: \"deny\" }\n }\n if (rule?.policy === \"ask\") {\n return {\n ask: `Allow tool ${input}?`,\n reason: `tool ${input}: rule requires confirmation`,\n suggestions: [{ kind: \"rule\", pattern: input, scope: \"tool\" }],\n verdict: \"ask\",\n }\n }\n return { verdict: \"allow\" }\n },\n}\n\nfunction resolveRule(input: string, rules: readonly Rule<\"tool\">[]): Rule<\"tool\"> | undefined {\n for (const rule of rules) {\n if (ignore().add(rule.pattern).ignores(input)) return rule\n }\n}\n","import type { PermissionHandler } from \"../types.ts\"\n\nimport { createRegistry } from \"@zaly/shared/registry\"\nimport { bashHandler } from \"./bash.ts\"\nimport { fileHandler } from \"./files.ts\"\nimport { toolHandler } from \"./tool.ts\"\n\nexport type HandlerLoader = () => PermissionHandler<string>\n\nconst builtin = {\n bash: () => bashHandler,\n read: () => fileHandler,\n tool: () => toolHandler,\n write: () => fileHandler,\n} as const satisfies Record<string, HandlerLoader>\n\n/** Open registry of permission scopes — declaration-merge to add your\n * own. The keys give `ctx.need(\"…\", input)` typed autocomplete and\n * catch typos at compile time. The value carries the *input* type for\n * that scope (almost always `string` — files take paths, bash takes\n * commands, the generic `tool` scope takes the tool name).\n *\n * Built-ins:\n * - `bash` → command string\n * - `read` → absolute path\n * - `write` → absolute path\n * - `tool` → tool name (or richer `name:arg` shape per tool)\n *\n * Add your own scope:\n *\n * ```ts\n * declare module \"@zaly/agent\" {\n * interface PermissionScopes {\n * fetch: string // expects a domain (or \"*\" for any)\n * }\n * }\n * ```\n *\n * Then `ctx.need(\"fetch\", domain)` is typed end-to-end. You still need\n * to register a runtime handler via `handlerRegistry.register(\"fetch\", …)`\n * — this interface is only the type-side declaration.\n */\nexport interface PermissionScopes {\n bash: string\n read: string\n tool: string\n write: string\n}\n\nexport type BuiltinScope = keyof typeof builtin\nexport type PermissionScope = keyof PermissionScopes\nexport type AnyScope = PermissionScope | (string & {})\n\nexport const handlerRegistry = createRegistry<HandlerLoader>(\"scope\").from(builtin)\n"],"x_google_ignoreList":[1],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"session-CUUdWFC5.mjs","names":["#nodes","#root","#store","#opts","#path","#id","#dir","#rebuild","#chain","#view","#settings","#started","#commit","#autostart","#head","#closed","#emitChanges","#migrate"],"sources":["../src/session/memory.ts","../src/session/session.ts"],"sourcesContent":["import type { SessionStore } from \"./store.ts\"\nimport type { SessionNode } from \"./types.ts\"\n\n/**\n * In-memory `SessionStore` — a `Map<uuid, SessionNode>` plus a `root`\n * pointer. No persistence. Construction takes optional initial nodes\n * for tests, replay tools, and the Claude session importer.\n */\nexport class MemoryStore implements SessionStore {\n readonly #nodes: Map<string, SessionNode>\n #root?: SessionNode\n\n constructor(nodes?: Iterable<SessionNode>) {\n this.#nodes = new Map()\n if (nodes) {\n for (const node of nodes) {\n this.#nodes.set(node.uuid, node)\n this.#root = node\n }\n }\n }\n\n get root(): SessionNode | undefined {\n return this.#root\n }\n\n async get(id: string): Promise<SessionNode | undefined> {\n return this.#nodes.get(id)\n }\n\n async write(node: SessionNode): Promise<void> {\n this.#nodes.set(node.uuid, node)\n this.#root = node\n }\n\n *all(): Iterable<SessionNode> {\n yield* this.#nodes.values()\n }\n}\n","import type { Message } from \"@zaly/ai\"\nimport type { SessionStore } from \"./store.ts\"\nimport type {\n MaskCheckpoint,\n PartialNode,\n SessionEvents,\n SessionInit,\n SessionMessage,\n SessionNode,\n SessionNodeView,\n SessionOptions,\n SessionSettings,\n SessionUpdate,\n SessionView,\n} from \"./types.ts\"\n\nimport { Emitter, normPath } from \"@zaly/shared\"\nimport { zalyPaths } from \"@zaly/shared/paths\"\nimport { isDeepStrictEqual } from \"node:util\"\nimport { join } from \"pathe\"\nimport { uuidv7 } from \"../utils/uuid.ts\"\nimport { JsonlStore } from \"./jsonl.ts\"\nimport { MemoryStore } from \"./memory.ts\"\n\nconst VERSION = 2\n\n/**\n * Conversation primitive. Owns a DAG of message + compaction + meta\n * nodes via a `SessionStore` backend; the *active* message list is\n * whatever you get by walking `parentUuid` backwards from the store's\n * root until you hit a `compact` node (or the start).\n *\n * **Meta is snapshot-based.** `session-meta` nodes carry the full\n * cumulative meta as of their commit time. Other node types (start,\n * resume, message, compact) don't carry meta — `Session` reconstructs\n * the cumulative meta at any point by finding the most recent\n * `session-meta` ancestor in a forward pass over the chain. Public\n * APIs return `SessionNodeView` (raw node + decorated `meta`).\n *\n * Designed so the loop, the persistor, the TUI, and any replay tool\n * all read from one source of truth and never mutate the DAG behind\n * each other's backs:\n *\n * - **Run loop** (`Agent`) calls `add()` on each new message and\n * `compact()` on overflow.\n * - **TUI** subscribes to the `node` event for streaming render.\n * - **Persistor** is the `SessionStore` — `MemoryStore` for ephemeral\n * sessions, `JsonlStore` for file-backed.\n *\n * Construction:\n * - Use `await Session.load(opts)` for the recommended path — picks\n * the right store (Memory if no path, JSONL if path).\n * - For tests / direct embedding, `new Session({ store })` works with\n * any pre-built store.\n */\nexport class Session<T extends SessionStore = SessionStore> extends Emitter<SessionEvents> {\n readonly #store: T\n #opts: SessionInit<T>\n #id: string\n #dir: string\n #settings: SessionSettings = {}\n #view: SessionView = { messages: [], nodes: new Map() }\n #closed = false\n #started = false\n #path?: string\n #head?: string\n\n protected constructor(opts: SessionInit<T>) {\n super()\n this.#opts = opts\n this.#path = opts.path\n this.#store = opts.store\n this.#id = opts.defaults?.sessionId ?? uuidv7()\n this.#dir = normPath(opts.dir ?? join(zalyPaths.env.tmp, \"sessions\", this.#id))\n }\n\n /** Recommended one-step path: pick the right store backend, hydrate\n * if applicable, return a ready-to-use session.\n *\n * - `path` provided → `JsonlStore` (file-backed, hydrates from disk\n * if the file exists, opens for append either way).\n * - No `path` → `MemoryStore` (ephemeral, no persistence). */\n static async load<T extends SessionStore = SessionStore>(\n opts: SessionOptions<T> & { store: T }\n ): Promise<Session<T>>\n static async load(opts: SessionOptions & { path: string }): Promise<Session<JsonlStore>>\n static async load(opts?: SessionOptions & {}): Promise<Session<MemoryStore>>\n static async load(opts: SessionOptions = {}): Promise<Session> {\n const session = await Session.create(opts)\n await session.#rebuild()\n return session\n }\n\n private static async create(opts: SessionOptions = {}): Promise<Session> {\n const path = opts.path ? normPath(opts.path) : undefined\n const store: SessionStore =\n opts.store ?? (path ? await JsonlStore.load(path) : new MemoryStore())\n const init: SessionInit = { ...opts, path, store }\n return new Session(init)\n }\n\n /** Efficiently fetch the most recent user message by walking backward until we hit\n * a user message, without reconstructing the full chain. Useful for a session picker. */\n static async lastMessage(opts: SessionOptions = {}): Promise<Message<\"user\"> | undefined> {\n const session = await Session.create(opts)\n let ret: Message<\"user\"> | undefined\n await session.#chain({\n stop: (node) => {\n if (node.type === \"message\" && node.message.role === \"user\") {\n ret = node.message\n return true\n }\n return false\n },\n })\n void session.close()\n return ret\n }\n\n async #rebuild(): Promise<void> {\n const chain = await this.#chain()\n this.#view = chain\n this.#settings = chain.settings\n this.#id = chain.settings.sessionId ?? this.#id\n }\n\n // ── Read ──────────────────────────────────────────────────────────────\n\n /** Active conversation — chain from current head back to the most\n * recent compact (exclusive), in chronological order. This is what\n * the agent sees on the next request. */\n get messages(): readonly Message[] {\n return this.#view.messages\n }\n\n get maskCheckpoint(): MaskCheckpoint | undefined {\n return this.#view.maskCheckpoint\n }\n\n get id(): string {\n return this.#id\n }\n\n get dir(): string {\n return this.#dir\n }\n\n get path(): string | undefined {\n return this.#path\n }\n\n get started(): boolean {\n return this.#started\n }\n\n /** Current session settings, merged with defaults. */\n get settings(): SessionSettings {\n return { ...this.#opts.defaults, ...this.#settings }\n }\n\n async update(settings: SessionUpdate, opts?: { force?: boolean }): Promise<string> {\n return this.#commit({ settings, type: \"session-settings\" }, opts)\n }\n\n /** Lazily emits `session-start` on a fresh session or `session-resume`\n * on a hydrated one. Called from `#commit` before the first non-marker\n * node lands so the start/resume marker always heads the chain. */\n async #autostart(): Promise<void> {\n if (!this.#started) await this.start()\n }\n\n /** Explicitly mark the session as started — writes a `session-start`\n * marker on a fresh session, `session-resume` on a hydrated one.\n * Idempotent; subsequent calls are no-ops. Optional `meta` is\n * applied via `update()` after the marker lands. */\n async start(settings?: SessionUpdate): Promise<string | undefined> {\n if (this.#started) return settings ? this.update(settings) : this.#store.root?.uuid\n this.#started = true\n const type = this.messages.length > 0 ? (\"session-resume\" as const) : (\"session-start\" as const)\n await this.#commit({ type })\n void this.emit(type)\n await this.update(settings ?? {})\n return this.#store.root?.uuid\n }\n\n /** Look up the decorated view for a node id — raw node plus the\n * cumulative meta as of that node's position. Returns the\n * message-typed view, or `undefined` if the id doesn't refer to a\n * message in this session. */\n async node(id?: string | Message): Promise<(SessionNodeView & { type: \"message\" }) | undefined> {\n const m = typeof id === \"string\" ? { id } : id\n if (!m?.id) return undefined\n const ret = this.#view.nodes.get(m.id)\n return ret?.type === \"message\" ? ret : undefined\n }\n\n /** Current head uuid — the root of the store, which is the most\n * recently appended node. */\n get head(): string | undefined {\n return this.#head ?? this.#store.root?.uuid\n }\n\n /** Full DAG iterator — backed by `store.all()`. Yields raw nodes\n * (no meta decoration). */\n nodes(): Iterable<SessionNode> | AsyncIterable<SessionNode> {\n return this.#store.all?.() ?? []\n }\n\n get root(): SessionNode | undefined {\n return this.#store.root\n }\n\n // ── Mutate ────────────────────────────────────────────────────────────\n\n /** Append a message. The new node is parented to the current head,\n * head advances, and a `node` event fires. Optional `meta` carries\n * usage / model / finish info — populate it for assistant turns\n * the agent commits after a step. Returns the assigned uuid.\n *\n * If `message.id` / `message.ts` are set (e.g. round-tripped from a\n * prior load), they're used as the node's uuid / timestamp; otherwise\n * fresh values are generated. The committed message always carries\n * both — `m.id === node.uuid` and `m.ts === node.ts`. */\n async add(message: Message): Promise<string> {\n const uuid = message.id ?? uuidv7()\n const ts = message.ts ?? Date.now()\n const m = { ...message, id: uuid, ts }\n return this.#commit({ message: m, type: \"message\" })\n }\n\n /** Mark a compaction boundary. Subsequent `add()` calls land after\n * this node; their messages form the new active conversation. The\n * pre-compact chain stays in the store but is no longer part of\n * `messages`. Also commits a fresh `session-meta` snapshot right\n * after the compact so post-compact lazy walks always have a nearby\n * meta anchor. */\n async compact(opts: {\n trigger?: \"manual\" | \"auto\"\n preTokens?: number\n durationMs?: number\n tail: number\n summary: Message<\"system\">\n }): Promise<string> {\n const node: SessionNode = {\n durationMs: opts.durationMs,\n parentUuid: this.#store.root?.uuid,\n preTokens: opts.preTokens,\n summary: opts.summary,\n tail: opts.tail,\n trigger: opts.trigger ?? \"manual\",\n ts: Date.now(),\n type: \"compact\",\n uuid: uuidv7(),\n }\n const compactUuid = await this.#commit(node)\n await this.update({}, { force: true })\n await this.#rebuild()\n void this.emit(\"compact\", { node })\n return compactUuid\n }\n\n async addMaskCheckpoint(opts: MaskCheckpoint): Promise<string> {\n this.#view.maskCheckpoint = opts\n return this.#commit({ ...opts, type: \"mask-checkpoint\" })\n }\n\n async checkout(uuid: string): Promise<void> {\n if (this.#closed) throw new Error(\"Session is closed\")\n this.#head = uuid\n await this.#rebuild()\n }\n\n /** Pre-active history of the current chain — messages from before\n * the most recent compact (and earlier compacts), in chronological\n * order. Useful for TUI scrollback that wants to render context the\n * agent itself no longer sees.\n *\n * `limit` truncates from the front (oldest), so the most recent\n * history messages always make it through. */\n async history(limit?: number): Promise<readonly Message[]> {\n const chain = await this.#chain({ active: false, limit })\n return chain.messages\n }\n\n /** Flush + close the underlying store. Subsequent writes throw. */\n async close(): Promise<void> {\n if (this.#closed) return\n this.#closed = true\n await this.#store.close?.()\n }\n\n // ── Internals ────────────────────────────────────────────────────────\n\n /** Atomically commit a node: register it in the store, update active\n * messages, persist, and emit. Single chokepoint for all mutations. */\n async #commit(n: PartialNode, opts: { force?: boolean } = {}): Promise<string> {\n let node: SessionNode = {\n ...n,\n parentUuid: this.head,\n ts: Date.now(),\n uuid: uuidv7(),\n }\n\n if (!this.#started && node.type !== \"session-start\" && node.type !== \"session-resume\") {\n await this.#autostart()\n }\n if (this.#closed) throw new Error(\"Session is closed\")\n\n const next: SessionSettings = {\n ...this.#opts.defaults,\n ...this.#settings,\n ...(node.type === \"session-settings\" ? node.settings : {}),\n sessionId: this.#id,\n version: VERSION,\n }\n if (next.cwd) next.cwd = normPath(next.cwd)\n if (next.workspace) next.workspace = normPath(next.workspace)\n\n if (node.type === \"session-settings\") {\n if (!opts.force && isDeepStrictEqual(this.#settings, next)) return this.#store.root!.uuid\n node = { ...node, settings: next }\n }\n\n node = { ...node, parentUuid: this.head }\n await this.#store.write(node)\n this.#head = undefined // invalidate head cache so it reflects the store's root on next read\n\n // Decorate the new node with cumulative meta and append to the\n // active chain view\n this.#view.nodes.set(node.uuid, { ...node, settings: next })\n if (node.type === \"message\") this.#view.messages.push(node.message)\n\n // cwd / meta event signaling\n if (node.type === \"session-settings\") {\n const prev = this.#settings\n this.#settings = next\n this.#emitChanges(prev)\n }\n\n void this.emit(\"node\", { node })\n return node.uuid\n }\n\n #emitChanges(prev: SessionSettings): void {\n const next = this.#settings\n if (next.cwd && next.cwd !== prev.cwd) void this.emit(\"cwd\", { cwd: next.cwd, prev: prev.cwd })\n if (next.modelId && next.modelId !== prev.modelId)\n void this.emit(\"model\", { model: next.modelId, prev: prev.modelId })\n if (next.reasoning && next.reasoning !== prev.reasoning)\n void this.emit(\"reasoning\", { effort: next.reasoning, prev: prev.reasoning })\n }\n\n /** Walk `parentUuid` from the given node back toward the root,\n * returning the chain as decorated views (each with cumulative meta).\n * The `active` flag controls what counts:\n * - `true` — only the active chain (stops at first compact /\n * session-start, with the kept tail spliced in via\n * the compact's `tail` field, summary substituted\n * in place of the compact node).\n * - `false` — includes everything back to the root\n * Returns chronological order. */\n async #chain(\n opts: {\n active?: boolean\n limit?: number\n uuid?: string\n stop?: (node: SessionNode) => boolean\n } = {}\n ): Promise<SessionView & { settings: SessionSettings }> {\n // Backward pass: collect raw nodes (new to old)\n const reverse: SessionNode[] = []\n let cursor = opts.uuid ?? this.head\n let limit = opts.limit ?? Infinity\n let compact: SessionNode<\"compact\"> | undefined\n const messageNodes: SessionNode<\"message\">[] = []\n let maskCheckpoint: SessionNode<\"mask-checkpoint\"> | undefined\n\n // Walk backward from the head until we hit a compact or the start, collecting nodes.\n while (cursor) {\n // eslint-disable-next-line no-await-in-loop\n const node = await this.#store.get(cursor)\n if (!node || (node.type === \"message\" && messageNodes.length + 1 > limit)) break\n cursor = node.parentUuid\n reverse.push(node)\n // Keep track if the last mask-checkpoint, until we hit a compaction.\n if (node.type === \"mask-checkpoint\" && !compact && !maskCheckpoint) maskCheckpoint = node\n if (node.type === \"message\") messageNodes.push(node)\n if (node.type === \"compact\" && (opts.active ?? true)) {\n if (compact) break // stop at the first compact\n compact = node\n // update limit to the compact's tail\n limit = Math.min(limit, messageNodes.length + node.tail)\n }\n if (opts.stop?.(node)) break\n }\n\n // Forward pass: decorate with cumulative meta. session-meta nodes\n // redefine the meta; everything else inherits the running snapshot.\n const nodes = new Map<string, SessionNodeView>()\n let settings: SessionSettings = {}\n for (const n of reverse.toReversed()) {\n if (n.type === \"session-settings\") settings = n.settings\n nodes.set(n.uuid, { ...n, settings })\n }\n\n this.#migrate(nodes)\n\n const messages: SessionMessage[] = []\n for (const n of messageNodes) {\n // add `id`, `ts` and `modelId` (if assistant turn) to the message\n const m = { ...n.message, id: n.uuid, ts: n.ts }\n if (m.role === \"assistant\") {\n // Add missing modelId to assistant messages if not present\n const s = nodes.get(n.uuid)?.settings ?? {}\n m.meta = { ...m.meta, modelId: m.meta?.modelId ?? s.modelId }\n }\n n.message = m\n messages.push(m)\n }\n\n // Add compaction summary as the first message\n if (compact) {\n messages.push({ ...compact.summary, id: compact.uuid, ts: compact.ts })\n nodes.set(compact.uuid, {\n message: compact.summary,\n parentUuid: compact.parentUuid,\n settings: nodes.get(compact.uuid)?.settings ?? {},\n ts: compact.ts,\n type: \"message\",\n uuid: compact.uuid,\n })\n }\n\n return {\n compact,\n maskCheckpoint,\n messages: messages.toReversed(),\n nodes,\n settings,\n }\n }\n\n #migrate(nodes: Map<string, SessionNodeView>) {\n for (const n of nodes.values()) {\n const version = n.settings.version ?? 0\n if (version === VERSION) continue\n n.settings = { ...n.settings, version: VERSION }\n\n if (n.type === \"compact\") {\n n.summary.meta = { kind: \"compaction-summary\", ...n.summary.meta }\n continue\n }\n\n if (n.type !== \"message\") continue\n const m = n.message\n\n if (\n m.role === \"system\" &&\n Array.isArray(m.content) &&\n m.content[0].type === \"meta\" &&\n m.content[0].tag === \"task\"\n ) {\n // Missing task meta data kind\n m.meta = { kind: \"task\", ...m.meta }\n }\n\n n.message = m\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"tasks-WmC270X2.mjs","names":["#map","#heartbeatMs","#syncHeartbeat","#tools","#startReadyDependents","#heartbeatTimer","#startSyncResult","#registerPending","#needsPreflight","#preflight","#start","#buildPart","#allocate","#dispatch","#startPending"],"sources":["../src/tasks.ts"],"sourcesContent":["import type {\n ContentPart,\n Message,\n MetaPart,\n TextPart,\n Tool,\n ToolCallPart,\n ToolContext,\n ToolResult,\n ToolResultPart,\n Streamable as TStreamable,\n} from \"@zaly/ai\"\n\nimport { AiError, isStreamable, runTool, stringifyContent, toErrorResult } from \"@zaly/ai\"\nimport { Emitter, safeStringify } from \"@zaly/shared\"\nimport { uuidv7 } from \"./utils/uuid.ts\"\n\nconst DEFAULT_GRACE_MS = 10_000\n\n/** Status of a task. `pending` = queued behind a dependency, not yet\n * started. `running` = work in progress (a Streamable is being polled).\n * `done` = result captured, no longer mutating. */\nexport type TaskStatus = \"pending\" | \"running\" | \"done\"\n\n/** Public projection of a registered task. Status-discriminated so the\n * fields visible at any moment are exactly the ones that make sense\n * for that lifecycle stage:\n *\n * - `running`: `elapsedMs` since start; `hasNewOutput` when the\n * streamable signals that `task_poll` would yield bytes.\n * - `pending`: queued behind another task (`waitingFor`); `elapsedMs`\n * counts time-since-registration.\n * - `done`: settled. Carries `durationMs` and the captured `result`.\n *\n * This is the only public task type — the registry exposes it via\n * `running()` / `finished()` / `get()`, events carry it (`task-done`\n * is narrowed to `status: \"done\"` so listeners get `result` typed\n * without checks), and `task_list` JSON-stringifies it directly. */\nexport type TaskInfo = {\n id: string\n type: string\n desc: string\n} & (\n | { status: \"running\"; elapsedMs: number; hasNewOutput?: boolean }\n | { status: \"pending\"; waitingFor: string; elapsedMs: number }\n | { status: \"done\"; durationMs: number; result: ToolResult }\n)\n\n/** A `TaskInfo` known to be in the `done` state. `task-done` event\n * payloads use this so listeners get `result` and `durationMs` typed\n * without manual narrowing. */\nexport type DoneTaskInfo = Extract<TaskInfo, { status: \"done\" }>\n\n/** Events emitted by a `Tasks` instance. `task-done` only fires for\n * completions that happen *outside* an in-flight `run()` round —\n * round-internal completions are folded into the `run()` return value\n * instead. `heartbeat` fires periodically (when `heartbeatMs` is set\n * and at least one task is active), giving the agent a hook to keep\n * the model engaged on long-running work. */\nexport type TasksEvents = {\n \"task-added\": { task: TaskInfo }\n \"task-removed\": { task: TaskInfo }\n \"task-done\": { task: DoneTaskInfo }\n heartbeat: { running: readonly TaskInfo[] }\n}\n\n/** Augment ToolMeta so tools and consumers can read freshness/state info\n * off `ToolResultPart.meta.task`. The registry stamps this on every\n * result it produces so the model can correlate placeholders to their\n * eventual completion. */\nexport interface TaskMeta {\n task?: {\n id: string\n status: TaskStatus\n type: string\n desc: string\n dependsOn?: string\n durationMs?: number\n }\n}\n\n/** Internal storage record. Not exposed — `toTaskInfo` projects it to\n * `TaskInfo` for any caller that crosses the boundary (events,\n * registry methods, the `task_*` tools). Carries everything the\n * registry needs to drive lifecycle transitions, plus the raw\n * timestamps `TaskInfo` derives elapsed/duration from. */\ninterface InternalTask {\n id: string\n type: string\n desc: string\n status: TaskStatus\n /** Wallclock at registration. */\n startedAt: number\n /** Wallclock at completion; set once status is `done`. */\n finishedAt?: number\n /** Captured result when status is `done`. */\n result?: ToolResult\n /** When set, this task is queued behind another and won't start until\n * the named task transitions to `done`. */\n waitingFor?: string\n /** Streamable returned by the tool, if any — held so we can `abort()`\n * on cancellation and call `poll()` for partial snapshots. */\n streamable?: TStreamable\n /** Resolves when the task transitions to `done`. Used by the round\n * race in `run()`. */\n donePromise: Promise<void>\n resolveDone: () => void\n /** When set, the task belongs to an in-flight `run()` round — its\n * completion does *not* fire `task-done` (the round folds it into the\n * returned parts instead). Cleared once the round ends. */\n ownerRound?: symbol\n /** Stashed call so a pending task can be restarted once its\n * dependency completes. */\n pending?: { tool: Tool; call: ToolCallPart; ctx: ToolContext }\n}\n\n/**\n * Tool registry + execution machinery + long-running task bookkeeping.\n * One Tasks instance per Agent. Owns the tool list (the agent delegates\n * `agent.tools` to it) and runs every assistant tool batch through\n * `run()`, which:\n *\n * - validates each call's params,\n * - dispatches to the named tool (sync results land immediately),\n * - races `Streamable` returns against a grace window,\n * - chains `parallel: false` calls behind their predecessors,\n * - returns `ToolResultPart[]` 1:1 with the assistant's calls,\n * surfacing partial results / placeholders for anything still running\n * when the grace expires.\n *\n * Completions during a round are folded into the returned parts and do\n * NOT fire `task-done`. Completions afterward fire normally — the agent\n * listens and injects a system message into the next step.\n */\nexport class Tasks extends Emitter<TasksEvents> {\n readonly #map = new Map<string, InternalTask>()\n graceMs = DEFAULT_GRACE_MS\n\n #tools: (() => Promise<readonly Tool[]>) | Tool[] = []\n\n /** Heartbeat interval in ms. When set, fires `heartbeat` events while\n * at least one task is pending or running. Self-managing: starts on\n * the first active task, stops when the last one finishes. Set to\n * `undefined` to disable. */\n #heartbeatMs?: number\n #heartbeatTimer?: ReturnType<typeof setInterval>\n\n get heartbeatMs(): number | undefined {\n return this.#heartbeatMs\n }\n set heartbeatMs(value: number | undefined) {\n this.#heartbeatMs = value\n this.#syncHeartbeat()\n }\n\n // ── Public registry surface ─────────────────────────────────────────\n\n /** Snapshot of every active task (pending or running). */\n running(): readonly TaskInfo[] {\n return [...this.#map.values()].filter((t) => t.status !== \"done\").map((t) => toTaskInfo(t))\n }\n\n /** Snapshot of every task that has completed. Drops are by `remove()`\n * or implicit retention; this list grows unless the caller prunes. */\n finished(): readonly DoneTaskInfo[] {\n return [...this.#map.values()]\n .filter((t): t is InternalTask & { status: \"done\" } => t.status === \"done\")\n .map((t) => toTaskInfo(t) as DoneTaskInfo)\n }\n\n /** Look up a task by id. Useful for `task_stop` / `task_poll`. */\n get(id: string): TaskInfo | undefined {\n const t = this.#map.get(id)\n return t ? toTaskInfo(t) : undefined\n }\n\n /** Snapshot of every task in the registry — running, pending, and done. */\n info(): readonly TaskInfo[] {\n return [...this.#map.values()].map(toTaskInfo)\n }\n\n async tools(): Promise<readonly Tool[]> {\n return typeof this.#tools === \"function\" ? this.#tools() : this.#tools\n }\n\n set $tools(tools: (() => Promise<readonly Tool[]>) | Tool[]) {\n this.#tools = tools\n }\n\n /** Non-consuming check: does the underlying streamable have output\n * the model hasn't seen since the last `poll()`? Returns false for\n * tasks without a streamable, completed tasks, or streamables that\n * don't implement the optional `hasNew` hook (no signal to give).\n * Used by heartbeats to flag tasks worth polling. */\n hasNewOutput(id: string): boolean {\n const t = this.#map.get(id)\n if (t?.status !== \"running\" || !t.streamable) return false\n return t.streamable.hasNew?.() ?? false\n }\n\n /** Poll a task's streamable for incremental output without changing\n * its lifecycle state. Advances the streamable's cursor — the next\n * call returns only what arrived since *this* one. Returns the same\n * `ToolResult` shape the original tool produces (parts + meta).\n * Errors when the task is unknown, already done, or has no streamable\n * (the latter is the case for plain-Promise tools — there's no\n * partial state to read). */\n pollOutput(id: string): ToolResult & { running: boolean } {\n const t = this.#map.get(id)\n if (!t) {\n throw new AiError({\n code: \"NOT_FOUND\",\n data: { id },\n message: `no task with id \"${id}\"`,\n })\n }\n if (t.status === \"done\") {\n throw new AiError({\n code: \"TASK_DONE\",\n data: { id },\n message: `task \"${id}\" has already completed; its result was injected as a system message`,\n })\n }\n if (!t.streamable) {\n throw new AiError({\n code: \"NOT_STREAMABLE\",\n data: { id },\n message:\n `task \"${id}\" has no incremental output — it's a plain-Promise tool ` +\n `whose result is delivered all-at-once when complete`,\n })\n }\n return t.streamable.poll()\n }\n\n /** Drop a task from the registry — does not abort. Caller is\n * responsible for `abort()`-ing if the task is still running. */\n remove(id: string): boolean {\n const task = this.#map.get(id)\n if (!task) return false\n this.#map.delete(id)\n void this.emit(\"task-removed\", { task: toTaskInfo(task) })\n return true\n }\n\n /** Mark a task done with a final result. Fires `task-done` UNLESS the\n * task is currently owned by an in-flight `run()` round — in that\n * case the round folds the completion into its returned parts and\n * the event is suppressed. Starts any pending dependents. */\n done(id: string, result: ToolResult): void {\n const task = this.#map.get(id)\n if (!task || task.status === \"done\") return\n task.status = \"done\"\n task.result = result\n task.finishedAt = Date.now()\n task.resolveDone()\n if (!task.ownerRound) {\n void this.emit(\"task-done\", { task: toTaskInfo(task) as DoneTaskInfo })\n }\n this.#startReadyDependents(id, result)\n this.#syncHeartbeat()\n }\n\n /** Abort a running task. Calls the streamable's `abort()` if present;\n * pending tasks transition straight to `done` with a cancelled\n * result. Idempotent. */\n abort(id: string, reason = \"aborted by request\"): void {\n const task = this.#map.get(id)\n if (!task || task.status === \"done\") return\n if (task.streamable) task.streamable.abort()\n if (task.status === \"pending\") {\n this.done(id, toErrorResult(new AiError({ code: \"TASK_ABORTED\", message: reason })))\n }\n // Running tasks: the streamable will eventually settle and call done()\n // via its watcher. We could force a synchronous \"done\" here, but\n // letting the streamable's natural completion path run keeps the\n // final snapshot accurate (output that arrived before abort lands).\n }\n\n /** Abort every active task. Used on agent dispose. Awaits each\n * task's `donePromise` so the registry is fully drained before\n * resolving — so callers can rely on no pending I/O after `dispose`. */\n async killAll(): Promise<void> {\n const active = [...this.#map.values()].filter((t) => t.status !== \"done\")\n for (const t of active) {\n if (t.streamable) t.streamable.abort()\n if (t.status === \"pending\") {\n this.done(\n t.id,\n toErrorResult(new AiError({ code: \"TASK_ABORTED\", message: \"agent disposed\" }))\n )\n }\n }\n await Promise.all(active.map((t) => t.donePromise.catch(() => undefined)))\n this.#syncHeartbeat()\n }\n\n /** Start or stop the heartbeat timer based on whether any task is\n * active and whether `heartbeatMs` is configured. Idempotent — called\n * after every state transition (`add` / `done` / `killAll`). The\n * timer is `unref`'d so it never holds the process open. */\n #syncHeartbeat(): void {\n const hasActive = [...this.#map.values()].some((t) => t.status !== \"done\")\n const want = hasActive && this.#heartbeatMs !== undefined\n if (want && !this.#heartbeatTimer) {\n this.#heartbeatTimer = setInterval(() => {\n void this.emit(\"heartbeat\", { running: this.info().filter((t) => t.status !== \"done\") })\n }, this.#heartbeatMs)\n this.#heartbeatTimer.unref()\n } else if (!want && this.#heartbeatTimer) {\n clearInterval(this.#heartbeatTimer)\n this.#heartbeatTimer = undefined\n }\n }\n\n // ── Batch execution ─────────────────────────────────────────────────\n\n /**\n * Execute a batch of assistant tool calls. Returns ToolResultPart[]\n * mapped 1:1 with `calls`. Sync completions land in the result array\n * immediately; long-running ones return a placeholder and the eventual\n * completion fires `task-done` post-round (the agent injects a system\n * message in response).\n *\n * The `messages` snapshot is what tools see on `ctx.messages` — pass\n * the conversation up to (and including) the assistant message that\n * produced these calls, so freshness checks can scan back through\n * prior tool results.\n */\n async run(\n calls: readonly ToolCallPart[],\n ctx: ToolContext,\n opts: { onResult?: (call: ToolCallPart, result: ToolResultPart, idx: number) => void } = {}\n ): Promise<ToolResultPart[]> {\n const round = Symbol(\"round\")\n const tasks: InternalTask[] = []\n let chainHead: string | undefined\n\n const tools = await this.tools()\n\n for (const call of calls) {\n const tool = tools.find((t) => t.name === call.name)\n if (!tool) {\n tasks.push(this.#startSyncResult(call, unknownToolResult(call.name), round))\n continue\n }\n\n const parallel = tool.parallel ?? false\n\n if (chainHead && !parallel) {\n // Queue behind the chain head — won't start until that finishes.\n tasks.push(\n this.#registerPending({ call, ctxBase: ctx, round, tool, waitingFor: chainHead })\n )\n chainHead = tasks[tasks.length - 1].id\n continue\n }\n\n if (this.#needsPreflight(tool, ctx)) {\n // Preflight is intentionally sequential: permission prompts should\n // appear one at a time and should not register/background a task while\n // waiting for the user's answer.\n // oxlint-disable-next-line no-await-in-loop\n const preflight = await this.#preflight(tool, call, ctx)\n if (preflight) {\n tasks.push(this.#startSyncResult(call, preflight, round))\n continue\n }\n }\n\n const task = this.#start({ call, ctxBase: ctx, round, tool })\n tasks.push(task)\n // If this call promoted to a long-running task and `parallel: false`,\n // subsequent serial calls chain behind it.\n if (task.status === \"running\" && !parallel) chainHead = task.id\n }\n\n const parts: ToolResultPart[] = []\n\n const resultPart = (idx: number): ToolResultPart => {\n if (parts[idx]) return parts[idx]\n const task = tasks[idx]\n if (task.status !== \"done\") task.ownerRound = undefined\n const call = calls[idx]\n const part = this.#buildPart(call, task)\n parts[idx] = part\n return part\n }\n\n let onResult = opts.onResult\n\n // Race: every task done, OR grace expires, OR ctx.signal aborts.\n const allDone = Promise.all(\n tasks.map(async (t, ti) => {\n await t.donePromise\n onResult?.(calls[ti], resultPart(ti), ti)\n })\n )\n const grace = sleep(this.graceMs)\n const aborted = ctx.signal\n ? new Promise<void>((resolve) => {\n if (ctx.signal!.aborted) resolve()\n else ctx.signal!.addEventListener(\"abort\", () => resolve(), { once: true })\n })\n : new Promise<void>(() => undefined) // never resolves\n await Promise.race([allDone, grace, aborted])\n onResult = undefined\n\n // Release ownership on still-active tasks BEFORE building the result\n // array, so the post-round listener sees them as free-floating if\n // they happen to settle in the same tick.\n return tasks.map((_, i) => resultPart(i))\n }\n\n // ── Internals ──────────────────────────────────────────────────────\n\n /** Build a \"done\" task synchronously from a precomputed result.\n * Used for unknown-tool errors and validation failures. */\n #startSyncResult(call: ToolCallPart, result: ToolResult, round: symbol): InternalTask {\n // oxlint-disable-next-line sort-keys -- semantic field order\n const task = this.#allocate({ id: uuidv7(), type: call.name, desc: call.name, round })\n task.status = \"done\"\n task.result = result\n task.finishedAt = task.startedAt\n task.resolveDone()\n return task\n }\n\n /** Allocate an internal task record, register it, and emit `task-added`. */\n #allocate(opts: {\n id: string\n type: string\n desc: string\n waitingFor?: string\n round: symbol\n }): InternalTask {\n let resolveDone!: () => void\n const donePromise = new Promise<void>((r) => {\n resolveDone = r\n })\n const task: InternalTask = {\n desc: opts.desc,\n donePromise,\n id: opts.id,\n ownerRound: opts.round,\n resolveDone,\n startedAt: Date.now(),\n status: \"pending\",\n type: opts.type,\n waitingFor: opts.waitingFor,\n }\n this.#map.set(task.id, task)\n void this.emit(\"task-added\", { task: toTaskInfo(task) })\n this.#syncHeartbeat()\n return task\n }\n\n /** Register a pending task (chained behind `dependsOn`). Does not\n * invoke the tool — that happens once the dependency completes. */\n #registerPending(opts: {\n call: ToolCallPart\n tool: Tool\n ctxBase: ToolContext\n waitingFor: string\n round: symbol\n }): InternalTask {\n // oxlint-disable-next-line sort-keys -- semantic field order\n const task = this.#allocate({\n id: uuidv7(),\n type: opts.tool.name,\n desc: descOfCall(opts.call),\n waitingFor: opts.waitingFor,\n round: opts.round,\n })\n task.pending = { call: opts.call, ctx: opts.ctxBase, tool: opts.tool }\n return task\n }\n\n /** Start a tool call. Allocates a task record, validates params,\n * invokes `tool.call`, detects `Streamable`, and wires completion\n * back to `done()`. Returns the task in its post-launch state\n * (`done` for sync failures or already-resolved sync calls,\n * `running` for everything else). */\n #start(opts: {\n call: ToolCallPart\n tool: Tool\n ctxBase: ToolContext\n round: symbol\n }): InternalTask {\n // oxlint-disable-next-line sort-keys -- semantic field order\n const task = this.#allocate({\n id: uuidv7(),\n type: opts.tool.name,\n desc: descOfCall(opts.call),\n round: opts.round,\n })\n this.#dispatch(task, opts.tool, opts.call, opts.ctxBase)\n return task\n }\n\n #needsPreflight(tool: Tool, ctx: ToolContext): boolean {\n return ctx.need !== undefined || tool.preflight !== undefined\n }\n\n /** Run schema validation and semantic/tool permission checks before a\n * public task is allocated. Returns a formatted tool error when the\n * call should short-circuit, otherwise undefined. */\n async #preflight(\n tool: Tool,\n call: ToolCallPart,\n ctxBase: ToolContext\n ): Promise<ToolResult | undefined> {\n const ctx: ToolContext = { ...ctxBase, tasks: this }\n let params: unknown\n try {\n params = await tool.validator.validateParams(call.params)\n await ctx.need?.(\"tool\", tool.name)\n await tool.preflight?.(params, ctx)\n } catch (error) {\n return toErrorResult(error)\n }\n }\n\n /** Validate / call / wire up completion for an existing task record.\n * Used both for fresh tasks (`#start`) and pending-becoming-ready\n * (`#startReadyDependents`). The task's id and registry entry are\n * reused so identifiers stay stable across the pending → running\n * transition.\n *\n * Delegates the validate/call/error machinery to `runTool` (in\n * streaming mode) so there's one source of truth. The task's status\n * is flipped to `running` synchronously — the round loop reads it\n * right after `#start` returns to decide chain heads, and we want\n * that read to see the post-launch state, not the allocation default.\n * Async resolution either keeps it `running` (streamable returned)\n * or transitions to `done` (sync result or error). */\n #dispatch(task: InternalTask, tool: Tool, call: ToolCallPart, ctxBase: ToolContext): void {\n const ctx: ToolContext = { ...ctxBase, tasks: this }\n task.status = \"running\"\n\n const dispatchPromise = runTool(tool, call.params, ctx, { preflight: false, streaming: true })\n\n void dispatchPromise.then(\n (settled) => {\n if (isStreamable(settled)) {\n // Long-running — hold the streamable for partial snapshots /\n // abort, wire its completion back through done().\n task.streamable = settled\n settled.done.then(\n () => {\n const snap = settled.poll()\n this.done(task.id, {\n content: snap.content,\n error: snap.error,\n isError: snap.isError,\n meta: snap.meta,\n })\n },\n // `done` shouldn't reject by contract; treat as internal bug.\n (error: unknown) => this.done(task.id, toErrorResult(error))\n )\n return\n }\n // Sync path: validation error, sync return, or thrown error —\n // runTool has already formatted everything into a ToolResult.\n this.done(task.id, settled)\n },\n // The permission gate threw (or AiError leaked from runTool).\n // Either way, format and settle the task.\n (error: unknown) => this.done(task.id, toErrorResult(error))\n )\n }\n\n /** Walk pending tasks; start any whose `dependsOn` just completed.\n * If the dependency failed, propagate cancellation: dependents land\n * in `done` with an `UPSTREAM_FAILED` result. */\n #startReadyDependents(completedId: string, completedResult: ToolResult): void {\n for (const t of this.#map.values()) {\n if (t.status !== \"pending\" || t.waitingFor !== completedId) continue\n if (completedResult.isError) {\n // Skip dependent — its predecessor failed.\n this.done(\n t.id,\n toErrorResult(\n new AiError({\n code: \"UPSTREAM_FAILED\",\n data: { dependsOn: completedId },\n message: `skipped: upstream task ${completedId} failed`,\n })\n )\n )\n continue\n }\n void this.#startPending(t)\n }\n }\n\n async #startPending(task: InternalTask): Promise<void> {\n const pending = task.pending\n if (!pending) return\n if (!this.#needsPreflight(pending.tool, pending.ctx)) {\n task.pending = undefined\n task.waitingFor = undefined\n task.ownerRound = undefined\n this.#dispatch(task, pending.tool, pending.call, pending.ctx)\n return\n }\n\n const preflight = await this.#preflight(pending.tool, pending.call, pending.ctx)\n if (preflight) {\n this.done(task.id, preflight)\n return\n }\n\n // Restart as a regular task. The round that originally registered\n // this task has already returned its placeholder, so whatever happens\n // now flows through normal `task-done`.\n task.pending = undefined\n task.waitingFor = undefined\n task.ownerRound = undefined\n this.#dispatch(task, pending.tool, pending.call, pending.ctx)\n }\n\n /** Build a `ToolResultPart` for a task at round-end. Done tasks pass\n * through their captured result; still-running tasks get a snapshot\n * from the streamable (if any) plus a placeholder note for the model. */\n #buildPart(call: ToolCallPart, task: InternalTask): ToolResultPart {\n if (task.status === \"done\" && task.result) {\n return {\n content: task.result.content,\n error: task.result.error,\n id: call.id,\n isError: task.result.isError,\n meta: stampTaskMeta(task.result.meta, task),\n name: call.name,\n type: \"tool-result\",\n }\n }\n\n // Pending: never started, will run when its dependency completes.\n // Placeholder uses the standard TaskInfo shape so it's parseable\n // the same way as task_list / heartbeat entries.\n if (task.status === \"pending\") {\n return {\n content: [{ data: toTaskInfo(task), tag: \"task\", type: \"meta\" }],\n id: call.id,\n isError: false,\n meta: stampTaskMeta(undefined, task),\n name: call.name,\n type: \"tool-result\",\n }\n }\n\n // Running: capture whatever the streamable has so far (partial output)\n // and append a `<task>` MetaPart with an explicit \"still running\"\n // hint. Without the hint, models tend to treat partial output as a\n // final answer and respond prematurely; with it they see the state\n // structurally and know the final result will arrive as a later\n // system message. Tagged `<task>` for symmetry with the pending /\n // task_list / heartbeat / task-done entries — same shape everywhere\n // a task surfaces.\n const snap = task.streamable?.poll()\n const baseContent = partialContentFrom(snap)\n const trailer: MetaPart = {\n content:\n \"Task is still running. Partial output above. The final result \" +\n \"will arrive as a system message when the task completes; you \" +\n \"do not need to poll. Continue with other work or wait.\",\n data: {\n ...toTaskInfo(task),\n },\n tag: \"task\",\n type: \"meta\",\n }\n const parts: ContentPart[] = []\n if (typeof baseContent === \"string\") {\n if (baseContent !== \"\") parts.push({ text: baseContent, type: \"text\" })\n } else {\n parts.push(...baseContent)\n }\n parts.push(trailer)\n return {\n content: parts,\n id: call.id,\n isError: false,\n meta: stampTaskMeta(snap?.meta, task),\n name: call.name,\n type: \"tool-result\",\n }\n }\n}\n\n/** Stamp `meta.task` with the current task state. Preserves any\n * tool-attached `meta` fields. Used both on done results (so the\n * model can correlate completion to the original call) and on\n * partial / pending placeholders. */\nfunction stampTaskMeta(\n base: ToolResult[\"meta\"] | undefined,\n task: InternalTask\n): ToolResult[\"meta\"] & TaskMeta {\n return {\n ...base,\n task: {\n dependsOn: task.waitingFor,\n desc: task.desc,\n durationMs: task.finishedAt ? task.finishedAt - task.startedAt : undefined,\n id: task.id,\n status: task.status,\n type: task.type,\n },\n }\n}\n\n/** Build the \"running\" placeholder content from a streamable snapshot.\n * Empty string when there's nothing yet to show; copies the parts\n * array so downstream mutation of the result message can't reach back\n * into the streamable's accumulator. */\nfunction partialContentFrom(\n snap: { content: ToolResult[\"content\"] } | undefined\n): ToolResultPart[\"content\"] {\n if (!snap) return \"\"\n if (typeof snap.content === \"string\") return snap.content\n return [...snap.content]\n}\n\n/** Best-effort human-readable label for a tool call. Reads a\n * `description` field from params if the tool defined one (bash does);\n * otherwise falls back to the tool name. Kept short so log lines stay\n * scannable. */\nfunction descOfCall(call: ToolCallPart): string {\n const params = call.params as { description?: unknown } | null | undefined\n if (params && typeof params === \"object\" && typeof params.description === \"string\") {\n return params.description\n }\n return call.name\n}\n\n/** Synthesize the tool-result payload returned when the model calls a\n * tool that wasn't registered for this turn. Same shape the previous\n * `unknownToolResult` helper produced; inlined here to keep `tasks.ts`\n * self-contained. */\nfunction unknownToolResult(name: string): ToolResult {\n const err = new AiError({\n code: \"UNKNOWN_TOOL\",\n message: `no tool named \"${name}\" is registered for this turn`,\n })\n return toErrorResult(err)\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => {\n const t = setTimeout(r, ms)\n t.unref()\n })\n}\n\n/** Render an array of `TaskInfo` as a `<tasks>` MetaPart for the model\n * (heartbeat pulses and `task_list`). Each task lands as one line of\n * JSON. The `result` field on done tasks is stripped — listing should\n * give an inventory, not historical output (the result was already\n * injected as a system message at task-done time, and re-shipping it\n * on every heartbeat / list call would be a token bomb). */\nexport function taskInfoPart(info: readonly TaskInfo[]): MetaPart {\n if (info.length === 0) return { data: \"no active tasks\", tag: \"tasks\", type: \"meta\" }\n const data = info.map((t) => safeStringify(t, omitResult)).join(\"\\n\")\n return { data, tag: \"tasks\", type: \"meta\" }\n}\n\n/** JSON.stringify replacer that drops the `result` field. Used by\n * `taskInfoPart` to keep listing payloads bounded. */\nfunction omitResult(key: string, value: unknown): unknown {\n return key === \"result\" ? undefined : value\n}\n\nfunction toTaskInfo(task: InternalTask): TaskInfo {\n const { id, status, type, desc } = task\n const ms = (task.finishedAt ?? Date.now()) - task.startedAt\n if (status === \"running\") {\n return { desc, elapsedMs: ms, hasNewOutput: task.streamable?.hasNew?.(), id, status, type }\n }\n if (status === \"pending\") {\n return { desc, elapsedMs: ms, id, status, type, waitingFor: task.waitingFor ?? \"\" }\n }\n // status === \"done\" — `result` is set by `done()` before status flips,\n // so the `?? toErrorResult(...)` is just a defensive fallback for\n // a malformed record (shouldn't happen in practice).\n return {\n desc,\n durationMs: ms,\n id,\n result:\n task.result ??\n toErrorResult(\n new AiError({\n code: \"INTERNAL\",\n message: `task \"${task.id}\" completed without a result`,\n })\n ),\n status,\n type,\n }\n}\n\n/** Format a finished task as the parts of a system inject. Layout:\n *\n * <task>{id, type, desc, status: \"done\", durationMs}</task>\n * {result body}\n *\n * The header is the standard `TaskInfo`-shaped JSON so consumers\n * (model, TUI) can parse it the same way they parse `task_list` and\n * heartbeat output — same shape everywhere a task surfaces.\n *\n * The body comes straight from `result.content`. For errors, that\n * already includes the `<error>{code, message, ...}</error>` MetaPart\n * + the formatted `❌ CODE: message` block (baked in by\n * `toErrorResult`), so this function doesn't need to special-case\n * errors — the structured tag and human body ride along with the\n * result content for any tool failure, anywhere.\n *\n * System messages can't carry attachments, so any image/pdf/etc. in\n * the result degrades to a `[image]` placeholder via\n * `stringifyContent` — best-effort, model still sees *something* was\n * there. */\nfunction formatTaskCompletion(task: DoneTaskInfo): (TextPart | MetaPart)[] {\n const { result, ...header } = task\n const parts: (TextPart | MetaPart)[] = [{ data: header, tag: \"task\", type: \"meta\" }]\n const bodyText = stringifyContent(result.content)\n if (bodyText !== \"\") parts.push({ text: bodyText, type: \"text\" })\n return parts\n}\n\n/** Build an `inject`-ready system message for a finished task. */\nexport function taskCompletionMessage(task: DoneTaskInfo): Message<\"system\"> {\n return {\n content: formatTaskCompletion(task),\n meta: { kind: \"task\", taskId: task.id },\n role: \"system\",\n }\n}\n"],"mappings":"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"}