@adecore/agents 0.21.1 → 0.21.2

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 (95) hide show
  1. package/dist/async.js +3 -8
  2. package/dist/chat/chat-core.d.ts +2 -2
  3. package/dist/chat/chat-core.js +4 -4
  4. package/dist/chat/chat-log.js +2 -2
  5. package/dist/chat/chat-store.js +4 -7
  6. package/dist/chat/chat-title.js +4 -8
  7. package/dist/chat/claude-backend.d.ts +1 -0
  8. package/dist/chat/claude-backend.js +8 -6
  9. package/dist/chat/claude-title.js +1 -1
  10. package/dist/chat/codex-backend.d.ts +2 -2
  11. package/dist/chat/codex-backend.js +5 -8
  12. package/dist/chat/codex-protocol.js +1 -1
  13. package/dist/chat/codex-thread.js +4 -5
  14. package/dist/chat/composer-preferences.js +2 -3
  15. package/dist/chat/limit-resume.js +2 -7
  16. package/dist/chat/visual-store.js +8 -6
  17. package/dist/context/refusal.js +3 -7
  18. package/dist/context/verb.js +1 -5
  19. package/dist/host/agent-host.js +2 -2
  20. package/dist/host/handlers.js +0 -1
  21. package/dist/host/wiring.js +1 -1
  22. package/dist/lineage.js +2 -3
  23. package/dist/messages/deliver-message.js +6 -11
  24. package/dist/messages/deliver-notice.js +8 -12
  25. package/dist/messages/notice-store.js +3 -5
  26. package/dist/outbox/outbox-worker.js +5 -2
  27. package/dist/providers/accounts/service.d.ts +2 -1
  28. package/dist/providers/accounts/service.js +11 -13
  29. package/dist/providers/claude.js +0 -1
  30. package/dist/providers/detect.js +2 -10
  31. package/dist/record-directory.js +0 -1
  32. package/dist/tasks/task-coordinator.d.ts +1 -0
  33. package/dist/tasks/task-coordinator.js +34 -34
  34. package/dist/tasks/task-store.js +1 -1
  35. package/dist/tasks/task-work.js +2 -2
  36. package/dist/tasks/waiting-child.js +5 -3
  37. package/dist/tasks/wake-parent.js +15 -10
  38. package/dist/tasks/wiring.js +32 -37
  39. package/dist/usage/aggregate.js +62 -46
  40. package/dist/usage/exchange.js +1 -1
  41. package/dist/usage/limits/monitor.d.ts +2 -2
  42. package/dist/usage/limits/monitor.js +2 -2
  43. package/dist/usage/limits/probe.js +1 -1
  44. package/dist/usage/pricing.js +1 -1
  45. package/dist/usage/roots.js +1 -2
  46. package/dist/usage/scanner.js +7 -9
  47. package/dist/usage/usage-service.d.ts +1 -2
  48. package/dist/usage/usage-service.js +3 -6
  49. package/package.json +3 -3
  50. package/src/async.ts +3 -9
  51. package/src/chat/chat-core.ts +4 -4
  52. package/src/chat/chat-log.ts +2 -2
  53. package/src/chat/chat-store.ts +4 -8
  54. package/src/chat/chat-title.ts +4 -8
  55. package/src/chat/claude-backend.ts +9 -6
  56. package/src/chat/claude-title.ts +1 -1
  57. package/src/chat/claude-transcript.ts +1 -1
  58. package/src/chat/codex-backend.ts +5 -8
  59. package/src/chat/codex-protocol.ts +1 -1
  60. package/src/chat/codex-thread.ts +6 -5
  61. package/src/chat/composer-preferences.ts +2 -3
  62. package/src/chat/limit-resume.ts +2 -7
  63. package/src/chat/thread.ts +1 -1
  64. package/src/chat/visual-store.ts +9 -6
  65. package/src/chat/wake-chat.ts +4 -1
  66. package/src/context/refusal.ts +3 -7
  67. package/src/context/verb.ts +1 -5
  68. package/src/host/agent-host.ts +2 -2
  69. package/src/host/handlers.ts +0 -1
  70. package/src/host/wiring.ts +1 -1
  71. package/src/lineage.ts +2 -3
  72. package/src/messages/deliver-message.ts +6 -11
  73. package/src/messages/deliver-notice.ts +8 -12
  74. package/src/messages/notice-store.ts +3 -5
  75. package/src/outbox/outbox-worker.ts +5 -2
  76. package/src/providers/accounts/service.ts +12 -13
  77. package/src/providers/accounts/variables.ts +1 -0
  78. package/src/providers/claude.ts +0 -1
  79. package/src/providers/detect.ts +2 -11
  80. package/src/record-directory.ts +0 -1
  81. package/src/skills.ts +0 -1
  82. package/src/tasks/task-coordinator.ts +35 -33
  83. package/src/tasks/task-store.ts +1 -1
  84. package/src/tasks/task-work.ts +2 -2
  85. package/src/tasks/waiting-child.ts +6 -3
  86. package/src/tasks/wake-parent.ts +21 -16
  87. package/src/tasks/wiring.ts +33 -41
  88. package/src/usage/aggregate.ts +80 -50
  89. package/src/usage/exchange.ts +1 -1
  90. package/src/usage/limits/monitor.ts +4 -4
  91. package/src/usage/limits/probe.ts +2 -2
  92. package/src/usage/pricing.ts +1 -1
  93. package/src/usage/roots.ts +1 -2
  94. package/src/usage/scanner.ts +8 -10
  95. package/src/usage/usage-service.ts +5 -9
