@koalaauto/agentchannel 3.11.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.
@@ -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.11.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.11.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.11.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",