@koalaauto/agentchannel 3.10.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +5 -1
  2. package/dist/src/adapters/claude.d.ts +19 -0
  3. package/dist/src/adapters/claude.js +52 -1
  4. package/dist/src/adapters/codex.d.ts +11 -0
  5. package/dist/src/adapters/codex.js +34 -2
  6. package/dist/src/adapters/fake.d.ts +10 -0
  7. package/dist/src/adapters/fake.js +9 -0
  8. package/dist/src/adapters/paths.d.ts +9 -1
  9. package/dist/src/adapters/paths.js +28 -3
  10. package/dist/src/adapters/types.d.ts +3 -2
  11. package/dist/src/args.d.ts +1 -1
  12. package/dist/src/args.js +18 -9
  13. package/dist/src/cli.js +81 -17
  14. package/dist/src/client.d.ts +48 -4
  15. package/dist/src/client.js +41 -5
  16. package/dist/src/desknotify.d.ts +2 -2
  17. package/dist/src/desknotify.js +2 -2
  18. package/dist/src/doctor.d.ts +4 -0
  19. package/dist/src/doctor.js +12 -0
  20. package/dist/src/envcheck.d.ts +22 -0
  21. package/dist/src/envcheck.js +98 -0
  22. package/dist/src/mcp.d.ts +1 -1
  23. package/dist/src/mcp.js +24 -12
  24. package/dist/src/names.d.ts +6 -1
  25. package/dist/src/names.js +15 -2
  26. package/dist/src/onboard.d.ts +11 -2
  27. package/dist/src/onboard.js +54 -2
  28. package/dist/src/prompts.d.ts +3 -3
  29. package/dist/src/prompts.js +4 -4
  30. package/dist/src/redact.d.ts +5 -0
  31. package/dist/src/redact.js +41 -0
  32. package/dist/src/runner/runner.d.ts +32 -1
  33. package/dist/src/runner/runner.js +214 -20
  34. package/dist/src/sandboxcheck.d.ts +97 -0
  35. package/dist/src/sandboxcheck.js +236 -0
  36. package/dist/src/service.d.ts +9 -3
  37. package/dist/src/service.js +39 -1
  38. package/dist/src/trace.d.ts +51 -0
  39. package/dist/src/trace.js +137 -0
  40. package/dist/src/version.d.ts +1 -1
  41. package/dist/src/version.js +1 -1
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,10 @@ installed or changed.
42
42
  --interactive-first` for the current directory; `--notify` only on a machine
43
43
  with a desktop), and starts it. Through npx the service runs the same pinned
44
44
  npx spec as the MCP entry, so nothing is installed globally.
45
+ - When the agent tool's sandbox does not work on this machine (on Linux: bubblewrap and
46
+ socat missing, or Ubuntu's AppArmor rule), it shows the commands that fix it and runs
47
+ them only after you say yes in a terminal (they use sudo). Without a terminal it only
48
+ prints them. The background helper takes no tasks until the sandbox works.
45
49
  - `--dry-run` prints the plan; the invite is not used and nothing is written.
46
50
  - `--yes` skips the question.
47
51
 
@@ -115,7 +119,7 @@ reminds you with one line, never the message itself:
115
119
  terminal-notifier / osascript without buttons), Linux `notify-send` with
116
120
  `--action` (libnotify 0.7.9+), Windows toast buttons. A click never answers for you:
117
121
  it opens the panel at the decision with that option preselected
118
- (`#/t/T-12/m/<id>?pick=<i>`), and you press Send there. The helper runs with
122
+ (`#/t/TASK-12/m/<id>?pick=<i>`), and you press Send there. The helper runs with
119
123
  your agent's key, which must not stand in for your own reply.
120
124
 
121
125
  At most one reminder per 10 minutes; changes inside the window become one
@@ -54,6 +54,19 @@ export declare function resultError(m: {
54
54
  errors?: string[];
55
55
  result?: string;
56
56
  }): string;
