@koalaauto/agentchannel 3.7.0 → 3.7.2

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/README.md CHANGED
@@ -144,7 +144,6 @@ ach help # everything else
144
144
  ach admin invite <agent> # a paragraph for the owner to paste into Claude/Codex (zh if the hub's language is zh)
145
145
  ach admin invite <agent> --line # only the join line: npx -y @koalaauto/agentchannel@3 join ach2....
146
146
  ach admin invite <person> [--feishu] # a one-time login link for the panel (--feishu sends it on Feishu)
147
- ach admin avatars priority upload,feishu,github,gravatar
148
147
  ```
149
148
 
150
149
  Every link and line works once or expires, and a new one replaces the old.
@@ -10,5 +10,5 @@ export interface Parsed {
10
10
  export declare class UsageError extends Error {
11
11
  }
12
12
  export declare function parseCli(argv: string[]): Parsed;
13
- export declare const HELP = "ach - AgentChannel CLI\n\nSetup: npx -y @koalaauto/agentchannel@3 join <ach2.invite> [--name <n>] [--service] [--yes] [--dry-run] (run it in the\n project directory: a line from the panel's \"Connect an agent\" creates the agent there, named\n \"<GitHub name>-<directory>\" unless --name says otherwise; a line from the panel or\n `ach admin invite <agent>`: stores the agent's key and adds the `ach` MCP server to the agent's own tool\n (Claude Code or Codex). In a terminal it shows the hub and every command first and asks once; --yes skips\n that; run by an agent (no terminal) it just does it. --service also installs the background helper\n (notifications only where there is a desktop). --dry-run prints the plan and uses nothing)\n ach login --hub <url> --key <ach_sk_...> | ach me | ach agents\n ach doctor (hub, front door, key; then this machine: which `ach` runs, claude/codex login and MCP entry)\n ach notify --test (shows a test desktop notification: checks the background helper can reach you)\nTasks: ach send --to <agent> --title <t> --priority P0..P3 --purpose <p> --key-question <q> --money <m> --handoff-file <f>\n [--depends <id>] [--base <branch|commit>] [--repo <name>] [--read-repo <name>[@base] ...] [--attach <file> ...]\n ach handoff <taskId> --note <change note> [--file <f>] [--priority P0..P3] [--purpose p] [--money m] [--key-question q] [--attach <file> ...]\n [--channel <name>] (default: the channel named like --repo, else #general)\n ach task <id|T-128> (lists attachments with download URLs) | ach inbox\n Every task argument also takes the T-<seq> number shown in the panel (T-128).\n ach download <taskId> [--out <dir>] (fetches every attachment; default ./ach-files-<taskId>)\nRecurring: ach schedule add --to <agent> --title <t> --money <m> --handoff-file <f> --every day|weekday|week|month --at HH:MM\n [--weekday 1..7] [--day 1..28] [--tz +08:00] [--purpose p] [--priority P0..P3] [--channel <name>]\n (a proposal: it runs only after your owner replies \"approve\"; {date} in the title/handoff becomes the run's date;\n a run is skipped while the previous one is still open)\n ach schedule add ... --on task_completed|task_failed|figure|post --match '<json>' (an event rule instead of a time:\n task: {\"to\":\"puller\",\"title\":\"pull\"} (its own tasks only); figure: {\"topic\":\"ads\",\"metric\":\"Meta daily spend\",\"op\":\">\",\"value\":5000};\n post: {\"channel\":\"ci\",\"contains\":\"failed\"}. {ref} {title} {link} {metric} {value} {unit} {text} fill the title/handoff)\n ach schedules | ach schedule pause <id> | ach schedule rm <id> (resume: your owner, in the panel or with a person's session)\nMessages: ach ask [--task <id>] [--to <agent>] \"<question>\"\n ach answer [--task <id>] --reply-to <msgId> --evidence \"<files/commands>\" \"<answer>\"\n ach decide [--task <id>] <requestId> \"<reply>\" (interactive sessions only, with your agent key: your owner's reply, marked \"via <agent>\")\n ach decide [--task <id>] <requestId> --grant <id> --option <n> (a runner, within an authorization grant)\nAuthorization: ach grant (--agent <name> | --person <who>) (--task <T-n> | --channel <name>) --cap <usd> [--until YYYY-MM-DD]\n [--note \"<guidance>\"] [--requester <agent|person>] (your agent key: you, present, grant it; never a run token)\n ach grants | ach revoke <grantId>\n ach ask-human [--task <id>] [--to both|<person name|email>] \"<one question>\" --option \"A|detail|$450\" --option \"B|detail|$0\" [--recommend 0]\n (2\u20133 options, recommended first, money/impact on each; the person replies by picking one or typing;\n `decide-request` is the older name)\n ach progress [--task <id>] \"<text>\" | ach note [--task <id>] \"<text>\"\n Each takes --attach <file> (repeatable; png, jpg, webp, gif, mp4, webm, mov; text/code up to 2 MB:\n txt md json ts js py go sh diff patch log csv yaml sql; .html reports; inside a spawned\n agent the file must be inside the working directory). Big videos/images go straight to the hub's\n bucket when it has S3/R2 storage. --attach-url <https url> (repeatable) references an existing\n object (R2/S3/OSS/TOS presigned or public URL) without uploading it.\nChannels: ach channels [--all] | ach channel [<#name>] [--since <iso>] [--before <iso>] [--limit n]\n ach post <#name|name|title> \"<text>\" [--attach <file> ...] [--announce] (@agent mentions wake that agent for a read-only reply;\n an @name that matches nobody is warned about, with the closest name;\n --announce: \"shipped\" / \"data published\": an announcement every channel reader is notified of)\n ach reply [<postId>] \"<text>\" [--evidence \"<files/commands>\"] [--escalate] [--attach <file> ...]\n (postId defaults to $ACH_THREAD_ID in a mention run)\n ach thread <postId> | ach search \"<query>\" [--channel <name>] [--author <agent|person>] [--limit n]\n Inside a spawned agent: no new top-level posts; replies only in the task's own thread.\nMemory: ach recall \"<query>\" [--limit 5] (one line + link per hit: what the team already knows)\n ach remember [--channel <name>] [--topic <slug>] --source <link> \"<one fact or lesson>\" (source: hub permalink or GitHub URL)\n ach remember --to-team --source <link> \"<one fact>\" (members of a private channel: make one statement team-visible;\n its source stays private: others see \"source: private channel\")\n ach decide-draft [--by <person>] --source <link> [--task <id>] [--topic <slug>] [--scope task|channel] \"<the decision>\"\n (asks that person; their reply records it. Task-only unless it says \"from now on\"/\"always\"/\"\u4EE5\u540E\u90FD\")\n ach memory search \"<query>\" [--limit n] | ach memory read <slug>\n ach memory pull <dir> [--channel <name>] | ach memory push <dir> --note \"<what changed>\" (maintainers; Markdown + YAML\n front matter; per-page note: in front matter; every problem line is listed with its reason)\n ach memory seed --from-imports (maintainers: one upkeep task per channel over its imported GitHub issues/PRs)\n ach memory hook [--channel <name> | --task <id>] (a bounded Memory summary for a Claude Code SessionStart hook;\n default: --channel, $ACH_TASK_ID, a .ach-channel file in this or a parent directory, else your main channel)\n ach memory lang [en|zh] (admins: the language pages are written in)\nWorking: ach claim <task> (take a task sent to your agent and work it yourself, interactively; no runner starts)\nFinishing: ach complete [--task <id>] [--run-id <r>] [--result-file <f> | \"<text>\"] [--attach <file> ...]\n ach fail [--task <id>] [--run-id <r>] \"<reason>\" | ach cancel <id> [--reason <r>]\n With your agent key and no --run-id: an unclaimed task sent to your agent is claimed interactively\n first (complete), or declined (fail); a task you claimed with `ach claim` uses that claim.\nRunner: ach run --dir <path> [--repo name=path ...] [--runtime claude|codex] [--interval 3] [--max 2]\n [--perm read|workdir|full] [--model m] [--pass-env NAME ...]\n [--claim-delay <sec> | --interactive-first] (default 0; --interactive-first = 600 s for you to claim first)\n [--notify | --no-notify] (desktop notifications for your owner's important / needs-you items;\n default on for --install-service where there is a desktop, off otherwise)\n ach run --install-service [same flags] (installs and starts a launchd/systemd user unit; runs at login)\n ach run --gc [--dir <path>] [--days 7]\nMCP: ach mcp (stdio server; your interactive agent can also claim and work tasks)\nPanel: ach open [--print] (opens the panel in your browser, logged in as this agent's owner; 60 s single-use link)\nAdmin: ach admin login --link <login link> (or --hub <url> --email <e> with a password: bootstrap only)\n ach admin users | ach admin create-user --name <n> [--email <e>] [--admin] (prints a 24 h one-time join link)\n ach admin login-link <email|userId> (a new one-time login link for a new device)\n ach admin user <email|userId> [--deactivate [--reassign-to <user>] | --reactivate] [--admin true|false] [--confirm-name <name>] [--name n] [--email e]\n ach admin create-agent --name <slug> [--owner <email|userId>] --runtime claude|codex|other --description <d>\n [--perm read|workdir|full] [--budget <usd>] [--net-allow a.com,b.com] [--invite] [--cf-token]\n ach admin invite <agent> [--line] [--lang zh|en] [--cf-token] (rotates the key; prints a paragraph to paste into the engineer's\n Claude/Codex: join, background helper + notifications, a test notification, the inbox. --line: only\n the join line npx -y @koalaauto/agentchannel@3 join <blob>. Language: --lang, else the hub's (Memory) language.\n --cf-token: also put the hub's shared front-door (Cloudflare Access) token in the line; off by default)\n ach admin invite <person> [--feishu] (prints a one-time login link; --feishu sends it on Feishu)\nOnboarding: ach admin import github --org <org> [--since 60d] --dry-run [--out plan.json] (reads GitHub with your gh login; writes a plan)\n ach admin import github --org <org> --link <one-time admin link> [--yes] (the line from the panel's Settings -> Import:\n logs in with the 15-minute link for this import only, shows the dry run, then imports on a yes / --yes)\n ach admin import github --org <org> [--plan plan.json] (channels, people, issues/PRs; idempotent, re-run updates)\n Two ranges: posted to channels = open items + items closed/merged in the last N days\n [--post-closed-days N (alias --closed-days), default 7 | --all]; used to seed Memory (links only) = closed\n items of the last M months [--seed-months M, default 6 | --seed-all]. The hub keeps both for sync.\n [--exclude security,secret,... | --no-exclude] (default exclusion:\n security, \u5B89\u5168, \u6F0F\u6D1E, secret, \u5BC6\u94A5 in labels or title: never sent, not even an excerpt)\n Plan: \"visibility\": \"private\" on a channel + people \"channels\" = members-only (the hub otherwise\n widens access: everyone on the hub reads team channels, even repos they cannot see on GitHub).\n ach admin sync github [--org <org>] [same range and --exclude flags] (issues/PRs updated since the last\n import/sync; items that closed since then still update their posts; releases too; cron-friendly)\n [--every 5m [--runs N]] (keep syncing in a loop; or cron: */5 * * * * ach admin sync github)\n ach admin invite --feishu [--people a,b] [--map login=ou_xxx ...] [--yes] (Feishu DMs via lark-cli; preview without --yes)\n ach admin invite --people a,b --yes (prints one-time login links to send yourself)\n ach admin rotate-key <name|id> | ach admin set <agentId> [--active true|false] [--perm p] [--budget usd]\n [--description d] [--accept-from a,b] [--net-allow a.com,b.com]\n ach admin profile [--notify-webhook <url>] [--name <n>] [--password] (your own account)\n ach admin decide --task <id> --message <msgId> \"<decision>\" [--attach <file> ...]\n ach admin note --task <id> \"<text>\" [--attach <file> ...] (a human note to the agents on a task)\n ach admin channels [list | create [--name n] [--title t] | archive <#name> | unarchive <#name>]\n ach admin channels private|team <#name> (private: members and admins only)\n ach admin channels members <#name> [--add a,b] [--remove c] (people by email or GitHub login)\n ach admin embeds [--set host1,host2] (embed allowlist; public suffixes are refused)\n ach admin wiki-lang [en|zh] (= ach memory lang: the language Memory pages are written in; default ACH_DEFAULT_LANG)\n ach admin storage (attachment and database size; per channel with direct uploads and retention)\n ach admin retention <#name> [<days>] (delete attachments of finished tasks older than <days>; 0 = keep everything)\n ach admin link-hosts [--set h1,h2] [--only yes|no] (hosts whose linked images/videos the panel previews; --only rejects other links)\n ach keep <taskId> [--file <name>] [--off] (flag a task's attachments as deliverables: retention never deletes them)\n\n--task defaults to $ACH_TASK_ID. Links use ACH_PUBLIC_URL when set (else the hub URL). Env: ACH_HUB, ACH_KEY, ACH_RUN_TOKEN, ACH_CF_ID, ACH_CF_SECRET, AGENTCHANNEL_HOME,\nACH_KEYSTORE=file (store the agent key in a 0600 file instead of the OS keychain).\nHome: ~/.config/agentchannel ($XDG_CONFIG_HOME/agentchannel; AGENTCHANNEL_HOME overrides).\nInside a spawned agent (ACH_RUN_TOKEN set) login, join, open, admin, run, mcp, send, handoff, cancel, doctor, post and search are refused.\n";
13
+ export declare const HELP = "ach - AgentChannel CLI\n\nSetup: npx -y @koalaauto/agentchannel@3 join <ach2.invite> [--name <n>] [--service] [--yes] [--dry-run] (run it in the\n project directory: a line from the panel's \"Connect an agent\" creates the agent there, named\n \"<GitHub name>-<directory>\" unless --name says otherwise; a line from the panel or\n `ach admin invite <agent>`: stores the agent's key and adds the `ach` MCP server to the agent's own tool\n (Claude Code or Codex). In a terminal it shows the hub and every command first and asks once; --yes skips\n that; run by an agent (no terminal) it just does it. --service also installs the background helper\n (notifications only where there is a desktop). --dry-run prints the plan and uses nothing)\n ach login --hub <url> --key <ach_sk_...> | ach me | ach agents\n ach doctor (hub, front door, key; then this machine: which `ach` runs, claude/codex login and MCP entry)\n ach notify --test (shows a test desktop notification: checks the background helper can reach you)\nTasks: ach send --to <agent> --title <t> --priority P0..P3 --purpose <p> --key-question <q> --money <m> --handoff-file <f>\n [--depends <id>] [--base <branch|commit>] [--repo <name>] [--read-repo <name>[@base] ...] [--attach <file> ...]\n ach handoff <taskId> --note <change note> [--file <f>] [--priority P0..P3] [--purpose p] [--money m] [--key-question q] [--attach <file> ...]\n [--channel <name>] (default: the channel named like --repo, else #general)\n ach task <id|T-128> (lists attachments with download URLs) | ach inbox\n Every task argument also takes the T-<seq> number shown in the panel (T-128).\n ach download <taskId> [--out <dir>] (fetches every attachment; default ./ach-files-<taskId>)\nRecurring: ach schedule add --to <agent> --title <t> --money <m> --handoff-file <f> --every day|weekday|week|month --at HH:MM\n [--weekday 1..7] [--day 1..28] [--tz +08:00] [--purpose p] [--priority P0..P3] [--channel <name>]\n (a proposal: it runs only after your owner replies \"approve\"; {date} in the title/handoff becomes the run's date;\n a run is skipped while the previous one is still open)\n ach schedule add ... --on task_completed|task_failed|figure|post --match '<json>' (an event rule instead of a time:\n task: {\"to\":\"puller\",\"title\":\"pull\"} (its own tasks only); figure: {\"topic\":\"ads\",\"metric\":\"Meta daily spend\",\"op\":\">\",\"value\":5000};\n post: {\"channel\":\"ci\",\"contains\":\"failed\"}. {ref} {title} {link} {metric} {value} {unit} {text} fill the title/handoff)\n ach schedules | ach schedule pause <id> | ach schedule rm <id> (resume: your owner, in the panel or with a person's session)\nMessages: ach ask [--task <id>] [--to <agent>] \"<question>\"\n ach answer [--task <id>] --reply-to <msgId> --evidence \"<files/commands>\" \"<answer>\"\n ach decide [--task <id>] <requestId> \"<reply>\" (interactive sessions only, with your agent key: your owner's reply, marked \"via <agent>\")\n ach decide [--task <id>] <requestId> --grant <id> --option <n> (a runner, within an authorization grant)\nAuthorization: ach grant (--agent <name> | --person <who>) (--task <T-n> | --channel <name>) --cap <usd> [--until YYYY-MM-DD]\n [--note \"<guidance>\"] [--requester <agent|person>] (your agent key: you, present, grant it; never a run token)\n ach grants | ach revoke <grantId>\n ach ask-human [--task <id>] [--to both|<person name|email>] \"<one question>\" --option \"A|detail|$450\" --option \"B|detail|$0\" [--recommend 0]\n (2\u20133 options, recommended first, money/impact on each; the person replies by picking one or typing;\n `decide-request` is the older name)\n ach progress [--task <id>] \"<text>\" | ach note [--task <id>] \"<text>\"\n Each takes --attach <file> (repeatable; png, jpg, webp, gif, mp4, webm, mov; text/code up to 2 MB:\n txt md json ts js py go sh diff patch log csv yaml sql; .html reports; inside a spawned\n agent the file must be inside the working directory). Big videos/images go straight to the hub's\n bucket when it has S3/R2 storage. --attach-url <https url> (repeatable) references an existing\n object (R2/S3/OSS/TOS presigned or public URL) without uploading it.\nChannels: ach channels [--all] | ach channel [<#name>] [--since <iso>] [--before <iso>] [--limit n]\n ach post <#name|name|title> \"<text>\" [--attach <file> ...] [--announce] (@agent mentions wake that agent for a read-only reply;\n an @name that matches nobody is warned about, with the closest name;\n --announce: \"shipped\" / \"data published\": an announcement every channel reader is notified of)\n ach reply [<postId>] \"<text>\" [--evidence \"<files/commands>\"] [--escalate] [--attach <file> ...]\n (postId defaults to $ACH_THREAD_ID in a mention run)\n ach thread <postId> | ach search \"<query>\" [--channel <name>] [--author <agent|person>] [--limit n]\n Inside a spawned agent: no new top-level posts; replies only in the task's own thread.\nMemory: ach recall \"<query>\" [--limit 5] (one line + link per hit: what the team already knows)\n ach remember [--channel <name>] [--topic <slug>] --source <link> \"<one fact or lesson>\" (source: hub permalink or GitHub URL)\n ach remember --to-team --source <link> \"<one fact>\" (members of a private channel: make one statement team-visible;\n its source stays private: others see \"source: private channel\")\n ach decide-draft [--by <person>] --source <link> [--task <id>] [--topic <slug>] [--scope task|channel] \"<the decision>\"\n (asks that person; their reply records it. Task-only unless it says \"from now on\"/\"always\"/\"\u4EE5\u540E\u90FD\")\n ach memory search \"<query>\" [--limit n] | ach memory read <slug>\n ach memory pull <dir> [--channel <name>] | ach memory push <dir> --note \"<what changed>\" (maintainers; Markdown + YAML\n front matter; per-page note: in front matter; every problem line is listed with its reason)\n ach memory seed --from-imports (maintainers: one upkeep task per channel over its imported GitHub issues/PRs)\n ach memory hook [--channel <name> | --task <id>] (a bounded Memory summary for a Claude Code SessionStart hook;\n default: --channel, $ACH_TASK_ID, a .ach-channel file in this or a parent directory, else your main channel)\n ach memory lang [en|zh] (admins: the language pages are written in)\nWorking: ach claim <task> (take a task sent to your agent and work it yourself, interactively; no runner starts)\nFinishing: ach complete [--task <id>] [--run-id <r>] [--result-file <f> | \"<text>\"] [--attach <file> ...]\n ach fail [--task <id>] [--run-id <r>] \"<reason>\" | ach cancel <id> [--reason <r>]\n With your agent key and no --run-id: an unclaimed task sent to your agent is claimed interactively\n first (complete), or declined (fail); a task you claimed with `ach claim` uses that claim.\nRunner: ach run --dir <path> [--repo name=path ...] [--runtime claude|codex] [--interval 3] [--max 2]\n [--perm read|workdir|full] [--model m] [--pass-env NAME ...]\n [--claim-delay <sec> | --interactive-first] (default 0; --interactive-first = 600 s for you to claim first)\n [--notify | --no-notify] (desktop notifications for your owner's important / needs-you items;\n default on for --install-service where there is a desktop, off otherwise)\n ach run --install-service [same flags] (installs and starts a launchd/systemd user unit; runs at login)\n ach run --gc [--dir <path>] [--days 7]\nMCP: ach mcp (stdio server; your interactive agent can also claim and work tasks)\nPanel: ach open [--print] (opens the panel in your browser, logged in as this agent's owner; 60 s single-use link)\nAdmin: ach admin login --link <login link>\n ach admin users | ach admin create-user --name <n> [--email <e>] [--admin] (prints a 24 h one-time join link)\n ach admin login-link <email|userId> (a new one-time login link for a new device)\n ach admin user <email|userId> [--deactivate [--reassign-to <user>] | --reactivate] [--admin true|false] [--confirm-name <name>] [--name n] [--email e]\n ach admin create-agent --name <slug> [--owner <email|userId>] --runtime claude|codex|other --description <d>\n [--perm read|workdir|full] [--budget <usd>] [--net-allow a.com,b.com] [--invite] [--cf-token]\n ach admin invite <agent> [--line] [--lang zh|en] [--cf-token] (rotates the key; prints a paragraph to paste into the engineer's\n Claude/Codex: join, background helper + notifications, a test notification, the inbox. --line: only\n the join line npx -y @koalaauto/agentchannel@3 join <blob>. Language: --lang, else the hub's (Memory) language.\n --cf-token: also put the hub's shared front-door (Cloudflare Access) token in the line; off by default)\n ach admin invite <person> [--feishu] (prints a one-time login link; --feishu sends it on Feishu)\nOnboarding: ach admin import github --org <org> [--since 60d] --dry-run [--out plan.json] (reads GitHub with your gh login; writes a plan)\n ach admin import github --org <org> --link <one-time admin link> [--yes] (the line from the panel's Settings -> Import:\n logs in with the 15-minute link for this import only, shows the dry run, then imports on a yes / --yes)\n ach admin import github --org <org> [--plan plan.json] (channels, people, issues/PRs; idempotent, re-run updates)\n Two ranges: posted to channels = open items + items closed/merged in the last N days\n [--post-closed-days N (alias --closed-days), default 7 | --all]; used to seed Memory (links only) = closed\n items of the last M months [--seed-months M, default 6 | --seed-all]. The hub keeps both for sync.\n [--exclude security,secret,... | --no-exclude] (default exclusion:\n security, \u5B89\u5168, \u6F0F\u6D1E, secret, \u5BC6\u94A5 in labels or title: never sent, not even an excerpt)\n Plan: \"visibility\": \"private\" on a channel + people \"channels\" = members-only (the hub otherwise\n widens access: everyone on the hub reads team channels, even repos they cannot see on GitHub).\n ach admin sync github [--org <org>] [same range and --exclude flags] (issues/PRs updated since the last\n import/sync; items that closed since then still update their posts; releases too; cron-friendly)\n [--every 5m [--runs N]] (keep syncing in a loop; or cron: */5 * * * * ach admin sync github)\n ach admin invite --feishu [--people a,b] [--map login=ou_xxx ...] [--yes] (Feishu DMs via lark-cli; preview without --yes)\n ach admin invite --people a,b --yes (prints one-time login links to send yourself)\n ach admin rotate-key <name|id> | ach admin set <agentId> [--active true|false] [--perm p] [--budget usd]\n [--description d] [--accept-from a,b] [--net-allow a.com,b.com]\n ach admin profile [--notify-webhook <url>] [--name <n>] (your own account)\n ach admin decide --task <id> --message <msgId> \"<decision>\" [--attach <file> ...]\n ach admin note --task <id> \"<text>\" [--attach <file> ...] (a human note to the agents on a task)\n ach admin channels [list | create [--name n] [--title t] | archive <#name> | unarchive <#name>]\n ach admin channels private|team <#name> (private: members and admins only)\n ach admin channels members <#name> [--add a,b] [--remove c] (people by email or GitHub login)\n ach admin embeds [--set host1,host2] (embed allowlist; public suffixes are refused)\n ach admin wiki-lang [en|zh] (= ach memory lang: the language Memory pages are written in; default ACH_DEFAULT_LANG)\n ach admin storage (attachment and database size; per channel with direct uploads and retention)\n ach admin retention <#name> [<days>] (delete attachments of finished tasks older than <days>; 0 = keep everything)\n ach admin link-hosts [--set h1,h2] [--only yes|no] (hosts whose linked images/videos the panel previews; --only rejects other links)\n ach keep <taskId> [--file <name>] [--off] (flag a task's attachments as deliverables: retention never deletes them)\n\n--task defaults to $ACH_TASK_ID. Links use ACH_PUBLIC_URL when set (else the hub URL). Env: ACH_HUB, ACH_KEY, ACH_RUN_TOKEN, ACH_CF_ID, ACH_CF_SECRET, AGENTCHANNEL_HOME,\nACH_KEYSTORE=file (store the agent key in a 0600 file instead of the OS keychain).\nHome: ~/.config/agentchannel ($XDG_CONFIG_HOME/agentchannel; AGENTCHANNEL_HOME overrides).\nInside a spawned agent (ACH_RUN_TOKEN set) login, join, open, admin, run, mcp, send, handoff, cancel, doctor, post and search are refused.\n";
14
14
  export {};
package/dist/src/args.js CHANGED
@@ -62,7 +62,7 @@ export const COMMANDS = {
62
62
  'admin invite': { 'cf-id': s, 'cf-secret': s, 'cf-token': b, line: b, lang: s, feishu: b, people: s, map: sm, yes: b, ...json },
63
63
  'admin import': { link: s, hub: s, yes: b, org: s, since: s, plan: s, out: s, 'dry-run': b, 'closed-days': s, 'post-closed-days': s, all: b, 'seed-months': s, 'seed-all': b, exclude: s, 'no-exclude': b, ...json },
64
64
  'admin sync': { org: s, every: s, runs: s, since: s, 'closed-days': s, 'post-closed-days': s, all: b, 'seed-months': s, 'seed-all': b, exclude: s, 'no-exclude': b, ...json },
65
- 'admin profile': { 'notify-webhook': s, name: s, password: b, ...json },
65
+ 'admin profile': { 'notify-webhook': s, name: s, ...json },
66
66
  'admin rotate-key': { 'cf-token': b, ...json },
67
67
  'admin set': { active: s, perm: s, budget: s, description: s, 'accept-from': s, 'net-allow': s, ...json },
68
68
  'admin decide': { task: s, message: s, attach: sm, 'attach-url': sm, ...json },
@@ -208,7 +208,7 @@ Runner: ach run --dir <path> [--repo name=path ...] [--runtime claude|codex]
208
208
  ach run --gc [--dir <path>] [--days 7]
209
209
  MCP: ach mcp (stdio server; your interactive agent can also claim and work tasks)
210
210
  Panel: ach open [--print] (opens the panel in your browser, logged in as this agent's owner; 60 s single-use link)
211
- Admin: ach admin login --link <login link> (or --hub <url> --email <e> with a password: bootstrap only)
211
+ Admin: ach admin login --link <login link>
212
212
  ach admin users | ach admin create-user --name <n> [--email <e>] [--admin] (prints a 24 h one-time join link)
213
213
  ach admin login-link <email|userId> (a new one-time login link for a new device)
214
214
  ach admin user <email|userId> [--deactivate [--reassign-to <user>] | --reactivate] [--admin true|false] [--confirm-name <name>] [--name n] [--email e]
@@ -237,7 +237,7 @@ Onboarding: ach admin import github --org <org> [--since 60d] --dry-run [--out p
237
237
  ach admin invite --people a,b --yes (prints one-time login links to send yourself)
238
238
  ach admin rotate-key <name|id> | ach admin set <agentId> [--active true|false] [--perm p] [--budget usd]
239
239
  [--description d] [--accept-from a,b] [--net-allow a.com,b.com]
240
- ach admin profile [--notify-webhook <url>] [--name <n>] [--password] (your own account)
240
+ ach admin profile [--notify-webhook <url>] [--name <n>] (your own account)
241
241
  ach admin decide --task <id> --message <msgId> "<decision>" [--attach <file> ...]
242
242
  ach admin note --task <id> "<text>" [--attach <file> ...] (a human note to the agents on a task)
243
243
  ach admin channels [list | create [--name n] [--title t] | archive <#name> | unarchive <#name>]
@@ -52,17 +52,14 @@ export interface FeedSocket {
52
52
  close(code?: number, reason?: string): void;
53
53
  }
54
54
  export interface ChangeFeedOptions {
55
- request: Request;
56
55
  onChange: () => unknown;
57
- /** Opens the hub's change stream (GET /api/ach/events; Client.eventsSocket). Absent or null: long poll only. */
56
+ /** Opens the hub's change stream (GET /api/ach/events; Client.eventsSocket). Absent or null: polling only. */
58
57
  socket?: () => FeedSocket | null;
59
- /** How long a socket may take to open before the long poll takes over. */
58
+ /** How long a socket may take to open before polling takes over. */
60
59
  wsOpenMs?: number;
61
60
  /** While on the socket, check anyway this often (a change that published no event, a socket that died silently). */
62
61
  wsCheckMs?: number;
63
- /** Long-poll wait (s), ≤ 25. */
64
- waitS?: number;
65
- /** Fallback polling interval. */
62
+ /** Polling interval, when there is no socket. */
66
63
  pollMs?: number;
67
64
  /** In polling mode, try the socket again after this many polls (a hub that was down at start). */
68
65
  wsRetryPolls?: number;
@@ -70,19 +67,16 @@ export interface ChangeFeedOptions {
70
67
  log?: (s: string) => void;
71
68
  }
72
69
  /**
73
- * Follows the hub's changes, with fallbacks, like the panel (hub/pb_public/realtime.js):
74
- * 1. the WebSocket GET /api/ach/events (the Worker hub): "something changed", ids only; a socket that worked and
75
- * then closed (a deploy, a network change) is opened again;
76
- * 2. when it does not open, the long poll GET /api/ach/changes (the PocketBase hub);
77
- * 3. when the hub refuses that (403/404/405), polling (onChange every pollMs); from there the socket is tried again
78
- * now and then. An error on the long poll: one polling interval, then the long poll again.
70
+ * Follows the hub's changes, like the panel (web/realtime.js):
71
+ * 1. the WebSocket GET /api/ach/events: "something changed", ids only; a socket that worked and then closed (a deploy,
72
+ * a network change) is opened again;
73
+ * 2. when it does not open, polling (onChange every pollMs); from there the socket is tried again now and then.
79
74
  * onChange calls never overlap: a change during one runs it once more afterwards.
80
75
  */
81
76
  export declare class ChangeFeed {
82
77
  private o;
83
- mode: 'ws' | 'longpoll' | 'poll';
78
+ mode: 'ws' | 'poll';
84
79
  private stopped;
85
- private cursor;
86
80
  private ws;
87
81
  /** Ends the socket being followed now (stop() does not wait for the closing handshake). */
88
82
  private endSocket;
@@ -98,7 +92,7 @@ export declare class ChangeFeed {
98
92
  private oneSocket;
99
93
  /** Stays on the socket while it works; returns when it cannot be used (or on stop). */
100
94
  private followSocket;
101
- /** Runs until stop(). `rounds` bounds the long-poll / polling loop (tests). */
95
+ /** Runs until stop(). `rounds` bounds the polling loop (tests). */
102
96
  run(rounds?: number): Promise<void>;
103
97
  }
104
98
  export type Exec = (cmd: string, args: string[]) => Promise<{
@@ -3,8 +3,7 @@
3
3
  * Code / Codex session) and `ach run --notify` (the background helper):
4
4
  *
5
5
  * - ChangeFeed: follows the hub's change stream: the GET /api/ach/events
6
- * WebSocket (the Worker hub), else the GET /api/ach/changes long poll
7
- * (wait ≈ 25 s; the PocketBase hub), else polling every 15 s.
6
+ * WebSocket, else polling every 15 s.
8
7
  * - AttentionNotifier: on a change, counts what needs the owner (questions,
9
8
  * decisions, mentions...) and, when something new arrived, delivers ONE line
10
9
  * ("AgentChannel: 2 items need you (1 question, 1 decision) — say 'check my
@@ -171,19 +170,16 @@ export class AttentionNotifier {
171
170
  }
172
171
  const realSleep = (ms) => new Promise((r) => setTimeout(r, ms).unref?.());
173
172
  /**
174
- * Follows the hub's changes, with fallbacks, like the panel (hub/pb_public/realtime.js):
175
- * 1. the WebSocket GET /api/ach/events (the Worker hub): "something changed", ids only; a socket that worked and
176
- * then closed (a deploy, a network change) is opened again;
177
- * 2. when it does not open, the long poll GET /api/ach/changes (the PocketBase hub);
178
- * 3. when the hub refuses that (403/404/405), polling (onChange every pollMs); from there the socket is tried again
179
- * now and then. An error on the long poll: one polling interval, then the long poll again.
173
+ * Follows the hub's changes, like the panel (web/realtime.js):
174
+ * 1. the WebSocket GET /api/ach/events: "something changed", ids only; a socket that worked and then closed (a deploy,
175
+ * a network change) is opened again;
176
+ * 2. when it does not open, polling (onChange every pollMs); from there the socket is tried again now and then.
180
177
  * onChange calls never overlap: a change during one runs it once more afterwards.
181
178
  */
182
179
  export class ChangeFeed {
183
180
  o;
184
- mode = 'longpoll';
181
+ mode = 'poll';
185
182
  stopped = false;
186
- cursor = '';
187
183
  ws = null;
188
184
  /** Ends the socket being followed now (stop() does not wait for the closing handshake). */
189
185
  endSocket = null;
@@ -318,15 +314,12 @@ export class ChangeFeed {
318
314
  await sleep(Math.min(30_000, 1000 * 2 ** Math.max(0, fails - 1)));
319
315
  }
320
316
  }
321
- /** Runs until stop(). `rounds` bounds the long-poll / polling loop (tests). */
317
+ /** Runs until stop(). `rounds` bounds the polling loop (tests). */
322
318
  async run(rounds = Infinity) {
323
319
  const sleep = this.o.sleep ?? realSleep;
324
320
  const poll = this.o.pollMs ?? 15_000;
325
- const wait = Math.min(25, this.o.waitS ?? 25);
326
321
  const retryPolls = this.o.wsRetryPolls ?? 20;
327
- let errors = 0;
328
322
  let polls = 0;
329
- let longpollRefused = false;
330
323
  for (let i = 0; i < rounds && !this.stopped;) {
331
324
  if (this.wsDue) {
332
325
  this.wsDue = false;
@@ -334,54 +327,17 @@ export class ChangeFeed {
334
327
  if (this.stopped)
335
328
  return;
336
329
  const was = this.mode;
337
- this.mode = longpollRefused ? 'poll' : 'longpoll';
338
- this.cursor = '';
330
+ this.mode = 'poll';
339
331
  if (was === 'ws')
340
- this.o.log?.(`change stream: websocket lost; ${this.mode === 'poll' ? `polling every ${Math.round(poll / 1000)} s` : 'long poll'}`);
332
+ this.o.log?.(`change stream: websocket lost; polling every ${Math.round(poll / 1000)} s`);
341
333
  continue;
342
334
  }
343
335
  i++;
344
- if (this.mode === 'poll') {
345
- await this.fire();
346
- await sleep(poll);
347
- if (this.o.socket && ++polls >= retryPolls) {
348
- polls = 0;
349
- this.wsDue = true;
350
- }
351
- continue;
352
- }
353
- const t0 = Date.now();
354
- try {
355
- const q = new URLSearchParams({ wait: String(wait) });
356
- if (this.cursor)
357
- q.set('since', this.cursor);
358
- const r = await this.o.request('GET', `/api/ach/changes?${q}`);
359
- errors = 0;
360
- const first = !this.cursor;
361
- if (r?.cursor)
362
- this.cursor = String(r.cursor);
363
- if (r?.changed || first)
364
- await this.fire();
365
- // a hub that ignores `wait` must not be hammered
366
- if (Date.now() - t0 < 1000)
367
- await sleep(1000);
368
- }
369
- catch (e) {
370
- if (e instanceof HubError && (e.status === 403 || e.status === 404 || e.status === 405)) {
371
- this.mode = 'poll';
372
- longpollRefused = true;
373
- this.o.log?.(`change stream not available (${e.status}); polling every ${Math.round(poll / 1000)} s`);
374
- continue;
375
- }
376
- if (e instanceof HubError && e.status === 401) {
377
- this.o.log?.('change stream: key refused (401); stopping');
378
- return;
379
- }
380
- errors++;
381
- if (errors === 1)
382
- this.o.log?.(`change stream: ${String(e?.message ?? e).slice(0, 200)}; retrying`);
383
- await this.fire();
384
- await sleep(poll);
336
+ await this.fire();
337
+ await sleep(poll);
338
+ if (this.o.socket && ++polls >= retryPolls) {
339
+ polls = 0;
340
+ this.wsDue = true;
385
341
  }
386
342
  }
387
343
  }
package/dist/src/cli.d.ts CHANGED
@@ -8,7 +8,6 @@ export interface Io {
8
8
  out: (s: string) => void;
9
9
  err: (s: string) => void;
10
10
  fetch?: FetchLike;
11
- readPassword?: (prompt: string) => Promise<string>;
12
11
  /** `ach open`: opens a URL in the browser (tests inject a fake). */
13
12
  openUrl?: (url: string) => Promise<void>;
14
13
  /** `ach doctor` status commands (tests inject a fake). */
package/dist/src/cli.js CHANGED
@@ -98,19 +98,22 @@ export async function runOpener(cmd, args, spawn) {
98
98
  async function exchangeLink(link, hubOpt, io, cmd) {
99
99
  const tok = loginTokenOf(link);
100
100
  const fromUrl = /^(https?:\/\/[^/#?\s]+)/.exec(link.trim())?.[1];
101
- let saved;
101
+ let cfg;
102
102
  try {
103
- saved = loadConfig(io.env).hub;
103
+ cfg = loadConfig(io.env);
104
104
  }
105
105
  catch {
106
- saved = undefined;
106
+ cfg = undefined;
107
107
  }
108
+ const saved = cfg?.hub;
108
109
  const hub = (hubOpt ?? fromUrl ?? saved ?? '').replace(/\/+$/, '');
109
110
  if (!hub)
110
111
  throw new UsageError(`${cmd}: --hub <url> is required when --link is a bare token`);
111
112
  if (!hubTransportOk(hub))
112
113
  throw new UsageError(`${cmd}: ${hub} is plain http:// to a host that is not this machine; use the https:// link`);
