@adecore/agents 0.23.1 → 0.24.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 (153) hide show
  1. package/README.md +3 -3
  2. package/dist/chat/attachment-store.js +4 -5
  3. package/dist/chat/backend.d.ts +6 -1
  4. package/dist/chat/background-work.d.ts +1 -1
  5. package/dist/chat/background-work.js +2 -1
  6. package/dist/chat/chat-core.d.ts +53 -161
  7. package/dist/chat/chat-core.js +146 -112
  8. package/dist/chat/chat-log.js +3 -3
  9. package/dist/chat/chat-process.d.ts +21 -0
  10. package/dist/chat/chat-process.js +109 -9
  11. package/dist/chat/chat-session.d.ts +10 -17
  12. package/dist/chat/chat-session.js +141 -182
  13. package/dist/chat/chat-store.d.ts +1 -0
  14. package/dist/chat/chat-store.js +105 -1
  15. package/dist/chat/claude-backend.d.ts +4 -0
  16. package/dist/chat/claude-backend.js +51 -27
  17. package/dist/chat/claude-protocol.d.ts +12 -0
  18. package/dist/chat/claude-protocol.js +137 -7
  19. package/dist/chat/claude-title.d.ts +1 -3
  20. package/dist/chat/claude-title.js +11 -39
  21. package/dist/chat/claude-transcript.d.ts +1 -0
  22. package/dist/chat/claude-transcript.js +36 -17
  23. package/dist/chat/codex-backend.d.ts +21 -1
  24. package/dist/chat/codex-backend.js +168 -10
  25. package/dist/chat/codex-protocol.d.ts +2 -0
  26. package/dist/chat/codex-protocol.js +37 -3
  27. package/dist/chat/codex-thread.js +1 -3
  28. package/dist/chat/codex-transport.d.ts +3 -0
  29. package/dist/chat/codex-transport.js +38 -35
  30. package/dist/chat/context-breakdown.js +2 -2
  31. package/dist/chat/errors.d.ts +1 -1
  32. package/dist/chat/external-sessions.js +2 -2
  33. package/dist/chat/fixtures/claude-2.1.296-goal.json +136 -0
  34. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10.jsonl +2 -0
  35. package/dist/chat/index.d.ts +29 -0
  36. package/dist/chat/index.js +23 -0
  37. package/dist/chat/projector.d.ts +2 -1
  38. package/dist/chat/projector.js +12 -3
  39. package/dist/chat/request-summary.js +1 -4
  40. package/dist/chat/subagent-settlement.js +1 -3
  41. package/dist/chat/thread.d.ts +3 -0
  42. package/dist/chat/thread.js +20 -3
  43. package/dist/chat/ui-queries.d.ts +2 -0
  44. package/dist/chat/ui-queries.js +40 -5
  45. package/dist/chat/ui-replies.d.ts +33 -0
  46. package/dist/chat/ui-replies.js +152 -0
  47. package/dist/context/index.d.ts +2 -0
  48. package/dist/context/index.js +3 -0
  49. package/dist/coordination/index.d.ts +15 -0
  50. package/dist/coordination/index.js +16 -0
  51. package/dist/host/handlers.js +8 -0
  52. package/dist/host/index.d.ts +7 -0
  53. package/dist/host/index.js +7 -0
  54. package/dist/index.d.ts +9 -0
  55. package/dist/index.js +10 -0
  56. package/dist/providers/accounts/status.js +1 -3
  57. package/dist/providers/claude.js +3 -1
  58. package/dist/providers/codex.js +3 -1
  59. package/dist/providers/index.d.ts +14 -0
  60. package/dist/providers/index.js +12 -0
  61. package/dist/run-process.js +54 -14
  62. package/dist/skills.d.ts +1 -0
  63. package/dist/skills.js +4 -0
  64. package/dist/tasks/task-coordinator.d.ts +4 -0
  65. package/dist/tasks/task-coordinator.js +16 -2
  66. package/dist/tasks/wiring.js +7 -1
  67. package/dist/{providers/accounts/test-accounts.d.ts → testing/accounts.d.ts} +2 -2
  68. package/dist/{providers/accounts/test-accounts.js → testing/accounts.js} +3 -3
  69. package/dist/testing/bin/fake-claude.d.ts +1 -0
  70. package/dist/testing/bin/fake-claude.js +6 -0
  71. package/dist/testing/bin/fake-codex.d.ts +1 -0
  72. package/dist/testing/bin/fake-codex.js +6 -0
  73. package/dist/{chat → testing}/fake-claude.d.ts +1 -1
  74. package/dist/{chat → testing}/fake-claude.js +0 -4
  75. package/dist/{chat → testing}/fake-cli.d.ts +1 -1
  76. package/dist/{chat → testing}/fake-codex.d.ts +1 -1
  77. package/dist/{chat → testing}/fake-codex.js +70 -8
  78. package/dist/testing/index.d.ts +6 -0
  79. package/dist/testing/index.js +7 -0
  80. package/dist/{outbox → testing}/manual-clock.d.ts +1 -1
  81. package/dist/{watch-test-helpers.d.ts → testing/watch.d.ts} +1 -1
  82. package/dist/usage/exchange.d.ts +5 -3
  83. package/dist/usage/exchange.js +25 -16
  84. package/dist/usage/index-file.d.ts +1 -0
  85. package/dist/usage/index-file.js +20 -2
  86. package/dist/usage/index.d.ts +9 -0
  87. package/dist/usage/index.js +6 -0
  88. package/dist/usage/limits/normalize.js +1 -3
  89. package/dist/usage/pricing.d.ts +5 -3
  90. package/dist/usage/pricing.js +28 -19
  91. package/dist/usage/scanner.js +39 -11
  92. package/dist/usage/usage-service.js +8 -3
  93. package/package.json +72 -11
  94. package/src/chat/attachment-store.ts +4 -6
  95. package/src/chat/backend.ts +8 -0
  96. package/src/chat/background-work.ts +2 -2
  97. package/src/chat/chat-core.ts +221 -143
  98. package/src/chat/chat-log.ts +3 -3
  99. package/src/chat/chat-process.ts +128 -8
  100. package/src/chat/chat-session.ts +153 -204
  101. package/src/chat/chat-store.ts +104 -1
  102. package/src/chat/claude-backend.ts +58 -26
  103. package/src/chat/claude-protocol.ts +147 -8
  104. package/src/chat/claude-title.ts +11 -39
  105. package/src/chat/claude-transcript.ts +38 -18
  106. package/src/chat/codex-backend.ts +182 -20
  107. package/src/chat/codex-protocol.ts +40 -5
  108. package/src/chat/codex-thread.ts +1 -4
  109. package/src/chat/codex-transport.ts +45 -35
  110. package/src/chat/context-breakdown.ts +2 -2
  111. package/src/chat/errors.ts +1 -0
  112. package/src/chat/external-sessions.ts +2 -2
  113. package/src/chat/fixtures/claude-2.1.296-goal.json +136 -0
  114. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10.jsonl +2 -0
  115. package/src/chat/index.ts +49 -0
  116. package/src/chat/projector.ts +13 -4
  117. package/src/chat/request-summary.ts +8 -5
  118. package/src/chat/subagent-settlement.ts +1 -4
  119. package/src/chat/thread.ts +22 -3
  120. package/src/chat/ui-queries.ts +46 -5
  121. package/src/chat/ui-replies.ts +187 -0
  122. package/src/context/index.ts +26 -0
  123. package/src/coordination/index.ts +67 -0
  124. package/src/host/handlers.ts +8 -0
  125. package/src/host/index.ts +8 -0
  126. package/src/index.ts +20 -0
  127. package/src/providers/accounts/status.ts +1 -4
  128. package/src/providers/claude.ts +3 -1
  129. package/src/providers/codex.ts +3 -1
  130. package/src/providers/index.ts +15 -0
  131. package/src/run-process.ts +56 -16
  132. package/src/skills.ts +5 -0
  133. package/src/tasks/task-coordinator.ts +22 -2
  134. package/src/tasks/wiring.ts +7 -1
  135. package/src/{providers/accounts/test-accounts.ts → testing/accounts.ts} +4 -4
  136. package/src/testing/bin/fake-claude.ts +7 -0
  137. package/src/testing/bin/fake-codex.ts +7 -0
  138. package/src/{chat → testing}/fake-claude.ts +1 -5
  139. package/src/{chat → testing}/fake-cli.ts +1 -1
  140. package/src/{chat → testing}/fake-codex.ts +75 -6
  141. package/src/testing/index.ts +7 -0
  142. package/src/{outbox → testing}/manual-clock.ts +1 -1
  143. package/src/{watch-test-helpers.ts → testing/watch.ts} +1 -1
  144. package/src/usage/exchange.ts +31 -20
  145. package/src/usage/index-file.ts +23 -2
  146. package/src/usage/index.ts +10 -0
  147. package/src/usage/limits/normalize.ts +1 -4
  148. package/src/usage/pricing.ts +34 -23
  149. package/src/usage/scanner.ts +48 -11
  150. package/src/usage/usage-service.ts +8 -3
  151. /package/dist/{chat → testing}/fake-cli.js +0 -0
  152. /package/dist/{outbox → testing}/manual-clock.js +0 -0
  153. /package/dist/{watch-test-helpers.js → testing/watch.js} +0 -0
