@adecore/agents 0.0.1 → 0.17.0-beta.1
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/LICENSE +110 -0
- package/README.md +33 -0
- package/dist/async.d.ts +2 -0
- package/dist/async.js +31 -0
- package/dist/chat/attachment-store.d.ts +10 -0
- package/dist/chat/attachment-store.js +93 -0
- package/dist/chat/backend.d.ts +214 -0
- package/dist/chat/backend.js +1 -0
- package/dist/chat/background-work.d.ts +8 -0
- package/dist/chat/background-work.js +33 -0
- package/dist/chat/bookmark-store.d.ts +25 -0
- package/dist/chat/bookmark-store.js +141 -0
- package/dist/chat/chat-core.d.ts +311 -0
- package/dist/chat/chat-core.js +1025 -0
- package/dist/chat/chat-log.d.ts +33 -0
- package/dist/chat/chat-log.js +163 -0
- package/dist/chat/chat-process.d.ts +46 -0
- package/dist/chat/chat-process.js +190 -0
- package/dist/chat/chat-session.d.ts +209 -0
- package/dist/chat/chat-session.js +1511 -0
- package/dist/chat/chat-store.d.ts +37 -0
- package/dist/chat/chat-store.js +202 -0
- package/dist/chat/chat-title.d.ts +10 -0
- package/dist/chat/chat-title.js +74 -0
- package/dist/chat/claude-backend.d.ts +35 -0
- package/dist/chat/claude-backend.js +214 -0
- package/dist/chat/claude-protocol.d.ts +41 -0
- package/dist/chat/claude-protocol.js +642 -0
- package/dist/chat/claude-title.d.ts +11 -0
- package/dist/chat/claude-title.js +97 -0
- package/dist/chat/claude-transcript.d.ts +28 -0
- package/dist/chat/claude-transcript.js +211 -0
- package/dist/chat/codex-backend.d.ts +51 -0
- package/dist/chat/codex-backend.js +364 -0
- package/dist/chat/codex-protocol.d.ts +56 -0
- package/dist/chat/codex-protocol.js +767 -0
- package/dist/chat/codex-thread.d.ts +32 -0
- package/dist/chat/codex-thread.js +131 -0
- package/dist/chat/codex-transport.d.ts +42 -0
- package/dist/chat/codex-transport.js +188 -0
- package/dist/chat/composer-preferences.d.ts +15 -0
- package/dist/chat/composer-preferences.js +46 -0
- package/dist/chat/context-breakdown.d.ts +2 -0
- package/dist/chat/context-breakdown.js +74 -0
- package/dist/chat/delta-coalescer.d.ts +12 -0
- package/dist/chat/delta-coalescer.js +55 -0
- package/dist/chat/errors.d.ts +4 -0
- package/dist/chat/errors.js +4 -0
- package/dist/chat/fake-claude.d.ts +3 -0
- package/dist/chat/fake-claude.js +982 -0
- package/dist/chat/fake-cli.d.ts +25 -0
- package/dist/chat/fake-cli.js +157 -0
- package/dist/chat/fake-codex.d.ts +10 -0
- package/dist/chat/fake-codex.js +502 -0
- package/dist/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
- package/dist/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
- package/dist/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
- package/dist/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
- package/dist/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
- package/dist/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
- package/dist/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
- package/dist/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
- package/dist/chat/input.d.ts +19 -0
- package/dist/chat/input.js +71 -0
- package/dist/chat/limit-resume.d.ts +11 -0
- package/dist/chat/limit-resume.js +68 -0
- package/dist/chat/projector.d.ts +60 -0
- package/dist/chat/projector.js +860 -0
- package/dist/chat/request-summary.d.ts +3 -0
- package/dist/chat/request-summary.js +143 -0
- package/dist/chat/subagent-projection.d.ts +8 -0
- package/dist/chat/subagent-projection.js +40 -0
- package/dist/chat/subagent-reader.d.ts +66 -0
- package/dist/chat/subagent-reader.js +396 -0
- package/dist/chat/subagent-settlement.d.ts +6 -0
- package/dist/chat/subagent-settlement.js +103 -0
- package/dist/chat/thread.d.ts +30 -0
- package/dist/chat/thread.js +147 -0
- package/dist/chat/wake-chat.d.ts +20 -0
- package/dist/chat/wake-chat.js +27 -0
- package/dist/client-sinks.d.ts +9 -0
- package/dist/client-sinks.js +34 -0
- package/dist/coded-error.d.ts +5 -0
- package/dist/coded-error.js +17 -0
- package/dist/context/argv.d.ts +11 -0
- package/dist/context/argv.js +58 -0
- package/dist/context/refusal.d.ts +9 -0
- package/dist/context/refusal.js +38 -0
- package/dist/context/verb.d.ts +85 -0
- package/dist/context/verb.js +233 -0
- package/dist/error-text.d.ts +5 -0
- package/dist/error-text.js +24 -0
- package/dist/events.d.ts +12 -0
- package/dist/events.js +1 -0
- package/dist/fs.d.ts +9 -0
- package/dist/fs.js +107 -0
- package/dist/host/agent-host.d.ts +28 -0
- package/dist/host/agent-host.js +105 -0
- package/dist/host/environment.d.ts +7 -0
- package/dist/host/environment.js +22 -0
- package/dist/host/handlers.d.ts +30 -0
- package/dist/host/handlers.js +111 -0
- package/dist/host/memory-port.d.ts +2 -0
- package/dist/host/memory-port.js +25 -0
- package/dist/host/wiring.d.ts +40 -0
- package/dist/host/wiring.js +120 -0
- package/dist/lineage.d.ts +44 -0
- package/dist/lineage.js +134 -0
- package/dist/messages/deliver-message.d.ts +21 -0
- package/dist/messages/deliver-message.js +42 -0
- package/dist/messages/deliver-notice.d.ts +25 -0
- package/dist/messages/deliver-notice.js +75 -0
- package/dist/messages/notice-notes.d.ts +15 -0
- package/dist/messages/notice-notes.js +26 -0
- package/dist/messages/notice-store.d.ts +30 -0
- package/dist/messages/notice-store.js +120 -0
- package/dist/modes.d.ts +11 -0
- package/dist/modes.js +28 -0
- package/dist/outbox/manual-clock.d.ts +10 -0
- package/dist/outbox/manual-clock.js +25 -0
- package/dist/outbox/outbox-worker.d.ts +44 -0
- package/dist/outbox/outbox-worker.js +173 -0
- package/dist/outbox/outbox.d.ts +37 -0
- package/dist/outbox/outbox.js +76 -0
- package/dist/providers/accounts/accounts.d.ts +20 -0
- package/dist/providers/accounts/accounts.js +158 -0
- package/dist/providers/accounts/launch.d.ts +17 -0
- package/dist/providers/accounts/launch.js +30 -0
- package/dist/providers/accounts/service.d.ts +87 -0
- package/dist/providers/accounts/service.js +538 -0
- package/dist/providers/accounts/shadow-home.d.ts +8 -0
- package/dist/providers/accounts/shadow-home.js +70 -0
- package/dist/providers/accounts/status.d.ts +12 -0
- package/dist/providers/accounts/status.js +85 -0
- package/dist/providers/accounts/store.d.ts +14 -0
- package/dist/providers/accounts/store.js +112 -0
- package/dist/providers/accounts/test-accounts.d.ts +12 -0
- package/dist/providers/accounts/test-accounts.js +38 -0
- package/dist/providers/accounts/variables.d.ts +27 -0
- package/dist/providers/accounts/variables.js +94 -0
- package/dist/providers/catalog.d.ts +17 -0
- package/dist/providers/catalog.js +76 -0
- package/dist/providers/claude-models.json +114 -0
- package/dist/providers/claude-provider.d.ts +4 -0
- package/dist/providers/claude-provider.js +29 -0
- package/dist/providers/claude.d.ts +15 -0
- package/dist/providers/claude.js +105 -0
- package/dist/providers/codex-models.json +135 -0
- package/dist/providers/codex-provider.d.ts +4 -0
- package/dist/providers/codex-provider.js +27 -0
- package/dist/providers/codex-rules.d.ts +9 -0
- package/dist/providers/codex-rules.js +35 -0
- package/dist/providers/codex.d.ts +14 -0
- package/dist/providers/codex.js +52 -0
- package/dist/providers/detect.d.ts +6 -0
- package/dist/providers/detect.js +30 -0
- package/dist/providers/provider.d.ts +25 -0
- package/dist/providers/provider.js +1 -0
- package/dist/providers/registry.d.ts +28 -0
- package/dist/providers/registry.js +118 -0
- package/dist/record-directory.d.ts +27 -0
- package/dist/record-directory.js +114 -0
- package/dist/run-process.d.ts +13 -0
- package/dist/run-process.js +42 -0
- package/dist/serializer.d.ts +9 -0
- package/dist/serializer.js +34 -0
- package/dist/skills.d.ts +21 -0
- package/dist/skills.js +174 -0
- package/dist/tasks/background-limit.d.ts +16 -0
- package/dist/tasks/background-limit.js +55 -0
- package/dist/tasks/end-children.d.ts +34 -0
- package/dist/tasks/end-children.js +53 -0
- package/dist/tasks/give-task.d.ts +14 -0
- package/dist/tasks/give-task.js +33 -0
- package/dist/tasks/task-coordinator.d.ts +52 -0
- package/dist/tasks/task-coordinator.js +323 -0
- package/dist/tasks/task-store.d.ts +49 -0
- package/dist/tasks/task-store.js +197 -0
- package/dist/tasks/task-work.d.ts +47 -0
- package/dist/tasks/task-work.js +28 -0
- package/dist/tasks/waiting-child.d.ts +51 -0
- package/dist/tasks/waiting-child.js +143 -0
- package/dist/tasks/wake-parent.d.ts +19 -0
- package/dist/tasks/wake-parent.js +109 -0
- package/dist/tasks/wiring.d.ts +51 -0
- package/dist/tasks/wiring.js +151 -0
- package/dist/title-file.d.ts +2 -0
- package/dist/title-file.js +55 -0
- package/dist/usage/accounts.d.ts +15 -0
- package/dist/usage/accounts.js +64 -0
- package/dist/usage/aggregate.d.ts +12 -0
- package/dist/usage/aggregate.js +154 -0
- package/dist/usage/exchange.d.ts +16 -0
- package/dist/usage/exchange.js +84 -0
- package/dist/usage/index-file.d.ts +15 -0
- package/dist/usage/index-file.js +123 -0
- package/dist/usage/limits/monitor.d.ts +50 -0
- package/dist/usage/limits/monitor.js +179 -0
- package/dist/usage/limits/normalize.d.ts +21 -0
- package/dist/usage/limits/normalize.js +160 -0
- package/dist/usage/limits/probe.d.ts +9 -0
- package/dist/usage/limits/probe.js +113 -0
- package/dist/usage/prices-snapshot.json +1071 -0
- package/dist/usage/pricing.d.ts +34 -0
- package/dist/usage/pricing.js +186 -0
- package/dist/usage/projects.d.ts +17 -0
- package/dist/usage/projects.js +49 -0
- package/dist/usage/readers/claude.d.ts +3 -0
- package/dist/usage/readers/claude.js +59 -0
- package/dist/usage/readers/codex.d.ts +23 -0
- package/dist/usage/readers/codex.js +155 -0
- package/dist/usage/record.d.ts +15 -0
- package/dist/usage/record.js +39 -0
- package/dist/usage/roots.d.ts +12 -0
- package/dist/usage/roots.js +29 -0
- package/dist/usage/scanner.d.ts +30 -0
- package/dist/usage/scanner.js +234 -0
- package/dist/usage/usage-service.d.ts +35 -0
- package/dist/usage/usage-service.js +130 -0
- package/dist/watch-seam.d.ts +29 -0
- package/dist/watch-seam.js +78 -0
- package/dist/watch-test-helpers.d.ts +28 -0
- package/dist/watch-test-helpers.js +86 -0
- package/package.json +42 -3
- package/src/async.ts +40 -0
- package/src/chat/attachment-store.ts +108 -0
- package/src/chat/backend.ts +199 -0
- package/src/chat/background-work.ts +42 -0
- package/src/chat/bookmark-store.ts +166 -0
- package/src/chat/chat-core.ts +1228 -0
- package/src/chat/chat-log.ts +187 -0
- package/src/chat/chat-process.ts +237 -0
- package/src/chat/chat-session.ts +1723 -0
- package/src/chat/chat-store.ts +261 -0
- package/src/chat/chat-title.ts +90 -0
- package/src/chat/claude-backend.ts +235 -0
- package/src/chat/claude-protocol.ts +684 -0
- package/src/chat/claude-title.ts +106 -0
- package/src/chat/claude-transcript.ts +233 -0
- package/src/chat/codex-backend.ts +397 -0
- package/src/chat/codex-protocol.ts +872 -0
- package/src/chat/codex-thread.ts +162 -0
- package/src/chat/codex-transport.ts +224 -0
- package/src/chat/composer-preferences.ts +67 -0
- package/src/chat/context-breakdown.ts +80 -0
- package/src/chat/delta-coalescer.ts +62 -0
- package/src/chat/errors.ts +25 -0
- package/src/chat/fake-claude.ts +1006 -0
- package/src/chat/fake-cli.ts +199 -0
- package/src/chat/fake-codex.ts +534 -0
- package/src/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
- package/src/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
- package/src/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
- package/src/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
- package/src/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
- package/src/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
- package/src/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
- package/src/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
- package/src/chat/input.ts +89 -0
- package/src/chat/limit-resume.ts +74 -0
- package/src/chat/projector.ts +969 -0
- package/src/chat/request-summary.ts +168 -0
- package/src/chat/subagent-projection.ts +45 -0
- package/src/chat/subagent-reader.ts +469 -0
- package/src/chat/subagent-settlement.ts +111 -0
- package/src/chat/thread.ts +167 -0
- package/src/chat/wake-chat.ts +45 -0
- package/src/client-sinks.ts +39 -0
- package/src/coded-error.ts +18 -0
- package/src/context/argv.ts +61 -0
- package/src/context/refusal.ts +50 -0
- package/src/context/verb.ts +392 -0
- package/src/error-text.ts +28 -0
- package/src/events.ts +9 -0
- package/src/fs.ts +118 -0
- package/src/host/agent-host.ts +124 -0
- package/src/host/environment.ts +31 -0
- package/src/host/handlers.ts +142 -0
- package/src/host/memory-port.ts +27 -0
- package/src/host/wiring.ts +164 -0
- package/src/lineage.ts +159 -0
- package/src/messages/deliver-message.ts +61 -0
- package/src/messages/deliver-notice.ts +120 -0
- package/src/messages/notice-notes.ts +34 -0
- package/src/messages/notice-store.ts +142 -0
- package/src/modes.ts +38 -0
- package/src/outbox/manual-clock.ts +31 -0
- package/src/outbox/outbox-worker.ts +214 -0
- package/src/outbox/outbox.ts +106 -0
- package/src/providers/accounts/accounts.ts +192 -0
- package/src/providers/accounts/launch.ts +55 -0
- package/src/providers/accounts/service.ts +632 -0
- package/src/providers/accounts/shadow-home.ts +85 -0
- package/src/providers/accounts/status.ts +106 -0
- package/src/providers/accounts/store.ts +158 -0
- package/src/providers/accounts/test-accounts.ts +48 -0
- package/src/providers/accounts/variables.ts +132 -0
- package/src/providers/catalog.ts +90 -0
- package/src/providers/claude-models.json +114 -0
- package/src/providers/claude-provider.ts +32 -0
- package/src/providers/claude.ts +123 -0
- package/src/providers/codex-models.json +135 -0
- package/src/providers/codex-provider.ts +30 -0
- package/src/providers/codex-rules.ts +54 -0
- package/src/providers/codex.ts +69 -0
- package/src/providers/detect.ts +40 -0
- package/src/providers/provider.ts +46 -0
- package/src/providers/registry.ts +150 -0
- package/src/record-directory.ts +136 -0
- package/src/run-process.ts +61 -0
- package/src/serializer.ts +41 -0
- package/src/skills.ts +199 -0
- package/src/tasks/background-limit.ts +72 -0
- package/src/tasks/end-children.ts +100 -0
- package/src/tasks/give-task.ts +52 -0
- package/src/tasks/task-coordinator.ts +384 -0
- package/src/tasks/task-store.ts +236 -0
- package/src/tasks/task-work.ts +62 -0
- package/src/tasks/waiting-child.ts +203 -0
- package/src/tasks/wake-parent.ts +142 -0
- package/src/tasks/wiring.ts +231 -0
- package/src/title-file.ts +57 -0
- package/src/usage/accounts.ts +86 -0
- package/src/usage/aggregate.ts +220 -0
- package/src/usage/exchange.ts +93 -0
- package/src/usage/index-file.ts +178 -0
- package/src/usage/limits/monitor.ts +239 -0
- package/src/usage/limits/normalize.ts +188 -0
- package/src/usage/limits/probe.ts +120 -0
- package/src/usage/prices-snapshot.json +1071 -0
- package/src/usage/pricing.ts +236 -0
- package/src/usage/projects.ts +65 -0
- package/src/usage/readers/claude.ts +60 -0
- package/src/usage/readers/codex.ts +196 -0
- package/src/usage/record.ts +60 -0
- package/src/usage/roots.ts +39 -0
- package/src/usage/scanner.ts +265 -0
- package/src/usage/usage-service.ts +161 -0
- package/src/watch-seam.ts +111 -0
- package/src/watch-test-helpers.ts +102 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type ChatApprovalItem, type ChatQuestionItem, type ChatRequestApproval, type ChatRequestSummary } from '@adecore/agent-contracts';
|
|
2
|
+
export declare function approvalSummary(item: ChatApprovalItem): ChatRequestApproval;
|
|
3
|
+
export declare function requestSummaries(pending: ReadonlyArray<ChatApprovalItem | ChatQuestionItem>): ChatRequestSummary[];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { CHAT_REQUEST_LIMITS } from '@adecore/agent-contracts';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function str(value) {
|
|
6
|
+
return typeof value === 'string' ? value : null;
|
|
7
|
+
}
|
|
8
|
+
// The keys a tool's input names its subject under, in the order a person would look for one.
|
|
9
|
+
const SUBJECT_KEYS = ['file_path', 'notebook_path', 'path', 'url', 'query', 'pattern', 'skill', 'description'];
|
|
10
|
+
/* Cuts text at `max` code units, never between the halves of a surrogate pair. */
|
|
11
|
+
function cut(text, max) {
|
|
12
|
+
if (text.length <= max) {
|
|
13
|
+
return text;
|
|
14
|
+
}
|
|
15
|
+
const end = /[\uD800-\uDBFF]/.test(text[max - 1] ?? '') ? max - 1 : max;
|
|
16
|
+
return text.slice(0, end);
|
|
17
|
+
}
|
|
18
|
+
function clip(text, max) {
|
|
19
|
+
return text.length <= max ? text : `${cut(text, max - 1)}…`;
|
|
20
|
+
}
|
|
21
|
+
/* Keeps the end of a path, where its file name is. */
|
|
22
|
+
function clipPath(path, max) {
|
|
23
|
+
if (path.length <= max) {
|
|
24
|
+
return path;
|
|
25
|
+
}
|
|
26
|
+
const start = path.length - (max - 1);
|
|
27
|
+
return `…${path.slice(/[\uDC00-\uDFFF]/.test(path[start] ?? '') ? start + 1 : start)}`;
|
|
28
|
+
}
|
|
29
|
+
function firstLine(text) {
|
|
30
|
+
return text.trim().split('\n', 1)[0] ?? '';
|
|
31
|
+
}
|
|
32
|
+
/* At most `maxLines` lines and `maxLength` code units, cut at a line; a first line longer than that is cut in itself. */
|
|
33
|
+
function clipLines(lines, maxLines, maxLength) {
|
|
34
|
+
const kept = [];
|
|
35
|
+
let length = 0;
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
if (kept.length === maxLines || length + line.length + (kept.length > 0 ? 1 : 0) > maxLength) {
|
|
38
|
+
if (kept.length === 0) {
|
|
39
|
+
return { text: cut(line, maxLength), truncated: true };
|
|
40
|
+
}
|
|
41
|
+
return { text: kept.join('\n'), truncated: true };
|
|
42
|
+
}
|
|
43
|
+
length += line.length + (kept.length > 0 ? 1 : 0);
|
|
44
|
+
kept.push(line);
|
|
45
|
+
}
|
|
46
|
+
return { text: kept.join('\n'), truncated: false };
|
|
47
|
+
}
|
|
48
|
+
function splitLines(text) {
|
|
49
|
+
return text === '' ? [] : text.replace(/\n$/, '').split('\n');
|
|
50
|
+
}
|
|
51
|
+
/* The lines a replacement takes out and puts in; a provider's edit is a fragment, so there is no context to show. */
|
|
52
|
+
function replacementLines(before, after) {
|
|
53
|
+
return [...splitLines(before).map((line) => `-${line}`), ...splitLines(after).map((line) => `+${line}`)];
|
|
54
|
+
}
|
|
55
|
+
/* Only what a unified diff changes: its headers and context lines stay in the thread. */
|
|
56
|
+
function changedLines(diff) {
|
|
57
|
+
return splitLines(diff).filter((line) => (line.startsWith('-') && !line.startsWith('---')) || (line.startsWith('+') && !line.startsWith('+++')));
|
|
58
|
+
}
|
|
59
|
+
/* The first file a call changes, as Codex's patch or Claude's edit and write tools carry it; null for any other call. */
|
|
60
|
+
function fileChange(toolName, input) {
|
|
61
|
+
const patches = Array.isArray(input.changes)
|
|
62
|
+
? input.changes.filter((change) => isRecord(change) && typeof change.path === 'string' && typeof change.diff === 'string')
|
|
63
|
+
: [];
|
|
64
|
+
if (patches.length > 0) {
|
|
65
|
+
return { path: patches[0].path, files: patches.length, lines: changedLines(patches[0].diff) };
|
|
66
|
+
}
|
|
67
|
+
const path = str(input.file_path);
|
|
68
|
+
if (path === null) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (toolName === 'Edit') {
|
|
72
|
+
return { path, files: 1, lines: replacementLines(str(input.old_string) ?? '', str(input.new_string) ?? '') };
|
|
73
|
+
}
|
|
74
|
+
if (toolName === 'Write') {
|
|
75
|
+
return { path, files: 1, lines: replacementLines('', str(input.content) ?? '') };
|
|
76
|
+
}
|
|
77
|
+
if (toolName === 'MultiEdit' && Array.isArray(input.edits)) {
|
|
78
|
+
return {
|
|
79
|
+
path,
|
|
80
|
+
files: 1,
|
|
81
|
+
lines: input.edits.filter(isRecord).flatMap((edit) => replacementLines(str(edit.old_string) ?? '', str(edit.new_string) ?? ''))
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function subjectOf(input) {
|
|
87
|
+
const named = SUBJECT_KEYS.map((key) => str(input[key])).find((value) => value !== null && value.trim() !== '');
|
|
88
|
+
const first = named ?? Object.values(input).find((value) => typeof value === 'string' && value.trim() !== '') ?? '';
|
|
89
|
+
return clip(firstLine(first), CHAT_REQUEST_LIMITS.subject);
|
|
90
|
+
}
|
|
91
|
+
export function approvalSummary(item) {
|
|
92
|
+
const input = isRecord(item.input) ? item.input : {};
|
|
93
|
+
const change = fileChange(item.toolName, input);
|
|
94
|
+
const command = change === null ? str(input.command) : null;
|
|
95
|
+
const diff = change === null ? null : clipLines(change.lines, CHAT_REQUEST_LIMITS.diffLines, CHAT_REQUEST_LIMITS.diff);
|
|
96
|
+
const shell = command === null ? null : clipLines(splitLines(command), CHAT_REQUEST_LIMITS.commandLines, CHAT_REQUEST_LIMITS.command);
|
|
97
|
+
const truncated = diff?.truncated === true || shell?.truncated === true;
|
|
98
|
+
return {
|
|
99
|
+
toolName: clip(item.toolName, CHAT_REQUEST_LIMITS.subject),
|
|
100
|
+
subject: change !== null
|
|
101
|
+
? clipPath(change.path, CHAT_REQUEST_LIMITS.subject)
|
|
102
|
+
: command !== null
|
|
103
|
+
? clip(firstLine(command), CHAT_REQUEST_LIMITS.subject)
|
|
104
|
+
: subjectOf(input),
|
|
105
|
+
description: item.description === null ? null : clip(item.description, CHAT_REQUEST_LIMITS.description),
|
|
106
|
+
...(change === null ? {} : { path: clipPath(change.path, CHAT_REQUEST_LIMITS.subject) }),
|
|
107
|
+
...(change !== null && change.files > 1 ? { files: change.files } : {}),
|
|
108
|
+
...(diff === null || diff.text === '' ? {} : { diff: diff.text }),
|
|
109
|
+
...(shell === null ? {} : { command: shell.text }),
|
|
110
|
+
...(truncated ? { truncated: true } : {}),
|
|
111
|
+
canAllowAlways: item.canAllowAlways,
|
|
112
|
+
...(item.allowAlways
|
|
113
|
+
? {
|
|
114
|
+
allowAlways: {
|
|
115
|
+
label: clip(item.allowAlways.label, CHAT_REQUEST_LIMITS.header),
|
|
116
|
+
description: clip(item.allowAlways.description, CHAT_REQUEST_LIMITS.description)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
: {})
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function questionSummary(item) {
|
|
123
|
+
return {
|
|
124
|
+
questions: item.questions.map((question) => ({
|
|
125
|
+
id: question.id,
|
|
126
|
+
header: clip(question.header, CHAT_REQUEST_LIMITS.header),
|
|
127
|
+
question: clip(question.question, CHAT_REQUEST_LIMITS.question),
|
|
128
|
+
choices: question.choices.map((choice) => ({ label: choice.label, description: clip(choice.description, CHAT_REQUEST_LIMITS.choiceDescription) })),
|
|
129
|
+
multiSelect: question.multiSelect
|
|
130
|
+
})),
|
|
131
|
+
...(item.async === undefined ? {} : { async: item.async })
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/* What a chat waits on, oldest first, as `ChatInfo.requests` carries it. */
|
|
135
|
+
export function requestSummaries(pending) {
|
|
136
|
+
return pending.slice(0, CHAT_REQUEST_LIMITS.perChat).map((item) => ({
|
|
137
|
+
requestId: item.requestId,
|
|
138
|
+
itemId: item.id,
|
|
139
|
+
kind: item.kind,
|
|
140
|
+
createdAt: item.createdAt,
|
|
141
|
+
...(item.kind === 'approval' ? { approval: approvalSummary(item) } : { question: questionSummary(item) })
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChatItem } from '@adecore/agent-contracts';
|
|
2
|
+
import { ThreadProjector } from './projector.ts';
|
|
3
|
+
import { ChatThread } from './thread.ts';
|
|
4
|
+
export declare function readingThread(providerName: string, now: () => number): {
|
|
5
|
+
thread: ChatThread;
|
|
6
|
+
projector: ThreadProjector;
|
|
7
|
+
};
|
|
8
|
+
export declare function settledReading(item: ChatItem): ChatItem;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ThreadProjector } from './projector.js';
|
|
2
|
+
import { ChatThread } from './thread.js';
|
|
3
|
+
// A subagent's conversation has no turns of its own; the projector is kept inside this one so it never opens any.
|
|
4
|
+
const READING_TURN = 'subagent-reading';
|
|
5
|
+
/* The info a thread needs to exist. Nothing reads it: a subagent's conversation is only ever its items. */
|
|
6
|
+
function readingInfo() {
|
|
7
|
+
return {
|
|
8
|
+
chatId: 'subagent',
|
|
9
|
+
provider: 'claude',
|
|
10
|
+
cwd: '',
|
|
11
|
+
agentSessionId: null,
|
|
12
|
+
model: null,
|
|
13
|
+
selection: { model: '', options: {} },
|
|
14
|
+
runtimeMode: 'full-access',
|
|
15
|
+
status: 'running',
|
|
16
|
+
running: true,
|
|
17
|
+
activeTurnId: READING_TURN,
|
|
18
|
+
slashCommands: [],
|
|
19
|
+
usage: { contextTokens: 0, contextWindow: 0, costUsd: 0, turns: 0 },
|
|
20
|
+
createdAt: 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/* A thread and the projector that writes it, for a conversation read back rather than streamed. */
|
|
24
|
+
export function readingThread(providerName, now) {
|
|
25
|
+
const thread = new ChatThread(readingInfo());
|
|
26
|
+
return { thread, projector: new ThreadProjector(thread, { providerName, now }) };
|
|
27
|
+
}
|
|
28
|
+
/*
|
|
29
|
+
* An item as a reader of a finished record sees it: nothing streams any more and nothing belongs to a
|
|
30
|
+
* turn, so the timeline draws the whole conversation flat instead of folding it behind a turn it never had.
|
|
31
|
+
*/
|
|
32
|
+
export function settledReading(item) {
|
|
33
|
+
if (item.kind === 'assistant') {
|
|
34
|
+
return { ...item, turnId: null, streaming: false };
|
|
35
|
+
}
|
|
36
|
+
if (item.kind === 'thinking') {
|
|
37
|
+
return { ...item, turnId: null, streaming: false, endedAt: item.endedAt ?? item.createdAt };
|
|
38
|
+
}
|
|
39
|
+
return { ...item, turnId: null };
|
|
40
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ChatInfo, type ChatItem, type ChatSubagentChangedEvent, type ChatSubagentResult } from '@adecore/agent-contracts';
|
|
2
|
+
import { type WatchSeams } from '../watch-seam.ts';
|
|
3
|
+
import { type CodexProcessSpec, type ThreadItemsParams } from './codex-thread.ts';
|
|
4
|
+
import { type SubagentSettlement } from './subagent-settlement.ts';
|
|
5
|
+
export interface SubagentChat {
|
|
6
|
+
info: ChatInfo;
|
|
7
|
+
running: boolean;
|
|
8
|
+
items(): ChatItem[];
|
|
9
|
+
listThreadItems(params: ThreadItemsParams): Promise<unknown> | null;
|
|
10
|
+
noteNative(toolUseId: string, native: {
|
|
11
|
+
agentId?: string;
|
|
12
|
+
threadId?: string;
|
|
13
|
+
}): void;
|
|
14
|
+
}
|
|
15
|
+
export interface SubagentReaderOptions {
|
|
16
|
+
chat(chatId: string): SubagentChat | null;
|
|
17
|
+
chatInfo?(chatId: string): Promise<ChatInfo | null>;
|
|
18
|
+
claudeProjectsDir: string;
|
|
19
|
+
claudeProjectsDirOf?(info: ChatInfo): string;
|
|
20
|
+
codexProcess(info: ChatInfo): CodexProcessSpec;
|
|
21
|
+
notify(clientId: string, event: ChatSubagentChangedEvent): void;
|
|
22
|
+
seams?: WatchSeams;
|
|
23
|
+
now?(): number;
|
|
24
|
+
listOnce?(spec: CodexProcessSpec, params: ThreadItemsParams): Promise<unknown>;
|
|
25
|
+
}
|
|
26
|
+
export declare class SubagentReader {
|
|
27
|
+
private readonly options;
|
|
28
|
+
private readonly seams;
|
|
29
|
+
private readonly now;
|
|
30
|
+
private readonly listOnce;
|
|
31
|
+
private readonly claudeFiles;
|
|
32
|
+
private readonly projections;
|
|
33
|
+
private readonly codexRows;
|
|
34
|
+
private readonly codexCache;
|
|
35
|
+
private readonly codexSeen;
|
|
36
|
+
private readonly holds;
|
|
37
|
+
private readonly dirWatches;
|
|
38
|
+
private readonly changed;
|
|
39
|
+
private cancelSettle;
|
|
40
|
+
constructor(options: SubagentReaderOptions);
|
|
41
|
+
read(chatId: string, toolUseId: string, cursor?: string, limit?: number): Promise<ChatSubagentResult>;
|
|
42
|
+
readAll(chatId: string, toolUseId: string): Promise<ChatItem[]>;
|
|
43
|
+
claudeSettlement(chatId: string, toolUseId: string): Promise<SubagentSettlement | null>;
|
|
44
|
+
hold(clientId: string, chatId: string, toolUseId: string): void;
|
|
45
|
+
release(clientId: string, chatId: string, toolUseId: string): void;
|
|
46
|
+
releaseClient(clientId: string): void;
|
|
47
|
+
releaseChat(chatId: string): void;
|
|
48
|
+
get holdCount(): number;
|
|
49
|
+
private require;
|
|
50
|
+
private drop;
|
|
51
|
+
private live;
|
|
52
|
+
private readClaude;
|
|
53
|
+
private claudeProjection;
|
|
54
|
+
private claudeFile;
|
|
55
|
+
private trimProjections;
|
|
56
|
+
private claudeSubagentOf;
|
|
57
|
+
private readCodex;
|
|
58
|
+
private codexThreadOf;
|
|
59
|
+
private codexThread;
|
|
60
|
+
private codexPage;
|
|
61
|
+
private watchClaude;
|
|
62
|
+
private onClaudeChange;
|
|
63
|
+
private pollCodex;
|
|
64
|
+
private markChanged;
|
|
65
|
+
private flushChanged;
|
|
66
|
+
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { workflowAgentIdOf } from '@adecore/agent-contracts';
|
|
3
|
+
import { SYSTEM_WATCH } from '../watch-seam.js';
|
|
4
|
+
import { findSubagentsDir, findWorkflowAgent, readSubagentMetas, TranscriptProjection } from './claude-transcript.js';
|
|
5
|
+
import { listThreadItemsOnce, parseThreadItemsPage, projectCodexItems } from './codex-thread.js';
|
|
6
|
+
import { ChatError } from './errors.js';
|
|
7
|
+
import { readSubagentSettlement } from './subagent-settlement.js';
|
|
8
|
+
const DEFAULT_PAGE = 60;
|
|
9
|
+
// How often an open panel asks a running Codex for the newest items of the thread it follows.
|
|
10
|
+
const CODEX_POLL_MS = 2000;
|
|
11
|
+
// A page asked of a process started for the question is kept this long, so paging back costs one process.
|
|
12
|
+
const CODEX_CACHE_MS = 5000;
|
|
13
|
+
// What a poll compares: enough of the newest end to see an item land or settle.
|
|
14
|
+
const CODEX_POLL_ITEMS = 10;
|
|
15
|
+
// Lines land in bursts; a panel is told once per burst, not once per line.
|
|
16
|
+
const CHANGE_SETTLE_MS = 150;
|
|
17
|
+
// Transcripts kept read, beyond the ones a client holds open.
|
|
18
|
+
const MAX_IDLE_PROJECTIONS = 8;
|
|
19
|
+
const CODEX_CURSOR = 'codex:';
|
|
20
|
+
function holdKey(chatId, toolUseId) {
|
|
21
|
+
return `${chatId}\n${toolUseId}`;
|
|
22
|
+
}
|
|
23
|
+
function findSubagent(items, toolUseId) {
|
|
24
|
+
return items.find((item) => item.kind === 'subagent' && item.toolUseId === toolUseId);
|
|
25
|
+
}
|
|
26
|
+
// A workflow's agent works while its Workflow row runs and its last report has it running.
|
|
27
|
+
function workflowAgentRunning(items, agentId) {
|
|
28
|
+
return items.some((item) => item.kind === 'tool' &&
|
|
29
|
+
item.state === 'running' &&
|
|
30
|
+
item.workflow?.agents.some((agent) => agent.agentId === agentId && agent.status === 'running') === true);
|
|
31
|
+
}
|
|
32
|
+
/*
|
|
33
|
+
* The whole conversation of a subagent, which the chat's own thread only keeps the beginning of:
|
|
34
|
+
* Claude's transcript beside the session, or the thread Codex keeps per agent. A client that holds a
|
|
35
|
+
* conversation is told when it grows (a counted watch on Claude's folder, a poll of the running Codex)
|
|
36
|
+
* for as long as it holds it, and the event carries nothing but that there is more.
|
|
37
|
+
*/
|
|
38
|
+
export class SubagentReader {
|
|
39
|
+
options;
|
|
40
|
+
seams;
|
|
41
|
+
now;
|
|
42
|
+
listOnce;
|
|
43
|
+
claudeFiles = new Map();
|
|
44
|
+
// Keyed on the transcript path, with the chat it was read for so a grandchild's status can be found.
|
|
45
|
+
projections = new Map();
|
|
46
|
+
// The subagents seen inside a Codex conversation, so a row of a grandchild knows its thread.
|
|
47
|
+
codexRows = new Map();
|
|
48
|
+
codexCache = new Map();
|
|
49
|
+
// The newest end of every Codex thread as a reader last saw it, which is what a poll compares against.
|
|
50
|
+
codexSeen = new Map();
|
|
51
|
+
holds = new Map();
|
|
52
|
+
dirWatches = new Map();
|
|
53
|
+
changed = new Set();
|
|
54
|
+
cancelSettle = null;
|
|
55
|
+
constructor(options) {
|
|
56
|
+
this.options = options;
|
|
57
|
+
this.seams = options.seams ?? SYSTEM_WATCH;
|
|
58
|
+
this.now = options.now ?? Date.now;
|
|
59
|
+
this.listOnce = options.listOnce ?? listThreadItemsOnce;
|
|
60
|
+
}
|
|
61
|
+
async read(chatId, toolUseId, cursor, limit = DEFAULT_PAGE) {
|
|
62
|
+
const chat = this.require(chatId);
|
|
63
|
+
return chat.info.provider === 'codex' ? this.readCodex(chat, toolUseId, cursor, limit) : this.readClaude(chat, toolUseId, cursor, limit);
|
|
64
|
+
}
|
|
65
|
+
/* Every item, for an agent reading the conversation as text; a Codex thread is paged through to its start. */
|
|
66
|
+
async readAll(chatId, toolUseId) {
|
|
67
|
+
const chat = this.require(chatId);
|
|
68
|
+
if (chat.info.provider !== 'codex') {
|
|
69
|
+
return (await this.claudeProjection(chat, toolUseId)).items();
|
|
70
|
+
}
|
|
71
|
+
const pages = [];
|
|
72
|
+
let cursor;
|
|
73
|
+
do {
|
|
74
|
+
const page = await this.readCodex(chat, toolUseId, cursor, 100);
|
|
75
|
+
pages.unshift(page.items);
|
|
76
|
+
cursor = page.history.cursor ?? undefined;
|
|
77
|
+
} while (cursor !== undefined);
|
|
78
|
+
return pages.flat();
|
|
79
|
+
}
|
|
80
|
+
/*
|
|
81
|
+
* What a Claude subagent's own transcript says about how it ended, for a row whose CLI is gone and
|
|
82
|
+
* so will never send the notification; null when there is no transcript or it does not show an end.
|
|
83
|
+
*/
|
|
84
|
+
async claudeSettlement(chatId, toolUseId) {
|
|
85
|
+
const chat = this.options.chat(chatId);
|
|
86
|
+
if (!chat || chat.info.provider !== 'claude') {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return await readSubagentSettlement((await this.claudeFile(chat, toolUseId)).path);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/* Keeps `clientId` told about this conversation until it lets go, closes its socket or the chat goes. */
|
|
97
|
+
hold(clientId, chatId, toolUseId) {
|
|
98
|
+
const key = holdKey(chatId, toolUseId);
|
|
99
|
+
const existing = this.holds.get(key);
|
|
100
|
+
if (existing) {
|
|
101
|
+
existing.clients.add(clientId);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const chat = this.options.chat(chatId);
|
|
105
|
+
if (!chat) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const stop = chat.info.provider === 'codex' ? this.pollCodex(key, chatId, toolUseId) : this.watchClaude(key);
|
|
109
|
+
this.holds.set(key, { chatId, toolUseId, clients: new Set([clientId]), stop });
|
|
110
|
+
}
|
|
111
|
+
release(clientId, chatId, toolUseId) {
|
|
112
|
+
const key = holdKey(chatId, toolUseId);
|
|
113
|
+
const hold = this.holds.get(key);
|
|
114
|
+
if (!hold) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
hold.clients.delete(clientId);
|
|
118
|
+
if (hold.clients.size === 0) {
|
|
119
|
+
this.drop(key, hold);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
releaseClient(clientId) {
|
|
123
|
+
for (const hold of [...this.holds.values()]) {
|
|
124
|
+
this.release(clientId, hold.chatId, hold.toolUseId);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/* Forgets everything about a chat that is gone. */
|
|
128
|
+
releaseChat(chatId) {
|
|
129
|
+
const ofChat = (key) => key.startsWith(`${chatId}\n`);
|
|
130
|
+
for (const [key, hold] of [...this.holds]) {
|
|
131
|
+
if (ofChat(key)) {
|
|
132
|
+
this.drop(key, hold);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const [key, file] of [...this.claudeFiles]) {
|
|
136
|
+
if (ofChat(key)) {
|
|
137
|
+
this.claudeFiles.delete(key);
|
|
138
|
+
this.projections.delete(file.path);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const map of [this.codexRows, this.codexSeen]) {
|
|
142
|
+
for (const key of [...map.keys()]) {
|
|
143
|
+
if (ofChat(key)) {
|
|
144
|
+
map.delete(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/* How many clients hold something, for a test that checks a socket let go of everything. */
|
|
150
|
+
get holdCount() {
|
|
151
|
+
return this.holds.size;
|
|
152
|
+
}
|
|
153
|
+
require(chatId) {
|
|
154
|
+
const chat = this.options.chat(chatId);
|
|
155
|
+
if (!chat) {
|
|
156
|
+
throw new ChatError('chat-not-found', `No chat ${chatId}`);
|
|
157
|
+
}
|
|
158
|
+
return chat;
|
|
159
|
+
}
|
|
160
|
+
drop(key, hold) {
|
|
161
|
+
this.holds.delete(key);
|
|
162
|
+
this.changed.delete(key);
|
|
163
|
+
hold.stop();
|
|
164
|
+
}
|
|
165
|
+
/* Running while its row says so, whether that row is in the chat's thread or in a conversation read before. */
|
|
166
|
+
live(chat, toolUseId, inside) {
|
|
167
|
+
const workflowAgent = workflowAgentIdOf(toolUseId);
|
|
168
|
+
if (workflowAgent !== null) {
|
|
169
|
+
return workflowAgentRunning(chat.items(), workflowAgent);
|
|
170
|
+
}
|
|
171
|
+
const row = findSubagent(chat.items(), toolUseId) ?? findSubagent(inside, toolUseId);
|
|
172
|
+
return row?.kind === 'subagent' && row.status === 'running';
|
|
173
|
+
}
|
|
174
|
+
async readClaude(chat, toolUseId, cursor, limit) {
|
|
175
|
+
const projection = await this.claudeProjection(chat, toolUseId);
|
|
176
|
+
const page = projection.page(limit, cursor);
|
|
177
|
+
const seen = [...this.projections.values()].filter((entry) => entry.chatId === chat.info.chatId).flatMap((entry) => entry.projection.items());
|
|
178
|
+
return {
|
|
179
|
+
items: page.items,
|
|
180
|
+
history: page.history,
|
|
181
|
+
source: 'claude-transcript',
|
|
182
|
+
context: { provider: 'claude', cwd: chat.info.cwd },
|
|
183
|
+
live: this.live(chat, toolUseId, seen)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async claudeProjection(chat, toolUseId) {
|
|
187
|
+
const file = await this.claudeFile(chat, toolUseId);
|
|
188
|
+
let entry = this.projections.get(file.path);
|
|
189
|
+
if (!entry) {
|
|
190
|
+
entry = { chatId: chat.info.chatId, projection: new TranscriptProjection(file.path) };
|
|
191
|
+
this.projections.set(file.path, entry);
|
|
192
|
+
this.trimProjections();
|
|
193
|
+
}
|
|
194
|
+
if (!(await entry.projection.refresh())) {
|
|
195
|
+
throw new ChatError('subagent-not-found', 'Claude has not written a transcript for this subagent');
|
|
196
|
+
}
|
|
197
|
+
return entry.projection;
|
|
198
|
+
}
|
|
199
|
+
async claudeFile(chat, toolUseId) {
|
|
200
|
+
const key = holdKey(chat.info.chatId, toolUseId);
|
|
201
|
+
const known = this.claudeFiles.get(key);
|
|
202
|
+
if (known) {
|
|
203
|
+
return known;
|
|
204
|
+
}
|
|
205
|
+
const found = await this.claudeSubagentOf(chat.info, toolUseId);
|
|
206
|
+
if (found === null) {
|
|
207
|
+
throw new ChatError('subagent-not-found', 'Claude has not written a transcript for this subagent');
|
|
208
|
+
}
|
|
209
|
+
const { dir, meta } = found;
|
|
210
|
+
const file = { dir, path: meta.transcript };
|
|
211
|
+
this.claudeFiles.set(key, file);
|
|
212
|
+
chat.noteNative(toolUseId, { agentId: meta.agentId });
|
|
213
|
+
return file;
|
|
214
|
+
}
|
|
215
|
+
/* The transcripts nobody holds are only a cache: past a handful, the oldest is read again when asked. */
|
|
216
|
+
trimProjections() {
|
|
217
|
+
const held = new Set([...this.holds.values()].map((hold) => this.claudeFiles.get(holdKey(hold.chatId, hold.toolUseId))?.path));
|
|
218
|
+
const idle = [...this.projections.keys()].filter((path) => !held.has(path));
|
|
219
|
+
for (const path of idle.slice(0, Math.max(0, idle.length - MAX_IDLE_PROJECTIONS))) {
|
|
220
|
+
this.projections.delete(path);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/*
|
|
224
|
+
* The folder and meta of a subagent, in the chat's own session or, for a row a fork copied, in the
|
|
225
|
+
* session of the chat it was forked from: the CLI keeps subagents beside the session that ran them,
|
|
226
|
+
* and a fork's transcript is a new session with none. One step per fork, so a fork of a fork works too.
|
|
227
|
+
*/
|
|
228
|
+
async claudeSubagentOf(info, toolUseId) {
|
|
229
|
+
const seen = new Set();
|
|
230
|
+
let current = info;
|
|
231
|
+
while (current !== null && !seen.has(current.chatId)) {
|
|
232
|
+
seen.add(current.chatId);
|
|
233
|
+
const sessionId = current.agentSessionId;
|
|
234
|
+
const dir = sessionId === null
|
|
235
|
+
? null
|
|
236
|
+
: await findSubagentsDir(this.options.claudeProjectsDirOf?.(current) ?? this.options.claudeProjectsDir, current.cwd, sessionId);
|
|
237
|
+
const workflowAgent = workflowAgentIdOf(toolUseId);
|
|
238
|
+
if (dir !== null && workflowAgent !== null) {
|
|
239
|
+
const meta = await findWorkflowAgent(dir, workflowAgent);
|
|
240
|
+
if (meta !== null) {
|
|
241
|
+
return { dir: dirname(meta.transcript), meta };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// A grandchild has a file of its own in the same folder, found by the call that opened it like any other.
|
|
245
|
+
const meta = dir === null ? undefined : (await readSubagentMetas(dir)).find((candidate) => candidate.toolUseId === toolUseId);
|
|
246
|
+
if (dir !== null && meta !== undefined) {
|
|
247
|
+
return { dir, meta };
|
|
248
|
+
}
|
|
249
|
+
const origin = current.forkOf?.chatId;
|
|
250
|
+
current = origin === undefined || !this.options.chatInfo ? null : await this.options.chatInfo(origin);
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
async readCodex(chat, toolUseId, cursor, limit) {
|
|
255
|
+
const threadId = this.codexThread(chat, toolUseId);
|
|
256
|
+
if (cursor !== undefined && !cursor.startsWith(CODEX_CURSOR)) {
|
|
257
|
+
throw new ChatError('history-expired', 'The conversation changed. Reload its history.');
|
|
258
|
+
}
|
|
259
|
+
const params = {
|
|
260
|
+
threadId,
|
|
261
|
+
limit,
|
|
262
|
+
sortDirection: 'desc',
|
|
263
|
+
...(cursor === undefined ? {} : { cursor: cursor.slice(CODEX_CURSOR.length) })
|
|
264
|
+
};
|
|
265
|
+
const { entries, nextCursor } = parseThreadItemsPage(await this.codexPage(chat, params));
|
|
266
|
+
if (cursor === undefined) {
|
|
267
|
+
// What a poll that starts after this read compares against, so a change in between is not lost.
|
|
268
|
+
this.codexSeen.set(holdKey(chat.info.chatId, toolUseId), JSON.stringify(entries.slice(0, CODEX_POLL_ITEMS)));
|
|
269
|
+
}
|
|
270
|
+
const items = projectCodexItems([...entries].reverse(), this.now());
|
|
271
|
+
for (const item of items) {
|
|
272
|
+
if (item.kind === 'subagent') {
|
|
273
|
+
this.codexRows.set(holdKey(chat.info.chatId, item.toolUseId), item);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
items,
|
|
278
|
+
history: { cursor: nextCursor === null ? null : `${CODEX_CURSOR}${nextCursor}` },
|
|
279
|
+
source: 'codex-thread',
|
|
280
|
+
context: { provider: 'codex', cwd: chat.info.cwd },
|
|
281
|
+
live: chat.running && this.live(chat, toolUseId, [...this.codexRows.values()])
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
codexThreadOf(chat, toolUseId) {
|
|
285
|
+
const row = findSubagent(chat.items(), toolUseId) ?? this.codexRows.get(holdKey(chat.info.chatId, toolUseId));
|
|
286
|
+
return row?.kind === 'subagent' ? (row.native?.threadId ?? null) : null;
|
|
287
|
+
}
|
|
288
|
+
codexThread(chat, toolUseId) {
|
|
289
|
+
const threadId = this.codexThreadOf(chat, toolUseId);
|
|
290
|
+
if (threadId === null) {
|
|
291
|
+
throw new ChatError('subagent-not-found', 'Codex has not said which thread this subagent works in');
|
|
292
|
+
}
|
|
293
|
+
return threadId;
|
|
294
|
+
}
|
|
295
|
+
async codexPage(chat, params) {
|
|
296
|
+
const running = chat.listThreadItems(params);
|
|
297
|
+
if (running !== null) {
|
|
298
|
+
return running;
|
|
299
|
+
}
|
|
300
|
+
const key = JSON.stringify(params);
|
|
301
|
+
const now = this.now();
|
|
302
|
+
const cached = this.codexCache.get(key);
|
|
303
|
+
if (cached && now - cached.at < CODEX_CACHE_MS) {
|
|
304
|
+
return cached.result;
|
|
305
|
+
}
|
|
306
|
+
const result = await this.listOnce(this.options.codexProcess(chat.info), params);
|
|
307
|
+
for (const [stale, entry] of [...this.codexCache]) {
|
|
308
|
+
if (now - entry.at >= CODEX_CACHE_MS) {
|
|
309
|
+
this.codexCache.delete(stale);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
this.codexCache.set(key, { at: now, result });
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
/* One watch per subagents folder, however many of its transcripts are held. */
|
|
316
|
+
watchClaude(key) {
|
|
317
|
+
const file = this.claudeFiles.get(key);
|
|
318
|
+
if (!file) {
|
|
319
|
+
return () => undefined;
|
|
320
|
+
}
|
|
321
|
+
let shared = this.dirWatches.get(file.dir);
|
|
322
|
+
if (!shared) {
|
|
323
|
+
const watcher = this.seams.watch(file.dir, { recursive: false }, (_event, filename) => this.onClaudeChange(file.dir, filename));
|
|
324
|
+
// A folder that disappears ends the growing, not the host.
|
|
325
|
+
watcher.on('error', () => undefined);
|
|
326
|
+
shared = { watcher, keys: new Set() };
|
|
327
|
+
this.dirWatches.set(file.dir, shared);
|
|
328
|
+
}
|
|
329
|
+
shared.keys.add(key);
|
|
330
|
+
return () => {
|
|
331
|
+
const current = this.dirWatches.get(file.dir);
|
|
332
|
+
current?.keys.delete(key);
|
|
333
|
+
if (current && current.keys.size === 0) {
|
|
334
|
+
current.watcher.close();
|
|
335
|
+
this.dirWatches.delete(file.dir);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
onClaudeChange(dir, filename) {
|
|
340
|
+
for (const key of this.dirWatches.get(dir)?.keys ?? []) {
|
|
341
|
+
const path = this.claudeFiles.get(key)?.path;
|
|
342
|
+
// A platform that names no file could mean any of them.
|
|
343
|
+
if (filename === null || (path !== undefined && path.endsWith(`/${filename}`))) {
|
|
344
|
+
this.markChanged(key);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
pollCodex(key, chatId, toolUseId) {
|
|
349
|
+
let cancel = null;
|
|
350
|
+
let stopped = false;
|
|
351
|
+
const tick = async () => {
|
|
352
|
+
cancel = null;
|
|
353
|
+
const chat = this.options.chat(chatId);
|
|
354
|
+
const threadId = chat ? this.codexThreadOf(chat, toolUseId) : null;
|
|
355
|
+
const asked = chat && threadId ? chat.listThreadItems({ threadId, limit: CODEX_POLL_ITEMS, sortDirection: 'desc' }) : null;
|
|
356
|
+
if (asked !== null) {
|
|
357
|
+
try {
|
|
358
|
+
const seen = JSON.stringify(parseThreadItemsPage(await asked).entries);
|
|
359
|
+
if (!stopped && this.codexSeen.get(key) !== seen) {
|
|
360
|
+
this.codexSeen.set(key, seen);
|
|
361
|
+
this.markChanged(key);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// The process went between the question and the answer; the next tick asks again.
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (!stopped) {
|
|
369
|
+
cancel = this.seams.schedule(() => tick(), CODEX_POLL_MS);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
cancel = this.seams.schedule(() => tick(), CODEX_POLL_MS);
|
|
373
|
+
return () => {
|
|
374
|
+
stopped = true;
|
|
375
|
+
cancel?.();
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
markChanged(key) {
|
|
379
|
+
this.changed.add(key);
|
|
380
|
+
if (this.cancelSettle !== null) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this.cancelSettle = this.seams.schedule(() => this.flushChanged(), CHANGE_SETTLE_MS);
|
|
384
|
+
}
|
|
385
|
+
flushChanged() {
|
|
386
|
+
this.cancelSettle = null;
|
|
387
|
+
const keys = [...this.changed];
|
|
388
|
+
this.changed.clear();
|
|
389
|
+
for (const key of keys) {
|
|
390
|
+
const hold = this.holds.get(key);
|
|
391
|
+
for (const clientId of hold?.clients ?? []) {
|
|
392
|
+
this.options.notify(clientId, { chatId: hold.chatId, toolUseId: hold.toolUseId });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface SubagentSettlement {
|
|
2
|
+
finishedAt: number | null;
|
|
3
|
+
report: string | null;
|
|
4
|
+
}
|
|
5
|
+
export declare function settlementOf(text: string): SubagentSettlement | null;
|
|
6
|
+
export declare function readSubagentSettlement(path: string): Promise<SubagentSettlement | null>;
|