@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.
- package/dist/async.d.ts +5 -0
- package/dist/async.js +13 -5
- package/dist/chat/acp-protocol.js +3 -5
- package/dist/chat/chat-core.d.ts +1 -0
- package/dist/chat/chat-core.js +9 -2
- package/dist/chat/chat-process.d.ts +1 -0
- package/dist/chat/chat-process.js +1 -1
- package/dist/chat/chat-session.d.ts +2 -0
- package/dist/chat/chat-session.js +9 -10
- package/dist/chat/claude-protocol.js +1 -6
- package/dist/chat/claude-transcript.js +1 -3
- package/dist/chat/codex-protocol.js +1 -6
- package/dist/chat/projector.js +1 -3
- package/dist/chat/request-summary.js +1 -3
- package/dist/coded-error.js +1 -5
- package/dist/context/argv.js +1 -2
- package/dist/context/verb.js +1 -3
- package/dist/error-text.d.ts +1 -0
- package/dist/error-text.js +4 -0
- package/dist/fields.d.ts +3 -0
- package/dist/fields.js +12 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +8 -4
- package/dist/lineage.js +11 -5
- package/dist/messages/notice-store.js +3 -4
- package/dist/providers/accounts/acp-account.js +3 -20
- package/dist/providers/accounts/service.d.ts +1 -2
- package/dist/providers/accounts/service.js +5 -9
- package/dist/providers/accounts/status.js +1 -3
- package/dist/providers/acp-agent-state.js +1 -3
- package/dist/providers/claude.js +0 -1
- package/dist/providers/codex.js +0 -1
- package/dist/providers/detect.js +1 -1
- package/dist/providers/shell-commands.js +1 -2
- package/dist/run-process.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -8
- package/dist/tasks/background-limit.js +3 -2
- package/dist/tasks/task-coordinator.js +11 -17
- package/dist/testing/watch.js +0 -1
- package/dist/usage/limits/monitor.js +1 -2
- package/dist/usage/readers/claude.js +3 -10
- package/dist/usage/readers/codex.js +2 -9
- package/dist/usage/readers/grok.js +3 -10
- package/dist/usage/readers/host-log.js +3 -10
- package/dist/usage/record.d.ts +2 -0
- package/dist/usage/record.js +13 -0
- package/package.json +6 -6
- package/src/async.ts +23 -5
- package/src/chat/acp-backend.ts +121 -0
- package/src/chat/acp-protocol.ts +172 -6
- package/src/chat/attachment-store.ts +21 -0
- package/src/chat/backend.ts +0 -2
- package/src/chat/bookmark-store.ts +23 -0
- package/src/chat/chat-core.ts +218 -7
- package/src/chat/chat-log.ts +19 -0
- package/src/chat/chat-process.ts +52 -1
- package/src/chat/chat-session.ts +414 -13
- package/src/chat/chat-store.ts +50 -0
- package/src/chat/chat-title.ts +9 -0
- package/src/chat/claude-backend.ts +50 -0
- package/src/chat/claude-protocol.ts +155 -8
- package/src/chat/claude-title.ts +10 -0
- package/src/chat/claude-transcript.ts +47 -4
- package/src/chat/codex-backend.ts +131 -0
- package/src/chat/codex-protocol.ts +180 -8
- package/src/chat/codex-thread.ts +23 -0
- package/src/chat/composer-preferences.ts +6 -0
- package/src/chat/context-breakdown.ts +11 -0
- package/src/chat/delta-coalescer.ts +8 -0
- package/src/chat/external-sessions.ts +28 -0
- package/src/chat/generated-image.ts +25 -0
- package/src/chat/index.ts +5 -0
- package/src/chat/input.ts +13 -0
- package/src/chat/json-rpc-transport.ts +28 -0
- package/src/chat/limit-resume.ts +13 -0
- package/src/chat/projector.ts +160 -4
- package/src/chat/readable-text.ts +3 -0
- package/src/chat/request-summary.ts +18 -4
- package/src/chat/subagent-projection.ts +3 -0
- package/src/chat/subagent-reader.ts +76 -0
- package/src/chat/subagent-settlement.ts +24 -0
- package/src/chat/thread-search.ts +13 -0
- package/src/chat/thread.ts +31 -0
- package/src/chat/ui-queries.ts +122 -0
- package/src/chat/ui-replies.ts +34 -0
- package/src/chat/visual-store.ts +49 -0
- package/src/chat/wake-chat.ts +3 -0
- package/src/client-sinks.ts +2 -0
- package/src/coded-error.ts +1 -5
- package/src/context/argv.ts +18 -2
- package/src/context/index.ts +1 -0
- package/src/context/refusal.ts +3 -0
- package/src/context/verb.ts +32 -4
- package/src/coordination/index.ts +10 -0
- package/src/error-text.ts +7 -0
- package/src/fields.ts +14 -0
- package/src/fs.ts +20 -4
- package/src/host/agent-host.ts +20 -0
- package/src/host/environment.ts +6 -0
- package/src/host/handlers.ts +18 -0
- package/src/host/memory-port.ts +4 -0
- package/src/host/wiring.ts +10 -0
- package/src/index.ts +1 -0
- package/src/lineage.ts +24 -5
- package/src/messages/deliver-message.ts +6 -0
- package/src/messages/deliver-notice.ts +13 -0
- package/src/messages/notice-store.ts +16 -4
- package/src/modes.ts +1 -0
- package/src/outbox/outbox-worker.ts +31 -0
- package/src/outbox/outbox.ts +5 -0
- package/src/providers/accounts/accounts.ts +36 -0
- package/src/providers/accounts/acp-account.ts +22 -27
- package/src/providers/accounts/launch.ts +4 -0
- package/src/providers/accounts/service.ts +110 -12
- package/src/providers/accounts/shadow-home.ts +10 -0
- package/src/providers/accounts/status.ts +13 -4
- package/src/providers/accounts/store.ts +25 -0
- package/src/providers/accounts/variables.ts +11 -1
- package/src/providers/acp-agent-state.ts +24 -3
- package/src/providers/acp-provider.ts +5 -0
- package/src/providers/acp-usage-log.ts +2 -0
- package/src/providers/catalog.ts +17 -0
- package/src/providers/claude.ts +10 -1
- package/src/providers/codex-rules.ts +3 -0
- package/src/providers/codex.ts +2 -1
- package/src/providers/detect.ts +6 -1
- package/src/providers/provider.ts +1 -1
- package/src/providers/registry.ts +18 -2
- package/src/providers/shell-commands.ts +37 -2
- package/src/record-directory.ts +19 -0
- package/src/run-process.ts +24 -3
- package/src/serializer.ts +3 -0
- package/src/skills.ts +73 -10
- package/src/tasks/background-limit.ts +9 -2
- package/src/tasks/end-children.ts +9 -0
- package/src/tasks/give-task.ts +8 -0
- package/src/tasks/task-coordinator.ts +81 -17
- package/src/tasks/task-store.ts +28 -0
- package/src/tasks/waiting-child.ts +21 -0
- package/src/tasks/wake-parent.ts +26 -0
- package/src/tasks/wiring.ts +22 -0
- package/src/testing/accounts.ts +2 -0
- package/src/testing/fake-acp-capture.ts +16 -0
- package/src/testing/fake-acp.ts +66 -0
- package/src/testing/fake-claude.ts +177 -0
- package/src/testing/fake-cli.ts +29 -0
- package/src/testing/fake-codex.ts +122 -0
- package/src/testing/manual-clock.ts +2 -0
- package/src/testing/watch.ts +7 -1
- package/src/title-file.ts +12 -0
- package/src/usage/accounts.ts +6 -0
- package/src/usage/aggregate.ts +19 -0
- package/src/usage/exchange.ts +12 -0
- package/src/usage/index-file.ts +27 -0
- package/src/usage/limits/monitor.ts +31 -3
- package/src/usage/limits/normalize.ts +30 -0
- package/src/usage/limits/probe.ts +17 -0
- package/src/usage/pricing.ts +35 -0
- package/src/usage/projects.ts +8 -0
- package/src/usage/readers/claude.ts +12 -9
- package/src/usage/readers/codex.ts +40 -8
- package/src/usage/readers/grok.ts +17 -9
- package/src/usage/readers/host-log.ts +9 -9
- package/src/usage/record.ts +22 -0
- package/src/usage/roots.ts +6 -0
- package/src/usage/scanner.ts +61 -0
- package/src/usage/usage-service.ts +14 -0
- package/src/watch-seam.ts +8 -0
- package/src/windows-command.ts +12 -0
package/src/context/verb.ts
CHANGED
|
@@ -145,6 +145,7 @@ function usageWithFlags(usage: string, detail: readonly string[]): string {
|
|
|
145
145
|
.map((line) => line.split('\t'))
|
|
146
146
|
.filter(([, syntax]) => !flagSpelled(usage, syntax!.split(' ')[0]!))
|
|
147
147
|
.map(([, syntax, need]) => (need === 'required' ? syntax! : `[${syntax}]`));
|
|
148
|
+
|
|
148
149
|
return [usage, ...missing].filter((part) => part !== '').join(' ');
|
|
149
150
|
}
|
|
150
151
|
|
|
@@ -182,9 +183,6 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
182
183
|
*/
|
|
183
184
|
const dryRunVerbs: string[] = [];
|
|
184
185
|
|
|
185
|
-
/* What a refusal over arguments points at; for an action `name` is both words, which is what `help` takes. */
|
|
186
|
-
const usageLinesOf = (name: string, usage: string): string[] => [`usage\t${name}\t${usage}`, `detail\t${cli} help ${name}`];
|
|
187
|
-
|
|
188
186
|
/* One set of arguments parsed and run. `name` is what a refusal calls it, so `view new` refuses under both its words. */
|
|
189
187
|
const runArgs = async <Positionals extends z.ZodType, Flags extends z.ZodObject>(
|
|
190
188
|
name: string,
|
|
@@ -193,11 +191,13 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
193
191
|
argv: readonly string[],
|
|
194
192
|
call: Call
|
|
195
193
|
): Promise<string[]> => {
|
|
196
|
-
const usageLines =
|
|
194
|
+
const usageLines = [`usage\t${name}\t${usage}`, `detail\t${cli} help ${name}`];
|
|
197
195
|
const { values, switches } = flagsOf(spec);
|
|
198
196
|
const parsed = parseArgv(argv, values, switches);
|
|
197
|
+
|
|
199
198
|
if (!parsed.ok) {
|
|
200
199
|
const dryRunAsked = argv.some((word) => word === `--${DRY_RUN_FLAG}` || word.startsWith(`--${DRY_RUN_FLAG}=`));
|
|
200
|
+
|
|
201
201
|
if (parsed.code === 'unknown-flag' && spec.dryRun !== true && dryRunAsked) {
|
|
202
202
|
throw new VerbRefusal(
|
|
203
203
|
'no-dry-run',
|
|
@@ -205,20 +205,27 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
205
205
|
dryRunVerbs.map((verb) => `verb\t${verb}\ttakes --${DRY_RUN_FLAG}`)
|
|
206
206
|
);
|
|
207
207
|
}
|
|
208
|
+
|
|
208
209
|
throw new VerbRefusal(parsed.code, parsed.message, usageLines);
|
|
209
210
|
}
|
|
211
|
+
|
|
210
212
|
const positionals = spec.positionals.safeParse(parsed.positionals);
|
|
213
|
+
|
|
211
214
|
if (!positionals.success) {
|
|
212
215
|
throw new VerbRefusal('bad-arguments', issueMessage(positionals.error, 'argument'), usageLines);
|
|
213
216
|
}
|
|
217
|
+
|
|
214
218
|
const { [REVISION_FLAG]: revision, ...given } = parsed.flags;
|
|
215
219
|
const flags = spec.flags.safeParse(given);
|
|
220
|
+
|
|
216
221
|
if (!flags.success) {
|
|
217
222
|
throw new VerbRefusal('bad-arguments', issueMessage(flags.error, 'flag'), usageLines);
|
|
218
223
|
}
|
|
224
|
+
|
|
219
225
|
if (revision !== undefined && !/^\d+$/.test(revision)) {
|
|
220
226
|
throw new VerbRefusal('bad-arguments', `--${REVISION_FLAG} takes the whole number a list printed as revision`, usageLines);
|
|
221
227
|
}
|
|
228
|
+
|
|
222
229
|
return spec.run(
|
|
223
230
|
{ positionals: positionals.data, flags: flags.data, switches: parsed.switches, dryRun: parsed.switches.has(DRY_RUN_FLAG) },
|
|
224
231
|
revision === undefined ? call : { ...call, expectedRevision: Number(revision) }
|
|
@@ -231,10 +238,13 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
231
238
|
spec: VerbSpec<Positionals, Flags, Call>
|
|
232
239
|
): Omit<Verb<Call>, 'served'> => {
|
|
233
240
|
const { values, switches } = flagsOf(spec);
|
|
241
|
+
|
|
234
242
|
if (spec.dryRun === true) {
|
|
235
243
|
dryRunVerbs.push(name);
|
|
236
244
|
}
|
|
245
|
+
|
|
237
246
|
const usage = usageWithFlags(spec.usage, spec.detail);
|
|
247
|
+
|
|
238
248
|
return {
|
|
239
249
|
name,
|
|
240
250
|
usage,
|
|
@@ -258,6 +268,7 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
258
268
|
|
|
259
269
|
const defineNoun = (spec: NounSpec<Call>): Noun<Call> => {
|
|
260
270
|
const words = spec.actions.map((action) => action.word);
|
|
271
|
+
|
|
261
272
|
return {
|
|
262
273
|
served: 'noun',
|
|
263
274
|
name: spec.name,
|
|
@@ -268,6 +279,7 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
268
279
|
async run(argv, call) {
|
|
269
280
|
const [word, ...rest] = argv;
|
|
270
281
|
const action = spec.actions.find((candidate) => candidate.word === word);
|
|
282
|
+
|
|
271
283
|
if (!action) {
|
|
272
284
|
throw new VerbRefusal(
|
|
273
285
|
'unknown-action',
|
|
@@ -275,6 +287,7 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
275
287
|
[...spec.actions.map((candidate) => `usage\t${candidate.name}\t${candidate.usage}`), `detail\t${cli} help ${spec.name}`]
|
|
276
288
|
);
|
|
277
289
|
}
|
|
290
|
+
|
|
278
291
|
return action.run(rest, call);
|
|
279
292
|
}
|
|
280
293
|
};
|
|
@@ -282,6 +295,7 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
282
295
|
|
|
283
296
|
const defineHelp = (spec: HelpSpec<Call>): Verb<Call> => {
|
|
284
297
|
const detailLines = (entry: VerbHelp): string[] => [`usage\t${entry.name}\t${entry.usage}`, `about\t${entry.summary}`, ...entry.detail, spec.refusal];
|
|
298
|
+
|
|
285
299
|
const nounLines = (noun: Noun<Call>): string[] => [
|
|
286
300
|
`usage\t${noun.name}\t${noun.usage}`,
|
|
287
301
|
`about\t${noun.summary}`,
|
|
@@ -290,9 +304,11 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
290
304
|
`detail\t${cli} help ${noun.name} <action>\tone action in full`,
|
|
291
305
|
spec.refusal
|
|
292
306
|
];
|
|
307
|
+
|
|
293
308
|
const topics = spec.topics ?? [];
|
|
294
309
|
const topicLines = topics.map((topic) => `topic\t${topic.name}\t${topic.summary}`);
|
|
295
310
|
const topicPointer = topics.length > 0 ? [`detail\t${cli} help <topic>\tone topic in full`] : [];
|
|
311
|
+
|
|
296
312
|
return defineVerb({
|
|
297
313
|
name: 'help',
|
|
298
314
|
usage: '[noun] [action]',
|
|
@@ -311,6 +327,7 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
311
327
|
flags: z.object({}),
|
|
312
328
|
run: async ({ positionals: [name, actionWord] }) => {
|
|
313
329
|
const entries = spec.entries();
|
|
330
|
+
|
|
314
331
|
if (name === undefined) {
|
|
315
332
|
return [
|
|
316
333
|
...summaryLines(entries),
|
|
@@ -323,16 +340,20 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
323
340
|
spec.refusal
|
|
324
341
|
];
|
|
325
342
|
}
|
|
343
|
+
|
|
326
344
|
const entry = entries.find((candidate) => candidate.name === name);
|
|
327
345
|
const topic = entry ? undefined : topics.find((candidate) => candidate.name === name);
|
|
346
|
+
|
|
328
347
|
if (topic) {
|
|
329
348
|
if (actionWord !== undefined) {
|
|
330
349
|
throw new VerbRefusal('bad-arguments', `${name} is a topic and has no actions; ${cli} help ${name} prints it`, [
|
|
331
350
|
`detail\t${cli} help ${name}\tthe topic in full`
|
|
332
351
|
]);
|
|
333
352
|
}
|
|
353
|
+
|
|
334
354
|
return [`about\t${topic.summary}`, ...topic.body];
|
|
335
355
|
}
|
|
356
|
+
|
|
336
357
|
if (!entry) {
|
|
337
358
|
throw new VerbRefusal('unknown-verb', topics.length > 0 ? `${name} is not a verb, a noun or a topic` : `${name} is not a verb or a noun`, [
|
|
338
359
|
`detail\t${cli} help\tevery verb and noun, with what each takes`,
|
|
@@ -340,21 +361,27 @@ export function createVerbRegistry<Call extends VerbCallBase>(options: { cli: st
|
|
|
340
361
|
...topicLines
|
|
341
362
|
]);
|
|
342
363
|
}
|
|
364
|
+
|
|
343
365
|
if (entry.served !== 'noun') {
|
|
344
366
|
if (actionWord !== undefined) {
|
|
345
367
|
throw new VerbRefusal('bad-arguments', `${name} is a verb and has no actions; ${cli} help ${name} details it`, [
|
|
346
368
|
`detail\t${cli} help ${name}\tone verb in full`
|
|
347
369
|
]);
|
|
348
370
|
}
|
|
371
|
+
|
|
349
372
|
return detailLines(entry);
|
|
350
373
|
}
|
|
374
|
+
|
|
351
375
|
if (actionWord === undefined) {
|
|
352
376
|
return nounLines(entry);
|
|
353
377
|
}
|
|
378
|
+
|
|
354
379
|
const action = entry.actions.find((candidate) => candidate.word === actionWord);
|
|
380
|
+
|
|
355
381
|
if (!action) {
|
|
356
382
|
throw new VerbRefusal('unknown-action', `${actionWord} is not an action of ${name}`, actionLines(entry));
|
|
357
383
|
}
|
|
384
|
+
|
|
358
385
|
return detailLines(action);
|
|
359
386
|
}
|
|
360
387
|
});
|
|
@@ -373,6 +400,7 @@ export function lengthOf(input: unknown): number {
|
|
|
373
400
|
if (typeof input === 'string' || Array.isArray(input)) {
|
|
374
401
|
return input.length;
|
|
375
402
|
}
|
|
403
|
+
|
|
376
404
|
return 0;
|
|
377
405
|
}
|
|
378
406
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
|
|
2
2
|
export { type OutboxEntryOf, OutboxStore, type OutboxWorkShape, type OutboxEntryFields, type OutboxStoreOptions } from '../outbox/outbox.ts';
|
|
3
|
+
|
|
3
4
|
export {
|
|
4
5
|
RETRY_DELAYS_MS,
|
|
5
6
|
type OutboxClock,
|
|
@@ -9,7 +10,9 @@ export {
|
|
|
9
10
|
OutboxWorker,
|
|
10
11
|
type OutboxWorkerOptions
|
|
11
12
|
} from '../outbox/outbox-worker.ts';
|
|
13
|
+
|
|
12
14
|
export { restartBackgroundLimits, type RestartedOutbox } from '../tasks/background-limit.ts';
|
|
15
|
+
|
|
13
16
|
export {
|
|
14
17
|
EndChildrenWorkSchema,
|
|
15
18
|
endChildren,
|
|
@@ -19,8 +22,10 @@ export {
|
|
|
19
22
|
type EndChildrenDeps,
|
|
20
23
|
type EndChildren
|
|
21
24
|
} from '../tasks/end-children.ts';
|
|
25
|
+
|
|
22
26
|
export { type TaskCoordinatorDeps, TaskCoordinator } from '../tasks/task-coordinator.ts';
|
|
23
27
|
export { TaskStore, type TaskListener, type TaskEvent, type TaskSink } from '../tasks/task-store.ts';
|
|
28
|
+
|
|
24
29
|
export {
|
|
25
30
|
BackgroundLimitWorkSchema,
|
|
26
31
|
WakeParentWorkSchema,
|
|
@@ -35,6 +40,7 @@ export {
|
|
|
35
40
|
type AnyOutboxEntry,
|
|
36
41
|
type TaskOutbox
|
|
37
42
|
} from '../tasks/task-work.ts';
|
|
43
|
+
|
|
38
44
|
export {
|
|
39
45
|
type ChatRequests,
|
|
40
46
|
chatRequests,
|
|
@@ -44,8 +50,10 @@ export {
|
|
|
44
50
|
type WaitingObserverDeps,
|
|
45
51
|
type WaitingWords
|
|
46
52
|
} from '../tasks/waiting-child.ts';
|
|
53
|
+
|
|
47
54
|
export { RESULT_PREVIEW_BYTES, wakePrompt } from '../tasks/wake-parent.ts';
|
|
48
55
|
export { type TaskVerbs, type TaskWords, wireTasks, type TaskChats, type TaskRecord, type TaskWiringDeps, type TaskWiring } from '../tasks/wiring.ts';
|
|
56
|
+
|
|
49
57
|
export {
|
|
50
58
|
DeliverMessageWorkSchema,
|
|
51
59
|
deliverMessageHandler,
|
|
@@ -53,6 +61,7 @@ export {
|
|
|
53
61
|
type DeliverMessageEntry,
|
|
54
62
|
type DeliverMessageDeps
|
|
55
63
|
} from '../messages/deliver-message.ts';
|
|
64
|
+
|
|
56
65
|
export {
|
|
57
66
|
type MessageWords,
|
|
58
67
|
type ChatNoticeTargets,
|
|
@@ -62,6 +71,7 @@ export {
|
|
|
62
71
|
showNotices,
|
|
63
72
|
type NoticeChat
|
|
64
73
|
} from '../messages/deliver-notice.ts';
|
|
74
|
+
|
|
65
75
|
export { NoticeNotes, unshownNotes } from '../messages/notice-notes.ts';
|
|
66
76
|
export { MAX_NOTICES, NOTICE_MAX_AGE_MS, type Notice, NoticeStore } from '../messages/notice-store.ts';
|
|
67
77
|
export { AgentLineageStore } from '../lineage.ts';
|
package/src/error-text.ts
CHANGED
|
@@ -9,12 +9,19 @@ export function describeError(error: unknown, debug: boolean): string {
|
|
|
9
9
|
if (!(error instanceof Error)) {
|
|
10
10
|
return String(error);
|
|
11
11
|
}
|
|
12
|
+
|
|
12
13
|
if (debug) {
|
|
13
14
|
return error.stack ?? `${error.name}: ${error.message}`;
|
|
14
15
|
}
|
|
16
|
+
|
|
15
17
|
return error.message === '' ? error.name : error.message;
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
/* What was thrown, as an Error a rejection can carry. */
|
|
21
|
+
export function asError(thrown: unknown): Error {
|
|
22
|
+
return thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
// The host decides once whether a stack is worth showing; the package reads no variable of its own.
|
|
19
26
|
let showStacks = false;
|
|
20
27
|
|
package/src/fields.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* A field of a frame or a file that no schema promises a string in. */
|
|
2
|
+
export function str(value: unknown): string | null {
|
|
3
|
+
return typeof value === 'string' ? value : null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* A count of a frame, zero for anything that is not a finite number. */
|
|
7
|
+
export function num(value: unknown): number {
|
|
8
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* The same, with an empty string read as nothing said. */
|
|
12
|
+
export function filledStr(value: unknown): string | null {
|
|
13
|
+
return typeof value === 'string' && value !== '' ? value : null;
|
|
14
|
+
}
|
package/src/fs.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { renameSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { open, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
|
+
import { wait } from './async.ts';
|
|
4
5
|
|
|
5
6
|
// Windows can refuse a rename while a watcher or an indexer holds the target; a few short retries cover it.
|
|
6
7
|
const RENAME_RETRIES = 5;
|
|
@@ -11,8 +12,12 @@ function sleepSync(ms: number): void {
|
|
|
11
12
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
export function hasErrorCode(e: unknown, ...codes: string[]): boolean {
|
|
16
|
+
return typeof e === 'object' && e !== null && 'code' in e && codes.includes(e.code as string);
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
function isTransient(e: unknown): boolean {
|
|
15
|
-
return
|
|
20
|
+
return hasErrorCode(e, 'EPERM', 'EBUSY', 'EACCES');
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
// Two writes of the same file in flight at once (a fire-and-forget save next to an awaited one) must not share a temp name.
|
|
@@ -30,6 +35,7 @@ export interface WriteAtomicOptions {
|
|
|
30
35
|
|
|
31
36
|
async function writeFlushed(path: string, content: string | Uint8Array, mode: number): Promise<void> {
|
|
32
37
|
const handle = await open(path, 'w', mode);
|
|
38
|
+
|
|
33
39
|
try {
|
|
34
40
|
await handle.writeFile(content);
|
|
35
41
|
await handle.sync();
|
|
@@ -43,7 +49,9 @@ async function syncDirectory(path: string): Promise<void> {
|
|
|
43
49
|
if (process.platform === 'win32') {
|
|
44
50
|
return;
|
|
45
51
|
}
|
|
52
|
+
|
|
46
53
|
const handle = await open(path, 'r');
|
|
54
|
+
|
|
47
55
|
try {
|
|
48
56
|
await handle.sync();
|
|
49
57
|
} finally {
|
|
@@ -58,22 +66,27 @@ async function syncDirectory(path: string): Promise<void> {
|
|
|
58
66
|
*/
|
|
59
67
|
export async function writeAtomic(target: string, content: string | Uint8Array, mode = 0o600, options: WriteAtomicOptions = {}): Promise<void> {
|
|
60
68
|
const temp = tempNameFor(target);
|
|
69
|
+
|
|
61
70
|
if (options.durable) {
|
|
62
71
|
await writeFlushed(temp, content, mode);
|
|
63
72
|
} else {
|
|
64
73
|
await writeFile(temp, content, { mode });
|
|
65
74
|
}
|
|
75
|
+
|
|
66
76
|
for (let attempt = 0; ; attempt++) {
|
|
67
77
|
try {
|
|
68
78
|
await rename(temp, target);
|
|
79
|
+
|
|
69
80
|
break;
|
|
70
81
|
} catch (e) {
|
|
71
82
|
if (!isTransient(e) || attempt >= RENAME_RETRIES) {
|
|
72
83
|
throw e;
|
|
73
84
|
}
|
|
74
|
-
|
|
85
|
+
|
|
86
|
+
await wait(RENAME_RETRY_MS);
|
|
75
87
|
}
|
|
76
88
|
}
|
|
89
|
+
|
|
77
90
|
if (options.durable) {
|
|
78
91
|
await syncDirectory(dirname(target));
|
|
79
92
|
}
|
|
@@ -88,11 +101,13 @@ export function replaceSync(temp: string, target: string): void {
|
|
|
88
101
|
for (let attempt = 0; ; attempt++) {
|
|
89
102
|
try {
|
|
90
103
|
renameSync(temp, target);
|
|
104
|
+
|
|
91
105
|
return;
|
|
92
106
|
} catch (e) {
|
|
93
107
|
if (!isTransient(e) || attempt >= RENAME_RETRIES) {
|
|
94
108
|
throw e;
|
|
95
109
|
}
|
|
110
|
+
|
|
96
111
|
sleepSync(RENAME_RETRY_MS);
|
|
97
112
|
}
|
|
98
113
|
}
|
|
@@ -105,7 +120,7 @@ export function writeAtomicSync(target: string, content: string | Uint8Array, mo
|
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
export function isNotFound(e: unknown): boolean {
|
|
108
|
-
return
|
|
123
|
+
return hasErrorCode(e, 'ENOENT');
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
// Bun on Windows fails a forced rm with EFAULT while another delete of the same file is pending; it goes either way.
|
|
@@ -113,7 +128,7 @@ export async function removeFile(path: string, remove: (path: string, options: {
|
|
|
113
128
|
try {
|
|
114
129
|
await remove(path, { force: true });
|
|
115
130
|
} catch (e) {
|
|
116
|
-
if (!(
|
|
131
|
+
if (!hasErrorCode(e, 'EFAULT')) {
|
|
117
132
|
throw e;
|
|
118
133
|
}
|
|
119
134
|
}
|
|
@@ -122,6 +137,7 @@ export async function removeFile(path: string, remove: (path: string, options: {
|
|
|
122
137
|
export async function fileExists(path: string): Promise<boolean> {
|
|
123
138
|
try {
|
|
124
139
|
await stat(path);
|
|
140
|
+
|
|
125
141
|
return true;
|
|
126
142
|
} catch {
|
|
127
143
|
return false;
|
package/src/host/agent-host.ts
CHANGED
|
@@ -53,34 +53,42 @@ export class AgentHost<Core extends ChatCore = ChatCore> {
|
|
|
53
53
|
static async open<Core extends ChatCore = ChatCore>(options: AgentHostOptions<Core>): Promise<AgentHost<Core>> {
|
|
54
54
|
const host = new AgentHost(options);
|
|
55
55
|
await host.accounts.load();
|
|
56
|
+
|
|
56
57
|
if (host.background) {
|
|
57
58
|
host.wiring.start();
|
|
58
59
|
}
|
|
60
|
+
|
|
59
61
|
return host;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
/* Serves one client over `port` until the returned function or `close` lets go of it. */
|
|
63
65
|
connect(port: FramePort): () => void {
|
|
64
66
|
const clientId = `port-${this.nextClient++}`;
|
|
67
|
+
|
|
65
68
|
const releases = [
|
|
66
69
|
port.onFrame((frame) => void this.receive(port, clientId, frame)),
|
|
67
70
|
this.wiring.connect(clientId, (event) => port.send({ type: 'event', event: event.event, payload: event.payload }))
|
|
68
71
|
];
|
|
72
|
+
|
|
69
73
|
const disconnect = (): void => {
|
|
70
74
|
if (!this.connections.delete(disconnect)) {
|
|
71
75
|
return;
|
|
72
76
|
}
|
|
77
|
+
|
|
73
78
|
for (const release of releases) {
|
|
74
79
|
release();
|
|
75
80
|
}
|
|
76
81
|
};
|
|
82
|
+
|
|
77
83
|
this.connections.add(disconnect);
|
|
84
|
+
|
|
78
85
|
return disconnect;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
/* Lets go of every client, writes every thread and ends every CLI; settles once they exited. */
|
|
82
89
|
close(): Promise<void> {
|
|
83
90
|
this.closing ??= this.shutDown();
|
|
91
|
+
|
|
84
92
|
return this.closing;
|
|
85
93
|
}
|
|
86
94
|
|
|
@@ -88,35 +96,47 @@ export class AgentHost<Core extends ChatCore = ChatCore> {
|
|
|
88
96
|
for (const disconnect of [...this.connections]) {
|
|
89
97
|
disconnect();
|
|
90
98
|
}
|
|
99
|
+
|
|
91
100
|
await this.wiring.stop();
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
private async receive(port: FramePort, clientId: string, frame: unknown): Promise<void> {
|
|
95
104
|
const parsed = parseRequest(frame);
|
|
105
|
+
|
|
96
106
|
if (!parsed.ok) {
|
|
97
107
|
// The id when the frame carried a usable one, so the client can settle its promise.
|
|
98
108
|
const id = typeof frame === 'object' && frame !== null && 'id' in frame && typeof frame.id === 'string' && frame.id !== '' ? frame.id : null;
|
|
99
109
|
port.send(errorReply(id, 'bad-request', parsed.message));
|
|
110
|
+
|
|
100
111
|
return;
|
|
101
112
|
}
|
|
113
|
+
|
|
102
114
|
const { id, type, payload } = parsed.value;
|
|
115
|
+
|
|
103
116
|
if (!isAgentRequest(type)) {
|
|
104
117
|
port.send(errorReply(id, 'unknown-request', `Unknown request type: ${type}`));
|
|
118
|
+
|
|
105
119
|
return;
|
|
106
120
|
}
|
|
121
|
+
|
|
107
122
|
const checked = AGENT_REQUEST_SCHEMAS[type].payload.safeParse(payload);
|
|
123
|
+
|
|
108
124
|
if (!checked.success) {
|
|
109
125
|
port.send(errorReply(id, 'bad-request', `Invalid payload for ${type}`));
|
|
126
|
+
|
|
110
127
|
return;
|
|
111
128
|
}
|
|
129
|
+
|
|
112
130
|
try {
|
|
113
131
|
const handler = this.wiring.handlers[type] as AgentHandler<AgentRequestType>;
|
|
114
132
|
port.send({ id, ok: true, result: await handler(checked.data as never, clientId) });
|
|
115
133
|
} catch (e) {
|
|
116
134
|
if (e instanceof CodedError) {
|
|
117
135
|
port.send(errorReply(id, e.code, e.message));
|
|
136
|
+
|
|
118
137
|
return;
|
|
119
138
|
}
|
|
139
|
+
|
|
120
140
|
console.error(`Handler for ${type} failed:`, errorText(e));
|
|
121
141
|
port.send(errorReply(id, 'internal', 'Request failed'));
|
|
122
142
|
}
|
package/src/host/environment.ts
CHANGED
|
@@ -13,19 +13,25 @@ const COMPATIBILITY_PREFIXES = ['RUIMTE_HOOK_', 'RUIMTE_CONTEXT_'];
|
|
|
13
13
|
export function cliEnvironment(env: Record<string, string | undefined> = process.env, policy: CliEnvironmentPolicy = {}): Record<string, string> {
|
|
14
14
|
const variables = new Set<string>([...RUIMTE_SESSION_VARIABLES, ...(policy.variables ?? [])]);
|
|
15
15
|
const prefixes = [...COMPATIBILITY_PREFIXES, ...(policy.prefixes ?? [])];
|
|
16
|
+
|
|
16
17
|
for (const host of policy.sessionPrefixes ?? []) {
|
|
17
18
|
const prefix = host.replace(/_+$/, '');
|
|
19
|
+
|
|
18
20
|
if (prefix === '') {
|
|
19
21
|
throw new Error('A session environment prefix must not be empty');
|
|
20
22
|
}
|
|
23
|
+
|
|
21
24
|
variables.add(`${prefix}_SESSION_ID`);
|
|
22
25
|
prefixes.push(`${prefix}_HOOK_`, `${prefix}_CONTEXT_`);
|
|
23
26
|
}
|
|
27
|
+
|
|
24
28
|
const clean: Record<string, string> = {};
|
|
29
|
+
|
|
25
30
|
for (const [name, value] of Object.entries(env)) {
|
|
26
31
|
if (value !== undefined && !variables.has(name) && !prefixes.some((prefix) => name.startsWith(prefix))) {
|
|
27
32
|
clean[name] = value;
|
|
28
33
|
}
|
|
29
34
|
}
|
|
35
|
+
|
|
30
36
|
return clean;
|
|
31
37
|
}
|
package/src/host/handlers.ts
CHANGED
|
@@ -42,6 +42,7 @@ export function chatHandlers(chats: ChatCore, providers: ProviderRegistry): { [T
|
|
|
42
42
|
// What a chat the host starts on its own is made with, while this client is connected.
|
|
43
43
|
'chat.setPreferences': (payload, clientId) => {
|
|
44
44
|
chats.composerPreferences.set(clientId, payload);
|
|
45
|
+
|
|
45
46
|
return {};
|
|
46
47
|
},
|
|
47
48
|
'chat.attach': (payload, clientId) => chats.attachWithBookmarks(payload.chatId, clientId, payload.historyLimit, payload.since),
|
|
@@ -49,18 +50,22 @@ export function chatHandlers(chats: ChatCore, providers: ProviderRegistry): { [T
|
|
|
49
50
|
'chat.subagent': (payload, clientId) => chats.subagent(clientId, payload),
|
|
50
51
|
'chat.stopSubagent': async (payload) => {
|
|
51
52
|
await chats.stopSubagent(payload.chatId, payload.toolUseId);
|
|
53
|
+
|
|
52
54
|
return {};
|
|
53
55
|
},
|
|
54
56
|
'chat.stopTask': async (payload) => {
|
|
55
57
|
await chats.stopTask(payload.chatId, payload.taskId);
|
|
58
|
+
|
|
56
59
|
return {};
|
|
57
60
|
},
|
|
58
61
|
'chat.goal': async (payload) => {
|
|
59
62
|
await chats.goal(payload.chatId, payload.action);
|
|
63
|
+
|
|
60
64
|
return {};
|
|
61
65
|
},
|
|
62
66
|
'chat.detach': (payload, clientId) => {
|
|
63
67
|
chats.detach(payload.chatId, clientId);
|
|
68
|
+
|
|
64
69
|
return {};
|
|
65
70
|
},
|
|
66
71
|
'chat.send': (payload) =>
|
|
@@ -83,15 +88,18 @@ export function chatHandlers(chats: ChatCore, providers: ProviderRegistry): { [T
|
|
|
83
88
|
'chat.unqueue': (payload) => ({ message: chats.unqueue(payload.chatId, payload.messageId) }),
|
|
84
89
|
'chat.sendNow': (payload) => {
|
|
85
90
|
chats.sendNow(payload.chatId, payload.messageId);
|
|
91
|
+
|
|
86
92
|
return {};
|
|
87
93
|
},
|
|
88
94
|
'skills.list': async (payload) => ({ skills: await chats.skills(payload.chatId) }),
|
|
89
95
|
'chat.compact': (payload) => {
|
|
90
96
|
chats.compact(payload.chatId);
|
|
97
|
+
|
|
91
98
|
return {};
|
|
92
99
|
},
|
|
93
100
|
'chat.clear': async (payload) => {
|
|
94
101
|
await chats.clear(payload.chatId, payload.force === true);
|
|
102
|
+
|
|
95
103
|
return {};
|
|
96
104
|
},
|
|
97
105
|
'chat.turnDiff': async (payload) => ({ diff: await chats.turnDiff(payload.chatId, payload.turnId) }),
|
|
@@ -101,18 +109,22 @@ export function chatHandlers(chats: ChatCore, providers: ProviderRegistry): { [T
|
|
|
101
109
|
'chat.continueOn': unsupported('continue a chat under another account'),
|
|
102
110
|
'chat.cancel': (payload) => {
|
|
103
111
|
chats.cancel(payload.chatId, payload.subagents === true);
|
|
112
|
+
|
|
104
113
|
return {};
|
|
105
114
|
},
|
|
106
115
|
'chat.approve': (payload) => {
|
|
107
116
|
chats.approve(payload.chatId, payload.requestId, payload.decision, payload.message);
|
|
117
|
+
|
|
108
118
|
return {};
|
|
109
119
|
},
|
|
110
120
|
'chat.answer': (payload) => {
|
|
111
121
|
chats.answer(payload.chatId, payload.requestId, payload.answers);
|
|
122
|
+
|
|
112
123
|
return {};
|
|
113
124
|
},
|
|
114
125
|
'chat.dismiss': (payload) => {
|
|
115
126
|
chats.dismiss(payload.chatId, payload.itemId);
|
|
127
|
+
|
|
116
128
|
return {};
|
|
117
129
|
},
|
|
118
130
|
'chat.addBookmark': async (payload) => ({ bookmarks: await chats.addBookmark(payload.chatId, payload.itemId, payload.name) }),
|
|
@@ -121,10 +133,12 @@ export function chatHandlers(chats: ChatCore, providers: ProviderRegistry): { [T
|
|
|
121
133
|
'chat.removeVisual': async (payload) => ({ visuals: await chats.removeVisual(payload.chatId, payload.visualId) }),
|
|
122
134
|
'chat.kill': async (payload) => {
|
|
123
135
|
await chats.kill(payload.chatId);
|
|
136
|
+
|
|
124
137
|
return {};
|
|
125
138
|
},
|
|
126
139
|
'chat.restart': async (payload) => {
|
|
127
140
|
await chats.restart(payload.chatId);
|
|
141
|
+
|
|
128
142
|
return {};
|
|
129
143
|
},
|
|
130
144
|
'chat.list': () => ({ chats: chats.list() })
|
|
@@ -139,6 +153,7 @@ export function accountHandlers(accounts: ProviderAccountsService): { [T in Acco
|
|
|
139
153
|
'accounts.create': (payload) => accounts.create(payload),
|
|
140
154
|
'accounts.watchLogin': (payload) => {
|
|
141
155
|
void accounts.watchLogin(payload.id);
|
|
156
|
+
|
|
142
157
|
return {};
|
|
143
158
|
}
|
|
144
159
|
};
|
|
@@ -150,15 +165,18 @@ export function usageHandlers(usage: UsageService, limits: UsageMonitor): { [T i
|
|
|
150
165
|
// While a client has the page open the host keeps scanning; every other client gets the event too.
|
|
151
166
|
'usage.subscribe': (_payload, clientId) => {
|
|
152
167
|
usage.follow(clientId);
|
|
168
|
+
|
|
153
169
|
return {};
|
|
154
170
|
},
|
|
155
171
|
'usage.unsubscribe': (_payload, clientId) => {
|
|
156
172
|
usage.unfollow(clientId);
|
|
173
|
+
|
|
157
174
|
return {};
|
|
158
175
|
},
|
|
159
176
|
'usage.limits': () => limits.snapshot(),
|
|
160
177
|
'usage.refreshLimits': async () => {
|
|
161
178
|
await limits.refresh(true);
|
|
179
|
+
|
|
162
180
|
return limits.snapshot();
|
|
163
181
|
}
|
|
164
182
|
};
|
package/src/host/memory-port.ts
CHANGED
|
@@ -7,9 +7,11 @@ import type { FramePort } from '@adecore/agent-contracts';
|
|
|
7
7
|
*/
|
|
8
8
|
export function memoryPortPair(): [FramePort, FramePort] {
|
|
9
9
|
const listeners: [Set<(frame: unknown) => void>, Set<(frame: unknown) => void>] = [new Set(), new Set()];
|
|
10
|
+
|
|
10
11
|
const end = (own: number): FramePort => ({
|
|
11
12
|
send: (frame) => {
|
|
12
13
|
const copy = structuredClone(frame);
|
|
14
|
+
|
|
13
15
|
queueMicrotask(() => {
|
|
14
16
|
for (const listener of listeners[1 - own]!) {
|
|
15
17
|
listener(copy);
|
|
@@ -18,10 +20,12 @@ export function memoryPortPair(): [FramePort, FramePort] {
|
|
|
18
20
|
},
|
|
19
21
|
onFrame: (listener) => {
|
|
20
22
|
listeners[own]!.add(listener);
|
|
23
|
+
|
|
21
24
|
return () => {
|
|
22
25
|
listeners[own]!.delete(listener);
|
|
23
26
|
};
|
|
24
27
|
}
|
|
25
28
|
});
|
|
29
|
+
|
|
26
30
|
return [end(0), end(1)];
|
|
27
31
|
}
|