@adecore/agents 0.38.0 → 0.40.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 (170) hide show
  1. package/dist/async.d.ts +5 -0
  2. package/dist/async.js +13 -5
  3. package/dist/chat/acp-protocol.js +3 -5
  4. package/dist/chat/chat-core.d.ts +1 -0
  5. package/dist/chat/chat-core.js +9 -2
  6. package/dist/chat/chat-process.d.ts +1 -0
  7. package/dist/chat/chat-process.js +1 -1
  8. package/dist/chat/chat-session.d.ts +2 -0
  9. package/dist/chat/chat-session.js +9 -10
  10. package/dist/chat/claude-protocol.js +1 -6
  11. package/dist/chat/claude-transcript.js +1 -3
  12. package/dist/chat/codex-protocol.js +1 -6
  13. package/dist/chat/projector.js +1 -3
  14. package/dist/chat/request-summary.js +1 -3
  15. package/dist/coded-error.js +1 -5
  16. package/dist/context/argv.js +1 -2
  17. package/dist/context/verb.js +1 -3
  18. package/dist/error-text.d.ts +1 -0
  19. package/dist/error-text.js +4 -0
  20. package/dist/fields.d.ts +3 -0
  21. package/dist/fields.js +12 -0
  22. package/dist/fs.d.ts +1 -0
  23. package/dist/fs.js +8 -4
  24. package/dist/lineage.js +11 -5
  25. package/dist/messages/notice-store.js +3 -4
  26. package/dist/providers/accounts/acp-account.js +3 -20
  27. package/dist/providers/accounts/service.d.ts +1 -2
  28. package/dist/providers/accounts/service.js +5 -9
  29. package/dist/providers/accounts/status.js +1 -3
  30. package/dist/providers/acp-agent-state.js +1 -3
  31. package/dist/providers/claude.js +0 -1
  32. package/dist/providers/codex.js +0 -1
  33. package/dist/providers/detect.js +1 -1
  34. package/dist/providers/shell-commands.js +1 -2
  35. package/dist/run-process.js +1 -3
  36. package/dist/skills.d.ts +1 -1
  37. package/dist/skills.js +5 -8
  38. package/dist/tasks/background-limit.js +3 -2
  39. package/dist/tasks/task-coordinator.js +11 -17
  40. package/dist/testing/watch.js +0 -1
  41. package/dist/usage/limits/monitor.js +1 -2
  42. package/dist/usage/readers/claude.js +3 -10
  43. package/dist/usage/readers/codex.js +2 -9
  44. package/dist/usage/readers/grok.js +3 -10
  45. package/dist/usage/readers/host-log.js +3 -10
  46. package/dist/usage/record.d.ts +2 -0
  47. package/dist/usage/record.js +13 -0
  48. package/package.json +6 -6
  49. package/src/async.ts +23 -5
  50. package/src/chat/acp-backend.ts +121 -0
  51. package/src/chat/acp-protocol.ts +172 -6
  52. package/src/chat/attachment-store.ts +21 -0
  53. package/src/chat/backend.ts +0 -2
  54. package/src/chat/bookmark-store.ts +23 -0
  55. package/src/chat/chat-core.ts +218 -7
  56. package/src/chat/chat-log.ts +19 -0
  57. package/src/chat/chat-process.ts +52 -1
  58. package/src/chat/chat-session.ts +414 -13
  59. package/src/chat/chat-store.ts +50 -0
  60. package/src/chat/chat-title.ts +9 -0
  61. package/src/chat/claude-backend.ts +50 -0
  62. package/src/chat/claude-protocol.ts +155 -8
  63. package/src/chat/claude-title.ts +10 -0
  64. package/src/chat/claude-transcript.ts +47 -4
  65. package/src/chat/codex-backend.ts +131 -0
  66. package/src/chat/codex-protocol.ts +180 -8
  67. package/src/chat/codex-thread.ts +23 -0
  68. package/src/chat/composer-preferences.ts +6 -0
  69. package/src/chat/context-breakdown.ts +11 -0
  70. package/src/chat/delta-coalescer.ts +8 -0
  71. package/src/chat/external-sessions.ts +28 -0
  72. package/src/chat/generated-image.ts +25 -0
  73. package/src/chat/index.ts +5 -0
  74. package/src/chat/input.ts +13 -0
  75. package/src/chat/json-rpc-transport.ts +28 -0
  76. package/src/chat/limit-resume.ts +13 -0
  77. package/src/chat/projector.ts +160 -4
  78. package/src/chat/readable-text.ts +3 -0
  79. package/src/chat/request-summary.ts +18 -4
  80. package/src/chat/subagent-projection.ts +3 -0
  81. package/src/chat/subagent-reader.ts +76 -0
  82. package/src/chat/subagent-settlement.ts +24 -0
  83. package/src/chat/thread-search.ts +13 -0
  84. package/src/chat/thread.ts +31 -0
  85. package/src/chat/ui-queries.ts +122 -0
  86. package/src/chat/ui-replies.ts +34 -0
  87. package/src/chat/visual-store.ts +49 -0
  88. package/src/chat/wake-chat.ts +3 -0
  89. package/src/client-sinks.ts +2 -0
  90. package/src/coded-error.ts +1 -5
  91. package/src/context/argv.ts +18 -2
  92. package/src/context/index.ts +1 -0
  93. package/src/context/refusal.ts +3 -0
  94. package/src/context/verb.ts +32 -4
  95. package/src/coordination/index.ts +10 -0
  96. package/src/error-text.ts +7 -0
  97. package/src/fields.ts +14 -0
  98. package/src/fs.ts +20 -4
  99. package/src/host/agent-host.ts +20 -0
  100. package/src/host/environment.ts +6 -0
  101. package/src/host/handlers.ts +18 -0
  102. package/src/host/memory-port.ts +4 -0
  103. package/src/host/wiring.ts +10 -0
  104. package/src/index.ts +1 -0
  105. package/src/lineage.ts +24 -5
  106. package/src/messages/deliver-message.ts +6 -0
  107. package/src/messages/deliver-notice.ts +13 -0
  108. package/src/messages/notice-store.ts +16 -4
  109. package/src/modes.ts +1 -0
  110. package/src/outbox/outbox-worker.ts +31 -0
  111. package/src/outbox/outbox.ts +5 -0
  112. package/src/providers/accounts/accounts.ts +36 -0
  113. package/src/providers/accounts/acp-account.ts +22 -27
  114. package/src/providers/accounts/launch.ts +4 -0
  115. package/src/providers/accounts/service.ts +110 -12
  116. package/src/providers/accounts/shadow-home.ts +10 -0
  117. package/src/providers/accounts/status.ts +13 -4
  118. package/src/providers/accounts/store.ts +25 -0
  119. package/src/providers/accounts/variables.ts +11 -1
  120. package/src/providers/acp-agent-state.ts +24 -3
  121. package/src/providers/acp-provider.ts +5 -0
  122. package/src/providers/acp-usage-log.ts +2 -0
  123. package/src/providers/catalog.ts +17 -0
  124. package/src/providers/claude.ts +10 -1
  125. package/src/providers/codex-rules.ts +3 -0
  126. package/src/providers/codex.ts +2 -1
  127. package/src/providers/detect.ts +6 -1
  128. package/src/providers/provider.ts +1 -1
  129. package/src/providers/registry.ts +18 -2
  130. package/src/providers/shell-commands.ts +37 -2
  131. package/src/record-directory.ts +19 -0
  132. package/src/run-process.ts +24 -3
  133. package/src/serializer.ts +3 -0
  134. package/src/skills.ts +73 -10
  135. package/src/tasks/background-limit.ts +9 -2
  136. package/src/tasks/end-children.ts +9 -0
  137. package/src/tasks/give-task.ts +8 -0
  138. package/src/tasks/task-coordinator.ts +81 -17
  139. package/src/tasks/task-store.ts +28 -0
  140. package/src/tasks/waiting-child.ts +21 -0
  141. package/src/tasks/wake-parent.ts +26 -0
  142. package/src/tasks/wiring.ts +22 -0
  143. package/src/testing/accounts.ts +2 -0
  144. package/src/testing/fake-acp-capture.ts +16 -0
  145. package/src/testing/fake-acp.ts +66 -0
  146. package/src/testing/fake-claude.ts +177 -0
  147. package/src/testing/fake-cli.ts +29 -0
  148. package/src/testing/fake-codex.ts +122 -0
  149. package/src/testing/manual-clock.ts +2 -0
  150. package/src/testing/watch.ts +7 -1
  151. package/src/title-file.ts +12 -0
  152. package/src/usage/accounts.ts +6 -0
  153. package/src/usage/aggregate.ts +19 -0
  154. package/src/usage/exchange.ts +12 -0
  155. package/src/usage/index-file.ts +27 -0
  156. package/src/usage/limits/monitor.ts +31 -3
  157. package/src/usage/limits/normalize.ts +30 -0
  158. package/src/usage/limits/probe.ts +17 -0
  159. package/src/usage/pricing.ts +35 -0
  160. package/src/usage/projects.ts +8 -0
  161. package/src/usage/readers/claude.ts +12 -9
  162. package/src/usage/readers/codex.ts +40 -8
  163. package/src/usage/readers/grok.ts +17 -9
  164. package/src/usage/readers/host-log.ts +9 -9
  165. package/src/usage/record.ts +22 -0
  166. package/src/usage/roots.ts +6 -0
  167. package/src/usage/scanner.ts +61 -0
  168. package/src/usage/usage-service.ts +14 -0
  169. package/src/watch-seam.ts +8 -0
  170. package/src/windows-command.ts +12 -0