package/dist/async.js CHANGED
@@ -1,8 +1,6 @@
1
- /* The two waits the host does: a pause it can be woken from, and a deadline on somebody else's promise. */
2
1
  /*
3
- * Resolves after `ms`, or at once when the signal is raised. An abort ends the wait rather than
4
- * failing it: every caller here is a loop that checks the signal itself on the next turn, and a
5
- * rejection would only be caught and thrown away.
2
+ * Resolves after `ms`, or at once when the signal is raised. An abort resolves instead of rejecting:
3
+ * every caller is a loop that checks the signal itself on its next turn.
6
4
  */
7
5
  export function wait(ms, signal) {
8
6
  return new Promise((resolve) => {
@@ -13,10 +11,7 @@ export function wait(ms, signal) {
13
11
  }, { once: true });
14
12
  });
15
13
  }
16
- /*
17
- * The promise, or `message` when it takes longer than `ms`. The timer is cleared either way, so a
18
- * call that answers at once does not hold the process for the rest of its deadline.
19
- */
14
+ /* The timer is cleared either way, so a call that answers at once does not hold the process until its deadline. */
20
15
  export function withTimeout(promise, ms, message) {
21
16
  return new Promise((resolve, reject) => {
22
17
  const timer = setTimeout(() => reject(new Error(message)), ms);
@@ -1,5 +1,4 @@
1
1
  import { type AgentKind, type ChatAttachment, type ChatAttachResult, type ChatAttachmentUpload, type ChatBookmark, type ChatCheckpointDiff, type ChatConfigurePayload, type ChatCreatePayload, type ChatEvent, type ChatHistoryResult, type ChatInfo, type ChatItem, type ChatQueuedMessage, type ChatUiChoicePayload, type ChatUiQueryPayload, type ChatUiLinkPayload, type ChatSkill, type ChatSubagentPayload, type ChatSubagentResult, type ChatTurnItem, type ChatVisual, type ModelSelection, type RuntimeMode } from '@adecore/agent-contracts';
2
- import { type ChatUiHost } from './ui-queries.ts';
3
2
  import { ClientSinks } from '../client-sinks.ts';
4
3
  import type { AgentEvent, AgentSink } from '../events.ts';
5
4
  import type { OutboxOutcome } from '../outbox/outbox-worker.ts';
@@ -8,6 +7,7 @@ import type { ProviderRegistry } from '../providers/registry.ts';
8
7
  import { SkillIndex } from '../skills.ts';
9
8
  import type { LimitsUpdate } from '../usage/limits/normalize.ts';
10
9
  import type { AttachmentStore } from './attachment-store.ts';
10
+ import { type BackgroundWork } from './background-work.ts';
11
11
  import type { BookmarkStore } from './bookmark-store.ts';
12
12
  import type { SpawnChatProcess } from './chat-process.ts';
13
13
  import { ChatSession, type ChatReferences, type ChatSendExtras, type PromptNotes, type ResumeWords, type TurnCheckpoints } from './chat-session.ts';
@@ -17,8 +17,8 @@ import type { CodexProcessSpec } from './codex-thread.ts';
17
17
  import type { CodexClientInfo } from './codex-transport.ts';
18
18
  import { ComposerPreferences } from './composer-preferences.ts';
19
19
  import { DeltaCoalescer } from './delta-coalescer.ts';
20
- import { type BackgroundWork } from './background-work.ts';
21
20
  import { SubagentReader, type SubagentReaderOptions } from './subagent-reader.ts';
21
+ import { type ChatUiHost } from './ui-queries.ts';
22
22
  import type { VisualInput, VisualStore } from './visual-store.ts';
23
23
  export interface InterruptedRun {
24
24
  chatId: string;
@@ -1,25 +1,25 @@
1
1
  import { homedir } from 'node:os';
2
2
  import { join } from 'node:path';
3
3
  import { ChatAttachmentUploadsSchema, generatedImageAttachment } from '@adecore/agent-contracts';
4
- import { ChatUiQueries } from './ui-queries.js';
5
4
  import { ClientSinks } from '../client-sinks.js';
6
5
  import { errorText } from '../error-text.js';
7
6
  import { AccountError, definedEnv, isDefaultAccountOf, launchEnv, storedAccount } from '../providers/accounts/launch.js';
8
7
  import { SkillIndex } from '../skills.js';
9
8
  import { usageRoots } from '../usage/roots.js';
9
+ import { commandLabel, isBackgroundWork, runsInBackground } from './background-work.js';
10
10
  import { ChatLog, COMPACT_ABOVE_BYTES } from './chat-log.js';
11
11
  import { ChatSession } from './chat-session.js';
12
12
  import { ComposerPreferences } from './composer-preferences.js';
13
13
  import { DeltaCoalescer } from './delta-coalescer.js';
14
- import { readableAssistantText } from './readable-text.js';
15
14
  import { ChatError } from './errors.js';
16
- import { commandLabel, isBackgroundWork, runsInBackground } from './background-work.js';
15
+ import { readableAssistantText } from './readable-text.js';
17
16
  import { SubagentReader } from './subagent-reader.js';
18
- /* A message a person or the agent wrote in the chat's own thread; a subagent's words belong to its row. */
17
+ import { ChatUiQueries } from './ui-queries.js';
19
18
  function withoutHidden(info) {
20
19
  const { hidden: _hidden, ...rest } = info;
21
20
  return rest;
22
21
  }
22
+ /* A message a person or the agent wrote in the chat's own thread; a subagent's words belong to its row. */
23
23
  function isBookmarkable(item) {
24
24
  return item.kind === 'user' || (item.kind === 'assistant' && (item.parentToolUseId ?? null) === null);
25
25
  }
@@ -31,8 +31,8 @@ export function parseLog(raw) {
31
31
  return lines;
32
32
  }
33
33
  /*
34
- * Number and synchronously append each broadcast event so disk, memory and snapshot boundaries keep
35
- * one order. Recent lines stay in memory for immediate `since` attachment.
34
+ * Numbers every broadcast event and appends it synchronously, so disk, memory and snapshot boundaries
35
+ * keep one order. Recent lines stay in memory to answer an attach with `since` at once.
36
36
  */
37
37
  export class ChatLog {
38
38
  path;
@@ -4,12 +4,12 @@ import { join } from 'node:path';
4
4
  import { ChatInfoSchema, ChatItemSchema } from '@adecore/agent-contracts';
5
5
  import { z } from 'zod';
6
6
  import { isNotFound, writeAtomic, writeAtomicSync } from '../fs.js';
7
- import { isBookmarkFileName } from './bookmark-store.js';
7
+ import { recordFileName } from '../record-directory.js';
8
8
  import { migrateInlineAttachments } from './attachment-store.js';
9
+ import { isBookmarkFileName } from './bookmark-store.js';
9
10
  import { parseLog } from './chat-log.js';
10
11
  import { ChatThread } from './thread.js';
11
12
  import { isVisualFileName } from './visual-store.js';
12
- import { recordFileName } from '../record-directory.js';
13
13
  // zod strips what it does not know inside the info and the items, so a file written by an older build
14
14
  // (with `interactionMode`, say) still parses and loses only the dropped field. A record without `seq`
15
15
  // predates the log. Keys beside these are the host's own, and come back to it as they were written.
@@ -24,7 +24,7 @@ const RecordSchema = z.looseObject({
24
24
  uiAccess: z.record(z.string(), z.unknown()).optional()
25
25
  });
26
26
  const OWN_KEYS = new Set(['info', 'items', 'seq', 'resetSeq', 'preambles', 'uiAccess']);
27
- function extrasOf(record) {
27
+ function withoutOwnKeys(record) {
28
28
  return Object.fromEntries(Object.entries(record).filter(([key]) => !OWN_KEYS.has(key)));
29
29
  }
30
30
  function logName(chatId) {
@@ -54,9 +54,6 @@ function liftUiAccess(record) {
54
54
  }
55
55
  return Object.keys(uiAccess).length === 0 ? record : { ...record, uiAccess };
56
56
  }
57
- function withoutOwnKeys(extras) {
58
- return Object.fromEntries(Object.entries(extras).filter(([key]) => !OWN_KEYS.has(key)));
59
- }
60
57
  /*
61
58
  * One JSON file per chat under `<home>/chats`, written while a turn runs and on shutdown, with
62
59
  * the log of what happened since beside it (`ChatLog` appends to it; reading plays it back).
@@ -146,7 +143,7 @@ export class ChatStore {
146
143
  resetSeq,
147
144
  preambles: snapshot.preambles ?? [],
148
145
  uiAccess: snapshot.uiAccess ?? {},
149
- extras: extrasOf(snapshot),
146
+ extras: withoutOwnKeys(snapshot),
150
147
  lines
151
148
  };
152
149
  }
@@ -1,5 +1,5 @@
1
- import { cleanTitle } from '../title-file.js';
2
1
  import { runProcess } from '../run-process.js';
2
+ import { cleanTitle } from '../title-file.js';
3
3
  // Enough of a conversation to name it; the rest of a long prompt or answer says nothing a title needs.
4
4
  const MAX_PROMPT_CHARS = 2000;
5
5
  const MAX_ANSWER_CHARS = 1500;
@@ -8,10 +8,7 @@ const TIMEOUT_MS = 45_000;
8
8
  function cap(text, limit) {
9
9
  return text.length <= limit ? text : `${text.slice(0, limit)}...`;
10
10
  }
11
- /*
12
- * What the CLI is asked. JSON again, as for a commit message: a model left to write freely opens
13
- * with a sentence, and that sentence would become the name of the node.
14
- */
11
+ /* JSON, since a model left to write freely opens with a sentence, and that sentence would become the name. */
15
12
  export function buildTitlePrompt(prompt, answer) {
16
13
  return [
17
14
  'Write a short title for the conversation below, as a person would name it in a list of chats.',
@@ -30,9 +27,8 @@ export function buildTitlePrompt(prompt, answer) {
30
27
  ].join('\n');
31
28
  }
32
29
  /*
33
- * The title out of what the CLI printed, or null. Strict on purpose, unlike a commit message: a run
34
- * that holds no `{"title": "..."}` object printed something other than a title, and a derived name
35
- * is better than a stray line of it.
30
+ * Strict on purpose: a run that holds no `{"title": "..."}` object printed something other than a
31
+ * title, and a derived name is better than a stray line of it.
36
32
  */
37
33
  export function parseTitle(output) {
38
34
  const start = [...output.matchAll(/\{\s*"title"/g)].at(-1)?.index;
@@ -27,6 +27,7 @@ export declare class ClaudeBackend implements ChatBackend {
27
27
  declineRequest(requestId: string, message: string): boolean;
28
28
  stop(): void;
29
29
  dispose(): Promise<void>;
30
+ private userMessage;
30
31
  private readLines;
31
32
  private handleLine;
32
33
  private readLine;
@@ -1,5 +1,5 @@
1
- import { errorText } from '../error-text.js';
2
1
  import { randomUUID } from 'node:crypto';
2
+ import { errorText } from '../error-text.js';
3
3
  import { claudeArgs, claudeEnv, promptPrefix } from '../providers/claude.js';
4
4
  import { ChatChild } from './chat-process.js';
5
5
  import { ClaudeProtocol } from './claude-protocol.js';
@@ -59,12 +59,10 @@ export class ClaudeBackend {
59
59
  return Promise.resolve();
60
60
  }
61
61
  sendTurn(input) {
62
- // The prefix is what the CLI must see first (ultrathink), then the note about the links, then what was typed.
63
- const prefix = `${promptPrefix(this.launch.selection)}${input.preamble === null ? '' : `${input.preamble}\n\n`}`;
64
62
  const promptId = input.promptId ?? randomUUID();
65
63
  this.active = true;
66
64
  this.protocol.beginPrompt(promptId);
67
- this.write(buildUserMessage({ text: input.text, attachments: input.attachments, prefix, skills: input.skills, promptId }));
65
+ this.write(this.userMessage(input, promptId));
68
66
  }
69
67
  steerTurn(input) {
70
68
  if (!this.child || !this.active || this.stdinClosed) {
@@ -75,8 +73,7 @@ export class ClaudeBackend {
75
73
  this.steers.set(promptId, { resolve, reject });
76
74
  this.protocol.beginSteer(promptId);
77
75
  try {
78
- const prefix = `${promptPrefix(this.launch.selection)}${input.preamble === null ? '' : `${input.preamble}\n\n`}`;
79
- this.write(buildUserMessage({ text: input.text, attachments: input.attachments, prefix, skills: input.skills, promptId }));
76
+ this.write(this.userMessage(input, promptId));
80
77
  }
81
78
  catch (error) {
82
79
  this.steers.delete(promptId);
@@ -126,6 +123,11 @@ export class ClaudeBackend {
126
123
  this.child = null;
127
124
  return child?.end() ?? Promise.resolve();
128
125
  }
126
+ userMessage(input, promptId) {
127
+ // What the CLI must see first (ultrathink), then the preamble, then what was typed.
128
+ const prefix = `${promptPrefix(this.launch.selection)}${input.preamble === null ? '' : `${input.preamble}\n\n`}`;
129
+ return buildUserMessage({ text: input.text, attachments: input.attachments, prefix, skills: input.skills, promptId });
130
+ }
129
131
  async readLines(child) {
130
132
  const reader = child.process.stdout.getReader();
131
133
  const decoder = new TextDecoder();
@@ -1,8 +1,8 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { readdir, stat } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
- import { usageRoots } from '../usage/roots.js';
5
4
  import { cleanTitle, readLines } from '../title-file.js';
5
+ import { usageRoots } from '../usage/roots.js';
6
6
  // The two records carry these words; a line without them is never parsed.
7
7
  const MARKERS = ['"ai-title"', '"custom-title"'];
8
8
  /*
@@ -7,12 +7,12 @@ export interface CodexBackendOptions {
7
7
  }
8
8
  export declare class CodexBackend implements ChatBackend {
9
9
  readonly acknowledgesTurns = true;
10
- private promptId;
11
10
  private readonly launch;
12
- private imageWrites;
13
11
  private readonly host;
14
12
  private readonly protocol;
15
13
  private readonly client;
14
+ private promptId;
15
+ private imageWrites;
16
16
  private transport;
17
17
  private threadId;
18
18
  private contextPending;
@@ -1,10 +1,10 @@
1
1
  import { accessSync, constants } from 'node:fs';
2
2
  import { attachmentImageMime } from '@adecore/agent-contracts';
3
+ import { errorText } from '../error-text.js';
3
4
  import { codexServiceTier, codexThreadOptions } from '../providers/codex.js';
4
5
  import { CodexProtocol } from './codex-protocol.js';
5
- import { attachmentNote } from './input.js';
6
6
  import { CodexTransport, DEFAULT_CODEX_CLIENT } from './codex-transport.js';
7
- import { errorText } from '../error-text.js';
7
+ import { attachmentNote } from './input.js';
8
8
  function textInput(text) {
9
9
  return [{ type: 'text', text, text_elements: [] }];
10
10
  }
@@ -37,18 +37,15 @@ export function parseSkillsList(result) {
37
37
  */
38
38
  export class CodexBackend {
39
39
  acknowledgesTurns = true;
40
- promptId = null;
41
40
  launch;
42
- imageWrites = null;
43
41
  host;
44
42
  protocol;
45
43
  client;
44
+ promptId = null;
45
+ imageWrites = null;
46
46
  transport = null;
47
47
  threadId = '';
48
- /*
49
- * Codex 0.154 ignores developer instructions on `thread/resume` (measured), so a thread started
50
- * before anything was linked would never hear of its links; the first prompt of a resumed process says so instead.
51
- */
48
+ // Codex 0.154 ignores developer instructions on `thread/resume` (measured), so the first prompt of a resumed process carries them.
52
49
  contextPending = null;
53
50
  imageInputSupported = null;
54
51
  /* A turn asked for that Codex has not named yet; `turn/interrupt` needs that name. */
@@ -130,7 +130,7 @@ function elicitationInput(meta) {
130
130
  }
131
131
  return input;
132
132
  }
133
- // Every spent window must reset; one unknown reset keeps resuming a person's choice.
133
+ // The latest reset of every spent window; one without a known reset leaves resuming to a person.
134
134
  function spentUntil(windows) {
135
135
  const spent = windows.filter((window) => window.used >= 1);
136
136
  if (spent.length === 0 || spent.some((window) => window.resetsAt === null)) {
@@ -1,8 +1,8 @@
1
+ import { errorText } from '../error-text.js';
1
2
  import { CodexProtocol } from './codex-protocol.js';
2
3
  import { CodexTransport, DEFAULT_CODEX_CLIENT } from './codex-transport.js';
3
4
  import { ChatError } from './errors.js';
4
5
  import { readingThread, settledReading } from './subagent-projection.js';
5
- import { errorText } from '../error-text.js';
6
6
  function isRecord(value) {
7
7
  return typeof value === 'object' && value !== null && !Array.isArray(value);
8
8
  }
@@ -85,14 +85,13 @@ async function nthTurnId(transport, threadId, turns) {
85
85
  let seen = 0;
86
86
  let cursor = null;
87
87
  do {
88
- const page = await transport.request('thread/turns/list', { threadId, limit: 100, sortDirection: 'asc', ...(cursor === null ? {} : { cursor }) });
89
- const data = isRecord(page) && Array.isArray(page.data) ? page.data.filter(isRecord) : [];
90
- for (const turn of data) {
88
+ const page = parseThreadItemsPage(await transport.request('thread/turns/list', { threadId, limit: 100, sortDirection: 'asc', ...(cursor === null ? {} : { cursor }) }));
89
+ for (const turn of page.entries) {
91
90
  if (++seen === turns) {
92
91
  return typeof turn.id === 'string' ? turn.id : null;
93
92
  }
94
93
  }
95
- cursor = isRecord(page) && typeof page.nextCursor === 'string' && page.nextCursor !== '' ? page.nextCursor : null;
94
+ cursor = page.nextCursor;
96
95
  } while (cursor !== null);
97
96
  return null;
98
97
  }
@@ -1,8 +1,7 @@
1
1
  /*
2
2
  * What each connected client says a new chat starts with, for the chats the host starts with no
3
- * client mounting them. Held per socket like the approval switch: a machine never remembers a
4
- * person's pick after that person's client is gone, so with nobody connected a chat gets the
5
- * host's own defaults.
3
+ * client mounting them. Held per client only, so with nobody connected a chat gets the host's own
4
+ * defaults rather than the pick of a person who left.
6
5
  */
7
6
  export class ComposerPreferences {
8
7
  byClient = new Map();
@@ -2,13 +2,8 @@
2
2
  export const LIMIT_RETRY_DELAYS_MS = [60_000, 5 * 60_000, 15 * 60_000];
3
3
  /* The last turn of a thread, when it stopped on a limit. */
4
4
  export function limitedTurn(items) {
5
- for (let i = items.length - 1; i >= 0; i--) {
6
- const item = items[i];
7
- if (item.kind === 'turn') {
8
- return item.state === 'error' && item.limit !== undefined ? item : null;
9
- }
10
- }
11
- return null;
5
+ const last = items.findLast((item) => item.kind === 'turn');
6
+ return last?.kind === 'turn' && last.state === 'error' && last.limit !== undefined ? last : null;
12
7
  }
13
8
  /* How many turns in a row, up to and including this one, stopped on a limit: the tries a person did not step in between. */
14
9
  function limitedInARow(items, turn) {
@@ -59,6 +59,12 @@ function tooLarge(bytes) {
59
59
  return refuse('visual-too-large', `The page is ${mebibytes(bytes)} and a visual may be at most ${mebibytes(VISUAL_LIMITS.bytes)}; ` +
60
60
  'load libraries from a public CDN URL and refer to large images by their http(s) URL instead of inlining them');
61
61
  }
62
+ function refuseOversized(html) {
63
+ const bytes = Buffer.byteLength(html, 'utf8');
64
+ if (bytes > VISUAL_LIMITS.bytes) {
65
+ tooLarge(bytes);
66
+ }
67
+ }
62
68
  /*
63
69
  * The visuals of every chat. The list of a chat is one file beside its record under `<home>/chats`;
64
70
  * each page is a file in the chat's attachment folder, `<id>.html`, so whatever serves an attachment
@@ -104,9 +110,7 @@ export class VisualStore {
104
110
  if (html.trim() === '') {
105
111
  refuse('visual-invalid', 'The page is empty; pass one self-contained HTML document');
106
112
  }
107
- if (Buffer.byteLength(html, 'utf8') > VISUAL_LIMITS.bytes) {
108
- tooLarge(Buffer.byteLength(html, 'utf8'));
109
- }
113
+ refuseOversized(html);
110
114
  return this.writeWorkspaceFile(chatId, name, html);
111
115
  }
112
116
  writePreview(chatId, png) {
@@ -116,9 +120,7 @@ export class VisualStore {
116
120
  async publish(chatId, input) {
117
121
  const { title, html, maxHeight, layout, heights } = checked(input);
118
122
  // Before the bootstrap is added, so a page far too large is never copied first.
119
- if (Buffer.byteLength(html, 'utf8') > VISUAL_LIMITS.bytes) {
120
- tooLarge(Buffer.byteLength(html, 'utf8'));
121
- }
123
+ refuseOversized(html);
122
124
  const page = Buffer.from(injectVisualBootstrap(html), 'utf8');
123
125
  if (page.byteLength > VISUAL_LIMITS.bytes) {
124
126
  tooLarge(page.byteLength);
@@ -1,8 +1,7 @@
1
1
  /*
2
2
  * How every no reads: `refused`, the code a script branches on and the sentence a model reads, and
3
- * under it a line per thing the actor can do instead. One row per line, and a line is a row of
4
- * tab-separated fields of its own, so only a newline is taken out of it: that would split the row in
5
- * two, and everything under the first row is read as advice.
3
+ * under it a line per thing the actor can do instead. A line keeps its tabs but loses its newlines,
4
+ * since everything under the first row is read as advice.
6
5
  */
7
6
  /* A field that goes into a tab-separated line; a tab or a newline in a title would split the row. */
8
7
  export function field(value) {
@@ -19,10 +18,7 @@ export function refusalBody(code, message, lines = []) {
19
18
  const [first, ...rest] = rows(code, message, lines);
20
19
  return [`refused\t${first}`, ...rest].join('\n');
21
20
  }
22
- /*
23
- * The same without the word that names it, for a host that adds the prefix in its CLI:
24
- * The context CLI puts the prefix on, so a CLI of an older build still prints one refusal.
25
- */
21
+ /* Without the `refused` prefix, for a host whose CLI adds it, so a CLI of an older build still prints one refusal. */
26
22
  export function refusalRows(code, message, lines = []) {
27
23
  return rows(code, message, lines).join('\n');
28
24
  }
@@ -223,11 +223,7 @@ export function lengthOf(input) {
223
223
  export function requiredField(needs) {
224
224
  return z.string({ error: needs }).min(1, needs);
225
225
  }
226
- /*
227
- * What a refusal lists, or one line saying the set is empty. A refusal that promises the groups of a
228
- * canvas and then prints nothing reads as a daemon that lost them, where the truth is that there are
229
- * none, which is a different thing to do something about.
230
- */
226
+ /* A refusal that promises a list and prints nothing reads as a host that lost it, where there is none. */
231
227
  export function orNote(lines, note) {
232
228
  return lines.length === 0 ? [`note\t${note}`] : lines;
233
229
  }
@@ -10,8 +10,8 @@ function isAgentRequest(type) {
10
10
  }
11
11
  /*
12
12
  * The chats of an app that has no daemon: it answers the agent requests over any `FramePort` and
13
- * sends the agent events back on it, frames checked on arrival. One port is one client. Closing the host writes every thread and ends every CLI; a chat
14
- * goes on at the next start through its CLI's own session.
13
+ * sends the agent events back on it, frames checked on arrival. One port is one client. A chat
14
+ * closed with the host goes on at the next start through its CLI's own session.
15
15
  */
16
16
  export class AgentHost {
17
17
  chats;
@@ -81,7 +81,6 @@ export function chatHandlers(chats, providers) {
81
81
  'chat.list': () => ({ chats: chats.list() })
82
82
  };
83
83
  }
84
- /* The accounts are the person's settings of this host. */
85
84
  export function accountHandlers(accounts) {
86
85
  return {
87
86
  'accounts.list': () => accounts.snapshot(),
@@ -6,10 +6,10 @@ import { suggestChatTitle } from '../chat/chat-title.js';
6
6
  import { VisualStore } from '../chat/visual-store.js';
7
7
  import { ClaudeTitleReader } from '../chat/claude-title.js';
8
8
  import { DEFAULT_CODEX_CLIENT } from '../chat/codex-transport.js';
9
+ import { errorText } from '../error-text.js';
9
10
  import { definedEnv } from '../providers/accounts/launch.js';
10
11
  import { ProviderAccountsService } from '../providers/accounts/service.js';
11
12
  import { createClaudeProvider } from '../providers/claude-provider.js';
12
- import { errorText } from '../error-text.js';
13
13
  import { createCodexProvider } from '../providers/codex-provider.js';
14
14
  import { codexRulesPathIn, defaultCodexHome, installCodexRules } from '../providers/codex-rules.js';
15
15
  import { ProviderRegistry } from '../providers/registry.js';
package/dist/lineage.js CHANGED
@@ -25,8 +25,8 @@ function openedByAgent(entry) {
25
25
  return entry.agent && entry.relation !== 'fork';
26
26
  }
27
27
  /*
28
- * Lineage lives in `lineage` under the host's data folder, outside the agent-writable project, so agents
29
- * cannot reset their own depth. Persisting it also prevents a restart of the host from resetting recursion limits.
28
+ * Lives under the host's data folder, outside the agent-writable project, so an agent cannot reset its
29
+ * own depth; on disk, so a restart of the host does not reset it either.
30
30
  */
31
31
  export class AgentLineageStore {
32
32
  dir;
@@ -117,7 +117,6 @@ export class AgentLineageStore {
117
117
  const entry = this.opened.get(nodeId);
118
118
  return entry?.relation === 'fork' ? entry.openedBy : null;
119
119
  }
120
- /* The forks made from a chat. */
121
120
  forksOf(nodeId) {
122
121
  return [...this.opened.all()].filter((entry) => entry.relation === 'fork' && entry.openedBy === nodeId).map((entry) => entry.nodeId);
123
122
  }
@@ -5,13 +5,11 @@ export const DeliverMessageWorkSchema = z.object({
5
5
  payload: z.object({ from: z.string().min(1) })
6
6
  });
7
7
  /*
8
- * Opens the turn a message earns a chat. The messages themselves are not carried here: they wait in
9
- * the notice store and the turn's preamble takes them (`NoticeNotes`), the same channel a person's
10
- * own prompt reads them through, so one message is heard once whichever of the two opens the turn.
8
+ * Opens the turn a message earns a chat. The messages wait in the notice store and the turn's preamble
9
+ * takes them (`NoticeNotes`), as a person's own prompt does, so a message is heard once either way.
11
10
  *
12
- * One attempt, never `wait`. A chat that turned out to be in a turn reads the message in front of its
13
- * next one, which is what a message to a busy chat has always done; holding the entry instead would
14
- * make the host start turns nobody asked for long after the news was worth anything.
11
+ * One attempt, never `wait`: a chat already in a turn reads the message in front of its next one, and
12
+ * holding the entry would start turns nobody asked for long after the news mattered.
15
13
  */
16
14
  export function deliverMessageHandler(deps) {
17
15
  return async (entry) => {
@@ -27,11 +25,8 @@ export function deliverMessageHandler(deps) {
27
25
  if (chat === null) {
28
26
  return;
29
27
  }
30
- /*
31
- * No note of its own, and the preamble stays off the thread. A person read the message as it
32
- * landed (`showNotices`), so anything the turn adds about it only says the same thing again;
33
- * the label on the turn already names who it came from.
34
- */
28
+ // No note and no preamble on the thread: a person read the message as it landed (`showNotices`),
29
+ // and the label on the turn names who it came from.
35
30
  chat.wake({
36
31
  text: deps.words.prompt(waiting.length),
37
32
  label: deps.words.label(waiting),
@@ -1,9 +1,8 @@
1
1
  /*
2
- * Whether the turn a chat is running was itself opened by a message. This is the whole of the stop:
3
- * two agents that read each other would otherwise wake each other for as long as they kept writing,
4
- * and nothing about that started with a person. It rides on the turn, so it survives a restart.
2
+ * Whether the turn a chat is running was itself opened by a message, which stops two agents that
3
+ * read each other from waking each other forever. It rides on the turn, so it survives a restart.
5
4
  * Between turns the last one counts, since work it left running (a background subagent) may still
6
- * write, and a turn the CLI opened by itself goes on with the step of the turn before it.
5
+ * write, and a turn the CLI opened by itself takes the step of the turn before it.
7
6
  */
8
7
  export function turnFromMessage(items, activeTurnId) {
9
8
  const from = activeTurnId === null ? items.findLastIndex((item) => item.kind === 'turn') : items.findIndex((item) => item.id === activeTurnId);
@@ -40,10 +39,9 @@ export function chatNoticeTargets(chats) {
40
39
  }
41
40
  /*
42
41
  * One message on its way to a chat, and whether the chat owes a turn on it. A chat between turns gets
43
- * one, the way a settled task gives one: a person watching two agents cannot tell a message from an
44
- * assignment, and a message nobody starts a turn for sits there until someone happens to prompt that
45
- * chat. One step deep, so the turn a message opened wakes nobody with a message of its own. A node the
46
- * host runs something else in, such as a terminal, is the host's to deliver to before this.
42
+ * one, as for a settled task, or the message sits there until someone happens to prompt that chat.
43
+ * One step deep: the turn a message opened wakes nobody with a message of its own. A node that runs
44
+ * something else, such as a terminal, is the host's to deliver to before this.
47
45
  */
48
46
  export async function deliverToChat(store, targets, notice) {
49
47
  const waiting = await store.put(notice);
@@ -60,10 +58,8 @@ export async function deliverToChat(store, targets, notice) {
60
58
  return { outcome: 'wake', waiting };
61
59
  }
62
60
  /*
63
- * What a chat has to show a person, in its thread, the moment a message lands and not when the model
64
- * gets round to it: without this a message left for a busy node is a file on disk and nothing else.
65
- * An id no chat holds shows nothing and marks nothing, so the chat that opens on that id later still
66
- * has all of it.
61
+ * Shows a person the message in the chat's thread the moment it lands, not when the model gets round
62
+ * to it. An id no chat holds shows and marks nothing, so a chat that opens on it later still has all of it.
67
63
  */
68
64
  export async function showNotices(store, chat, words, targetId) {
69
65
  if (!(await chat.has(targetId))) {
@@ -5,10 +5,8 @@ import { RecordDirectory } from '../record-directory.js';
5
5
  // Per receiver. Past this the oldest goes: an agent in a loop must not fill a queue nobody reads.
6
6
  export const MAX_NOTICES = 10;
7
7
  /*
8
- * A message nobody picked up in this long is dropped rather than delivered. It is news about work
9
- * happening now ("the build is green"), and a model reading it six hours late would act on
10
- * something that has moved on. The cap alone would not do it: one message in a queue of one waits
11
- * for ever.
8
+ * A message is news about work happening now ("the build is green"), and a model reading it six hours
9
+ * late would act on something that moved on. The cap alone would leave a queue of one waiting forever.
12
10
  */
13
11
  export const NOTICE_MAX_AGE_MS = 6 * 60 * 60 * 1000;
14
12
  const NoticeSchema = z.object({
@@ -109,7 +107,7 @@ export class NoticeStore {
109
107
  fresh(notice) {
110
108
  return this.now() - notice.createdAt < NOTICE_MAX_AGE_MS;
111
109
  }
112
- /* The queue of one node, emptied here and on disk both; an empty one leaves no file behind. */
110
+ /* In memory and on disk both; an empty queue leaves no file behind. */
113
111
  async persist(targetId, queue) {
114
112
  if (queue.length === 0) {
115
113
  await this.queues.remove(targetId);
@@ -80,6 +80,9 @@ export class OutboxWorker {
80
80
  const now = this.clock.now();
81
81
  const busy = new Set(this.running);
82
82
  let nextDue = null;
83
+ const dueAt = (at) => {
84
+ nextDue = nextDue === null ? at : Math.min(nextDue, at);
85
+ };
83
86
  for (const entry of this.store.list()) {
84
87
  // A waiting entry holds no lane. The resume of a chat must not queue behind a wake that waits for it.
85
88
  if (busy.has(entry.target) || this.waiting.has(entry.id)) {
@@ -87,7 +90,7 @@ export class OutboxWorker {
87
90
  }
88
91
  // Nor does work due at a later time, which nothing owed before it has to wait for.
89
92
  if (entry.attempts === 0 && entry.notBefore > now) {
90
- nextDue = nextDue === null ? entry.notBefore : Math.min(nextDue, entry.notBefore);
93
+ dueAt(entry.notBefore);
91
94
  continue;
92
95
  }
93
96
  const lanes = this.store.lanesOf(entry);
@@ -100,7 +103,7 @@ export class OutboxWorker {
100
103
  continue;
101
104
  }
102
105
  if (entry.notBefore > now) {
103
- nextDue = nextDue === null ? entry.notBefore : Math.min(nextDue, entry.notBefore);
106
+ dueAt(entry.notBefore);
104
107
  continue;
105
108
  }
106
109
  void this.run(entry);
@@ -1,4 +1,5 @@
1
1
  import { type AgentKind, type ProviderAccount, type ProviderAccountCreatePayload, type ProviderAccountCreateResult, type ProviderAccountMap, type ProviderAccounts, type ProviderInfo } from '@adecore/agent-contracts';
2
+ import { type CodexClientInfo } from '../../chat/codex-transport.ts';
2
3
  import type { AgentSink } from '../../events.ts';
3
4
  import type { ChatProvider } from '../provider.ts';
4
5
  import { type Env } from './accounts.ts';
@@ -6,7 +7,6 @@ import { type AccountLaunches } from './launch.ts';
6
7
  import { type ShadowHomeReport } from './shadow-home.ts';
7
8
  import { type AskAccount } from './status.ts';
8
9
  import { type AccountsHost, type SecretStore } from './variables.ts';
9
- import { type CodexClientInfo } from '../../chat/codex-transport.ts';
10
10
  export interface ProviderAccountsOptions {
11
11
  home: string;
12
12
  host?: AccountsHost;
@@ -68,6 +68,7 @@ export declare class ProviderAccountsService implements AccountLaunches {
68
68
  create(payload: ProviderAccountCreatePayload): Promise<ProviderAccountCreateResult>;
69
69
  watchLogin(id: string): Promise<void>;
70
70
  refresh(): Promise<ProviderAccounts>;
71
+ private folderOf;
71
72
  private runWatch;
72
73
  private serial;
73
74
  private commit;