@@ -0,0 +1,15 @@
1
+ export { type OutboxEntryOf, OutboxStore, type OutboxWorkShape, type OutboxEntryFields, type OutboxStoreOptions } from '../outbox/outbox.ts';
2
+ export { RETRY_DELAYS_MS, type OutboxClock, systemClock, type OutboxOutcome, type OutboxHandlers, OutboxWorker, type OutboxWorkerOptions } from '../outbox/outbox-worker.ts';
3
+ export { restartBackgroundLimits, type RestartedOutbox } from '../tasks/background-limit.ts';
4
+ export { EndChildrenWorkSchema, endChildren, type EndChildrenWork, type EndChildrenEntry, type EndChildrenOutbox, type EndChildrenDeps, type EndChildren } from '../tasks/end-children.ts';
5
+ export { type TaskCoordinatorDeps, TaskCoordinator } from '../tasks/task-coordinator.ts';
6
+ export { TaskStore, type TaskListener, type TaskEvent, type TaskSink } from '../tasks/task-store.ts';
7
+ export { BackgroundLimitWorkSchema, WakeParentWorkSchema, GiveTaskWorkSchema, DeliverWaitingWorkSchema, type BackgroundLimitWork, type WakeParentWork, type GiveTaskWork, type DeliverWaitingWork, type TaskWork, type BackgroundLimitEntry, type AnyOutboxEntry, type TaskOutbox } from '../tasks/task-work.ts';
8
+ export { type ChatRequests, chatRequests, WAITING_GRACE_MS, WaitingObserver, type WaitingRequest, type WaitingObserverDeps, type WaitingWords } from '../tasks/waiting-child.ts';
9
+ export { RESULT_PREVIEW_BYTES, wakePrompt } from '../tasks/wake-parent.ts';
10
+ export { type TaskVerbs, type TaskWords, wireTasks, type TaskChats, type TaskRecord, type TaskWiringDeps, type TaskWiring } from '../tasks/wiring.ts';
11
+ export { DeliverMessageWorkSchema, deliverMessageHandler, type DeliverMessageWork, type DeliverMessageEntry, type DeliverMessageDeps } from '../messages/deliver-message.ts';
12
+ export { type MessageWords, type ChatNoticeTargets, type ChatDelivery, chatNoticeTargets, deliverToChat, showNotices, type NoticeChat } from '../messages/deliver-notice.ts';
13
+ export { NoticeNotes, unshownNotes } from '../messages/notice-notes.ts';
14
+ export { MAX_NOTICES, NOTICE_MAX_AGE_MS, type Notice, NoticeStore } from '../messages/notice-store.ts';
15
+ export { AgentLineageStore } from '../lineage.ts';
@@ -0,0 +1,16 @@
1
+ // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
+ export { OutboxStore } from '../outbox/outbox.js';
3
+ export { RETRY_DELAYS_MS, systemClock, OutboxWorker } from '../outbox/outbox-worker.js';
4
+ export { restartBackgroundLimits } from '../tasks/background-limit.js';
5
+ export { EndChildrenWorkSchema, endChildren } from '../tasks/end-children.js';
6
+ export { TaskCoordinator } from '../tasks/task-coordinator.js';
7
+ export { TaskStore } from '../tasks/task-store.js';
8
+ export { BackgroundLimitWorkSchema, WakeParentWorkSchema, GiveTaskWorkSchema, DeliverWaitingWorkSchema } from '../tasks/task-work.js';
9
+ export { chatRequests, WAITING_GRACE_MS, WaitingObserver } from '../tasks/waiting-child.js';
10
+ export { RESULT_PREVIEW_BYTES, wakePrompt } from '../tasks/wake-parent.js';
11
+ export { wireTasks } from '../tasks/wiring.js';
12
+ export { DeliverMessageWorkSchema, deliverMessageHandler } from '../messages/deliver-message.js';
13
+ export { chatNoticeTargets, deliverToChat, showNotices } from '../messages/deliver-notice.js';
14
+ export { NoticeNotes, unshownNotes } from '../messages/notice-notes.js';
15
+ export { MAX_NOTICES, NOTICE_MAX_AGE_MS, NoticeStore } from '../messages/notice-store.js';
16
+ export { AgentLineageStore } from '../lineage.js';
@@ -27,6 +27,10 @@ export function chatHandlers(chats, providers) {
27
27
  await chats.stopTask(payload.chatId, payload.taskId);
28
28
  return {};
29
29
  },
