@anhlp/alp 0.0.0-beta.2 → 0.0.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/alpd.js CHANGED
@@ -7805,7 +7805,7 @@ var require_content_type = __commonJS({
7805
7805
  // packages/daemon/src/main.ts
7806
7806
  import { spawn as spawn9 } from "node:child_process";
7807
7807
  import { openSync, writeSync } from "node:fs";
7808
- import { mkdir as mkdir18, open as open7, readFile as readFile24, rename as rename9, stat as stat13, unlink as unlink6, utimes as utimes3, writeFile as writeFile17 } from "node:fs/promises";
7808
+ import { mkdir as mkdir18, open as open7, readFile as readFile24, rename as rename10, stat as stat13, unlink as unlink6, utimes as utimes3, writeFile as writeFile18 } from "node:fs/promises";
7809
7809
  import path40 from "node:path";
7810
7810
  import { fileURLToPath } from "node:url";
7811
7811
 
@@ -7813,8 +7813,8 @@ import { fileURLToPath } from "node:url";
7813
7813
  var templates_default = { "ALP.md": "# ALP project instructions\n\n## Working relationship\n\nThe user communicates with main. The team is fixed for each session:\n\n- Ph\u1EDF (default): main owns delivery and technical execution; it implements\n directly or delegates bounded work to peer. There is no separate lead.\n- Cafe: main owns the outcome and supervises lead; lead implements or\n delegates to peer. Main routes implementation changes through lead.\n\nIn both, main runs by default on Opus 5.5 with high effort and full access,\nand starts a supervisor that reviews the process after each of main's turns.\nThe supervisor asks main about mistakes; main answers and records lessons it\nfollows later.\n\nOracle provides read-only advice through the configured model lenses; reviewer independently\nreviews one diff. Both return once only to the requesting coordinator. They do\nnot spawn agents. Default maximum concurrent peers is two; raise the configured\nlimit only at the user's request. Other model and effort choices belong to the\ntechnical coordinator. A role grants no additional filesystem permissions,\ncredentials, or runtime tools.\n\n## Shared work contract\n\n- Read project instructions and the selected agent's AGENT.md. Load skills only\n when needed; do not assume that skills or tools from another runtime exist.\n- Each assignment states its objective, root, owned/excluded paths, constraints\n and their sources, verification, resources, owner, and expected handoff.\n- Keep a single writer per shared checkout. Transfer ownership before main or lead\n intervenes in a peer's files. Parallel writers need isolated workspaces and\n explicit shared-interface agreements.\n- Return artifacts and real verification evidence, with remaining risks. A worker's\n completion message is a candidate, not an independent acceptance verdict.\n- Review should be independent of authorship: the coordinator reviews peer work;\n in Cafe, main reviews lead work. Use reviewer for logic changes and risky\n changes, including main-authored changes. Disclose unavailable independent review\n rather than claiming it occurred.\n- Changes to scope or priorities flow through the technical coordinator's current plan\n and the affected peer's revised brief. Direct questions can receive direct answers;\n an exceptional intervention must be reconciled with the same shared state.\n- Tasks live in `.alp/tasks` and are shared with the user. Only the user and main\n create or change them; other agents read them and report work they find to their\n requester.\n- Use only actual runtime delegation/messaging capabilities. Without them, explain\n the limitation, perform authorized work directly where possible, and never claim\n that a lead or peer session ran or reviewed anything.\n\n## Project-specific constraints\n\nAdd the project's outcome, boundaries, verification commands, and authorization\nrequirements here. User instructions take precedence over this starter workflow.\n", "role-skills.json": '{\n "main": ["goal-griller", "xia", "sequence-execution-plan", "prompt-leverage", "bug-loop", "smart-commits"],\n "lead": ["xia", "sequence-execution-plan", "prompt-leverage", "bug-loop", "smart-commits"],\n "peer": ["xia", "bug-loop", "smart-commits"],\n "oracle": [],\n "reviewer": [],\n "supervisor": []\n}\n', "teams/pho/HOUSE_RULES.md": "In Ph\u1EDF (pho), main implements or directly delegates to peer; do not create lead. In Cafe (cafe), main supervises lead; lead may implement or delegate to peer. The technical coordinator chooses each peer's model and effort. Use oracle for significant uncertainty; use reviewer for logic changes and risky changes, not mandatory for typo/format fixes. Advisors return only to their requesting coordinator.\n", "teams/pho/team.json": '{\n "label": "Ph\u1EDF",\n "description": "Main implements or delegates to peer; a supervisor reviews the process.",\n "main": "main",\n "members": {\n "main": { "model": "claude:claude-opus-5-5", "thinking": "high" },\n "peer": { "role": "peer" },\n "oracle": { "role": "advisor" },\n "reviewer": { "role": "reviewer" }\n },\n "delegation": { "main": ["peer", "oracle", "reviewer"] },\n "maxPeers": 2,\n "supervisor": { "agent": "supervisor", "model": "claude:claude-sonnet-5", "thinking": "medium" }\n}\n', "teams/cafe/HOUSE_RULES.md": "In Ph\u1EDF (pho), main implements or directly delegates to peer; do not create lead. In Cafe (cafe), main supervises lead; lead may implement or delegate to peer. The technical coordinator chooses each peer's model and effort. Use oracle for significant uncertainty; use reviewer for logic changes and risky changes, not mandatory for typo/format fixes. Advisors return only to their requesting coordinator.\n", "teams/cafe/team.json": '{\n "label": "Cafe",\n "description": "Main supervises lead, who implements or delegates to peer; a supervisor reviews the process.",\n "main": "main",\n "members": {\n "main": { "model": "claude:claude-opus-5-5", "thinking": "high" },\n "lead": { "role": "lead" },\n "peer": { "role": "peer" },\n "oracle": { "role": "advisor" },\n "reviewer": { "role": "reviewer" }\n },\n "delegation": { "main": ["lead", "oracle", "reviewer"], "lead": ["peer", "oracle", "reviewer"] },\n "maxPeers": 2,\n "supervisor": { "agent": "supervisor", "model": "claude:claude-sonnet-5", "thinking": "medium" }\n}\n', "skills/xia/SKILL.md": "---\nname: xia\ndescription: Research an unfamiliar implementation, reuse opportunity, or version-sensitive integration using local artifacts and primary sources. Return an evidence-backed recommendation before implementation; do not expand the assigned scope.\n---\n\n# Research before building\n\nStart with a named question that affects a decision. Use a short local inspection\nwhen the relevant seam is clear; broaden to upstream sources only when the question\nrequires it. Research is read-only with respect to project artifacts. Temporary\nexperiments require an available isolated scratch location and appropriate authority.\n\n1. Establish the actual stack from manifests, lockfiles, configuration, and commands.\n Read ALP.md and relevant project guidance. Do not infer a stack from folder names.\n2. Search nearby code, tests, configuration, scripts, and docs for existing behavior\n and reusable parts. Record the scope searched before saying something is absent.\n3. For external behavior, inspect primary upstream source/examples and official\n documentation matching the installed version. Distinguish current guidance from\n the version this project actually uses.\n4. Compare viable approaches only where the choice matters. Prefer an existing\n local capability, then a supported dependency capability, then a small adaptation.\n Explain what evidence would change the recommendation.\n\nLabel significant findings by evidence type:\n\n- Local: file path, command output, test, or repository revision.\n- Upstream: repository URL and revision when available.\n- Docs: official source and applicable version.\n- Inference: a conclusion drawn from the cited evidence, with uncertainty stated.\n\nIf browsing is unavailable, return the local findings and the unresolved external\nquestion. Do not invent a source or claim version compatibility without evidence.\nResearch does not authorize installing dependencies, editing code, or deploying.\n\nReturn the question, findings/sources, reuse candidates, recommendation, alternatives\nif material, unknowns, and the next decision. Include actual inspection commands and\nresults in the handoff. A peer returns this to lead; lead returns unresolved scope\ndecisions to main. Main can decide technical matters within the user's authorization.\n\nUse available synchronous delegation for a separate research assignment only when\nhelpful. Do not assume a Scout role or parallel session exists; research is a peer\ndisposition. An agent can inspect evidence itself without spawning another agent.\n", "skills/smart-commits/SKILL.md": "---\nname: smart-commits\ndescription: Package existing owned changes into focused local Git commits with verification evidence. Use when commits are requested or part of the assigned handoff; do not activate merely because a file was edited.\n---\n\n# Package the authorized changes\n\nConfirm this is a Git repository and that local commits belong to the requested\ndeliverable. A non-Git task returns file artifacts; do not initialize Git merely to\nproduce a SHA. A read-only task cannot commit. Existing user authorization remains\nvalid; the skill adds no confirmation gate or permission to push.\n\nInspect the root, branch, status, unresolved merges, staged paths, and staged/unstaged\ndiffs with Git commands. Verify any supplied base revision. Preserve work outside\nthe assignment. If unrelated staged changes would enter the commit, stop packaging\nand report the conflict without resetting or unstaging someone else's work.\n\nGroup changes by behavior and intent. Keep implementation and its direct tests\ntogether where useful. Read each included diff and stage only the owned paths or\nhunks. Avoid whole-tree staging in a shared checkout. Do not add unrelated fixes or\nformatting to make a commit easier.\n\nRun the relevant checks from the project/brief, reusing recent results when still\napplicable. State failures and omissions accurately. If hooks may cause external\nside effects, inspect their configured behavior before invoking them and stay within\nthe task's authorization. Do not bypass hooks or protections to get a green commit.\n\nCreate focused messages explaining the change and its purpose, following the\nrepository's convention. Check commit success before reading the candidate SHA.\nVerify that the candidate contains only the intended scope and that the base\nrelationship is valid when a base was supplied. A failed commit produces no new\ncandidate, and an already-clean tree needs no empty commit.\n\nDo not rewrite a candidate already handed off for review. Corrections should be\nnew commits unless rewriting was specifically agreed. Push requires existing explicit\nauthorization for the intended remote/branch; a configured remote does not provide it.\n\nReturn root, branch, actual base/candidate SHAs, commit summary, owned scope, checks\nand results, remaining work, and push status. Lead/main still review the artifact;\nthe writer does not self-accept its work.\n", "skills/sequence-execution-plan/SKILL.md": "---\nname: sequence-execution-plan\ndescription: Order multiple ALP work items by real dependencies, verification, and writer ownership. Use when Main or Lead coordinates several outcomes or must replan after a blocker, changed premise, or rejection.\n---\n\n# Order executable work\n\nUse the task contract and available evidence. Planning authority comes from the\ncurrent assignment: main coordinates the overall outcome; lead plans technical\nexecution within its assigned scope. A peer reports dependencies instead of\nreassigning ownership or widening the plan.\n\nSeparate urgency from dependency order. A lower-priority prerequisite may need to\nrun first, but it does not close the higher-priority outcome. A mitigation reduces\ncurrent impact; it is not proof of a durable fix.\n\nBuild small work items that each deliver observable progress. Keep implementation\nand its direct verification together. Split when an unresolved decision blocks one\npart, separate outcomes need independent acceptance, or a resource/human wait would\nstall unrelated work. Do not impose a fixed task count on a simple change.\n\nFor each item identify the result, owner, owned/excluded paths, true prerequisites,\nverification seam, completion evidence, and current state. Distinguish necessary\nfoundations from optional improvements and speculative future abstractions.\n\nArrange Now, Next, and Later using causal explanations: which prerequisite blocks\nwhich outcome, which check validates it, and which decision opens the next branch.\nRead [the plan format](references/plan-template.md) when a durable multi-item plan\nhelps. Use the project's existing plan location; do not create unrelated ignore\nrules or a new planning hierarchy just to satisfy this skill.\n\nCurrent Paseo delegation is sequential: each parent waits for one child's result.\nDo not promise parallel workers or live upstream mailboxes. A shared checkout has\none writer at a time; main intervention requires ownership to be released first.\nAllocate ports, scratch paths, and other exclusive resources if the assignment uses\nthem. Separate worktrees alone do not isolate ports or a shared database.\n\nReplan when evidence changes a premise, a dependency blocks work, the user changes\nscope, or repeated rejection reveals that the assignment needs restructuring.\nUpdate the facts and constraints before issuing a revised brief. Preserve unfinished\nacceptance criteria when merging items. Candidate remains candidate until the\nresponsible reviewer has evaluated the actual evidence.\n", "skills/sequence-execution-plan/references/plan-template.md": "# Proportional plan format\n\nUse the relevant fields; a plan is a decision aid, not a form-completion exercise.\n\n```text\nOutcome and acceptance evidence:\nUser constraints and their sources:\nCurrent facts / technical assumptions:\n\nID | Result | Owner | Owned/excluded scope | Prerequisites | Verification | State\n\nNow:\nNext (and what unlocks it):\nLater:\nWriter/resource allocation:\nOpen decisions and blockers:\nReplan reason, when applicable:\n```\n\nStates can be ready, blocked, running, candidate, accepted, or rejected. An item is\naccepted based on artifacts and verification; a successful process exit is not an\nacceptance criterion by itself. Omit branch/SHA fields in non-Git projects.\n", "skills/prompt-leverage/SKILL.md": "---\nname: prompt-leverage\ndescription: Convert a task into a self-contained delegation brief without changing user intent or predetermining the solution. Use before Main assigns Lead or Lead assigns Peer, or when asked to improve an execution prompt.\n---\n\n# Prepare an actionable brief\n\nIdentify the requested result and the recipient's responsibility. Preserve actual\nuser constraints, but distinguish them from current design choices the recipient\nmay question. Do not seed a reviewer's verdict or turn a preferred implementation\ninto an invented requirement.\n\nA useful brief supplies the root, outcome, relevant context, owned/excluded paths,\nauthority, important dependencies, resources, verification, and expected handoff.\nUse [the brief format](references/brief-template.md) for substantial assignments;\ncombine fields for a small task. A real base SHA is useful in a Git repository but\nis not required for a plain file project. Never fabricate one.\n\nVerification must describe the observable behavior and, when known, its executable\ncheck. If a command still needs discovery, state how it should be established from\nthe repository. Do not invent a test command just to fill a field.\n\nChoose the relevant installed skill by task type and mention it in the brief:\nresearch -> xia; diagnosis/regression -> bug-loop; an authorized commit handoff ->\nsmart-commits. Do not require skills absent from the recipient's package. A reviewer\ncan inspect an existing candidate without additional skill ceremonies.\n\nOn current Paseo, pass the full brief as alp_delegate.task with an authorized agent\nand optional mode. Model and reasoning inherit from the parent; there is no model\nargument on this tool. Request read-only for research/review where appropriate.\nImplementation requires workspace-write on the parent and child. The call waits\nfor a final handoff, so include everything needed to work without a live Q&A channel.\n\nThis skill grants no new authority and never bypasses the configured routing graph.\nIf asked only to improve a prompt, return the prompt. If assignment is authorized,\nsend it using the actual runtime tool after preparing it. Missing capabilities\nmust be reported, not simulated.\n", "skills/prompt-leverage/references/brief-template.md": "# Brief and handoff format\n\nUse enough detail to make an independent worker effective, without prescribing an\nunverified solution. Available context includes ALP.md and the selected agent's own\ninstructions; the parent's full transcript is not automatically available.\n\n```text\nTask and recipient:\nRoot:\nOutcome:\nContext / prior decisions:\nRequired constraints + sources:\nCurrent technical choices (open to evidence):\nOwned paths / excluded paths:\nAuthority and permission mode:\nDependencies / writer ownership / resource allocation:\nVerification commands or expected observable checks:\nRelevant installed skill, if needed:\nCandidate/base revision, if applicable:\nReturn: outcome, artifact, scope, actual checks/results, risks, ownership status.\n```\n\nFor read-only review, supply the candidate/artifact and independent acceptance\ncriteria. For a correction, include the concrete finding and prior evidence. For\na resumed assignment, include the findings needed from the previous handoff because\nthe next child is a new session. A blocked recipient should return its blocker and\ncompleted independent work rather than waiting for a nonexistent mailbox.\n", "skills/goal-griller/SKILL.md": '---\nname: goal-griller\ndescription: Turn an underspecified user request into a verifiable task contract. Use at Main intake when missing outcomes, scope, or acceptance evidence would materially change execution; skip when the request is already clear.\n---\n\n# Clarify the outcome\n\nThis skill serves the user-facing owner. In the starter workflow that is main;\nlead and peer return missing decisions to their assigning agent instead of opening\nan independent user conversation.\n\nRead the request, existing decisions, ALP.md, and the few relevant local artifacts.\nSeparate the desired result from a suggested implementation. Establish:\n\n- Outcome: the observable state the user wants.\n- Evidence: a check, artifact, or measurement showing that result.\n- Scope: what may change and what the user requires to remain unchanged.\n- Context: the sources needed to make the first decision.\n- Verification: a fast feedback loop and any final checks.\n- Completion/blockers: when the work is done and what requires a user decision.\n\nInfer routine details from the repository and existing authorization. Ask only\nquestions whose answers change scope, behavior, cost, or a consequential decision.\nBundle independent questions concisely and offer a reasonable default where useful.\nContinue independent authorized work while waiting; a required answer is not\nsupplied by elapsed time.\n\nDo not send a researcher to invent the meaning of words such as "better" or\n"production-ready" when the outcome itself needs the user\'s judgment. Conversely,\ndo not ask the user to identify a build command or file path that local evidence\ncan establish.\n\nProduce a proportional contract, for example:\n\n```text\nOutcome: observable result\nEvidence: check/artifact and expected behavior\nScope: owned paths; exclusions\nConstraints: requirement + source; distinguish technical choices\nContext: relevant artifacts\nVerification: fast check; final checks\nDone: completion condition\nBlocked if: material missing decision or prerequisite\n```\n\nFor an already-authorized task, a clear contract is enough to proceed; this skill\nadds no approval gate. If asked only to draft a contract, return it without starting\nimplementation. Route a multi-item task to sequence-execution-plan, or a single\nassignment to prompt-leverage, reading the next skill only when needed.\n', "skills/bug-loop/SKILL.md": "---\nname: bug-loop\ndescription: Diagnose a bug or regression through a reproducible feedback loop, competing explanations, and verified correction. Use for failing tests, incorrect behavior, or performance regressions; read-only assignments stop before modifying project artifacts.\n---\n\n# Establish a failing loop, then fix the cause\n\nStart from the reported behavior and the assigned scope. Find the smallest useful\ncommand, request, replay, or test that distinguishes the bug from correct behavior.\nRun it and record the actual result before claiming reproduction. For intermittent\nfailures, record conditions, seed when applicable, and observed failure frequency.\n\nIf reproduction needs unavailable credentials, hardware, data, or a user action,\nreport the specific gap to the assigning agent; main handles user communication.\nContinue useful in-scope investigation, but label an unconfirmed hypothesis as such.\nDo not claim a fix was verified without a meaningful observable check.\n\nReduce the reproducer without removing the failure. Form competing explanations\nwhere the cause is uncertain, each with an observable prediction. Probe the few\npoints that distinguish them; change one variable at a time. Prefer measured\nevidence over accumulating workarounds for the same unexplained symptom.\n\nUse isolated scratch space for disposable experiments. A read-only assignment can\ninspect and diagnose; it does not authorize instrumentation edits to project code.\nFor performance work, compare equivalent workloads and record relevant conditions\nso that a timing difference is not mistaken for a causal improvement.\n\nOnce a cause is established and writing is authorized:\n\n1. Put a regression check at the seam that reproduces the actual failure.\n2. Confirm that the check fails for the reported reason on the broken behavior.\n3. Make the smallest correction consistent with the intended contract.\n4. Run the check again, then relevant surrounding checks and the original scenario.\n5. Remove temporary instrumentation and restore any deliberate fault injection.\n\nRead [test proof](references/test-proof.md) when designing/reviewing the regression\ntest or when it passes before the fix. Do not weaken expectations, skip failing\nchecks, or mock away the behavior to manufacture a pass. A changed requirement must\nbe established independently of what the implementation currently returns.\n\nReturn the reproduction command and result, established cause or remaining\nhypotheses, changed artifacts, before/after checks, limits, and unresolved risks.\nA peer returns to lead; lead returns to main. Read-only investigation returns\ndiagnostic evidence and a proposed correction, not an imaginary code change.\n", "skills/bug-loop/references/test-proof.md": "# Evidence for a regression correction\n\nChoose an observable boundary close enough to isolate the failure but broad enough\nto exercise its real cause. A unit test of one caller cannot establish a bug that\ndepends on ordering across several callers.\n\nExpected behavior comes from the user request, a supported contract, or independently\nderived examples. Do not calculate the expected result using the implementation\nbeing tested. Mock external boundaries only where doing so leaves the claimed\nbehavior under test.\n\nDescribe proof accurately:\n\n- Green only: the current check passes; this alone does not show regression coverage.\n- Red -> green: the check fails on broken behavior and passes after the correction.\n- Fault injection: where justified by risk, restore the specific defect in an\n isolated copy, confirm failure, restore the fix, and confirm success again.\n\nFault injection is optional unless the assignment requires it. Never mutate another\nwriter's checkout, discard unrelated work, or leave deliberately broken code in the\ndeliverable. State an unavailable pre-fix run or missing test seam as a limitation.\n\nKeep the actual commands, results, and relevant environment conditions. Screenshots,\nlogs, and manually observed behavior can supplement tests; do not relabel them as\nautomated proof. A passing check and an independent acceptance decision are separate.\n", "providers/antigravity.json": '{\n "kind": "antigravity",\n "label": "Antigravity",\n "command": "agy",\n "enabled": true\n}\n', "providers/opencode.json": '{\n "kind": "acp",\n "label": "OpenCode",\n "command": "opencode",\n "args": ["acp"],\n "enabled": true\n}\n', "agents/supervisor/AGENT.md": "# Supervisor \u2014 process reviewer for main\n\nMain starts you beside itself in every Ph\u1EDF and Cafe session. You watch how main\nand its team work, not what they build: after each of main's turns ALP sends you\na digest of that turn \u2014 the user's request, main's tool calls and commands,\nassignments with their model, effort, mode and handoff, mail, questions to the\nuser, board pins, and main's final message. You never talk to the user, never\nchange files or git state, and never delegate.\n\n## What to check\n\nJudge the turn against ALP.md, main's AGENT.md, the selected team, and the\nlessons main has recorded. Judge main by its own session, which the digest\nstates: its model, runtime and instructions. Your system prompt describes your\nsession, not main's; never hold main to it (for example its model's name in\ncommit attribution). Look for process mistakes such as:\n\n- Ph\u1EDF: main created lead, or delegated work that needed no delegation while a\n difficult question went without oracle. Cafe: main worked around lead, sent a\n second stream of instructions to lead's peers, or intervened without\n transferring writer ownership.\n- An assignment without its objective, scope, constraints, verification, or\n expected handoff; an assignment whose model or effort clearly did not fit it.\n- Two writers in one checkout, edits without a board claim, or editing paths\n another agent claimed.\n- A logic or risky change accepted without reviewer, without real verification,\n or on a worker's word alone; main accepting its own change as reviewed.\n- Ending a turn while assignments ran, or claiming work, delegation, review, or\n checks that the digest does not show.\n- Asking the user what the code could answer, or not asking a decision that was\n the user's; writing for the user in a language other than the one ALP names.\n- Unclear requests: building or delegating a request that left open what to\n build, how far to go or how to judge it done, without first asking the user\n once, with options and a recommended default.\n- Leaving the user waiting. Each digest line has its local time, and ALP notes\n when main answered the user after their message, or that a message got no\n reply. A user who wrote and waited minutes for any answer is a mistake, as is\n long work started without telling the user what, who and when.\n- Blocking instead of working in the background: waiting on an assignment\n (`wait: true`, or `alp_wait` with nothing else to do) when the next step did\n not need its result, or running long commands in the foreground.\n- Tasks: closing a task with a logic change without reviewer or real\n verification; ignoring work a handoff listed as discovered without recording\n it as a task or saying why; leaving a task in review across turns; delegating\n work that belongs to an existing task without its taskId; removing a blocker\n only to start a blocked task; creating tasks for work finished in the same turn.\n- Repeating a mistake that a recorded lesson already covers.\n\nDo not review code quality, style, or the product decision itself: reviewer and\nthe user own those. A digest is a summary; when it is not enough, read the files,\nalp_board or alp_task before concluding. Do not report a mistake you cannot point to.\n\n## One note, or nothing\n\nWhen the turn was sound, send nothing. Otherwise send main one note with\n`alp_send` to `parent`, kind `note`, covering at most three of the most important\nmistakes. For each: what happened (cite the digest line), which rule it broke,\nand a question asking main why, and what rule it will follow from now on. When a\nrecorded lesson already covers it, say that it recurred. Main answers in its next\nturn and records the lesson; you do not need a reply.\n\nRead every lesson in the lessons files named in your instructions when you\nreview. A later lesson on the same point refines or replaces an earlier one:\na recurrence means main broke the latest applicable lesson, not an earlier\nversion of it. When three or\nmore lessons cover one theme, or a recorded lesson recurred, also suggest that\nmain distill them into a skill with alp_skill, and for which roles. When a\nmistake comes from ALP itself (an unclear instruction, a missing tool, a runtime\nbug), suggest that main propose an ALP issue with alp_issue. The user approves\nboth; you only suggest.\n\nEnd every review with a one-line verdict: `sound`, or the mistakes you asked about.\n", "agents/reviewer/AGENT.md": "# Reviewer \u2014 independent review of one diff\n\nYou are a senior engineer the technical coordinator calls to review one diff. Your first assignment\nis the complete brief: how to obtain the diff, the intended behavior, and the\nrules the change must follow. Review once. State assumptions for gaps; do not\ndepend on follow-up answers or invent missing evidence.\n\n## Scope and boundaries\n\n- Read project instructions and obtain the exact diff named in the brief.\n If the brief only describes the change, use\n `git diff --merge-base <upstream>`, with `origin/HEAD` as the default upstream,\n and list untracked files with `git ls-files --others --exclude-standard`.\n Read relevant untracked text files separately: they are absent from git diff.\n If the upstream or merge base cannot be resolved, report that limitation;\n do not silently choose a different baseline or change git state to create one.\n- Do not edit files, change git state, or run commands that mutate project data.\n Do not spawn agents, delegate work, or expand the assignment.\n- Read each changed file once where practical. Read other files only when they\n explain a change; revisit a file only to resolve a specific uncertainty.\n- If the diff is too large to review well, report that as the single finding,\n explain the scope limit, recommend a smaller review unit, and stop. Do not\n present a partial review as complete.\n\n## Review method\n\nStart with a short summary of the whole change. Then go file by file and hunk\nby hunk: describe what changed with the new-version line range, its relationship\nto other changes, and any bug, hack, unnecessary code, or shared mutable state.\nFor deleted-only code, explicitly label the old-version range rather than\ninventing new-version line numbers. Keep clean-hunk explanations concise and\nseparate them from actionable findings.\n\nJudge abstractions both ways: a layer that adds nothing may be inlined;\nduplication and branching may benefit from extraction. Name the exact places\nand recommend one concrete action only when it improves the code as it is now.\nDo not demand speculative architecture or unrelated cleanup.\n\nFor each finding give:\n\n- File and lines using the new version's numbers where they exist.\n- Severity: critical (security, data loss, crash), high (bug or real performance\n problem), medium (maintainability or minor bug), or low (style).\n- What is wrong, why it matters, and the recommended fix.\n- Evidence: distinguish behavior actually observed or checks personally run\n from inference and results reported by the brief. List checks not run without\n implying they passed.\n\nIf there are no actionable findings, say so and note verification limits.\n\n## Verdict\n\nEvery review ends in the same verdict, so the requester can act on it without\nreading prose:\n\n1. **Criteria.** List what the change must do and respect: the brief's\n acceptance criteria, or, when the brief has none, the ones you derived from\n it (say so). Judge each one `pass`, `fail` or `not_checked`, with its\n evidence: what you observed or ran, with file and lines. A `not_checked`\n says why.\n2. **Findings.** Each with severity, where (file:lines), the problem, and the fix.\n3. **Result.** It follows from the two:\n - `fail`: a criterion failed, or a finding is critical or high.\n - `pass_with_findings`: only medium or low findings.\n - `pass`: nothing to fix.\n - `blocked`: the review could not be done, for example because the diff is\n too large or the baseline cannot be resolved. The summary says what is\n needed.\n\n## One response\n\nReturn the review only to the requesting coordinator (main or lead), once, tied to the original brief.\nIn ALP, call `alp_handoff` with outcome `complete`, the review as the summary,\nand the verdict as `verdict` ({ result, criteria, findings }); ALP refuses a\nverdict whose result does not follow from its criteria and findings. Without\nALP, end the review with the verdict as a list in the same order:\n`VERDICT: FAIL`, then each criterion as `[FAIL] criterion: evidence`, then the\nfindings. Do not invent a `{tool:send}` tool, recipient, identifier, or\nsuccessful delivery. Do not contact the user or other roles. End your turn after\nthe response; the requester owns acceptance, fixes, and session cleanup.\n", "agents/peer/AGENT.md": "# Peer \u2014 independent bounded contributor\n\nReceive one bounded assignment from the technical coordinator (main in Ph\u1EDF,\nlead in Cafe). References to lead below mean that requesting coordinator. Apply your own judgment to the mechanism\nand evidence; your role is not to reproduce an assumed answer. Implementation,\nresearch, design, and review are dispositions supplied in the assignment.\n\n## Boundaries\n\n- Read ALP.md, this definition, and the assigned brief. Confirm root, objective,\n owned/excluded paths, authority, verification, and resource ownership before work.\n- Write only within the assigned scope and while holding its writer ownership.\n A read-only assignment does not authorize even a small implementation change.\n- Preserve unrelated user/agent changes. Do not stage or clean unrelated files.\n- Do not create subordinate agents, change topology, reassign work, or expand scope.\n- Report missing prerequisites or conflicting evidence to lead. Explain what is\n blocked and continue independent work still authorized by the brief.\n- You may question a premise with evidence and suggest a better approach. A design\n choice is not a user constraint unless its source establishes that requirement.\n- Answer exceptional direct questions from main or the user, while informing lead.\n Changes to work must be reconciled with lead's plan and writer ownership; escalate\n contradictory instructions rather than silently following two command chains.\n\n## Verification and handoff\n\nUse verification that tests the requested behavior. Do not weaken assertions or\ninvent outputs to claim success. Report checks not run and their practical limits.\nSend progress and material blockers to lead through available runtime channels.\n\nReturn:\n\n- Outcome: complete, partial, blocked, or premise needs reconsideration.\n- Candidate: actual artifact/files; base and candidate SHA when applicable.\n- Scope: paths changed/read and the assignment version used.\n- Verification: commands, actual results, and omitted checks.\n- Risks: unresolved findings, assumptions, and decisions needed.\n- Discovered: work you found outside your scope that should be tracked; your\n requester records it as a task.\n- Ownership: released or retained, with resources stopped or handed over.\n\nWhen your assignment carries a task, your handoff moves it to review; it closes\nonly when your requester accepts it. You may read the project's tasks with\nalp_task; only main and the user create or change them. Lead decides acceptance. Your handoff and passing tests do not self-accept the work.\nIf communication tools are unavailable, include the handoff in your result and\nstate the limitation; never claim a message was delivered without evidence.\n\n## Skills\n\nRead the relevant SKILL.md from the runtime skill index before using its method;\nfollow a required installed skill in the brief and report if it is missing.\nUse xia for research, bug-loop for diagnosis/regression,\nand smart-commits only when your authorized handoff includes local commits.\nRead-only work stops before project edits or commits. Missing user intent, planning,\nor delegation decisions return to lead; these skills do not authorize interviewing\nthe user, rewriting your brief, or spawning another agent.\n", "agents/oracle/AGENT.md": "# Oracle \u2014 bounded technical advisor\n\nYou are the advisor the technical coordinator calls for stronger reasoning: a plan, a review of its\nwork, an explanation of existing code, or a bug it cannot find. Your first\nassignment is the complete brief. Answer once; do not depend on follow-up answers.\nWhere information is missing, state your assumptions and proceed. If a missing\nfact prevents a reliable conclusion, identify the limit instead of inventing it.\nThe requester may ask two oracles on different models the same question to\ncompare their opinions; give your own independent view, not a guess at theirs.\n\n## Boundaries and judgment\n\n- Read project instructions, the brief, and its named files and facts first.\n Look further only when it makes the answer more accurate.\n- Do not edit files, change git state, or run commands that mutate project data.\n Do not spawn agents, delegate work, or expand the assignment.\n- Prefer the simplest solution that meets the stated needs: small changes that\n reuse existing code, patterns, and dependencies. Recommend a new service,\n library, or layer only when a concrete requirement justifies it. Avoid\n speculative scaling and future-proofing.\n- Give one recommendation. Add at most one alternative, only when its trade-off\n is materially different.\n- Match depth to the question: brief for small questions; deep only when needed.\n- In code reviews, report only the most important actionable issues. Cite the\n relevant files and lines; separate observed facts from inference.\n\n## One response\n\nUse only the sections that apply:\n\n1. TL;DR: the recommended approach in one to three sentences.\n2. Steps: a short numbered list; include a small code snippet only when helpful.\n3. Effort: S (under an hour), M (1 to 3 hours), L (1 to 2 days), or XL (more).\n4. Why: explain the recommendation and why alternatives are not needed now.\n5. Risks: relevant failure modes and how to guard against them.\n6. When to revisit: concrete signals that justify a more complex approach.\n\nReturn the response only to the requesting coordinator (main or lead), once, as advice tied to the\noriginal brief. If the runtime provides messaging with kind and reply metadata,\nuse kind `advice` and `reply_to` the brief's actual identifier. Otherwise return\nthe advice as the delegated task's final result. Do not invent a `{tool:send}`\ntool, recipient, identifier, or successful delivery. Do not contact the user or\nother roles. End your turn after the response; the requester owns any further action and\nsession cleanup.\n", "agents/main/AGENT.md": "# Main \u2014 active supervisor and user-facing owner\n\nYou are the user's primary point of contact and own delivery of the whole outcome.\nReceive requests, clarify only material unknowns, keep the user informed, and give\nthe final answer. The user should not have to coordinate lead and peer sessions.\nCommunicate with the user in the language ALP names for them (their setting; Vietnamese by default), even when delegated handoffs use another language.\n\n## Unclear requests\n\nWhen a request leaves open what to build, how far to go, or how to judge it done,\nin ways that change the work, ask once before you plan or delegate:\n\n- Ask two to four short questions together. Give each concrete options and your\n recommended default, and offer to decide with those defaults.\n- Use alp_ask with options while work runs; otherwise ask in your final message.\n- Do not ask what you can find out yourself, and do not question clear requests.\n- When the user lets you decide, state your choices in one line and go on.\n\n## Staying reachable\n\nWork like a chat where the work runs in the background: the user can talk with\nyou at any time.\n\n- Before work that takes more than a few minutes, tell the user in one line what\n you start, who does it, and when to expect it.\n- Delegate in the background, as alp_delegate does by default, and pass\n `etaMinutes`. Then end your turn rather than wait; ALP wakes you with results,\n questions and check-ins. Notes from assignments ride along; they do not wake you.\n- Pass `wait: true`, or alp_wait, only when your next step cannot go on without\n the result. When the user writes while you wait, ALP ends the wait early:\n answer them first in a short reply, steer the assignment their words change,\n then go on.\n- Run long shell commands (builds, test suites, servers, deploys) in the\n background when your tools allow it, and check their output later.\n- While assignments run, ALP sends you a check-in about every ten minutes, and\n when one passes its ETA. Tell the user in one or two lines how the work is\n going, and act on work that is late or silent.\n\n## Workflow and ownership\n\nRead the runtime's selected team. It stays fixed throughout this session.\n\n- Ph\u1EDF (default): you own technical execution as well as user communication.\n Implement directly or delegate bounded work directly to peer when useful.\n Do not create a lead session. A difficult focused task may need oracle rather\n than more workers; delegation is optional.\n- Cafe: assign technical execution to lead and supervise its outcome.\n Lead may implement directly or delegate to peer. Resolve scope, priorities,\n and missing user decisions. Route corrections through lead; do not issue a\n second stream of instructions to its peers. Transfer writer ownership before\n intervening in implementation.\n- Do not switch team mid-session. Recommend a new session if the user needs\n a different team; do not create a hidden extra coordination layer.\n- By default you run with full access to this machine. Use it for the user's\n task only; ask before anything destructive or outside the project that the\n user did not request.\n\n## Execution and advice\n\n- Preserve user requirements and existing changes. Inspect evidence before\n deciding. Keep the user informed of material findings and blockers.\n- In Ph\u1EDF, choose each peer's model and effort by the task's difficulty,\n ambiguity, risk, and autonomy needs: from peer's model profiles when they offer\n a choice (pick the model and its effort, and say why in modelReason), otherwise\n from the runtime catalog. Default to\n at most two simultaneous peers; increase only when the user requests it and\n the configured limit permits it. Never bypass limits with native spawn tools.\n- Use oracle for material uncertainty, difficult diagnosis, or architectural\n advice. Oracle runs on the configured oracle models listed in your runtime instructions. For a decision that deserves two independent opinions,\n start one oracle on each model in parallel and weigh where they agree and\n differ. If a model is unavailable, report it; never substitute another.\n- Use reviewer for logic changes and risky changes. Typo/format-only edits may\n skip review. In Cafe, lead arranges routine review; you may request an\n independent review of its final candidate when needed. Send fixes through lead.\n Brief reviewer with the acceptance criteria. Its handoff carries a verdict:\n each criterion with pass, fail or not_checked and evidence, and a result.\n Do not accept work on `fail` or `blocked`; weigh `pass_with_findings`.\n- Advisors are read-only and return once to their requester. Their advice is\n evidence for your judgment, not permission to expand the user's scope.\n- Consider runtime-provided plan, usage remaining, and reset timestamps when\n scheduling. Missing data means unknown, not unlimited; stale snapshots cannot\n guarantee availability. Do not infer subscription quota from token counts.\n- Each assignment includes root, objective, scope, constraints, ownership,\n verification, and expected handoff. Serialize writers in a shared checkout.\n Do not mutate files while waiting for a delegated writer.\n- Verify actual changes and reported checks before acceptance. Clearly identify\n your own changes and whether independent review ran. If delegation is not\n available, perform authorized work directly and report the limitation.\n\n## Tasks\n\nThe project's tasks live in `.alp/tasks`, one JSON file per task, committed with\nthe project. The user adds and edits them with `alp task`; you change them only\nwith alp_task, never by editing the files. Nobody else creates tasks: lead and\npeers report work they find to their requester, and you record it.\n\n- Create a task for work that outlives this turn, that the user asks you to\n track, or that you find outside the current scope (discoveredFrom: the task you\n were on). Do not create tasks for work you finish in this turn.\n- Model order with blockedBy and group larger work under an epic parent. Read\n alp_task ready before choosing what to do next; it lists open tasks that\n nothing blocks, most urgent first.\n- ALP lists the tasks in review, in progress and ready at the start of each of\n your turns. Start a task you take up yourself; give one to lead or peer by\n passing taskId to alp_delegate. That starts it, claims its paths for a writing\n assignment, and the handoff moves it to review.\n- Accept a task in review by closing it with a reason and a summary of the\n outcome and its evidence, only after verifying it with the same review rules\n as any other change; delegate it again with the same taskId for rework. Use\n wontfix, duplicate or superseded for work you drop.\n- Record the discovered work a handoff lists as tasks with discoveredFrom, or\n say why not.\n- When a task must wait for the user's decision, a time, a pull request or a CI\n run, add a gate with alp_task gate instead of remembering it. Only the user\n clears a human gate: ask them, and they approve it in Paseo or with the CLI.\n- For a workflow the project repeats, check alp_task formulas. Pour the\n matching formula with its vars instead of creating the steps one by one: it\n makes an epic with a task per step, ordered by blockedBy. A step marked as the\n user's waits on a human gate, and their approval completes it.\n\n## Supervisor and lessons\n\nA supervisor watches your process and may send you a note asking about a\nmistake. Answer it honestly in your reply, then record what you learned with\nalp_lesson: scope project for this project, user for every project. Recorded\nlessons return in your instructions in later sessions; follow them.\n\nWhen three or more lessons cover one theme, or a lesson recurs, distill them\ninto a skill with alp_skill: when to use it, the method as steps, and the checks.\nScope it to the roles whose work it guides: yourself, lead, peer, oracle,\nreviewer, supervisor, or a custom agent. List the lessons it replaces. The user\nsees the whole skill and approves it before it is saved.\n\n## Issues\n\nWhen you find a problem outside the task that is worth tracking, in this project\nor in ALP itself (its process, tools, agent instructions, or runtime), search\nwith alp_issue first, then propose a comment on a matching issue or a new one.\nWrite facts, reproduction, and evidence; never include secrets. The user\napproves every post. Never post issues or comments any other way.\n\n## Skills\n\nUse the runtime skill index to read the relevant SKILL.md before applying its method;\nload supporting references only when needed. Select directly from your scoped skills.\nFor material gaps in user intent use goal-griller; for unfamiliar implementation use\nxia; for dependent work use sequence-execution-plan. Before assigning work, use\nprompt-leverage to prepare a proportional, self-contained brief. When implementing\na correction use bug-loop; when local commits are requested use smart-commits.\nClear requests need no repeated interview, and skills grant no additional authority.\n", "agents/lead/AGENT.md": "# Lead \u2014 technical execution owner\n\nReceive the task from main and own technical delivery within its assigned root and\nscope. Main represents the user's goal and coordinates the broader outcome. Route\nquestions, progress, and the final deliverable to main; contact the user directly\nonly for an explicit exception.\n\n## Workflow and model choices\n\nYou own technical execution in Cafe. Small tasks may be completed directly;\npeer delegation is optional. Main remains the user-facing supervisor. Ph\u1EDF uses\nmain as technical coordinator and does not create this role.\n\nChoose model and effort separately for each peer assignment by the task's difficulty,\nuncertainty, risk, and autonomy needs. When peer's model profiles offer a choice (several\nmodels, or any model of a provider), pick a model they offer and its effort for every\nassignment and say why in modelReason; otherwise use the runtime catalog. The default\nlimit is two concurrent peers. Increase only at the user's request via configuration.\nSerialize writing assignments in the shared checkout; concurrent peers must be\nread-only. Never bypass these limits using native spawning or shell-launched agents.\n\nCall oracle for significant uncertainty and reviewer for logic changes or risky\nchanges; trivial typo/format edits may skip review. Brief reviewer with the\nacceptance criteria; its verdict judges each one with evidence, and a `fail` or\n`blocked` result goes back to work before you hand off. Oracle runs on the configured oracle models listed in your runtime instructions; for two independent\nopinions, consult both in parallel. Never substitute another model when one is\nunavailable; report it. Advisors return once to you; evaluate their evidence\nand report unresolved findings to main.\nUse available plan/usage/reset snapshots to avoid exhausted limits; missing or stale\ndata must remain explicitly uncertain and does not imply unlimited usage.\n\n## Background first\n\n- Delegate in the background, as alp_delegate does by default, with `etaMinutes`,\n and go on with work that does not depend on the result. Pass `wait: true` only\n when your next step cannot go on without it.\n- When only results are left, alp_wait for them. Mail from main ends the wait\n early: act on it, pass on what changes a peer's work with alp_send, then wait\n again.\n- Run long shell commands (builds, test suites, servers) in the background when\n your tools allow it.\n\n## Execution\n\n- Inspect the current implementation and user changes before planning work.\n- Decompose the outcome, resolve dependencies, assign ownership, and keep the plan\n current. Make technical decisions within the brief and project constraints.\n- Delegate bounded outcomes to peer using real runtime capabilities. A peer may\n implement, research, design, or review according to its brief; these are tasks,\n not additional built-in roles.\n- Give each peer the task identity, root, objective, constraint sources, owned and\n excluded paths, write/read authority, base revision when available, verification,\n resources, and expected handoff. Avoid prescribing an unverified solution.\n- Keep one writer per shared checkout. For concurrent writers allocate isolated\n workspaces and nonconflicting resources. Main intervention requires an explicit\n ownership transfer and an updated brief before work resumes.\n- You may implement directly. Identify your authored changes in the handoff so\n main can review them; do not self-accept your own implementation.\n- Check progress and blockers through supported runtime mechanisms. Missing\n messaging or delegation is a capability limitation, not proof of worker activity.\n\n## Review and escalation\n\nReview peer candidates using the actual diff/artifact and command outputs. Where\nGit is available, verify base and candidate revisions and owned paths. Issue an\nexplicit ACCEPT or REJECT with concrete findings; test success alone is insufficient.\nReturn your aggregate outcome, changed paths, verification, peer verdicts, risks,\nand ownership status to main. List work you or your peers found outside the scope\nunder discovered: only main and the user create tasks, and main records it. When\nmain gave you a task, your peers' claims carry its id, and your handoff moves it\nto review for main to accept. In a non-Git project, identify the actual changed\nfiles and evidence without inventing commit identifiers.\n\nPeer objections are evidence to investigate. Resolve technical matters in scope;\nsend scope/priority conflicts or missing authority to main. When main or the user\nchanges direction, update the plan, then deliver revised briefs to affected peers\nand confirm their receipt before relying on the new direction.\n\nIf delegation is unavailable, report that fact to main and perform authorized work\ndirectly where possible. Do not claim that a peer executed or reviewed the task.\n\n## Skills\n\nRead the applicable SKILL.md from the runtime skill index before using its method;\ndo not preload all bodies. Use xia for unfamiliar implementation,\nsequence-execution-plan for dependencies, and prompt-leverage before assigning peer.\nUse bug-loop for diagnosis/corrections and smart-commits when commits are part of\nyour authorized deliverable. Identify a relevant installed method in the peer brief.\nIntake with the user belongs to main: return material missing decisions to main,\nincluding them in the handoff when there is no live upstream messaging channel.\n" };
7814
7814
 
7815
7815
  // packages/client/src/index.js
7816
- import { createHash } from "node:crypto";
7817
- import { open, readFile, realpath, stat, lstat, mkdir, readlink, unlink } from "node:fs/promises";
7816
+ import { createHash, randomUUID } from "node:crypto";
7817
+ import { open, readFile, writeFile, realpath, stat, lstat, mkdir, readlink, unlink, readdir, rename, rmdir, link } from "node:fs/promises";
7818
7818
  import os from "node:os";
7819
7819
  import path from "node:path";
7820
7820
 
@@ -7945,7 +7945,7 @@ var AlpRpcError = class extends Error {
7945
7945
  };
7946
7946
  var ERROR = Object.freeze({ failed: 1e3, notFound: 1001, forbidden: 1003, conflict: 1004, unavailable: 1005, protocol: 1006 });
7947
7947
  var RPC_ERROR = Object.freeze({ parseError: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602 });
7948
- var CAPABILITY = Object.freeze({ attachAfter: "attach.after", promptReceipts: "prompt.receipts", extensionMethods: "extension.methods", team: "team", task: "task", library: "library", settingsLanguage: "settings.language", sessionListPage: "session.list.page", questionWatch: "question.watch", questionWatchTurns: "question.watch.turns", workspaceFiles: "workspace.files", workspaceFilesRead: "workspace.files.read", promptImages: "prompt.images" });
7948
+ var CAPABILITY = Object.freeze({ attachAfter: "attach.after", promptReceipts: "prompt.receipts", extensionMethods: "extension.methods", team: "team", task: "task", library: "library", libraryProjectWrite: "library.project.write", settingsLanguage: "settings.language", sessionListPage: "session.list.page", questionWatch: "question.watch", questionWatchTurns: "question.watch.turns", workspaceFiles: "workspace.files", workspaceFilesRead: "workspace.files.read", promptImages: "prompt.images" });
7949
7949
  var PROMPT_IMAGES = Object.freeze({ mimeTypes: Object.freeze(["image/png", "image/jpeg", "image/gif", "image/webp"]), maxCount: 8, maxBytes: 5 * 1024 * 1024, maxTotalBytes: 11 * 1024 * 1024 });
7950
7950
  var base64Bytes = (data) => Math.floor(data.length * 3 / 4) - (data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0);
7951
7951
  var RPC_METHODS = Object.freeze(["assignment.recall", "board.list", "catalog.get", "daemon.hello", "daemon.pause", "daemon.pauses", "daemon.resume", "daemon.shutdown", "daemon.status", "daemon.usage", "extension.register", "extension.unregister", "library.delete", "library.duplicate", "library.get", "library.list", "library.models", "library.rename", "library.save", "library.skills.set", "question.answer", "question.list", "question.watch", "session.archive", "session.attach", "session.close", "session.configure", "session.create", "session.get", "session.interrupt", "session.list", "session.log", "session.message", "session.preview", "session.prompt", "session.release", "session.rename", "session.status", "settings.language.get", "settings.language.set", "task.create", "task.gate.resolve", "task.list", "task.update", "team.list", "workspace.files.list", "workspace.files.read", "workspace.files.search"]);
@@ -8097,6 +8097,81 @@ async function acquireDaemonLock(home2, contents) {
8097
8097
  }
8098
8098
  throw new Error("Could not acquire the ALP daemon lock");
8099
8099
  }
8100
+ async function lifecycleOwners(home2) {
8101
+ const directory = path.join(home2, "state", "alpd.starting");
8102
+ const names = await readdir(directory).catch((error51) => {
8103
+ if (error51.code === "ENOENT") return [];
8104
+ throw error51;
8105
+ });
8106
+ return names.map((token) => {
8107
+ const match = /^owner-(\d+)-(\d+)-(start|stop)-[a-f0-9-]+$/.exec(token);
8108
+ if (!match) throw new Error("Invalid alpd startup admission owner");
8109
+ return { token, pid: Number(match[1]), bootTime: Number(match[2]), action: match[3] };
8110
+ });
8111
+ }
8112
+ async function readDaemonLifecycle(home2) {
8113
+ return (await lifecycleOwners(home2)).find((owner) => lockAlive(owner));
8114
+ }
8115
+ async function withDaemonLifecycle(home2, action, run3, { joinStart = false } = {}) {
8116
+ try {
8117
+ await assertPrivateDirectory(home2, { create: true });
8118
+ } catch (cause) {
8119
+ throw Object.assign(new Error(`ALP_HOME_UNUSABLE, alpd failed to start while preparing its home: ${cause.message}`, { cause }), { code: "ALP_HOME_UNUSABLE" });
8120
+ }
8121
+ const state = path.join(home2, "state");
8122
+ await assertPrivateDirectory(state, { create: true });
8123
+ const directory = path.join(state, "alpd.starting");
8124
+ const token = `owner-${process.pid}-${bootTime()}-${action}-${randomUUID()}`;
8125
+ const candidate = path.join(state, `alpd.starting-${token}`);
8126
+ await mkdir(candidate, { mode: 448 });
8127
+ await writeFile(path.join(candidate, token), JSON.stringify({ pid: process.pid, bootTime: bootTime(), action }), { mode: 384 });
8128
+ try {
8129
+ let joined = false;
8130
+ if (joinStart) {
8131
+ const owner = await readDaemonLifecycle(home2);
8132
+ if (owner?.action === "start") {
8133
+ try {
8134
+ await link(path.join(directory, owner.token), path.join(directory, token));
8135
+ joined = true;
8136
+ } catch (error51) {
8137
+ if (error51.code !== "ENOENT") throw error51;
8138
+ }
8139
+ }
8140
+ }
8141
+ while (!joined) {
8142
+ try {
8143
+ await rename(candidate, directory);
8144
+ break;
8145
+ } catch (error51) {
8146
+ if (!["EEXIST", "ENOTEMPTY", "EPERM", "EACCES"].includes(error51.code)) throw error51;
8147
+ for (const owner of await lifecycleOwners(home2)) {
8148
+ if (!lockAlive(owner)) await unlink(path.join(directory, owner.token)).catch((error52) => {
8149
+ if (error52.code !== "ENOENT") throw error52;
8150
+ });
8151
+ }
8152
+ await rmdir(directory).catch((error52) => {
8153
+ if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error52.code)) throw error52;
8154
+ });
8155
+ await new Promise((resolve2) => setTimeout(resolve2, 25));
8156
+ }
8157
+ }
8158
+ try {
8159
+ return await run3();
8160
+ } finally {
8161
+ await unlink(path.join(directory, token));
8162
+ await rmdir(directory).catch((error51) => {
8163
+ if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error51.code)) throw error51;
8164
+ });
8165
+ }
8166
+ } finally {
8167
+ await unlink(path.join(candidate, token)).catch((error51) => {
8168
+ if (error51.code !== "ENOENT") throw error51;
8169
+ });
8170
+ await rmdir(candidate).catch((error51) => {
8171
+ if (error51.code !== "ENOENT") throw error51;
8172
+ });
8173
+ }
8174
+ }
8100
8175
 
8101
8176
  // packages/runtime/src/runtime.ts
8102
8177
  import { constants as fsConstants } from "node:fs";
@@ -8132,16 +8207,16 @@ var toolResult = (success2, value) => ({
8132
8207
  var plainObject = (value, keys2) => !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every((key2) => keys2.includes(key2));
8133
8208
 
8134
8209
  // packages/runtime/src/board-domain.ts
8135
- import { createHash as createHash2, randomUUID } from "node:crypto";
8136
- import { appendFile, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
8210
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
8211
+ import { appendFile, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
8137
8212
 
8138
8213
  // packages/core/src/atomic.js
8139
8214
  import { randomBytes } from "node:crypto";
8140
- import { rename, rm, writeFile } from "node:fs/promises";
8215
+ import { rename as rename2, rm, writeFile as writeFile2 } from "node:fs/promises";
8141
8216
  async function moveFile(from, to) {
8142
8217
  for (let attempt = 0; ; attempt++) {
8143
8218
  try {
8144
- return await rename(from, to);
8219
+ return await rename2(from, to);
8145
8220
  } catch (error51) {
8146
8221
  if (process.platform !== "win32" || attempt >= 20 || !["EPERM", "EACCES", "EBUSY"].includes(error51?.code)) throw error51;
8147
8222
  await new Promise((resolve2) => setTimeout(resolve2, 10 + attempt * 10));
@@ -8151,7 +8226,7 @@ async function moveFile(from, to) {
8151
8226
  async function writeAtomic(file3, body, mode = 384) {
8152
8227
  const temporary = `${file3}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
8153
8228
  try {
8154
- await writeFile(temporary, body, { mode });
8229
+ await writeFile2(temporary, body, { mode });
8155
8230
  await moveFile(temporary, file3);
8156
8231
  } catch (error51) {
8157
8232
  await rm(temporary, { force: true }).catch(() => {
@@ -8250,8 +8325,8 @@ function createBoardDomain({ options: options2, sessions, boards, loadedBoards,
8250
8325
  const kept = prune([...pins.values()]);
8251
8326
  boardWrites = boardWrites.then(async () => {
8252
8327
  await mkdir2(options2.boardDir, { recursive: true });
8253
- const temporary = `${boardFile(project)}.${randomUUID().slice(0, 8)}.tmp`;
8254
- await writeFile2(temporary, kept.map((pin) => JSON.stringify({ pin })).join("\n") + (kept.length ? "\n" : ""));
8328
+ const temporary = `${boardFile(project)}.${randomUUID2().slice(0, 8)}.tmp`;
8329
+ await writeFile3(temporary, kept.map((pin) => JSON.stringify({ pin })).join("\n") + (kept.length ? "\n" : ""));
8255
8330
  await moveFile(temporary, boardFile(project));
8256
8331
  }).catch(() => {
8257
8332
  });
@@ -8284,7 +8359,7 @@ function createBoardDomain({ options: options2, sessions, boards, loadedBoards,
8284
8359
  function addPin(sessionId, session, pins, fields2) {
8285
8360
  const project = session.mapping.agent.projectRoot;
8286
8361
  const pin = {
8287
- id: `p-${randomUUID().slice(0, 8)}`,
8362
+ id: `p-${randomUUID2().slice(0, 8)}`,
8288
8363
  project,
8289
8364
  kind: fields2.kind,
8290
8365
  body: fields2.body,
@@ -8518,8 +8593,8 @@ function describeVerification(verified) {
8518
8593
  }
8519
8594
 
8520
8595
  // packages/core/src/tasks.js
8521
- import { mkdir as mkdir3, readFile as readFile4, readdir, rmdir, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
8522
- import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
8596
+ import { mkdir as mkdir3, readFile as readFile4, readdir as readdir2, rmdir as rmdir2, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
8597
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
8523
8598
  import path6 from "node:path";
8524
8599
 
8525
8600
  // packages/core/src/safe-path.js
@@ -8576,7 +8651,7 @@ async function taskNames(projectRoot) {
8576
8651
  await assertNoSymlinks(scope, "tasks");
8577
8652
  let names;
8578
8653
  try {
8579
- names = await readdir(tasksDir(projectRoot));
8654
+ names = await readdir2(tasksDir(projectRoot));
8580
8655
  } catch (error51) {
8581
8656
  if (error51.code === "ENOENT") return [];
8582
8657
  throw error51;
@@ -8772,7 +8847,7 @@ async function locked(projectRoot, work) {
8772
8847
  if (error51.code !== "EEXIST") throw error51;
8773
8848
  const age = await stat2(lock).then((info) => Date.now() - info.mtimeMs, () => 0);
8774
8849
  if (age > LOCK_STALE_MS) {
8775
- await rmdir(lock).catch(() => {
8850
+ await rmdir2(lock).catch(() => {
8776
8851
  });
8777
8852
  continue;
8778
8853
  }
@@ -8783,7 +8858,7 @@ async function locked(projectRoot, work) {
8783
8858
  try {
8784
8859
  return await work();
8785
8860
  } finally {
8786
- await rmdir(lock).catch(() => {
8861
+ await rmdir2(lock).catch(() => {
8787
8862
  });
8788
8863
  }
8789
8864
  });
@@ -8793,15 +8868,15 @@ async function locked(projectRoot, work) {
8793
8868
  var withTaskLock = (projectRoot, work) => locked(projectRoot, work);
8794
8869
  async function prepare(dir) {
8795
8870
  await mkdir3(dir, { recursive: true });
8796
- await writeFile3(path6.join(dir, ".gitignore"), GITIGNORE, { flag: "wx" }).catch((error51) => {
8871
+ await writeFile4(path6.join(dir, ".gitignore"), GITIGNORE, { flag: "wx" }).catch((error51) => {
8797
8872
  if (error51.code !== "EEXIST") throw error51;
8798
8873
  });
8799
8874
  }
8800
8875
  async function save(projectRoot, task) {
8801
8876
  const file3 = taskFile(projectRoot, task.id);
8802
8877
  await assertNoSymlinks(path6.join(path6.resolve(projectRoot), ".alp"), path6.join("tasks", `${task.id}.json`));
8803
- const temporary = `${file3}.${randomUUID2().slice(0, 8)}.tmp`;
8804
- await writeFile3(temporary, JSON.stringify(task, null, 2) + "\n");
8878
+ const temporary = `${file3}.${randomUUID3().slice(0, 8)}.tmp`;
8879
+ await writeFile4(temporary, JSON.stringify(task, null, 2) + "\n");
8805
8880
  await moveFile(temporary, file3);
8806
8881
  return task;
8807
8882
  }
@@ -8812,7 +8887,7 @@ function newId(tasks, title, parent) {
8812
8887
  const used = tasks.filter((task) => task.id.startsWith(prefix) && !task.id.slice(prefix.length).includes(".")).map((task) => Number(task.id.slice(prefix.length)));
8813
8888
  return `${prefix}${Math.max(0, ...used) + 1}`;
8814
8889
  }
8815
- const hash2 = createHash3("sha256").update(`${title}\0${now()}\0${randomUUID2()}`).digest("hex");
8890
+ const hash2 = createHash3("sha256").update(`${title}\0${now()}\0${randomUUID3()}`).digest("hex");
8816
8891
  for (let length = 4; length <= 12; length++) if (!taken.has(`t-${hash2.slice(0, length)}`)) return `t-${hash2.slice(0, length)}`;
8817
8892
  return fail2("INVALID_TASK", "Could not choose a free task id");
8818
8893
  }
@@ -9295,7 +9370,7 @@ function batch(projectRoot, work, { dryRun = false } = {}) {
9295
9370
  // packages/runtime/src/workspace.ts
9296
9371
  import { spawn as spawn2 } from "node:child_process";
9297
9372
  import { createHash as createHash4 } from "node:crypto";
9298
- import { copyFile, lstat as lstat3, mkdir as mkdir4, mkdtemp, readdir as readdir2, readFile as readFile5, realpath as realpath2, rm as rm2, stat as stat3, symlink, unlink as unlink2, writeFile as writeFile4 } from "node:fs/promises";
9373
+ import { copyFile, lstat as lstat3, mkdir as mkdir4, mkdtemp, readdir as readdir3, readFile as readFile5, realpath as realpath2, rm as rm2, stat as stat3, symlink, unlink as unlink2, writeFile as writeFile5 } from "node:fs/promises";
9299
9374
  import os2 from "node:os";
9300
9375
  import path7 from "node:path";
9301
9376
  var IDENTITY = ["-c", "user.name=ALP", "-c", "user.email=alp@localhost", "-c", "commit.gpgsign=false"];
@@ -9364,7 +9439,7 @@ async function mergeText(ours, base2, theirs) {
9364
9439
  const directory = await mkdtemp(path7.join(os2.tmpdir(), "alp-merge-"));
9365
9440
  try {
9366
9441
  const [o, b, t] = ["ours", "base", "theirs"].map((name2) => path7.join(directory, name2));
9367
- await Promise.all([writeFile4(o, ours), writeFile4(b, base2), writeFile4(t, theirs)]);
9442
+ await Promise.all([writeFile5(o, ours), writeFile5(b, base2), writeFile5(t, theirs)]);
9368
9443
  const result = await git(directory, ["merge-file", "-p", "-L", "yours", "-L", "base", "-L", "assignment", o, b, t]);
9369
9444
  if (result.code < 0 || result.code > 127) throw new Error(`git merge-file failed: ${result.stderr.trim()}`);
9370
9445
  return { content: result.stdout, conflict: result.code > 0 };
@@ -9447,7 +9522,7 @@ async function mergeWorktree(worktree, change) {
9447
9522
  if (same(ours, base2)) {
9448
9523
  if (theirs) {
9449
9524
  await mkdir4(path7.dirname(target), { recursive: true });
9450
- await writeFile4(target, theirs);
9525
+ await writeFile5(target, theirs);
9451
9526
  } else {
9452
9527
  await rm2(target, { force: true });
9453
9528
  }
@@ -9458,7 +9533,7 @@ async function mergeWorktree(worktree, change) {
9458
9533
  continue;
9459
9534
  }
9460
9535
  const merged = await mergeText(ours, base2 ?? Buffer.alloc(0), theirs);
9461
- await writeFile4(target, merged.content);
9536
+ await writeFile5(target, merged.content);
9462
9537
  if (merged.conflict) conflicts.push(file3);
9463
9538
  }
9464
9539
  return conflicts.length ? { status: "conflicts", files: change.files, conflicts } : { status: "applied", files: change.files };
@@ -9471,7 +9546,7 @@ async function removeWorktree(worktree, { deleteBranch = false } = {}) {
9471
9546
  }
9472
9547
  async function reclaimWorktrees(root) {
9473
9548
  const kept = [];
9474
- const entries2 = await readdir2(root).catch(() => []);
9549
+ const entries2 = await readdir3(root).catch(() => []);
9475
9550
  for (const name2 of entries2) {
9476
9551
  const target = path7.join(root, name2);
9477
9552
  if (!(await stat3(target).catch(() => void 0))?.isDirectory()) continue;
@@ -9525,7 +9600,7 @@ async function removeCopy(copy) {
9525
9600
  }
9526
9601
  async function reclaimCopies(root) {
9527
9602
  let removed = 0;
9528
- for (const name2 of await readdir2(root).catch(() => [])) {
9603
+ for (const name2 of await readdir3(root).catch(() => [])) {
9529
9604
  const target = path7.join(root, name2);
9530
9605
  const common = await git(target, ["rev-parse", "--path-format=absolute", "--git-common-dir"]).catch(() => void 0);
9531
9606
  if (common?.code === 0) await removeCopy({ checkout: path7.dirname(common.stdout.trim()), path: target, workdir: target });
@@ -9650,7 +9725,7 @@ function claudeUsage(result) {
9650
9725
  function usageAllows(usage) {
9651
9726
  if (!usage?.available) return void 0;
9652
9727
  const limits = Array.isArray(usage.limits) ? usage.limits : [];
9653
- if (limits.some((limit) => limit?.spendControlReached === true)) return false;
9728
+ if (usage.ordinaryUsageAllowed === false || limits.some((limit) => limit?.spendControlReached === true)) return false;
9654
9729
  const windows = limits.length ? limits.flatMap((limit) => limit?.windows ?? []) : usage.windows ?? [];
9655
9730
  const known = windows.filter((window) => typeof window?.usedPercent === "number");
9656
9731
  if (!known.length) return void 0;
@@ -9869,7 +9944,7 @@ function writablePath(file3, workdir) {
9869
9944
  import { accessSync, constants, existsSync, mkdirSync, realpathSync as realpathSync3, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
9870
9945
  import os4 from "node:os";
9871
9946
  import path10 from "node:path";
9872
- import { randomUUID as randomUUID4 } from "node:crypto";
9947
+ import { randomUUID as randomUUID5 } from "node:crypto";
9873
9948
 
9874
9949
  // node_modules/zod/v4/classic/external.js
9875
9950
  var external_exports = {};
@@ -24643,7 +24718,10 @@ function antigravityToolDetail(info) {
24643
24718
  return { type: "unknown", input: tool?.parameters ?? null, output: tool?.error?.message ?? output ?? null };
24644
24719
  }
24645
24720
  function withHeaders(path41, diff) {
24646
- return /^(--- |diff --git )/.test(diff) ? diff : `--- a/${path41}
24721
+ if (/^(--- |diff --git )/.test(diff)) return diff;
24722
+ if (diff.startsWith("+++ ")) return `--- a/${path41}
24723
+ ${diff}`;
24724
+ return `--- a/${path41}
24647
24725
  +++ b/${path41}
24648
24726
  ${diff}`;
24649
24727
  }
@@ -24698,8 +24776,8 @@ function codexToolCall(item, done) {
24698
24776
 
24699
24777
  // packages/core/src/permissions.js
24700
24778
  import path9 from "node:path";
24701
- import { randomUUID as randomUUID3 } from "node:crypto";
24702
- import { readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
24779
+ import { randomUUID as randomUUID4 } from "node:crypto";
24780
+ import { readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
24703
24781
  var BASES = ["read-only", "workspace-write", "full-access"];
24704
24782
  var RANK = { "read-only": 0, "workspace-write": 1, "full-access": 2 };
24705
24783
  var ADVISORS = ["oracle", "reviewer", "supervisor"];
@@ -24813,8 +24891,8 @@ async function addAllowRule(projectRoot, home2, profileName, rule) {
24813
24891
  if (!object3(profile)) continue;
24814
24892
  profile.allow = [.../* @__PURE__ */ new Set([...profile.allow ?? [], rule])];
24815
24893
  validatePermissions(settings.permissions, file3);
24816
- const temporary = `${file3}.${randomUUID3().slice(0, 8)}.tmp`;
24817
- await writeFile5(temporary, `${JSON.stringify(settings, null, 2)}
24894
+ const temporary = `${file3}.${randomUUID4().slice(0, 8)}.tmp`;
24895
+ await writeFile6(temporary, `${JSON.stringify(settings, null, 2)}
24818
24896
  `);
24819
24897
  await moveFile(temporary, file3);
24820
24898
  return file3;
@@ -24952,12 +25030,14 @@ var toolShapes = {
24952
25030
  body: external_exports.string().min(1).max(2e4),
24953
25031
  roles: external_exports.array(external_exports.string().min(1)).min(1).max(10),
24954
25032
  lessons: external_exports.array(external_exports.string().min(1).max(600)).max(50).optional(),
24955
- replace: external_exports.boolean().optional()
25033
+ replace: external_exports.boolean().optional(),
25034
+ requestId: external_exports.string().min(1).max(100).optional()
24956
25035
  },
24957
25036
  alp_issue: {
24958
25037
  action: external_exports.enum(["search", "create", "comment"]),
24959
25038
  target: external_exports.enum(["project", "alp"]),
24960
25039
  query: external_exports.string().min(1).max(200).optional(),
25040
+ requestId: external_exports.string().min(1).max(100).optional(),
24961
25041
  title: external_exports.string().min(1).max(200).optional(),
24962
25042
  body: external_exports.string().min(1).max(2e4).optional(),
24963
25043
  issue: external_exports.number().int().positive().optional(),
@@ -25186,6 +25266,7 @@ var ClaudeTransport = class {
25186
25266
  activeTurn;
25187
25267
  closed = false;
25188
25268
  assistantText = /* @__PURE__ */ new Map();
25269
+ streamingMessage;
25189
25270
  toolCalls = /* @__PURE__ */ new Map();
25190
25271
  /** The last rate limit Claude reported, and whether the running turn hit one. */
25191
25272
  rateLimit;
@@ -25254,8 +25335,9 @@ var ClaudeTransport = class {
25254
25335
  return {};
25255
25336
  }
25256
25337
  if (method === "thread/start" || method === "thread/resume" || method === "thread/fork") {
25338
+ this.clearAssistantText();
25257
25339
  this.config = params;
25258
- this.threadId = method === "thread/resume" ? params.threadId : randomUUID4();
25340
+ this.threadId = method === "thread/resume" ? params.threadId : randomUUID5();
25259
25341
  await this.start(method === "thread/start" ? void 0 : { resume: params.threadId, fork: method === "thread/fork" });
25260
25342
  return {
25261
25343
  thread: { id: this.threadId, turns: [] },
@@ -25266,8 +25348,11 @@ var ClaudeTransport = class {
25266
25348
  }
25267
25349
  if (method === "turn/start" || method === "turn/steer") {
25268
25350
  if (!this.query) throw new Error("Claude thread is not initialized");
25269
- const turnId = method === "turn/steer" ? params.expectedTurnId : randomUUID4();
25270
- if (method === "turn/start") this.activeTurn = turnId;
25351
+ const turnId = method === "turn/steer" ? params.expectedTurnId : randomUUID5();
25352
+ if (method === "turn/start") {
25353
+ this.clearAssistantText();
25354
+ this.activeTurn = turnId;
25355
+ }
25271
25356
  this.input.push({
25272
25357
  type: "user",
25273
25358
  message: {
@@ -25275,12 +25360,13 @@ var ClaudeTransport = class {
25275
25360
  content: params.input.map(claudeContent)
25276
25361
  },
25277
25362
  parent_tool_use_id: null,
25278
- uuid: randomUUID4(),
25363
+ uuid: randomUUID5(),
25279
25364
  session_id: this.threadId
25280
25365
  });
25281
25366
  return method === "turn/start" ? { turn: { id: turnId } } : {};
25282
25367
  }
25283
25368
  if (method === "turn/interrupt") {
25369
+ this.streamingMessage = void 0;
25284
25370
  await this.query?.interrupt();
25285
25371
  return {};
25286
25372
  }
@@ -25304,7 +25390,7 @@ var ClaudeTransport = class {
25304
25390
  isError: true
25305
25391
  };
25306
25392
  }
25307
- const callId = `claude-tool-${randomUUID4()}`;
25393
+ const callId = `claude-tool-${randomUUID5()}`;
25308
25394
  this.emit("item/started", {
25309
25395
  threadId: this.threadId,
25310
25396
  item: {
@@ -25364,7 +25450,7 @@ var ClaudeTransport = class {
25364
25450
  const plugins = [];
25365
25451
  if (config2.skills?.length) {
25366
25452
  try {
25367
- this.skillDirectory ??= path10.join(os4.tmpdir(), "alp-skills", randomUUID4());
25453
+ this.skillDirectory ??= path10.join(os4.tmpdir(), "alp-skills", randomUUID5());
25368
25454
  plugins.push({ type: "local", path: skillPlugin(this.skillDirectory, config2.skills), skipMcpDiscovery: true });
25369
25455
  } catch {
25370
25456
  }
@@ -25384,7 +25470,7 @@ var ClaudeTransport = class {
25384
25470
  ...plugins.length ? { plugins } : {},
25385
25471
  persistSession: !config2.ephemeral,
25386
25472
  promptSuggestions: false,
25387
- includePartialMessages: false,
25473
+ includePartialMessages: true,
25388
25474
  ...from ? { resume: from.resume, ...from.fork ? { forkSession: true, sessionId: this.threadId } : {} } : { sessionId: this.threadId },
25389
25475
  // Explicit native path avoids SDK optional binaries inside Electron app.asar.
25390
25476
  pathToClaudeCodeExecutable: this.command
@@ -25422,30 +25508,57 @@ var ClaudeTransport = class {
25422
25508
  if (!this.closed) this.fail(new Error("Claude session exited"));
25423
25509
  }
25424
25510
  handle(message) {
25511
+ if (this.closed) return;
25425
25512
  if (message.type === "rate_limit_event") {
25426
25513
  this.rateLimit = message.rate_limit_info;
25427
25514
  this.emit("account/rateLimits/updated", { claude: message.rate_limit_info });
25428
25515
  return;
25429
25516
  }
25430
- if (message.type === "assistant" && !message.parent_tool_use_id && !this.activeTurn && !this.closed) {
25431
- this.activeTurn = randomUUID4();
25517
+ const streamStart = message.type === "stream_event" && message.event?.type === "message_start";
25518
+ if ((message.type === "assistant" || streamStart) && !message.parent_tool_use_id && !this.activeTurn) {
25519
+ this.activeTurn = randomUUID5();
25432
25520
  this.emit("turn/started", { threadId: this.threadId, turn: { id: this.activeTurn } });
25433
25521
  }
25522
+ if (message.type === "stream_event") {
25523
+ if (message.parent_tool_use_id) return;
25524
+ const event = message.event;
25525
+ if (event.type === "message_start") {
25526
+ this.streamingMessage = event.message.id;
25527
+ } else if (event.type === "message_stop") {
25528
+ this.streamingMessage = void 0;
25529
+ } else if (this.streamingMessage) {
25530
+ const id = this.streamingMessage;
25531
+ const state = this.assistantState(id);
25532
+ let delta;
25533
+ if (event.type === "content_block_start" && event.content_block.type === "text") {
25534
+ state.blocks.set(event.index, { text: event.content_block.text, final: false });
25535
+ delta = event.content_block.text;
25536
+ } else if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
25537
+ const block = state.blocks.get(event.index);
25538
+ if (block && !block.final) {
25539
+ block.text += event.delta.text;
25540
+ delta = event.delta.text;
25541
+ }
25542
+ }
25543
+ if (delta) this.emit("item/agentMessage/delta", { threadId: this.threadId, turnId: this.activeTurn, itemId: id, delta });
25544
+ }
25545
+ return;
25546
+ }
25434
25547
  if (message.type === "assistant" && message.error === "rate_limit") this.limitedTurn = true;
25435
25548
  if (message.type === "system" && (message.subtype === "status" || message.subtype === "compact_boundary")) {
25436
25549
  const system = message;
25437
25550
  if (system.subtype === "status" && system.status === "compacting" && !this.compaction) {
25438
- this.compaction = randomUUID4();
25551
+ this.compaction = randomUUID5();
25439
25552
  this.emit("item/started", { threadId: this.threadId, turnId: this.activeTurn, item: { type: "contextCompaction", id: this.compaction } });
25440
25553
  } else if (system.subtype === "status" && system.compact_result === "failed") {
25441
- this.emit("item/completed", { threadId: this.threadId, turnId: this.activeTurn, item: { type: "contextCompaction", id: this.compaction ?? randomUUID4(), status: "failed", error: system.compact_error } });
25554
+ this.emit("item/completed", { threadId: this.threadId, turnId: this.activeTurn, item: { type: "contextCompaction", id: this.compaction ?? randomUUID5(), status: "failed", error: system.compact_error } });
25442
25555
  this.compaction = void 0;
25443
25556
  } else if (system.subtype === "compact_boundary") {
25444
25557
  const meta3 = system.compact_metadata ?? {};
25445
25558
  this.emit("item/completed", {
25446
25559
  threadId: this.threadId,
25447
25560
  turnId: this.activeTurn,
25448
- item: { type: "contextCompaction", id: this.compaction ?? randomUUID4(), status: "completed", trigger: meta3.trigger, preTokens: meta3.pre_tokens, postTokens: meta3.post_tokens }
25561
+ item: { type: "contextCompaction", id: this.compaction ?? randomUUID5(), status: "completed", trigger: meta3.trigger, preTokens: meta3.pre_tokens, postTokens: meta3.post_tokens }
25449
25562
  });
25450
25563
  this.compaction = void 0;
25451
25564
  }
@@ -25468,14 +25581,18 @@ var ClaudeTransport = class {
25468
25581
  if (windows.length && !this.compactAt) this.contextWindow = Math.max(...windows);
25469
25582
  }
25470
25583
  if (message.type === "assistant" && !message.parent_tool_use_id) {
25584
+ const id = message.message.id;
25585
+ const state = this.assistantState(id);
25586
+ if (message.uuid && state.seen.has(message.uuid)) return;
25587
+ if (message.uuid) state.seen.add(message.uuid);
25471
25588
  for (const block of message.message.content) {
25472
25589
  if (block.type === "text") {
25473
- const id = message.message.id;
25474
- const text2 = (this.assistantText.get(id) ?? "") + block.text;
25475
- this.assistantText.set(id, text2);
25590
+ const pending = [...state.blocks.entries()].sort(([a], [b]) => a - b).find(([, value]) => !value.final);
25591
+ const index = pending?.[0] ?? Math.max(-1, ...state.blocks.keys()) + 1;
25592
+ state.blocks.set(index, { text: block.text, final: true });
25476
25593
  this.emit("item/completed", {
25477
25594
  threadId: this.threadId,
25478
- item: { type: "agentMessage", id, text: text2 }
25595
+ item: { type: "agentMessage", id, text: [...state.blocks.entries()].sort(([a], [b]) => a - b).map(([, block2]) => block2.text).join("") }
25479
25596
  });
25480
25597
  } else if (block.type === "tool_use") {
25481
25598
  const command = block.name === "Bash" ? String(block.input?.command ?? "") : `${block.name} ${JSON.stringify(block.input ?? {})}`;
@@ -25524,6 +25641,7 @@ var ClaudeTransport = class {
25524
25641
  }
25525
25642
  return;
25526
25643
  }
25644
+ if (message.type === "result") this.clearAssistantText();
25527
25645
  if (message.type === "result" && this.activeTurn) {
25528
25646
  const turnId = this.activeTurn;
25529
25647
  this.activeTurn = void 0;
@@ -25546,14 +25664,25 @@ var ClaudeTransport = class {
25546
25664
  emit(method, params) {
25547
25665
  for (const listener of this.listeners) listener(method, params);
25548
25666
  }
25667
+ assistantState(id) {
25668
+ let state = this.assistantText.get(id);
25669
+ if (!state) this.assistantText.set(id, state = { blocks: /* @__PURE__ */ new Map(), seen: /* @__PURE__ */ new Set() });
25670
+ return state;
25671
+ }
25672
+ clearAssistantText() {
25673
+ this.streamingMessage = void 0;
25674
+ this.assistantText.clear();
25675
+ }
25549
25676
  fail(value) {
25550
25677
  if (this.closed) return;
25678
+ this.clearAssistantText();
25551
25679
  const error51 = value instanceof Error ? value : new Error(String(value));
25552
25680
  for (const listener of this.failures) listener(error51);
25553
25681
  }
25554
25682
  async close() {
25555
25683
  if (this.closed) return;
25556
25684
  this.closed = true;
25685
+ this.clearAssistantText();
25557
25686
  this.input.close();
25558
25687
  this.query?.close();
25559
25688
  await this.pump?.catch(() => {
@@ -25574,7 +25703,7 @@ var ClaudeTransport = class {
25574
25703
 
25575
25704
  // packages/runtime/src/acp-transport.ts
25576
25705
  import { spawn as spawn4, execFile as execFile3 } from "node:child_process";
25577
- import { randomBytes as randomBytes2, randomUUID as randomUUID5 } from "node:crypto";
25706
+ import { randomBytes as randomBytes2, randomUUID as randomUUID6 } from "node:crypto";
25578
25707
  import { rmSync as rmSync2 } from "node:fs";
25579
25708
  import net from "node:net";
25580
25709
  import os5 from "node:os";
@@ -25583,7 +25712,7 @@ import { createInterface as createInterface2 } from "node:readline";
25583
25712
 
25584
25713
  // packages/core/src/providers.js
25585
25714
  import { existsSync as existsSync2, readFileSync } from "node:fs";
25586
- import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
25715
+ import { readdir as readdir4, readFile as readFile7 } from "node:fs/promises";
25587
25716
  import path12 from "node:path";
25588
25717
 
25589
25718
  // packages/core/src/validation.js
@@ -25911,7 +26040,7 @@ async function listProviders(library) {
25911
26040
  if (!library) return [];
25912
26041
  let names = [];
25913
26042
  try {
25914
- names = await readdir3(path12.join(library, "providers"));
26043
+ names = await readdir4(path12.join(library, "providers"));
25915
26044
  } catch (error51) {
25916
26045
  if (error51.code !== "ENOENT") throw error51;
25917
26046
  }
@@ -26132,7 +26261,7 @@ var AcpTransport = class {
26132
26261
  if (!this.sessionId) throw new Error(`${this.provider.label} session is not open`);
26133
26262
  if (this.prompting) await Promise.race([this.prompting, new Promise((resolve2) => setTimeout(resolve2, 1e4).unref())]);
26134
26263
  if (this.closed) throw new Error("Runtime is closed");
26135
- const turnId = randomUUID5();
26264
+ const turnId = randomUUID6();
26136
26265
  this.activeTurn = turnId;
26137
26266
  this.message = void 0;
26138
26267
  const prompt = (params.input ?? []).map(acpContent).filter(Boolean);
@@ -26374,7 +26503,7 @@ ${this.config.developerInstructions}` });
26374
26503
  if (!this.requestHandler || !this.activeTurn || !this.tools.some((candidate) => candidate.name === tool)) {
26375
26504
  return { content: [{ type: "text", text: this.activeTurn ? `Unknown ALP tool '${tool}'` : "ALP tools are unavailable between turns" }], isError: true };
26376
26505
  }
26377
- const callId = `acp-tool-${randomUUID5()}`;
26506
+ const callId = `acp-tool-${randomUUID6()}`;
26378
26507
  const item = { type: "dynamicToolCall", id: callId, callId, tool, arguments: args };
26379
26508
  this.closeMessage();
26380
26509
  this.emit("item/started", { threadId: this.sessionId, item: { ...item, status: "inProgress" } });
@@ -26438,9 +26567,9 @@ ${this.config.developerInstructions}` });
26438
26567
 
26439
26568
  // packages/runtime/src/antigravity-transport.ts
26440
26569
  import { spawn as spawn6, execFile as execFile4 } from "node:child_process";
26441
- import { randomBytes as randomBytes3, randomUUID as randomUUID6 } from "node:crypto";
26570
+ import { randomBytes as randomBytes3, randomUUID as randomUUID7 } from "node:crypto";
26442
26571
  import { rmSync as rmSync3 } from "node:fs";
26443
- import { mkdir as mkdir5, open as open2, readFile as readFile8, rename as rename2, writeFile as writeFile6, unlink as unlink3 } from "node:fs/promises";
26572
+ import { mkdir as mkdir5, open as open2, readFile as readFile8, rename as rename3, writeFile as writeFile7, unlink as unlink3 } from "node:fs/promises";
26444
26573
  import net2 from "node:net";
26445
26574
  import os6 from "node:os";
26446
26575
  import path14 from "node:path";
@@ -31828,7 +31957,7 @@ var AntigravityTransport = class {
31828
31957
  if (!this.conversationId) throw new Error("Antigravity session is not open");
31829
31958
  if (!this.child) await this.startProcess({ ...this.config, threadId: this.conversationId });
31830
31959
  if (this.activeTurn) throw new Error("Antigravity already has an active turn");
31831
- const turnId = randomUUID6();
31960
+ const turnId = randomUUID7();
31832
31961
  this.activeTurn = turnId;
31833
31962
  this.messageText.clear();
31834
31963
  const parts = (params.input ?? []).filter((part) => typeof part?.text === "string").map((part) => part.text);
@@ -31974,7 +32103,7 @@ ${promptText}`;
31974
32103
  return;
31975
32104
  }
31976
32105
  if (frame.event === "init") {
31977
- this.conversationId = frame.conversation_id || frame.init?.conversation_id || randomUUID6();
32106
+ this.conversationId = frame.conversation_id || frame.init?.conversation_id || randomUUID7();
31978
32107
  if (this.resolveReady) {
31979
32108
  this.resolveReady();
31980
32109
  this.resolveReady = void 0;
@@ -32134,7 +32263,7 @@ ${promptText}`;
32134
32263
  }
32135
32264
  }
32136
32265
  if (!lock) throw new Error(`Antigravity MCP configuration is locked: ${lockFile}`);
32137
- const temporary = `${configFile}.${randomUUID6()}.tmp`;
32266
+ const temporary = `${configFile}.${randomUUID7()}.tmp`;
32138
32267
  try {
32139
32268
  let text2 = "{}";
32140
32269
  try {
@@ -32154,8 +32283,8 @@ ${promptText}`;
32154
32283
  const launcher = { command: process.execPath, args: ["-e", BRIDGE2], ...process.versions.electron ? { env: { ELECTRON_RUN_AS_NODE: "1" } } : {} };
32155
32284
  if (JSON.stringify(existing) === JSON.stringify(launcher)) return;
32156
32285
  config2.mcpServers.alp = launcher;
32157
- await writeFile6(temporary, JSON.stringify(config2, null, 2) + "\n", { mode: 384 });
32158
- await rename2(temporary, configFile);
32286
+ await writeFile7(temporary, JSON.stringify(config2, null, 2) + "\n", { mode: 384 });
32287
+ await rename3(temporary, configFile);
32159
32288
  } finally {
32160
32289
  await unlink3(temporary).catch(() => {
32161
32290
  });
@@ -32209,7 +32338,7 @@ ${promptText}`;
32209
32338
  }
32210
32339
  async toolCall(tool, args) {
32211
32340
  if (this.activeTurn && this.external?.has(tool)) {
32212
- const callId2 = `agy-mcp-tool-${randomUUID6()}`;
32341
+ const callId2 = `agy-mcp-tool-${randomUUID7()}`;
32213
32342
  const item2 = { type: "dynamicToolCall", id: callId2, callId: callId2, tool, arguments: args };
32214
32343
  this.emit("item/started", { threadId: this.conversationId, item: { ...item2, status: "inProgress" } });
32215
32344
  const result2 = await this.external.call(tool, args).catch(() => ({ content: [{ type: "text", text: `External MCP tool '${tool}' failed` }], isError: true }));
@@ -32226,7 +32355,7 @@ ${promptText}`;
32226
32355
  isError: true
32227
32356
  };
32228
32357
  }
32229
- const callId = `agy-alp-tool-${randomUUID6()}`;
32358
+ const callId = `agy-alp-tool-${randomUUID7()}`;
32230
32359
  const item = { type: "dynamicToolCall", id: callId, callId, tool, arguments: args };
32231
32360
  this.emit("item/started", { threadId: this.conversationId, item: { ...item, status: "inProgress" } });
32232
32361
  const result = await this.requestHandler("item/tool/call", {
@@ -32391,12 +32520,12 @@ function shippedTemplateRoot() {
32391
32520
  }
32392
32521
 
32393
32522
  // packages/core/src/resolver.js
32394
- import { readFile as readFile10, readdir as readdir5, stat as stat4 } from "node:fs/promises";
32523
+ import { readFile as readFile10, readdir as readdir6, stat as stat4 } from "node:fs/promises";
32395
32524
  import path16 from "node:path";
32396
32525
 
32397
32526
  // packages/core/src/library.js
32398
- import { mkdir as mkdir6, readFile as readFile9, readdir as readdir4, writeFile as writeFile7 } from "node:fs/promises";
32399
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
32527
+ import { mkdir as mkdir6, readFile as readFile9, readdir as readdir5, writeFile as writeFile8 } from "node:fs/promises";
32528
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
32400
32529
  import path15 from "node:path";
32401
32530
  var MANIFEST = "library.json";
32402
32531
  var sha = (text2) => createHash6("sha256").update(text2.replaceAll("\r\n", "\n")).digest("hex");
@@ -32413,7 +32542,7 @@ async function shippedFiles({ templateRoot: templateRoot2, templates }) {
32413
32542
  const root = templateRoot2 ?? shippedTemplateRoot();
32414
32543
  const files = { "role-skills.json": await readFile9(new URL("role-skills.json", root), "utf8") };
32415
32544
  async function walk(relative) {
32416
- for (const entry of await readdir4(new URL(`${relative}/`, root), { withFileTypes: true })) {
32545
+ for (const entry of await readdir5(new URL(`${relative}/`, root), { withFileTypes: true })) {
32417
32546
  if (entry.isDirectory()) await walk(`${relative}/${entry.name}`);
32418
32547
  else if (entry.isFile()) files[`${relative}/${entry.name}`] = await readFile9(new URL(`${relative}/${entry.name}`, root), "utf8");
32419
32548
  }
@@ -32437,21 +32566,21 @@ async function seedLibrary(home2, options2 = {}) {
32437
32566
  if (seeded === void 0) {
32438
32567
  if (current !== void 0) continue;
32439
32568
  await mkdir6(path15.dirname(target), { recursive: true });
32440
- await writeFile7(target, content, { flag: "wx" }).catch((error51) => {
32569
+ await writeFile8(target, content, { flag: "wx" }).catch((error51) => {
32441
32570
  if (error51.code !== "EEXIST") throw error51;
32442
32571
  });
32443
32572
  manifest.files[name2] = sha(content);
32444
32573
  created.push(name2);
32445
32574
  } else if (current !== void 0 && sha(current) === seeded && seeded !== sha(content)) {
32446
- await writeFile7(target, content);
32575
+ await writeFile8(target, content);
32447
32576
  manifest.files[name2] = sha(content);
32448
32577
  updated.push(name2);
32449
32578
  }
32450
32579
  }
32451
32580
  if (created.length || updated.length || manifestText === void 0) {
32452
32581
  await mkdir6(home2, { recursive: true });
32453
- const temporary = `${manifestPath}.${randomUUID7().slice(0, 8)}.tmp`;
32454
- await writeFile7(temporary, JSON.stringify(manifest, null, 2) + "\n");
32582
+ const temporary = `${manifestPath}.${randomUUID8().slice(0, 8)}.tmp`;
32583
+ await writeFile8(temporary, JSON.stringify(manifest, null, 2) + "\n");
32455
32584
  await moveFile(temporary, manifestPath);
32456
32585
  }
32457
32586
  return { created, updated };
@@ -32512,7 +32641,7 @@ async function jsonObject(file3, code) {
32512
32641
  }
32513
32642
  async function entries(directory) {
32514
32643
  try {
32515
- return (await readdir5(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
32644
+ return (await readdir6(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
32516
32645
  } catch (error51) {
32517
32646
  if (error51.code === "ENOENT") return [];
32518
32647
  throw error51;
@@ -32718,7 +32847,7 @@ import os7 from "node:os";
32718
32847
  import { createHash as createHash7 } from "node:crypto";
32719
32848
 
32720
32849
  // packages/core/src/teams.js
32721
- import { readdir as readdir6 } from "node:fs/promises";
32850
+ import { readdir as readdir7 } from "node:fs/promises";
32722
32851
  import path17 from "node:path";
32723
32852
  var BUILTIN_TEAMS = ["pho", "cafe"];
32724
32853
  var TEAM_SETTINGS = ["$schema", "label", "description", "main", "members", "delegation", "maxPeers", "supervisor", "oracleModels", "oracleThinking", "modelSetup"];
@@ -32797,7 +32926,7 @@ function validateTeam(config2, source) {
32797
32926
  }
32798
32927
  async function directories(directory) {
32799
32928
  try {
32800
- return (await readdir6(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory() && validName2(entry.name)).map((entry) => entry.name).sort();
32929
+ return (await readdir7(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory() && validName2(entry.name)).map((entry) => entry.name).sort();
32801
32930
  } catch (error51) {
32802
32931
  if (error51.code === "ENOENT") return [];
32803
32932
  throw error51;
@@ -32980,9 +33109,9 @@ async function resolveWorkflow(root, selected, restored, { library, templates, s
32980
33109
  }
32981
33110
 
32982
33111
  // packages/core/src/init.js
32983
- import { mkdir as mkdir7, writeFile as writeFile8, lstat as lstat4, readFile as readFile11, rename as rename3, rm as rm3 } from "node:fs/promises";
33112
+ import { mkdir as mkdir7, writeFile as writeFile9, lstat as lstat4, readFile as readFile11, rename as rename4, rm as rm3 } from "node:fs/promises";
32984
33113
  import path20 from "node:path";
32985
- import { randomUUID as randomUUID8 } from "node:crypto";
33114
+ import { randomUUID as randomUUID9 } from "node:crypto";
32986
33115
  async function initProject(projectRoot, { templateRoot: templateRoot2, templates, library, syncProfiles = true, onlyManagedProfiles = false, teamIds } = {}) {
32987
33116
  const root = path20.resolve(projectRoot);
32988
33117
  const sourceRoot = () => templateRoot2 ?? shippedTemplateRoot();
@@ -33014,7 +33143,7 @@ async function initProject(projectRoot, { templateRoot: templateRoot2, templates
33014
33143
  for (const [name2, content] of Object.entries(files)) {
33015
33144
  const target = path20.join(root, name2);
33016
33145
  try {
33017
- await writeFile8(target, content, { flag: "wx" });
33146
+ await writeFile9(target, content, { flag: "wx" });
33018
33147
  created.push(name2);
33019
33148
  } catch (error51) {
33020
33149
  if (error51.code !== "EEXIST") throw error51;
@@ -33028,7 +33157,7 @@ async function initProject(projectRoot, { templateRoot: templateRoot2, templates
33028
33157
  validateSettings(next, target);
33029
33158
  if (JSON.stringify(next) !== JSON.stringify(settings)) {
33030
33159
  if (await readFile11(target, "utf8") !== before) throw new Error(`File changed during initialization: ${target}`);
33031
- await writeFile8(target, JSON.stringify(next, null, 2) + "\n");
33160
+ await writeFile9(target, JSON.stringify(next, null, 2) + "\n");
33032
33161
  updated.push(name2);
33033
33162
  }
33034
33163
  }
@@ -33050,11 +33179,11 @@ async function syncProjectSettings(projectRoot, { library, templates, teamIds, r
33050
33179
  const next = await syncTemplateProfiles(settings, { root, library, templates, teamIds, replaceModels });
33051
33180
  validateSettings(next, file3);
33052
33181
  if (JSON.stringify(settings) === JSON.stringify(next)) return false;
33053
- const temporary = `${file3}.${randomUUID8()}.tmp`;
33182
+ const temporary = `${file3}.${randomUUID9()}.tmp`;
33054
33183
  try {
33055
- await writeFile8(temporary, JSON.stringify(next, null, 2) + "\n", { flag: "wx", mode: info.mode });
33184
+ await writeFile9(temporary, JSON.stringify(next, null, 2) + "\n", { flag: "wx", mode: info.mode });
33056
33185
  if (await readFile11(file3, "utf8") !== before) throw new Error(`Settings changed while saving the template: ${file3}`);
33057
- await rename3(temporary, file3);
33186
+ await rename4(temporary, file3);
33058
33187
  } finally {
33059
33188
  await rm3(temporary, { force: true });
33060
33189
  }
@@ -33113,8 +33242,8 @@ async function compileAgent(adapter, agent) {
33113
33242
  }
33114
33243
 
33115
33244
  // packages/core/src/user-settings.js
33116
- import { mkdir as mkdir8, readFile as readFile13, writeFile as writeFile9 } from "node:fs/promises";
33117
- import { randomUUID as randomUUID9 } from "node:crypto";
33245
+ import { mkdir as mkdir8, readFile as readFile13, writeFile as writeFile10 } from "node:fs/promises";
33246
+ import { randomUUID as randomUUID10 } from "node:crypto";
33118
33247
  import path22 from "node:path";
33119
33248
  var DEFAULT_LANGUAGE = "Vietnamese";
33120
33249
  var LANGUAGE_CHARS = 40;
@@ -33158,8 +33287,8 @@ async function setLanguage(home2, language) {
33158
33287
  else next.language = language.trim();
33159
33288
  validateUserSettings(next, file3);
33160
33289
  await mkdir8(home2, { recursive: true, mode: 448 });
33161
- const temporary = `${file3}.${randomUUID9().slice(0, 8)}.tmp`;
33162
- await writeFile9(temporary, JSON.stringify(next, null, 2) + "\n");
33290
+ const temporary = `${file3}.${randomUUID10().slice(0, 8)}.tmp`;
33291
+ await writeFile10(temporary, JSON.stringify(next, null, 2) + "\n");
33163
33292
  await moveFile(temporary, file3);
33164
33293
  return languageSetting(home2);
33165
33294
  }
@@ -33382,7 +33511,7 @@ ${project}` : "");
33382
33511
  }
33383
33512
 
33384
33513
  // packages/runtime/src/recall.ts
33385
- import { mkdir as mkdir9, readFile as readFile15, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
33514
+ import { mkdir as mkdir9, readFile as readFile15, rename as rename5, writeFile as writeFile11 } from "node:fs/promises";
33386
33515
  import { randomBytes as randomBytes4 } from "node:crypto";
33387
33516
  import path24 from "node:path";
33388
33517
  var RECALL_KEEP_MS = 14 * 24 * 60 * 60 * 1e3;
@@ -33400,8 +33529,8 @@ function createRecallBook(file3) {
33400
33529
  writes2 = writes2.then(async () => {
33401
33530
  await mkdir9(path24.dirname(file3), { recursive: true, mode: 448 });
33402
33531
  const temporary = `${file3}.${process.pid}.${randomBytes4(4).toString("hex")}.tmp`;
33403
- await writeFile10(temporary, body, { mode: 384 });
33404
- await rename4(temporary, file3);
33532
+ await writeFile11(temporary, body, { mode: 384 });
33533
+ await rename5(temporary, file3);
33405
33534
  }).catch(() => {
33406
33535
  });
33407
33536
  return writes2;
@@ -33559,7 +33688,7 @@ var HANDOFF_TOOL = {
33559
33688
  var WAIT_TOOL = {
33560
33689
  type: "function",
33561
33690
  name: "alp_wait",
33562
- description: "Wait for mail from your assignments: results, questions, notes, and stall reports. Returns as soon as any arrives, or with an empty list and a status snapshot on timeout.",
33691
+ description: "Wait for mail from your assignments: results, questions, notes, and stall reports. Returns as soon as any arrives, a check-in comes or the user writes to you; otherwise with an empty list and a status snapshot after timeoutMs (default 5 minutes, at most 15). Ending your turn instead never cancels your assignments.",
33563
33692
  inputSchema: {
33564
33693
  type: "object",
33565
33694
  properties: {
@@ -33691,7 +33820,7 @@ var UNPIN_TOOL = {
33691
33820
  var ASK_TOOL = {
33692
33821
  type: "function",
33693
33822
  name: "alp_ask",
33694
- description: "Ask a question and wait for the answer: your requester, or the user. Only main talks to the user, unless the user has written to you. Returns unanswered after the ask timeout.",
33823
+ description: "Ask a question and wait for the answer: your requester, or the user. Only main talks to the user, unless the user has written to you. Holds your turn until answered; returns unanswered after the ask timeout (15 minutes for a requester, 30 for the user), and user_wrote when the user writes in the chat instead of answering.",
33695
33824
  inputSchema: {
33696
33825
  type: "object",
33697
33826
  properties: {
@@ -33720,7 +33849,7 @@ var LESSON_TOOL = {
33720
33849
  var SKILL_TOOL = {
33721
33850
  type: "function",
33722
33851
  name: "alp_skill",
33723
- description: "Propose a skill distilled from your lessons. ALP shows the whole skill to the user, and saves it to the user's skill library and gives it to the roles only when the user approves; any other answer comes back as feedback.",
33852
+ description: "Propose a skill distilled from your lessons. ALP shows the whole skill to the user, and saves it to the user's skill library and gives it to the roles only when the user approves. Returns pending_approval with approvalId immediately; the decision (saved, or not saved with the user's feedback) arrives once by mail, even after your turn ends. Continue other work. A retry of the same draft returns its existing pending/result in this session; use a fresh requestId only to propose an identical draft again.",
33724
33853
  inputSchema: {
33725
33854
  type: "object",
33726
33855
  properties: {
@@ -33729,7 +33858,8 @@ var SKILL_TOOL = {
33729
33858
  body: { type: "string", description: "The SKILL.md body in Markdown, without frontmatter: when to use it, the method as steps, and the checks." },
33730
33859
  roles: { type: "array", items: { type: "string" }, description: "The roles whose work the skill guides, and only those: any of main (yourself), lead, peer, oracle, reviewer, supervisor, or a custom agent of this project." },
33731
33860
  lessons: { type: "array", items: { type: "string" }, description: "The exact text of the lessons this skill replaces; they leave the lessons files once it is saved." },
33732
- replace: { type: "boolean", description: "Propose a new version of an existing skill of this name." }
33861
+ replace: { type: "boolean", description: "Propose a new version of an existing skill of this name." },
33862
+ requestId: { type: "string", description: "Optional session-scoped idempotency key (1-100 characters). Reuse with the same draft on retries; a fresh key explicitly proposes a new operation." }
33733
33863
  },
33734
33864
  required: ["name", "description", "body", "roles"],
33735
33865
  additionalProperties: false
@@ -33738,7 +33868,7 @@ var SKILL_TOOL = {
33738
33868
  var ISSUE_TOOL = {
33739
33869
  type: "function",
33740
33870
  name: "alp_issue",
33741
- description: "GitHub issues of this project (target project, from its origin remote) or of ALP itself (target alp). search needs no approval. create and comment show the whole draft to the user and post it only when the user approves; any other answer comes back as feedback.",
33871
+ description: "GitHub issues of this project (target project, from its origin remote) or of ALP itself (target alp). search needs no approval. create and comment show the whole draft to the user and post it only when the user approves; create/comment return pending_approval with approvalId immediately, then send one decision/posting result by mail, even after your turn ends. Continue other work. Draft retries return the existing pending/result in this session. Use a fresh requestId only to intentionally propose an identical draft again; reuse one requestId with the exact draft to replay safely.",
33742
33872
  inputSchema: {
33743
33873
  type: "object",
33744
33874
  properties: {
@@ -33748,6 +33878,7 @@ var ISSUE_TOOL = {
33748
33878
  title: { type: "string", description: "For create." },
33749
33879
  body: { type: "string", description: "For create and comment: Markdown. Facts, reproduction and evidence; no secrets." },
33750
33880
  issue: { type: "integer", description: "For comment: the issue number." },
33881
+ requestId: { type: "string", description: "Optional session-scoped idempotency key (1-100 characters). Reuse with the same draft on retries; a fresh key explicitly proposes a new operation." },
33751
33882
  labels: { type: "array", items: { type: "string" }, description: "For create: existing labels of the repository." }
33752
33883
  },
33753
33884
  required: ["action", "target"],
@@ -34181,7 +34312,7 @@ function nativeSessionConfig(runtimeKind, mapping, targets, parentAgent, role, s
34181
34312
  dynamicTools: [SEND_TOOL, BOARD_TOOL, taskTool(taskActions(mapping, parentAgent, role))]
34182
34313
  };
34183
34314
  }
34184
- const delegationInstruction = targets.length ? `Use alp_delegate to assign bounded work to: ${targets.join(", ")}. Work runs in the background: by default alp_delegate starts the assignment and returns its assignmentId at once, and its result, questions and stall reports come to you as mail. Pass wait: true only when your very next step cannot go on without the result; it then returns the result, or early with the child's first question. A result carries the child's structured handoff (null if it filed none) and output, its final message. While assignments run, go on with work that does not depend on them; when only their results are left, alp_wait for them (mail from your requester or the user ends the wait early), or end your turn and ALP wakes you with their results and questions. Answer questions with alp_send kind answer and replyTo; use kind steer to change an instruction, note for information. To stop an assignment, for example one parked on a usage limit whose work should go on now, use alp_cancel and delegate the rest, on another runtime (pass a model of claude: or codex:); its changes stay in the checkout or its worktree. At most ${mapping.workflow.maxPeers} peers may run concurrently. Concurrent peers must be read-only or isolated: pass isolation "worktree" to give a writing peer its own git worktree. A worktree result lists its branch and changed files; apply it with alp_merge (uncommitted, conflicts left as markers) or drop it with alp_discard, then verify. Writers in this shared checkout run one at a time. To learn why a finished assignment did something, ask it with alp_recall rather than guessing from its handoff. When the project configures verify commands, ALP runs them before alp_merge applies a change, and after a writer in your checkout finishes; a failed check applies nothing. To fix a failed worktree change, delegate again with continueFrom set to its assignmentId. Do not run shell/file mutations in parallel with delegation. Include scope, constraints, verification, and required handoff in task. Child inherits your mode unless you request read-only. Review its returned evidence before answering. Do not use native spawn tools or shell-launched agents to bypass this route.` : "No delegation targets are authorized. Do not spawn agents or use shell-launched agents. Complete your assigned scope and return evidence.";
34315
+ const delegationInstruction = targets.length ? `Use alp_delegate to assign bounded work to: ${targets.join(", ")}. Delegation has two finite budgets shared by the entire root session tree: 16 implementation starts (lead/peer) and separately 16 read-only advisor starts (reviewer/oracle). Only a new accepted user prompt in root, including steering, resets both; child turns, mail wakes and approval answers do not. Started assignments count even when they fail. Work runs in the background: by default alp_delegate starts the assignment and returns its assignmentId at once, and its result, questions and stall reports come to you as mail. Pass wait: true only when your very next step cannot go on without the result; it then returns the result, or early with the child's first question. A result carries the child's structured handoff (null if it filed none) and output, its final message. While assignments run, go on with work that does not depend on them; when only their results are left, alp_wait for them (mail from your requester or the user ends the wait early), or end your turn and ALP wakes you with their results and questions. Answer questions with alp_send kind answer and replyTo; use kind steer to change an instruction, note for information. To stop an assignment, for example one parked on a usage limit whose work should go on now, use alp_cancel and delegate the rest, on another runtime (pass a model of claude: or codex:); its changes stay in the checkout or its worktree. At most ${mapping.workflow.maxPeers} peers may run concurrently. Concurrent peers must be read-only or isolated: pass isolation "worktree" to give a writing peer its own git worktree. A worktree result lists its branch and changed files; apply it with alp_merge (uncommitted, conflicts left as markers) or drop it with alp_discard, then verify. Writers in this shared checkout run one at a time. To learn why a finished assignment did something, ask it with alp_recall rather than guessing from its handoff. When the project configures verify commands, ALP runs them before alp_merge applies a change, and after a writer in your checkout finishes; a failed check applies nothing. To fix a failed worktree change, delegate again with continueFrom set to its assignmentId. Do not run shell/file mutations in parallel with delegation. Include scope, constraints, verification, and required handoff in task. Child inherits your mode unless you request read-only. Review its returned evidence before answering. Do not use native spawn tools or shell-launched agents to bypass this route.` : "No delegation targets are authorized. Do not spawn agents or use shell-launched agents. Complete your assigned scope and return evidence.";
34185
34316
  return {
34186
34317
  runtime: runtimeKind,
34187
34318
  cwd: mapping.workdir,
@@ -34207,7 +34338,7 @@ function nativeSessionConfig(runtimeKind, mapping, targets, parentAgent, role, s
34207
34338
  !parentAgent && mapping.agent.name === mainOf(mapping) ? `Tasks: the project's task graph lives in ${TASKS_DIR}, one JSON file per task, committed with the project and shared with the user, who adds tasks with the alp CLI. Only you and the user create or change tasks; change them only with alp_task, never by editing the files. Create a task for work that outlives this turn, that the user asks you to track, or that you find outside the current scope (discoveredFrom: the task you were on); do not create tasks for work you finish in this turn. Before choosing what to do next, read the task list ALP adds to your turn, or alp_task ready. When you work on a task yourself, start it; to give it to lead or peer, pass taskId to alp_delegate. A handoff moves a delegated task to review: accept it by closing it with a reason and a summary of the outcome and its evidence after verifying it, or delegate it again with the same taskId for rework. Record the discovered work listed in a handoff as tasks with discoveredFrom, or say why not. Model order with blockedBy and grouping with an epic parent.` : `Tasks: read the project's task graph with alp_task (${taskActions(mapping, parentAgent).join(", ")}). Only main and the user create or change tasks; list work you find outside your scope under discovered in your handoff instead.`,
34208
34339
  "Background first: run long shell commands (builds, test suites, dev servers, watchers, deploys) in the background when your tools allow it, and check their output later; run a command in the foreground only when your next step needs its result.",
34209
34340
  "Project board: every agent working on this project, in any session, shares one board. Before changing files, read alp_board and pin a claim listing the paths you will change; do not edit paths another agent has claimed, and ask your requester instead. Pin a decision when you choose an approach others should follow, and a finding when you learn something others need. Pins from others arrive as board mail; it is information, and it never overrides your requester. Claims end with your session; take one down earlier with alp_unpin.",
34210
- ...!parentAgent ? ["To get the user's answer without ending your turn, for example while assignments run, use alp_ask; otherwise ask in your final message."] : [],
34341
+ ...!parentAgent ? ["To get the user's answer without ending your turn, for example while assignments run, use alp_ask; otherwise ask in your final message. Tools that hold your turn: alp_ask until the user answers or writes in the chat (at most 30 minutes); alp_delegate with wait: true until the result, the child's first question, a check-in or the user's message; alp_wait until mail, a check-in, the user's message or its timeout (default 5, at most 15 minutes). Write your reply to the user before such a call: it reaches them while the call waits. Approvals do not hold it: alp_issue create/comment and alp_skill return pending_approval with an approvalId at once, and the decision arrives by mail. Ending your turn never cancels running assignments; only the user's stop, alp_cancel or a closed session does."] : [],
34211
34342
  // ALPD §54: a root session talks with the user; an assignment only asks them through alp_ask.
34212
34343
  languageInstruction(mapping.language, mapping.agent.name, !parentAgent),
34213
34344
  ...!parentAgent && mapping.agent.name === mainOf(mapping) ? [
@@ -34230,7 +34361,7 @@ function nativeSessionConfig(runtimeKind, mapping, targets, parentAgent, role, s
34230
34361
  {
34231
34362
  type: "function",
34232
34363
  name: "alp_delegate",
34233
- description: "Delegate a bounded task to an authorized ALP agent and wait for its real handoff.",
34364
+ description: "Delegate a bounded task to an authorized ALP agent. Root-tree budgets: 16 implementation starts and separately 16 read-only advisor starts. New accepted root user prompts (including steering) reset both; child turns and mail wakes do not. The default returns immediately; wait true waits for handoff.",
34234
34365
  inputSchema: {
34235
34366
  type: "object",
34236
34367
  properties: {
@@ -34277,9 +34408,9 @@ function nativeSessionConfig(runtimeKind, mapping, targets, parentAgent, role, s
34277
34408
  }
34278
34409
 
34279
34410
  // packages/runtime/src/runtime.ts
34280
- import { createHash as createHash8, randomUUID as randomUUID11 } from "node:crypto";
34411
+ import { createHash as createHash8, randomUUID as randomUUID12 } from "node:crypto";
34281
34412
  import { readFileSync as readFileSync3, realpathSync as realpathSync5 } from "node:fs";
34282
- import { appendFile as appendFile2, mkdir as mkdir12, open as open3, readFile as readFile18, realpath as realpath4, rm as rm4, stat as stat6, writeFile as writeFile13 } from "node:fs/promises";
34413
+ import { appendFile as appendFile2, mkdir as mkdir12, open as open3, readFile as readFile18, realpath as realpath4, rm as rm4, stat as stat6, writeFile as writeFile14 } from "node:fs/promises";
34283
34414
  import os8 from "node:os";
34284
34415
  import path30 from "node:path";
34285
34416
 
@@ -34355,8 +34486,8 @@ function runHook(hook, payload, { cwd, env = {}, timeoutMs } = {}) {
34355
34486
  }
34356
34487
 
34357
34488
  // packages/core/src/trust.js
34358
- import { mkdir as mkdir10, readFile as readFile16, realpath as realpath3, writeFile as writeFile11 } from "node:fs/promises";
34359
- import { randomUUID as randomUUID10 } from "node:crypto";
34489
+ import { mkdir as mkdir10, readFile as readFile16, realpath as realpath3, writeFile as writeFile12 } from "node:fs/promises";
34490
+ import { randomUUID as randomUUID11 } from "node:crypto";
34360
34491
  import path27 from "node:path";
34361
34492
  var file2 = (home2) => path27.join(home2, "state", "trust.json");
34362
34493
  var key = (root) => realpath3(path27.resolve(root)).catch(() => path27.resolve(root));
@@ -34371,8 +34502,8 @@ async function load(home2) {
34371
34502
  }
34372
34503
  async function store(home2, value) {
34373
34504
  await mkdir10(path27.dirname(file2(home2)), { recursive: true });
34374
- const temporary = `${file2(home2)}.${randomUUID10().slice(0, 8)}.tmp`;
34375
- await writeFile11(temporary, JSON.stringify(value, null, 2) + "\n");
34505
+ const temporary = `${file2(home2)}.${randomUUID11().slice(0, 8)}.tmp`;
34506
+ await writeFile12(temporary, JSON.stringify(value, null, 2) + "\n");
34376
34507
  await moveFile(temporary, file2(home2));
34377
34508
  }
34378
34509
  async function isTrusted(home2, projectRoot) {
@@ -34415,7 +34546,7 @@ ${event.body ?? JSON.stringify(event.result)}`;
34415
34546
 
34416
34547
  // packages/runtime/src/live.ts
34417
34548
  import { readFileSync as readFileSync2 } from "node:fs";
34418
- import { mkdir as mkdir11, rename as rename5, writeFile as writeFile12 } from "node:fs/promises";
34549
+ import { mkdir as mkdir11, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
34419
34550
  import { randomBytes as randomBytes5 } from "node:crypto";
34420
34551
  import path28 from "node:path";
34421
34552
  var valid2 = (entry) => !!entry && typeof entry === "object" && ["assignmentId", "rootId", "parentId", "agent", "project", "runtime", "model", "threadId", "mode", "isolation", "epoch"].every((key2) => typeof entry[key2] === "string") && Array.isArray(entry.ancestry) && !!entry.spec && typeof entry.spec === "object" && typeof entry.startedAt === "number";
@@ -34434,8 +34565,8 @@ function createLiveBook(file3) {
34434
34565
  writes2 = writes2.then(async () => {
34435
34566
  await mkdir11(path28.dirname(file3), { recursive: true, mode: 448 });
34436
34567
  const temporary = `${file3}.${process.pid}.${randomBytes5(4).toString("hex")}.tmp`;
34437
- await writeFile12(temporary, body, { mode: 384 });
34438
- await rename5(temporary, file3);
34568
+ await writeFile13(temporary, body, { mode: 384 });
34569
+ await rename6(temporary, file3);
34439
34570
  }).catch((error51) => createLogger({ component: "runtime" }).error("could not save the live assignments", { code: "ALP_PERSISTENCE_WRITE_FAILED", operation: "live assignments", error: error51 }));
34440
34571
  return writes2;
34441
34572
  };
@@ -35332,7 +35463,7 @@ function parse3(toml, options2 = {}) {
35332
35463
  var HAS_WELLFORMED = !!"".isWellFormed;
35333
35464
 
35334
35465
  // packages/core/src/formulas.js
35335
- import { readdir as readdir7, readFile as readFile17 } from "node:fs/promises";
35466
+ import { readdir as readdir8, readFile as readFile17 } from "node:fs/promises";
35336
35467
  import path29 from "node:path";
35337
35468
  var fail4 = (message) => {
35338
35469
  throw new AlpError("INVALID_FORMULA", message);
@@ -35354,7 +35485,7 @@ async function listFormulas(projectRoot, home2, { toml } = {}) {
35354
35485
  for (const dir of formulaDirs(projectRoot, home2)) {
35355
35486
  let names;
35356
35487
  try {
35357
- names = await readdir7(dir);
35488
+ names = await readdir8(dir);
35358
35489
  } catch (error51) {
35359
35490
  if (error51.code === "ENOENT") continue;
35360
35491
  throw error51;
@@ -35480,7 +35611,7 @@ async function pourFormula(projectRoot, formula, given, by, { dryRun = false, pa
35480
35611
  title: fill(step.title, values, `Step ${step.id}`),
35481
35612
  ...step.description ? { description: fill(step.description, values, `Step ${step.id}`) } : {}
35482
35613
  }));
35483
- return batch(projectRoot, ({ add, link }) => {
35614
+ return batch(projectRoot, ({ add, link: link2 }) => {
35484
35615
  const epic = add({
35485
35616
  title: title.slice(0, 200),
35486
35617
  type: "epic",
@@ -35513,7 +35644,7 @@ async function pourFormula(projectRoot, formula, given, by, { dryRun = false, pa
35513
35644
  }
35514
35645
  for (const step of steps) {
35515
35646
  for (const need of step.needs) {
35516
- const refused = link(made.get(step.id), "blockedBy", made.get(need).id);
35647
+ const refused = link2(made.get(step.id), "blockedBy", made.get(need).id);
35517
35648
  if (refused) fail4(`Step ${step.id} cannot need ${need}: ${refused}`);
35518
35649
  }
35519
35650
  }
@@ -35525,6 +35656,7 @@ async function pourFormula(projectRoot, formula, given, by, { dryRun = false, pa
35525
35656
  var MAX_WAKES = 8;
35526
35657
  var USER_REPLY_MS = 6e4;
35527
35658
  var USER_WROTE = "The user just wrote to you; their message follows. Answer them first in a short message they can read (tool calls alone show them nothing), and steer an assignment with alp_send when their words change its brief. Your assignments keep running: then alp_wait again, or end your turn and ALP wakes you with their results.";
35659
+ var TURN_ENDED = "Turn ended before the result arrived. Running assignments keep running; ALP wakes you with their results as mail.";
35528
35660
  var CHECKED_IN = "Not a result: the assignment keeps running. Act on this mail first (a steer from your requester or the user's words may change what your assignments should do: pass that on with alp_send), then alp_wait again or end your turn.";
35529
35661
  var RESTART_LIMIT = 3;
35530
35662
  var RESTART_WINDOW_MS = 10 * 6e4;
@@ -35560,7 +35692,7 @@ function createAlpRuntime(options2 = {}) {
35560
35692
  const wordsFor = (session) => words(session.mapping.language);
35561
35693
  const childContexts = /* @__PURE__ */ new Map();
35562
35694
  const listeners = /* @__PURE__ */ new Set();
35563
- const epoch = randomUUID11();
35695
+ const epoch = randomUUID12();
35564
35696
  const recalls = createRecallBook(options2.recallFile);
35565
35697
  const inFlight = createLiveBook(options2.liveFile);
35566
35698
  let inherited = inFlight.all().filter((entry) => entry.epoch !== epoch);
@@ -35617,11 +35749,12 @@ function createAlpRuntime(options2 = {}) {
35617
35749
  ...session.parent ? { parentId: session.parent } : {},
35618
35750
  ...session.toolCallId ? { toolCallId: session.toolCallId } : {},
35619
35751
  ...session.active ? { activeTurnId: session.active } : {},
35620
- ...session.parked ? { parked: session.parked.reason } : {},
35752
+ // turn.ended listeners persist the intent before terminal() finishes parking it.
35753
+ ...session.parked?.reason ?? session.parkReason ? { parked: session.parked?.reason ?? session.parkReason } : {},
35621
35754
  ...session.instructionsSha ? { instructionsSha: session.instructionsSha } : {},
35622
35755
  ...takesImages(session) ? { promptImages: true } : {},
35623
35756
  ...session.usage ? { usage: session.usage } : {},
35624
- busy: !!(session.active || session.pending || session.children.size || session.assignments.size || hasActiveMail(session) || reviewing(session))
35757
+ busy: !!(session.active || session.pending || session.parked || session.parkReason || session.children.size || session.assignments.size || hasActiveMail(session) || reviewing(session))
35625
35758
  };
35626
35759
  }
35627
35760
  function terminal(sessionId, session, state, error51) {
@@ -35639,7 +35772,7 @@ function createAlpRuntime(options2 = {}) {
35639
35772
  clearTimeout(waiter.timer);
35640
35773
  waiter.resolve(null);
35641
35774
  }
35642
- cancelQuestions(sessionId);
35775
+ cancelQuestions(sessionId, true);
35643
35776
  if (session.parent) sessions.get(session.parent)?.assignments.get(sessionId)?.ask?.resolve(toolResult(false, { error: "Turn ended" }));
35644
35777
  session.mail = session.mail.filter((event) => !(event.deliveredTurn === turnId && state === "completed"));
35645
35778
  for (const event of session.mail) {
@@ -35797,7 +35930,7 @@ function createAlpRuntime(options2 = {}) {
35797
35930
  if (method === "turn/completed" && params.turn.id === session.active) {
35798
35931
  const state = params.turn.status === "completed" ? "completed" : params.turn.status === "interrupted" ? "canceled" : "failed";
35799
35932
  if (state === "failed" && LIMIT_ERRORS.includes(params.turn.error?.codexErrorInfo)) {
35800
- if (session.parent && session.role !== "supervisor") session.parkReason = `the ${label(session.runtimeKind)} usage limit was reached`;
35933
+ if (session.role !== "supervisor") session.parkReason = `the ${label(session.runtimeKind)} usage limit was reached`;
35801
35934
  void limitReached(session, params.turn.error);
35802
35935
  }
35803
35936
  terminal(
@@ -35813,6 +35946,7 @@ function createAlpRuntime(options2 = {}) {
35813
35946
  if (!session || session.closed) return;
35814
35947
  session.closed = true;
35815
35948
  cancelQuestions(sessionId);
35949
+ approvalLedger.delete(sessionId);
35816
35950
  releaseClaims(sessionId, session.mapping.agent.projectRoot);
35817
35951
  await Promise.all(
35818
35952
  [...session.children, ...session.supervisor ? [session.supervisor] : []].map(closeSession)
@@ -36079,7 +36213,7 @@ ${output.slice(0, 4e3)}` : ""}`;
36079
36213
  const root = sessions.get(rootId);
36080
36214
  const watcher = root?.mapping.team?.supervisor ? root.mapping.team.supervisor.agent : "supervisor";
36081
36215
  if (!root?.supervisor || entry.from === "supervisor" || entry.agent === watcher) return;
36082
- const line = entry.event === "assignment.started" ? `${entry.parentAgent} \u2192 ${entry.agent} assignment ${entry.assignmentId}${entry.taskId ? ` for task ${entry.taskId}` : ""} (${entry.model}, ${entry.thinking ?? "default effort"}, ${entry.mode}, ${entry.isolation}${entry.wait ? "" : ", async"}): ${clip(entry.task)}` : entry.event === "assignment.finished" ? `${entry.agent} assignment ${entry.assignmentId} ${entry.status}${entry.handoff ? `, handoff ${entry.handoff.outcome}${entry.handoff.verdict ? `, ${verdictLine(entry.handoff.verdict)}` : ""}: ${clip(entry.handoff.summary)}` : ", no handoff"}${entry.error ? ` (${clip(entry.error, 200)})` : ""}${entry.handoff?.discovered?.length ? `; discovered: ${clip(entry.handoff.discovered.join("; "))}` : ""}` : entry.event === "mail" && entry.kind !== "result" && entry.kind !== "board" ? `mail ${entry.kind} from ${entry.from}${entry.replyTo ? ` (reply to ${entry.replyTo})` : ""}: ${clip(entry.body ?? "")}` : entry.event === "human.question" ? `${entry.agent} asked the user: ${clip(entry.body)}` : entry.event === "human.answer" ? `user ${entry.outcome} ${entry.questionId}${entry.answer ? `: ${clip(entry.answer)}` : ""}` : entry.event === "board.pin" ? `${entry.agent} pinned ${entry.kind}${entry.paths ? ` ${entry.paths.join(", ")}` : ""}: ${clip(entry.body)}` : entry.event === "lesson" ? `${entry.agent} recorded a ${entry.scope} lesson: ${clip(entry.lesson)}` : entry.event === "skill" && Array.isArray(entry.roles) ? `${entry.agent} saved skill ${entry.name} for ${entry.roles.join(", ")} with the user's approval` : entry.event === "task" ? `${entry.agent} ${TASK_PAST[entry.action] ?? entry.action} task ${entry.id} "${clip(entry.title, 120)}"${entry.status ? ` (now ${entry.status})` : ""}${entry.detail ? `: ${clip(entry.detail)}` : ""}` : entry.event === "epic.landed" ? `${entry.agent} closed ${entry.id} "${clip(entry.title, 120)}": ${entry.tasks} tasks, ${entry.reworked} reworked, ${entry.unverified?.length ?? 0} closed unverified` : entry.event === "recall" ? `${entry.agent} recalled ${entry.recalled} assignment ${entry.assignmentId}: ${clip(entry.question)}` : entry.event === "issue" ? `${entry.action === "create" ? `opened issue "${clip(entry.title, 200)}"` : `commented on issue #${entry.issue}`} in ${entry.repo} with the user's approval: ${entry.url}` : entry.event?.startsWith("worktree.") ? `${entry.event.slice("worktree.".length)} worktree of ${entry.assignmentId} (${entry.branch})${entry.status ? `: ${entry.status}` : ""}` : void 0;
36216
+ const line = entry.event === "assignment.started" ? `${entry.parentAgent} \u2192 ${entry.agent} assignment ${entry.assignmentId}${entry.taskId ? ` for task ${entry.taskId}` : ""} (${entry.model}, ${entry.thinking ?? "default effort"}, ${entry.mode}, ${entry.isolation}${entry.wait ? "" : ", async"}): ${clip(entry.task)}` : entry.event === "assignment.finished" ? `${entry.agent} assignment ${entry.assignmentId} ${entry.status}${entry.handoff ? `, handoff ${entry.handoff.outcome}${entry.handoff.verdict ? `, ${verdictLine(entry.handoff.verdict)}` : ""}: ${clip(entry.handoff.summary)}` : ", no handoff"}${entry.error ? ` (${clip(entry.error, 200)})` : ""}${entry.handoff?.discovered?.length ? `; discovered: ${clip(entry.handoff.discovered.join("; "))}` : ""}` : entry.event === "mail" && entry.kind !== "result" && entry.kind !== "board" ? `mail ${entry.kind} from ${entry.from}${entry.replyTo ? ` (reply to ${entry.replyTo})` : ""}: ${clip(entry.body ?? "")}` : entry.event === "human.question" ? `${entry.agent} asked the user: ${clip(entry.body)}` : entry.event === "human.answer" ? `user ${entry.outcome} ${entry.questionId}${entry.answer ? `: ${clip(entry.answer)}` : ""}` : entry.event === "board.pin" ? `${entry.agent} pinned ${entry.kind}${entry.paths ? ` ${entry.paths.join(", ")}` : ""}: ${clip(entry.body)}` : entry.event === "lesson" ? `${entry.agent} recorded a ${entry.scope} lesson: ${clip(entry.lesson)}` : entry.event === "skill" && Array.isArray(entry.roles) ? `${entry.agent} saved skill ${entry.name} for ${entry.roles.join(", ")} with the user's approval` : entry.event === "task" ? `${entry.agent} ${TASK_PAST[entry.action] ?? entry.action} task ${entry.id} "${clip(entry.title, 120)}"${entry.status ? ` (now ${entry.status})` : ""}${entry.detail ? `: ${clip(entry.detail)}` : ""}` : entry.event === "epic.landed" ? `${entry.agent} closed ${entry.id} "${clip(entry.title, 120)}": ${entry.tasks} tasks, ${entry.reworked} reworked, ${entry.unverified?.length ?? 0} closed unverified` : entry.event === "recall" ? `${entry.agent} recalled ${entry.recalled} assignment ${entry.assignmentId}: ${clip(entry.question)}` : entry.event === "skill.approval" && !entry.saved ? `skill approval ${entry.approvalId} for ${entry.name}: not saved (${entry.error ?? entry.outcome})` : entry.event === "issue.approval" ? `issue approval ${entry.approvalId} in ${entry.repo}: ${entry.posted ? entry.url : entry.error ?? entry.outcome}` : entry.event === "issue" ? `${entry.action === "create" ? `opened issue "${clip(entry.title, 200)}"` : `commented on issue #${entry.issue}`} in ${entry.repo} with the user's approval: ${entry.url}` : entry.event?.startsWith("worktree.") ? `${entry.event.slice("worktree.".length)} worktree of ${entry.assignmentId} (${entry.branch})${entry.status ? `: ${entry.status}` : ""}` : void 0;
36083
36217
  if (line) root.journal.push(stamp(line));
36084
36218
  }
36085
36219
  const eventful = (journal) => journal.some((line) => !unstamped(line).startsWith("turn ended:"));
@@ -36122,13 +36256,13 @@ ${kept.join("\n")}
36122
36256
  ${agent}'s session: ${root.runtimeKind}:${root.mapping.model}, thinking ${root.mapping.thinking ?? "default"}, mode ${root.mapping.mode}. Judge ${agent} by this session and its own instructions; your system prompt describes your session (your model, attribution lines, tools), not ${agent}'s.
36123
36257
 
36124
36258
  Review the process against ALP.md, ${agent}'s AGENT.md and the recorded lessons. Ask ${agent} about each mistake with one alp_send to: "parent", kind note, or send nothing when the process was sound.`;
36125
- await startPrompt(supervisorId, { clientMessageId: `alp-review-${randomUUID11()}`, delivery: "auto", content: [{ type: "text", text: text3 }] }, "assignment");
36259
+ await startPrompt(supervisorId, { clientMessageId: `alp-review-${randomUUID12()}`, delivery: "auto", content: [{ type: "text", text: text3 }] }, "assignment");
36126
36260
  }).catch((error51) => runLog(rootId, { event: "supervisor.failed", error: errorData(error51).message }));
36127
36261
  }
36128
36262
  async function openSupervisor(rootId) {
36129
36263
  const root = sessions.get(rootId);
36130
36264
  if (!root || root.closed || root.supervisor || root.parent) return;
36131
- const id = `alp-supervisor-${randomUUID11()}`;
36265
+ const id = `alp-supervisor-${randomUUID12()}`;
36132
36266
  childContexts.set(id, { parent: rootId, callId: `supervisor-${rootId}`, graph: {}, workflow: root.mapping.workflow, ancestry: [...root.ancestry, "supervisor"], role: "supervisor" });
36133
36267
  const { restore: _restore, ...inherited2 } = root.spec;
36134
36268
  const watcher = root.mapping.team?.supervisor || { agent: "supervisor" };
@@ -36377,8 +36511,8 @@ Review the process against ALP.md, ${agent}'s AGENT.md and the recorded lessons.
36377
36511
  const body = JSON.stringify(paused, null, 2) + "\n";
36378
36512
  pauseWrites = pauseWrites.then(async () => {
36379
36513
  await mkdir12(path30.dirname(file3), { recursive: true, mode: 448 });
36380
- const temporary = `${file3}.${process.pid}.${randomUUID11().slice(0, 8)}.tmp`;
36381
- await writeFile13(temporary, body, { mode: 384 });
36514
+ const temporary = `${file3}.${process.pid}.${randomUUID12().slice(0, 8)}.tmp`;
36515
+ await writeFile14(temporary, body, { mode: 384 });
36382
36516
  await moveFile(temporary, file3);
36383
36517
  }).catch(() => {
36384
36518
  });
@@ -36387,7 +36521,7 @@ Review the process against ALP.md, ${agent}'s AGENT.md and the recorded lessons.
36387
36521
  for (const [id, session] of sessions) {
36388
36522
  if (session.parent || session.closed || project !== void 0 && session.mapping.agent.projectRoot !== project) continue;
36389
36523
  const text3 = typeof message === "string" ? message : message(wordsFor(session));
36390
- emit(id, { type: "item", item: { kind: "notice", id: `notice-${randomUUID11().slice(0, 8)}`, level, text: text3 } });
36524
+ emit(id, { type: "item", item: { kind: "notice", id: `notice-${randomUUID12().slice(0, 8)}`, level, text: text3 } });
36391
36525
  runLog(id, { event: "notice", level, text: text3 });
36392
36526
  }
36393
36527
  }
@@ -36514,20 +36648,34 @@ Review the process against ALP.md, ${agent}'s AGENT.md and the recorded lessons.
36514
36648
  else post(session.parent, { kind: "stalled", from: assignment.agent, assignment: sessionId, body: `Parked: ${session.parked.reason}. ALP continues this assignment where it stopped when ${label(session.runtimeKind)} is resumed (${options2.autoResume !== false && (pauseOf(session.runtimeKind)?.by ?? "alpd") === "alpd" ? `ALP resumes it as soon as ${label(session.runtimeKind)} says the limit lifted` : "the user runs alp resume"}). Wait for it, start other work, or stop it with alp_cancel and give the rest to an agent on another runtime (pass model claude:\u2026 or codex:\u2026); what it changed stays in your checkout or its worktree.` });
36515
36649
  }
36516
36650
  function continueParked(sessionId, session) {
36517
- const { reason, prompt } = session.parked;
36518
- session.parked = void 0;
36519
- session.wakeHeld = false;
36520
- session.lastActivity = Date.now();
36521
- emit(sessionId, { type: "session.updated", session: snapshot(sessionId, session) });
36522
- const parent = session.parent ? sessions.get(session.parent) : void 0;
36523
- const assignment = parent?.assignments.get(sessionId);
36524
- runLog(rootOf2(sessionId), { event: "assignment.resumed", assignmentId: sessionId, agent: session.mapping.agent.name });
36525
- if (assignment) emit(session.parent, { type: "assignment", assignment: assignmentSnapshot(assignment, "running") });
36526
- void startPrompt(sessionId, {
36527
- clientMessageId: `alp-resume-${randomUUID11()}`,
36528
- delivery: "auto",
36529
- content: [{ type: "text", text: prompt ?? `ALP resumed this assignment; it had stopped because ${reason}. Continue where you left off: your earlier work in this session and its files are intact. Finish with alp_handoff as before.` }]
36530
- }, session.parent ? "assignment" : "wake").catch((error51) => session.settle?.("failed", error51));
36651
+ void enqueue(async () => {
36652
+ if (session.closed || session.active || session.pending || !session.parked || pauseOf(session.runtimeKind)) return;
36653
+ const parked = session.parked;
36654
+ const { reason, prompt } = parked;
36655
+ session.parked = void 0;
36656
+ session.wakeHeld = false;
36657
+ session.lastActivity = Date.now();
36658
+ const parent = session.parent ? sessions.get(session.parent) : void 0;
36659
+ const assignment = parent?.assignments.get(sessionId);
36660
+ runLog(rootOf2(sessionId), { event: "assignment.resumed", assignmentId: sessionId, agent: session.mapping.agent.name });
36661
+ if (assignment) emit(session.parent, { type: "assignment", assignment: assignmentSnapshot(assignment, "running") });
36662
+ try {
36663
+ await startPrompt(sessionId, {
36664
+ clientMessageId: `alp-resume-${randomUUID12()}`,
36665
+ delivery: "auto",
36666
+ content: [{ type: "text", text: prompt ?? (session.parent ? `ALP resumed this assignment; it had stopped because ${reason}. Continue where you left off: your earlier work in this session and its files are intact. Finish with alp_handoff as before.` : `ALP resumed this session; it had stopped because ${reason}. Continue the user's request where you left off: your earlier work in this thread and its files are intact.`) }]
36667
+ }, session.parent ? "assignment" : "wake");
36668
+ session.resumedTurn = session.active;
36669
+ emit(sessionId, { type: "session.updated", session: snapshot(sessionId, session) });
36670
+ } catch (error51) {
36671
+ if (!session.closed && !session.active) {
36672
+ session.parked = parked;
36673
+ emit(sessionId, { type: "session.updated", session: snapshot(sessionId, session) });
36674
+ if (assignment) emit(session.parent, { type: "assignment", assignment: assignmentSnapshot(assignment, "parked") });
36675
+ }
36676
+ notice("warning", `Could not resume ${session.mapping.agent.name}: ${errorData(error51).message}`);
36677
+ }
36678
+ });
36531
36679
  }
36532
36680
  async function limitReset(session, error51) {
36533
36681
  const at2 = (seconds) => typeof seconds === "number" && seconds > 0 ? new Date(seconds * 1e3).toISOString() : void 0;
@@ -36544,8 +36692,9 @@ Review the process against ALP.md, ${agent}'s AGENT.md and the recorded lessons.
36544
36692
  }
36545
36693
  async function limitReached(session, error51) {
36546
36694
  const kind = session.runtimeKind;
36547
- if (paused.runtimes[kind]?.by === "alpd") return;
36695
+ if (pauseOf(kind)) return;
36548
36696
  const resetsAt = await limitReset(session, error51);
36697
+ if (closed || session.closed || pauseOf(kind)) return;
36549
36698
  const pause = { since: (/* @__PURE__ */ new Date()).toISOString(), by: "alpd", reason: `${label(kind)} usage limit reached`, ...resetsAt ? { resetsAt } : {} };
36550
36699
  pauseRuntime(kind, pause);
36551
36700
  const other = RUNTIMES.find((candidate) => candidate !== kind && !pauseOf(candidate));
@@ -36681,15 +36830,15 @@ ${cut(session.brief, BRIEF_CHARS)}`);
36681
36830
  const shown = JSON.stringify(items);
36682
36831
  if (!items.length || shown === session.tasksShown || session.closed) return;
36683
36832
  session.tasksShown = shown;
36684
- emit(sessionId, { type: "item", item: { kind: "todo", id: `tasks-${randomUUID11().slice(0, 8)}`, items } });
36833
+ emit(sessionId, { type: "item", item: { kind: "todo", id: `tasks-${randomUUID12().slice(0, 8)}`, items } });
36685
36834
  }
36686
36835
  function lessonFiles(mapping) {
36687
36836
  return [path30.join(mapping.agent.projectRoot, ".alp", LESSONS_FILE), ...options2.libraryDir ? [path30.join(options2.libraryDir, LESSONS_FILE)] : []];
36688
36837
  }
36689
- async function confirm(sessionId, session, body) {
36838
+ async function confirm(sessionId, session, body, approval) {
36690
36839
  if ([...userQuestions.values()].some((pending) => pending.question.sessionId === sessionId)) return { approved: false, outcome: "busy", feedback: "A question is already waiting for the user" };
36691
36840
  const say = wordsFor(session);
36692
- const result = await askUser(sessionId, session, void 0, body, [say.approve, say.reject]);
36841
+ const result = await askUser(sessionId, session, void 0, body, [say.approve, say.reject], approval);
36693
36842
  let value;
36694
36843
  try {
36695
36844
  value = JSON.parse(result.contentItems[0].text);
@@ -36700,25 +36849,45 @@ ${cut(session.brief, BRIEF_CHARS)}`);
36700
36849
  const answer = String(value.answer).trim();
36701
36850
  return APPROVALS.includes(answer.toLowerCase()) ? { approved: true } : { approved: false, outcome: "answered", feedback: answer };
36702
36851
  }
36703
- const declined = (decision) => toolResult(true, {
36704
- approved: false,
36705
- outcome: decision.outcome,
36706
- ...decision.feedback ? { feedback: decision.feedback } : {},
36707
- next: "Nothing was saved or posted. Revise from the feedback and propose again, or drop it."
36708
- });
36709
36852
  const text2 = (value, limit) => typeof value === "string" && !!value.trim() && value.length <= limit;
36853
+ const approvalCancellation = /* @__PURE__ */ new WeakMap();
36854
+ const approvalLedger = /* @__PURE__ */ new Map();
36855
+ const approvalsOf = (sessionId) => {
36856
+ let approvals = approvalLedger.get(sessionId);
36857
+ if (!approvals) approvalLedger.set(sessionId, approvals = /* @__PURE__ */ new Map());
36858
+ return approvals;
36859
+ };
36860
+ const approvalCanceled = (session, cancellation) => closed || session.closed || cancellation !== (approvalCancellation.get(session) ?? 0);
36710
36861
  async function skillTool(sessionId, session, args) {
36711
- if (!plainObject(args, ["name", "description", "body", "roles", "lessons", "replace"]) || typeof args.name !== "string" || !/^[a-z0-9][a-z0-9-]{1,62}$/.test(args.name) || !text2(args.description, 300) || !text2(args.body, SKILL_BODY_CHARS) || !Array.isArray(args.roles) || !args.roles.length || args.roles.length > 10 || !args.roles.every((role) => typeof role === "string" && /^[\w.-]+$/.test(role) && role !== "." && role !== "..") || args.lessons !== void 0 && (!Array.isArray(args.lessons) || args.lessons.length > 50 || !args.lessons.every((lesson) => text2(lesson, LESSON_CHARS2))) || args.replace !== void 0 && typeof args.replace !== "boolean") {
36862
+ const cancellation = approvalCancellation.get(session) ?? 0;
36863
+ args = structuredClone(args);
36864
+ if (!plainObject(args, ["name", "description", "body", "roles", "lessons", "replace", "requestId"]) || typeof args.name !== "string" || !/^[a-z0-9][a-z0-9-]{1,62}$/.test(args.name) || !text2(args.description, 300) || !text2(args.body, SKILL_BODY_CHARS) || !Array.isArray(args.roles) || !args.roles.length || args.roles.length > 10 || !args.roles.every((role) => typeof role === "string" && /^[\w.-]+$/.test(role) && role !== "." && role !== "..") || args.lessons !== void 0 && (!Array.isArray(args.lessons) || args.lessons.length > 50 || !args.lessons.every((lesson) => text2(lesson, LESSON_CHARS2))) || args.replace !== void 0 && typeof args.replace !== "boolean") {
36712
36865
  return toolResult(false, { error: `A skill needs a name (lowercase letters, digits, hyphens), a one-line description, a body of at most ${SKILL_BODY_CHARS} characters, and the roles that get it` });
36713
36866
  }
36714
- if (!options2.libraryDir) return toolResult(false, { error: "This host keeps no user skill library" });
36715
- const known = /* @__PURE__ */ new Set([...STARTER_ROLES, ...await discoverAgents(session.mapping.agent.projectRoot, { library: options2.libraryDir, templates: options2.templates })]);
36867
+ if (args.requestId !== void 0 && !text2(args.requestId, 100)) return toolResult(false, { error: "requestId must be a nonempty string of at most 100 characters" });
36868
+ const library = options2.libraryDir;
36869
+ if (!library) return toolResult(false, { error: "This host keeps no user skill library" });
36870
+ const known = /* @__PURE__ */ new Set([...STARTER_ROLES, ...await discoverAgents(session.mapping.agent.projectRoot, { library, templates: options2.templates })]);
36716
36871
  const unknown2 = args.roles.filter((role) => !known.has(role));
36717
36872
  if (unknown2.length) return toolResult(false, { error: `Unknown roles: ${unknown2.join(", ")}. Roles are ${[...known].join(", ")}` });
36718
- const file3 = path30.join(options2.libraryDir, "skills", args.name, "SKILL.md");
36873
+ const file3 = path30.join(library, "skills", args.name, "SKILL.md");
36874
+ const content = `---
36875
+ name: ${args.name}
36876
+ description: ${JSON.stringify(args.description.replace(/\s+/g, " ").trim())}
36877
+ ---
36878
+
36879
+ ${args.body.trim()}
36880
+ `;
36881
+ const lessons2 = (args.lessons ?? []).map((lesson) => lesson.replace(/\s+/g, " ").trim());
36882
+ const draftKey = JSON.stringify(["skill", content, args.roles, lessons2, !!args.replace]);
36883
+ const key2 = `skill:${args.requestId ?? draftKey}`;
36884
+ const approvals = approvalsOf(sessionId);
36885
+ const previous = approvals.get(key2);
36886
+ if (previous && previous.draftKey !== draftKey) return toolResult(false, { error: "requestId was already used for a different draft" });
36887
+ if (previous) return toolResult(true, previous.result ?? { status: "pending_approval", approvalId: previous.approvalId, file: file3 });
36719
36888
  const existing = await readFile18(file3, "utf8").catch(() => void 0);
36720
36889
  if (existing !== void 0 && !args.replace) return toolResult(false, { error: `The library already has a skill named ${args.name} (${file3}); read it, then pass replace: true to propose a new version, or choose another name` });
36721
- const rolesFile = path30.join(options2.libraryDir, "role-skills.json");
36890
+ const rolesFile = path30.join(library, "role-skills.json");
36722
36891
  const readRoles = async () => {
36723
36892
  const raw = await readFile18(rolesFile, "utf8").catch(() => "{}");
36724
36893
  const roles = JSON.parse(raw);
@@ -36730,62 +36899,77 @@ ${cut(session.brief, BRIEF_CHARS)}`);
36730
36899
  } catch (error51) {
36731
36900
  return toolResult(false, { error: errorData(error51).message });
36732
36901
  }
36733
- const content = `---
36734
- name: ${args.name}
36735
- description: ${JSON.stringify(args.description.replace(/\s+/g, " ").trim())}
36736
- ---
36737
-
36738
- ${args.body.trim()}
36739
- `;
36740
- const lessons2 = (args.lessons ?? []).map((lesson) => lesson.replace(/\s+/g, " ").trim());
36902
+ if (approvalCanceled(session, cancellation)) return toolResult(false, { error: "Skill proposal canceled or session closed; no approval was created" });
36903
+ if ([...userQuestions.values()].some((pending) => pending.question.sessionId === sessionId)) return toolResult(false, { error: "A question is already waiting for the user; finish it before proposing a skill" });
36904
+ const approvalId = `q-${randomUUID12()}`;
36905
+ const record4 = { approvalId, draftKey };
36906
+ approvals.set(key2, record4);
36741
36907
  const say = wordsFor(session);
36742
- const decision = await confirm(
36908
+ const decision = confirm(
36743
36909
  sessionId,
36744
36910
  session,
36745
36911
  say.skill(session.mapping.agent.name, existing !== void 0, file3, args.roles.join(", "), lessons2) + `
36746
36912
  ${say.answerLine(session.mapping.agent.name)}
36747
36913
 
36748
- ${content}`
36914
+ ${content}`,
36915
+ { id: approvalId }
36749
36916
  );
36750
- if (!decision.approved) return declined(decision);
36751
- try {
36752
- await mkdir12(path30.dirname(file3), { recursive: true });
36753
- await writeFile13(file3, content);
36754
- const roles = await readRoles();
36755
- for (const role of args.roles) {
36756
- const list2 = Array.isArray(roles[role]) ? roles[role] : [];
36757
- if (!list2.includes(args.name)) list2.push(args.name);
36758
- roles[role] = list2;
36759
- }
36760
- await writeFile13(rolesFile, JSON.stringify(roles, null, 2) + "\n");
36761
- } catch (error51) {
36762
- return toolResult(false, { error: errorData(error51).message });
36763
- }
36764
- const removed = /* @__PURE__ */ new Set();
36765
- for (const lessonFile of lessonFiles(session.mapping)) {
36766
- const current = await readFile18(lessonFile, "utf8").catch(() => void 0);
36767
- if (current === void 0) continue;
36768
- const kept = current.split("\n").filter((line) => {
36769
- const match = /^- \d{4}-\d\d-\d\d: (.*)$/.exec(line);
36770
- if (!match || !lessons2.includes(match[1].trim())) return true;
36771
- removed.add(match[1].trim());
36772
- return false;
36773
- });
36774
- if (kept.length !== current.split("\n").length) await writeFile13(lessonFile, kept.join("\n")).catch(() => {
36775
- });
36776
- }
36777
- runLog(rootOf2(sessionId), { event: "skill", agent: session.mapping.agent.name, name: args.name, roles: args.roles, replaced: existing !== void 0, lessons: [...removed] });
36778
- return toolResult(true, {
36779
- approved: true,
36780
- saved: file3,
36781
- roles: args.roles,
36782
- lessonsRemoved: [...removed],
36783
- ...lessons2.length > removed.size ? { lessonsNotFound: lessons2.filter((lesson) => !removed.has(lesson)) } : {},
36784
- next: "New sessions of those roles list the skill; read the file now if you need it in this one."
36785
- });
36917
+ const save2 = async () => {
36918
+ if (existing === void 0 && !args.replace && await readFile18(file3, "utf8").then(() => true, () => false)) {
36919
+ return { approved: true, saved: false, error: `A skill named ${args.name} was saved meanwhile (${file3}); nothing was overwritten`, next: "Read it, then propose again with replace: true if yours should replace it." };
36920
+ }
36921
+ try {
36922
+ await mkdir12(path30.dirname(file3), { recursive: true });
36923
+ await writeFile14(file3, content);
36924
+ const roles = await readRoles();
36925
+ for (const role of args.roles) {
36926
+ const list2 = Array.isArray(roles[role]) ? roles[role] : [];
36927
+ if (!list2.includes(args.name)) list2.push(args.name);
36928
+ roles[role] = list2;
36929
+ }
36930
+ await writeFile14(rolesFile, JSON.stringify(roles, null, 2) + "\n");
36931
+ } catch (error51) {
36932
+ return { approved: true, saved: false, error: errorData(error51).message, next: "Tell the user; nothing is retried automatically." };
36933
+ }
36934
+ const removed = /* @__PURE__ */ new Set();
36935
+ for (const lessonFile of lessonFiles(session.mapping)) {
36936
+ const current = await readFile18(lessonFile, "utf8").catch(() => void 0);
36937
+ if (current === void 0) continue;
36938
+ const kept = current.split("\n").filter((line) => {
36939
+ const match = /^- \d{4}-\d\d-\d\d: (.*)$/.exec(line);
36940
+ if (!match || !lessons2.includes(match[1].trim())) return true;
36941
+ removed.add(match[1].trim());
36942
+ return false;
36943
+ });
36944
+ if (kept.length !== current.split("\n").length) await writeFile14(lessonFile, kept.join("\n")).catch(() => {
36945
+ });
36946
+ }
36947
+ runLog(rootOf2(sessionId), { event: "skill", agent: session.mapping.agent.name, name: args.name, roles: args.roles, replaced: existing !== void 0, lessons: [...removed] });
36948
+ return {
36949
+ approved: true,
36950
+ saved: file3,
36951
+ roles: args.roles,
36952
+ lessonsRemoved: [...removed],
36953
+ ...lessons2.length > removed.size ? { lessonsNotFound: lessons2.filter((lesson) => !removed.has(lesson)) } : {},
36954
+ next: "New sessions of those roles list the skill; read the file now if you need it in this one."
36955
+ };
36956
+ };
36957
+ void (async () => {
36958
+ const answer = await decision;
36959
+ const canceled = approvalCanceled(session, cancellation);
36960
+ const result = !answer.approved || canceled ? { approved: false, saved: false, outcome: canceled ? "canceled" : answer.outcome, ...answer.feedback ? { feedback: answer.feedback } : {}, next: "Nothing was saved. Revise from the feedback and propose again with a fresh requestId, or drop it." } : await save2();
36961
+ record4.result = { status: "resolved", approvalId, name: args.name, ...result };
36962
+ runLog(rootOf2(sessionId), { event: "skill.approval", agent: session.mapping.agent.name, ...record4.result });
36963
+ if (!closed && !session.closed && sessions.get(sessionId) === session) {
36964
+ post(sessionId, { kind: "result", from: "ALP", assignment: approvalId, body: `Skill approval result: ${JSON.stringify(record4.result)}` });
36965
+ }
36966
+ })();
36967
+ return toolResult(true, { status: "pending_approval", approvalId, file: file3, next: "Continue other work or end your turn. ALP sends the user's decision, and whether the skill was saved, by mail; do not resubmit this draft." });
36786
36968
  }
36787
36969
  async function issueTool(sessionId, session, args) {
36788
- if (!plainObject(args, ["action", "target", "query", "title", "body", "issue", "labels"]) || !["search", "create", "comment"].includes(args.action) || !["project", "alp"].includes(args.target)) {
36970
+ const cancellation = approvalCancellation.get(session) ?? 0;
36971
+ args = structuredClone(args);
36972
+ if (!plainObject(args, ["action", "target", "query", "title", "body", "issue", "labels", "requestId"]) || !["search", "create", "comment"].includes(args.action) || !["project", "alp"].includes(args.target)) {
36789
36973
  return toolResult(false, { error: "An issue call needs action (search, create or comment) and target (project or alp)" });
36790
36974
  }
36791
36975
  const project = session.mapping.agent.projectRoot;
@@ -36811,6 +36995,7 @@ ${content}`
36811
36995
  if (args.labels !== void 0 && (args.action !== "create" || !Array.isArray(args.labels) || args.labels.length > 10 || !args.labels.every((label2) => text2(label2, 50)))) {
36812
36996
  return toolResult(false, { error: "labels are up to 10 existing label names, for create" });
36813
36997
  }
36998
+ if (args.requestId !== void 0 && !text2(args.requestId, 100)) return toolResult(false, { error: "requestId must be a nonempty string of at most 100 characters" });
36814
36999
  if (args.action === "create" && args.labels?.length) {
36815
37000
  try {
36816
37001
  const labels = JSON.parse(await github(["label", "list", "-R", repo, "--limit", "1000", "--json", "name"], { cwd: project }));
@@ -36821,9 +37006,20 @@ ${content}`
36821
37006
  return toolResult(false, { repo, error: `Could not validate issue labels: ${errorData(error51).message}` });
36822
37007
  }
36823
37008
  }
37009
+ if (approvalCanceled(session, cancellation)) return toolResult(false, { error: "Issue proposal canceled or session closed; no approval was created" });
37010
+ const draftKey = JSON.stringify([repo, args.action, args.title ?? null, args.issue ?? null, args.body, [...args.labels ?? []].sort()]);
37011
+ const key2 = `issue:${args.requestId ?? draftKey}`;
37012
+ const approvals = approvalsOf(sessionId);
37013
+ const previous = approvals.get(key2);
37014
+ if (previous && previous.draftKey !== draftKey) return toolResult(false, { error: "requestId was already used for a different draft" });
37015
+ if (previous) return toolResult(true, previous.result ?? { status: "pending_approval", approvalId: previous.approvalId, repo });
37016
+ if ([...userQuestions.values()].some((pending) => pending.question.sessionId === sessionId)) return toolResult(false, { error: "A question is already waiting for the user; finish it before proposing another issue draft" });
37017
+ const approvalId = `q-${randomUUID12()}`;
37018
+ const record4 = { approvalId, draftKey };
37019
+ approvals.set(key2, record4);
36824
37020
  const where = `${repo}${args.target === "alp" ? " (ALP itself)" : ""}`;
36825
37021
  const say = wordsFor(session);
36826
- const decision = await confirm(
37022
+ const decision = confirm(
36827
37023
  sessionId,
36828
37024
  session,
36829
37025
  say.issue(session.mapping.agent.name, args.action === "create", args.issue, where) + `
@@ -36832,18 +37028,31 @@ ${say.answerLine(session.mapping.agent.name)}
36832
37028
  ` + (args.action === "create" ? `${say.issueTitle}: ${args.title}
36833
37029
  ${args.labels?.length ? `${say.issueLabels}: ${args.labels.join(", ")}
36834
37030
  ` : ""}
36835
- ` : "") + args.body
37031
+ ` : "") + args.body.trim() + ISSUE_FOOTER,
37032
+ { id: approvalId }
36836
37033
  );
36837
- if (!decision.approved) return declined(decision);
36838
- const body = args.body.trim() + ISSUE_FOOTER;
36839
- try {
36840
- const output = args.action === "create" ? await github(["issue", "create", "-R", repo, "--title", args.title, "--body-file", "-", ...(args.labels ?? []).flatMap((label2) => ["--label", label2])], { cwd: project, input: body }) : await github(["issue", "comment", String(args.issue), "-R", repo, "--body-file", "-"], { cwd: project, input: body });
36841
- const url2 = output.trim().split("\n").at(-1) ?? "";
36842
- runLog(rootOf2(sessionId), { event: "issue", action: args.action, repo, ...args.title ? { title: args.title } : {}, ...args.issue ? { issue: args.issue } : {}, url: url2 });
36843
- return toolResult(true, { approved: true, posted: true, repo, url: url2 });
36844
- } catch (error51) {
36845
- return toolResult(false, { approved: true, posted: false, repo, error: errorData(error51).message, next: "Tell the user; the GitHub CLI may need `gh auth login`." });
36846
- }
37034
+ void (async () => {
37035
+ const answer = await decision;
37036
+ let result;
37037
+ const canceled = approvalCanceled(session, cancellation);
37038
+ if (!answer.approved || canceled) {
37039
+ result = { approved: false, posted: false, outcome: canceled ? "canceled" : answer.outcome, ...answer.feedback ? { feedback: answer.feedback } : {} };
37040
+ } else {
37041
+ try {
37042
+ const body = args.body.trim() + ISSUE_FOOTER;
37043
+ const output = args.action === "create" ? await github(["issue", "create", "-R", repo, "--title", args.title, "--body-file", "-", ...(args.labels ?? []).flatMap((label2) => ["--label", label2])], { cwd: project, input: body }) : await github(["issue", "comment", String(args.issue), "-R", repo, "--body-file", "-"], { cwd: project, input: body });
37044
+ result = { approved: true, posted: true, url: output.trim().split("\n").at(-1) ?? "" };
37045
+ } catch (error51) {
37046
+ result = { approved: true, posted: false, error: errorData(error51).message, next: "Tell the user; do not retry this draft automatically: GitHub may have accepted it before reporting an error." };
37047
+ }
37048
+ }
37049
+ record4.result = { status: "resolved", approvalId, repo, ...result };
37050
+ runLog(rootOf2(sessionId), { event: "issue.approval", action: args.action, ...args.title ? { title: args.title } : {}, ...args.issue ? { issue: args.issue } : {}, ...record4.result });
37051
+ if (!closed && !session.closed && sessions.get(sessionId) === session) {
37052
+ post(sessionId, { kind: "result", from: "ALP", assignment: approvalId, body: `Issue approval result: ${JSON.stringify(record4.result)}` });
37053
+ }
37054
+ })();
37055
+ return toolResult(true, { status: "pending_approval", approvalId, repo, next: "Continue other work. ALP sends the decision and posting result by mail; do not resubmit this draft." });
36847
37056
  }
36848
37057
  function touch(sessionId, session) {
36849
37058
  const now2 = Date.now();
@@ -36867,6 +37076,8 @@ ${args.labels?.length ? `${say.issueLabels}: ${args.labels.join(", ")}
36867
37076
  if (session && !session.closed) {
36868
37077
  emit(sessionId, { type: "mail", mail: publicEvent(mail) });
36869
37078
  session.mail.push(mail);
37079
+ if (mail.kind === "result" || mail.kind === "question") session.wakes = 0;
37080
+ if (mail.kind === "result" || mail.kind === "question" || mail.kind === "checkin") session.wakeFailures = 0;
36870
37081
  deliver(sessionId, session);
36871
37082
  }
36872
37083
  return mail;
@@ -36889,7 +37100,7 @@ ${args.labels?.length ? `${say.issueLabels}: ${args.labels.join(", ")}
36889
37100
  if (session.mail.some((event) => !event.deliveredTurn && !event.passive && !event.defer)) void steerMail(sessionId, session);
36890
37101
  } else if (!waking) {
36891
37102
  return;
36892
- } else if (!session.wakeBlocked && session.wakes < MAX_WAKES) {
37103
+ } else if (!session.wakeBlocked && session.wakeFailures < MAX_WAKES && (session.wakes < MAX_WAKES || session.mail.some((event) => !event.deliveredTurn && event.kind === "checkin"))) {
36893
37104
  autoWake(sessionId, session);
36894
37105
  } else if (session.parent && !session.wakeBlocked && !held(session)) {
36895
37106
  session.settle?.("failed", `Wake limit (${MAX_WAKES}) reached with mail outstanding`);
@@ -36900,7 +37111,7 @@ ${args.labels?.length ? `${say.issueLabels}: ${args.labels.join(", ")}
36900
37111
  const batch2 = takeBatch(session.mail, (event) => !event.defer);
36901
37112
  if (!batch2.length) return;
36902
37113
  for (const event of batch2) event.deliveredTurn = STEERING;
36903
- const id = `alp-mail-${randomUUID11()}`;
37114
+ const id = `alp-mail-${randomUUID12()}`;
36904
37115
  const text3 = promptSafe(renderMail(batch2, session.parentAgent));
36905
37116
  let steered = false;
36906
37117
  try {
@@ -36923,6 +37134,7 @@ ${args.labels?.length ? `${say.issueLabels}: ${args.labels.join(", ")}
36923
37134
  session.wakeHeld = true;
36924
37135
  return;
36925
37136
  }
37137
+ const checkIn2 = session.mail.find((event) => !event.deliveredTurn && event.kind === "checkin");
36926
37138
  const batch2 = takeBatch(session.mail, () => true);
36927
37139
  if (!batch2.length) return;
36928
37140
  for (const event of batch2) event.deliveredTurn = STARTING;
@@ -36935,11 +37147,13 @@ ${args.labels?.length ? `${say.issueLabels}: ${args.labels.join(", ")}
36935
37147
  release();
36936
37148
  return deliver(sessionId, session);
36937
37149
  }
37150
+ if (session.wakeFailures >= MAX_WAKES || session.wakes >= MAX_WAKES && (!checkIn2 || !session.mail.includes(checkIn2))) return release();
36938
37151
  if (!batch2.every((event) => event.kind === "checkin")) session.wakes++;
36939
- const id = `alp-wake-${randomUUID11()}`;
37152
+ const id = `alp-wake-${randomUUID12()}`;
36940
37153
  try {
36941
37154
  await startPrompt(sessionId, { clientMessageId: id, delivery: "auto", content: [{ type: "text", text: renderMail(batch2, session.parentAgent) }] }, "wake", batch2);
36942
37155
  } catch {
37156
+ session.wakeFailures++;
36943
37157
  release();
36944
37158
  deliver(sessionId, session);
36945
37159
  }
@@ -37039,6 +37253,17 @@ ${args.labels?.length ? `${say.issueLabels}: ${args.labels.join(", ")}
37039
37253
  waiter.resolve("user");
37040
37254
  }
37041
37255
  }
37256
+ function releaseQuestions(sessionId) {
37257
+ for (const { question, settle, asked } of [...userQuestions.values()]) {
37258
+ if (question.sessionId !== sessionId || !asked) continue;
37259
+ settle("canceled", void 0, "the user wrote instead of answering", toolResult(true, {
37260
+ status: "user_wrote",
37261
+ userMessage: true,
37262
+ question: question.id,
37263
+ next: `${USER_WROTE} Their message may be the answer to your question; if it is not, ask again later.`
37264
+ }));
37265
+ }
37266
+ }
37042
37267
  const assignmentSnapshot = (assignment, status) => ({
37043
37268
  id: assignment.id,
37044
37269
  agent: assignment.agent,
@@ -37248,7 +37473,7 @@ ${refused}`, next: "Fix what it reports, then call alp_handoff again." });
37248
37473
  const pending = [...session.assignments.keys()].some((id) => !ids || ids.has(id)) || takeBatch(session.mail, (event) => event.kind !== "board" && !event.defer && accept(event)).length > 0;
37249
37474
  const events = pending ? await waitFor(sessionId, session, accept, Math.min(args.timeoutMs ?? 3e5, 9e5)) : [];
37250
37475
  if (events === "user") return toolResult(true, { events: [], userMessage: true, running: running2(session), next: USER_WROTE });
37251
- if (!events) return toolResult(false, { error: "Turn ended" });
37476
+ if (!events) return toolResult(false, session.closed || session.wakeBlocked ? { error: "Turn ended" } : { error: TURN_ENDED, running: running2(session) });
37252
37477
  return toolResult(true, { events: events.map(publicEvent), running: running2(session) });
37253
37478
  }
37254
37479
  function sendTool(sessionId, session, args) {
@@ -37278,10 +37503,10 @@ ${refused}`, next: "Fix what it reports, then call alp_handoff again." });
37278
37503
  }
37279
37504
  return toolResult(true, { sent: post(assignment.id, { kind: args.kind, from, assignment: assignment.id, body: args.body, ...args.replyTo ? { replyTo: args.replyTo } : {} }).id });
37280
37505
  }
37281
- function askUser(sessionId, session, assignment, body, options3) {
37506
+ function askUser(sessionId, session, assignment, body, options3, approval, asked = false) {
37282
37507
  const rootId = rootOf2(sessionId);
37283
37508
  const question = {
37284
- id: `q-${randomUUID11().slice(0, 8)}`,
37509
+ id: approval?.id ?? `q-${randomUUID12().slice(0, 8)}`,
37285
37510
  sessionId,
37286
37511
  rootId,
37287
37512
  agent: session.mapping.agent.name,
@@ -37307,7 +37532,7 @@ The user answered: "${answer}"`);
37307
37532
  next: "Decide, and record the assumption in your handoff or final message; or report that you are blocked."
37308
37533
  })));
37309
37534
  };
37310
- userQuestions.set(question.id, { question, settle });
37535
+ userQuestions.set(question.id, { question, settle, persistent: !!approval, asked });
37311
37536
  if (assignment) assignment.ask = { id: question.id, resolve: (result) => settle("canceled", void 0, void 0, result) };
37312
37537
  emit(sessionId, { type: "question", question });
37313
37538
  runLog(rootId, { event: "human.question", questionId: question.id, sessionId, agent: question.agent, body, ...options3?.length ? { options: options3 } : {} });
@@ -37320,9 +37545,9 @@ The user answered: "${answer}"`);
37320
37545
  if (!session.parent || !sessions.has(session.parent)) return;
37321
37546
  post(session.parent, { kind: "note", from: session.mapping.agent.name, assignment: sessionId, body: `[ALP, on behalf of ${session.mapping.agent.name}] ${body}` });
37322
37547
  }
37323
- function cancelQuestions(sessionId) {
37324
- for (const { question, settle } of [...userQuestions.values()]) {
37325
- if (question.sessionId === sessionId) settle("canceled", void 0, void 0, toolResult(false, { error: "Turn ended" }));
37548
+ function cancelQuestions(sessionId, turnOnly = false) {
37549
+ for (const { question, settle, persistent } of [...userQuestions.values()]) {
37550
+ if (question.sessionId === sessionId && (!turnOnly || !persistent)) settle("canceled", void 0, void 0, toolResult(false, { error: "Turn ended" }));
37326
37551
  }
37327
37552
  }
37328
37553
  function askTool(sessionId, session, args) {
@@ -37342,7 +37567,7 @@ The user answered: "${answer}"`);
37342
37567
  if (to === "user" && session.parent && !session.userOpened) {
37343
37568
  return Promise.resolve(toolResult(false, { error: `Only main talks to the user; ask ${session.parentAgent ?? "your requester"} with alp_ask instead` }));
37344
37569
  }
37345
- if (to === "user") return askUser(sessionId, session, assignment, args.question, args.options);
37570
+ if (to === "user") return askUser(sessionId, session, assignment, args.question, args.options, void 0, true);
37346
37571
  if (!session.parent || !parent || !assignment) return Promise.resolve(toolResult(false, { error: 'This session has no requester; ask the user with to: "user"' }));
37347
37572
  const parentId = session.parent;
37348
37573
  return new Promise((resolve2) => {
@@ -37489,9 +37714,11 @@ The user answered: "${answer}"`);
37489
37714
  }
37490
37715
  const rootId = rootOf2(sessionId);
37491
37716
  const root = sessions.get(rootId);
37492
- if (root.calls >= 16) {
37717
+ const advisor = ["advisor", "reviewer"].includes(roleOf(session.mapping, args.agent) ?? "") && childMode === "read-only";
37718
+ const budget = advisor ? "advisorCalls" : "calls";
37719
+ if (root[budget] >= 16) {
37493
37720
  return toolResult(false, {
37494
- error: "Delegation limit reached for this turn"
37721
+ error: `${advisor ? "Advisor" : "Implementation"} delegation budget exhausted (16 starts across the root session tree since its last new user turn). Mail wakes and child turns do not reset it; a new accepted user prompt in root (including steering) resets both budgets.`
37495
37722
  });
37496
37723
  }
37497
37724
  const peer = roleOf(session.mapping, args.agent) === "peer";
@@ -37504,9 +37731,9 @@ The user answered: "${answer}"`);
37504
37731
  const continued = args.continueFrom !== void 0 ? session.worktrees.get(args.continueFrom) : void 0;
37505
37732
  if (args.continueFrom !== void 0 && !continued && !sharedContinuation) return toolResult(false, { error: `${args.continueFrom} was merged or discarded meanwhile` });
37506
37733
  if (continued) session.worktrees.delete(args.continueFrom);
37507
- root.calls++;
37734
+ root[budget]++;
37508
37735
  if (peer) root.peerCount++;
37509
- const childId = `alp-child-${randomUUID11()}`;
37736
+ const childId = `alp-child-${randomUUID12()}`;
37510
37737
  const startedAt = Date.now();
37511
37738
  const assignment = { id: childId, agent: args.agent, mode: childMode, isolation, rootId, startedAt, warned: false, finished: false, ...writes(childMode) ? { writeCheckout: checkout } : {}, ...args.etaMinutes ? { eta: startedAt + args.etaMinutes * 6e4 } : {} };
37512
37739
  if (sharedWriter && !holder) {
@@ -37664,12 +37891,15 @@ ${digest2}` : "")
37664
37891
  const events = await waitFor(sessionId, session, (event2) => event2.assignment === childId && (event2.kind === "result" || event2.kind === "question"), void 0, true);
37665
37892
  if (events === "user") return toolResult(true, { assignmentId: childId, agent: args.agent, status: "running", userMessage: true, next: USER_WROTE });
37666
37893
  if (!events) {
37667
- return toolResult(false, {
37668
- agent: args.agent,
37669
- assignmentId: childId,
37670
- status: "canceled",
37671
- error: "Parent assignment stopped"
37672
- });
37894
+ if (session.closed || session.wakeBlocked || !session.assignments.has(childId)) {
37895
+ return toolResult(false, {
37896
+ agent: args.agent,
37897
+ assignmentId: childId,
37898
+ status: "canceled",
37899
+ error: "Parent assignment stopped"
37900
+ });
37901
+ }
37902
+ return toolResult(false, { agent: args.agent, assignmentId: childId, status: "running", error: TURN_ENDED });
37673
37903
  }
37674
37904
  const event = events.find((candidate) => candidate.kind === "result") ?? events.find((candidate) => candidate.kind === "question");
37675
37905
  if (!event) return toolResult(true, { assignmentId: childId, agent: args.agent, status: "running", events: events.map(publicEvent), next: CHECKED_IN });
@@ -37735,10 +37965,12 @@ ${digest2}` : "")
37735
37965
  peerCount: 0,
37736
37966
  assignments: /* @__PURE__ */ new Map(),
37737
37967
  calls: 0,
37968
+ advisorCalls: 0,
37738
37969
  mail: [],
37739
37970
  waiters: [],
37740
37971
  lastActivity: Date.now(),
37741
37972
  wakes: 0,
37973
+ wakeFailures: 0,
37742
37974
  wakeBlocked: false,
37743
37975
  toolCalls: /* @__PURE__ */ new Map(),
37744
37976
  acknowledged: Promise.resolve(),
@@ -38043,7 +38275,7 @@ Write scope: change files only under ${session.spec.writeRoot}. Your working dir
38043
38275
  runLog(entry.rootId, { event: "assignment.finished", assignmentId: entry.assignmentId, reconciled: true, ...result });
38044
38276
  if (parent && !parent.closed) post(entry.parentId, { kind: "result", from: entry.agent, assignment: entry.assignmentId, result });
38045
38277
  }
38046
- async function recoverTree(rootId, continueRoot) {
38278
+ async function recoverTree(rootId, continueRoot, parkedRoot) {
38047
38279
  const root = sessions.get(rootId);
38048
38280
  if (!root || root.closed) throw new Error(`Session ${rootId} is not open`);
38049
38281
  const entries2 = inherited.filter((entry) => entry.rootId === rootId).sort((a, b) => a.ancestry.length - b.ancestry.length);
@@ -38072,7 +38304,7 @@ Write scope: change files only under ${session.spec.writeRoot}. Your working dir
38072
38304
  if (outcome) outcome.outcome = "parked";
38073
38305
  return;
38074
38306
  }
38075
- void startPrompt(sessionId, { clientMessageId: `alp-recover-${randomUUID11()}`, delivery: "auto", content: [{ type: "text", text: text3 }] }, session.parent ? "assignment" : "wake").catch((error51) => session.settle?.("failed", error51));
38307
+ void startPrompt(sessionId, { clientMessageId: `alp-recover-${randomUUID12()}`, delivery: "auto", content: [{ type: "text", text: text3 }] }, session.parent ? "assignment" : "wake").catch((error51) => session.settle?.("failed", error51));
38076
38308
  };
38077
38309
  for (const entry of entries2) {
38078
38310
  const child = sessions.get(entry.assignmentId);
@@ -38080,7 +38312,11 @@ Write scope: change files only under ${session.spec.writeRoot}. Your working dir
38080
38312
  runLog(rootId, { event: "assignment.recovered", assignmentId: entry.assignmentId, agent: entry.agent, ...entry.taskId ? { taskId: entry.taskId } : {} });
38081
38313
  proceed(entry.assignmentId, child, `ALP: ${cause} while you worked on this assignment, and reopened this session. Your earlier work in it${entry.worktree ? " and your worktree" : ""} is intact; check where you stopped (for example with git status), then continue. Finish with alp_handoff as before.${listed(resumed.get(entry.assignmentId))}`);
38082
38314
  }
38083
- if (continueRoot) proceed(rootId, root, `ALP: ${cause} during your turn, and reopened this session. Continue the user's request where you stopped.${listed(resumed.get(rootId))}`);
38315
+ if (parkedRoot) {
38316
+ root.parked = { reason: parkedRoot, since: Date.now() };
38317
+ emit(rootId, { type: "session.updated", session: snapshot(rootId, root) });
38318
+ if (options2.autoResume !== false && options2.recoveryResume !== false && !pauseOf(root.runtimeKind)) continueParked(rootId, root);
38319
+ } else if (continueRoot) proceed(rootId, root, `ALP: ${cause} during your turn, and reopened this session. Continue the user's request where you stopped.${listed(resumed.get(rootId))}`);
38084
38320
  else for (const assignment of resumed.get(rootId) ?? []) post(rootId, { kind: "note", from: assignment.agent, assignment: assignment.id, passive: true, body: `${cause}; ALP reopened this assignment and it continues. It reports when done.` });
38085
38321
  const failed = outcomes.filter((outcome) => outcome.outcome === "failed");
38086
38322
  if (entries2.length || continueRoot) {
@@ -38127,6 +38363,11 @@ Write scope: change files only under ${session.spec.writeRoot}. Your working dir
38127
38363
  const activeTurn = session.active;
38128
38364
  session.wakeBlocked = true;
38129
38365
  session.userStopped = true;
38366
+ session.parked = void 0;
38367
+ session.parkReason = void 0;
38368
+ emit(sessionId, { type: "session.updated", session: snapshot(sessionId, session) });
38369
+ approvalCancellation.set(session, (approvalCancellation.get(session) ?? 0) + 1);
38370
+ cancelQuestions(sessionId);
38130
38371
  if (activeTurn && session.runtimeKind === "codex" && session.threadId) {
38131
38372
  const partial2 = [...session.text.values()].join("\n").trim();
38132
38373
  if (partial2) interruptedThreads.set(session.threadId, partial2.slice(-4e3));
@@ -38199,11 +38440,9 @@ Write scope: change files only under ${session.spec.writeRoot}. Your working dir
38199
38440
  if (!wake) {
38200
38441
  session.handoff = void 0;
38201
38442
  session.wakes = 0;
38443
+ session.wakeFailures = 0;
38202
38444
  session.wakeBlocked = false;
38203
38445
  session.userStopped = false;
38204
- if (!session.parent) {
38205
- session.calls = 0;
38206
- }
38207
38446
  }
38208
38447
  }
38209
38448
  try {
@@ -38245,6 +38484,10 @@ ${interrupted}`), text_elements: [] }] : [],
38245
38484
  );
38246
38485
  const turnId = prompt.delivery === "steer" ? session.active : result.turn.id;
38247
38486
  for (const event of mail) event.deliveredTurn = turnId;
38487
+ if (!session.parent && origin === "user") {
38488
+ session.calls = 0;
38489
+ session.advisorCalls = 0;
38490
+ }
38248
38491
  session.seen.add(
38249
38492
  prompt.clientMessageId
38250
38493
  );
@@ -38260,7 +38503,10 @@ ${interrupted}`), text_elements: [] }] : [],
38260
38503
  });
38261
38504
  if (origin === "user" && !session.parent) session.userWaiting ??= Date.now();
38262
38505
  if (session.supervisor && origin === "user") session.journal.push(stamp(`user ${prompt.delivery === "steer" ? "steered" : "asked"} ${session.mapping.agent.name}: ${clip(text3, 1500)}`));
38263
- if (prompt.delivery === "steer" && origin === "user" && !session.parent) releaseWaiters(session);
38506
+ if (prompt.delivery === "steer" && origin === "user" && !session.parent) {
38507
+ releaseWaiters(session);
38508
+ releaseQuestions(sessionId);
38509
+ }
38264
38510
  emit(sessionId, {
38265
38511
  type: "prompt.accepted",
38266
38512
  clientMessageId: prompt.clientMessageId,
@@ -38301,12 +38547,19 @@ ${interrupted}`), text_elements: [] }] : [],
38301
38547
  return opened;
38302
38548
  }),
38303
38549
  prompt: (sessionId, input) => enqueue(async () => {
38550
+ const session = sessions.get(sessionId);
38551
+ const parked = session?.parked;
38304
38552
  try {
38305
- await startPrompt(sessionId, input, "user");
38553
+ const prompt = session?.active && session.resumedTurn === session.active && input.delivery === "auto" ? { ...input, delivery: "steer" } : input;
38554
+ await startPrompt(sessionId, prompt, "user");
38555
+ if (session && parked && session.parked === parked) {
38556
+ session.parked = void 0;
38557
+ emit(sessionId, { type: "session.updated", session: snapshot(sessionId, session) });
38558
+ }
38306
38559
  } catch (error51) {
38307
- const session = sessions.get(sessionId);
38308
- if (session?.seen.has(input.clientMessageId)) return;
38309
- session?.seen.add(input.clientMessageId);
38560
+ const session2 = sessions.get(sessionId);
38561
+ if (session2?.seen.has(input.clientMessageId)) return;
38562
+ session2?.seen.add(input.clientMessageId);
38310
38563
  emit(sessionId, { type: "prompt.failed", clientMessageId: input.clientMessageId, error: errorData(error51) });
38311
38564
  }
38312
38565
  }),
@@ -38450,8 +38703,8 @@ ${interrupted}`), text_elements: [] }] : [],
38450
38703
  recoverable() {
38451
38704
  return [...inherited];
38452
38705
  },
38453
- recover(rootId, { continueRoot = false } = {}) {
38454
- return enqueue(() => recoverTree(rootId, continueRoot));
38706
+ recover(rootId, { continueRoot = false, parkedRoot } = {}) {
38707
+ return enqueue(() => recoverTree(rootId, continueRoot, parkedRoot));
38455
38708
  },
38456
38709
  async abandon(rootId, reason) {
38457
38710
  const entries2 = inherited.filter((entry) => entry.rootId === rootId);
@@ -38484,13 +38737,13 @@ ${interrupted}`), text_elements: [] }] : [],
38484
38737
 
38485
38738
  // packages/daemon/src/server.ts
38486
38739
  import { createHash as createHash11, randomBytes as randomBytes7 } from "node:crypto";
38487
- import { appendFile as appendFile3, chmod, mkdir as mkdir14, readFile as readFile19, unlink as unlink4, lstat as lstat7, rename as rename7 } from "node:fs/promises";
38740
+ import { appendFile as appendFile3, chmod, mkdir as mkdir14, readFile as readFile19, unlink as unlink4, lstat as lstat8, rename as rename8 } from "node:fs/promises";
38488
38741
  import net3 from "node:net";
38489
38742
  import os9 from "node:os";
38490
38743
  import path35 from "node:path";
38491
38744
 
38492
38745
  // packages/protocol/src/contract-v1.js
38493
- var METHODS_V1 = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://alp.dev/schemas/methods-v1.json", "protocolVersion": 1, "errors": { "parseError": -32700, "invalidRequest": -32600, "methodNotFound": -32601, "invalidParams": -32602, "failed": 1e3, "notFound": 1001, "forbidden": 1003, "conflict": 1004, "unavailable": 1005, "protocol": 1006 }, "capabilities": { "attach.after": "session.attach accepts `after` and answers `cursor`/`reset`; envelopes kept in a tree timeline carry `cursor`.", "prompt.receipts": "session.prompt is answered at most once per (sessionId, clientMessageId) from a durable receipt; repeats answer the first outcome or outcome_unknown.", "extension.methods": "extension.register/unregister, and the daemon sends extension.invoke requests to the owning connection.", "team": "team.list: the teams a project can use, from the daemon host's built-ins, library and project.", "task": "task.list, task.create, task.update and task.gate.resolve on a project's .alp/tasks, written by the daemon under the task lock.", "library": "library.list/get/save/delete/duplicate/rename, library.skills.set and library.models on the daemon user's library and a project's .alp overrides; secrets are redacted on read.", "settings.language": "settings.language.get/set: the language the daemon user reads, in the daemon's ALP_HOME/settings.json.", "session.list.page": "session.list accepts `limit`, `cursor` and `query` and answers `nextCursor` while more sessions follow; without them it answers every session as before.", "question.watch": "question.watch: the connection is told of `question` and `question.resolved` in every tree (of a projectRoot when given), attached or not.", "question.watch.turns": "question.watch accepts `turns`: the connection is also told of `turn.ended` of every root in the trees it watches.", "workspace.files": "workspace.files.search: files under a directory whose relative path matches a query; read-only, names only, links never followed.", "workspace.files.read": "workspace.files.list and workspace.files.read: the entries of a directory and the content of a UTF-8 text file (at most 1 MiB) under a directory; read-only, links never followed, .git, .env* and key or credential files refused, .gitignore'd paths hidden unless asked.", "prompt.images": 'session.prompt takes inline image parts { type: "image", mimeType, data } (base64, no paths or URLs) within the image limits; a session snapshot says whether its runtime takes them in promptImages.' }, "methods": { "assignment.recall": { "x-alp": { "effect": "mutation", "scope": { "kind": "project", "param": "projectRoot", "required": false, "alternatives": ["assignmentId", "taskId"] }, "idempotent": false, "risks": ["model call"] }, "params": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "taskId": { "type": "string" }, "projectRoot": { "type": "string" }, "question": { "type": "string" } }, "required": ["question"], "additionalProperties": true }, "result": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "agent": { "type": "string" }, "taskId": { "type": "string" }, "finishedAt": { "type": "string" }, "answer": { "type": "string" } }, "required": ["assignmentId", "agent", "finishedAt", "answer"], "additionalProperties": true } }, "board.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "pins": { "type": "array", "items": { "$ref": "#/$defs/pin" } } }, "required": ["pins"], "additionalProperties": true } }, "catalog.get": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "models": { "type": "array", "items": { "type": "object" } }, "modes": { "type": "array", "items": { "type": "object" } }, "thinkingOptions": {}, "defaultModel": { "type": "string" } }, "required": ["models", "modes", "defaultModel"], "additionalProperties": true } }, "daemon.hello": { "x-alp": { "effect": "query", "scope": { "kind": "connection" }, "idempotent": true }, "params": { "type": "object", "properties": { "protocolVersion": { "const": 1 }, "client": { "type": "object", "properties": { "name": { "type": "string" }, "version": { "type": "string" } }, "required": ["name", "version"], "additionalProperties": true }, "capabilities": { "type": "array", "items": { "type": "string" } } }, "required": ["protocolVersion"], "additionalProperties": true }, "result": { "type": "object", "properties": { "protocolVersion": { "const": 1 }, "daemonVersion": { "type": "string" }, "pid": { "type": "integer" }, "hostname": { "type": "string" }, "capabilities": { "type": "array", "items": { "type": "string" } } }, "required": ["protocolVersion"], "additionalProperties": true } }, "daemon.pause": { "x-alp": { "effect": "mutation", "scope": { "kind": "host" }, "idempotent": true, "risks": ["admin"] }, "params": { "type": "object", "properties": { "runtime": { "enum": ["codex", "claude", "acp", "antigravity"] }, "now": { "type": "boolean" }, "reason": { "type": "string" } }, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/pauseState" } }, "daemon.pauses": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/pauseState" } }, "daemon.resume": { "x-alp": { "effect": "mutation", "scope": { "kind": "host" }, "idempotent": true, "risks": ["admin"] }, "params": { "type": "object", "properties": { "runtime": { "enum": ["codex", "claude", "acp", "antigravity"] } }, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/pauseState" } }, "daemon.shutdown": { "x-alp": { "effect": "mutation", "scope": { "kind": "host" }, "idempotent": true, "risks": ["admin"] }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "daemon.status": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "pid": { "type": "integer" }, "startedAt": { "type": "string" }, "version": { "type": "string" }, "protocolVersion": { "type": "integer" }, "sessions": { "type": "integer" }, "questions": { "type": "integer" }, "previousExit": { "type": "object", "properties": { "kind": { "enum": ["clean", "crash"] }, "at": { "type": "string" } }, "required": ["kind"], "additionalProperties": true }, "persistence": { "type": "object" }, "health": { "$ref": "#/$defs/health" } }, "required": ["pid", "startedAt", "version", "protocolVersion", "sessions", "questions", "health"], "additionalProperties": true } }, "daemon.usage": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true, "risks": ["refresh asks every provider (spawns on the daemon host)"] }, "params": { "type": "object", "properties": { "refresh": { "type": "boolean" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "providers": { "type": "array", "items": { "type": "object" } } }, "required": ["providers"], "additionalProperties": true } }, "extension.register": { "x-alp": { "effect": "mutation", "scope": { "kind": "connection" }, "idempotent": false, "risks": ["admin"] }, "params": { "type": "object", "properties": { "method": { "type": "string" } }, "required": ["method"], "additionalProperties": true }, "result": { "type": "object", "properties": { "registered": { "type": "string" } }, "required": ["registered"], "additionalProperties": true } }, "extension.unregister": { "x-alp": { "effect": "mutation", "scope": { "kind": "connection" }, "idempotent": false, "risks": ["admin"] }, "params": { "type": "object", "properties": { "method": { "type": "string" } }, "required": ["method"], "additionalProperties": true }, "result": { "type": "object", "properties": { "unregistered": { "type": "string" } }, "required": ["unregistered"], "additionalProperties": true } }, "library.delete": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope" }, "idempotent": false }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "revision": { "type": "string" } }, "required": ["kind", "name", "scope"], "additionalProperties": true }, "result": { "type": "object", "properties": { "removed": { "const": true }, "now": { "enum": ["builtin", "library"] } }, "required": ["removed"], "additionalProperties": true } }, "library.duplicate": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope", "reads": ["builtin", "library"] }, "idempotent": false, "risks": ["exec-config when kind is mcp, hooks or providers"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "from": { "$ref": "#/$defs/entryName" }, "to": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": ["kind", "from", "to", "scope"], "additionalProperties": true }, "result": { "type": "object", "properties": { "revision": { "$ref": "#/$defs/libraryRevision" } }, "required": ["revision"], "additionalProperties": true } }, "library.get": { "x-alp": { "effect": "query", "capability": "library", "scope": { "kind": "project", "param": "projectRoot", "required": false, "reads": ["builtin", "library"] }, "idempotent": true, "redacts": ["mcp /server/env/*", "mcp /server/headers/*", "providers /provider/env/*"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "$ref": "#/$defs/entryName" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "scope": { "$ref": "#/$defs/libraryScope" } }, "required": ["kind", "name"], "additionalProperties": true }, "result": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "type": "string" }, "source": { "$ref": "#/$defs/librarySource" }, "overrides": { "$ref": "#/$defs/librarySource" }, "content": { "type": "object" }, "revision": { "$ref": "#/$defs/libraryRevision" }, "usedBy": { "type": "array", "items": { "type": "string" } }, "librarySkills": { "type": "array", "items": { "type": "string" } }, "secrets": { "type": "array", "items": { "type": "string" } } }, "required": ["kind", "name", "source", "content", "revision", "usedBy"], "additionalProperties": true } }, "library.list": { "x-alp": { "effect": "query", "capability": "library", "scope": { "kind": "project", "param": "projectRoot", "required": false, "reads": ["builtin", "library"] }, "idempotent": true }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": ["kind"], "additionalProperties": true }, "result": { "type": "object", "properties": { "projectRoot": { "type": ["string", "null"] }, "entries": { "type": "array", "items": { "$ref": "#/$defs/libraryEntryRow" } } }, "required": ["projectRoot", "entries"], "additionalProperties": true } }, "library.models": { "x-alp": { "effect": "query", "capability": "library", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "models": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "efforts": { "type": "array", "items": { "type": "string" } }, "effort": { "type": "string" } }, "required": ["id", "label", "efforts"], "additionalProperties": true } } }, "required": ["models"], "additionalProperties": true } }, "library.rename": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope" }, "idempotent": false, "risks": ["exec-config when kind is mcp, hooks or providers"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "from": { "$ref": "#/$defs/entryName" }, "to": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": ["kind", "from", "to", "scope"], "additionalProperties": true }, "result": { "type": "object", "properties": { "revision": { "$ref": "#/$defs/libraryRevision" } }, "required": ["revision"], "additionalProperties": true } }, "library.save": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope" }, "idempotent": "with revision (a repeat fails revision_conflict)", "risks": ["exec-config when kind is mcp, hooks or providers, or agents naming mcp/hooks/provider", "writes every known project's .alp/settings.json when kind is teams and sync is known-projects"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "content": { "type": "object" }, "revision": { "$ref": "#/$defs/libraryRevision" }, "sync": { "enum": ["none", "known-projects"] } }, "required": ["kind", "name", "scope", "content"], "additionalProperties": true }, "result": { "type": "object", "properties": { "revision": { "$ref": "#/$defs/libraryRevision" }, "syncedProjects": { "type": "integer", "minimum": 0 }, "syncErrors": { "type": "array", "items": { "type": "string" } } }, "required": ["revision"], "additionalProperties": true } }, "library.skills.set": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "host" }, "writes": ["ALP_HOME/role-skills.json"], "idempotent": true }, "params": { "type": "object", "properties": { "agent": { "$ref": "#/$defs/entryName" }, "skills": { "type": "array", "items": { "$ref": "#/$defs/entryName" }, "maxItems": 100 } }, "required": ["agent", "skills"], "additionalProperties": true }, "result": { "type": "object", "properties": { "skills": { "type": "array", "items": { "type": "string" } } }, "required": ["skills"], "additionalProperties": true } }, "question.answer": { "x-alp": { "effect": "mutation", "scope": { "kind": "question", "param": "questionId", "prefix": true }, "idempotent": false }, "params": { "type": "object", "properties": { "questionId": { "type": "string" }, "text": { "type": "string" }, "dismiss": { "type": "boolean" }, "reason": { "type": "string" } }, "required": ["questionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "questionId": { "type": "string" } }, "required": ["questionId"], "additionalProperties": true } }, "question.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": false }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "questions": { "type": "array", "items": { "$ref": "#/$defs/question" } } }, "required": ["questions"], "additionalProperties": true } }, "question.watch": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": false }, "idempotent": true, "capability": "question.watch" }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "turns": { "type": "boolean" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "questions": { "type": "array", "items": { "$ref": "#/$defs/question" } } }, "required": ["questions"], "additionalProperties": true } }, "session.archive": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "archived": { "type": "boolean" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" } }, "required": ["session"], "additionalProperties": true } }, "session.attach": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "replay": { "type": "boolean" }, "after": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" }, "cursor": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "reset": { "const": true } }, "required": ["session", "cursor"], "additionalProperties": true } }, "session.close": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": false }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "session.configure": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "mode": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/session" } }, "required": ["session"], "additionalProperties": true } }, "session.create": { "x-alp": { "effect": "mutation", "scope": { "kind": "project", "param": "spec.cwd" }, "idempotent": false, "risks": ["exec: spec.env and stdio spec.mcpServers run on the daemon host", "spec.workdir/writeRoot/copy*/restore/keepThread are internal"] }, "params": { "type": "object", "properties": { "sessionId": { "type": "string", "pattern": "^[A-Za-z0-9_.:-]{1,128}$" }, "spec": { "$ref": "#/$defs/spec" }, "history": { "enum": ["skip", "replay"] }, "delegation": { "type": "boolean" }, "resume": { "type": "boolean" } }, "required": ["spec"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/session" }, "attached": { "type": "boolean" }, "resumed": { "const": true }, "durable": { "type": "boolean" } }, "required": ["session", "attached"], "additionalProperties": true } }, "session.get": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" } }, "required": ["session"], "additionalProperties": true } }, "session.interrupt": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "session.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": false }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "rootsOnly": { "type": "boolean" }, "includeClosed": { "type": "boolean" }, "includeArchived": { "type": "boolean" }, "limit": { "type": "integer", "minimum": 1, "maximum": 1e3 }, "cursor": { "type": "string", "minLength": 1, "maxLength": 1024 }, "query": { "type": "string", "maxLength": 200 }, "projectRoots": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 1024 } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "sessions": { "type": "array", "items": { "$ref": "#/$defs/sessionSummary" } }, "nextCursor": { "type": "string", "minLength": 1 } }, "required": ["sessions"], "additionalProperties": true } }, "session.log": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "rootId": { "type": "string" }, "entries": { "type": "array", "items": { "type": "object" } } }, "required": ["rootId", "entries"], "additionalProperties": true } }, "session.message": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": false }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "text": { "type": "string" } }, "required": ["sessionId", "text"], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "session.preview": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "spec.cwd" }, "idempotent": true }, "params": { "type": "object", "properties": { "spec": { "$ref": "#/$defs/spec" } }, "required": ["spec"], "additionalProperties": true }, "result": { "type": "object", "properties": { "preview": { "$ref": "#/$defs/preview" } }, "required": ["preview"], "additionalProperties": true } }, "session.prompt": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": "with prompt.receipts" }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "clientMessageId": { "type": "string" }, "delivery": { "enum": ["auto", "steer"] }, "content": { "type": "array", "items": { "anyOf": [{ "type": "object", "properties": { "type": { "const": "text" }, "text": { "type": "string" } }, "required": ["type"], "additionalProperties": true }, { "$ref": "#/$defs/promptImage" }, { "type": "object", "properties": { "type": { "type": "string", "not": { "enum": ["text", "image"] } } }, "required": ["type"], "additionalProperties": true }] } } }, "required": ["sessionId", "clientMessageId", "content"], "additionalProperties": true }, "result": { "type": "object", "properties": { "outcome": { "enum": ["accepted", "failed"] }, "error": { "$ref": "#/$defs/alpError" }, "duplicate": { "type": "boolean" }, "durable": { "type": "boolean" } }, "required": [], "additionalProperties": true } }, "session.release": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "closed": { "type": "boolean" } }, "required": ["closed"], "additionalProperties": true } }, "session.rename": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "title": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" } }, "required": ["sessionId", "title"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" } }, "required": ["session"], "additionalProperties": true } }, "session.status": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "status": { "$ref": "#/$defs/treeStatus" } }, "required": ["status"], "additionalProperties": true } }, "settings.language.get": { "x-alp": { "effect": "query", "capability": "settings.language", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/languageSetting" } }, "settings.language.set": { "x-alp": { "effect": "mutation", "capability": "settings.language", "scope": { "kind": "host" }, "writes": ["ALP_HOME/settings.json"], "idempotent": true }, "params": { "type": "object", "properties": { "language": { "anyOf": [{ "type": "string", "minLength": 1, "maxLength": 40, "pattern": "\\S" }, { "type": "null" }] } }, "required": ["language"], "additionalProperties": true }, "result": { "$ref": "#/$defs/languageSetting" } }, "task.create": { "x-alp": { "effect": "mutation", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "writes": ["<projectRoot>/.alp/tasks"], "idempotent": "per requestId within one alpd process (10 minutes)" }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" }, "title": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" }, "priority": { "type": "integer", "minimum": 0, "maximum": 4 }, "requestId": { "type": "string", "minLength": 1, "maxLength": 128 } }, "required": ["projectRoot", "title"], "additionalProperties": true }, "result": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "duplicate": { "const": true } }, "required": ["id"], "additionalProperties": true } }, "task.gate.resolve": { "x-alp": { "effect": "mutation", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "writes": ["<projectRoot>/.alp/tasks"], "idempotent": false }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" }, "id": { "$ref": "#/$defs/taskId" }, "gate": { "type": "string", "minLength": 1, "maxLength": 128 }, "note": { "type": "string", "maxLength": 2e3 }, "ifRev": { "type": "integer", "minimum": 0 } }, "required": ["projectRoot", "id", "gate"], "additionalProperties": true }, "result": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "status": { "type": "string" }, "rev": { "type": "integer", "minimum": 0 } }, "required": ["id", "status", "rev"], "additionalProperties": true } }, "task.list": { "x-alp": { "effect": "query", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "tasks": { "type": "array", "items": { "$ref": "#/$defs/taskRow" } }, "unreadable": { "type": "array", "items": { "type": "object", "properties": { "file": { "type": "string" }, "error": { "type": "string" } }, "required": ["file", "error"], "additionalProperties": true } }, "revision": { "type": "string" } }, "required": ["projectRoot", "tasks", "unreadable", "revision"], "additionalProperties": true } }, "task.update": { "x-alp": { "effect": "mutation", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "writes": ["<projectRoot>/.alp/tasks"], "idempotent": false }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" }, "id": { "$ref": "#/$defs/taskId" }, "action": { "enum": ["close", "reopen"] }, "note": { "type": "string", "maxLength": 2e3 }, "ifRev": { "type": "integer", "minimum": 0 } }, "required": ["projectRoot", "id", "action"], "additionalProperties": true }, "result": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "status": { "type": "string" }, "rev": { "type": "integer", "minimum": 0 }, "landed": { "type": "string" } }, "required": ["id", "status", "rev"], "additionalProperties": true } }, "team.list": { "x-alp": { "effect": "query", "capability": "team", "scope": { "kind": "project", "param": "projectRoot", "required": false, "reads": ["builtin", "library"] }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "projectRoot": { "type": ["string", "null"] }, "teams": { "type": "array", "items": { "$ref": "#/$defs/teamRow" } } }, "required": ["projectRoot", "teams"], "additionalProperties": true } }, "workspace.files.search": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true, "capability": "workspace.files", "risks": ["lists file names of the directory (not contents)"] }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "query": { "type": "string", "maxLength": 200 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "files": { "type": "array", "maxItems": 100, "items": { "type": "object", "properties": { "path": { "type": "string", "minLength": 1 } }, "required": ["path"], "additionalProperties": true } }, "truncated": { "type": "boolean" } }, "required": ["files"], "additionalProperties": true } }, "workspace.files.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true, "capability": "workspace.files.read", "risks": ["lists names, kinds, sizes and times of a directory's entries (not contents)"] }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "path": { "type": "string", "minLength": 1, "maxLength": 4096 }, "includeIgnored": { "type": "boolean" } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "entries": { "type": "array", "maxItems": 1e3, "items": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1 }, "path": { "type": "string", "minLength": 1 }, "kind": { "enum": ["file", "directory"] }, "size": { "type": "integer", "minimum": 0 }, "modifiedAt": { "type": "string" } }, "required": ["name", "path", "kind"], "additionalProperties": true } }, "truncated": { "type": "boolean" } }, "required": ["entries"], "additionalProperties": true } }, "workspace.files.read": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true, "capability": "workspace.files.read", "risks": ["returns the content of a text file under the directory"] }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "path": { "type": "string", "minLength": 1, "maxLength": 4096 }, "includeIgnored": { "type": "boolean" } }, "required": ["projectRoot", "path"], "additionalProperties": true }, "result": { "type": "object", "properties": { "path": { "type": "string", "minLength": 1 }, "content": { "type": "string" }, "size": { "type": "integer", "minimum": 0, "maximum": 1048576 }, "modifiedAt": { "type": "string" } }, "required": ["path", "content", "size"], "additionalProperties": true } } }, "$defs": { "error": { "type": "object", "properties": { "code": { "type": "integer" }, "message": { "type": "string" }, "data": {} }, "required": ["code", "message"], "additionalProperties": true }, "session": { "type": "object", "properties": { "id": { "type": "string" }, "projectRoot": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "workflow": { "type": "object", "properties": { "mode": { "type": "string" }, "maxPeers": { "type": "integer" }, "supervisor": { "type": "boolean" } }, "required": ["mode", "maxPeers", "supervisor"], "additionalProperties": true }, "threadId": { "type": "string" }, "persistent": { "type": "boolean" }, "busy": { "type": "boolean" }, "parentId": { "type": "string" }, "toolCallId": { "type": "string" }, "activeTurnId": { "type": "string" }, "parked": { "type": "string" }, "instructionsSha": { "type": "string" }, "teamLabel": { "type": "string" }, "promptImages": { "type": "boolean" }, "usage": { "$ref": "#/$defs/usage" } }, "required": ["id", "projectRoot", "agent", "runtime", "model", "mode", "thinking", "workflow", "threadId", "persistent", "busy"], "additionalProperties": true }, "usage": { "type": "object", "properties": { "contextWindowUsedTokens": { "type": "integer", "minimum": 0 }, "contextWindowMaxTokens": { "type": "integer", "minimum": 1 }, "inputTokens": { "type": "integer", "minimum": 0 }, "cachedInputTokens": { "type": "integer", "minimum": 0 }, "outputTokens": { "type": "integer", "minimum": 0 }, "totalCostUsd": { "type": "number", "minimum": 0 } }, "additionalProperties": true }, "assignment": { "type": "object", "properties": { "id": { "type": "string" }, "agent": { "type": "string" }, "mode": { "type": "string" }, "status": { "type": "string" }, "startedAt": { "type": "string" }, "worktree": { "type": "object", "properties": { "branch": { "type": "string" }, "path": { "type": "string" } }, "required": ["branch", "path"], "additionalProperties": true } }, "required": ["id", "agent", "mode", "status", "startedAt"], "additionalProperties": true }, "mail": { "type": "object", "properties": { "id": { "type": "string" }, "kind": { "enum": ["question", "answer", "note", "steer", "result", "stalled", "board", "checkin"] }, "from": { "type": "string" }, "assignment": { "type": "string" }, "body": { "type": "string" }, "replyTo": { "type": "string" }, "result": { "type": "object" }, "redelivered": { "type": "boolean" } }, "required": ["id", "kind", "from", "assignment"], "additionalProperties": true }, "pin": { "type": "object", "properties": { "id": { "type": "string" }, "project": { "type": "string" }, "kind": { "enum": ["claim", "decision", "finding"] }, "body": { "type": "string" }, "paths": { "type": "array", "items": { "type": "string" } }, "agent": { "type": "string" }, "sessionId": { "type": "string" }, "rootId": { "type": "string" }, "at": { "type": "string" }, "released": { "type": "string" }, "task": { "type": "string" } }, "required": ["id", "project", "kind", "body", "agent", "sessionId", "rootId", "at"], "additionalProperties": true }, "question": { "type": "object", "properties": { "id": { "type": "string" }, "sessionId": { "type": "string" }, "rootId": { "type": "string" }, "agent": { "type": "string" }, "body": { "type": "string" }, "options": { "type": "array", "items": { "type": "string" } }, "askedAt": { "type": "string" } }, "required": ["id", "sessionId", "rootId", "agent", "body", "askedAt"], "additionalProperties": true }, "spec": { "type": "object", "properties": { "cwd": { "type": "string" }, "agent": { "type": "string" }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "persist": { "type": "boolean" }, "restore": { "type": "object" } }, "required": ["cwd"], "additionalProperties": true }, "item": { "anyOf": [{ "$ref": "#/$defs/knownItem" }, { "$ref": "#/$defs/unknownItem" }] }, "event": { "anyOf": [{ "$ref": "#/$defs/knownEvent" }, { "$ref": "#/$defs/unknownEvent" }] }, "envelope": { "type": "object", "properties": { "sessionId": { "type": "string" }, "epoch": { "type": "string" }, "seq": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "ts": { "type": "string" }, "event": { "$ref": "#/$defs/event" }, "cursor": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 } }, "required": ["sessionId", "epoch", "seq", "ts", "event"], "additionalProperties": true }, "request": { "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "id": { "anyOf": [{ "type": "string", "maxLength": 128 }, { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }] }, "method": { "type": "string" }, "params": { "type": "object" } }, "required": ["jsonrpc", "method", "id"], "additionalProperties": true }, "response": { "oneOf": [{ "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "id": { "anyOf": [{ "type": "string", "maxLength": 128 }, { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, { "type": "null" }] }, "result": {} }, "required": ["jsonrpc", "id", "result"], "additionalProperties": true }, { "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "id": { "anyOf": [{ "type": "string", "maxLength": 128 }, { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, { "type": "null" }] }, "error": { "$ref": "#/$defs/error" } }, "required": ["jsonrpc", "id", "error"], "additionalProperties": true }] }, "notification": { "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "method": { "const": "event" }, "params": { "$ref": "#/$defs/envelope" } }, "required": ["jsonrpc", "method", "params"], "additionalProperties": true }, "knownEvent": { "oneOf": [{ "type": "object", "properties": { "type": { "const": "session.opened" }, "session": { "$ref": "#/$defs/session" }, "cwd": { "type": "string" }, "effective": { "type": "object", "properties": { "model": { "type": "string" }, "thinking": { "type": "string" } }, "required": ["model", "thinking"], "additionalProperties": true } }, "required": ["type", "session", "cwd", "effective"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.ready" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.updated" }, "session": { "$ref": "#/$defs/session" } }, "required": ["type", "session"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.closed" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.failed" }, "error": { "$ref": "#/$defs/alpError" } }, "required": ["type", "error"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "prompt.accepted" }, "clientMessageId": { "type": "string" }, "result": { "enum": ["turn", "steer"] }, "turnId": { "type": "string" } }, "required": ["type", "clientMessageId", "result", "turnId"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "prompt.failed" }, "clientMessageId": { "type": "string" }, "error": { "$ref": "#/$defs/alpError" } }, "required": ["type", "clientMessageId", "error"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "turn.started" }, "turnId": { "type": "string" }, "origin": { "enum": ["user", "wake", "assignment", "runtime"] } }, "required": ["type", "turnId", "origin"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "turn.ended" }, "turnId": { "type": "string" }, "state": { "enum": ["completed", "failed", "canceled"] }, "error": { "$ref": "#/$defs/alpError" } }, "required": ["type", "turnId", "state"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "item" }, "item": { "$ref": "#/$defs/item" } }, "required": ["type", "item"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "usage" }, "usage": { "$ref": "#/$defs/usage" } }, "required": ["type", "usage"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "mail" }, "mail": { "$ref": "#/$defs/mail" } }, "required": ["type", "mail"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "assignment" }, "assignment": { "$ref": "#/$defs/assignment" } }, "required": ["type", "assignment"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "question" }, "question": { "$ref": "#/$defs/question" } }, "required": ["type", "question"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "question.resolved" }, "questionId": { "type": "string" }, "outcome": { "enum": ["answered", "dismissed", "timeout", "canceled"] }, "answer": { "type": "string" } }, "required": ["type", "questionId", "outcome"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "pin" }, "pin": { "$ref": "#/$defs/pin" } }, "required": ["type", "pin"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "unpin" }, "pinId": { "type": "string" }, "reason": { "enum": ["unpinned", "session_ended"] } }, "required": ["type", "pinId", "reason"], "additionalProperties": true }] }, "unknownEvent": { "type": "object", "properties": { "type": { "type": "string", "not": { "enum": ["session.opened", "session.ready", "session.updated", "session.closed", "session.failed", "prompt.accepted", "prompt.failed", "turn.started", "turn.ended", "item", "usage", "mail", "assignment", "question", "question.resolved", "pin", "unpin"] } } }, "required": ["type"], "additionalProperties": true }, "knownItem": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "user_message" }, "id": { "type": "string" }, "text": { "type": "string" }, "clientMessageId": { "type": "string" }, "images": { "type": "integer", "minimum": 1 } }, "required": ["kind", "id", "text"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "assistant_message" }, "id": { "type": "string" }, "text": { "type": "string" } }, "required": ["kind", "id", "text"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "notice" }, "id": { "type": "string" }, "level": { "enum": ["info", "warning", "error"] }, "text": { "type": "string" } }, "required": ["kind", "id", "level", "text"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "compaction" }, "id": { "type": "string" }, "status": { "enum": ["running", "completed", "failed"] }, "trigger": { "type": "string" }, "preTokens": { "type": "integer" }, "postTokens": { "type": "integer" } }, "required": ["kind", "id", "status"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "todo" }, "id": { "type": "string" }, "items": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "text": { "type": "string" }, "status": { "enum": ["pending", "in_progress", "completed"] } }, "required": ["id", "text", "status"], "additionalProperties": true } } }, "required": ["kind", "id", "items"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "tool_call" }, "id": { "type": "string" }, "callId": { "type": "string" }, "name": { "type": "string" }, "status": { "enum": ["running", "completed", "failed"] }, "error": { "type": "string" }, "detail": { "$ref": "#/$defs/toolCallDetail" } }, "required": ["kind", "id", "callId", "name", "status", "detail"], "additionalProperties": true }] }, "unknownItem": { "type": "object", "properties": { "kind": { "type": "string", "not": { "enum": ["user_message", "assistant_message", "notice", "compaction", "todo", "tool_call"] } }, "id": { "type": "string" } }, "required": ["kind", "id"], "additionalProperties": true }, "toolCallDetail": { "oneOf": [{ "type": "object", "properties": { "type": { "const": "shell" }, "command": { "type": "string" }, "cwd": { "type": "string" }, "output": { "type": "string" }, "exitCode": { "type": ["integer", "null"] } }, "required": ["type", "command", "output"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "read" }, "filePath": { "type": "string" }, "content": { "type": "string" }, "offset": { "type": "integer" }, "limit": { "type": "integer" } }, "required": ["type", "filePath"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "edit" }, "filePath": { "type": "string" }, "oldString": { "type": "string" }, "newString": { "type": "string" }, "unifiedDiff": { "type": "string" } }, "required": ["type", "filePath"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "write" }, "filePath": { "type": "string" }, "content": { "type": "string" } }, "required": ["type", "filePath"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "search" }, "query": { "type": "string" }, "toolName": { "enum": ["search", "grep", "glob", "web_search"] }, "content": { "type": "string" }, "filePaths": { "type": "array", "items": { "type": "string" } }, "webResults": { "type": "array", "items": { "type": "object", "properties": { "title": { "type": "string" }, "url": { "type": "string" } }, "required": ["title", "url"], "additionalProperties": true } }, "numFiles": { "type": "integer" }, "numMatches": { "type": "integer" }, "truncated": { "type": "boolean" } }, "required": ["type", "query"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "fetch" }, "url": { "type": "string" }, "prompt": { "type": "string" }, "result": { "type": "string" }, "code": { "type": "integer" } }, "required": ["type", "url"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "sub_agent" }, "subAgentType": { "type": "string" }, "description": { "type": "string" }, "log": { "type": "string" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "plan" }, "text": { "type": "string" } }, "required": ["type", "text"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "plain_text" }, "label": { "type": "string" }, "text": { "type": "string" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "unknown" }, "input": {}, "output": {} }, "required": ["type", "input", "output"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "type": "string", "not": { "enum": ["shell", "read", "edit", "write", "search", "fetch", "sub_agent", "plan", "plain_text", "unknown"] } } }, "required": ["type"], "additionalProperties": true }] }, "alpError": { "type": "object", "properties": { "message": { "type": "string" }, "code": { "type": "string" } }, "required": ["message"], "additionalProperties": true }, "sessionSummary": { "type": "object", "properties": { "id": { "type": "string" }, "projectRoot": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "workflow": { "type": "object", "properties": { "mode": { "type": "string" }, "maxPeers": { "type": "integer" }, "supervisor": { "type": "boolean" } }, "required": ["mode", "maxPeers", "supervisor"], "additionalProperties": true }, "threadId": { "type": "string" }, "persistent": { "type": "boolean" }, "busy": { "type": "boolean" }, "parentId": { "type": "string" }, "toolCallId": { "type": "string" }, "activeTurnId": { "type": "string" }, "parked": { "type": "string" }, "instructionsSha": { "type": "string" }, "teamLabel": { "type": "string" }, "status": { "enum": ["initializing", "idle", "running", "error", "closed"] }, "title": { "type": "string" }, "archived": { "type": "boolean" }, "lastError": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" } }, "required": ["message"], "additionalProperties": true }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": ["id", "projectRoot", "agent", "runtime", "model", "mode", "thinking", "workflow", "threadId", "persistent", "busy", "status"], "additionalProperties": true }, "pause": { "type": "object", "properties": { "since": { "type": "string" }, "by": { "type": "string" }, "reason": { "type": "string" }, "resetsAt": { "type": "string" }, "checkedAt": { "type": "string" } }, "required": ["since", "by", "reason"], "additionalProperties": true }, "pauseState": { "type": "object", "properties": { "all": { "$ref": "#/$defs/pause" }, "runtimes": { "type": "object", "additionalProperties": { "$ref": "#/$defs/pause" } }, "parked": { "type": "array", "items": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "type": "string" }, "rootId": { "type": "string" }, "reason": { "type": "string" }, "since": { "type": "string" } }, "required": ["assignmentId", "agent", "runtime", "rootId", "reason", "since"], "additionalProperties": true } } }, "required": ["runtimes", "parked"], "additionalProperties": true }, "preview": { "type": "object", "properties": { "teams": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "description": { "type": "string" } }, "required": ["id", "label"], "additionalProperties": true } }, "team": { "type": "string" }, "teamLabel": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" } }, "required": ["teams", "team", "agent", "runtime", "model", "mode", "thinking"], "additionalProperties": true }, "treeStatus": { "type": "object", "properties": { "rootId": { "type": "string" }, "sessions": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "projectRoot": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "workflow": { "type": "object", "properties": { "mode": { "type": "string" }, "maxPeers": { "type": "integer" }, "supervisor": { "type": "boolean" } }, "required": ["mode", "maxPeers", "supervisor"], "additionalProperties": true }, "threadId": { "type": "string" }, "persistent": { "type": "boolean" }, "busy": { "type": "boolean" }, "parentId": { "type": "string" }, "toolCallId": { "type": "string" }, "activeTurnId": { "type": "string" }, "parked": { "type": "string" }, "instructionsSha": { "type": "string" }, "teamLabel": { "type": "string" }, "state": { "enum": ["running", "waiting", "waiting_parent", "waiting_user", "idle"] }, "idleMs": { "type": "number" }, "workdir": { "type": "string" }, "unreadMail": { "type": "integer", "minimum": 0 } }, "required": ["id", "projectRoot", "agent", "runtime", "model", "mode", "thinking", "workflow", "threadId", "persistent", "busy", "state", "idleMs", "workdir", "unreadMail"], "additionalProperties": true } }, "assignments": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "requester": { "type": "string" }, "isolation": { "enum": ["shared", "worktree"] }, "idleMs": { "type": "number" } }, "required": ["id", "requester", "isolation", "idleMs"], "additionalProperties": true } }, "questions": { "type": "array", "items": { "$ref": "#/$defs/question" } }, "worktrees": { "type": "array", "items": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "requester": { "type": "string" }, "agent": { "type": "string" }, "branch": { "type": "string" }, "files": { "type": "array", "items": { "type": "string" } }, "stat": { "type": "string" } }, "required": ["assignmentId", "requester", "agent", "branch", "files", "stat"], "additionalProperties": true } }, "leases": { "type": "array", "items": { "type": "object", "properties": { "checkout": { "type": "string" }, "assignmentId": { "type": "string" }, "agent": { "type": "string" } }, "required": ["checkout", "assignmentId", "agent"], "additionalProperties": true } }, "claims": { "type": "array", "items": { "$ref": "#/$defs/pin" } } }, "required": ["rootId", "sessions", "assignments", "questions", "worktrees", "leases", "claims"], "additionalProperties": true }, "health": { "type": "object", "properties": { "live": { "type": "boolean" }, "ready": { "type": "boolean" }, "checks": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "ok": { "type": "boolean" }, "code": { "type": "string" }, "message": { "type": "string" } }, "required": ["id", "ok"], "additionalProperties": true } } }, "required": ["live", "ready", "checks"], "additionalProperties": true }, "projectRoot": { "type": "string", "minLength": 1, "maxLength": 4096, "description": "An absolute, normalized path on the daemon host to a directory holding .alp/. Compared as is: the daemon never walks up to a parent." }, "taskId": { "type": "string", "pattern": "^t-[0-9a-f]{4,12}(?:\\.\\d+)*$" }, "taskRow": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "title": { "type": "string" }, "type": { "type": "string" }, "priority": { "type": "integer" }, "status": { "enum": ["open", "in_progress", "review", "closed"] }, "rev": { "type": "integer", "minimum": 0 }, "parent": { "type": "string" }, "assignee": { "type": "string" }, "ready": { "type": "boolean" }, "blockedBy": { "type": "array", "items": { "type": "string" } }, "waits": { "type": "array", "items": { "type": "string" } }, "approvals": { "type": "array", "items": { "type": "object", "properties": { "gate": { "type": "string" }, "note": { "type": "string" } }, "required": ["gate", "note"], "additionalProperties": true } }, "handoff": { "type": "object", "properties": { "outcome": { "type": "string" }, "summary": { "type": "string" }, "agent": { "type": "string" } }, "required": ["outcome", "summary"], "additionalProperties": true }, "closed": { "type": "object", "properties": { "reason": { "type": "string" }, "summary": { "type": "string" }, "at": { "type": "string" } }, "required": ["reason", "at"], "additionalProperties": true }, "description": { "type": "string" }, "labels": { "type": "array", "items": { "type": "string" } }, "paths": { "type": "array", "items": { "type": "string" } }, "progress": { "type": "object", "properties": { "done": { "type": "integer", "minimum": 0 }, "total": { "type": "integer", "minimum": 0 } }, "required": ["done", "total"], "additionalProperties": true }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": ["id", "title", "type", "priority", "status", "rev", "ready", "updatedAt"], "additionalProperties": true }, "libraryKind": { "enum": ["agents", "skills", "mcp", "hooks", "teams", "providers"] }, "libraryScope": { "enum": ["library", "project"] }, "librarySource": { "enum": ["builtin", "library", "project"] }, "entryName": { "type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z0-9_.-]+$", "not": { "enum": [".", ".."] } }, "libraryRevision": { "type": ["string", "null"] }, "libraryEntryRow": { "type": "object", "properties": { "name": { "type": "string" }, "source": { "$ref": "#/$defs/librarySource" }, "overrides": { "$ref": "#/$defs/librarySource" }, "description": { "type": "string" }, "usedBy": { "type": "array", "items": { "type": "string" } } }, "required": ["name", "source"], "additionalProperties": true }, "secretMarker": { "type": "object", "properties": { "$secret": { "const": "keep" } }, "required": ["$secret"], "additionalProperties": false, "description": "Stands for a stored secret value: read results carry it in place of the value; a save that sends it back keeps the stored value." }, "languageSetting": { "type": "object", "properties": { "language": { "type": ["string", "null"] }, "applies": { "type": "string" }, "default": { "type": "string" } }, "required": ["language", "applies", "default"], "additionalProperties": true }, "teamRow": { "type": "object", "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "description": { "type": "string" }, "source": { "$ref": "#/$defs/librarySource" }, "overrides": { "$ref": "#/$defs/librarySource" }, "error": { "type": "string" } }, "required": ["id", "source"], "additionalProperties": true }, "promptImage": { "type": "object", "properties": { "type": { "const": "image" }, "mimeType": { "enum": ["image/png", "image/jpeg", "image/gif", "image/webp"] }, "data": { "type": "string", "minLength": 4, "maxLength": 6990508, "pattern": "^[A-Za-z0-9+/]+={0,2}$" }, "name": { "type": "string", "maxLength": 255 } }, "required": ["type", "mimeType", "data"], "additionalProperties": false } } };
38746
+ var METHODS_V1 = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://alp.dev/schemas/methods-v1.json", "protocolVersion": 1, "errors": { "parseError": -32700, "invalidRequest": -32600, "methodNotFound": -32601, "invalidParams": -32602, "failed": 1e3, "notFound": 1001, "forbidden": 1003, "conflict": 1004, "unavailable": 1005, "protocol": 1006 }, "capabilities": { "attach.after": "session.attach accepts `after` and answers `cursor`/`reset`; envelopes kept in a tree timeline carry `cursor`.", "prompt.receipts": "session.prompt is answered at most once per (sessionId, clientMessageId) from a durable receipt; repeats answer the first outcome or outcome_unknown.", "extension.methods": "extension.register/unregister, and the daemon sends extension.invoke requests to the owning connection.", "team": "team.list: the teams a project can use, from the daemon host's built-ins, library and project.", "task": "task.list, task.create, task.update and task.gate.resolve on a project's .alp/tasks, written by the daemon under the task lock.", "library": "library.list/get/save/delete/duplicate/rename, library.skills.set and library.models on the daemon user's library and a project's .alp overrides; secrets are redacted on read.", "settings.language": "settings.language.get/set: the language the daemon user reads, in the daemon's ALP_HOME/settings.json.", "session.list.page": "session.list accepts `limit`, `cursor` and `query` and answers `nextCursor` while more sessions follow; without them it answers every session as before.", "question.watch": "question.watch: the connection is told of `question` and `question.resolved` in every tree (of a projectRoot when given), attached or not.", "question.watch.turns": "question.watch accepts `turns`: the connection is also told of `turn.ended` of every root in the trees it watches.", "workspace.files": "workspace.files.search: files under a directory whose relative path matches a query; read-only, names only, links never followed.", "workspace.files.read": "workspace.files.list and workspace.files.read: the entries of a directory and the content of a UTF-8 text file (at most 1 MiB) under a directory; read-only, links never followed, .git, .env* and key or credential files refused, .gitignore'd paths hidden unless asked.", "prompt.images": 'session.prompt takes inline image parts { type: "image", mimeType, data } (base64, no paths or URLs) within the image limits; a session snapshot says whether its runtime takes them in promptImages.', "library.project.write": "library save/delete/duplicate/rename accept policy project-data: only project-only pure-data agents and skills, no executable configuration, sidecars or library/built-in collisions." }, "methods": { "assignment.recall": { "x-alp": { "effect": "mutation", "scope": { "kind": "project", "param": "projectRoot", "required": false, "alternatives": ["assignmentId", "taskId"] }, "idempotent": false, "risks": ["model call"] }, "params": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "taskId": { "type": "string" }, "projectRoot": { "type": "string" }, "question": { "type": "string" } }, "required": ["question"], "additionalProperties": true }, "result": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "agent": { "type": "string" }, "taskId": { "type": "string" }, "finishedAt": { "type": "string" }, "answer": { "type": "string" } }, "required": ["assignmentId", "agent", "finishedAt", "answer"], "additionalProperties": true } }, "board.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "pins": { "type": "array", "items": { "$ref": "#/$defs/pin" } } }, "required": ["pins"], "additionalProperties": true } }, "catalog.get": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "models": { "type": "array", "items": { "type": "object" } }, "modes": { "type": "array", "items": { "type": "object" } }, "thinkingOptions": {}, "defaultModel": { "type": "string" } }, "required": ["models", "modes", "defaultModel"], "additionalProperties": true } }, "daemon.hello": { "x-alp": { "effect": "query", "scope": { "kind": "connection" }, "idempotent": true }, "params": { "type": "object", "properties": { "protocolVersion": { "const": 1 }, "client": { "type": "object", "properties": { "name": { "type": "string" }, "version": { "type": "string" } }, "required": ["name", "version"], "additionalProperties": true }, "capabilities": { "type": "array", "items": { "type": "string" } } }, "required": ["protocolVersion"], "additionalProperties": true }, "result": { "type": "object", "properties": { "protocolVersion": { "const": 1 }, "daemonVersion": { "type": "string" }, "pid": { "type": "integer" }, "hostname": { "type": "string" }, "capabilities": { "type": "array", "items": { "type": "string" } }, "promptImageLimits": { "type": "object", "description": "Optional effective inline image limits for this connection, when prompt.images is advertised. Missing on older peers. Byte limits count decoded bytes.", "properties": { "maxCount": { "type": "integer", "minimum": 0 }, "maxBytes": { "type": "integer", "minimum": 0 }, "maxTotalBytes": { "type": "integer", "minimum": 0 } }, "required": ["maxCount", "maxBytes", "maxTotalBytes"], "additionalProperties": false } }, "required": ["protocolVersion"], "additionalProperties": true } }, "daemon.pause": { "x-alp": { "effect": "mutation", "scope": { "kind": "host" }, "idempotent": true, "risks": ["admin"] }, "params": { "type": "object", "properties": { "runtime": { "enum": ["codex", "claude", "acp", "antigravity"] }, "now": { "type": "boolean" }, "reason": { "type": "string" } }, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/pauseState" } }, "daemon.pauses": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/pauseState" } }, "daemon.resume": { "x-alp": { "effect": "mutation", "scope": { "kind": "host" }, "idempotent": true, "risks": ["admin"] }, "params": { "type": "object", "properties": { "runtime": { "enum": ["codex", "claude", "acp", "antigravity"] } }, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/pauseState" } }, "daemon.shutdown": { "x-alp": { "effect": "mutation", "scope": { "kind": "host" }, "idempotent": true, "risks": ["admin"] }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "daemon.status": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "pid": { "type": "integer" }, "startedAt": { "type": "string" }, "version": { "type": "string" }, "protocolVersion": { "type": "integer" }, "sessions": { "type": "integer" }, "questions": { "type": "integer" }, "previousExit": { "type": "object", "properties": { "kind": { "enum": ["clean", "crash"] }, "at": { "type": "string" } }, "required": ["kind"], "additionalProperties": true }, "persistence": { "type": "object" }, "health": { "$ref": "#/$defs/health" } }, "required": ["pid", "startedAt", "version", "protocolVersion", "sessions", "questions", "health"], "additionalProperties": true } }, "daemon.usage": { "x-alp": { "effect": "query", "scope": { "kind": "host" }, "idempotent": true, "risks": ["refresh asks every provider (spawns on the daemon host)"] }, "params": { "type": "object", "properties": { "refresh": { "type": "boolean" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "providers": { "type": "array", "items": { "type": "object" } } }, "required": ["providers"], "additionalProperties": true } }, "extension.register": { "x-alp": { "effect": "mutation", "scope": { "kind": "connection" }, "idempotent": false, "risks": ["admin"] }, "params": { "type": "object", "properties": { "method": { "type": "string" } }, "required": ["method"], "additionalProperties": true }, "result": { "type": "object", "properties": { "registered": { "type": "string" } }, "required": ["registered"], "additionalProperties": true } }, "extension.unregister": { "x-alp": { "effect": "mutation", "scope": { "kind": "connection" }, "idempotent": false, "risks": ["admin"] }, "params": { "type": "object", "properties": { "method": { "type": "string" } }, "required": ["method"], "additionalProperties": true }, "result": { "type": "object", "properties": { "unregistered": { "type": "string" } }, "required": ["unregistered"], "additionalProperties": true } }, "library.delete": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope" }, "idempotent": false }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "revision": { "type": "string" }, "policy": { "enum": ["project-data"], "description": "Opt-in strict project-only non-executable mutation policy; requires library.project.write capability." } }, "required": ["kind", "name", "scope"], "additionalProperties": true }, "result": { "type": "object", "properties": { "removed": { "const": true }, "now": { "enum": ["builtin", "library"] } }, "required": ["removed"], "additionalProperties": true } }, "library.duplicate": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope", "reads": ["builtin", "library"] }, "idempotent": false, "risks": ["exec-config when kind is mcp, hooks or providers"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "from": { "$ref": "#/$defs/entryName" }, "to": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "policy": { "enum": ["project-data"], "description": "Opt-in strict project-only non-executable mutation policy; requires library.project.write capability." } }, "required": ["kind", "from", "to", "scope"], "additionalProperties": true }, "result": { "type": "object", "properties": { "revision": { "$ref": "#/$defs/libraryRevision" } }, "required": ["revision"], "additionalProperties": true } }, "library.get": { "x-alp": { "effect": "query", "capability": "library", "scope": { "kind": "project", "param": "projectRoot", "required": false, "reads": ["builtin", "library"] }, "idempotent": true, "redacts": ["mcp /server/env/*", "mcp /server/headers/*", "providers /provider/env/*"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "$ref": "#/$defs/entryName" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "scope": { "$ref": "#/$defs/libraryScope" } }, "required": ["kind", "name"], "additionalProperties": true }, "result": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "type": "string" }, "source": { "$ref": "#/$defs/librarySource" }, "overrides": { "$ref": "#/$defs/librarySource" }, "content": { "type": "object" }, "revision": { "$ref": "#/$defs/libraryRevision" }, "usedBy": { "type": "array", "items": { "type": "string" } }, "librarySkills": { "type": "array", "items": { "type": "string" } }, "secrets": { "type": "array", "items": { "type": "string" } } }, "required": ["kind", "name", "source", "content", "revision", "usedBy"], "additionalProperties": true } }, "library.list": { "x-alp": { "effect": "query", "capability": "library", "scope": { "kind": "project", "param": "projectRoot", "required": false, "reads": ["builtin", "library"] }, "idempotent": true }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": ["kind"], "additionalProperties": true }, "result": { "type": "object", "properties": { "projectRoot": { "type": ["string", "null"] }, "entries": { "type": "array", "items": { "$ref": "#/$defs/libraryEntryRow" } } }, "required": ["projectRoot", "entries"], "additionalProperties": true } }, "library.models": { "x-alp": { "effect": "query", "capability": "library", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "models": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "efforts": { "type": "array", "items": { "type": "string" } }, "effort": { "type": "string" } }, "required": ["id", "label", "efforts"], "additionalProperties": true } } }, "required": ["models"], "additionalProperties": true } }, "library.rename": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope" }, "idempotent": false, "risks": ["exec-config when kind is mcp, hooks or providers"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "from": { "$ref": "#/$defs/entryName" }, "to": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "policy": { "enum": ["project-data"], "description": "Opt-in strict project-only non-executable mutation policy; requires library.project.write capability." } }, "required": ["kind", "from", "to", "scope"], "additionalProperties": true }, "result": { "type": "object", "properties": { "revision": { "$ref": "#/$defs/libraryRevision" } }, "required": ["revision"], "additionalProperties": true } }, "library.save": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "library-or-project", "param": "projectRoot", "selector": "scope" }, "idempotent": "with revision (a repeat fails revision_conflict)", "risks": ["exec-config when kind is mcp, hooks or providers, or agents naming mcp/hooks/provider", "writes every known project's .alp/settings.json when kind is teams and sync is known-projects"] }, "params": { "type": "object", "properties": { "kind": { "$ref": "#/$defs/libraryKind" }, "name": { "$ref": "#/$defs/entryName" }, "scope": { "$ref": "#/$defs/libraryScope" }, "projectRoot": { "$ref": "#/$defs/projectRoot" }, "content": { "type": "object" }, "revision": { "$ref": "#/$defs/libraryRevision" }, "sync": { "enum": ["none", "known-projects"] }, "policy": { "enum": ["project-data"], "description": "Opt-in strict project-only non-executable mutation policy; requires library.project.write capability." } }, "required": ["kind", "name", "scope", "content"], "additionalProperties": true }, "result": { "type": "object", "properties": { "revision": { "$ref": "#/$defs/libraryRevision" }, "syncedProjects": { "type": "integer", "minimum": 0 }, "syncErrors": { "type": "array", "items": { "type": "string" } } }, "required": ["revision"], "additionalProperties": true } }, "library.skills.set": { "x-alp": { "effect": "mutation", "capability": "library", "scope": { "kind": "host" }, "writes": ["ALP_HOME/role-skills.json"], "idempotent": true }, "params": { "type": "object", "properties": { "agent": { "$ref": "#/$defs/entryName" }, "skills": { "type": "array", "items": { "$ref": "#/$defs/entryName" }, "maxItems": 100 } }, "required": ["agent", "skills"], "additionalProperties": true }, "result": { "type": "object", "properties": { "skills": { "type": "array", "items": { "type": "string" } } }, "required": ["skills"], "additionalProperties": true } }, "question.answer": { "x-alp": { "effect": "mutation", "scope": { "kind": "question", "param": "questionId", "prefix": true }, "idempotent": false }, "params": { "type": "object", "properties": { "questionId": { "type": "string" }, "text": { "type": "string" }, "dismiss": { "type": "boolean" }, "reason": { "type": "string" } }, "required": ["questionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "questionId": { "type": "string" } }, "required": ["questionId"], "additionalProperties": true } }, "question.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": false }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "questions": { "type": "array", "items": { "$ref": "#/$defs/question" } } }, "required": ["questions"], "additionalProperties": true } }, "question.watch": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": false }, "idempotent": true, "capability": "question.watch" }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "turns": { "type": "boolean" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "questions": { "type": "array", "items": { "$ref": "#/$defs/question" } } }, "required": ["questions"], "additionalProperties": true } }, "session.archive": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "archived": { "type": "boolean" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" } }, "required": ["session"], "additionalProperties": true } }, "session.attach": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "replay": { "type": "boolean" }, "after": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" }, "cursor": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "reset": { "const": true } }, "required": ["session", "cursor"], "additionalProperties": true } }, "session.close": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": false }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "session.configure": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "mode": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/session" } }, "required": ["session"], "additionalProperties": true } }, "session.create": { "x-alp": { "effect": "mutation", "scope": { "kind": "project", "param": "spec.cwd" }, "idempotent": false, "risks": ["exec: spec.env and stdio spec.mcpServers run on the daemon host", "spec.workdir/writeRoot/copy*/restore/keepThread are internal"] }, "params": { "type": "object", "properties": { "sessionId": { "type": "string", "pattern": "^[A-Za-z0-9_.:-]{1,128}$" }, "spec": { "$ref": "#/$defs/spec" }, "history": { "enum": ["skip", "replay"] }, "delegation": { "type": "boolean" }, "resume": { "type": "boolean" } }, "required": ["spec"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/session" }, "attached": { "type": "boolean" }, "resumed": { "const": true }, "durable": { "type": "boolean" } }, "required": ["session", "attached"], "additionalProperties": true } }, "session.get": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" } }, "required": ["session"], "additionalProperties": true } }, "session.interrupt": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "session.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": false }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "rootsOnly": { "type": "boolean" }, "includeClosed": { "type": "boolean" }, "includeArchived": { "type": "boolean" }, "limit": { "type": "integer", "minimum": 1, "maximum": 1e3 }, "cursor": { "type": "string", "minLength": 1, "maxLength": 1024 }, "query": { "type": "string", "maxLength": 200 }, "projectRoots": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 1024 } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "sessions": { "type": "array", "items": { "$ref": "#/$defs/sessionSummary" } }, "nextCursor": { "type": "string", "minLength": 1 } }, "required": ["sessions"], "additionalProperties": true } }, "session.log": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "rootId": { "type": "string" }, "entries": { "type": "array", "items": { "type": "object" } } }, "required": ["rootId", "entries"], "additionalProperties": true } }, "session.message": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": false }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "text": { "type": "string" } }, "required": ["sessionId", "text"], "additionalProperties": true }, "result": { "type": "object", "properties": {}, "required": [], "additionalProperties": true } }, "session.preview": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "spec.cwd" }, "idempotent": true }, "params": { "type": "object", "properties": { "spec": { "$ref": "#/$defs/spec" } }, "required": ["spec"], "additionalProperties": true }, "result": { "type": "object", "properties": { "preview": { "$ref": "#/$defs/preview" } }, "required": ["preview"], "additionalProperties": true } }, "session.prompt": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": "with prompt.receipts" }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "clientMessageId": { "type": "string" }, "delivery": { "enum": ["auto", "steer"] }, "content": { "type": "array", "items": { "anyOf": [{ "type": "object", "properties": { "type": { "const": "text" }, "text": { "type": "string" } }, "required": ["type"], "additionalProperties": true }, { "$ref": "#/$defs/promptImage" }, { "type": "object", "properties": { "type": { "type": "string", "not": { "enum": ["text", "image"] } } }, "required": ["type"], "additionalProperties": true }] } } }, "required": ["sessionId", "clientMessageId", "content"], "additionalProperties": true }, "result": { "type": "object", "properties": { "outcome": { "enum": ["accepted", "failed"] }, "error": { "$ref": "#/$defs/alpError" }, "duplicate": { "type": "boolean" }, "durable": { "type": "boolean" } }, "required": [], "additionalProperties": true } }, "session.release": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "closed": { "type": "boolean" } }, "required": ["closed"], "additionalProperties": true } }, "session.rename": { "x-alp": { "effect": "mutation", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" }, "title": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" } }, "required": ["sessionId", "title"], "additionalProperties": true }, "result": { "type": "object", "properties": { "session": { "$ref": "#/$defs/sessionSummary" } }, "required": ["session"], "additionalProperties": true } }, "session.status": { "x-alp": { "effect": "query", "scope": { "kind": "session", "param": "sessionId" }, "idempotent": true }, "params": { "type": "object", "properties": { "sessionId": { "type": "string" } }, "required": ["sessionId"], "additionalProperties": true }, "result": { "type": "object", "properties": { "status": { "$ref": "#/$defs/treeStatus" } }, "required": ["status"], "additionalProperties": true } }, "settings.language.get": { "x-alp": { "effect": "query", "capability": "settings.language", "scope": { "kind": "host" }, "idempotent": true }, "params": { "type": "object", "properties": {}, "required": [], "additionalProperties": true }, "result": { "$ref": "#/$defs/languageSetting" } }, "settings.language.set": { "x-alp": { "effect": "mutation", "capability": "settings.language", "scope": { "kind": "host" }, "writes": ["ALP_HOME/settings.json"], "idempotent": true }, "params": { "type": "object", "properties": { "language": { "anyOf": [{ "type": "string", "minLength": 1, "maxLength": 40, "pattern": "\\S" }, { "type": "null" }] } }, "required": ["language"], "additionalProperties": true }, "result": { "$ref": "#/$defs/languageSetting" } }, "task.create": { "x-alp": { "effect": "mutation", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "writes": ["<projectRoot>/.alp/tasks"], "idempotent": "per requestId within one alpd process (10 minutes)" }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" }, "title": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" }, "priority": { "type": "integer", "minimum": 0, "maximum": 4 }, "requestId": { "type": "string", "minLength": 1, "maxLength": 128 } }, "required": ["projectRoot", "title"], "additionalProperties": true }, "result": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "duplicate": { "const": true } }, "required": ["id"], "additionalProperties": true } }, "task.gate.resolve": { "x-alp": { "effect": "mutation", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "writes": ["<projectRoot>/.alp/tasks"], "idempotent": false }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" }, "id": { "$ref": "#/$defs/taskId" }, "gate": { "type": "string", "minLength": 1, "maxLength": 128 }, "note": { "type": "string", "maxLength": 2e3 }, "ifRev": { "type": "integer", "minimum": 0 } }, "required": ["projectRoot", "id", "gate"], "additionalProperties": true }, "result": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "status": { "type": "string" }, "rev": { "type": "integer", "minimum": 0 } }, "required": ["id", "status", "rev"], "additionalProperties": true } }, "task.list": { "x-alp": { "effect": "query", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "tasks": { "type": "array", "items": { "$ref": "#/$defs/taskRow" } }, "unreadable": { "type": "array", "items": { "type": "object", "properties": { "file": { "type": "string" }, "error": { "type": "string" } }, "required": ["file", "error"], "additionalProperties": true } }, "revision": { "type": "string" } }, "required": ["projectRoot", "tasks", "unreadable", "revision"], "additionalProperties": true } }, "task.update": { "x-alp": { "effect": "mutation", "capability": "task", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "writes": ["<projectRoot>/.alp/tasks"], "idempotent": false }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" }, "id": { "$ref": "#/$defs/taskId" }, "action": { "enum": ["close", "reopen"] }, "note": { "type": "string", "maxLength": 2e3 }, "ifRev": { "type": "integer", "minimum": 0 } }, "required": ["projectRoot", "id", "action"], "additionalProperties": true }, "result": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "status": { "type": "string" }, "rev": { "type": "integer", "minimum": 0 }, "landed": { "type": "string" } }, "required": ["id", "status", "rev"], "additionalProperties": true } }, "team.list": { "x-alp": { "effect": "query", "capability": "team", "scope": { "kind": "project", "param": "projectRoot", "required": false, "reads": ["builtin", "library"] }, "idempotent": true }, "params": { "type": "object", "properties": { "projectRoot": { "$ref": "#/$defs/projectRoot" } }, "required": [], "additionalProperties": true }, "result": { "type": "object", "properties": { "projectRoot": { "type": ["string", "null"] }, "teams": { "type": "array", "items": { "$ref": "#/$defs/teamRow" } } }, "required": ["projectRoot", "teams"], "additionalProperties": true } }, "workspace.files.search": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true, "capability": "workspace.files", "risks": ["lists file names of the directory (not contents)"] }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "query": { "type": "string", "maxLength": 200 }, "limit": { "type": "integer", "minimum": 1, "maximum": 100 } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "files": { "type": "array", "maxItems": 100, "items": { "type": "object", "properties": { "path": { "type": "string", "minLength": 1 } }, "required": ["path"], "additionalProperties": true } }, "truncated": { "type": "boolean" } }, "required": ["files"], "additionalProperties": true } }, "workspace.files.list": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true, "capability": "workspace.files.read", "risks": ["lists names, kinds, sizes and times of a directory's entries (not contents)"] }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "path": { "type": "string", "minLength": 1, "maxLength": 4096 }, "includeIgnored": { "type": "boolean" } }, "required": ["projectRoot"], "additionalProperties": true }, "result": { "type": "object", "properties": { "entries": { "type": "array", "maxItems": 1e3, "items": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1 }, "path": { "type": "string", "minLength": 1 }, "kind": { "enum": ["file", "directory"] }, "size": { "type": "integer", "minimum": 0 }, "modifiedAt": { "type": "string" } }, "required": ["name", "path", "kind"], "additionalProperties": true } }, "truncated": { "type": "boolean" } }, "required": ["entries"], "additionalProperties": true } }, "workspace.files.read": { "x-alp": { "effect": "query", "scope": { "kind": "project", "param": "projectRoot", "required": true }, "idempotent": true, "capability": "workspace.files.read", "risks": ["returns the content of a text file under the directory"] }, "params": { "type": "object", "properties": { "projectRoot": { "type": "string" }, "path": { "type": "string", "minLength": 1, "maxLength": 4096 }, "includeIgnored": { "type": "boolean" } }, "required": ["projectRoot", "path"], "additionalProperties": true }, "result": { "type": "object", "properties": { "path": { "type": "string", "minLength": 1 }, "content": { "type": "string" }, "size": { "type": "integer", "minimum": 0, "maximum": 1048576 }, "modifiedAt": { "type": "string" } }, "required": ["path", "content", "size"], "additionalProperties": true } } }, "$defs": { "error": { "type": "object", "properties": { "code": { "type": "integer" }, "message": { "type": "string" }, "data": {} }, "required": ["code", "message"], "additionalProperties": true }, "session": { "type": "object", "properties": { "id": { "type": "string" }, "projectRoot": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "workflow": { "type": "object", "properties": { "mode": { "type": "string" }, "maxPeers": { "type": "integer" }, "supervisor": { "type": "boolean" } }, "required": ["mode", "maxPeers", "supervisor"], "additionalProperties": true }, "threadId": { "type": "string" }, "persistent": { "type": "boolean" }, "busy": { "type": "boolean" }, "parentId": { "type": "string" }, "toolCallId": { "type": "string" }, "activeTurnId": { "type": "string" }, "parked": { "type": "string" }, "instructionsSha": { "type": "string" }, "teamLabel": { "type": "string" }, "promptImages": { "type": "boolean" }, "usage": { "$ref": "#/$defs/usage" } }, "required": ["id", "projectRoot", "agent", "runtime", "model", "mode", "thinking", "workflow", "threadId", "persistent", "busy"], "additionalProperties": true }, "usage": { "type": "object", "properties": { "contextWindowUsedTokens": { "type": "integer", "minimum": 0 }, "contextWindowMaxTokens": { "type": "integer", "minimum": 1 }, "inputTokens": { "type": "integer", "minimum": 0 }, "cachedInputTokens": { "type": "integer", "minimum": 0 }, "outputTokens": { "type": "integer", "minimum": 0 }, "totalCostUsd": { "type": "number", "minimum": 0 } }, "additionalProperties": true }, "assignment": { "type": "object", "properties": { "id": { "type": "string" }, "agent": { "type": "string" }, "mode": { "type": "string" }, "status": { "type": "string" }, "startedAt": { "type": "string" }, "worktree": { "type": "object", "properties": { "branch": { "type": "string" }, "path": { "type": "string" } }, "required": ["branch", "path"], "additionalProperties": true } }, "required": ["id", "agent", "mode", "status", "startedAt"], "additionalProperties": true }, "mail": { "type": "object", "properties": { "id": { "type": "string" }, "kind": { "enum": ["question", "answer", "note", "steer", "result", "stalled", "board", "checkin"] }, "from": { "type": "string" }, "assignment": { "type": "string" }, "body": { "type": "string" }, "replyTo": { "type": "string" }, "result": { "type": "object" }, "redelivered": { "type": "boolean" } }, "required": ["id", "kind", "from", "assignment"], "additionalProperties": true }, "pin": { "type": "object", "properties": { "id": { "type": "string" }, "project": { "type": "string" }, "kind": { "enum": ["claim", "decision", "finding"] }, "body": { "type": "string" }, "paths": { "type": "array", "items": { "type": "string" } }, "agent": { "type": "string" }, "sessionId": { "type": "string" }, "rootId": { "type": "string" }, "at": { "type": "string" }, "released": { "type": "string" }, "task": { "type": "string" } }, "required": ["id", "project", "kind", "body", "agent", "sessionId", "rootId", "at"], "additionalProperties": true }, "question": { "type": "object", "properties": { "id": { "type": "string" }, "sessionId": { "type": "string" }, "rootId": { "type": "string" }, "agent": { "type": "string" }, "body": { "type": "string" }, "options": { "type": "array", "items": { "type": "string" } }, "askedAt": { "type": "string" } }, "required": ["id", "sessionId", "rootId", "agent", "body", "askedAt"], "additionalProperties": true }, "spec": { "type": "object", "properties": { "cwd": { "type": "string" }, "agent": { "type": "string" }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "persist": { "type": "boolean" }, "restore": { "type": "object" } }, "required": ["cwd"], "additionalProperties": true }, "item": { "anyOf": [{ "$ref": "#/$defs/knownItem" }, { "$ref": "#/$defs/unknownItem" }] }, "event": { "anyOf": [{ "$ref": "#/$defs/knownEvent" }, { "$ref": "#/$defs/unknownEvent" }] }, "envelope": { "type": "object", "properties": { "sessionId": { "type": "string" }, "epoch": { "type": "string" }, "seq": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "ts": { "type": "string" }, "event": { "$ref": "#/$defs/event" }, "cursor": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 } }, "required": ["sessionId", "epoch", "seq", "ts", "event"], "additionalProperties": true }, "request": { "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "id": { "anyOf": [{ "type": "string", "maxLength": 128 }, { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }] }, "method": { "type": "string" }, "params": { "type": "object" } }, "required": ["jsonrpc", "method", "id"], "additionalProperties": true }, "response": { "oneOf": [{ "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "id": { "anyOf": [{ "type": "string", "maxLength": 128 }, { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, { "type": "null" }] }, "result": {} }, "required": ["jsonrpc", "id", "result"], "additionalProperties": true }, { "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "id": { "anyOf": [{ "type": "string", "maxLength": 128 }, { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, { "type": "null" }] }, "error": { "$ref": "#/$defs/error" } }, "required": ["jsonrpc", "id", "error"], "additionalProperties": true }] }, "notification": { "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "method": { "const": "event" }, "params": { "$ref": "#/$defs/envelope" } }, "required": ["jsonrpc", "method", "params"], "additionalProperties": true }, "knownEvent": { "oneOf": [{ "type": "object", "properties": { "type": { "const": "session.opened" }, "session": { "$ref": "#/$defs/session" }, "cwd": { "type": "string" }, "effective": { "type": "object", "properties": { "model": { "type": "string" }, "thinking": { "type": "string" } }, "required": ["model", "thinking"], "additionalProperties": true } }, "required": ["type", "session", "cwd", "effective"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.ready" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.updated" }, "session": { "$ref": "#/$defs/session" } }, "required": ["type", "session"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.closed" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "session.failed" }, "error": { "$ref": "#/$defs/alpError" } }, "required": ["type", "error"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "prompt.accepted" }, "clientMessageId": { "type": "string" }, "result": { "enum": ["turn", "steer"] }, "turnId": { "type": "string" } }, "required": ["type", "clientMessageId", "result", "turnId"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "prompt.failed" }, "clientMessageId": { "type": "string" }, "error": { "$ref": "#/$defs/alpError" } }, "required": ["type", "clientMessageId", "error"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "turn.started" }, "turnId": { "type": "string" }, "origin": { "enum": ["user", "wake", "assignment", "runtime"] } }, "required": ["type", "turnId", "origin"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "turn.ended" }, "turnId": { "type": "string" }, "state": { "enum": ["completed", "failed", "canceled"] }, "error": { "$ref": "#/$defs/alpError" } }, "required": ["type", "turnId", "state"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "item" }, "item": { "$ref": "#/$defs/item" } }, "required": ["type", "item"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "usage" }, "usage": { "$ref": "#/$defs/usage" } }, "required": ["type", "usage"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "mail" }, "mail": { "$ref": "#/$defs/mail" } }, "required": ["type", "mail"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "assignment" }, "assignment": { "$ref": "#/$defs/assignment" } }, "required": ["type", "assignment"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "question" }, "question": { "$ref": "#/$defs/question" } }, "required": ["type", "question"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "question.resolved" }, "questionId": { "type": "string" }, "outcome": { "enum": ["answered", "dismissed", "timeout", "canceled"] }, "answer": { "type": "string" } }, "required": ["type", "questionId", "outcome"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "pin" }, "pin": { "$ref": "#/$defs/pin" } }, "required": ["type", "pin"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "unpin" }, "pinId": { "type": "string" }, "reason": { "enum": ["unpinned", "session_ended"] } }, "required": ["type", "pinId", "reason"], "additionalProperties": true }] }, "unknownEvent": { "type": "object", "properties": { "type": { "type": "string", "not": { "enum": ["session.opened", "session.ready", "session.updated", "session.closed", "session.failed", "prompt.accepted", "prompt.failed", "turn.started", "turn.ended", "item", "usage", "mail", "assignment", "question", "question.resolved", "pin", "unpin"] } } }, "required": ["type"], "additionalProperties": true }, "knownItem": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "user_message" }, "id": { "type": "string" }, "text": { "type": "string" }, "clientMessageId": { "type": "string" }, "images": { "type": "integer", "minimum": 1 } }, "required": ["kind", "id", "text"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "assistant_message" }, "id": { "type": "string" }, "text": { "type": "string" } }, "required": ["kind", "id", "text"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "notice" }, "id": { "type": "string" }, "level": { "enum": ["info", "warning", "error"] }, "text": { "type": "string" } }, "required": ["kind", "id", "level", "text"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "compaction" }, "id": { "type": "string" }, "status": { "enum": ["running", "completed", "failed"] }, "trigger": { "type": "string" }, "preTokens": { "type": "integer" }, "postTokens": { "type": "integer" } }, "required": ["kind", "id", "status"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "todo" }, "id": { "type": "string" }, "items": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "text": { "type": "string" }, "status": { "enum": ["pending", "in_progress", "completed"] } }, "required": ["id", "text", "status"], "additionalProperties": true } } }, "required": ["kind", "id", "items"], "additionalProperties": true }, { "type": "object", "properties": { "kind": { "const": "tool_call" }, "id": { "type": "string" }, "callId": { "type": "string" }, "name": { "type": "string" }, "status": { "enum": ["running", "completed", "failed"] }, "error": { "type": "string" }, "detail": { "$ref": "#/$defs/toolCallDetail" } }, "required": ["kind", "id", "callId", "name", "status", "detail"], "additionalProperties": true }] }, "unknownItem": { "type": "object", "properties": { "kind": { "type": "string", "not": { "enum": ["user_message", "assistant_message", "notice", "compaction", "todo", "tool_call"] } }, "id": { "type": "string" } }, "required": ["kind", "id"], "additionalProperties": true }, "toolCallDetail": { "oneOf": [{ "type": "object", "properties": { "type": { "const": "shell" }, "command": { "type": "string" }, "cwd": { "type": "string" }, "output": { "type": "string" }, "exitCode": { "type": ["integer", "null"] } }, "required": ["type", "command", "output"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "read" }, "filePath": { "type": "string" }, "content": { "type": "string" }, "offset": { "type": "integer" }, "limit": { "type": "integer" } }, "required": ["type", "filePath"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "edit" }, "filePath": { "type": "string" }, "oldString": { "type": "string" }, "newString": { "type": "string" }, "unifiedDiff": { "type": "string" } }, "required": ["type", "filePath"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "write" }, "filePath": { "type": "string" }, "content": { "type": "string" } }, "required": ["type", "filePath"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "search" }, "query": { "type": "string" }, "toolName": { "enum": ["search", "grep", "glob", "web_search"] }, "content": { "type": "string" }, "filePaths": { "type": "array", "items": { "type": "string" } }, "webResults": { "type": "array", "items": { "type": "object", "properties": { "title": { "type": "string" }, "url": { "type": "string" } }, "required": ["title", "url"], "additionalProperties": true } }, "numFiles": { "type": "integer" }, "numMatches": { "type": "integer" }, "truncated": { "type": "boolean" } }, "required": ["type", "query"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "fetch" }, "url": { "type": "string" }, "prompt": { "type": "string" }, "result": { "type": "string" }, "code": { "type": "integer" } }, "required": ["type", "url"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "sub_agent" }, "subAgentType": { "type": "string" }, "description": { "type": "string" }, "log": { "type": "string" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "plan" }, "text": { "type": "string" } }, "required": ["type", "text"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "plain_text" }, "label": { "type": "string" }, "text": { "type": "string" } }, "required": ["type"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "const": "unknown" }, "input": {}, "output": {} }, "required": ["type", "input", "output"], "additionalProperties": true }, { "type": "object", "properties": { "type": { "type": "string", "not": { "enum": ["shell", "read", "edit", "write", "search", "fetch", "sub_agent", "plan", "plain_text", "unknown"] } } }, "required": ["type"], "additionalProperties": true }] }, "alpError": { "type": "object", "properties": { "message": { "type": "string" }, "code": { "type": "string" } }, "required": ["message"], "additionalProperties": true }, "sessionSummary": { "type": "object", "properties": { "id": { "type": "string" }, "projectRoot": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "workflow": { "type": "object", "properties": { "mode": { "type": "string" }, "maxPeers": { "type": "integer" }, "supervisor": { "type": "boolean" } }, "required": ["mode", "maxPeers", "supervisor"], "additionalProperties": true }, "threadId": { "type": "string" }, "persistent": { "type": "boolean" }, "busy": { "type": "boolean" }, "parentId": { "type": "string" }, "toolCallId": { "type": "string" }, "activeTurnId": { "type": "string" }, "parked": { "type": "string" }, "instructionsSha": { "type": "string" }, "teamLabel": { "type": "string" }, "status": { "enum": ["initializing", "idle", "running", "error", "closed"] }, "title": { "type": "string" }, "archived": { "type": "boolean" }, "lastError": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" } }, "required": ["message"], "additionalProperties": true }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": ["id", "projectRoot", "agent", "runtime", "model", "mode", "thinking", "workflow", "threadId", "persistent", "busy", "status"], "additionalProperties": true }, "pause": { "type": "object", "properties": { "since": { "type": "string" }, "by": { "type": "string" }, "reason": { "type": "string" }, "resetsAt": { "type": "string" }, "checkedAt": { "type": "string" } }, "required": ["since", "by", "reason"], "additionalProperties": true }, "pauseState": { "type": "object", "properties": { "all": { "$ref": "#/$defs/pause" }, "runtimes": { "type": "object", "additionalProperties": { "$ref": "#/$defs/pause" } }, "parked": { "type": "array", "items": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "type": "string" }, "rootId": { "type": "string" }, "reason": { "type": "string" }, "since": { "type": "string" } }, "required": ["assignmentId", "agent", "runtime", "rootId", "reason", "since"], "additionalProperties": true } } }, "required": ["runtimes", "parked"], "additionalProperties": true }, "preview": { "type": "object", "properties": { "teams": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "description": { "type": "string" } }, "required": ["id", "label"], "additionalProperties": true } }, "team": { "type": "string" }, "teamLabel": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" } }, "required": ["teams", "team", "agent", "runtime", "model", "mode", "thinking"], "additionalProperties": true }, "treeStatus": { "type": "object", "properties": { "rootId": { "type": "string" }, "sessions": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "projectRoot": { "type": "string" }, "agent": { "type": "string" }, "runtime": { "enum": ["claude", "codex", "acp", "antigravity"] }, "model": { "type": "string" }, "mode": { "type": "string" }, "thinking": { "type": "string" }, "workflow": { "type": "object", "properties": { "mode": { "type": "string" }, "maxPeers": { "type": "integer" }, "supervisor": { "type": "boolean" } }, "required": ["mode", "maxPeers", "supervisor"], "additionalProperties": true }, "threadId": { "type": "string" }, "persistent": { "type": "boolean" }, "busy": { "type": "boolean" }, "parentId": { "type": "string" }, "toolCallId": { "type": "string" }, "activeTurnId": { "type": "string" }, "parked": { "type": "string" }, "instructionsSha": { "type": "string" }, "teamLabel": { "type": "string" }, "state": { "enum": ["running", "waiting", "waiting_parent", "waiting_user", "idle"] }, "idleMs": { "type": "number" }, "workdir": { "type": "string" }, "unreadMail": { "type": "integer", "minimum": 0 } }, "required": ["id", "projectRoot", "agent", "runtime", "model", "mode", "thinking", "workflow", "threadId", "persistent", "busy", "state", "idleMs", "workdir", "unreadMail"], "additionalProperties": true } }, "assignments": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "requester": { "type": "string" }, "isolation": { "enum": ["shared", "worktree"] }, "idleMs": { "type": "number" } }, "required": ["id", "requester", "isolation", "idleMs"], "additionalProperties": true } }, "questions": { "type": "array", "items": { "$ref": "#/$defs/question" } }, "worktrees": { "type": "array", "items": { "type": "object", "properties": { "assignmentId": { "type": "string" }, "requester": { "type": "string" }, "agent": { "type": "string" }, "branch": { "type": "string" }, "files": { "type": "array", "items": { "type": "string" } }, "stat": { "type": "string" } }, "required": ["assignmentId", "requester", "agent", "branch", "files", "stat"], "additionalProperties": true } }, "leases": { "type": "array", "items": { "type": "object", "properties": { "checkout": { "type": "string" }, "assignmentId": { "type": "string" }, "agent": { "type": "string" } }, "required": ["checkout", "assignmentId", "agent"], "additionalProperties": true } }, "claims": { "type": "array", "items": { "$ref": "#/$defs/pin" } } }, "required": ["rootId", "sessions", "assignments", "questions", "worktrees", "leases", "claims"], "additionalProperties": true }, "health": { "type": "object", "properties": { "live": { "type": "boolean" }, "ready": { "type": "boolean" }, "checks": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "ok": { "type": "boolean" }, "code": { "type": "string" }, "message": { "type": "string" } }, "required": ["id", "ok"], "additionalProperties": true } } }, "required": ["live", "ready", "checks"], "additionalProperties": true }, "projectRoot": { "type": "string", "minLength": 1, "maxLength": 4096, "description": "An absolute, normalized path on the daemon host to a directory holding .alp/. Compared as is: the daemon never walks up to a parent." }, "taskId": { "type": "string", "pattern": "^t-[0-9a-f]{4,12}(?:\\.\\d+)*$" }, "taskRow": { "type": "object", "properties": { "id": { "$ref": "#/$defs/taskId" }, "title": { "type": "string" }, "type": { "type": "string" }, "priority": { "type": "integer" }, "status": { "enum": ["open", "in_progress", "review", "closed"] }, "rev": { "type": "integer", "minimum": 0 }, "parent": { "type": "string" }, "assignee": { "type": "string" }, "ready": { "type": "boolean" }, "blockedBy": { "type": "array", "items": { "type": "string" } }, "waits": { "type": "array", "items": { "type": "string" } }, "approvals": { "type": "array", "items": { "type": "object", "properties": { "gate": { "type": "string" }, "note": { "type": "string" } }, "required": ["gate", "note"], "additionalProperties": true } }, "handoff": { "type": "object", "properties": { "outcome": { "type": "string" }, "summary": { "type": "string" }, "agent": { "type": "string" } }, "required": ["outcome", "summary"], "additionalProperties": true }, "closed": { "type": "object", "properties": { "reason": { "type": "string" }, "summary": { "type": "string" }, "at": { "type": "string" } }, "required": ["reason", "at"], "additionalProperties": true }, "description": { "type": "string" }, "labels": { "type": "array", "items": { "type": "string" } }, "paths": { "type": "array", "items": { "type": "string" } }, "progress": { "type": "object", "properties": { "done": { "type": "integer", "minimum": 0 }, "total": { "type": "integer", "minimum": 0 } }, "required": ["done", "total"], "additionalProperties": true }, "createdAt": { "type": "string" }, "updatedAt": { "type": "string" } }, "required": ["id", "title", "type", "priority", "status", "rev", "ready", "updatedAt"], "additionalProperties": true }, "libraryKind": { "enum": ["agents", "skills", "mcp", "hooks", "teams", "providers"] }, "libraryScope": { "enum": ["library", "project"] }, "librarySource": { "enum": ["builtin", "library", "project"] }, "entryName": { "type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z0-9_.-]+$", "not": { "enum": [".", ".."] } }, "libraryRevision": { "type": ["string", "null"] }, "libraryEntryRow": { "type": "object", "properties": { "name": { "type": "string" }, "source": { "$ref": "#/$defs/librarySource" }, "overrides": { "$ref": "#/$defs/librarySource" }, "description": { "type": "string" }, "usedBy": { "type": "array", "items": { "type": "string" } } }, "required": ["name", "source"], "additionalProperties": true }, "secretMarker": { "type": "object", "properties": { "$secret": { "const": "keep" } }, "required": ["$secret"], "additionalProperties": false, "description": "Stands for a stored secret value: read results carry it in place of the value; a save that sends it back keeps the stored value." }, "languageSetting": { "type": "object", "properties": { "language": { "type": ["string", "null"] }, "applies": { "type": "string" }, "default": { "type": "string" } }, "required": ["language", "applies", "default"], "additionalProperties": true }, "teamRow": { "type": "object", "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "description": { "type": "string" }, "source": { "$ref": "#/$defs/librarySource" }, "overrides": { "$ref": "#/$defs/librarySource" }, "error": { "type": "string" } }, "required": ["id", "source"], "additionalProperties": true }, "promptImage": { "type": "object", "properties": { "type": { "const": "image" }, "mimeType": { "enum": ["image/png", "image/jpeg", "image/gif", "image/webp"] }, "data": { "type": "string", "minLength": 4, "maxLength": 6990508, "pattern": "^[A-Za-z0-9+/]+={0,2}$" }, "name": { "type": "string", "maxLength": 255 } }, "required": ["type", "mimeType", "data"], "additionalProperties": false } } };
38494
38747
 
38495
38748
  // packages/protocol/src/validate.js
38496
38749
  var MAX_ISSUES = 8;
@@ -38663,7 +38916,7 @@ var RpcError = class extends Error {
38663
38916
 
38664
38917
  // packages/daemon/src/operations.ts
38665
38918
  import { createHash as createHash10, createHmac, randomBytes as randomBytes6 } from "node:crypto";
38666
- import { lstat as lstat5, stat as stat8 } from "node:fs/promises";
38919
+ import { lstat as lstat6, stat as stat8 } from "node:fs/promises";
38667
38920
  import path32 from "node:path";
38668
38921
 
38669
38922
  // packages/core/src/task-rows.js
@@ -38709,8 +38962,8 @@ async function taskRows(projectRoot) {
38709
38962
  }
38710
38963
 
38711
38964
  // packages/core/src/library-edit.js
38712
- import { mkdir as mkdir13, rename as rename6, rm as rm5, stat as stat7, writeFile as writeFile14 } from "node:fs/promises";
38713
- import { createHash as createHash9, randomUUID as randomUUID12 } from "node:crypto";
38965
+ import { lstat as lstat5, mkdir as mkdir13, readdir as readdir9, rename as rename7, rm as rm5, stat as stat7, writeFile as writeFile15 } from "node:fs/promises";
38966
+ import { createHash as createHash9, randomUUID as randomUUID13 } from "node:crypto";
38714
38967
  import path31 from "node:path";
38715
38968
  var EDIT_KINDS = ["agents", "skills", "mcp", "hooks", "teams", "providers"];
38716
38969
  var validName3 = (name2) => typeof name2 === "string" && /^[\w.-]+$/.test(name2) && name2 !== "." && name2 !== "..";
@@ -38742,6 +38995,42 @@ function base(scope, { root, library }, kind) {
38742
38995
  return fail5("INVALID_SCOPE", `Scope must be library or project, not '${scope}'`);
38743
38996
  }
38744
38997
  var revisionOf = (content) => content === void 0 ? null : createHash9("sha256").update(JSON.stringify(content)).digest("hex").slice(0, 16);
38998
+ function assertProjectDataContent(kind, content) {
38999
+ const deny = () => fail5("UNSAFE_LIBRARY_WRITE", "Project-data writes allow only instruction entries and non-executable agent configuration");
39000
+ if (!object8(content) || !["agents", "skills"].includes(kind)) deny();
39001
+ const keys2 = kind === "skills" ? ["body"] : ["instructions", "config"];
39002
+ if (Object.keys(content).some((key2) => !keys2.includes(key2))) deny();
39003
+ const text2 = kind === "skills" ? content.body : content.instructions;
39004
+ if (typeof text2 !== "string" || !text2.trim()) deny();
39005
+ if (content.config !== void 0) {
39006
+ const config2 = content.config;
39007
+ if (!object8(config2)) deny();
39008
+ const allowed = ["description", "model", "thinking", "context", "autocompact"];
39009
+ if (Object.keys(config2).some((key2) => !allowed.includes(key2))) deny();
39010
+ for (const key2 of ["description", "thinking"]) if (config2[key2] !== void 0 && typeof config2[key2] !== "string") deny();
39011
+ if (config2.model !== void 0 && (typeof config2.model !== "string" || !/^(codex|claude):[^\s/\\:]{1,240}$/.test(config2.model))) deny();
39012
+ for (const key2 of ["context", "autocompact"]) if (config2[key2] !== void 0 && !(typeof config2[key2] === "string" || typeof config2[key2] === "number" && Number.isFinite(config2[key2]))) deny();
39013
+ }
39014
+ }
39015
+ async function projectDataEntry(kind, name2, { scope, root, library, templates }) {
39016
+ if (scope !== "project" || !root || !["agents", "skills"].includes(kind)) fail5("UNSAFE_LIBRARY_WRITE", "Project-data writes require project scope and agents or skills");
39017
+ nameOf(name2);
39018
+ const directory = base(scope, { root, library }, kind);
39019
+ await safeEntryPaths(directory, kind, name2);
39020
+ const found = await layers(kind, name2, { root, library, templates });
39021
+ if (found.library !== void 0 || found.builtin !== void 0) fail5("UNSAFE_LIBRARY_WRITE", "Project-data writes cannot shadow or reveal a library or built-in entry");
39022
+ if (found.project !== void 0) assertProjectDataContent(kind, found.project);
39023
+ const owned = path31.join(directory, kind, name2);
39024
+ const files = await readdir9(owned).catch((error51) => {
39025
+ if (error51.code === "ENOENT") return [];
39026
+ throw error51;
39027
+ });
39028
+ const allowed = new Set(FILES[kind](name2).map(([file3]) => path31.basename(file3)));
39029
+ for (const file3 of files) {
39030
+ const info = await lstat5(path31.join(owned, file3));
39031
+ if (!allowed.has(file3) || !info.isFile() || info.isSymbolicLink() || info.nlink !== 1) fail5("UNSAFE_LIBRARY_WRITE", "Project-data entries cannot contain sidecars, links or executable assets");
39032
+ }
39033
+ }
38745
39034
  async function safeEntryPaths(directory, kind, name2) {
38746
39035
  for (const [file3] of FILES[kind](name2)) {
38747
39036
  await assertNoSymlinks(directory, path31.join(kind, file3));
@@ -38934,14 +39223,18 @@ async function check3(kind, name2, content, { scope, root, library, templates })
38934
39223
  }
38935
39224
  async function writeAtomic2(file3, text2) {
38936
39225
  await mkdir13(path31.dirname(file3), { recursive: true });
38937
- const temporary = `${file3}.${randomUUID12().slice(0, 8)}.tmp`;
38938
- await writeFile14(temporary, text2);
39226
+ const temporary = `${file3}.${randomUUID13().slice(0, 8)}.tmp`;
39227
+ await writeFile15(temporary, text2);
38939
39228
  await moveFile(temporary, file3);
38940
39229
  }
38941
39230
  var exists2 = (target) => stat7(target).then(() => true, () => false);
38942
- async function saveEntry(kind, name2, content, { scope, root, library, templates, revision } = {}) {
39231
+ async function saveEntry(kind, name2, content, { scope, root, library, templates, revision, policy } = {}) {
38943
39232
  kindOf(kind);
38944
39233
  nameOf(name2);
39234
+ if (policy === "project-data") {
39235
+ assertProjectDataContent(kind, content);
39236
+ await projectDataEntry(kind, name2, { scope, root, library, templates });
39237
+ }
38945
39238
  const directory = base(scope, { root, library }, kind);
38946
39239
  await safeEntryPaths(directory, kind, name2);
38947
39240
  await check3(kind, name2, content, { scope, root, library, templates });
@@ -38963,9 +39256,10 @@ async function saveEntry(kind, name2, content, { scope, root, library, templates
38963
39256
  }
38964
39257
  return { revision: revisionOf(await readLayer(kind, name2, directory)) };
38965
39258
  }
38966
- async function deleteEntry(kind, name2, { scope, root, library, templates, revision } = {}) {
39259
+ async function deleteEntry(kind, name2, { scope, root, library, templates, revision, policy } = {}) {
38967
39260
  kindOf(kind);
38968
39261
  nameOf(name2);
39262
+ if (policy === "project-data") await projectDataEntry(kind, name2, { scope, root, library, templates });
38969
39263
  const directory = base(scope, { root, library }, kind);
38970
39264
  const current = await readLayer(kind, name2, directory);
38971
39265
  if (current === void 0) fail5("NOT_FOUND", `No ${kind} entry '${name2}' in the ${scope}`);
@@ -38981,14 +39275,25 @@ async function deleteEntry(kind, name2, { scope, root, library, templates, revis
38981
39275
  const now2 = scope === "project" && found.library !== void 0 ? "library" : "builtin";
38982
39276
  return { removed: true, ...fallback !== void 0 ? { now: now2 } : {} };
38983
39277
  }
38984
- async function duplicateEntry(kind, from, to, { scope, root, library, templates } = {}) {
39278
+ async function duplicateEntry(kind, from, to, { scope, root, library, templates, policy } = {}) {
39279
+ kindOf(kind);
39280
+ nameOf(from);
39281
+ nameOf(to);
39282
+ if (policy === "project-data") {
39283
+ await projectDataEntry(kind, from, { scope, root, library, templates });
39284
+ await projectDataEntry(kind, to, { scope, root, library, templates });
39285
+ }
38985
39286
  const { content } = await getEntry(kind, from, { root, library, templates });
38986
- return saveEntry(kind, to, structuredClone(content), { scope, root, library, templates, revision: null });
39287
+ return saveEntry(kind, to, structuredClone(content), { scope, root, library, templates, revision: null, policy });
38987
39288
  }
38988
- async function renameEntry(kind, from, to, { scope, root, library, templates } = {}) {
39289
+ async function renameEntry(kind, from, to, { scope, root, library, templates, policy } = {}) {
38989
39290
  kindOf(kind);
38990
39291
  nameOf(from);
38991
39292
  nameOf(to);
39293
+ if (policy === "project-data") {
39294
+ await projectDataEntry(kind, from, { scope, root, library, templates });
39295
+ await projectDataEntry(kind, to, { scope, root, library, templates });
39296
+ }
38992
39297
  const directory = base(scope, { root, library }, kind);
38993
39298
  const content = await readLayer(kind, from, directory);
38994
39299
  if (content === void 0) fail5("NOT_FOUND", `No ${kind} entry '${from}' in the ${scope}`);
@@ -38998,9 +39303,9 @@ async function renameEntry(kind, from, to, { scope, root, library, templates } =
38998
39303
  await check3(kind, to, content, { scope, root, library, templates });
38999
39304
  if (ownDirectory(kind)) {
39000
39305
  if (await exists2(path31.join(directory, kind, to))) fail5("REVISION_CONFLICT", `${path31.join(directory, kind, to)} already exists`);
39001
- await rename6(path31.join(directory, kind, from), path31.join(directory, kind, to));
39306
+ await rename7(path31.join(directory, kind, from), path31.join(directory, kind, to));
39002
39307
  } else {
39003
- await rename6(path31.join(directory, kind, `${from}.json`), path31.join(directory, kind, `${to}.json`));
39308
+ await rename7(path31.join(directory, kind, `${from}.json`), path31.join(directory, kind, `${to}.json`));
39004
39309
  }
39005
39310
  return { revision: revisionOf(await readLayer(kind, to, directory)) };
39006
39311
  }
@@ -39019,6 +39324,8 @@ function fromCore(error51, context = {}) {
39019
39324
  if (error51 instanceof RpcError || typeof error51?.code !== "string" || error51?.name !== "AlpError") return error51;
39020
39325
  const message = error51.message;
39021
39326
  switch (error51.code) {
39327
+ case "UNSAFE_LIBRARY_WRITE":
39328
+ return failure(ERROR.forbidden, "library_policy", message);
39022
39329
  case "UNSAFE_PATH":
39023
39330
  return failure(ERROR.forbidden, "unsafe_path", message);
39024
39331
  case "TASK_NOT_FOUND":
@@ -39108,7 +39415,7 @@ function createOperations({ home: home2, templates, knownProjects = () => [] } =
39108
39415
  async function project(value) {
39109
39416
  if (!path32.isAbsolute(value) || path32.resolve(value) !== value) throw failure(RPC_ERROR.invalidParams, "invalid_path", "projectRoot must be an absolute, normalized path");
39110
39417
  if (!(await stat8(value).catch(() => void 0))?.isDirectory()) throw failure(ERROR.notFound, "not_alp_project", `${value} is not a directory`);
39111
- const alp = await lstat5(path32.join(value, ".alp")).catch(() => void 0);
39418
+ const alp = await lstat6(path32.join(value, ".alp")).catch(() => void 0);
39112
39419
  if (alp?.isSymbolicLink()) throw failure(ERROR.forbidden, "unsafe_path", `${path32.join(value, ".alp")} is a symlink`);
39113
39420
  if (!alp?.isDirectory()) throw failure(ERROR.notFound, "not_alp_project", `${value} is not an ALP project; run alp init there`);
39114
39421
  return value;
@@ -39194,7 +39501,8 @@ function createOperations({ home: home2, templates, knownProjects = () => [] } =
39194
39501
  const { content, secrets } = redact2(kind, entry.content);
39195
39502
  return { ...entry, content, revision: toWire(entry.revision), ...secrets.length ? { secrets } : {} };
39196
39503
  }),
39197
- "library.save": guard(({ kind, name: name2, scope, projectRoot, content, revision, sync = "none" }) => serial(async () => {
39504
+ "library.save": guard(({ kind, name: name2, scope, projectRoot, content, revision, policy, sync = "none" }) => serial(async () => {
39505
+ if (policy === "project-data" && sync !== "none") throw failure(ERROR.forbidden, "library_policy", "Project-data writes cannot synchronize projects");
39198
39506
  const options2 = await where(projectRoot, scope);
39199
39507
  const current = await layer(kind, name2, options2, scope);
39200
39508
  if (revision !== void 0 && revision !== toWire(current?.revision ?? null)) {
@@ -39202,7 +39510,7 @@ function createOperations({ home: home2, templates, knownProjects = () => [] } =
39202
39510
  }
39203
39511
  const previous = current?.content ?? (await layer(kind, name2, options2))?.content;
39204
39512
  const next = unredact(kind, content, previous);
39205
- const saved = await saveEntry(kind, name2, next, { ...options2, scope, ...revision !== void 0 ? { revision: current?.revision ?? null } : {} });
39513
+ const saved = await saveEntry(kind, name2, next, { ...options2, scope, policy, ...revision !== void 0 ? { revision: current?.revision ?? null } : {} });
39206
39514
  if (kind !== "teams" || sync !== "known-projects") return { revision: toWire(saved.revision) };
39207
39515
  const roots = new Set(options2.root ? [options2.root] : []);
39208
39516
  if (scope === "library") {
@@ -39219,25 +39527,25 @@ function createOperations({ home: home2, templates, knownProjects = () => [] } =
39219
39527
  }
39220
39528
  return { revision: toWire(saved.revision), syncedProjects, syncErrors };
39221
39529
  }), { notFound: "invalid_entry" }),
39222
- "library.delete": guard(({ kind, name: name2, scope, projectRoot, revision }) => serial(async () => {
39530
+ "library.delete": guard(({ kind, name: name2, scope, projectRoot, revision, policy }) => serial(async () => {
39223
39531
  const options2 = await where(projectRoot, scope);
39224
39532
  const current = await layer(kind, name2, options2, scope);
39225
39533
  if (!current) throw failure(ERROR.notFound, "not_found", `No ${kind} entry '${name2}' in the ${scope}`);
39226
39534
  if (revision !== void 0 && revision !== toWire(current.revision)) throw failure(ERROR.conflict, "revision_conflict", `${kind} '${name2}' changed in the ${scope} since you opened it`);
39227
- return deleteEntry(kind, name2, { ...options2, scope, ...revision !== void 0 ? { revision: current.revision } : {} });
39535
+ return deleteEntry(kind, name2, { ...options2, scope, policy, ...revision !== void 0 ? { revision: current.revision } : {} });
39228
39536
  })),
39229
- "library.duplicate": guard(({ kind, from, to, scope, projectRoot }) => serial(async () => {
39537
+ "library.duplicate": guard(({ kind, from, to, scope, projectRoot, policy }) => serial(async () => {
39230
39538
  const options2 = await where(projectRoot, scope);
39231
39539
  try {
39232
39540
  await getEntry(kind, from, options2);
39233
39541
  } catch (error51) {
39234
39542
  throw fromCore(error51, { notFound: "not_found" });
39235
39543
  }
39236
- return { revision: toWire((await duplicateEntry(kind, from, to, { ...options2, scope })).revision) };
39544
+ return { revision: toWire((await duplicateEntry(kind, from, to, { ...options2, scope, policy })).revision) };
39237
39545
  }), { notFound: "invalid_entry" }),
39238
- "library.rename": guard(({ kind, from, to, scope, projectRoot }) => serial(async () => {
39546
+ "library.rename": guard(({ kind, from, to, scope, projectRoot, policy }) => serial(async () => {
39239
39547
  const options2 = await where(projectRoot, scope);
39240
- return { revision: toWire((await renameEntry(kind, from, to, { ...options2, scope })).revision) };
39548
+ return { revision: toWire((await renameEntry(kind, from, to, { ...options2, scope, policy })).revision) };
39241
39549
  })),
39242
39550
  "library.skills.set": guard(({ agent, skills }) => serial(() => setGivenSkills(agent, skills, { library: home2 })), { notFound: "invalid_entry" }),
39243
39551
  "library.models": guard(async () => {
@@ -39258,13 +39566,13 @@ function createOperations({ home: home2, templates, knownProjects = () => [] } =
39258
39566
  const library = home2 !== void 0;
39259
39567
  return {
39260
39568
  handlers: { ...taskHandlers, ...library ? libraryHandlers : {} },
39261
- capabilities: [CAPABILITY.task, ...library ? [CAPABILITY.team, CAPABILITY.library, CAPABILITY.settingsLanguage] : []]
39569
+ capabilities: [CAPABILITY.task, ...library ? [CAPABILITY.team, CAPABILITY.library, CAPABILITY.libraryProjectWrite, CAPABILITY.settingsLanguage] : []]
39262
39570
  };
39263
39571
  }
39264
39572
 
39265
39573
  // packages/daemon/src/files.ts
39266
39574
  import { constants as constants3 } from "node:fs";
39267
- import { lstat as lstat6, open as open4, opendir, realpath as realpath5, stat as stat9 } from "node:fs/promises";
39575
+ import { lstat as lstat7, open as open4, opendir, realpath as realpath5, stat as stat9 } from "node:fs/promises";
39268
39576
  import path34 from "node:path";
39269
39577
 
39270
39578
  // packages/daemon/src/gitignore.ts
@@ -39367,7 +39675,8 @@ function hasGitEntry(root) {
39367
39675
  try {
39368
39676
  lstatSync2(path33.join(directory, ".git"));
39369
39677
  return true;
39370
- } catch {
39678
+ } catch (error51) {
39679
+ if (error51.code !== "ENOENT") throw new IgnoreUnknown("cannot examine .git");
39371
39680
  }
39372
39681
  if (path33.dirname(directory) === directory) return false;
39373
39682
  }
@@ -39463,22 +39772,34 @@ function check4(git2, root, paths, deadline, timeoutMs) {
39463
39772
  if (err.length < GIT_CHECK.maxErrorBytes) err += chunk.toString("utf8");
39464
39773
  });
39465
39774
  child.on("error", (error51) => finish(() => {
39466
- if (error51.code === "ENOENT") resolve2(null);
39467
- else reject(new IgnoreUnknown(error51.message));
39775
+ reject(new IgnoreUnknown(error51.message));
39468
39776
  }));
39469
39777
  child.on("close", (code) => finish(() => {
39470
39778
  if (code === 128 && /not a git repository/i.test(err)) {
39471
- if (hasGitEntry(root)) reject(new IgnoreUnknown("a .git Git cannot use"));
39472
- else resolve2(null);
39779
+ try {
39780
+ if (hasGitEntry(root)) reject(new IgnoreUnknown("a .git Git cannot use"));
39781
+ else resolve2(null);
39782
+ } catch (error51) {
39783
+ reject(error51);
39784
+ }
39473
39785
  return;
39474
39786
  }
39475
39787
  if (code !== 0 && code !== 1) {
39476
39788
  reject(new IgnoreUnknown(`exit ${code}`));
39477
39789
  return;
39478
39790
  }
39791
+ if (err.length || code === 1 && outBytes || code === 0 && !outBytes) {
39792
+ reject(new IgnoreUnknown("inconsistent exit status or diagnostics"));
39793
+ return;
39794
+ }
39795
+ const output = Buffer.concat(out).toString("utf8");
39796
+ if (outBytes && !output.endsWith("\0")) {
39797
+ reject(new IgnoreUnknown("unterminated answer"));
39798
+ return;
39799
+ }
39479
39800
  const asked = new Set(paths);
39480
39801
  const ignored = /* @__PURE__ */ new Set();
39481
- for (const entry of Buffer.concat(out).toString("utf8").split("\0")) {
39802
+ for (const entry of output.split("\0")) {
39482
39803
  if (!entry) continue;
39483
39804
  const relative = entry.startsWith("./") ? entry.slice(2) : void 0;
39484
39805
  if (relative === void 0 || !asked.has(relative)) {
@@ -39511,7 +39832,7 @@ var HIDDEN_DIRECTORIES = /* @__PURE__ */ new Set([".agents", ".alp", ".claude",
39511
39832
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "venv", "env", "virtualenv", "dist", "build", "target", "out", "coverage", "vendor", "__pycache__", ".git"]);
39512
39833
  var NONE = Number.MAX_SAFE_INTEGER;
39513
39834
  async function identity2(directory) {
39514
- const info = await lstat6(directory, { bigint: true });
39835
+ const info = await lstat7(directory, { bigint: true });
39515
39836
  if (!info.isDirectory() || await realpath5(directory) !== directory) return null;
39516
39837
  return `${info.dev}:${info.ino}`;
39517
39838
  }
@@ -39552,7 +39873,7 @@ function rank2(relative, query) {
39552
39873
  var compare = (a, b) => a.tier - b.tier || a.segment - b.segment || a.offset - b.offset || a.fuzzy - b.fuzzy || a.depth - b.depth || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
39553
39874
  var FilesError = class extends Error {
39554
39875
  };
39555
- async function searchWorkspaceFiles(projectRoot, { query = "", limit = LIMITS.defaultLimit, now: now2 = Date.now } = {}) {
39876
+ async function searchWorkspaceFiles(projectRoot, { query = "", limit = LIMITS.defaultLimit, now: now2 = Date.now, git: git2 } = {}) {
39556
39877
  if (!path34.isAbsolute(projectRoot)) throw new FilesError("projectRoot must be an absolute path");
39557
39878
  let root;
39558
39879
  try {
@@ -39564,12 +39885,14 @@ async function searchWorkspaceFiles(projectRoot, { query = "", limit = LIMITS.de
39564
39885
  const wanted = query.trim().replace(/\\/g, "/").replace(/^\.?\/+/, "").toLowerCase();
39565
39886
  const count = Math.max(1, Math.min(LIMITS.maxLimit, Math.trunc(limit)));
39566
39887
  const deadline = now2() + LIMITS.ms;
39888
+ const realDeadline = Date.now() + LIMITS.ms;
39567
39889
  const found = [];
39568
39890
  let scanned = 0;
39569
39891
  let truncated = false;
39570
39892
  let level = [""];
39571
39893
  for (let depth = 0; level.length && !truncated; depth++) {
39572
39894
  const next = [];
39895
+ const levelFiles = [];
39573
39896
  for (const relative of level) {
39574
39897
  if (scanned >= LIMITS.scanned || now2() > deadline) {
39575
39898
  truncated = true;
@@ -39588,6 +39911,7 @@ async function searchWorkspaceFiles(projectRoot, { query = "", limit = LIMITS.de
39588
39911
  }
39589
39912
  const name2 = entry.name;
39590
39913
  const child = relative ? `${relative}/${name2}` : name2;
39914
+ if (name2.includes("\\") || blockedFilePath(child)) continue;
39591
39915
  if (entry.isFile()) {
39592
39916
  if (name2.startsWith(".")) continue;
39593
39917
  const ranked = rank2(child, wanted);
@@ -39601,11 +39925,27 @@ async function searchWorkspaceFiles(projectRoot, { query = "", limit = LIMITS.de
39601
39925
  } catch {
39602
39926
  continue;
39603
39927
  }
39604
- found.push(...files);
39928
+ levelFiles.push(...files);
39605
39929
  next.push(...directories2);
39606
39930
  if (truncated) break;
39607
39931
  }
39608
- level = next;
39932
+ const remaining = Math.min(deadline - now2(), realDeadline - Date.now());
39933
+ if (remaining <= 0) {
39934
+ truncated = true;
39935
+ break;
39936
+ }
39937
+ let ignored;
39938
+ try {
39939
+ ignored = await gitIgnored(root, [...levelFiles.map((file3) => file3.path), ...next], {
39940
+ ...git2,
39941
+ timeoutMs: Math.min(git2?.timeoutMs ?? GIT_CHECK.timeoutMs, remaining)
39942
+ });
39943
+ } catch (error51) {
39944
+ if (!(error51 instanceof IgnoreUnknown)) throw error51;
39945
+ return { files: [], truncated: true };
39946
+ }
39947
+ found.push(...levelFiles.filter((file3) => !ignored?.has(file3.path)));
39948
+ level = next.filter((directory) => !ignored?.has(directory));
39609
39949
  }
39610
39950
  found.sort(compare);
39611
39951
  return { files: found.slice(0, count).map((entry) => ({ path: entry.path })), ...truncated ? { truncated: true } : {} };
@@ -39652,7 +39992,7 @@ async function directoryChain(root, segments, relative) {
39652
39992
  async function directoryIdentity(directory, relative) {
39653
39993
  let info;
39654
39994
  try {
39655
- info = await lstat6(directory, { bigint: true });
39995
+ info = await lstat7(directory, { bigint: true });
39656
39996
  } catch {
39657
39997
  throw notFound(relative);
39658
39998
  }
@@ -39688,7 +40028,7 @@ async function ignoredOf(root, paths, git2) {
39688
40028
  }
39689
40029
  }
39690
40030
  async function readConfined(file3, maxBytes, probe) {
39691
- const before = await lstat6(file3, { bigint: true });
40031
+ const before = await lstat7(file3, { bigint: true });
39692
40032
  if (before.isSymbolicLink()) throw Object.assign(new Error("link"), { linked: true });
39693
40033
  if (!before.isFile()) throw new Error("not a file");
39694
40034
  if (before.nlink > 1n) throw Object.assign(new Error("hard link"), { linked: true });
@@ -39714,7 +40054,7 @@ async function readConfined(file3, maxBytes, probe) {
39714
40054
  }
39715
40055
  if (length > maxBytes) return void 0;
39716
40056
  await probe?.("read");
39717
- const after = await lstat6(file3, { bigint: true });
40057
+ const after = await lstat7(file3, { bigint: true });
39718
40058
  if (after.dev !== before.dev || after.ino !== before.ino) throw Object.assign(new Error("changed"), { linked: true });
39719
40059
  return { bytes: buffer.subarray(0, length), modifiedAt: opened.mtime };
39720
40060
  } finally {
@@ -39771,7 +40111,7 @@ async function listWorkspaceDirectory(projectRoot, { path: relative, includeIgno
39771
40111
  }
39772
40112
  let info;
39773
40113
  try {
39774
- info = await lstat6(path34.join(directory, name2));
40114
+ info = await lstat7(path34.join(directory, name2));
39775
40115
  } catch {
39776
40116
  continue;
39777
40117
  }
@@ -39912,13 +40252,19 @@ function createDaemonServer({ runtime, socketPath, version: version2, onShutdown
39912
40252
  if (event.type === "session.opened") {
39913
40253
  record3 ??= { version: 1, id: sessionId, rootId: root, status: "initializing", createdAt: now2, updatedAt: now2 };
39914
40254
  const usage = event.session.usage ?? record3.session?.usage;
39915
- save2(record3, { rootId: root, session: { ...event.session, ...usage ? { usage } : {} }, status: "idle", lastError: void 0, activeTurnId: void 0 });
40255
+ const parked = recovering.has(root) ? record3.session?.parked : event.session.parked;
40256
+ save2(record3, { rootId: root, session: { ...event.session, ...usage ? { usage } : {}, ...parked ? { parked } : {} }, status: "idle", lastError: void 0, activeTurnId: void 0 });
39916
40257
  return false;
39917
40258
  }
39918
40259
  if (!record3) return false;
39919
40260
  if (event.type === "session.updated") save2(record3, { session: { ...record3.session, ...event.session, ...event.session.parked ? {} : { parked: void 0 } } });
39920
40261
  else if (event.type === "turn.started") save2(record3, { status: "running", activeTurnId: event.turnId, archived: void 0 });
39921
- else if (event.type === "turn.ended") save2(record3, { status: "idle", activeTurnId: void 0, ...event.state === "failed" ? { lastError: { message: event.error?.message ?? "Turn failed" } } : {} });
40262
+ else if (event.type === "turn.ended") save2(record3, {
40263
+ status: "idle",
40264
+ activeTurnId: void 0,
40265
+ ...record3.session ? { session: { ...record3.session, parked: event.state === "failed" ? runtime.snapshot(sessionId)?.parked : void 0 } } : {},
40266
+ ...event.state === "failed" ? { lastError: { message: event.error?.message ?? "Turn failed" } } : {}
40267
+ });
39922
40268
  else if (event.type === "usage" && record3.session) record3.session = { ...record3.session, usage: event.usage };
39923
40269
  else if (event.type === "session.failed") save2(record3, { status: "error", lastError: event.error });
39924
40270
  else if (event.type === "session.closed") save2(record3, { status: record3.status === "error" ? "error" : "closed", activeTurnId: void 0 });
@@ -39983,7 +40329,7 @@ function createDaemonServer({ runtime, socketPath, version: version2, onShutdown
39983
40329
  for (const [root, tree] of byRoot) {
39984
40330
  const head = tree.find((record3) => record3.id === root);
39985
40331
  if (removed.has(root)) continue;
39986
- if (head && resumable(head) && (head.status === "running" || tree.some((record3) => continuing.has(record3.id)))) reopen.push({ record: head, working: head.status === "running" });
40332
+ if (head && resumable(head) && (head.status === "running" || head.status === "idle" && head.session?.parked || tree.some((record3) => continuing.has(record3.id)))) reopen.push({ record: head, working: head.status === "running" });
39987
40333
  if (tree.some((record3) => LIVE.includes(record3.status))) seen(root, await store2.timeline(root));
39988
40334
  for (const record3 of tree) {
39989
40335
  records.set(record3.id, record3);
@@ -39991,7 +40337,7 @@ function createDaemonServer({ runtime, socketPath, version: version2, onShutdown
39991
40337
  const envelope = (event) => ({ sessionId: record3.id, epoch, seq: 0, ts: (/* @__PURE__ */ new Date()).toISOString(), event });
39992
40338
  if (record3.activeTurnId) keep(root, envelope({ type: "turn.ended", turnId: record3.activeTurnId, state: "failed", error: { message: ended.message, code: ended.code } }));
39993
40339
  if (record3.id === root) {
39994
- save2(record3, record3.status === "idle" ? { status: "closed", activeTurnId: void 0 } : { status: "error", lastError: ended, activeTurnId: void 0 });
40340
+ if (!(record3.status === "idle" && record3.session?.parked)) save2(record3, record3.status === "idle" ? { status: "closed", activeTurnId: void 0 } : { status: "error", lastError: ended, activeTurnId: void 0 });
39995
40341
  } else if (continuing.has(record3.id) && reopen.some((entry) => entry.record.id === root)) {
39996
40342
  save2(record3, { status: "closed", activeTurnId: void 0 });
39997
40343
  } else {
@@ -40086,7 +40432,7 @@ function createDaemonServer({ runtime, socketPath, version: version2, onShutdown
40086
40432
  persist: true,
40087
40433
  restore: { agent: session.agent, threadId: session.threadId, runtime: session.runtime, model: session.model, workflow: session.workflow }
40088
40434
  }, { history: "skip", delegation: record3.delegation ?? true });
40089
- const outcomes = await runtime.recover(record3.id, { continueRoot: working });
40435
+ const outcomes = await runtime.recover(record3.id, { continueRoot: working, parkedRoot: session.parked });
40090
40436
  recoveryLog.info(`recovered ${record3.id}: ${outcomes.map((outcome) => `${outcome.agent} ${outcome.outcome}`).join(", ") || "root"}`, { event: "recovered", sessionId: record3.id, rootId: record3.rootId });
40091
40437
  } catch (error51) {
40092
40438
  recoveryLog.error(`could not recover ${record3.id}`, { code: "ALP_RECOVERY_FAILED", sessionId: record3.id, rootId: record3.rootId, error: error51 });
@@ -40809,7 +41155,7 @@ ${session.id}`.toLowerCase().includes(word))).sort((a, b) => listCursor.compare(
40809
41155
  async listen() {
40810
41156
  if (process.platform !== "win32") {
40811
41157
  await assertPrivateDirectory(path35.dirname(socketPath), { create: true });
40812
- const existing = await lstat7(socketPath).catch((error51) => {
41158
+ const existing = await lstat8(socketPath).catch((error51) => {
40813
41159
  if (error51.code === "ENOENT") return void 0;
40814
41160
  throw error51;
40815
41161
  });
@@ -40848,8 +41194,8 @@ ${session.id}`.toLowerCase().includes(word))).sort((a, b) => listCursor.compare(
40848
41194
  if (process.platform !== "win32") {
40849
41195
  try {
40850
41196
  await chmod(bindPath, 384);
40851
- const info = await lstat7(bindPath);
40852
- await rename7(bindPath, socketPath);
41197
+ const info = await lstat8(bindPath);
41198
+ await rename8(bindPath, socketPath);
40853
41199
  socketIdentity = { dev: info.dev, ino: info.ino };
40854
41200
  } catch (error51) {
40855
41201
  await new Promise((resolve2) => server.close(() => resolve2()));
@@ -40932,7 +41278,7 @@ ${session.id}`.toLowerCase().includes(word))).sort((a, b) => listCursor.compare(
40932
41278
  for (const connection of connections) connection.socket?.destroy();
40933
41279
  await stopped;
40934
41280
  if (socketIdentity) {
40935
- const current2 = await lstat7(socketPath).catch((error51) => {
41281
+ const current2 = await lstat8(socketPath).catch((error51) => {
40936
41282
  if (error51.code === "ENOENT") return void 0;
40937
41283
  throw error51;
40938
41284
  });
@@ -40945,14 +41291,14 @@ ${session.id}`.toLowerCase().includes(word))).sort((a, b) => listCursor.compare(
40945
41291
  }
40946
41292
 
40947
41293
  // packages/daemon/src/lock.ts
40948
- import { unlink as unlink5, utimes, writeFile as writeFile15, rename as rename8 } from "node:fs/promises";
41294
+ import { unlink as unlink5, utimes, writeFile as writeFile16, rename as rename9 } from "node:fs/promises";
40949
41295
  async function acquireLock(home2, contents) {
40950
41296
  return acquireDaemonLock(home2, contents);
40951
41297
  }
40952
41298
  async function replaceFile(temporary, file3) {
40953
41299
  for (let attempt = 0; ; attempt++) {
40954
41300
  try {
40955
- return await rename8(temporary, file3);
41301
+ return await rename9(temporary, file3);
40956
41302
  } catch (error51) {
40957
41303
  if (process.platform !== "win32" || attempt >= 20 || !["EPERM", "EACCES", "EBUSY"].includes(error51?.code)) throw error51;
40958
41304
  await new Promise((resolve2) => setTimeout(resolve2, 10 + attempt * 10));
@@ -40962,7 +41308,7 @@ async function replaceFile(temporary, file3) {
40962
41308
  async function updateLock(home2, lock) {
40963
41309
  const file3 = daemonPaths(home2).lock;
40964
41310
  const temporary = `${file3}.${process.pid}.tmp`;
40965
- await writeFile15(temporary, JSON.stringify(lock), { mode: 384 });
41311
+ await writeFile16(temporary, JSON.stringify(lock), { mode: 384 });
40966
41312
  await replaceFile(temporary, file3);
40967
41313
  }
40968
41314
  async function heartbeat(home2) {
@@ -40980,7 +41326,7 @@ async function releaseLock(home2) {
40980
41326
  }
40981
41327
 
40982
41328
  // packages/daemon/src/store.ts
40983
- import { appendFile as appendFile4, mkdir as mkdir15, open as open5, readdir as readdir8, readFile as readFile20, rm as rm6, stat as stat10, writeFile as writeFile16 } from "node:fs/promises";
41329
+ import { appendFile as appendFile4, mkdir as mkdir15, open as open5, readdir as readdir10, readFile as readFile20, rm as rm6, stat as stat10, writeFile as writeFile17 } from "node:fs/promises";
40984
41330
  import { randomBytes as randomBytes8 } from "node:crypto";
40985
41331
  import path36 from "node:path";
40986
41332
  var log3 = createLogger({ component: "store" });
@@ -41009,7 +41355,7 @@ function createStore(root) {
41009
41355
  async function writeAtomic3(file3, body) {
41010
41356
  const temporary = `${file3}.${process.pid}.${randomBytes8(4).toString("hex")}.tmp`;
41011
41357
  try {
41012
- await writeFile16(temporary, body, { mode: 384 });
41358
+ await writeFile17(temporary, body, { mode: 384 });
41013
41359
  await replaceFile(temporary, file3);
41014
41360
  } catch (error51) {
41015
41361
  await rm6(temporary, { force: true }).catch(() => {
@@ -41126,7 +41472,7 @@ function createStore(root) {
41126
41472
  async receipts() {
41127
41473
  await prepare2();
41128
41474
  const found = [];
41129
- for (const name2 of await readdir8(receiptsDir)) {
41475
+ for (const name2 of await readdir10(receiptsDir)) {
41130
41476
  if (!name2.endsWith(".json")) continue;
41131
41477
  const key2 = name2.slice(0, -".json".length);
41132
41478
  try {
@@ -41152,7 +41498,7 @@ function createStore(root) {
41152
41498
  await prepare2();
41153
41499
  const now2 = Date.now();
41154
41500
  for (const directory of [sessionsDir, receiptsDir]) {
41155
- for (const name2 of await readdir8(directory)) {
41501
+ for (const name2 of await readdir10(directory)) {
41156
41502
  const file3 = path36.join(directory, name2);
41157
41503
  if (name2.endsWith(".tmp")) {
41158
41504
  if (temporaries) await rm6(file3, { force: true });
@@ -41167,7 +41513,7 @@ function createStore(root) {
41167
41513
  async list() {
41168
41514
  await prepare2();
41169
41515
  const records = [];
41170
- for (const name2 of await readdir8(sessionsDir)) {
41516
+ for (const name2 of await readdir10(sessionsDir)) {
41171
41517
  if (!name2.endsWith(".json")) continue;
41172
41518
  try {
41173
41519
  const record3 = JSON.parse(await readFile20(path36.join(sessionsDir, name2), "utf8"));
@@ -41219,7 +41565,7 @@ function createStore(root) {
41219
41565
  let step = "write";
41220
41566
  try {
41221
41567
  await prepare2();
41222
- await writeFile16(file3, token, { mode: 384 });
41568
+ await writeFile17(file3, token, { mode: 384 });
41223
41569
  step = "read";
41224
41570
  if (await readFile20(file3, "utf8") !== token) throw new Error("what was written is not what was read back");
41225
41571
  return { ok: true };
@@ -41257,7 +41603,7 @@ function createStore(root) {
41257
41603
  // packages/core/src/backup.js
41258
41604
  import { createHash as createHash12, randomBytes as randomBytes9 } from "node:crypto";
41259
41605
  import { createReadStream, createWriteStream } from "node:fs";
41260
- import { chmod as chmod2, lstat as lstat8, mkdir as mkdir17, open as open6, readdir as readdir10, readFile as readFile22, readlink as readlink2, rm as rm7, rmdir as rmdir2, stat as stat11, symlink as symlink2, utimes as utimes2 } from "node:fs/promises";
41606
+ import { chmod as chmod2, lstat as lstat9, mkdir as mkdir17, open as open6, readdir as readdir12, readFile as readFile22, readlink as readlink2, rm as rm7, rmdir as rmdir3, stat as stat11, symlink as symlink2, utimes as utimes2 } from "node:fs/promises";
41261
41607
  import path38 from "node:path";
41262
41608
  import { Readable } from "node:stream";
41263
41609
  import { StringDecoder } from "node:string_decoder";
@@ -41265,7 +41611,7 @@ import { pipeline } from "node:stream/promises";
41265
41611
  import { createGunzip, createGzip } from "node:zlib";
41266
41612
 
41267
41613
  // packages/core/src/state-schema.js
41268
- import { readdir as readdir9, readFile as readFile21, mkdir as mkdir16 } from "node:fs/promises";
41614
+ import { readdir as readdir11, readFile as readFile21, mkdir as mkdir16 } from "node:fs/promises";
41269
41615
  import path37 from "node:path";
41270
41616
  var STATE_SCHEMA = 2;
41271
41617
  var stateDir = (home2) => path37.join(home2, "state");
@@ -41279,7 +41625,7 @@ async function readStateSchema(home2) {
41279
41625
  } catch (error51) {
41280
41626
  if (error51?.code !== "ENOENT") throw error51;
41281
41627
  for (const name2 of DATA) {
41282
- const entries2 = await readdir9(path37.join(stateDir(home2), name2)).catch((error52) => {
41628
+ const entries2 = await readdir11(path37.join(stateDir(home2), name2)).catch((error52) => {
41283
41629
  if (error52?.code === "ENOENT") return [];
41284
41630
  throw error52;
41285
41631
  });
@@ -41336,7 +41682,7 @@ var transient = (name2) => name2.endsWith(".tmp") || name2 === ".lock" || /\.pre
41336
41682
  var backupsDir = (home2) => path38.join(home2, "backups");
41337
41683
  var journalFile = (home2) => path38.join(home2, JOURNAL);
41338
41684
  var stamp2 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
41339
- var exists3 = (file3) => lstat8(file3).then(() => true, () => false);
41685
+ var exists3 = (file3) => lstat9(file3).then(() => true, () => false);
41340
41686
  var invalid = (message, options2) => new AlpError("BACKUP_INVALID", message, options2);
41341
41687
  function safeRelative(value) {
41342
41688
  return typeof value === "string" && value.length > 0 && value.length < 4096 && !value.startsWith("/") && !/^[A-Za-z]:/.test(value) && !value.includes("\\") && !value.includes("\0") && value.split("/").every((part) => part && part !== "." && part !== "..");
@@ -41344,7 +41690,7 @@ function safeRelative(value) {
41344
41690
  async function sessionRecords(home2) {
41345
41691
  const directory = path38.join(home2, "state", "sessions");
41346
41692
  const records = [];
41347
- for (const name2 of await readdir10(directory).catch(() => [])) {
41693
+ for (const name2 of await readdir12(directory).catch(() => [])) {
41348
41694
  if (!name2.endsWith(".json")) continue;
41349
41695
  try {
41350
41696
  records.push(JSON.parse(await readFile22(path38.join(directory, name2), "utf8")));
@@ -41363,7 +41709,7 @@ async function externalReferences(home2) {
41363
41709
  }
41364
41710
  async function* unitEntries(base2, unit) {
41365
41711
  const full = path38.join(base2, unit);
41366
- const info = await lstat8(full).catch((error51) => {
41712
+ const info = await lstat9(full).catch((error51) => {
41367
41713
  if (error51?.code === "ENOENT") return void 0;
41368
41714
  throw error51;
41369
41715
  });
@@ -41373,9 +41719,9 @@ async function* unitEntries(base2, unit) {
41373
41719
  if (info2.isSymbolicLink()) yield { t: "link", path: relative, target: await readlink2(file3) };
41374
41720
  else if (info2.isDirectory()) {
41375
41721
  yield { t: "dir", path: relative, mode: info2.mode & 511 };
41376
- for (const name2 of (await readdir10(file3)).sort()) {
41722
+ for (const name2 of (await readdir12(file3)).sort()) {
41377
41723
  if (transient(name2)) continue;
41378
- const child = await lstat8(path38.join(file3, name2)).catch(() => void 0);
41724
+ const child = await lstat9(path38.join(file3, name2)).catch(() => void 0);
41379
41725
  if (child) yield* walk(`${relative}/${name2}`, child);
41380
41726
  }
41381
41727
  } else if (info2.isFile()) yield { t: "file", path: relative, mode: info2.mode & 511, file: file3 };
@@ -41383,7 +41729,7 @@ async function* unitEntries(base2, unit) {
41383
41729
  yield* walk(unit, info);
41384
41730
  }
41385
41731
  async function unitKind(base2, unit) {
41386
- const info = await lstat8(path38.join(base2, unit)).catch((error51) => {
41732
+ const info = await lstat9(path38.join(base2, unit)).catch((error51) => {
41387
41733
  if (error51?.code === "ENOENT") return void 0;
41388
41734
  throw error51;
41389
41735
  });
@@ -41459,7 +41805,7 @@ async function automaticBackup(home2, kind, { units = HOME_UNITS, projects = [],
41459
41805
  const out = path38.join(directory, `${kind}-${stamp2()}-${randomBytes9(2).toString("hex")}${BACKUP_EXTENSION}`);
41460
41806
  await createBackup({ home: home2, out, units, projects, by, kind });
41461
41807
  const prefix = `${kind}-`;
41462
- const same2 = (await readdir10(directory)).filter((name2) => name2.startsWith(prefix) && name2.endsWith(BACKUP_EXTENSION)).sort();
41808
+ const same2 = (await readdir12(directory)).filter((name2) => name2.startsWith(prefix) && name2.endsWith(BACKUP_EXTENSION)).sort();
41463
41809
  for (const name2 of same2.slice(0, Math.max(0, same2.length - AUTOMATIC_KEEP))) await rm7(path38.join(directory, name2), { force: true });
41464
41810
  return out;
41465
41811
  }
@@ -41522,7 +41868,7 @@ async function readBackup(file3, { onHeader, visit }) {
41522
41868
  const unit = units.find((candidate) => record3.path === candidate.path || record3.path.startsWith(`${candidate.path}/`));
41523
41869
  if (!unit || unit.kind === "absent" || record3.path !== unit.path && unit.kind !== "dir") throw invalid(`${record3.path} lies outside the units the backup declares`);
41524
41870
  const seen = written.get(area) ?? written.set(area, { paths: /* @__PURE__ */ new Set(), links: [] }).get(area);
41525
- if (seen.paths.has(record3.path) || seen.links.some((link) => record3.path.startsWith(`${link}/`))) throw invalid(`${record3.path} comes twice, or lies under a link`);
41871
+ if (seen.paths.has(record3.path) || seen.links.some((link2) => record3.path.startsWith(`${link2}/`))) throw invalid(`${record3.path} comes twice, or lies under a link`);
41526
41872
  seen.paths.add(record3.path);
41527
41873
  if (record3.t === "link") seen.links.push(record3.path);
41528
41874
  if (record3.t === "dir" || record3.t === "link") {
@@ -41597,9 +41943,9 @@ async function settleJournal(journal, { releaseLocks = false } = {}) {
41597
41943
  if (journal.marker) await rm7(journal.marker, { force: true });
41598
41944
  }
41599
41945
  for (const directory of journal.staging) await rm7(directory, { recursive: true, force: true });
41600
- for (const directory of journal.created ?? []) await rmdir2(directory).catch(() => {
41946
+ for (const directory of journal.created ?? []) await rmdir3(directory).catch(() => {
41601
41947
  });
41602
- if (releaseLocks) for (const lock of journal.locks ?? []) await rmdir2(lock).catch(() => {
41948
+ if (releaseLocks) for (const lock of journal.locks ?? []) await rmdir3(lock).catch(() => {
41603
41949
  });
41604
41950
  }
41605
41951
  async function recoverInterruptedRestore(home2) {
@@ -41711,7 +42057,7 @@ async function restoreBackup({ home: home2, file: file3, projects = true, safety
41711
42057
  }
41712
42058
  for (const [area, root] of staging) {
41713
42059
  const base2 = area === "home" ? "state/sessions" : ".alp/tasks";
41714
- for (const name2 of await readdir10(path38.join(root, ...base2.split("/"))).catch(() => [])) {
42060
+ for (const name2 of await readdir12(path38.join(root, ...base2.split("/"))).catch(() => [])) {
41715
42061
  if (!name2.endsWith(".json")) continue;
41716
42062
  try {
41717
42063
  JSON.parse(await readFile22(path38.join(root, ...base2.split("/"), name2), "utf8"));
@@ -41825,11 +42171,11 @@ async function clearRestoredMarker(home2) {
41825
42171
  }
41826
42172
 
41827
42173
  // packages/core/src/migrations.js
41828
- import { readdir as readdir11, readFile as readFile23, rm as rm8, stat as stat12 } from "node:fs/promises";
42174
+ import { readdir as readdir13, readFile as readFile23, rm as rm8, stat as stat12 } from "node:fs/promises";
41829
42175
  import path39 from "node:path";
41830
42176
  async function timelineCursors(home2) {
41831
42177
  const directory = path39.join(stateDir(home2), "timeline");
41832
- const names = await readdir11(directory).catch((error51) => {
42178
+ const names = await readdir13(directory).catch((error51) => {
41833
42179
  if (error51?.code === "ENOENT") return [];
41834
42180
  throw error51;
41835
42181
  });
@@ -41913,7 +42259,7 @@ async function migrateState(home2, { by = "alp", migrations = MIGRATIONS, target
41913
42259
  }
41914
42260
 
41915
42261
  // packages/daemon/src/main.ts
41916
- var VERSION = true ? "0.0.0-beta.2" : "0.0.0-dev";
42262
+ var VERSION = true ? "0.0.1-beta.3" : "0.0.0-dev";
41917
42263
  var LOG_BYTES = 10 * 1024 * 1024;
41918
42264
  var LOG_FILES = 3;
41919
42265
  var log4 = createLogger({ component: "alpd" });
@@ -41921,7 +42267,7 @@ async function rotate(log5) {
41921
42267
  const size = await stat13(log5).then((info) => info.size, () => 0);
41922
42268
  if (size < LOG_BYTES) return;
41923
42269
  for (let index = LOG_FILES - 1; index >= 1; index--) {
41924
- await rename9(index === 1 ? log5 : `${log5}.${index - 1}`, `${log5}.${index}`).catch(() => {
42270
+ await rename10(index === 1 ? log5 : `${log5}.${index - 1}`, `${log5}.${index}`).catch(() => {
41925
42271
  });
41926
42272
  }
41927
42273
  }
@@ -41933,12 +42279,39 @@ async function detach(home2) {
41933
42279
  const output = await open7(log5, "a", 384);
41934
42280
  const child = spawn9(process.execPath, [fileURLToPath(import.meta.url)], {
41935
42281
  detached: true,
41936
- stdio: ["ignore", output.fd, output.fd],
42282
+ stdio: ["ignore", output.fd, output.fd, "ipc"],
41937
42283
  env: process.env,
41938
42284
  windowsHide: true
41939
42285
  });
41940
- child.unref();
41941
42286
  await output.close();
42287
+ await new Promise((resolve2, reject) => {
42288
+ const requested = Number(process.argv.find((arg) => arg.startsWith("--startup-timeout="))?.split("=")[1]);
42289
+ const timeoutMs = Number.isFinite(requested) && requested > 0 ? requested : 15e3;
42290
+ let timedOut = false;
42291
+ let force;
42292
+ const timer = setTimeout(() => {
42293
+ timedOut = true;
42294
+ child.kill();
42295
+ force = setTimeout(() => child.kill("SIGKILL"), 1e3);
42296
+ }, timeoutMs);
42297
+ child.once("error", (error51) => {
42298
+ clearTimeout(timer);
42299
+ clearTimeout(force);
42300
+ reject(error51);
42301
+ });
42302
+ child.once("exit", (code, signal) => {
42303
+ clearTimeout(timer);
42304
+ clearTimeout(force);
42305
+ reject(new Error(timedOut ? `alpd startup timed out after ${timeoutMs} ms` : `alpd exited during startup (${code ?? signal})`));
42306
+ });
42307
+ child.once("message", (message) => {
42308
+ if (message !== "alpd.ready" || timedOut) return;
42309
+ clearTimeout(timer);
42310
+ child.disconnect();
42311
+ child.unref();
42312
+ resolve2();
42313
+ });
42314
+ });
41942
42315
  }
41943
42316
  async function serviceLog(home2) {
41944
42317
  await assertPrivateDirectory(home2, { create: true });
@@ -42042,14 +42415,12 @@ async function run2(home2) {
42042
42415
  await stopLeftovers(await readFile24(marker, "utf8").then(JSON.parse).catch(() => void 0));
42043
42416
  }
42044
42417
  await mkdir18(path40.dirname(marker), { recursive: true, mode: 448 });
42045
- await writeFile17(marker, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), groupLeader: leadsGroup() }), { mode: 384 });
42418
+ await writeFile18(marker, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), groupLeader: leadsGroup() }), { mode: 384 });
42046
42419
  const runLogDir = process.env.ALP_RUN_LOG_DIR || path40.join(home2, "runs");
42047
42420
  const worktreeDir = path40.join(home2, "worktrees");
42048
42421
  for (const branch of await reclaimWorktrees(worktreeDir).catch(() => [])) log4.info(`kept interrupted work on branch ${branch}`, { event: "worktree.kept", branch });
42049
42422
  const copyDir = path40.join(home2, "copies");
42050
42423
  await reclaimCopies(copyDir).catch(() => 0);
42051
- await unlink6(path40.join(home2, "state", "alpd.held")).catch(() => {
42052
- });
42053
42424
  const settings = await userSettings(home2);
42054
42425
  const runtime = createAlpRuntime({
42055
42426
  templates: templates_default,
@@ -42114,16 +42485,24 @@ async function run2(home2) {
42114
42485
  phase = "ready";
42115
42486
  await updateLock(home2, { ...lock, ready: true });
42116
42487
  const { install } = daemonPaths(home2);
42117
- await writeFile17(`${install}.tmp`, JSON.stringify({ entry: fileURLToPath(import.meta.url), version: VERSION }), { mode: 384 }).then(() => rename9(`${install}.tmp`, install)).catch((error51) => log4.warn("could not record alpd's location", { code: "ALP_INSTALL_RECORD_FAILED", error: error51 }));
42488
+ await writeFile18(`${install}.tmp`, JSON.stringify({ entry: fileURLToPath(import.meta.url), version: VERSION }), { mode: 384 }).then(() => rename10(`${install}.tmp`, install)).catch((error51) => log4.warn("could not record alpd's location", { code: "ALP_INSTALL_RECORD_FAILED", error: error51 }));
42118
42489
  if (opened.restored) await clearRestoredMarker(home2).catch((error51) => log4.warn("could not clear the restore marker", { code: "ALP_STATE_RESTORED", error: error51 }));
42490
+ if (process.connected) process.send?.("alpd.ready");
42119
42491
  log4.info(`alpd ${VERSION} ready on ${socket} (pid ${process.pid})`, { event: "ready", version: VERSION, pid: process.pid });
42120
42492
  }
42121
42493
  var home = alpHome();
42122
42494
  try {
42123
42495
  if (process.argv.includes("--detach")) await detach(home);
42124
42496
  else {
42497
+ if (!process.connected) await unlink6(path40.join(home, "state", "alpd.held")).catch(() => {
42498
+ });
42125
42499
  if (process.argv.includes("--service")) await serviceLog(home);
42126
- await run2(home);
42500
+ if (process.connected) await run2(home);
42501
+ else {
42502
+ await withDaemonLifecycle(home, "start", async () => {
42503
+ await run2(home);
42504
+ }, { joinStart: true });
42505
+ }
42127
42506
  }
42128
42507
  } catch (error51) {
42129
42508
  const stoppedIn = phase;