@adecore/agents 0.0.1 → 0.17.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +48 -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,106 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { usageRoots } from '../usage/roots.ts';
|
|
5
|
+
import { cleanTitle, readLines } from '../title-file.ts';
|
|
6
|
+
|
|
7
|
+
interface Progress {
|
|
8
|
+
offset: number;
|
|
9
|
+
aiTitle: string | null;
|
|
10
|
+
customTitle: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// The two records carry these words; a line without them is never parsed.
|
|
14
|
+
const MARKERS = ['"ai-title"', '"custom-title"'];
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* The name Claude Code gave a session, out of its own transcript. It writes an `ai-title` record a
|
|
18
|
+
* few seconds after the first prompt, anywhere in the file and more than once, and a `custom-title`
|
|
19
|
+
* when a person ran /rename in the CLI, which wins because a person chose it. A transcript only ever
|
|
20
|
+
* grows, so each path is read from where the previous read stopped: a file of many megabytes costs
|
|
21
|
+
* its full read once, and every read after that only the lines added since.
|
|
22
|
+
*/
|
|
23
|
+
export class ClaudeTitleReader {
|
|
24
|
+
private readonly progress = new Map<string, Progress>();
|
|
25
|
+
private readonly found = new Map<string, string>();
|
|
26
|
+
private readonly projectsDir: string;
|
|
27
|
+
private readonly chunkBytes: number;
|
|
28
|
+
|
|
29
|
+
constructor(projectsDir: string = usageRoots().find((root) => root.provider === 'claude')?.path ?? '', chunkBytes = 4 * 1024 * 1024) {
|
|
30
|
+
this.projectsDir = projectsDir;
|
|
31
|
+
this.chunkBytes = chunkBytes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* The title of a session found by its id, for a chat, which knows no transcript path; in the projects of its account. */
|
|
35
|
+
async forSession(agentSessionId: string, projectsDir: string = this.projectsDir): Promise<string | null> {
|
|
36
|
+
const path = await this.locate(agentSessionId, projectsDir);
|
|
37
|
+
return path === null ? null : await this.forTranscript(path);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async forTranscript(path: string): Promise<string | null> {
|
|
41
|
+
let size: number;
|
|
42
|
+
try {
|
|
43
|
+
size = (await stat(path)).size;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
let progress = this.progress.get(path);
|
|
48
|
+
// Smaller than where we were is a file that was replaced, not one that grew.
|
|
49
|
+
if (!progress || size < progress.offset) {
|
|
50
|
+
progress = { offset: 0, aiTitle: null, customTitle: null };
|
|
51
|
+
this.progress.set(path, progress);
|
|
52
|
+
}
|
|
53
|
+
if (size > progress.offset) {
|
|
54
|
+
const current = progress;
|
|
55
|
+
current.offset = await readLines(path, current.offset, size, this.chunkBytes, (line) => this.take(line, current));
|
|
56
|
+
}
|
|
57
|
+
return progress.customTitle ?? progress.aiTitle;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private take(line: string, progress: Progress): void {
|
|
61
|
+
if (!MARKERS.some((marker) => line.includes(marker))) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let record: unknown;
|
|
65
|
+
try {
|
|
66
|
+
record = JSON.parse(line);
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof record !== 'object' || record === null) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const entry = record as Record<string, unknown>;
|
|
74
|
+
if (entry.type === 'ai-title') {
|
|
75
|
+
progress.aiTitle = cleanTitle(entry.aiTitle) ?? progress.aiTitle;
|
|
76
|
+
} else if (entry.type === 'custom-title') {
|
|
77
|
+
progress.customTitle = cleanTitle(entry.customTitle) ?? progress.customTitle;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* The folder a transcript sits in is named after the working directory, so the file name is the only key a session id gives. */
|
|
82
|
+
private async locate(agentSessionId: string, projectsDir: string): Promise<string | null> {
|
|
83
|
+
const known = this.found.get(agentSessionId);
|
|
84
|
+
if (known !== undefined && existsSync(known)) {
|
|
85
|
+
return known;
|
|
86
|
+
}
|
|
87
|
+
if (projectsDir === '' || agentSessionId.includes('/')) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
let dirs: string[];
|
|
91
|
+
try {
|
|
92
|
+
dirs = await readdir(projectsDir);
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const file = `${agentSessionId}.jsonl`;
|
|
97
|
+
for (const dir of dirs) {
|
|
98
|
+
const path = join(projectsDir, dir, file);
|
|
99
|
+
if (existsSync(path)) {
|
|
100
|
+
this.found.set(agentSessionId, path);
|
|
101
|
+
return path;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import type { ChatHistoryResult, ChatItem } from '@adecore/agent-contracts';
|
|
5
|
+
import { readLines } from '../title-file.ts';
|
|
6
|
+
import { ClaudeProtocol } from './claude-protocol.ts';
|
|
7
|
+
import { readingThread, settledReading } from './subagent-projection.ts';
|
|
8
|
+
import type { ChatThread } from './thread.ts';
|
|
9
|
+
import type { ThreadProjector } from './projector.ts';
|
|
10
|
+
|
|
11
|
+
const CHUNK_BYTES = 4 * 1024 * 1024;
|
|
12
|
+
|
|
13
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
14
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function str(value: unknown): string | null {
|
|
18
|
+
return typeof value === 'string' && value !== '' ? value : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* The words of a user line that somebody said, or null for a line that carries tool results. */
|
|
22
|
+
function spokenText(content: unknown): string | null {
|
|
23
|
+
if (typeof content === 'string') {
|
|
24
|
+
return content;
|
|
25
|
+
}
|
|
26
|
+
if (!Array.isArray(content) || content.some((block) => isRecord(block) && block.type === 'tool_result')) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const texts = content.filter((block) => isRecord(block) && block.type === 'text').map((block) => String((block as { text?: unknown }).text ?? ''));
|
|
30
|
+
return texts.length === 0 ? null : texts.join('\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* What Claude Code writes beside a subagent's transcript. Pinned on 2.1.273, where only the agent's own id is always there. */
|
|
34
|
+
export interface SubagentMeta {
|
|
35
|
+
agentId: string;
|
|
36
|
+
toolUseId: string | null;
|
|
37
|
+
// Set for an agent a subagent opened, which gets a file of its own in the same folder.
|
|
38
|
+
parentAgentId: string | null;
|
|
39
|
+
description: string | null;
|
|
40
|
+
transcript: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* The folder name Claude Code gives a working directory: every character that is not a letter or a digit becomes a dash. */
|
|
44
|
+
export function claudeProjectSlug(cwd: string): string {
|
|
45
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, '-');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* Where the subagents of a session keep their transcripts. The folder of the chat's own directory
|
|
50
|
+
* first; a slug the CLI wrote differently (or a session resumed from somewhere else) is found by the
|
|
51
|
+
* session id alone, which is unique across every project.
|
|
52
|
+
*/
|
|
53
|
+
export async function findSubagentsDir(projectsDir: string, cwd: string, sessionId: string): Promise<string | null> {
|
|
54
|
+
if (projectsDir === '' || sessionId.includes('/') || sessionId.includes('..')) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const own = join(projectsDir, claudeProjectSlug(cwd), sessionId, 'subagents');
|
|
58
|
+
if (existsSync(own)) {
|
|
59
|
+
return own;
|
|
60
|
+
}
|
|
61
|
+
let dirs: string[];
|
|
62
|
+
try {
|
|
63
|
+
dirs = await readdir(projectsDir);
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
for (const dir of dirs) {
|
|
68
|
+
const candidate = join(projectsDir, dir, sessionId, 'subagents');
|
|
69
|
+
if (existsSync(candidate)) {
|
|
70
|
+
return candidate;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Every meta file in a subagents folder that says which agent it is about; one that does not parse is left out. */
|
|
77
|
+
export async function readSubagentMetas(dir: string): Promise<SubagentMeta[]> {
|
|
78
|
+
let names: string[];
|
|
79
|
+
try {
|
|
80
|
+
names = await readdir(dir);
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const metas: SubagentMeta[] = [];
|
|
85
|
+
for (const name of names) {
|
|
86
|
+
const match = /^agent-(.+)\.meta\.json$/.exec(name);
|
|
87
|
+
if (!match) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
let parsed: unknown;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(await readFile(join(dir, name), 'utf8'));
|
|
93
|
+
} catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!isRecord(parsed)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const agentId = match[1]!;
|
|
100
|
+
metas.push({
|
|
101
|
+
agentId,
|
|
102
|
+
toolUseId: str(parsed.toolUseId),
|
|
103
|
+
parentAgentId: str(parsed.parentAgentId),
|
|
104
|
+
description: str(parsed.description),
|
|
105
|
+
transcript: join(dir, `agent-${agentId}.jsonl`)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return metas;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* The transcript of one agent a workflow started. Claude Code 2.1.282 keeps a folder per run under
|
|
113
|
+
* `workflows` in the subagents folder, and nothing but the agent id ties an agent to its run.
|
|
114
|
+
*/
|
|
115
|
+
export async function findWorkflowAgent(subagentsDir: string, agentId: string): Promise<SubagentMeta | null> {
|
|
116
|
+
if (!/^[\w-]+$/.test(agentId)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const workflowsDir = join(subagentsDir, 'workflows');
|
|
120
|
+
let runs: string[];
|
|
121
|
+
try {
|
|
122
|
+
runs = await readdir(workflowsDir);
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
for (const run of runs) {
|
|
127
|
+
const dir = join(workflowsDir, run);
|
|
128
|
+
const transcript = join(dir, `agent-${agentId}.jsonl`);
|
|
129
|
+
if (existsSync(transcript)) {
|
|
130
|
+
return { agentId, toolUseId: null, parentAgentId: null, description: null, transcript };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/*
|
|
137
|
+
* One subagent transcript as thread items, read on from where the last read stopped: a transcript only
|
|
138
|
+
* grows, and a panel that follows a working agent asks again every time a line lands. The lines have
|
|
139
|
+
* the envelope of the main transcript, so an assistant or a tool result goes through the same mapping
|
|
140
|
+
* the live stream does. A file that shrank or was rewritten is read again from the start under a new
|
|
141
|
+
* thread, whose cursors then no longer match, so a client holding one is told the history expired.
|
|
142
|
+
*/
|
|
143
|
+
export class TranscriptProjection {
|
|
144
|
+
readonly path: string;
|
|
145
|
+
private thread!: ChatThread;
|
|
146
|
+
private projector!: ThreadProjector;
|
|
147
|
+
private protocol!: ClaudeProtocol;
|
|
148
|
+
private offset = 0;
|
|
149
|
+
private size = 0;
|
|
150
|
+
private mtimeMs = 0;
|
|
151
|
+
private lineTime = 0;
|
|
152
|
+
|
|
153
|
+
constructor(path: string) {
|
|
154
|
+
this.path = path;
|
|
155
|
+
this.reset();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Reads what was added since the last call; false when the file is not there. */
|
|
159
|
+
async refresh(): Promise<boolean> {
|
|
160
|
+
let info: { size: number; mtimeMs: number };
|
|
161
|
+
try {
|
|
162
|
+
info = await stat(this.path);
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
if (info.size < this.size || info.mtimeMs < this.mtimeMs) {
|
|
167
|
+
this.reset();
|
|
168
|
+
}
|
|
169
|
+
this.size = info.size;
|
|
170
|
+
this.mtimeMs = info.mtimeMs;
|
|
171
|
+
if (info.size > this.offset) {
|
|
172
|
+
this.offset = await readLines(this.path, this.offset, info.size, CHUNK_BYTES, (line) => this.take(line));
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
items(): ChatItem[] {
|
|
178
|
+
return this.thread.list().map(settledReading);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* The page before `cursor`, or the newest page without one; the cursors are the thread's own. */
|
|
182
|
+
page(limit: number, cursor?: string): ChatHistoryResult {
|
|
183
|
+
const page = this.thread.history(limit, cursor);
|
|
184
|
+
return { items: page.items.map(settledReading), history: page.history };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private reset(): void {
|
|
188
|
+
const reading = readingThread('Claude', () => this.lineTime);
|
|
189
|
+
this.thread = reading.thread;
|
|
190
|
+
this.projector = reading.projector;
|
|
191
|
+
this.protocol = new ClaudeProtocol();
|
|
192
|
+
this.offset = 0;
|
|
193
|
+
this.size = 0;
|
|
194
|
+
this.mtimeMs = 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private take(line: string): void {
|
|
198
|
+
if (line.trim() === '') {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
let entry: unknown;
|
|
202
|
+
try {
|
|
203
|
+
entry = JSON.parse(line);
|
|
204
|
+
} catch {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!isRecord(entry) || (entry.type !== 'user' && entry.type !== 'assistant')) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const time = typeof entry.timestamp === 'string' ? Date.parse(entry.timestamp) : Number.NaN;
|
|
211
|
+
if (Number.isFinite(time)) {
|
|
212
|
+
this.lineTime = time;
|
|
213
|
+
}
|
|
214
|
+
const message = isRecord(entry.message) ? entry.message : {};
|
|
215
|
+
const said = entry.type === 'user' ? spokenText(message.content) : null;
|
|
216
|
+
if (said !== null) {
|
|
217
|
+
// The prompt it was given, or a message sent to it later; a reminder the CLI slipped in is not somebody talking.
|
|
218
|
+
if (entry.isMeta !== true && said.trim() !== '') {
|
|
219
|
+
this.thread.upsert({
|
|
220
|
+
id: `user-${str(entry.uuid) ?? this.thread.list().length}`,
|
|
221
|
+
kind: 'user',
|
|
222
|
+
createdAt: this.lineTime,
|
|
223
|
+
turnId: null,
|
|
224
|
+
text: said
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
for (const event of this.protocol.handle({ type: entry.type, message })) {
|
|
230
|
+
this.projector.project(0, event);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|