@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
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * P4: `ach run --install-service` writes a macOS launchd agent or a Linux
3
3
  * systemd user unit that starts this runner at login and restarts it, then
4
- * loads it (startService).
4
+ * loads it (startService). design §27, Windows: a script in the user's Startup
5
+ * folder (no admin rights needed) that runs the runner hidden and restarts it.
5
6
  */
6
7
  export interface ServiceSpec {
7
- platform: 'darwin' | 'linux';
8
+ platform: 'darwin' | 'linux' | 'win32';
8
9
  agentName: string;
9
10
  /** What starts the CLI: node + cli.js of an install, or `npx -y <spec>` when it runs from the npx cache. */
10
11
  command: string[];
@@ -23,6 +24,11 @@ export interface ServiceFile {
23
24
  export declare function serviceLabel(agentName: string): string;
24
25
  export declare function launchdPlist(s: ServiceSpec): string;
25
26
  export declare function systemdUnit(s: ServiceSpec): string;
26
- export declare function serviceFile(s: ServiceSpec, dir?: string): ServiceFile;
27
+ /**
28
+ * Windows: a VBScript in the Startup folder. It runs the runner with no window, appends its output to the log, and
29
+ * starts it again 10 s after it exits, except after exit 78 (key rotated or agent deactivated: restarting cannot help).
30
+ */
31
+ export declare function windowsScript(s: ServiceSpec): string;
32
+ export declare function serviceFile(s: ServiceSpec, dir?: string, env?: NodeJS.ProcessEnv): ServiceFile;
27
33
  /** Write the unit file (mode 0644; it holds no secrets). */
28
34
  export declare function writeServiceFile(f: ServiceFile): void;
@@ -72,7 +72,45 @@ RestartPreventExitStatus=78
72
72
  WantedBy=default.target
73
73
  `;
74
74
  }
75
- export function serviceFile(s, dir) {
75
+ /** VBScript string literal. */
76
+ function vbs(s) {
77
+ return `"${s.replace(/"/g, '""')}"`;
78
+ }
79
+ /** cmd.exe argument: quoted unless plain. */
80
+ function cmdArg(s) {
81
+ return /^[A-Za-z0-9_.:\\/=@,+-]+$/.test(s) ? s : `"${s.replace(/"/g, '\\"')}"`;
82
+ }
83
+ /**
84
+ * Windows: a VBScript in the Startup folder. It runs the runner with no window, appends its output to the log, and
85
+ * starts it again 10 s after it exits, except after exit 78 (key rotated or agent deactivated: restarting cannot help).
86
+ */
87
+ export function windowsScript(s) {
88
+ const line = `${[...s.command, 'run', ...s.args].map(cmdArg).join(' ')} >> ${cmdArg(s.logFile)} 2>&1`;
89
+ const env = Object.entries(s.env).map(([k, v]) => `env(${vbs(k)}) = ${vbs(v)}`).join('\n');
90
+ return `' AgentChannel runner (agent ${s.agentName}): starts at login, restarts after it exits (not after exit 78).
91
+ Option Explicit
92
+ Dim sh, env, code
93
+ Set sh = CreateObject("WScript.Shell")
94
+ Set env = sh.Environment("Process")
95
+ ${env}
96
+ Do
97
+ code = sh.Run("cmd /c " & ${vbs(`"${line}"`)}, 0, True)
98
+ If code = 78 Then Exit Do
99
+ WScript.Sleep 10000
100
+ Loop
101
+ `;
102
+ }
103
+ export function serviceFile(s, dir, env = process.env) {
104
+ if (s.platform === 'win32') {
105
+ const name = `agentchannel-runner-${s.agentName.replace(/[^A-Za-z0-9-]/g, '-')}.vbs`;
106
+ const startup = join(env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
107
+ const path = join(dir ?? startup, name);
108
+ // stop: the script's own wscript, then the runner it started (its command line holds the same --dir)
109
+ const kill = (like) => `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '${like.replace(/'/g, "''")}' } | ForEach-Object { Invoke-CimMethod -InputObject $_ -MethodName Terminate | Out-Null }`;
110
+ const dirArg = s.args[s.args.indexOf('--dir') + 1] ?? '';
111
+ const stop = `powershell -NoProfile -Command "${kill(`*${name}*`)}; ${kill(`* run *--dir*${dirArg}*`)}"`;
112
+ return { path, content: windowsScript(s), start: [`${stop}`, `wscript "${path}"`], stop: [stop, `del "${path}"`] };
113
+ }
76
114
  if (s.platform === 'darwin') {
77
115
  const label = serviceLabel(s.agentName);
78
116
  const path = join(dir ?? join(homedir(), 'Library', 'LaunchAgents'), `${label}.plist`);
@@ -0,0 +1,51 @@
1
+ import type { AdapterEvent } from './adapters/types.js';
2
+ export type StepKind = 'thinking' | 'text' | 'tool' | 'mark';
3
+ export interface TraceStep {
4
+ /** Milliseconds since the run started. */
5
+ t: number;
6
+ kind: StepKind;
7
+ /** tool: the tool's name (Bash, Read, shell, ...); mark: what happened (task_completed, ...). */
8
+ name?: string;
9
+ /** tool: the command, the file, or the arguments, in a line or two. */
10
+ input?: string;
11
+ /** tool: how it ended. */
12
+ is_error?: boolean;
13
+ exit?: number;
14
+ /** tool: what it printed (cut); thinking / text: the words (cut). */
15
+ output?: string;
16
+ /** tool: how long it ran. */
17
+ ms?: number;
18
+ }
19
+ export interface Trace {
20
+ steps: TraceStep[];
21
+ /** Steps dropped from the middle of a long run. */
22
+ omitted: number;
23
+ }
24
+ /** Limits: each text field, the number of steps, and the whole trace as JSON. */
25
+ export declare const FIELD_MAX = 2000;
26
+ export declare const STEPS_MAX = 300;
27
+ export declare const TRACE_BYTES_MAX = 150000;
28
+ /** Cut to `max`, keeping the start and the end (where a command's error usually is). */
29
+ export declare function cut(s: string, max?: number): string;
30
+ /** The command or the file a tool call is about, in a line. */
31
+ export declare function summarizeInput(name: string, input: unknown): string;
32
+ export declare class TraceRecorder {
33
+ private exact;
34
+ private now;
35
+ private start;
36
+ private steps;
37
+ private omitted;
38
+ private open;
39
+ private marked;
40
+ private lastText;
41
+ /** `exact`: secrets this process holds (its key, the run token), masked wherever they appear. */
42
+ constructor(exact?: readonly string[], now?: () => number, start?: number);
43
+ private clean;
44
+ private add;
45
+ /** Feed one adapter event. Events it does not know are ignored. */
46
+ event(e: AdapterEvent): void;
47
+ /** Something happened around the run (the task was completed while the run went on). Once per name. */
48
+ mark(name: string): void;
49
+ /** The trace as it is now, within the size limit. */
50
+ finish(): Trace;
51
+ }
@@ -0,0 +1,137 @@
1
+ // The execution trace of one run: what the agent thought (the model's own summary), which commands and files it touched, how each
2
+ // ended and what they printed, and how long each took. Built from the adapter's events, masked and cut down before anything
3
+ // leaves this machine, and uploaded to the hub with the result so both sides of a task can audit it.
4
+ import { isSecretFile, redact } from './redact.js';
5
+ /** Limits: each text field, the number of steps, and the whole trace as JSON. */
6
+ export const FIELD_MAX = 2000;
7
+ export const STEPS_MAX = 300;
8
+ export const TRACE_BYTES_MAX = 150_000;
9
+ /** Cut to `max`, keeping the start and the end (where a command's error usually is). */
10
+ export function cut(s, max = FIELD_MAX) {
11
+ if (s.length <= max)
12
+ return s;
13
+ const head = Math.floor(max * 0.7);
14
+ const tail = max - head;
15
+ return `${s.slice(0, head)}\n…[${s.length - max} characters omitted]…\n${s.slice(s.length - tail)}`;
16
+ }
17
+ const asText = (v) => {
18
+ if (typeof v === 'string')
19
+ return v;
20
+ if (v === undefined || v === null)
21
+ return '';
22
+ try {
23
+ return JSON.stringify(v);
24
+ }
25
+ catch {
26
+ return String(v);
27
+ }
28
+ };
29
+ /** The command or the file a tool call is about, in a line. */
30
+ export function summarizeInput(name, input) {
31
+ const o = input && typeof input === 'object' ? input : {};
32
+ const pick = (...keys) => keys.map((k) => o[k]).find((v) => typeof v === 'string' && v);
33
+ if (typeof input === 'string')
34
+ return input;
35
+ const cmd = pick('command', 'cmd');
36
+ if (cmd)
37
+ return cmd;
38
+ const file = pick('file_path', 'path', 'notebook_path');
39
+ if (file)
40
+ return file + (typeof o.pattern === 'string' ? ` ${o.pattern}` : '');
41
+ const other = pick('pattern', 'query', 'url', 'prompt', 'description');
42
+ if (other)
43
+ return other;
44
+ return asText(input);
45
+ }
46
+ export class TraceRecorder {
47
+ exact;
48
+ now;
49
+ start;
50
+ steps = [];
51
+ omitted = 0;
52
+ open = new Map();
53
+ marked = new Set();
54
+ lastText = '';
55
+ /** `exact`: secrets this process holds (its key, the run token), masked wherever they appear. */
56
+ constructor(exact = [], now = Date.now, start = now()) {
57
+ this.exact = exact;
58
+ this.now = now;
59
+ this.start = start;
60
+ }
61
+ clean(s, max = FIELD_MAX) {
62
+ // cut first (a command can print megabytes), keeping the start and the end; mask what is left; then the exact limit
63
+ return cut(redact(cut(s, max * 2), this.exact), max);
64
+ }
65
+ add(step) {
66
+ this.steps.push(step);
67
+ // a long run keeps its beginning and its end: drop the step after the first 60% of the budget
68
+ if (this.steps.length > STEPS_MAX) {
69
+ this.steps.splice(Math.floor(STEPS_MAX * 0.6), 1);
70
+ this.omitted++;
71
+ }
72
+ return step;
73
+ }
74
+ /** Feed one adapter event. Events it does not know are ignored. */
75
+ event(e) {
76
+ const d = e.data;
77
+ const t = this.now() - this.start;
78
+ if (e.type === 'thinking') {
79
+ const text = asText(d).trim();
80
+ if (text)
81
+ this.add({ t, kind: 'thinking', output: this.clean(text) });
82
+ }
83
+ else if (e.type === 'text') {
84
+ const text = asText(d).trim();
85
+ // the same words twice in a row (a final message repeated) are one step
86
+ if (text && text !== this.lastText)
87
+ this.add({ t, kind: 'text', output: this.clean(text) });
88
+ this.lastText = text;
89
+ }
90
+ else if (e.type === 'tool_start' && d && typeof d === 'object') {
91
+ const id = String(d.id ?? '');
92
+ const name = String(d.name ?? d.type ?? 'tool');
93
+ const input = summarizeInput(name, d.input);
94
+ const secret = /^(read|cat|view)$/i.test(name) && isSecretFile(String(d.input?.file_path ?? d.input?.path ?? ''));
95
+ const step = this.add({ t, kind: 'tool', name, input: this.clean(input) });
96
+ if (id)
97
+ this.open.set(id, { step, at: this.now(), secret });
98
+ }
99
+ else if (e.type === 'tool_end' && d && typeof d === 'object') {
100
+ const id = String(d.id ?? '');
101
+ const o = this.open.get(id);
102
+ const step = o?.step ?? this.add({ t, kind: 'tool', name: String(d.name ?? 'tool') });
103
+ if (o) {
104
+ step.ms = this.now() - o.at;
105
+ this.open.delete(id);
106
+ }
107
+ if (d.is_error !== undefined)
108
+ step.is_error = !!d.is_error;
109
+ if (typeof d.exit === 'number')
110
+ step.exit = d.exit;
111
+ const out = asText(d.output);
112
+ if (out)
113
+ step.output = o?.secret ? '[contents of a secrets file not recorded]' : this.clean(out);
114
+ }
115
+ }
116
+ /** Something happened around the run (the task was completed while the run went on). Once per name. */
117
+ mark(name) {
118
+ if (this.marked.has(name))
119
+ return;
120
+ this.marked.add(name);
121
+ this.add({ t: this.now() - this.start, kind: 'mark', name });
122
+ }
123
+ /** The trace as it is now, within the size limit. */
124
+ finish() {
125
+ let steps = this.steps.map((s) => ({ ...s }));
126
+ let omitted = this.omitted;
127
+ const size = () => JSON.stringify(steps).length;
128
+ // too big: shorten what each step printed, then drop from the middle
129
+ if (size() > TRACE_BYTES_MAX)
130
+ steps = steps.map((s) => (s.output && s.output.length > 600 ? { ...s, output: cut(s.output, 600) } : s));
131
+ while (size() > TRACE_BYTES_MAX && steps.length > 20) {
132
+ steps.splice(Math.floor(steps.length / 2), 1);
133
+ omitted++;
134
+ }
135
+ return { steps, omitted };
136
+ }
137
+ }
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "3.10.0";
1
+ export declare const VERSION = "3.12.0";
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.10.0';
1
+ export const VERSION = '3.12.0';
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.10.0",
3
+ "version": "3.12.0",
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",