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