@adecore/agents 0.39.0 → 0.40.1

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 (172) 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.js +0 -2
  5. package/dist/chat/chat-process.d.ts +1 -0
  6. package/dist/chat/chat-process.js +1 -1
  7. package/dist/chat/chat-session.js +6 -8
  8. package/dist/chat/claude-protocol.js +1 -6
  9. package/dist/chat/claude-transcript.js +1 -3
  10. package/dist/chat/codex-protocol.js +1 -6
  11. package/dist/chat/projector.js +1 -3
  12. package/dist/chat/request-summary.js +1 -3
  13. package/dist/coded-error.js +1 -5
  14. package/dist/context/argv.js +1 -2
  15. package/dist/context/verb.js +1 -3
  16. package/dist/error-text.d.ts +1 -0
  17. package/dist/error-text.js +4 -0
  18. package/dist/fields.d.ts +3 -0
  19. package/dist/fields.js +12 -0
  20. package/dist/fs.d.ts +1 -0
  21. package/dist/fs.js +8 -4
  22. package/dist/lineage.js +11 -5
  23. package/dist/messages/notice-store.js +3 -4
  24. package/dist/providers/accounts/acp-account.js +3 -20
  25. package/dist/providers/accounts/service.d.ts +1 -2
  26. package/dist/providers/accounts/service.js +5 -9
  27. package/dist/providers/accounts/status.js +1 -3
  28. package/dist/providers/acp-agent-state.js +1 -3
  29. package/dist/providers/claude.js +0 -1
  30. package/dist/providers/codex-models.json +3 -2
  31. package/dist/providers/codex.js +0 -1
  32. package/dist/providers/detect.js +1 -1
  33. package/dist/providers/shell-commands.js +1 -2
  34. package/dist/run-process.js +1 -3
  35. package/dist/skills.d.ts +1 -1
  36. package/dist/skills.js +5 -8
  37. package/dist/tasks/background-limit.js +3 -2
  38. package/dist/tasks/task-coordinator.js +11 -17
  39. package/dist/testing/watch.js +0 -1
  40. package/dist/usage/limits/monitor.js +1 -2
  41. package/dist/usage/prices-snapshot.json +8 -0
  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 +208 -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 +401 -10
  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-models.json +3 -2
  126. package/src/providers/codex-rules.ts +3 -0
  127. package/src/providers/codex.ts +2 -1
  128. package/src/providers/detect.ts +6 -1
  129. package/src/providers/provider.ts +1 -1
  130. package/src/providers/registry.ts +18 -2
  131. package/src/providers/shell-commands.ts +37 -2
  132. package/src/record-directory.ts +19 -0
  133. package/src/run-process.ts +24 -3
  134. package/src/serializer.ts +3 -0
  135. package/src/skills.ts +73 -10
  136. package/src/tasks/background-limit.ts +9 -2
  137. package/src/tasks/end-children.ts +9 -0
  138. package/src/tasks/give-task.ts +8 -0
  139. package/src/tasks/task-coordinator.ts +81 -17
  140. package/src/tasks/task-store.ts +28 -0
  141. package/src/tasks/waiting-child.ts +21 -0
  142. package/src/tasks/wake-parent.ts +26 -0
  143. package/src/tasks/wiring.ts +22 -0
  144. package/src/testing/accounts.ts +2 -0
  145. package/src/testing/fake-acp-capture.ts +16 -0
  146. package/src/testing/fake-acp.ts +66 -0
  147. package/src/testing/fake-claude.ts +177 -0
  148. package/src/testing/fake-cli.ts +29 -0
  149. package/src/testing/fake-codex.ts +122 -0
  150. package/src/testing/manual-clock.ts +2 -0
  151. package/src/testing/watch.ts +7 -1
  152. package/src/title-file.ts +12 -0
  153. package/src/usage/accounts.ts +6 -0
  154. package/src/usage/aggregate.ts +19 -0
  155. package/src/usage/exchange.ts +12 -0
  156. package/src/usage/index-file.ts +27 -0
  157. package/src/usage/limits/monitor.ts +31 -3
  158. package/src/usage/limits/normalize.ts +30 -0
  159. package/src/usage/limits/probe.ts +17 -0
  160. package/src/usage/prices-snapshot.json +8 -0
  161. package/src/usage/pricing.ts +35 -0
  162. package/src/usage/projects.ts +8 -0
  163. package/src/usage/readers/claude.ts +12 -9
  164. package/src/usage/readers/codex.ts +40 -8
  165. package/src/usage/readers/grok.ts +17 -9
  166. package/src/usage/readers/host-log.ts +9 -9
  167. package/src/usage/record.ts +22 -0
  168. package/src/usage/roots.ts +6 -0
  169. package/src/usage/scanner.ts +61 -0
  170. package/src/usage/usage-service.ts +14 -0
  171. package/src/watch-seam.ts +8 -0
  172. package/src/windows-command.ts +12 -0