57
+ /** A tool_result's content (a string, or blocks of text) as text. */
58
+ export declare function toolResultText(content: unknown): string;
59
+ /**
60
+ * Ask for the model's thinking as a summary, so the execution trace can say why it decided what it did (by default the SDK
61
+ * returns it empty). Adaptive thinking is what current models take; a model pinned with --model that is older than that is
62
+ * left alone, and ACH_TRACE_THINKING=off turns it off.
63
+ */
64
+ export declare function thinkingFor(model: string | undefined, env?: Record<string, string | undefined>): {
65
+ thinking?: {
66
+ type: 'adaptive';
67
+ display: 'summarized';
68
+ };
69
+ };
57
70
  export interface ClaudeAdapterOptions {
58
71
  model?: string;
59
72
  }
@@ -61,5 +74,11 @@ export declare class ClaudeAdapter implements RuntimeAdapter {
61
74
  private o;
62
75
  readonly name = "claude";
63
76
  constructor(o?: ClaudeAdapterOptions);
77
+ /**
78
+ * Run, asking for the thinking summary. The default model cannot be seen from here (Claude Code settings may select an older
79
+ * one that takes no adaptive thinking): if the run dies at the start with an error about the thinking option, before the agent did
80
+ * anything, it is run once more without it, and this process stops asking.
81
+ */
64
82
  run(opts: AdapterRunOptions): Promise<AdapterResult>;
83
+ private runOnce;
65
84
  }
@@ -143,13 +143,59 @@ export function resultError(m) {
143
143
  return m.subtype;
144
144
  return 'the run ended with an error';
145
145
  }
