@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.
- package/README.md +5 -1
- package/dist/src/adapters/claude.d.ts +19 -0
- package/dist/src/adapters/claude.js +52 -1
- package/dist/src/adapters/codex.d.ts +11 -0
- package/dist/src/adapters/codex.js +34 -2
- package/dist/src/adapters/fake.d.ts +10 -0
- package/dist/src/adapters/fake.js +9 -0
- package/dist/src/adapters/paths.d.ts +9 -1
- package/dist/src/adapters/paths.js +28 -3
- package/dist/src/adapters/types.d.ts +3 -2
- package/dist/src/args.d.ts +1 -1
- package/dist/src/args.js +18 -9
- package/dist/src/cli.js +81 -17
- package/dist/src/client.d.ts +48 -4
- package/dist/src/client.js +41 -5
- package/dist/src/desknotify.d.ts +2 -2
- package/dist/src/desknotify.js +2 -2
- package/dist/src/doctor.d.ts +4 -0
- package/dist/src/doctor.js +12 -0
- package/dist/src/envcheck.d.ts +22 -0
- package/dist/src/envcheck.js +98 -0
- package/dist/src/mcp.d.ts +1 -1
- package/dist/src/mcp.js +24 -12
- package/dist/src/names.d.ts +6 -1
- package/dist/src/names.js +15 -2
- package/dist/src/onboard.d.ts +11 -2
- package/dist/src/onboard.js +54 -2
- package/dist/src/prompts.d.ts +3 -3
- package/dist/src/prompts.js +4 -4
- package/dist/src/redact.d.ts +5 -0
- package/dist/src/redact.js +41 -0
- package/dist/src/runner/runner.d.ts +32 -1
- package/dist/src/runner/runner.js +214 -20
- package/dist/src/sandboxcheck.d.ts +97 -0
- package/dist/src/sandboxcheck.js +236 -0
- package/dist/src/service.d.ts +9 -3
- package/dist/src/service.js +39 -1
- package/dist/src/trace.d.ts +51 -0
- package/dist/src/trace.js +137 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/dist/src/service.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/src/service.js
CHANGED
|
@@ -72,7 +72,45 @@ RestartPreventExitStatus=78
|
|
|
72
72
|
WantedBy=default.target
|
|
73
73
|
`;
|
|
74
74
|
}
|
|
75
|
-
|
|
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
|
+
}
|
package/dist/src/version.d.ts
CHANGED
package/dist/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koalaauto/agentchannel",
|
|
3
|
-
"version": "3.
|
|
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",
|