@adecore/agents 0.39.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 (168) 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.js +0 -1
  31. package/dist/providers/detect.js +1 -1
  32. package/dist/providers/shell-commands.js +1 -2
  33. package/dist/run-process.js +1 -3
  34. package/dist/skills.d.ts +1 -1
  35. package/dist/skills.js +5 -8
  36. package/dist/tasks/background-limit.js +3 -2
  37. package/dist/tasks/task-coordinator.js +11 -17
  38. package/dist/testing/watch.js +0 -1
  39. package/dist/usage/limits/monitor.js +1 -2
  40. package/dist/usage/readers/claude.js +3 -10
  41. package/dist/usage/readers/codex.js +2 -9
  42. package/dist/usage/readers/grok.js +3 -10
  43. package/dist/usage/readers/host-log.js +3 -10
  44. package/dist/usage/record.d.ts +2 -0
  45. package/dist/usage/record.js +13 -0
  46. package/package.json +6 -6
  47. package/src/async.ts +23 -5
  48. package/src/chat/acp-backend.ts +121 -0
  49. package/src/chat/acp-protocol.ts +172 -6
  50. package/src/chat/attachment-store.ts +21 -0
  51. package/src/chat/backend.ts +0 -2
  52. package/src/chat/bookmark-store.ts +23 -0
  53. package/src/chat/chat-core.ts +208 -7
  54. package/src/chat/chat-log.ts +19 -0
  55. package/src/chat/chat-process.ts +52 -1
  56. package/src/chat/chat-session.ts +401 -10
  57. package/src/chat/chat-store.ts +50 -0
  58. package/src/chat/chat-title.ts +9 -0
  59. package/src/chat/claude-backend.ts +50 -0
  60. package/src/chat/claude-protocol.ts +155 -8
  61. package/src/chat/claude-title.ts +10 -0
  62. package/src/chat/claude-transcript.ts +47 -4
  63. package/src/chat/codex-backend.ts +131 -0
  64. package/src/chat/codex-protocol.ts +180 -8
  65. package/src/chat/codex-thread.ts +23 -0
  66. package/src/chat/composer-preferences.ts +6 -0
  67. package/src/chat/context-breakdown.ts +11 -0
  68. package/src/chat/delta-coalescer.ts +8 -0
  69. package/src/chat/external-sessions.ts +28 -0
  70. package/src/chat/generated-image.ts +25 -0
  71. package/src/chat/index.ts +5 -0
  72. package/src/chat/input.ts +13 -0
  73. package/src/chat/json-rpc-transport.ts +28 -0
  74. package/src/chat/limit-resume.ts +13 -0
  75. package/src/chat/projector.ts +160 -4
  76. package/src/chat/readable-text.ts +3 -0
  77. package/src/chat/request-summary.ts +18 -4
  78. package/src/chat/subagent-projection.ts +3 -0
  79. package/src/chat/subagent-reader.ts +76 -0
  80. package/src/chat/subagent-settlement.ts +24 -0
  81. package/src/chat/thread-search.ts +13 -0
  82. package/src/chat/thread.ts +31 -0
  83. package/src/chat/ui-queries.ts +122 -0
  84. package/src/chat/ui-replies.ts +34 -0
  85. package/src/chat/visual-store.ts +49 -0
  86. package/src/chat/wake-chat.ts +3 -0
  87. package/src/client-sinks.ts +2 -0
  88. package/src/coded-error.ts +1 -5
  89. package/src/context/argv.ts +18 -2
  90. package/src/context/index.ts +1 -0
  91. package/src/context/refusal.ts +3 -0
  92. package/src/context/verb.ts +32 -4
  93. package/src/coordination/index.ts +10 -0
  94. package/src/error-text.ts +7 -0
  95. package/src/fields.ts +14 -0
  96. package/src/fs.ts +20 -4
  97. package/src/host/agent-host.ts +20 -0
  98. package/src/host/environment.ts +6 -0
  99. package/src/host/handlers.ts +18 -0
  100. package/src/host/memory-port.ts +4 -0
  101. package/src/host/wiring.ts +10 -0
  102. package/src/index.ts +1 -0
  103. package/src/lineage.ts +24 -5
  104. package/src/messages/deliver-message.ts +6 -0
  105. package/src/messages/deliver-notice.ts +13 -0
  106. package/src/messages/notice-store.ts +16 -4
  107. package/src/modes.ts +1 -0
  108. package/src/outbox/outbox-worker.ts +31 -0
  109. package/src/outbox/outbox.ts +5 -0
  110. package/src/providers/accounts/accounts.ts +36 -0
  111. package/src/providers/accounts/acp-account.ts +22 -27
  112. package/src/providers/accounts/launch.ts +4 -0
  113. package/src/providers/accounts/service.ts +110 -12
  114. package/src/providers/accounts/shadow-home.ts +10 -0
  115. package/src/providers/accounts/status.ts +13 -4
  116. package/src/providers/accounts/store.ts +25 -0
  117. package/src/providers/accounts/variables.ts +11 -1
  118. package/src/providers/acp-agent-state.ts +24 -3
  119. package/src/providers/acp-provider.ts +5 -0
  120. package/src/providers/acp-usage-log.ts +2 -0
  121. package/src/providers/catalog.ts +17 -0
  122. package/src/providers/claude.ts +10 -1
  123. package/src/providers/codex-rules.ts +3 -0
  124. package/src/providers/codex.ts +2 -1
  125. package/src/providers/detect.ts +6 -1
  126. package/src/providers/provider.ts +1 -1
  127. package/src/providers/registry.ts +18 -2
  128. package/src/providers/shell-commands.ts +37 -2
  129. package/src/record-directory.ts +19 -0
  130. package/src/run-process.ts +24 -3
  131. package/src/serializer.ts +3 -0
  132. package/src/skills.ts +73 -10
  133. package/src/tasks/background-limit.ts +9 -2
  134. package/src/tasks/end-children.ts +9 -0
  135. package/src/tasks/give-task.ts +8 -0
  136. package/src/tasks/task-coordinator.ts +81 -17
  137. package/src/tasks/task-store.ts +28 -0
  138. package/src/tasks/waiting-child.ts +21 -0
  139. package/src/tasks/wake-parent.ts +26 -0
  140. package/src/tasks/wiring.ts +22 -0
  141. package/src/testing/accounts.ts +2 -0
  142. package/src/testing/fake-acp-capture.ts +16 -0
  143. package/src/testing/fake-acp.ts +66 -0
  144. package/src/testing/fake-claude.ts +177 -0
  145. package/src/testing/fake-cli.ts +29 -0
  146. package/src/testing/fake-codex.ts +122 -0
  147. package/src/testing/manual-clock.ts +2 -0
  148. package/src/testing/watch.ts +7 -1
  149. package/src/title-file.ts +12 -0
  150. package/src/usage/accounts.ts +6 -0
  151. package/src/usage/aggregate.ts +19 -0
  152. package/src/usage/exchange.ts +12 -0
  153. package/src/usage/index-file.ts +27 -0
  154. package/src/usage/limits/monitor.ts +31 -3
  155. package/src/usage/limits/normalize.ts +30 -0
  156. package/src/usage/limits/probe.ts +17 -0
  157. package/src/usage/pricing.ts +35 -0
  158. package/src/usage/projects.ts +8 -0
  159. package/src/usage/readers/claude.ts +12 -9
  160. package/src/usage/readers/codex.ts +40 -8
  161. package/src/usage/readers/grok.ts +17 -9
  162. package/src/usage/readers/host-log.ts +9 -9
  163. package/src/usage/record.ts +22 -0
  164. package/src/usage/roots.ts +6 -0
  165. package/src/usage/scanner.ts +61 -0
  166. package/src/usage/usage-service.ts +14 -0
  167. package/src/watch-seam.ts +8 -0
  168. package/src/windows-command.ts +12 -0