@@ -5,8 +5,7 @@ const REFUSED = new Set(['|', '>', '<', '$', '`', '(', ')', '{', '}', '*', '?',
5
5
  /*
6
6
  * Whether a shell command line is only simple commands, joined by `&&`, `||`, `;` or newlines, each of
7
7
  * which starts with one of `allowed`. Anything the tokenizer cannot prove simple answers false, so a
8
- * true is safe to act on without a person: a pipe, a redirect, an expansion, a subshell, a background
9
- * `&`, an unquoted glob or an unbalanced quote all refuse the whole line.
8
+ * true is safe to act on without a person.
10
9
  */
11
10
  export function isOnlyCommands(command: string, allowed: readonly string[]): boolean {
12
11
  let word = '';
@@ -20,103 +19,139 @@ export function isOnlyCommands(command: string, allowed: readonly string[]): boo
20
19
  if (inWord) {
21
20
  words.push(word);
22
21
  }
22
+
23
23
  word = '';
24
24
  inWord = false;
25
25
  };
26
26
 
27
27
  const endCommand = (separator: string): boolean => {
28
28
  endWord();
29
+
29
30
  if (words.length === 0) {
30
31
  // Blank lines are fine, and so is a newline after `&&` or `||`; an empty command before any other separator is not.
31
32
  return separator === '\n';
32
33
  }
34
+
33
35
  if (!allowed.includes(words[0]!)) {
34
36
  return false;
35
37
  }
38
+
36
39
  commands++;
37
40
  words = [];
38
41
  dangling = separator === '&&' || separator === '||';
42
+
39
43
  return true;
40
44
  };
41
45
 
42
46
  for (let i = 0; i < command.length; i++) {
43
47
  const char = command[i]!;
44
48
  const next = command[i + 1];
49
+
45
50
  if (char === ' ' || char === '\t') {
46
51
  endWord();
52
+
47
53
  continue;
48
54
  }
55
+
49
56
  if (char === '\n' || char === ';') {
50
57
  if (!endCommand(char)) {
51
58
  return false;
52
59
  }
60
+
53
61
  continue;
54
62
  }
63
+
55
64
  if ((char === '&' || char === '|') && next === char) {
56
65
  if (!endCommand(char + next)) {
57
66
  return false;
58
67
  }
68
+
59
69
  i++;
70
+
60
71
  continue;
61
72
  }
73
+
62
74
  if (char === '&' || REFUSED.has(char)) {
63
75
  return false;
64
76
  }
77
+
65
78
  if (char === '\\') {
66
79
  if (next === undefined) {
67
80
  return false;
68
81
  }
82
+
69
83
  i++;
84
+
70
85
  if (next !== '\n') {
71
86
  word += next;
72
87
  inWord = true;
73
88
  }
89
+
74
90
  continue;
75
91
  }
92
+
76
93
  if (char === "'") {
77
94
  const close = command.indexOf("'", i + 1);
95
+
78
96
  if (close === -1) {
79
97
  return false;
80
98
  }
99
+
81
100
  word += command.slice(i + 1, close);
82
101
  inWord = true;
83
102
  i = close;
103
+
84
104
  continue;
85
105
  }
106
+
86
107
  if (char === '"') {
87
108
  inWord = true;
88
109
  let closed = false;
110
+
89
111
  for (i++; i < command.length; i++) {
90
112
  const quoted = command[i]!;
113
+
91
114
  if (quoted === '"') {
92
115
  closed = true;
116
+
93
117
  break;
94
118
  }
119
+
95
120
  if (quoted === '$' || quoted === '`') {
96
121
  return false;
97
122
  }
123
+
98
124
  if (quoted === '\\') {
99
125
  const escaped = command[i + 1];
126
+
100
127
  if (escaped === undefined) {
101
128
  return false;
102
129
  }
130
+
103
131
  i++;
132
+
104
133
  if (escaped === '"' || escaped === '\\' || escaped === '$' || escaped === '`') {
105
134
  word += escaped;
106
135
  } else if (escaped !== '\n') {
107
136
  word += quoted + escaped;
108
137
  }
138
+
109
139
  continue;
110
140
  }
141
+
111
142
  word += quoted;
112
143
  }
144
+
113
145
  if (!closed) {
114
146
  return false;
115
147
  }
148
+
116
149
  continue;
117
150
  }
151
+
118
152
  word += char;
119
153
  inWord = true;
120
154
  }
155
+
121
156
  return endCommand('\n') && commands > 0 && !dangling;
122
157
  }
@@ -54,36 +54,48 @@ export class RecordDirectory<T> {
54
54
  /* Reads what an earlier run of the host wrote down. Call before anything can ask. */
55
55
  async load(): Promise<void> {
56
56
  let names: string[];
57
+
57
58
  try {
58
59
  names = await readdir(this.dir);
59
60
  } catch (e) {
60
61
  if (isNotFound(e)) {
61
62
  return;
62
63
  }
64
+
63
65
  throw e;
64
66
  }
67
+
65
68
  for (const name of names) {
66
69
  if (!name.endsWith('.json')) {
67
70
  continue;
68
71
  }
72
+
69
73
  const raw = await readFile(join(this.dir, name), 'utf8').catch(() => null);
74
+
70
75
  if (raw === null) {
71
76
  continue;
72
77
  }
78
+
73
79
  let parsed: ReturnType<typeof this.schema.safeParse>;
80
+
74
81
  try {
75
82
  parsed = this.schema.safeParse(JSON.parse(raw));
76
83
  } catch {
77
84
  continue;
78
85
  }
86
+
79
87
  if (!parsed.success) {
80
88
  continue;
81
89
  }
90
+
82
91
  const record = this.keep === null ? parsed.data : this.keep(parsed.data);
92
+
83
93
  if (record === null) {
84
94
  await this.removeFile(join(this.dir, name));
95
+
85
96
  continue;
86
97
  }
98
+
87
99
  this.records.set(this.idOf(record), record);
88
100
  }
89
101
  }
@@ -109,18 +121,23 @@ export class RecordDirectory<T> {
109
121
  write(record: T): Promise<boolean> {
110
122
  const id = this.idOf(record);
111
123
  this.records.set(id, record);
124
+
112
125
  return this.files.run(id, async () => {
113
126
  if (this.records.get(id) !== record) {
114
127
  return false;
115
128
  }
129
+
116
130
  await mkdir(this.dir, { recursive: true, mode: 0o700 });
117
131
  await this.writeFile(this.pathOf(id), JSON.stringify(record));
132
+
118
133
  if (this.records.get(id) === record) {
119
134
  return true;
120
135
  }
136
+
121
137
  if (!this.records.has(id)) {
122
138
  await this.removeFile(this.pathOf(id));
123
139
  }
140
+
124
141
  return false;
125
142
  });
126
143
  }
@@ -128,6 +145,7 @@ export class RecordDirectory<T> {
128
145
  /* Forgets a record here and now; its file follows, after what is queued for it. */
129
146
  remove(id: string): Promise<void> {
130
147
  this.records.delete(id);
148
+
131
149
  return this.files.run(id, () => this.removeFile(this.pathOf(id)));
132
150
  }
133
151
 
@@ -136,6 +154,7 @@ export class RecordDirectory<T> {
136
154
  const id = this.idOf(listed);
137
155
  // Read again after each await: a record written meanwhile may no longer be one to drop.
138
156
  const current = this.records.get(id);
157
+
139
158
  if (current !== undefined && drop(current)) {
140
159
  await this.remove(id);
141
160
  }
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { killProcessTree } from './chat/chat-process.ts';
2
+ import { KILL_GRACE_MS, killProcessTree } from './chat/chat-process.ts';
3
3
  import { spawnTarget } from './windows-command.ts';
4
4
 
5
5
  export interface ProcessResult {
@@ -19,8 +19,6 @@ export interface RunProcessOptions {
19
19
  timeoutMessage?: string;
20
20
  }
21
21
 
22
- // What a command and everything it started get between the SIGTERM and the SIGKILL.
23
- const KILL_GRACE_MS = 2000;
24
22
  // Stdout and stderr together; a short command that prints more is ended and fails instead of filling memory.
25
23
  const MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
26
24
 
@@ -28,11 +26,15 @@ const MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
28
26
  export function runProcess(command: readonly string[], options: RunProcessOptions = {}): Promise<ProcessResult> {
29
27
  return new Promise((resolve, reject) => {
30
28
  const [executable, ...args] = command;
29
+
31
30
  if (executable === undefined) {
32
31
  reject(new Error('No command to run'));
32
+
33
33
  return;
34
34
  }
35
+
35
36
  const target = spawnTarget(executable, args, options.env === undefined ? {} : { env: options.env });
37
+
36
38
  const child = spawn(target.file, target.args, {
37
39
  ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
38
40
  ...(options.env === undefined ? {} : { env: options.env }),
@@ -42,60 +44,79 @@ export function runProcess(command: readonly string[], options: RunProcessOption
42
44
  detached: process.platform !== 'win32',
43
45
  windowsHide: true
44
46
  });
47
+
45
48
  const stdout: Buffer[] = [];
46
49
  const stderr: Buffer[] = [];
47
50
  let printed = 0;
48
51
  let settled = false;
49
52
  let timer: ReturnType<typeof setTimeout> | null = null;
53
+
50
54
  const settle = (): boolean => {
51
55
  if (settled) {
52
56
  return false;
53
57
  }
58
+
54
59
  settled = true;
60
+
55
61
  if (timer !== null) {
56
62
  clearTimeout(timer);
57
63
  }
64
+
58
65
  return true;
59
66
  };
67
+
60
68
  const end = (message: string): void => {
61
69
  if (!settle()) {
62
70
  return;
63
71
  }
72
+
64
73
  const pid = child.pid;
74
+
65
75
  if (pid !== undefined) {
66
76
  killProcessTree(pid, 'SIGTERM');
67
77
  // Sent even when the command left in time: what it started may still ignore the SIGTERM.
68
78
  setTimeout(() => killProcessTree(pid, 'SIGKILL'), KILL_GRACE_MS).unref?.();
69
79
  }
80
+
70
81
  reject(new Error(message));
71
82
  };
83
+
72
84
  const collect = (into: Buffer[]) => (chunk: Buffer) => {
73
85
  if (settled) {
74
86
  return;
75
87
  }
88
+
76
89
  printed += chunk.length;
90
+
77
91
  if (printed > MAX_OUTPUT_BYTES) {
78
92
  end(`${executable} printed more than ${MAX_OUTPUT_BYTES / 1024 / 1024} MB`);
93
+
79
94
  return;
80
95
  }
96
+
81
97
  into.push(chunk);
82
98
  };
99
+
83
100
  child.stdout?.on('data', collect(stdout));
84
101
  child.stderr?.on('data', collect(stderr));
102
+
85
103
  if (options.timeoutMs !== undefined) {
86
104
  const message = options.timeoutMessage ?? `${executable} did not finish in time`;
87
105
  timer = setTimeout(() => end(message), options.timeoutMs);
88
106
  }
107
+
89
108
  child.on('error', (error) => {
90
109
  if (settle()) {
91
110
  reject(error);
92
111
  }
93
112
  });
113
+
94
114
  child.on('close', (exitCode) => {
95
115
  if (settle()) {
96
116
  resolve({ exitCode, stdout: Buffer.concat(stdout).toString('utf8'), stderr: Buffer.concat(stderr).toString('utf8') });
97
117
  }
98
118
  });
119
+
99
120
  if (options.stdin !== undefined && child.stdin) {
100
121
  child.stdin.on('error', () => undefined);
101
122
  child.stdin.end(options.stdin);
package/src/serializer.ts CHANGED
@@ -9,6 +9,7 @@ export class Serializer {
9
9
  run<T>(work: () => Promise<T>): Promise<T> {
10
10
  const next = this.chain.then(work, work);
11
11
  this.chain = next.catch(() => undefined);
12
+
12
13
  return next;
13
14
  }
14
15
 
@@ -30,12 +31,14 @@ export class KeyedSerializer {
30
31
  const next = previous.then(work, work);
31
32
  const settled = next.catch(() => undefined);
32
33
  this.chains.set(key, settled);
34
+
33
35
  // A key whose chain ran out leaves nothing behind, or the map grows with every id ever written.
34
36
  void settled.then(() => {
35
37
  if (this.chains.get(key) === settled) {
36
38
  this.chains.delete(key);
37
39
  }
38
40
  });
41
+
39
42
  return next;
40
43
  }
41
44
  }