@@ -59,9 +59,7 @@ export interface ProviderRegistryOptions {
59
59
  providers?: ChatProvider[];
60
60
  // The executables to probe, when they are not the ones the providers name.
61
61
  commands?: Partial<Record<AgentKind, string>>;
62
- // How to probe; a test answers without spawning anything.
63
62
  detect?: (command: string) => Promise<CliDetection>;
64
- // The environment a probe runs in.
65
63
  env?: Record<string, string | undefined>;
66
64
  }
67
65
 
@@ -89,25 +87,31 @@ export class ProviderRegistry {
89
87
  */
90
88
  add(provider: ChatProvider): void {
91
89
  const index = this.providers.findIndex((entry) => entry.kind === provider.kind);
90
+
92
91
  if (index === -1) {
93
92
  this.providers.push(provider);
94
93
  } else {
95
94
  this.unfollow(this.providers[index]!);
96
95
  this.providers[index] = provider;
97
96
  }
97
+
98
98
  this.cache.delete(provider.kind);
99
+
99
100
  if (this.listeners.size > 0) {
100
101
  this.follow(provider);
101
102
  }
103
+
102
104
  this.changed(provider.kind);
103
105
  }
104
106
 
105
107
  /* Stops offering a kind; a record that names it gets the stand-in `get` answers for a kind the host does not offer. */
106
108
  remove(kind: AgentKind): void {
107
109
  const index = this.providers.findIndex((entry) => entry.kind === kind);
110
+
108
111
  if (index === -1) {
109
112
  return;
110
113
  }
114
+
111
115
  this.unfollow(this.providers[index]!);
112
116
  this.providers.splice(index, 1);
113
117
  this.cache.delete(kind);
@@ -146,11 +150,13 @@ export class ProviderRegistry {
146
150
  */
147
151
  async oneShotProvider(preferred?: AgentKind): Promise<ChatProvider | null> {
148
152
  const order = preferred ? [this.get(preferred), ...this.providers] : this.providers;
153
+
149
154
  for (const provider of order) {
150
155
  if (provider.oneShotArgs && (await this.detection(provider)).installed) {
151
156
  return provider;
152
157
  }
153
158
  }
159
+
154
160
  return null;
155
161
  }
156
162
 
@@ -172,13 +178,17 @@ export class ProviderRegistry {
172
178
  this.follow(provider);
173
179
  }
174
180
  }
181
+
175
182
  this.listeners.add(listener);
183
+
176
184
  return () => {
177
185
  this.listeners.delete(listener);
186
+
178
187
  if (this.listeners.size === 0) {
179
188
  for (const stop of this.stops.values()) {
180
189
  stop();
181
190
  }
191
+
182
192
  this.stops.clear();
183
193
  }
184
194
  };
@@ -216,17 +226,23 @@ export class ProviderRegistry {
216
226
  if (provider.enabled?.() === false) {
217
227
  return DISABLED;
218
228
  }
229
+
219
230
  const cached = this.cache.get(provider.kind);
231
+
220
232
  if (cached && Date.now() - cached.at < DETECTION_TTL_MS) {
221
233
  return cached.detection;
222
234
  }
235
+
223
236
  const command = this.commands[provider.kind] ?? provider.command[0]!;
224
237
  const detection = this.detect ? await this.detect(command) : await provider.detect(command, this.env);
238
+
225
239
  // Turned off while the probe ran.
226
240
  if (provider.enabled?.() === false) {
227
241
  return DISABLED;
228
242
  }
243
+
229
244
  this.cache.set(provider.kind, { at: Date.now(), detection });
245
+
230
246
  return detection;
231
247
  }
232
248
  }
@@ -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
  }