@@ -26,12 +26,15 @@ export function isDefaultAccount(id: string, account: Pick<ProviderAccount, 'kin
26
26
 
27
27
  export function expandHome(path: string, env: Env): string {
28
28
  const home = homeFolder(env);
29
+
29
30
  if (path === '~') {
30
31
  return home;
31
32
  }
33
+
32
34
  if (path.startsWith('~/') || (process.platform === 'win32' && path.startsWith('~\\'))) {
33
35
  return join(home, path.slice(2));
34
36
  }
37
+
35
38
  return resolve(path);
36
39
  }
37
40
 
@@ -55,34 +58,45 @@ export function accountProblem(
55
58
  if (isOfferedKind(id, kinds) && id !== account.kind) {
56
59
  return `"${id}" is the id of the default ${id} account`;
57
60
  }
61
+
58
62
  const variables = variablesProblem(account.env ?? [], reserved);
63
+
59
64
  if (variables !== null) {
60
65
  return variables;
61
66
  }
67
+
62
68
  if (isDefaultAccount(id, account)) {
63
69
  return account.home !== undefined || account.shadowHome !== undefined ? 'The default account uses the folder of the CLI itself' : null;
64
70
  }
71
+
65
72
  if (provider.home === undefined) {
66
73
  return null;
67
74
  }
75
+
68
76
  if (account.home === undefined) {
69
77
  return 'An account needs a config folder';
70
78
  }
79
+
71
80
  if (!isRootedPath(account.home)) {
72
81
  return 'A config folder is an absolute path or starts with ~';
73
82
  }
83
+
74
84
  if (account.shadowHome === undefined) {
75
85
  return null;
76
86
  }
87
+
77
88
  if (account.kind !== 'codex') {
78
89
  return 'Only a Codex account has a shadow home';
79
90
  }
91
+
80
92
  if (!isRootedPath(account.shadowHome)) {
81
93
  return 'A shadow home is an absolute path or starts with ~';
82
94
  }
95
+
83
96
  if (expandHome(account.shadowHome, env) === expandHome(account.home, env)) {
84
97
  return 'The shadow home has to be another folder than the config folder';
85
98
  }
99
+
86
100
  return null;
87
101
  }
88
102
 
@@ -90,6 +104,7 @@ export function accountProblem(
90
104
  export function folderVariablesOf(providers: AccountProviders): string[] {
91
105
  return providers.kinds.flatMap((kind) => {
92
106
  const home = providers.get(kind).home;
107
+
93
108
  return home === undefined ? [] : [home.env];
94
109
  });
95
110
  }
@@ -97,12 +112,15 @@ export function folderVariablesOf(providers: AccountProviders): string[] {
97
112
  /* The map with a default account for every CLI the host offers, those first and in catalog order. */
98
113
  export function withDefaults(accounts: ProviderAccountMap, kinds: readonly AgentKind[]): ProviderAccountMap {
99
114
  const complete: ProviderAccountMap = {};
115
+
100
116
  for (const kind of kinds) {
101
117
  complete[kind] = accounts[kind] ?? { kind };
102
118
  }
119
+
103
120
  for (const [id, account] of Object.entries(accounts)) {
104
121
  complete[id] ??= account;
105
122
  }
123
+
106
124
  return complete;
107
125
  }
108
126
 
@@ -111,6 +129,7 @@ export function defaultFolder(provider: ChatProvider, env: Env): string {
111
129
  if (provider.home === undefined) {
112
130
  return '';
113
131
  }
132
+
114
133
  return env[provider.home.env] ?? join(homeFolder(env), provider.home.fallback);
115
134
  }
116
135
 
@@ -119,6 +138,7 @@ export function accountFolder(id: string, account: ProviderAccount, provider: Ch
119
138
  if (isDefaultAccount(id, account) || provider.home === undefined || account.home === undefined) {
120
139
  return defaultFolder(provider, env);
121
140
  }
141
+
122
142
  return expandHome(account.shadowHome ?? account.home, env);
123
143
  }
124
144
 
@@ -127,6 +147,7 @@ export function transcriptFolder(id: string, account: ProviderAccount, provider:
127
147
  if (isDefaultAccount(id, account) || provider.home === undefined || account.home === undefined) {
128
148
  return defaultFolder(provider, env);
129
149
  }
150
+
130
151
  return expandHome(account.home, env);
131
152
  }
132
153
 
@@ -139,14 +160,19 @@ export function transcriptFolder(id: string, account: ProviderAccount, provider:
139
160
  */
140
161
  export function accountEnv(id: string, account: ProviderAccount, provider: ChatProvider, baseEnv: Env, variables: Record<string, string> = {}): Env {
141
162
  const withVariables = (env: Env): Env => (Object.keys(variables).length === 0 ? env : { ...env, ...variables });
163
+
142
164
  if (isDefaultAccount(id, account) || provider.home === undefined || account.home === undefined) {
143
165
  return withVariables(baseEnv);
144
166
  }
167
+
145
168
  const env = { ...baseEnv };
169
+
146
170
  for (const name of provider.home.loginEnv) {
147
171
  delete env[name];
148
172
  }
173
+
149
174
  env[provider.home.env] = accountFolder(id, account, provider, baseEnv);
175
+
150
176
  return withVariables(env);
151
177
  }
152
178
 
@@ -163,11 +189,15 @@ export function canContinue(from: NamedAccount, to: NamedAccount, providers: Acc
163
189
  if (from.id === to.id) {
164
190
  return true;
165
191
  }
192
+
166
193
  const kind = from.account.kind;
194
+
167
195
  if (kind !== to.account.kind || !isOfferedKind(kind, providers.kinds)) {
168
196
  return false;
169
197
  }
198
+
170
199
  const provider = providers.get(kind);
200
+
171
201
  return transcriptFolder(from.id, from.account, provider, env) === transcriptFolder(to.id, to.account, provider, env);
172
202
  }
173
203
 
@@ -179,17 +209,23 @@ export function readAccount(id: string, raw: unknown, providers: AccountProvider
179
209
  if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
180
210
  return null;
181
211
  }
212
+
182
213
  const kind = (raw as { kind?: unknown }).kind;
214
+
183
215
  if (typeof kind !== 'string' || kind === '') {
184
216
  return null;
185
217
  }
218
+
186
219
  if (!isOfferedKind(kind, providers.kinds)) {
187
220
  return isOfferedKind(id, providers.kinds) ? null : raw;
188
221
  }
222
+
189
223
  const parsed = ProviderAccountSchema.safeParse(raw);
190
224
  const reserved = { host, folderVariables: folderVariablesOf(providers) };
225
+
191
226
  if (!parsed.success || accountProblem(id, parsed.data, providers.get(kind), env, reserved, providers.kinds) !== null) {
192
227
  return null;
193
228
  }
229
+
194
230
  return parsed.data;
195
231
  }
@@ -1,6 +1,7 @@
1
1
  import { mkdtemp, rm } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
+ import { withDeadline } from '../../async.ts';
4
5
  import { DEFAULT_ACP_CLIENT, type AcpClientInfo } from '../../chat/acp-backend.ts';
5
6
  import {
6
7
  ACP_AUTH_REQUIRED,
@@ -13,9 +14,10 @@ import {
13
14
  } from '../../chat/acp-protocol.ts';
14
15
  import type { SpawnChatProcess } from '../../chat/chat-process.ts';
15
16
  import { JsonRpcError, JsonRpcTransport, type JsonRpcFrame } from '../../chat/json-rpc-transport.ts';
17
+ import { definedEnv } from './launch.ts';
16
18
  import { ASK_TIMEOUT_MS, type AccountReading } from './status.ts';
17
19
 
18
- /* The clock the probe's deadline runs on; a test hands in one it moves itself. */
20
+ /* The clock the probe's deadline runs on. */
19
21
  export interface ProbeTimer {
20
22
  setTimeout(run: () => void, ms: number): unknown;
21
23
  clearTimeout(handle: unknown): void;
@@ -39,31 +41,6 @@ export interface AcpAccountProbe {
39
41
  const SIGNED_OUT: AccountReading = { signedIn: false, email: null, plan: null, organization: null };
40
42
  const SIGNED_IN: AccountReading = { signedIn: true, email: null, plan: null, organization: null };
41
43
 
42
- const REAL_TIMER: ProbeTimer = {
43
- setTimeout: (run, ms) => setTimeout(run, ms),
44
- clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>)
45
- };
46
-
47
- function definedEnv(env: Record<string, string | undefined>): Record<string, string> {
48
- return Object.fromEntries(Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined));
49
- }
50
-
51
- function withDeadline<T>(work: Promise<T>, ms: number, timer: ProbeTimer, message: string): Promise<T> {
52
- return new Promise((resolve, reject) => {
53
- const handle = timer.setTimeout(() => reject(new Error(message)), ms);
54
- work.then(
55
- (value) => {
56
- timer.clearTimeout(handle);
57
- resolve(value);
58
- },
59
- (e: unknown) => {
60
- timer.clearTimeout(handle);
61
- reject(e instanceof Error ? e : new Error(String(e)));
62
- }
63
- );
64
- });
65
- }
66
-
67
44
  /*
68
45
  * Asks an ACP agent whether the account's environment is signed in: a session it opens means yes, "authentication
69
46
  * required" means no. ACP has no request that says who, so a reading carries no email or plan. The session is opened
@@ -76,17 +53,21 @@ export async function askAcpAccount(probe: AcpAccountProbe): Promise<AccountRead
76
53
  // An agent may list its commands before it answers with the session's id.
77
54
  const listed = new Map<string, string[]>();
78
55
  let sessionId: string | null = null;
56
+
79
57
  const noteCommands = (frame: JsonRpcFrame): void => {
80
58
  const update = frame.method === 'session/update' && (frame.id === undefined || frame.id === null) ? sessionUpdateOf(frame.params) : null;
59
+
81
60
  if (update === null || update.commands === null) {
82
61
  return;
83
62
  }
63
+
84
64
  if (sessionId === null) {
85
65
  listed.set(update.sessionId, update.commands);
86
66
  } else if (update.sessionId === sessionId) {
87
67
  probe.onCommands?.(update.commands);
88
68
  }
89
69
  };
70
+
90
71
  try {
91
72
  const started = new JsonRpcTransport({
92
73
  command: [...probe.command],
@@ -104,7 +85,9 @@ export async function askAcpAccount(probe: AcpAccountProbe): Promise<AccountRead
104
85
  },
105
86
  onExit: () => undefined
106
87
  });
88
+
107
89
  transport = started;
90
+
108
91
  const reading = async (): Promise<AccountReading> => {
109
92
  const capabilities = parseInitialize(
110
93
  await started.request('initialize', {
@@ -113,34 +96,45 @@ export async function askAcpAccount(probe: AcpAccountProbe): Promise<AccountRead
113
96
  clientInfo: { name: client.name, title: client.title ?? client.name, version: client.version }
114
97
  })
115
98
  );
99
+
116
100
  if (capabilities.protocolVersion !== ACP_PROTOCOL_VERSION) {
117
101
  throw new Error(`${probe.name} speaks ACP version ${capabilities.protocolVersion}; only version ${ACP_PROTOCOL_VERSION} is supported`);
118
102
  }
103
+
119
104
  let result: unknown;
105
+
120
106
  try {
121
107
  result = await started.request('session/new', { cwd: folder, mcpServers: [] });
122
108
  } catch (error) {
123
109
  if (error instanceof JsonRpcError && error.code === ACP_AUTH_REQUIRED) {
124
110
  return SIGNED_OUT;
125
111
  }
112
+
126
113
  throw error;
127
114
  }
115
+
128
116
  sessionId = sessionIdOf(result);
117
+
129
118
  if (sessionId === null) {
130
119
  throw new Error(`${probe.name} opened a session without an id`);
131
120
  }
121
+
132
122
  probe.onSession?.(parseSessionInfo(result, sessionId, capabilities.agent));
133
123
  const commands = listed.get(sessionId);
124
+
134
125
  if (commands !== undefined) {
135
126
  probe.onCommands?.(commands);
136
127
  }
128
+
137
129
  if (capabilities.closeSession) {
138
130
  // The answer is known by now; an agent that cannot close it keeps an empty session, nothing worse.
139
131
  await started.request('session/close', { sessionId }).catch(() => undefined);
140
132
  }
133
+
141
134
  return SIGNED_IN;
142
135
  };
143
- return await withDeadline(reading(), probe.timeoutMs ?? ASK_TIMEOUT_MS, probe.timer ?? REAL_TIMER, `${probe.name} did not answer in time`);
136
+
137
+ return await withDeadline(reading(), probe.timeoutMs ?? ASK_TIMEOUT_MS, `${probe.name} did not answer in time`, probe.timer);
144
138
  } finally {
145
139
  await transport?.dispose();
146
140
  await rm(folder, { recursive: true, force: true });
@@ -152,6 +146,7 @@ function refuseRequest(transport: JsonRpcTransport, frame: JsonRpcFrame): void {
152
146
  if (frame.id === undefined || frame.id === null || !transport.alive) {
153
147
  return;
154
148
  }
149
+
155
150
  try {
156
151
  transport.respondError(frame.id as number | string, -32601, `Method not found: ${String(frame.method)}`);
157
152
  } catch {
@@ -37,19 +37,23 @@ export function launchEnv(accounts: AccountLaunches | null, kind: AgentKind, acc
37
37
  if (accounts !== null) {
38
38
  return accounts.envFor(kind, account, baseEnv);
39
39
  }
40
+
40
41
  if (isDefaultAccountOf(kind, account)) {
41
42
  return baseEnv;
42
43
  }
44
+
43
45
  throw new AccountError('account-unavailable', `The account '${account}' is not available on this machine: it keeps no accounts.`);
44
46
  }
45
47
 
46
48
  /* An environment without its unset variables, which is what a spawned process is given. */
47
49
  export function definedEnv(env: Env): Record<string, string> {
48
50
  const defined: Record<string, string> = {};
51
+
49
52
  for (const [key, value] of Object.entries(env)) {
50
53
  if (value !== undefined) {
51
54
  defined[key] = value;
52
55
  }
53
56
  }
57
+
54
58
  return defined;
55
59
  }