30
+ 'chat.goal': async (payload) => {
31
+ await chats.goal(payload.chatId, payload.action);
32
+ return {};
33
+ },
30
34
  'chat.detach': (payload, clientId) => {
31
35
  chats.detach(payload.chatId, clientId);
32
36
  return {};
@@ -78,6 +82,10 @@ export function chatHandlers(chats, providers) {
78
82
  await chats.kill(payload.chatId);
79
83
  return {};
80
84
  },
85
+ 'chat.restart': async (payload) => {
86
+ await chats.restart(payload.chatId);
87
+ return {};
88
+ },
81
89
  'chat.list': () => ({ chats: chats.list() })
82
90
  };
83
91
  }
@@ -0,0 +1,7 @@
1
+ export { AgentHost, type AgentHostOptions } from './agent-host.ts';
2
+ export { RUIMTE_SESSION_VARIABLES, cliEnvironment, type CliEnvironmentPolicy } from './environment.ts';
3
+ export { type AgentHandler, type AgentHandlers, chatHandlers, accountHandlers, usageHandlers, type AgentRequestMap, type ChatRequestType } from './handlers.ts';
4
+ export { memoryPortPair } from './memory-port.ts';
5
+ export { type AgentWiring, wireAgents, type AgentWiringOptions } from './wiring.ts';
6
+ export { ClientSinks } from '../client-sinks.ts';
7
+ export type { AgentEvent, AgentEventMap, AgentSink } from '../events.ts';
@@ -0,0 +1,7 @@
1
+ // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
+ export { AgentHost } from './agent-host.js';
3
+ export { RUIMTE_SESSION_VARIABLES, cliEnvironment } from './environment.js';
4
+ export { chatHandlers, accountHandlers, usageHandlers } from './handlers.js';
5
+ export { memoryPortPair } from './memory-port.js';
6
+ export { wireAgents } from './wiring.js';
7
+ export { ClientSinks } from '../client-sinks.js';
@@ -0,0 +1,9 @@
1
+ export { wait, withTimeout } from './async.ts';
2
+ export { CodedError } from './coded-error.ts';
3
+ export { describeError, setErrorStacks, errorText } from './error-text.ts';
4
+ export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, isNotFound, fileExists, type WriteAtomicOptions } from './fs.ts';
5
+ export { recordFileName, RecordDirectory, type RecordDirectoryOptions } from './record-directory.ts';
6
+ export { runProcess, type ProcessResult, type RunProcessOptions } from './run-process.ts';
7
+ export { Serializer, KeyedSerializer } from './serializer.ts';
8
+ export { cleanTitle, readLines } from './title-file.ts';
9
+ export { type DirectoryWatcher, type WatchSeams, supportsRecursive, SYSTEM_WATCH, type Settled, settled, PerClientWatches, type WatchDirectory, type Schedule } from './watch-seam.ts';
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
+ export { wait, withTimeout } from './async.js';
3
+ export { CodedError } from './coded-error.js';
4
+ export { describeError, setErrorStacks, errorText } from './error-text.js';
5
+ export { tempNameFor, writeAtomic, replaceSync, writeAtomicSync, isNotFound, fileExists } from './fs.js';
6
+ export { recordFileName, RecordDirectory } from './record-directory.js';
7
+ export { runProcess } from './run-process.js';
8
+ export { Serializer, KeyedSerializer } from './serializer.js';
9
+ export { cleanTitle, readLines } from './title-file.js';
10
+ export { supportsRecursive, SYSTEM_WATCH, settled, PerClientWatches } from './watch-seam.js';
@@ -1,3 +1,4 @@
1
+ import { isRecord } from '@adecore/agent-contracts';
1
2
  import { withTimeout } from '../../async.js';