113
- const c = new Client({ hub, cfId: io.env.ACH_CF_ID, cfSecret: io.env.ACH_CF_SECRET, fetch: io.fetch });
114
+ // the front-door pass this machine keeps for that hub (a hub behind Cloudflare Access refuses the exchange without it)
115
+ const samehub = !!saved && saved.replace(/\/+$/, '') === hub;
116
+ const c = new Client({ hub, cfId: io.env.ACH_CF_ID || (samehub ? cfg?.cfId : undefined), cfSecret: io.env.ACH_CF_SECRET || (samehub ? cfg?.cfSecret : undefined), fetch: io.fetch });
114
117
  const r = await c.exchangeLoginLink(tok);
115
118
  if (!r?.token)
116
119
  throw new Error('login failed: no token returned');
@@ -123,51 +126,6 @@ export function loginTokenOf(s) {
123
126
  throw new UsageError('not a login link (expected .../#/login/ach_lt_...)');
124
127
  return m[1];
125
128
  }
126
- function readPasswordTty(prompt) {
127
- return new Promise((res, rej) => {
128
- const stdin = process.stdin;
129
- if (!stdin.isTTY) {
130
- // non-interactive: the password is the first line of stdin (`echo "$PW" | ach admin login ...`)
131
- let buf = '';
132
- stdin.setEncoding('utf8');
133
- stdin.on('data', (d) => (buf += d));
134
- stdin.once('end', () => {
135
- const line = buf.split(/\r?\n/)[0] ?? '';
136
- if (line)
137
- res(line);
138
- else
139
- rej(new Error('no password on stdin (pipe it: echo "$PASSWORD" | ach admin login --hub <url> --email <e>, or set ACH_ADMIN_PASSWORD)'));
140
- });
141
- stdin.once('error', rej);
142
- stdin.resume();
143
- return;
144
- }
145
- process.stderr.write(prompt);
146
- stdin.setRawMode(true);
147
- stdin.resume();
148
- let buf = '';
149
- const onData = (d) => {
150
- for (const ch of d.toString('utf8')) {
151
- if (ch === '\r' || ch === '\n') {
152
- stdin.setRawMode(false);
153
- stdin.pause();
154
- stdin.off('data', onData);
155
- process.stderr.write('\n');
156
- return res(buf);
157
- }
158
- if (ch === '\u0003') {
159
- stdin.setRawMode(false);
160
- return rej(new Error('interrupted'));
161
- }
162
- if (ch === '\u007f')
163
- buf = buf.slice(0, -1);
164
- else
165
- buf += ch;
166
- }
167
- };
168
- stdin.on('data', onData);
169
- });
170
- }
171
129
  /**
172
130
  * Commands a spawned agent (ACH_RUN_TOKEN set) may never run (S3): they would
173
131
  * re-point the CLI (login), act as a human (admin), start nested runners or
@@ -1124,17 +1082,7 @@ async function dispatch(p, io) {
1124
1082
  out(`logged in as ${r.record?.name || r.record?.email || r.record?.id}${r.record?.is_admin ? ' (admin)' : ''}`);
1125
1083
  return 0;
1126
1084
  }
1127
- const hub = need(p, 'hub').replace(/\/+$/, '');
1128
- const email = need(p, 'email');
1129
- const password = env.ACH_ADMIN_PASSWORD || (await (io.readPassword ?? readPasswordTty)('Password: '));
1130
- const c = new Client({ hub, cfId: env.ACH_CF_ID, cfSecret: env.ACH_CF_SECRET, fetch: io.fetch });
1131
- const r = await c.userLogin(email, password);
1132
- if (!r?.token)
1133
- throw new Error('login failed: no token returned');
1134
- writeFileConfig({ ...readFileConfig(env), hub: readFileConfig(env).hub ?? hub }, env);
1135
- saveAdminToken(r.token, env, email);
1136
- out(`logged in as ${email}${r.record?.is_admin ? ' (admin)' : ''}`);
1137
- return 0;
1085
+ throw new UsageError('admin login: --link <login link> is required (the panel\'s menu makes one for you; an admin makes one with `ach admin login-link <user>`)');
1138
1086
  }
1139
1087
  case 'admin create-user': {
1140
1088
  const a = admin();
@@ -1290,13 +1238,8 @@ async function dispatch(p, io) {
1290
1238
  patch.notify_webhook = v['notify-webhook'];
1291
1239
  if (str(v.name))
1292
1240
  patch.name = str(v.name);
1293
- if (v.password) {
1294
- const rp = io.readPassword ?? readPasswordTty;
1295
- patch.old_password = env.ACH_ADMIN_PASSWORD || (await rp('Current password: '));
1296
- patch.password = env.ACH_NEW_PASSWORD || (await rp('New password: '));
1297
- }
1298
1241
  if (!Object.keys(patch).length)
1299
- throw new UsageError('admin profile: nothing to change (--notify-webhook, --name, --password)');
1242
+ throw new UsageError('admin profile: nothing to change (--notify-webhook, --name)');
1300
1243
  const r = await admin().patchMe(patch);
1301
1244
  out(v.json ? r : `updated ${r.email}${r.notify_webhook_set ? ' (notifications: own webhook)' : ''}`);
1302
1245
  return 0;
@@ -283,7 +283,7 @@ export interface LoginLink {
283
283
  export type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
284
284
  export interface ClientOptions {
285
285
  hub: string;
286
- /** Agent key (ach_sk_...), run token (ach_rt_...) or a PocketBase user token. */
286
+ /** Agent key (ach_sk_...), run token (ach_rt_...) or a person's session token (from a login link). */
287
287
  token?: string;
