@zgeoff/atc 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +4 -0
  2. package/package.json +3 -1
  3. package/src/agents/agent-adapter.ts +11 -0
  4. package/src/agents/atc-bridge-files.ts +11 -0
  5. package/src/agents/build-cli-argv.ts +13 -0
  6. package/src/agents/build-cli-command.ts +4 -9
  7. package/src/agents/claude-adapter.ts +23 -2
  8. package/src/agents/codex-adapter.ts +2 -0
  9. package/src/agents/gateway-adapter.ts +23 -1
  10. package/src/agents/grok-adapter.ts +2 -0
  11. package/src/agents/write-atc-bridge.ts +42 -0
  12. package/src/agents/write-hook-settings.ts +2 -1
  13. package/src/cli.ts +51 -0
  14. package/src/client/daemon-client.ts +13 -1
  15. package/src/daemon/answer-byte-cap.ts +1 -0
  16. package/src/daemon/build-headless-env.ts +23 -0
  17. package/src/daemon/build-session-message-event.ts +29 -0
  18. package/src/daemon/build-session-report-event.ts +24 -0
  19. package/src/daemon/daemon-connection.ts +200 -0
  20. package/src/daemon/daemon.ts +264 -3
  21. package/src/daemon/hooks.ts +13 -4
  22. package/src/daemon/mint-message-id.ts +12 -0
  23. package/src/daemon/parse-report.ts +44 -0
  24. package/src/daemon/session-runtime.ts +9 -0
  25. package/src/daemon/start-headless-run.ts +10 -2
  26. package/src/daemon/start-headless-turn.ts +1 -0
  27. package/src/daemon/tap-registry.ts +67 -0
  28. package/src/mcp-server.ts +56 -0
  29. package/src/protocol/request-param-schemas.ts +12 -0
  30. package/src/report.ts +59 -0
  31. package/src/shared/message-id.ts +7 -0
  32. package/src/shared/report-kinds.ts +1 -0
  33. package/src/shared/report.ts +18 -2
  34. package/src/shared/to-message-id.ts +11 -0
  35. package/src/store/message-owner.ts +9 -0
  36. package/src/store/message-record.ts +18 -0
  37. package/src/store/run-migrations.ts +52 -2
  38. package/src/store/state-store.ts +144 -2
  39. package/src/tap.ts +120 -0
package/src/tap.ts ADDED
@@ -0,0 +1,120 @@
1
+ import { z } from 'zod';
2
+ import { DaemonClient } from './client/daemon-client';
3
+ import { DaemonError } from './protocol/daemon-error';
4
+ import { daemonSocketPath } from './shared/config';
5
+ import { getBuild } from './shared/get-build';
6
+
7
+ const INBOX_MESSAGE_SCHEMA = z.looseObject({
8
+ message: z.string(),
9
+ from: z.string(),
10
+ text: z.string(),
11
+ sentAt: z.number(),
12
+ });
13
+
14
+ interface InboxMessage {
15
+ readonly message: string;
16
+ readonly from: string;
17
+ readonly text: string;
18
+ readonly sentAt: number;
19
+ }
20
+
21
+ /**
22
+ * Streams one session's inbox to stdout, one NDJSON line per message, and
23
+ * acks each message once its line is written. It talks to the daemon that
24
+ * is already running and never boots one: a tap runs inside the session it
25
+ * serves, so a restart from here would take the whole fleet down. Exits 1
26
+ * with a hint when no daemon listens or the session cannot be tapped, and
27
+ * 0 once the daemon closes the connection or ends the subscription because
28
+ * another tap replaced it or the session was removed.
29
+ */
30
+ export async function runTap(session: string): Promise<void> {
31
+ let client: DaemonClient;
32
+
33
+ try {
34
+ client = await DaemonClient.open(daemonSocketPath);
35
+ } catch {
36
+ console.error(`atc tap: no daemon at ${daemonSocketPath} — start atc first`);
37
+ process.exit(1);
38
+ }
39
+
40
+ const closed = Promise.withResolvers<void>();
41
+ let written: Promise<void> = Promise.resolve();
42
+
43
+ client.onClose = () => {
44
+ closed.resolve();
45
+ };
46
+
47
+ client.onEvent = (event) => {
48
+ if (event.ev === 'InboxClosed' && event['s'] === session) {
49
+ closed.resolve();
50
+
51
+ return;
52
+ }
53
+
54
+ if (event.ev !== 'InboxMessage' || event['s'] !== session) {
55
+ return;
56
+ }
57
+
58
+ const parsed = INBOX_MESSAGE_SCHEMA.safeParse(event);
59
+
60
+ if (!parsed.success) {
61
+ return;
62
+ }
63
+
64
+ const msg: InboxMessage = {
65
+ message: parsed.data.message,
66
+ from: parsed.data.from,
67
+ text: parsed.data.text,
68
+ sentAt: parsed.data.sentAt,
69
+ };
70
+
71
+ const previous = written;
72
+
73
+ written = (async () => {
74
+ await previous;
75
+ await ackInboxMessage(client, session, msg);
76
+ })();
77
+ };
78
+
79
+ try {
80
+ await client.sendHello(getBuild());
81
+ await client.sendRequest('session.tap', { session });
82
+ } catch (error) {
83
+ console.error(`atc tap: ${formatError(error)}`);
84
+ client.stop();
85
+ process.exit(1);
86
+ }
87
+
88
+ await closed.promise;
89
+ await written;
90
+
91
+ process.exit(0);
92
+ }
93
+
94
+ interface RequestSender {
95
+ readonly sendRequest: DaemonClient['sendRequest'];
96
+ }
97
+
98
+ async function ackInboxMessage(
99
+ client: RequestSender,
100
+ session: string,
101
+ msg: InboxMessage,
102
+ ): Promise<void> {
103
+ const line = `${JSON.stringify({ id: msg.message, from: msg.from, text: msg.text, sentAt: msg.sentAt })}\n`;
104
+
105
+ try {
106
+ await Bun.write(Bun.stdout, line);
107
+ } catch {
108
+ process.exit(1);
109
+ }
110
+
111
+ try {
112
+ await client.sendRequest('message.ack', { session, message: msg.message });
113
+ } catch (error) {
114
+ console.error(`atc tap: ${formatError(error)}`);
115
+ }
116
+ }
117
+
118
+ function formatError(error: unknown): string {
119
+ return error instanceof DaemonError ? `${error.code}: ${error.message}` : String(error);
120
+ }