146
+ /** Set when the API refused the thinking option once: no later run in this process asks for it again. */
147
+ let thinkingRejected = false;
148
+ /** A tool_result's content (a string, or blocks of text) as text. */
149
+ export function toolResultText(content) {
150
+ if (typeof content === 'string')
151
+ return content;
152
+ if (!Array.isArray(content))
153
+ return '';
154
+ return content.map((c) => (typeof c === 'string' ? c : c?.type === 'text' && typeof c.text === 'string' ? c.text : c?.type === 'image' ? '[image]' : '')).filter(Boolean).join('\n');
155
+ }
156
+ /**
157
+ * Ask for the model's thinking as a summary, so the execution trace can say why it decided what it did (by default the SDK
158
+ * returns it empty). Adaptive thinking is what current models take; a model pinned with --model that is older than that is
159
+ * left alone, and ACH_TRACE_THINKING=off turns it off.
160
+ */
161
+ export function thinkingFor(model, env = {}) {
162
+ if ((env.ACH_TRACE_THINKING ?? process.env.ACH_TRACE_THINKING) === 'off' || thinkingRejected)
163
+ return {};
164
+ // the model Claude Code will use: the one pinned here, else the one the environment selects
165
+ model = model ?? env.ANTHROPIC_MODEL ?? process.env.ANTHROPIC_MODEL;
166
+ if (model) {
167
+ // "claude-opus-4-6", "claude-sonnet-5-5", "claude-3-5-sonnet-...": the version after or before the family name
168
+ const m = /(?:opus|sonnet|haiku|fable)[-.](\d{1,2})(?!\d)(?:[-.](\d)(?!\d))?/i.exec(model) ?? /claude[-.](\d+)(?:[-.](\d))?[-.](?:opus|sonnet|haiku|fable)/i.exec(model);
169
+ const major = m ? Number(m[1]) : 0;
170
+ const minor = m && m[2] ? Number(m[2]) : 0;
171
+ if (!(major > 4 || (major === 4 && minor >= 6)))
172
+ return {};
173
+ }
174
+ return { thinking: { type: 'adaptive', display: 'summarized' } };
175
+ }
146
176
  export class ClaudeAdapter {
147
177
  o;
148
178
  name = 'claude';
149
179
  constructor(o = {}) {
150
180
  this.o = o;
151
181
  }
182
+ /**
183
+ * Run, asking for the thinking summary. The default model cannot be seen from here (Claude Code settings may select an older
184
+ * one that takes no adaptive thinking): if the run dies at the start with an error about the thinking option, before the agent did
185
+ * anything, it is run once more without it, and this process stops asking.
186
+ */
152
187
  async run(opts) {
188
+ let progressed = false;
189
+ const first = await this.runOnce({ ...opts, onEvent: (e) => { if (e.type === 'text' || e.type === 'tool_start')
190
+ progressed = true; opts.onEvent(e); } }, true);
191
+ if (!first.ok && !progressed && !opts.signal.aborted && Object.keys(thinkingFor(this.o.model, opts.env)).length && /thinking|adaptive/i.test(first.error ?? '')) {
192
+ thinkingRejected = true;
193
+ opts.onEvent({ type: 'error', data: 'the model does not take the thinking option; running without it' });
194
+ return this.runOnce(opts, false);
195
+ }
196
+ return first;
197
+ }
198
+ async runOnce(opts, withThinking) {
153
199
  const { query } = await import('@anthropic-ai/claude-agent-sdk');
154
200
  const abortController = new AbortController();
155
201
  const onAbort = () => abortController.abort();
@@ -163,6 +209,7 @@ export class ClaudeAdapter {
163
209
  abortController,
164
210
  ...(opts.resume ? { resume: opts.resume } : {}),
165
211
  ...(this.o.model ? { model: this.o.model } : {}),
212
+ ...(withThinking ? thinkingFor(this.o.model, opts.env) : {}),
166
213
  ...claudePermOptions(opts.perm, {
167
214
  cwd: opts.cwd,
168
215
  hub: opts.hub,
@@ -199,6 +246,10 @@ export class ClaudeAdapter {
199
246
  lastText.push(b.text);
200
247
  opts.onEvent({ type: 'text', data: b.text });
201
248
  }
249
+ else if (b.type === 'thinking' && typeof b.thinking === 'string' && b.thinking.trim()) {
250
+ // the model's own summary of its reasoning (empty when the model hides it): for the execution trace
251
+ opts.onEvent({ type: 'thinking', data: b.thinking });
252
+ }
202
253
  else if (b.type === 'tool_use') {
203
254
  opts.onEvent({ type: 'tool_start', data: { id: b.id, name: b.name, input: b.input } });
204
255
  }
@@ -208,7 +259,7 @@ export class ClaudeAdapter {
208
259
  const content = anyM.message?.content;
209
260
  for (const b of (Array.isArray(content) ? content : [])) {
210
261
  if (b?.type === 'tool_result')
211
- opts.onEvent({ type: 'tool_end', data: { id: b.tool_use_id } });
262
+ opts.onEvent({ type: 'tool_end', data: { id: b.tool_use_id, is_error: !!b.is_error, output: toolResultText(b.content) } });
212
263
  }
213
264
  }
214
265
  else if (m.type === 'result') {
@@ -61,6 +61,17 @@ export declare function killCodexTree(cwd: string, o?: {
61
61
  kill?: (pid: number, sig: NodeJS.Signals) => void;
62
62
  graceMs?: number;
63
63
  }): number[];
64
+ /** What a Codex item is, for the execution trace: its name and the command, the files or the arguments. */
65
+ export declare function codexToolStart(item: any): {
66
+ name: string;
67
+ input: unknown;
68
+ };
69
+ /** How a Codex item ended: its exit code, whether it failed, what it printed. */
70
+ export declare function codexToolEnd(item: any): {
71
+ is_error?: boolean;
72
+ exit?: number;
73
+ output?: string;
74
+ };
64
75
  /** Codex runtime adapter (@openai/codex-sdk). Cancel: see killCodexTree. */
65
76
  export declare class CodexAdapter implements RuntimeAdapter {
66
77
  private o;
@@ -139,6 +139,34 @@ export function killCodexTree(cwd, o = {}) {
139
139
  setTimeout(() => send('SIGKILL'), o.graceMs ?? KILL_GRACE_MS).unref();
140
140
  return pids;
141
141
  }
142
+ /** What a Codex item is, for the execution trace: its name and the command, the files or the arguments. */
143
+ export function codexToolStart(item) {
144
+ switch (item?.type) {
145
+ case 'command_execution':
146
+ return { name: 'shell', input: item.command };
147
+ case 'file_change':
148
+ return { name: 'file_change', input: (item.changes ?? []).map((c) => `${c.kind} ${c.path}`).join('\n') };
149
+ case 'mcp_tool_call':
150
+ return { name: `${item.server}.${item.tool}`, input: item.arguments };
151
+ case 'web_search':
152
+ return { name: 'web_search', input: item.query };
153
+ default:
154
+ return { name: String(item?.type ?? 'tool'), input: '' };
155
+ }
156
+ }
157
+ /** How a Codex item ended: its exit code, whether it failed, what it printed. */
158
+ export function codexToolEnd(item) {
159
+ switch (item?.type) {
160
+ case 'command_execution':
161
+ return { ...(typeof item.exit_code === 'number' ? { exit: item.exit_code } : {}), is_error: item.status === 'failed' || (typeof item.exit_code === 'number' && item.exit_code !== 0), output: item.aggregated_output ?? '' };
162
+ case 'file_change':
163
+ return { is_error: item.status === 'failed' };
164
+ case 'mcp_tool_call':
165
+ return { is_error: item.status === 'failed' || !!item.error, output: item.error?.message ?? (item.result ? JSON.stringify(item.result.structured_content ?? item.result.content) : '') };
166
+ default:
167
+ return {};
168
+ }
169
+ }
142
170
  /** Codex runtime adapter (@openai/codex-sdk). Cancel: see killCodexTree. */
143
171
  export class CodexAdapter {
144
172
  o;
@@ -178,11 +206,15 @@ export class CodexAdapter {
178
206
  break;
179
207
  case 'item.completed':
180
208
  if (ev.item.type !== 'agent_message' && ev.item.type !== 'reasoning')
181
- opts.onEvent({ type: 'tool_end', data: { id: ev.item.id } });
209
+ opts.onEvent({ type: 'tool_end', data: { id: ev.item.id, ...codexToolEnd(ev.item) } });
182
210
  if (ev.item.type === 'agent_message') {
183
211
  finalText = ev.item.text;
184
212
  opts.onEvent({ type: 'text', data: ev.item.text });
185
213
  }
214
+ else if (ev.item.type === 'reasoning') {
215
+ if (ev.item.text?.trim())
216
+ opts.onEvent({ type: 'thinking', data: ev.item.text });
217
+ }
186
218
  else if (ev.item.type === 'error') {
187
219
  opts.onEvent({ type: 'error', data: ev.item.message });
188
220
  }
@@ -192,7 +224,7 @@ export class CodexAdapter {
192
224
  break;
193
225
  case 'item.started':
194
226
  if (ev.item.type !== 'agent_message' && ev.item.type !== 'reasoning') {
195
- opts.onEvent({ type: 'tool_start', data: { id: ev.item.id, type: ev.item.type } });
227
+ opts.onEvent({ type: 'tool_start', data: { id: ev.item.id, type: ev.item.type, ...codexToolStart(ev.item) } });
196
228
  }
197
229
  else
198
230
  opts.onEvent({ type: 'tool', data: { type: ev.type } });
@@ -20,6 +20,16 @@ export type FakeStep = {
20
20
  fail: string;
21
21
  } | {
22
22
  cost: number;
23
+ } | {
24
+ thinking: string;
25
+ } | {
26
+ call: {
27
+ name: string;
28
+ input: unknown;
29
+ output?: string;
30
+ is_error?: boolean;
31
+ exit?: number;
32
+ };
23
33
  } | {
24
34
  toolStart: string;
25
35
  } | {
@@ -83,6 +83,15 @@ export class FakeAdapter {
83
83
  cost = step.cost;
84
84
  opts.onEvent({ type: 'usage', data: { costUsd: cost } });
85
85
  }
86
+ else if ('thinking' in step) {
87
+ opts.onEvent({ type: 'thinking', data: step.thinking });
88
+ }
89
+ else if ('call' in step) {
90
+ // one whole tool call, with what it printed (for the execution trace)
91
+ const id = `call-${Math.random().toString(36).slice(2, 8)}`;
92
+ opts.onEvent({ type: 'tool_start', data: { id, name: step.call.name, input: step.call.input } });
93
+ opts.onEvent({ type: 'tool_end', data: { id, is_error: step.call.is_error, exit: step.call.exit, output: step.call.output } });
94
+ }
86
95
  else if ('toolStart' in step) {
87
96
  opts.onEvent({ type: 'tool_start', data: { id: step.toolStart } });
88
97
  }
@@ -5,8 +5,16 @@
5
5
  */
6
6
  export declare function realish(p: string, depth?: number): string;
7
7
  export declare function isInside(root: string, p: string): boolean;
8
- /** Root of this npm package (dist/src/adapters/paths.js -> package dir): the `ach` shim runs code from here. */
8
+ /**
9
+ * Root of this npm package (dist/src/adapters/paths.js -> package dir): the `ach` shim runs code from here. The real path: an
10
+ * `npm link` or a symlinked install is read through its link, but a sandbox allows the place the files are.
11
+ */
9
12
  export declare function packageRoot(): string;
13
+ /**
14
+ * The node_modules folders above the package that Node searches for its dependencies. A global install keeps them inside the
15
+ * package, but `npm link` and a monorepo checkout keep them beside or above it: the agent's `ach` must read them too.
16
+ */
17
+ export declare function dependencyRoots(from?: string): string[];
10
18
  /**
11
19
  * Paths under $HOME a spawned agent may read besides its cwd (S4): the node
12
20
  * install, npm and git config, and this package (the `ach` CLI). The git
@@ -36,9 +36,33 @@ export function isInside(root, p) {
36
36
  const rel = relative(realish(root), realish(p));
37
37
  return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
38
38
  }
39
- /** Root of this npm package (dist/src/adapters/paths.js -> package dir): the `ach` shim runs code from here. */
39
+ /**
40
+ * Root of this npm package (dist/src/adapters/paths.js -> package dir): the `ach` shim runs code from here. The real path: an
41
+ * `npm link` or a symlinked install is read through its link, but a sandbox allows the place the files are.
42
+ */
40
43
  export function packageRoot() {
41
- return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
44
+ const p = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
45
+ try {
46
+ return realpathSync(p);
47
+ }
48
+ catch {
49
+ return p;
50
+ }
51
+ }
52
+ /**
53
+ * The node_modules folders above the package that Node searches for its dependencies. A global install keeps them inside the
54
+ * package, but `npm link` and a monorepo checkout keep them beside or above it: the agent's `ach` must read them too.
55
+ */
56
+ export function dependencyRoots(from = packageRoot()) {
57
+ const out = [];
58
+ for (let d = dirname(from);; d = dirname(d)) {
59
+ const nm = join(d, 'node_modules');
60
+ if (existsSync(nm))
61
+ out.push(realish(nm));
62
+ if (dirname(d) === d)
63
+ break;
64
+ }
65
+ return out;
42
66
  }
43
67
  /**
44
68
  * Paths under $HOME a spawned agent may read besides its cwd (S4): the node
@@ -46,10 +70,11 @@ export function packageRoot() {
46
70
  * common dir of the worktree is added by the caller.
47
71
  */
48
72
  export function toolchainReadRoots(home = homedir()) {
49
- const nodePrefix = resolve(dirname(process.execPath), '..');
73
+ const nodePrefix = realish(resolve(dirname(process.execPath), '..'));
50
74
  return [
51
75
  nodePrefix,
52
76
  packageRoot(),
77
+ ...dependencyRoots(),
53
78
  join(home, '.gitconfig'),
54
79
  join(home, '.config', 'git'),
55
80
  join(home, '.npmrc'),
@@ -1,9 +1,10 @@
1
1
  import type { Perm } from '../perm.js';
2
2
  /**
3
3
  * `tool_start` / `tool_end` (data: {id}) bracket one tool call; the runner's
4
- * idle watchdog is suspended while any call is in flight (C6).
4
+ * idle watchdog is suspended while any call is in flight (C6). `tool_start` data: {id, name, input}; `tool_end` data: {id, is_error?,
5
+ * exit?, output?} (what the call printed, for the execution trace); `thinking` data: the model's own summary of its reasoning.
5
6
  */
6
- export type AdapterEventType = 'text' | 'tool' | 'tool_start' | 'tool_end' | 'session' | 'usage' | 'error';
7
+ export type AdapterEventType = 'text' | 'thinking' | 'tool' | 'tool_start' | 'tool_end' | 'session' | 'usage' | 'error';
7
8
  export interface AdapterEvent {
8
9
  type: AdapterEventType;
9
10
  data: any;
@@ -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>\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";
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 [--review] (the task waits for acceptance when its worker is done: ach accept or ach request-changes)\n ach accept <task> [<note>] | ach request-changes <task> \"<what to change>\" (the sender's side judges work sent with --review)\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|TASK-128> (lists attachments with download URLs) | ach inbox\n Every task argument also takes the TASK-<seq> number shown in the panel (TASK-128; T-128 still works).\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 <TASK-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 path] [--folder f] [--title t] | archive <#name> | unarchive <#name>]\n ach admin channels move <#old> <#new-path> (a channel is a path like reela/onair, at most 3 levels; the old path keeps working)\n ach admin channels private|team <#name> [--cascade] (private: members and admins only; never wider than the channel above)\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 memory-maintainers [<agent> ...] (who keeps Memory current; no name: show it. Default: the admin agent with no setup problem seen most recently)\n ach admin memory-upkeep [<#channel>] (queue Memory upkeep now; also restarts one paused after repeated failures)\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
@@ -13,8 +13,10 @@ export const COMMANDS = {
13
13
  agents: { ...json },
14
14
  send: {
15
15
  channel: s, to: s, title: s, priority: s, purpose: s, money: s,
16
- 'handoff-file': s, handoff: s, depends: sm, base: s, repo: s, 'read-repo': sm, links: s, 'key-question': s, attach: sm, 'attach-url': sm, ...json,
16
+ 'handoff-file': s, handoff: s, depends: sm, base: s, repo: s, 'read-repo': sm, links: s, 'key-question': s, review: b, attach: sm, 'attach-url': sm, ...json,
17
17
  },
18
+ accept: { task: s, ...json },
19
+ 'request-changes': { task: s, ...json },
18
20
  handoff: { file: s, note: s, priority: s, purpose: s, money: s, 'key-question': s, attach: sm, 'attach-url': sm, ...json },
19
21
  task: { ...json },
20
22
  'schedule add': {
@@ -43,8 +45,8 @@ export const COMMANDS = {
43
45
  'decide-request': { task: s, to: s, attach: sm, 'attach-url': sm, option: sm, recommend: s, ...json },
44
46
  progress: { task: s, attach: sm, 'attach-url': sm, ...json },
45
47
  note: { task: s, attach: sm, 'attach-url': sm, ...json },
46
- complete: { task: s, 'result-file': s, cost: s, 'run-id': s, attach: sm, 'attach-url': sm, ...json },
47
- fail: { task: s, 'run-id': s, ...json },
48
+ complete: { task: s, 'result-file': s, cost: s, 'run-id': s, 'owner-action': s, attach: sm, 'attach-url': sm, ...json },
49
+ fail: { task: s, 'run-id': s, 'owner-action': s, ...json },
48
50
  claim: { task: s, ...json },
49
51
  cancel: { reason: s, ...json },
50
52
  run: {
@@ -66,10 +68,12 @@ export const COMMANDS = {
66
68
  'admin rotate-key': { 'cf-token': b, ...json },
67
69
  'admin set': { active: s, perm: s, budget: s, description: s, 'accept-from': s, 'net-allow': s, ...json },
68
70
  'admin decide': { task: s, message: s, attach: sm, 'attach-url': sm, ...json },
69
- 'admin channels': { name: s, title: s, description: s, add: s, remove: s, ...json },
71
+ 'admin channels': { name: s, folder: s, title: s, description: s, add: s, remove: s, cascade: b, ...json },
70
72
  'admin embeds': { set: s, ...json },
71
73
  'admin storage': { ...json },
72
74
  'admin retention': { ...json },
75
+ 'admin memory-upkeep': { ...json },
76
+ 'admin memory-maintainers': { ...json },
73
77
  'admin link-hosts': { set: s, only: s, ...json },
74
78
  keep: { file: s, off: b, ...json },
75
79
  'admin note': { task: s, attach: sm, 'attach-url': sm, ...json },
@@ -144,10 +148,12 @@ Setup: npx -y @koalaauto/agentchannel@3 join <ach2.invite> [--name <n>] [--
144
148
  ach notify --test (shows a test desktop notification: checks the background helper can reach you)
145
149
  Tasks: ach send --to <agent> --title <t> --priority P0..P3 --purpose <p> --key-question <q> --money <m> --handoff-file <f>
146
150
  [--depends <id>] [--base <branch|commit>] [--repo <name>] [--read-repo <name>[@base] ...] [--attach <file> ...]
151
+ [--review] (the task waits for acceptance when its worker is done: ach accept or ach request-changes)
152
+ ach accept <task> [<note>] | ach request-changes <task> "<what to change>" (the sender's side judges work sent with --review)
147
153
  ach handoff <taskId> --note <change note> [--file <f>] [--priority P0..P3] [--purpose p] [--money m] [--key-question q] [--attach <file> ...]
148
154
  [--channel <name>] (default: the channel named like --repo, else #general)
149
- ach task <id|T-128> (lists attachments with download URLs) | ach inbox
150
- Every task argument also takes the T-<seq> number shown in the panel (T-128).
155
+ ach task <id|TASK-128> (lists attachments with download URLs) | ach inbox
156
+ Every task argument also takes the TASK-<seq> number shown in the panel (TASK-128; T-128 still works).
151
157
  ach download <taskId> [--out <dir>] (fetches every attachment; default ./ach-files-<taskId>)
152
158
  Recurring: ach schedule add --to <agent> --title <t> --money <m> --handoff-file <f> --every day|weekday|week|month --at HH:MM
153
159
  [--weekday 1..7] [--day 1..28] [--tz +08:00] [--purpose p] [--priority P0..P3] [--channel <name>]
@@ -161,7 +167,7 @@ Messages: ach ask [--task <id>] [--to <agent>] "<question>"
161
167
  ach answer [--task <id>] --reply-to <msgId> --evidence "<files/commands>" "<answer>"
162
168
  ach decide [--task <id>] <requestId> "<reply>" (interactive sessions only, with your agent key: your owner's reply, marked "via <agent>")
163
169
  ach decide [--task <id>] <requestId> --grant <id> --option <n> (a runner, within an authorization grant)
164
- Authorization: ach grant (--agent <name> | --person <who>) (--task <T-n> | --channel <name>) --cap <usd> [--until YYYY-MM-DD]
170
+ Authorization: ach grant (--agent <name> | --person <who>) (--task <TASK-n> | --channel <name>) --cap <usd> [--until YYYY-MM-DD]
165
171
  [--note "<guidance>"] [--requester <agent|person>] (your agent key: you, present, grant it; never a run token)
166
172
  ach grants | ach revoke <grantId>
167
173
  ach ask-human [--task <id>] [--to both|<person name|email>] "<one question>" --option "A|detail|$450" --option "B|detail|$0" [--recommend 0]
@@ -240,12 +246,15 @@ Onboarding: ach admin import github --org <org> [--since 60d] --dry-run [--out p
240
246
  ach admin profile [--notify-webhook <url>] [--name <n>] (your own account)
241
247
  ach admin decide --task <id> --message <msgId> "<decision>" [--attach <file> ...]
242
248
  ach admin note --task <id> "<text>" [--attach <file> ...] (a human note to the agents on a task)
243
- ach admin channels [list | create [--name n] [--title t] | archive <#name> | unarchive <#name>]
244
- ach admin channels private|team <#name> (private: members and admins only)
249
+ ach admin channels [list | create [--name path] [--folder f] [--title t] | archive <#name> | unarchive <#name>]
250
+ ach admin channels move <#old> <#new-path> (a channel is a path like reela/onair, at most 3 levels; the old path keeps working)
251
+ ach admin channels private|team <#name> [--cascade] (private: members and admins only; never wider than the channel above)
245
252
  ach admin channels members <#name> [--add a,b] [--remove c] (people by email or GitHub login)
246
253
  ach admin embeds [--set host1,host2] (embed allowlist; public suffixes are refused)
247
254
  ach admin wiki-lang [en|zh] (= ach memory lang: the language Memory pages are written in; default ACH_DEFAULT_LANG)
248
255
  ach admin storage (attachment and database size; per channel with direct uploads and retention)
256
+ ach admin memory-maintainers [<agent> ...] (who keeps Memory current; no name: show it. Default: the admin agent with no setup problem seen most recently)
257
+ ach admin memory-upkeep [<#channel>] (queue Memory upkeep now; also restarts one paused after repeated failures)
249
258
  ach admin retention <#name> [<days>] (delete attachments of finished tasks older than <days>; 0 = keep everything)
250
259
  ach admin link-hosts [--set h1,h2] [--only yes|no] (hosts whose linked images/videos the panel previews; --only rejects other links)
251
260
  ach keep <taskId> [--file <name>] [--off] (flag a task's attachments as deliverables: retention never deletes them)