@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,111 @@
|
|
|
1
|
+
import { ChatError } from '../chat/errors.js';
|
|
2
|
+
// A fork lands on a canvas and a summary goes back along its line, which a host of chats alone does not have.
|
|
3
|
+
function unsupported(what) {
|
|
4
|
+
return () => {
|
|
5
|
+
throw new ChatError('chat-unsupported', `This host does not ${what}`);
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/* The chat requests over a core, including the ones only a host with more than chats can answer, which say so. */
|
|
9
|
+
export function chatHandlers(chats, providers) {
|
|
10
|
+
return {
|
|
11
|
+
'provider.list': async () => ({ providers: await providers.list() }),
|
|
12
|
+
'chat.create': (payload) => chats.create(payload),
|
|
13
|
+
'chat.configure': (payload) => chats.configure(payload),
|
|
14
|
+
// What a chat the host starts on its own is made with, while this client is connected.
|
|
15
|
+
'chat.setPreferences': (payload, clientId) => {
|
|
16
|
+
chats.composerPreferences.set(clientId, payload);
|
|
17
|
+
return {};
|
|
18
|
+
},
|
|
19
|
+
'chat.attach': (payload, clientId) => chats.attachWithBookmarks(payload.chatId, clientId, payload.historyLimit, payload.since),
|
|
20
|
+
'chat.history': (payload) => chats.history(payload.chatId, payload.cursor, payload.limit),
|
|
21
|
+
'chat.subagent': (payload, clientId) => chats.subagent(clientId, payload),
|
|
22
|
+
'chat.stopSubagent': async (payload) => {
|
|
23
|
+
await chats.stopSubagent(payload.chatId, payload.toolUseId);
|
|
24
|
+
return {};
|
|
25
|
+
},
|
|
26
|
+
'chat.stopTask': async (payload) => {
|
|
27
|
+
await chats.stopTask(payload.chatId, payload.taskId);
|
|
28
|
+
return {};
|
|
29
|
+
},
|
|
30
|
+
'chat.detach': (payload, clientId) => {
|
|
31
|
+
chats.detach(payload.chatId, clientId);
|
|
32
|
+
return {};
|
|
33
|
+
},
|
|
34
|
+
'chat.send': (payload) => chats.send(payload.chatId, payload.text, { mentions: payload.mentions, skills: payload.skills, chats: payload.chats }, payload.attachments),
|
|
35
|
+
'chat.unqueue': (payload) => ({ message: chats.unqueue(payload.chatId, payload.messageId) }),
|
|
36
|
+
'chat.sendNow': (payload) => {
|
|
37
|
+
chats.sendNow(payload.chatId, payload.messageId);
|
|
38
|
+
return {};
|
|
39
|
+
},
|
|
40
|
+
'skills.list': async (payload) => ({ skills: await chats.skills(payload.chatId) }),
|
|
41
|
+
'chat.compact': (payload) => {
|
|
42
|
+
chats.compact(payload.chatId);
|
|
43
|
+
return {};
|
|
44
|
+
},
|
|
45
|
+
'chat.clear': async (payload) => {
|
|
46
|
+
await chats.clear(payload.chatId, payload.force === true);
|
|
47
|
+
return {};
|
|
48
|
+
},
|
|
49
|
+
'chat.turnDiff': async (payload) => ({ diff: await chats.turnDiff(payload.chatId, payload.turnId) }),
|
|
50
|
+
'chat.fork': unsupported('fork chats'),
|
|
51
|
+
'chat.forkInfo': unsupported('fork chats'),
|
|
52
|
+
'chat.summarize': unsupported('fork chats'),
|
|
53
|
+
'chat.continueOn': unsupported('continue a chat under another account'),
|
|
54
|
+
'chat.cancel': (payload) => {
|
|
55
|
+
chats.cancel(payload.chatId, payload.subagents === true);
|
|
56
|
+
return {};
|
|
57
|
+
},
|
|
58
|
+
'chat.approve': (payload) => {
|
|
59
|
+
chats.approve(payload.chatId, payload.requestId, payload.decision, payload.message);
|
|
60
|
+
return {};
|
|
61
|
+
},
|
|
62
|
+
'chat.answer': (payload) => {
|
|
63
|
+
chats.answer(payload.chatId, payload.requestId, payload.answers);
|
|
64
|
+
return {};
|
|
65
|
+
},
|
|
66
|
+
'chat.dismiss': (payload) => {
|
|
67
|
+
chats.dismiss(payload.chatId, payload.itemId);
|
|
68
|
+
return {};
|
|
69
|
+
},
|
|
70
|
+
'chat.addBookmark': async (payload) => ({ bookmarks: await chats.addBookmark(payload.chatId, payload.itemId, payload.name) }),
|
|
71
|
+
'chat.renameBookmark': async (payload) => ({ bookmarks: await chats.renameBookmark(payload.chatId, payload.itemId, payload.name) }),
|
|
72
|
+
'chat.removeBookmark': async (payload) => ({ bookmarks: await chats.removeBookmark(payload.chatId, payload.itemId) }),
|
|
73
|
+
'chat.kill': async (payload) => {
|
|
74
|
+
await chats.kill(payload.chatId);
|
|
75
|
+
return {};
|
|
76
|
+
},
|
|
77
|
+
'chat.list': () => ({ chats: chats.list() })
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/* The accounts are the person's settings of this host. */
|
|
81
|
+
export function accountHandlers(accounts) {
|
|
82
|
+
return {
|
|
83
|
+
'accounts.list': () => accounts.snapshot(),
|
|
84
|
+
'accounts.save': (payload) => accounts.save(payload.accounts),
|
|
85
|
+
'accounts.refresh': () => accounts.refresh(),
|
|
86
|
+
'accounts.create': (payload) => accounts.create(payload),
|
|
87
|
+
'accounts.watchLogin': (payload) => {
|
|
88
|
+
void accounts.watchLogin(payload.id);
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function usageHandlers(usage, limits) {
|
|
94
|
+
return {
|
|
95
|
+
'usage.summary': (payload) => usage.summary(payload),
|
|
96
|
+
// While a client has the page open the host keeps scanning; every other client gets the event too.
|
|
97
|
+
'usage.subscribe': (_payload, clientId) => {
|
|
98
|
+
usage.follow(clientId);
|
|
99
|
+
return {};
|
|
100
|
+
},
|
|
101
|
+
'usage.unsubscribe': (_payload, clientId) => {
|
|
102
|
+
usage.unfollow(clientId);
|
|
103
|
+
return {};
|
|
104
|
+
},
|
|
105
|
+
'usage.limits': () => limits.snapshot(),
|
|
106
|
+
'usage.refreshLimits': async () => {
|
|
107
|
+
await limits.refresh(true);
|
|
108
|
+
return limits.snapshot();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Two ends of a port in one process, for a test or a host and a client that live side by side. A
|
|
3
|
+
* frame crosses as a copy and only once the sender's own code ran, the way a MessagePort delivers it,
|
|
4
|
+
* so neither end ever holds the other's object.
|
|
5
|
+
*/
|
|
6
|
+
export function memoryPortPair() {
|
|
7
|
+
const listeners = [new Set(), new Set()];
|
|
8
|
+
const end = (own) => ({
|
|
9
|
+
send: (frame) => {
|
|
10
|
+
const copy = structuredClone(frame);
|
|
11
|
+
queueMicrotask(() => {
|
|
12
|
+
for (const listener of listeners[1 - own]) {
|
|
13
|
+
listener(copy);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
onFrame: (listener) => {
|
|
18
|
+
listeners[own].add(listener);
|
|
19
|
+
return () => {
|
|
20
|
+
listeners[own].delete(listener);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return [end(0), end(1)];
|
|
25
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ChatCore, type ChatCoreOptions } from '../chat/chat-core.ts';
|
|
2
|
+
import type { SpawnChatProcess } from '../chat/chat-process.ts';
|
|
3
|
+
import type { ClaudeBackendOptions } from '../chat/claude-backend.ts';
|
|
4
|
+
import { type CodexClientInfo } from '../chat/codex-transport.ts';
|
|
5
|
+
import type { AgentEvent } from '../events.ts';
|
|
6
|
+
import { ProviderAccountsService } from '../providers/accounts/service.ts';
|
|
7
|
+
import type { AccountsHost } from '../providers/accounts/variables.ts';
|
|
8
|
+
import { type CodexRules } from '../providers/codex-rules.ts';
|
|
9
|
+
import { ProviderRegistry, type ProviderRegistryOptions } from '../providers/registry.ts';
|
|
10
|
+
import { UsageMonitor } from '../usage/limits/monitor.ts';
|
|
11
|
+
import { UsageService } from '../usage/usage-service.ts';
|
|
12
|
+
import { type CliEnvironmentPolicy } from './environment.ts';
|
|
13
|
+
import { type AgentHandlers } from './handlers.ts';
|
|
14
|
+
export interface AgentWiringOptions<Core extends ChatCore = ChatCore> {
|
|
15
|
+
dataDir: string;
|
|
16
|
+
env?: Record<string, string | undefined>;
|
|
17
|
+
environmentPolicy?: CliEnvironmentPolicy;
|
|
18
|
+
systemNote?: string;
|
|
19
|
+
client?: CodexClientInfo;
|
|
20
|
+
accountsHost?: AccountsHost;
|
|
21
|
+
claude?: ClaudeBackendOptions;
|
|
22
|
+
codexRules?: CodexRules;
|
|
23
|
+
core?: (options: ChatCoreOptions) => Core;
|
|
24
|
+
command?: string[];
|
|
25
|
+
codexCommand?: string[];
|
|
26
|
+
spawn?: SpawnChatProcess;
|
|
27
|
+
detect?: ProviderRegistryOptions['detect'];
|
|
28
|
+
}
|
|
29
|
+
export interface AgentWiring<Core extends ChatCore = ChatCore> {
|
|
30
|
+
providers: ProviderRegistry;
|
|
31
|
+
accounts: ProviderAccountsService;
|
|
32
|
+
limits: UsageMonitor;
|
|
33
|
+
usage: UsageService;
|
|
34
|
+
chats: Core;
|
|
35
|
+
handlers: AgentHandlers;
|
|
36
|
+
connect(clientId: string, send: (event: AgentEvent) => void): () => void;
|
|
37
|
+
start(): void;
|
|
38
|
+
stop(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export declare function wireAgents<Core extends ChatCore = ChatCore>(options: AgentWiringOptions<Core>): AgentWiring<Core>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { AttachmentStore } from '../chat/attachment-store.js';
|
|
2
|
+
import { BookmarkStore } from '../chat/bookmark-store.js';
|
|
3
|
+
import { ChatCore } from '../chat/chat-core.js';
|
|
4
|
+
import { ChatStore } from '../chat/chat-store.js';
|
|
5
|
+
import { suggestChatTitle } from '../chat/chat-title.js';
|
|
6
|
+
import { ClaudeTitleReader } from '../chat/claude-title.js';
|
|
7
|
+
import { DEFAULT_CODEX_CLIENT } from '../chat/codex-transport.js';
|
|
8
|
+
import { definedEnv } from '../providers/accounts/launch.js';
|
|
9
|
+
import { ProviderAccountsService } from '../providers/accounts/service.js';
|
|
10
|
+
import { createClaudeProvider } from '../providers/claude-provider.js';
|
|
11
|
+
import { errorText } from '../error-text.js';
|
|
12
|
+
import { createCodexProvider } from '../providers/codex-provider.js';
|
|
13
|
+
import { codexRulesPathIn, defaultCodexHome, installCodexRules } from '../providers/codex-rules.js';
|
|
14
|
+
import { ProviderRegistry } from '../providers/registry.js';
|
|
15
|
+
import { chatSessionAccounts, limitAccountsOf, usageAccountsOf, usageRootsOf } from '../usage/accounts.js';
|
|
16
|
+
import { UsageMonitor } from '../usage/limits/monitor.js';
|
|
17
|
+
import { UsageService } from '../usage/usage-service.js';
|
|
18
|
+
import { cliEnvironment } from './environment.js';
|
|
19
|
+
import { accountHandlers, chatHandlers, usageHandlers } from './handlers.js';
|
|
20
|
+
// A folder the rule cannot be written to only costs an approval per call, so it never stops the host.
|
|
21
|
+
async function installRulesIn(kind, folder, rules) {
|
|
22
|
+
if (kind !== 'codex') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const path = codexRulesPathIn(folder, rules);
|
|
26
|
+
await installCodexRules(path, rules).catch((e) => console.error(`Could not write the codex rules in ${path}:`, errorText(e)));
|
|
27
|
+
}
|
|
28
|
+
/*
|
|
29
|
+
* The chats of a host with the providers, accounts and usage around them, and the handlers of
|
|
30
|
+
* every agent request. `AgentHost` serves them over a port; an app that answers requests on a wire
|
|
31
|
+
* of its own takes the handlers and `connect` from here. The accounts are read with
|
|
32
|
+
* `accounts.load()` before the first request.
|
|
33
|
+
*/
|
|
34
|
+
export function wireAgents(options) {
|
|
35
|
+
const env = options.env === undefined || options.environmentPolicy !== undefined ? cliEnvironment(options.env, options.environmentPolicy) : options.env;
|
|
36
|
+
const client = options.client ?? DEFAULT_CODEX_CLIENT;
|
|
37
|
+
const codexRules = options.codexRules;
|
|
38
|
+
const providers = new ProviderRegistry({ providers: [createClaudeProvider(options.claude), createCodexProvider({ client })], env, detect: options.detect });
|
|
39
|
+
const accounts = new ProviderAccountsService({
|
|
40
|
+
home: options.dataDir,
|
|
41
|
+
providers,
|
|
42
|
+
env,
|
|
43
|
+
client,
|
|
44
|
+
...(options.accountsHost ? { host: options.accountsHost } : {}),
|
|
45
|
+
...(codexRules ? { install: (kind, folder) => installRulesIn(kind, folder, codexRules) } : {})
|
|
46
|
+
});
|
|
47
|
+
if (codexRules) {
|
|
48
|
+
void installRulesIn('codex', defaultCodexHome(env), codexRules);
|
|
49
|
+
}
|
|
50
|
+
const nameOf = (kind) => providers.get(kind).name;
|
|
51
|
+
const limits = new UsageMonitor({ providers, accounts: limitAccountsOf(accounts, nameOf, env), client });
|
|
52
|
+
accounts.listen(() => limits.accountsChanged());
|
|
53
|
+
const attachments = new AttachmentStore(options.dataDir);
|
|
54
|
+
// Only without `core`, where `Core` is `ChatCore` itself.
|
|
55
|
+
const core = options.core ?? ((coreOptions) => new ChatCore(coreOptions));
|
|
56
|
+
const chats = core({
|
|
57
|
+
providers,
|
|
58
|
+
store: new ChatStore(options.dataDir, { attachments }),
|
|
59
|
+
attachments,
|
|
60
|
+
bookmarks: new BookmarkStore(options.dataDir),
|
|
61
|
+
env,
|
|
62
|
+
...(options.systemNote === undefined ? {} : { instructions: options.systemNote }),
|
|
63
|
+
...(options.command ? { command: options.command } : {}),
|
|
64
|
+
...(options.codexCommand ? { codexCommand: options.codexCommand } : {}),
|
|
65
|
+
...(options.spawn ? { spawn: options.spawn } : {}),
|
|
66
|
+
codexClient: client,
|
|
67
|
+
accounts,
|
|
68
|
+
onLimits: (update) => limits.applyLive(update),
|
|
69
|
+
claudeTitles: new ClaudeTitleReader(),
|
|
70
|
+
nameChat: (kind, input) => suggestChatTitle(providers, kind, input, definedEnv(env))
|
|
71
|
+
});
|
|
72
|
+
const usage = new UsageService({
|
|
73
|
+
home: options.dataDir,
|
|
74
|
+
knownProjects: () => Promise.resolve([]),
|
|
75
|
+
roots: () => usageRootsOf(accounts),
|
|
76
|
+
sessionAccounts: () => chatSessionAccounts(chats.list()),
|
|
77
|
+
accounts: () => usageAccountsOf(accounts, nameOf)
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
providers,
|
|
81
|
+
accounts,
|
|
82
|
+
limits,
|
|
83
|
+
usage,
|
|
84
|
+
chats,
|
|
85
|
+
handlers: {
|
|
86
|
+
...chatHandlers(chats, providers),
|
|
87
|
+
...accountHandlers(accounts),
|
|
88
|
+
...usageHandlers(usage, limits)
|
|
89
|
+
},
|
|
90
|
+
connect: (clientId, send) => {
|
|
91
|
+
const releases = [
|
|
92
|
+
chats.subscribe(clientId, send),
|
|
93
|
+
accounts.subscribe(clientId, send),
|
|
94
|
+
usage.subscribe(clientId, send),
|
|
95
|
+
limits.subscribe(clientId, send)
|
|
96
|
+
];
|
|
97
|
+
return () => {
|
|
98
|
+
for (const release of releases) {
|
|
99
|
+
release();
|
|
100
|
+
}
|
|
101
|
+
chats.detachAll(clientId);
|
|
102
|
+
usage.unfollow(clientId);
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
start: () => {
|
|
106
|
+
limits.start();
|
|
107
|
+
accounts.start();
|
|
108
|
+
},
|
|
109
|
+
stop: async () => {
|
|
110
|
+
try {
|
|
111
|
+
usage.stop();
|
|
112
|
+
limits.stop();
|
|
113
|
+
accounts.stop();
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await chats.shutdown();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type RuntimeMode } from '@adecore/agent-contracts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const LineageSchema: z.ZodObject<{
|
|
4
|
+
projectId: z.ZodString;
|
|
5
|
+
nodeId: z.ZodString;
|
|
6
|
+
openedBy: z.ZodString;
|
|
7
|
+
depth: z.ZodNumber;
|
|
8
|
+
agent: z.ZodDefault<z.ZodBoolean>;
|
|
9
|
+
relation: z.ZodOptional<z.ZodLiteral<"fork">>;
|
|
10
|
+
ceiling: z.ZodOptional<z.ZodEnum<{
|
|
11
|
+
auto: "auto";
|
|
12
|
+
"auto-accept-edits": "auto-accept-edits";
|
|
13
|
+
"full-access": "full-access";
|
|
14
|
+
supervised: "supervised";
|
|
15
|
+
}>>;
|
|
16
|
+
createdAt: z.ZodNumber;
|
|
17
|
+
endedAt: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
type Lineage = z.infer<typeof LineageSchema>;
|
|
20
|
+
export declare class AgentLineageStore {
|
|
21
|
+
readonly dir: string;
|
|
22
|
+
private readonly opened;
|
|
23
|
+
constructor(dataDir: string);
|
|
24
|
+
load(): Promise<void>;
|
|
25
|
+
put(record: Omit<Lineage, 'createdAt' | 'endedAt'>): Promise<void>;
|
|
26
|
+
descendants(nodeId: string): string[];
|
|
27
|
+
projectOf(nodeId: string): string | null;
|
|
28
|
+
endedAt(nodeId: string): number | null;
|
|
29
|
+
markEnded(nodeIds: readonly string[]): Promise<void>;
|
|
30
|
+
orphans(projectId: string, ids: ReadonlySet<string>): Array<{
|
|
31
|
+
nodeId: string;
|
|
32
|
+
openedBy: string;
|
|
33
|
+
}>;
|
|
34
|
+
ceilingOf(nodeId: string): RuntimeMode | null;
|
|
35
|
+
depthOf(nodeId: string): number;
|
|
36
|
+
openedCount(callerId: string, include?: (nodeId: string) => boolean): number;
|
|
37
|
+
madeBy(nodeId: string): string | null;
|
|
38
|
+
startedBy(nodeId: string): string | null;
|
|
39
|
+
forkedFrom(nodeId: string): string | null;
|
|
40
|
+
forksOf(nodeId: string): string[];
|
|
41
|
+
forksLeaving(projectId: string, ids: ReadonlySet<string>): string[];
|
|
42
|
+
prune(projectId: string, ids: ReadonlySet<string>): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
package/dist/lineage.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { RuntimeModeSchema } from '@adecore/agent-contracts';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { RecordDirectory } from './record-directory.js';
|
|
5
|
+
const LineageSchema = z.object({
|
|
6
|
+
projectId: z.string().min(1),
|
|
7
|
+
nodeId: z.string().min(1),
|
|
8
|
+
/* The node that ran the verb; a caller is counted against this to cap one runaway agent. */
|
|
9
|
+
openedBy: z.string().min(1),
|
|
10
|
+
depth: z.number().int().nonnegative(),
|
|
11
|
+
/* Whether this is an agent node, which is what the depth and the per-caller cap are about. A
|
|
12
|
+
record written before a plain node was written down at all is one of those, hence the default. */
|
|
13
|
+
agent: z.boolean().default(true),
|
|
14
|
+
/* A fork is a sibling a person made beside `openedBy` rather than a node it opened; absent is opened by a verb. */
|
|
15
|
+
relation: z.literal('fork').optional(),
|
|
16
|
+
/* The widest mode the agent may start in, the mode of its opener when it was made. Absent on a
|
|
17
|
+
record from before, and on a node nobody may widen anyway since no agent started it. */
|
|
18
|
+
ceiling: RuntimeModeSchema.optional(),
|
|
19
|
+
createdAt: z.number(),
|
|
20
|
+
/* When stopping or deleting the node that opened it ended this one too; a cascade never ends a node twice. */
|
|
21
|
+
endedAt: z.number().optional()
|
|
22
|
+
});
|
|
23
|
+
/* Opened by the agent it names, which is what every rule about children is about; a fork lives on its own. */
|
|
24
|
+
function openedByAgent(entry) {
|
|
25
|
+
return entry.agent && entry.relation !== 'fork';
|
|
26
|
+
}
|
|
27
|
+
/*
|
|
28
|
+
* Lineage lives in `lineage` under the host's data folder, outside the agent-writable project, so agents
|
|
29
|
+
* cannot reset their own depth. Persisting it also prevents a restart of the host from resetting recursion limits.
|
|
30
|
+
*/
|
|
31
|
+
export class AgentLineageStore {
|
|
32
|
+
dir;
|
|
33
|
+
opened;
|
|
34
|
+
constructor(dataDir) {
|
|
35
|
+
this.dir = join(dataDir, 'lineage');
|
|
36
|
+
this.opened = new RecordDirectory({ dir: this.dir, schema: LineageSchema, idOf: (entry) => entry.nodeId });
|
|
37
|
+
}
|
|
38
|
+
/* Reads what an earlier run of the host wrote down. Call before any verb can ask. */
|
|
39
|
+
load() {
|
|
40
|
+
return this.opened.load();
|
|
41
|
+
}
|
|
42
|
+
async put(record) {
|
|
43
|
+
await this.opened.write({ ...record, createdAt: Date.now() });
|
|
44
|
+
}
|
|
45
|
+
/*
|
|
46
|
+
* The agent nodes this one opened and the ones those opened in turn that no cascade ended yet,
|
|
47
|
+
* nearest first, so whoever ends them can go from the leaves back up.
|
|
48
|
+
*/
|
|
49
|
+
descendants(nodeId) {
|
|
50
|
+
const found = [];
|
|
51
|
+
const seen = new Set([nodeId]);
|
|
52
|
+
for (let i = -1; i < found.length; i++) {
|
|
53
|
+
const parent = i === -1 ? nodeId : found[i];
|
|
54
|
+
for (const entry of this.opened.all()) {
|
|
55
|
+
if (entry.openedBy === parent && openedByAgent(entry) && entry.endedAt === undefined && !seen.has(entry.nodeId)) {
|
|
56
|
+
seen.add(entry.nodeId);
|
|
57
|
+
found.push(entry.nodeId);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return found;
|
|
62
|
+
}
|
|
63
|
+
/* The project a node was made in, for work owed about it after the node that opened it is gone. */
|
|
64
|
+
projectOf(nodeId) {
|
|
65
|
+
return this.opened.get(nodeId)?.projectId ?? null;
|
|
66
|
+
}
|
|
67
|
+
endedAt(nodeId) {
|
|
68
|
+
return this.opened.get(nodeId)?.endedAt ?? null;
|
|
69
|
+
}
|
|
70
|
+
/* On the wall clock, like `createdAt`: a chat holds the mark against the time its turn started. */
|
|
71
|
+
async markEnded(nodeIds) {
|
|
72
|
+
const at = Date.now();
|
|
73
|
+
for (const nodeId of nodeIds) {
|
|
74
|
+
const entry = this.opened.get(nodeId);
|
|
75
|
+
if (entry && entry.endedAt === undefined) {
|
|
76
|
+
await this.opened.write({ ...entry, endedAt: at });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/* The nodes of this project whose opener it no longer places, and that no cascade ended yet. */
|
|
81
|
+
orphans(projectId, ids) {
|
|
82
|
+
return [...this.opened.all()]
|
|
83
|
+
.filter((entry) => entry.projectId === projectId && openedByAgent(entry) && entry.endedAt === undefined && ids.has(entry.nodeId) && !ids.has(entry.openedBy))
|
|
84
|
+
.map((entry) => ({ nodeId: entry.nodeId, openedBy: entry.openedBy }));
|
|
85
|
+
}
|
|
86
|
+
/* The widest mode this agent node may start in, every time it starts; null for one no agent opened. */
|
|
87
|
+
ceilingOf(nodeId) {
|
|
88
|
+
const entry = this.opened.get(nodeId);
|
|
89
|
+
return entry !== undefined && openedByAgent(entry) ? (entry.ceiling ?? null) : null;
|
|
90
|
+
}
|
|
91
|
+
/* How deep a node sits. A node nobody wrote down is one a person made, which is where a chain starts. */
|
|
92
|
+
depthOf(nodeId) {
|
|
93
|
+
return this.opened.get(nodeId)?.depth ?? 0;
|
|
94
|
+
}
|
|
95
|
+
// A host may exclude completed hidden agents while keeping their lineage for later reads and tasks.
|
|
96
|
+
openedCount(callerId, include = () => true) {
|
|
97
|
+
let count = 0;
|
|
98
|
+
for (const entry of this.opened.all()) {
|
|
99
|
+
if (entry.openedBy === callerId && openedByAgent(entry) && include(entry.nodeId)) {
|
|
100
|
+
count += 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return count;
|
|
104
|
+
}
|
|
105
|
+
/* Who made this node, or null for one a person made: the whole of the rule node delete follows. */
|
|
106
|
+
madeBy(nodeId) {
|
|
107
|
+
const entry = this.opened.get(nodeId);
|
|
108
|
+
return entry === undefined || entry.relation === 'fork' ? null : entry.openedBy;
|
|
109
|
+
}
|
|
110
|
+
/* The node that opened this one as an agent, or null for a node no agent or team started. */
|
|
111
|
+
startedBy(nodeId) {
|
|
112
|
+
const entry = this.opened.get(nodeId);
|
|
113
|
+
return entry !== undefined && openedByAgent(entry) ? entry.openedBy : null;
|
|
114
|
+
}
|
|
115
|
+
/* The chat a fork was made from, or null for a node that is no fork. */
|
|
116
|
+
forkedFrom(nodeId) {
|
|
117
|
+
const entry = this.opened.get(nodeId);
|
|
118
|
+
return entry?.relation === 'fork' ? entry.openedBy : null;
|
|
119
|
+
}
|
|
120
|
+
/* The forks made from a chat. */
|
|
121
|
+
forksOf(nodeId) {
|
|
122
|
+
return [...this.opened.all()].filter((entry) => entry.relation === 'fork' && entry.openedBy === nodeId).map((entry) => entry.nodeId);
|
|
123
|
+
}
|
|
124
|
+
/* The forks of this project whose node is gone, read before `prune` forgets them. */
|
|
125
|
+
forksLeaving(projectId, ids) {
|
|
126
|
+
return [...this.opened.all()]
|
|
127
|
+
.filter((entry) => entry.projectId === projectId && entry.relation === 'fork' && !ids.has(entry.nodeId))
|
|
128
|
+
.map((entry) => entry.nodeId);
|
|
129
|
+
}
|
|
130
|
+
/* Drops what this project wrote down for ids it no longer has: the node was deleted. */
|
|
131
|
+
prune(projectId, ids) {
|
|
132
|
+
return this.opened.prune((entry) => entry.projectId === projectId && !ids.has(entry.nodeId));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { WakeChat } from '../chat/wake-chat.ts';
|
|
3
|
+
import type { OutboxEntryOf } from '../outbox/outbox.ts';
|
|
4
|
+
import type { OutboxOutcome } from '../outbox/outbox-worker.ts';
|
|
5
|
+
import type { MessageWords } from './deliver-notice.ts';
|
|
6
|
+
import type { NoticeStore } from './notice-store.ts';
|
|
7
|
+
export declare const DeliverMessageWorkSchema: z.ZodObject<{
|
|
8
|
+
kind: z.ZodLiteral<"deliver-message">;
|
|
9
|
+
payload: z.ZodObject<{
|
|
10
|
+
from: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type DeliverMessageWork = z.infer<typeof DeliverMessageWorkSchema>;
|
|
14
|
+
export type DeliverMessageEntry = OutboxEntryOf<DeliverMessageWork>;
|
|
15
|
+
export interface DeliverMessageDeps {
|
|
16
|
+
notices: Pick<NoticeStore, 'waiting'>;
|
|
17
|
+
chat(chatId: string): Promise<WakeChat | null>;
|
|
18
|
+
placed(nodeId: string): boolean;
|
|
19
|
+
words: Pick<MessageWords, 'prompt' | 'label'>;
|
|
20
|
+
}
|
|
21
|
+
export declare function deliverMessageHandler(deps: DeliverMessageDeps): (entry: DeliverMessageEntry) => Promise<OutboxOutcome>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const DeliverMessageWorkSchema = z.object({
|
|
3
|
+
kind: z.literal('deliver-message'),
|
|
4
|
+
// The target is the chat the message was left for; the message itself waits in the notice store, with any that came in beside it.
|
|
5
|
+
payload: z.object({ from: z.string().min(1) })
|
|
6
|
+
});
|
|
7
|
+
/*
|
|
8
|
+
* Opens the turn a message earns a chat. The messages themselves are not carried here: they wait in
|
|
9
|
+
* the notice store and the turn's preamble takes them (`NoticeNotes`), the same channel a person's
|
|
10
|
+
* own prompt reads them through, so one message is heard once whichever of the two opens the turn.
|
|
11
|
+
*
|
|
12
|
+
* One attempt, never `wait`. A chat that turned out to be in a turn reads the message in front of its
|
|
13
|
+
* next one, which is what a message to a busy chat has always done; holding the entry instead would
|
|
14
|
+
* make the host start turns nobody asked for long after the news was worth anything.
|
|
15
|
+
*/
|
|
16
|
+
export function deliverMessageHandler(deps) {
|
|
17
|
+
return async (entry) => {
|
|
18
|
+
if (!deps.placed(entry.target)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const waiting = deps.notices.waiting(entry.target);
|
|
22
|
+
// A turn opened for an earlier message took these along, or they went stale: nothing to wake anyone for.
|
|
23
|
+
if (waiting.length === 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const chat = await deps.chat(entry.target);
|
|
27
|
+
if (chat === null) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
/*
|
|
31
|
+
* No note of its own, and the preamble stays off the thread. A person read the message as it
|
|
32
|
+
* landed (`showNotices`), so anything the turn adds about it only says the same thing again;
|
|
33
|
+
* the label on the turn already names who it came from.
|
|
34
|
+
*/
|
|
35
|
+
chat.wake({
|
|
36
|
+
text: deps.words.prompt(waiting.length),
|
|
37
|
+
label: deps.words.label(waiting),
|
|
38
|
+
taskIds: [],
|
|
39
|
+
messageFrom: [...new Set(waiting.map((notice) => notice.from))]
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ChatItem } from '@adecore/agent-contracts';
|
|
2
|
+
import type { ChatCore } from '../chat/chat-core.ts';
|
|
3
|
+
import type { Notice, NoticeStore } from './notice-store.ts';
|
|
4
|
+
export interface MessageWords {
|
|
5
|
+
heard(notice: Notice): string;
|
|
6
|
+
shown(notice: Notice): string;
|
|
7
|
+
prompt(count: number): string;
|
|
8
|
+
label(notices: readonly Notice[]): string;
|
|
9
|
+
}
|
|
10
|
+
export interface ChatNoticeTargets {
|
|
11
|
+
chat(id: string): Promise<'idle' | 'running' | 'none'>;
|
|
12
|
+
fromMessage(id: string): boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ChatDelivery {
|
|
15
|
+
outcome: 'wake' | 'no-chat' | 'in-turn' | 'from-message';
|
|
16
|
+
waiting: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function turnFromMessage(items: readonly ChatItem[], activeTurnId: string | null): boolean;
|
|
19
|
+
export declare function chatNoticeTargets(chats: Pick<ChatCore, 'get' | 'hasStored'>): ChatNoticeTargets;
|
|
20
|
+
export declare function deliverToChat(store: Pick<NoticeStore, 'put'>, targets: ChatNoticeTargets, notice: Omit<Notice, 'createdAt'>): Promise<ChatDelivery>;
|
|
21
|
+
export interface NoticeChat {
|
|
22
|
+
has(id: string): Promise<boolean>;
|
|
23
|
+
note(id: string, text: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export declare function showNotices(store: Pick<NoticeStore, 'show'>, chat: NoticeChat, words: Pick<MessageWords, 'shown'>, targetId: string): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Whether the turn a chat is running was itself opened by a message. This is the whole of the stop:
|
|
3
|
+
* two agents that read each other would otherwise wake each other for as long as they kept writing,
|
|
4
|
+
* and nothing about that started with a person. It rides on the turn, so it survives a restart.
|
|
5
|
+
* Between turns the last one counts, since work it left running (a background subagent) may still
|
|
6
|
+
* write, and a turn the CLI opened by itself goes on with the step of the turn before it.
|
|
7
|
+
*/
|
|
8
|
+
export function turnFromMessage(items, activeTurnId) {
|
|
9
|
+
const from = activeTurnId === null ? items.findLastIndex((item) => item.kind === 'turn') : items.findIndex((item) => item.id === activeTurnId);
|
|
10
|
+
for (let i = from; i >= 0; i--) {
|
|
11
|
+
const turn = items[i];
|
|
12
|
+
if (turn.kind !== 'turn') {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if ((turn.messageFrom ?? []).length > 0) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (turn.origin !== 'agent' || turn.taskIds !== undefined || turn.summaryFor !== undefined) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
/* The chats of a core as a message sees them. */
|
|
25
|
+
export function chatNoticeTargets(chats) {
|
|
26
|
+
return {
|
|
27
|
+
chat: async (id) => {
|
|
28
|
+
const session = chats.get(id);
|
|
29
|
+
if (session) {
|
|
30
|
+
return session.info.activeTurnId === null ? 'idle' : 'running';
|
|
31
|
+
}
|
|
32
|
+
// A chat nobody has loaded is idle: the turn opens on the thread the host reads back from disk.
|
|
33
|
+
return (await chats.hasStored(id)) ? 'idle' : 'none';
|
|
34
|
+
},
|
|
35
|
+
fromMessage: (id) => {
|
|
36
|
+
const session = chats.get(id);
|
|
37
|
+
return session !== undefined && turnFromMessage(session.thread.list(), session.info.activeTurnId);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/*
|
|
42
|
+
* One message on its way to a chat, and whether the chat owes a turn on it. A chat between turns gets
|
|
43
|
+
* one, the way a settled task gives one: a person watching two agents cannot tell a message from an
|
|
44
|
+
* assignment, and a message nobody starts a turn for sits there until someone happens to prompt that
|
|
45
|
+
* chat. One step deep, so the turn a message opened wakes nobody with a message of its own. A node the
|
|
46
|
+
* host runs something else in, such as a terminal, is the host's to deliver to before this.
|
|
47
|
+
*/
|
|
48
|
+
export async function deliverToChat(store, targets, notice) {
|
|
49
|
+
const waiting = await store.put(notice);
|
|
50
|
+
const chat = await targets.chat(notice.targetId);
|
|
51
|
+
if (chat === 'none') {
|
|
52
|
+
return { outcome: 'no-chat', waiting };
|
|
53
|
+
}
|
|
54
|
+
if (chat === 'running') {
|
|
55
|
+
return { outcome: 'in-turn', waiting };
|
|
56
|
+
}
|
|
57
|
+
if (targets.fromMessage(notice.from)) {
|
|
58
|
+
return { outcome: 'from-message', waiting };
|
|
59
|
+
}
|
|
60
|
+
return { outcome: 'wake', waiting };
|
|
61
|
+
}
|
|
62
|
+
/*
|
|
63
|
+
* What a chat has to show a person, in its thread, the moment a message lands and not when the model
|
|
64
|
+
* gets round to it: without this a message left for a busy node is a file on disk and nothing else.
|
|
65
|
+
* An id no chat holds shows nothing and marks nothing, so the chat that opens on that id later still
|
|
66
|
+
* has all of it.
|
|
67
|
+
*/
|
|
68
|
+
export async function showNotices(store, chat, words, targetId) {
|
|
69
|
+
if (!(await chat.has(targetId))) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const notice of await store.show(targetId)) {
|
|
73
|
+
await chat.note(targetId, words.shown(notice));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PromptNotes } from '../chat/chat-session.ts';
|
|
2
|
+
import type { MessageWords } from './deliver-notice.ts';
|
|
3
|
+
import type { NoticeStore } from './notice-store.ts';
|
|
4
|
+
export declare class NoticeNotes implements PromptNotes {
|
|
5
|
+
private readonly store;
|
|
6
|
+
private readonly chatId;
|
|
7
|
+
private readonly words;
|
|
8
|
+
constructor(store: Pick<NoticeStore, 'take'>, chatId: string, words: Pick<MessageWords, 'heard'>);
|
|
9
|
+
next(): {
|
|
10
|
+
shown: string[];
|
|
11
|
+
heard: string[];
|
|
12
|
+
};
|
|
13
|
+
reset(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare function unshownNotes(store: Pick<NoticeStore, 'show'>, chatId: string, words: Pick<MessageWords, 'shown'>): Promise<string[]>;
|