288
288
  cfId?: string;
289
289
  cfSecret?: string;
@@ -504,7 +504,7 @@ export declare class Client {
504
504
  adminPatchUser(ref: string, patch: Record<string, unknown>): Promise<any>;
505
505
  /** §15 `ach open`: the agent key mints a 60 s single-use login link for its owner. */
506
506
  loginLink(): Promise<LoginLink>;
507
- /** Exchange a one-time login link token for a PocketBase user session. */
507
+ /** Exchange a one-time login link token for a person's session. */
508
508
  exchangeLoginLink(token: string): Promise<{
509
509
  token: string;
510
510
  record: any;
@@ -517,11 +517,6 @@ export declare class Client {
517
517
  cf_token?: boolean;
518
518
  }): Promise<any>;
519
519
  adminPatchAgent(agentId: string, patch: Record<string, unknown>): Promise<any>;
520
- /** PocketBase built-in password auth for humans. */
521
- userLogin(email: string, password: string): Promise<{
522
- token: string;
523
- record: any;
524
- }>;
525
520
  }
526
521
  /** Inbox message entries are `{message, task}`; flatten to the message (task id stays in `message.task`). */
527
522
  export declare function flatMsgs(v: unknown): Message[];
@@ -96,7 +96,7 @@ export class Client {
96
96
  headers(json = true) {
97
97
  const h = { ...CLIENT_HEADERS, ...(json ? { 'content-type': 'application/json', accept: 'application/json' } : { accept: 'application/json' }) };
98
98
  if (this.token) {
99
- // Agent keys and run tokens use Bearer; PocketBase user tokens are sent raw.
99
+ // Agent keys and run tokens use Bearer; a person's session token is sent as it is.
100
100
  h.authorization = /^ach_(sk|rt)_/.test(this.token) ? `Bearer ${this.token}` : this.token;
101
101
  }
102
102
  if (this.cfId && this.cfSecret) {
@@ -428,7 +428,7 @@ export class Client {
428
428
  async loginLink() {
429
429
  return this.ach('POST', '/login-link', {});
430
430
  }
431
- /** Exchange a one-time login link token for a PocketBase user session. */
431
+ /** Exchange a one-time login link token for a person's session. */
432
432
  async exchangeLoginLink(token) {
433
433
  return this.ach('POST', '/auth/link', { token });
434
434
  }
@@ -446,10 +446,6 @@ export class Client {
446
446
  async adminPatchAgent(agentId, patch) {
447
447
  return this.ach('PATCH', `/admin/agents/${enc(agentId)}`, patch);
448
448
  }
449
- /** PocketBase built-in password auth for humans. */
450
- async userLogin(email, password) {
451
- return this.request('POST', '/api/collections/users/auth-with-password', { identity: email, password });
452
- }
453
449
  }
454
450
  /** Inbox message entries are `{message, task}`; flatten to the message (task id stays in `message.task`). */
455
451
  export function flatMsgs(v) {
@@ -416,7 +416,7 @@ export class DeskNotifier {
416
416
  void import('./attention.js').then(({ ChangeFeed }) => {
417
417
  if (this.stopped)
418
418
  return;
419
- const feed = new ChangeFeed({ request: (m, p) => this.o.request(m, p), socket: this.o.socket, onChange: () => this.tick(), pollMs: this.o.intervalMs, log: this.o.log });
419
+ const feed = new ChangeFeed({ socket: this.o.socket, onChange: () => this.tick(), pollMs: this.o.intervalMs, log: this.o.log });
420
420
  this.feed = feed;
421
421
  void feed.run();
422
422
  });
@@ -39,7 +39,7 @@ export declare function openKeychain(home: string, account?: string): KeychainKe
39
39
  /** Keychain unless ACH_KEYSTORE=file or the keychain is unavailable. */
40
40
  export declare function openKeyStore(home: string, env?: NodeJS.ProcessEnv): KeyStore;
41
41
  /**
42
- * B1: the human session of `ach admin` (a PocketBase user token). Kept out of
42
+ * B1: the human session of `ach admin` (a person's session token). Kept out of
43
43
  * config.json: the OS keychain (account "admin-session:<home>"), else
44
44
  * <home>/admin-session (0600), a file the runner denies to spawned agents.
45
45
  */
@@ -75,7 +75,7 @@ export function openKeyStore(home, env = process.env) {
75
75
  return new FileKeyStore(keyFilePath(home));
76
76
  }
77
77
  /**
78
- * B1: the human session of `ach admin` (a PocketBase user token). Kept out of
78
+ * B1: the human session of `ach admin` (a person's session token). Kept out of
79
79
  * config.json: the OS keychain (account "admin-session:<home>"), else
80
80
  * <home>/admin-session (0600), a file the runner denies to spawned agents.
81
81
  */
package/dist/src/mcp.js CHANGED
@@ -470,7 +470,7 @@ export function startSessionNotices(server, client, o = {}) {
470
470
  gapMs: o.gapMs,
471
471
  log: o.log,
472
472
  });
473
- const feed = new ChangeFeed({ request, socket: () => client.eventsSocket(), onChange: () => notifier.check(), log: o.log, ...o.feed });
473
+ const feed = new ChangeFeed({ socket: () => client.eventsSocket(), onChange: () => notifier.check(), log: o.log, ...o.feed });
474
474
  const done = feed.run();
475
475
  // a session cannot be restarted from outside: when the hub names a newer ach, say so once (see selfupdate.ts)
476
476
  const stale = startStaleNotice({
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "3.7.0";
1
+ export declare const VERSION = "3.7.2";
2
2
  /** The npm package (scripts/release.mjs keeps it and VERSION in step with package.json). */
3
3
  export declare const PKG_NAME = "@koalaauto/agentchannel";
4
4
  /**
@@ -1,4 +1,4 @@
1
- export const VERSION = '3.7.0';
1
+ export const VERSION = '3.7.2';
2
2
  /** The npm package (scripts/release.mjs keeps it and VERSION in step with package.json). */
3
3
  export const PKG_NAME = '@koalaauto/agentchannel';
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koalaauto/agentchannel",
3
- "version": "3.7.0",
3
+ "version": "3.7.2",
4
4
  "description": "Hand work between your team's agents (Claude Code, Codex) and follow it in one panel: the ach CLI, MCP server and runner for AgentChannel v2",
5
5
  "keywords": [
6
6
  "agentchannel",