2
3
  import { CodexTransport } from '../../chat/codex-transport.js';
3
4
  import { runProcess } from '../../run-process.js';
@@ -6,9 +7,6 @@ const ASK_TIMEOUT_MS = 20_000;
6
7
  function text(value) {
7
8
  return typeof value === 'string' && value !== '' ? value : null;
8
9
  }
9
- function isRecord(value) {
10
- return typeof value === 'object' && value !== null && !Array.isArray(value);
11
- }
12
10
  /* `claude auth status --json`, as Claude Code 2.1.282 prints it: `loggedIn`, `email`, `orgName`, `subscriptionType`. */
13
11
  export function readClaudeAuthStatus(output) {
14
12
  let status;
@@ -85,6 +85,7 @@ export const CLAUDE_CAPABILITIES = {
85
85
  denyReason: true,
86
86
  allowAlways: true,
87
87
  asyncQuestions: false,
88
+ steer: true,
88
89
  compaction: 'prompt',
89
90
  reportsCost: true,
90
91
  /*
@@ -94,7 +95,8 @@ export const CLAUDE_CAPABILITIES = {
94
95
  */
95
96
  reportsContextWindow: false,
96
97
  reportsThinking: true,
97
- slashCommands: true
98
+ slashCommands: true,
99
+ goals: 'command'
98
100
  };
99
101
  // Claude Code takes its flags before `--resume`, which takes the id.
100
102
  export const CLAUDE_RESUME_COMMAND = 'claude {flags} --resume {id}';
@@ -42,11 +42,13 @@ export const CODEX_CAPABILITIES = {
42
42
  denyReason: false,
43
43
  allowAlways: true,
44
44
  asyncQuestions: true,
45
+ steer: true,
45
46
  compaction: 'native',
46
47
  reportsCost: false,
47
48
  reportsContextWindow: true,
48
49
  reportsThinking: true,
49
- slashCommands: false
50
+ slashCommands: false,
51
+ goals: 'native'
50
52
  };
51
53
  // `codex resume` is a subcommand that reads `[OPTIONS] [SESSION_ID]`, so the flags go after it.
52
54
  export const CODEX_RESUME_COMMAND = 'codex resume {flags} {id}';
@@ -0,0 +1,14 @@
1
+ export { AccountError, type AccountLaunches, storedAccount, launchEnv } from './accounts/launch.ts';
2
+ export { ProviderAccountsService, type ProviderAccountsOptions } from './accounts/service.ts';
3
+ export type { ShadowHomeReport } from './accounts/shadow-home.ts';
4
+ export type { AccountReading, AskAccount } from './accounts/status.ts';
5
+ export { type AccountsHost, variablesProblem, keychainService, type SecretStore, type ReservedVariables } from './accounts/variables.ts';
6
+ export { ModelCatalog, type ModelCatalogInput } from './catalog.ts';
7
+ export { createClaudeProvider } from './claude-provider.ts';
8
+ export { createCodexProvider } from './codex-provider.ts';
9
+ export { type CodexRules, codexRulesText, codexRulesPathIn, defaultCodexHome, installCodexRules, type CodexRulesResult } from './codex-rules.ts';
10
+ export { codexThreadOptions, codexServiceTier } from './codex.ts';
11
+ export { type CliDetection, detectCli } from './detect.ts';
12
+ export type { ChatProvider, ProviderHome } from './provider.ts';
13
+ export { ProviderRegistry, type ProviderRegistryOptions } from './registry.ts';
14
+ export { MODE_ORDER, narrowerMode, ceilingForOpening, modeFlag } from '../modes.ts';
@@ -0,0 +1,12 @@
1
+ // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
+ export { AccountError, storedAccount, launchEnv } from './accounts/launch.js';
3
+ export { ProviderAccountsService } from './accounts/service.js';
4
+ export { variablesProblem, keychainService } from './accounts/variables.js';
5
+ export { ModelCatalog } from './catalog.js';
6
+ export { createClaudeProvider } from './claude-provider.js';
7
+ export { createCodexProvider } from './codex-provider.js';
8
+ export { codexRulesText, codexRulesPathIn, defaultCodexHome, installCodexRules } from './codex-rules.js';
9
+ export { codexThreadOptions, codexServiceTier } from './codex.js';
10
+ export { detectCli } from './detect.js';
11
+ export { ProviderRegistry } from './registry.js';
12
+ export { MODE_ORDER, narrowerMode, ceilingForOpening, modeFlag } from '../modes.js';
@@ -1,4 +1,9 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { killProcessTree } from './chat/chat-process.js';
3
+ // What a command and everything it started get between the SIGTERM and the SIGKILL.
4
+ const KILL_GRACE_MS = 2000;
5
+ // Stdout and stderr together; a short command that prints more is ended and fails instead of filling memory.
6
+ const MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
2
7
  /* One short command to its end, with what it printed. A command that cannot start rejects. */
3
8
  export function runProcess(command, options = {}) {
4
9
  return new Promise((resolve, reject) => {
@@ -10,29 +15,64 @@ export function runProcess(command, options = {}) {
10
15
  const child = spawn(executable, args, {
11
16
  ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
12
17
  ...(options.env === undefined ? {} : { env: options.env }),
13
- stdio: [options.stdin === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe']
18
+ stdio: [options.stdin === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'],
19
+ // A group of its own, so ending the command also ends what it started; Windows ends the tree through `taskkill`.
20
+ detached: process.platform !== 'win32',
21
+ windowsHide: true
14
22
  });
15
23
  const stdout = [];
16
24
  const stderr = [];
17
- child.stdout?.on('data', (chunk) => stdout.push(chunk));
18
- child.stderr?.on('data', (chunk) => stderr.push(chunk));
19
- const timer = options.timeoutMs === undefined
20
- ? null
21
- : setTimeout(() => {
22
- child.kill('SIGTERM');
23
- reject(new Error(options.timeoutMessage ?? `${executable} did not finish in time`));
24
- }, options.timeoutMs);
25
- child.on('error', (error) => {
25
+ let printed = 0;
26
+ let settled = false;
27
+ let timer = null;
28
+ const settle = () => {
29
+ if (settled) {
30
+ return false;
31
+ }
32
+ settled = true;
26
33
  if (timer !== null) {
27
34
  clearTimeout(timer);
28
35
  }
29
- reject(error);
36
+ return true;
37
+ };
38
+ const end = (message) => {
39
+ if (!settle()) {
40
+ return;
41
+ }
42
+ const pid = child.pid;
43
+ if (pid !== undefined) {
44
+ killProcessTree(pid, 'SIGTERM');
45
+ // Sent even when the command left in time: what it started may still ignore the SIGTERM.
46
+ setTimeout(() => killProcessTree(pid, 'SIGKILL'), KILL_GRACE_MS).unref?.();
47
+ }
48
+ reject(new Error(message));
49
+ };
50
+ const collect = (into) => (chunk) => {
51
+ if (settled) {
52
+ return;
53
+ }
54
+ printed += chunk.length;
55
+ if (printed > MAX_OUTPUT_BYTES) {
56
+ end(`${executable} printed more than ${MAX_OUTPUT_BYTES / 1024 / 1024} MB`);
57
+ return;
58
+ }
59
+ into.push(chunk);
60
+ };
61
+ child.stdout?.on('data', collect(stdout));
62
+ child.stderr?.on('data', collect(stderr));
63
+ if (options.timeoutMs !== undefined) {
64
+ const message = options.timeoutMessage ?? `${executable} did not finish in time`;
65
+ timer = setTimeout(() => end(message), options.timeoutMs);
66
+ }
67
+ child.on('error', (error) => {
68
+ if (settle()) {
69
+ reject(error);
70
+ }
30
71
  });
31
72
  child.on('close', (exitCode) => {
32
- if (timer !== null) {
33
- clearTimeout(timer);
73
+ if (settle()) {
74
+ resolve({ exitCode, stdout: Buffer.concat(stdout).toString('utf8'), stderr: Buffer.concat(stderr).toString('utf8') });
34
75
  }
35
- resolve({ exitCode, stdout: Buffer.concat(stdout).toString('utf8'), stderr: Buffer.concat(stderr).toString('utf8') });
36
76
  });
37
77
  if (options.stdin !== undefined && child.stdin) {
38
78
  child.stdin.on('error', () => undefined);
package/dist/skills.d.ts CHANGED
@@ -18,4 +18,5 @@ export declare class SkillIndex {
18
18
  private readonly cache;
19
19
  constructor(options?: SkillRootOptions);
20
20
  list(kind: AgentKind, cwd: string): Promise<ChatSkill[]>;
21
+ forget(kind: AgentKind, cwd: string): void;
21
22
  }
package/dist/skills.js CHANGED
@@ -171,4 +171,8 @@ export class SkillIndex {
171
171
  this.cache.set(key, { at: Date.now(), skills });
172
172
  return skills;
173
173
  }
174
+ /* Drops the scan of one CLI in one folder, so the next look reads the disk again. */
175
+ forget(kind, cwd) {
176
+ this.cache.delete(`${kind}:${cwd}`);
177
+ }
174
178
  }
@@ -12,6 +12,10 @@ export interface TaskCoordinatorDeps {
12
12
  alert(nodeId: string, title: string, body: string): void;
13
13
  retryAt?(chatId: string, turn: ChatTurnItem, items: readonly ChatItem[]): number | null;
14
14
  afterBackgroundWork(chatId: string): 'reports' | 'silent' | 'gone';
15
+ activeGoal?(chatId: string): {
16
+ objective: string;
17
+ running: boolean;
18
+ } | null;
15
19
  commandsOf(chatId: string): readonly ChatBackgroundTask[];
16
20
  limit: {
17
21
  owed(taskId: string): boolean;
@@ -33,6 +33,9 @@ function outlastedNote(commands) {
33
33
  function restartedNote(commands) {
34
34
  return `The machine restarted while this still ran in the background: ${commandList(commands)}.`;
35
35
  }
36
+ function goalLostNote(objective) {
37
+ return `The agent stopped before its goal was met: ${objective}`;
38
+ }
36
39
  /* What a settled turn of a child makes of its task. */
37
40
  export function resultOfTurn(turn, items, at) {
38
41
  if (turn.state === 'done') {
@@ -49,8 +52,9 @@ export function resultOfTurn(turn, items, at) {
49
52
  /*
50
53
  * Settles tasks from what children do and does nothing else: it writes the task store and owes wakes
51
54
  * in the outbox, never starts a turn, since it runs inside the broadcast of the child it heard. A chat
52
- * child settles when a turn ends with none of its own tasks still open or waiting to wake it and no work
53
- * of its CLI's still running in the background, or once the commands it runs there outlast their limit;
55
+ * child settles when a turn ends with none of its own tasks still open or waiting to wake it, no goal
56
+ * still active and no work of its CLI's still running in the background, or once the commands it runs
57
+ * there outlast their limit;
54
58
  * an agent that is not a chat, which the host tells it about, settles only on `done`, or fails when it
55
59
  * ends without one.
56
60
  */
@@ -185,6 +189,16 @@ export class TaskCoordinator {
185
189
  this.hold(task, paused);
186
190
  return;
187
191
  }
192
+ // A goal holds the task with no limit, and the parent sleeps on through the turns its CLI opens toward it.
193
+ const goal = turn.state === 'done' ? (this.deps.activeGoal?.(chatId) ?? null) : null;
194
+ if (goal?.running === true) {
195
+ return;
196
+ }
197
+ if (goal !== null) {
198
+ const { result } = resultOfTurn(turn, items, this.deps.now());
199
+ this.settle(task, 'failed', { ...result, text: `${result.text}\n\n${goalLostNote(goal.objective)}`, source: 'exit' });
200
+ return;
201
+ }
188
202
  // A turn a person stopped or that failed ends the task as it always did, whatever still runs beside it.
189
203
  if (turn.state === 'done' && this.heldInBackground(chatId, task, items)) {
190
204
  this.track(this.deps.tasks.holdBackground(task.id, {
@@ -37,6 +37,11 @@ export function wireTasks(deps) {
37
37
  return reportsOnBackgroundWork(chat.info.provider) ? 'reports' : 'silent';
38
38
  },
39
39
  commandsOf: (chatId) => chats.get(chatId)?.info.background ?? [],
40
+ activeGoal: (chatId) => {
41
+ const chat = chats.get(chatId);
42
+ const goal = chat?.info.goal;
43
+ return chat && goal?.status === 'active' ? { objective: goal.objective, running: chat.running } : null;
44
+ },
40
45
  limit
41
46
  });
42
47
  deps.tasks.onChange((task) => {
@@ -68,7 +73,8 @@ export function wireTasks(deps) {
68
73
  }
69
74
  const stored = await chats.storedBackground(task.childId);
70
75
  const storedWork = stored.items.length > 0 || stored.commands.length > 0;
71
- if (!deps.placed(task.childId) || (task.background === undefined && task.requiresTaskTurn !== true && !storedWork)) {
76
+ // A goal still active went down with the CLI, which its child's last turn settles on.
77
+ if (!deps.placed(task.childId) || (task.background === undefined && task.requiresTaskTurn !== true && !storedWork && !stored.goal)) {
72
78
  return;
73
79
  }
74
80
  if (task.background === undefined && stored.turnId !== null && storedWork) {
@@ -1,6 +1,6 @@
1
1
  import type { ProviderAccountMap } from '@adecore/agent-contracts';
2
- import { ProviderAccountsService } from './service.ts';
3
- import type { SecretStore } from './variables.ts';
2
+ import { ProviderAccountsService } from '../providers/accounts/service.ts';
3
+ import type { SecretStore } from '../providers/accounts/variables.ts';
4
4
  export declare function memorySecrets(values?: Map<string, string>): SecretStore & {
5
5
  values: Map<string, string>;
6
6
  };
@@ -1,6 +1,6 @@
1
- import { providerFor } from '../registry.js';
2
- import { ProviderAccountsService } from './service.js';
3
- import { accountsPath, writeAccounts } from './store.js';
1
+ import { providerFor } from '../providers/registry.js';
2
+ import { ProviderAccountsService } from '../providers/accounts/service.js';
3
+ import { accountsPath, writeAccounts } from '../providers/accounts/store.js';
4
4
  /* A keychain in memory; `values` is what it holds, by key. */
5
5
  export function memorySecrets(values = new Map()) {
6
6
  return {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { fakeClaude } from '../fake-claude.js';
2
+ import { runOverStdio } from '../fake-cli.js';
3
+ // Run as a script, it stands in for the Claude CLI behind real pipes; imported, it does nothing.
4
+ if (import.meta.main) {
5
+ await runOverStdio(fakeClaude);
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { fakeCodex } from '../fake-codex.js';
2
+ import { runOverStdio } from '../fake-cli.js';
3
+ // Run as a script, it stands in for `codex app-server` behind real pipes; imported, it does nothing.
4
+ if (import.meta.main) {
5
+ await runOverStdio(fakeCodex);
6
+ }
@@ -1,3 +1,3 @@
1
- import { type FakeCli, type FakeIo, type FakeProgram } from './fake-cli.ts';
1
+ import type { FakeCli, FakeIo, FakeProgram } from './fake-cli.ts';
2
2
  export declare function fakeClaude(io: FakeIo): FakeProgram;
3
3
  export declare function claudeStoppingOnResume(sessionIds: ReadonlySet<string>, how: 'missing' | 'exit'): FakeCli;
@@ -5,7 +5,6 @@
5
5
  import { spawn } from 'node:child_process';
6
6
  import { writeFileSync } from 'node:fs';
7
7
  import { resolve } from 'node:path';
8
- import { runOverStdio } from './fake-cli.js';
9
8
  export function fakeClaude(io) {
10
9
  const args = io.argv;
11
10
  const resumeAt = args.indexOf('--resume');
@@ -1050,6 +1049,3 @@ export function claudeStoppingOnResume(sessionIds, how) {
1050
1049
  return { onLine: () => undefined };
1051
1050
  };
1052
1051
  }
1053
- if (import.meta.main) {
1054
- await runOverStdio(fakeClaude);
1055
- }
@@ -1,4 +1,4 @@
1
- import type { SpawnChatProcess } from './chat-process.ts';
1
+ import type { SpawnChatProcess } from '../chat/chat-process.ts';
2
2
  export interface FakeIo {
3
3
  readonly argv: string[];
4
4
  readonly cwd: string;
@@ -1,4 +1,4 @@
1
- import { type FakeCli } from './fake-cli.ts';
1
+ import type { FakeCli } from './fake-cli.ts';
2
2
  type Frame = Record<string, unknown>;
3
3
  export declare const FAKE_CHILD_STEPS = 250;
4
4
  export declare const fakeCodexForks: Frame[];
@@ -1,8 +1,3 @@
1
- /*
2
- * Network-free `codex app-server` fake. Prompt commands exercise approvals, edits, questions,
3
- * interrupts, failures, names, subagents, pagination and forks through the real JSON-RPC shapes.
4
- */
5
- import { runOverStdio } from './fake-cli.js';
6
1
  // How many steps a spawned agent takes: more than a thread row keeps, so only its own thread has them all.
7
2
  export const FAKE_CHILD_STEPS = 250;
8
3
  // The threads of spawned agents, outside any one process: the real ones are on disk, so a process that never ran them reads them too.
@@ -93,6 +88,34 @@ export function fakeCodexWith(options) {
93
88
  });
94
89
  turnId = '';
95
90
  };
91
+ /*
92
+ * The thread's goal (Codex 0.162.1). An objective `steps:N` takes N turns: the first is the one the client
93
+ * starts, which ends when the test says later is, and Codex opens every next one itself while the goal
94
+ * is active, also later, and marks the goal complete in the last.
95
+ */
96
+ let goal = null;
97
+ let goalSteps = 0;
98
+ const goalChanged = () => {
99
+ notify('thread/goal/updated', { threadId, turnId: turnId === '' ? null : turnId, goal });
100
+ };
101
+ const goalStep = () => {
102
+ goalSteps += 1;
103
+ agentMessage(`goal step ${goalSteps}`);
104
+ const needed = Number(/steps:(\d+)/.exec(String(goal?.objective ?? ''))?.[1] ?? '1');
105
+ if (goal !== null) {
106
+ goal = { ...goal, tokensUsed: Number(goal.tokensUsed) + 100, status: goalSteps >= needed ? 'complete' : goal.status };
107
+ goalChanged();
108
+ }
109
+ turnCompleted('completed');
110
+ if (goal?.status === 'active') {
111
+ io.later(() => {
112
+ if (goal?.status === 'active') {
113
+ turnStarted();
114
+ goalStep();
115
+ }
116
+ });
117
+ }
118
+ };
96
119
  let pendingApproval = null;
97
120
  let pendingQuestion = null;
98
121
  let pendingSteer = false;
@@ -132,6 +155,10 @@ export function fakeCodexWith(options) {
132
155
  const user = item('userMessage', { clientId: null, content: [{ type: 'text', text, text_elements: [] }] });
133
156
  started(user);
134
157
  completed(user);
158
+ if (goal !== null && (text === goal.objective || text === '')) {
159
+ io.later(goalStep);
160
+ return;
161
+ }
135
162
  if (text === 'crash') {
136
163
  io.exit(1);
137
164
  return;
@@ -494,6 +521,44 @@ export function fakeCodexWith(options) {
494
521
  turnCompleted('interrupted');
495
522
  }
496
523
  return;
524
+ case 'thread/goal/get':
525
+ out({ id, result: { goal } });
526
+ return;
527
+ case 'thread/goal/set': {
528
+ const at = Math.floor(Date.now() / 1000);
529
+ if (typeof params.objective === 'string') {
530
+ goal = {
531
+ threadId,
532
+ objective: params.objective,
533
+ status: 'active',
534
+ tokenBudget: null,
535
+ tokensUsed: 0,
536
+ timeUsedSeconds: 0,
537
+ createdAt: at,
538
+ updatedAt: at
539
+ };
540
+ goalSteps = 0;
541
+ }
542
+ if (goal === null) {
543
+ out({ id, error: { code: -32600, message: 'no goal to update' } });
544
+ return;
545
+ }
546
+ if (typeof params.status === 'string') {
547
+ goal = { ...goal, status: params.status, updatedAt: at };
548
+ }
549
+ out({ id, result: { goal } });
550
+ goalChanged();
551
+ return;
552
+ }
553
+ case 'thread/goal/clear': {
554
+ const cleared = goal !== null;
555
+ goal = null;
556
+ out({ id, result: { cleared } });
557
+ if (cleared) {
558
+ notify('thread/goal/cleared', { threadId });
559
+ }
560
+ return;
561
+ }
497
562
  case 'thread/compact/start':
498
563
  out({ id, result: {} });
499
564
  turnStarted();
@@ -530,6 +595,3 @@ export function fakeCodexWith(options) {
530
595
  };
531
596
  }
532
597
  export const fakeCodex = fakeCodexWith({});
533
- if (import.meta.main) {
534
- await runOverStdio(fakeCodex);
535
- }
@@ -0,0 +1,6 @@
1
+ export { type FakeIo, type FakeProgram, type FakeCli, type StartedFake, type InProcessCli, inProcess, runOverStdio } from './fake-cli.ts';
2
+ export { fakeClaude, claudeStoppingOnResume } from './fake-claude.ts';
3
+ export { FAKE_CHILD_STEPS, fakeCodexForks, type FakeCodexOptions, fakeCodexWith, fakeCodex } from './fake-codex.ts';
4
+ export { memorySecrets, testAccounts } from './accounts.ts';
5
+ export { ManualClock } from './manual-clock.ts';
6
+ export { FakeDirectoryWatcher, FakeWatch, EventLog } from './watch.ts';
@@ -0,0 +1,7 @@
1
+ // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
+ export { inProcess, runOverStdio } from './fake-cli.js';
3
+ export { fakeClaude, claudeStoppingOnResume } from './fake-claude.js';
4
+ export { FAKE_CHILD_STEPS, fakeCodexForks, fakeCodexWith, fakeCodex } from './fake-codex.js';
5
+ export { memorySecrets, testAccounts } from './accounts.js';
6
+ export { ManualClock } from './manual-clock.js';
7
+ export { FakeDirectoryWatcher, FakeWatch, EventLog } from './watch.js';
@@ -1,4 +1,4 @@
1
- import type { OutboxClock } from './outbox-worker.ts';
1
+ import type { OutboxClock } from '../outbox/outbox-worker.ts';
2
2
  export declare class ManualClock implements OutboxClock {
3
3
  private time;
4
4
  private timers;
@@ -1,4 +1,4 @@
1
- import type { DirectoryWatcher, WatchDirectory, WatchSeams } from './watch-seam.ts';
1
+ import type { DirectoryWatcher, WatchDirectory, WatchSeams } from '../watch-seam.ts';
2
2
  export declare class FakeDirectoryWatcher implements DirectoryWatcher {
3
3
  readonly path: string;
4
4
  readonly recursive: boolean;
@@ -6,11 +6,13 @@ export declare class ExchangeRates {
6
6
  private readonly home;
7
7
  private readonly allowFetch;
8
8
  private rate;
9
- private loadedFromDisk;
9
+ private stored;
10
10
  private inFlight;
11
11
  constructor(home: string, allowFetch?: boolean);
12
12
  get current(): UsageRate | null;
13
- ensure(): Promise<void>;
13
+ load(): Promise<void>;
14
+ ensure(): Promise<boolean>;
14
15
  private get file();
15
- private refresh;
16
+ private readStored;
17
+ private fetchFresh;
16
18
  }