@zgeoff/atc 0.1.12 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -12
- package/package.json +7 -2
- package/src/{agent-adapter.ts → agents/agent-adapter.ts} +31 -14
- package/src/{build-cli-command.ts → agents/build-cli-command.ts} +1 -1
- package/src/agents/build-hook-settings.ts +59 -0
- package/src/{claude-adapter.ts → agents/claude-adapter.ts} +41 -80
- package/src/{codex-adapter.ts → agents/codex-adapter.ts} +41 -30
- package/src/agents/gateway-adapter.ts +106 -0
- package/src/{grok-adapter.ts → agents/grok-adapter.ts} +83 -46
- package/src/agents/resolve-agent-home.ts +13 -0
- package/src/agents/truncate-detail.ts +7 -0
- package/src/agents/write-hook-settings.ts +37 -0
- package/src/cli.ts +25 -14
- package/src/{boot-daemon.ts → client/boot-daemon.ts} +8 -7
- package/src/client/build-client-machine.ts +119 -0
- package/src/client/collect-agent-picks.ts +31 -0
- package/src/{daemon-client.ts → client/daemon-client.ts} +4 -4
- package/src/{dirs.ts → client/dirs.ts} +8 -0
- package/src/client/format-overlay-agent-mark.ts +15 -0
- package/src/{index.ts → client/index.ts} +181 -550
- package/src/client/keys.ts +111 -0
- package/src/client/parse-daemon-event.ts +114 -0
- package/src/{pick-tab-target.ts → client/pick-tab-target.ts} +2 -2
- package/src/client/spawn-picker.ts +305 -0
- package/src/client/to-mirror-session.ts +65 -0
- package/src/{ui.ts → client/ui.ts} +13 -8
- package/src/{attach-registry.ts → daemon/attach-registry.ts} +12 -10
- package/src/daemon/build-session-event.ts +43 -0
- package/src/{daemon-connection.ts → daemon/daemon-connection.ts} +194 -108
- package/src/{daemon.ts → daemon/daemon.ts} +142 -312
- package/src/{hooks.ts → daemon/hooks.ts} +6 -4
- package/src/daemon/mint-session-id.ts +12 -0
- package/src/{permission-registry.ts → daemon/permission-registry.ts} +5 -3
- package/src/daemon/restore-fleet.ts +141 -0
- package/src/daemon/run-eject-handoff.ts +33 -0
- package/src/{screen-model.ts → daemon/screen-model.ts} +1 -1
- package/src/daemon/session-runtime.ts +64 -0
- package/src/{sessions.ts → daemon/sessions.ts} +77 -133
- package/src/{start-headless-run.ts → daemon/start-headless-run.ts} +9 -3
- package/src/daemon/start-headless-turn.ts +57 -0
- package/src/hook-report.ts +2 -2
- package/src/mcp-server.ts +33 -44
- package/src/protocol/parse-request-params.ts +31 -0
- package/src/{protocol.ts → protocol/protocol.ts} +1 -1
- package/src/protocol/request-param-schemas.ts +109 -0
- package/src/shared/agent-session-id.ts +8 -0
- package/src/shared/build-optional-boolean.ts +10 -0
- package/src/shared/build-optional-string-array.ts +14 -0
- package/src/shared/build-optional-string.ts +10 -0
- package/src/shared/collect-gateways.ts +113 -0
- package/src/{config.ts → shared/config.ts} +47 -31
- package/src/{get-build.ts → shared/get-build.ts} +21 -6
- package/src/shared/get-record.ts +20 -0
- package/src/shared/session-id.ts +8 -0
- package/src/shared/to-agent-session-id.ts +12 -0
- package/src/shared/to-session-id.ts +11 -0
- package/src/shared/to-shell-arg.ts +7 -0
- package/src/statusline.ts +2 -2
- package/src/store/bun-sqlite-driver.ts +96 -0
- package/src/store/fleet-entry.ts +72 -0
- package/src/store/run-migrations.ts +242 -0
- package/src/store/state-store.ts +226 -0
- package/src/format-overlay-agent-mark.ts +0 -17
- package/src/state-store.ts +0 -236
- /package/src/{normalize-hook-event.ts → agents/normalize-hook-event.ts} +0 -0
- /package/src/{print-codex-hook-file.ts → agents/print-codex-hook-file.ts} +0 -0
- /package/src/{print-grok-hook-file.ts → agents/print-grok-hook-file.ts} +0 -0
- /package/src/{build-leader-chords.ts → client/build-leader-chords.ts} +0 -0
- /package/src/{daemon-error.ts → protocol/daemon-error.ts} +0 -0
- /package/src/{outbound-queue.ts → protocol/outbound-queue.ts} +0 -0
- /package/src/{collect-clean-env.ts → shared/collect-clean-env.ts} +0 -0
- /package/src/{report.ts → shared/report.ts} +0 -0
- /package/src/{reset-input-modes.ts → shared/reset-input-modes.ts} +0 -0
- /package/src/{resolve-repo-root.ts → shared/resolve-repo-root.ts} +0 -0
package/src/state-store.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { Database } from 'bun:sqlite';
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { toAgentKind } from './agent-adapter';
|
|
4
|
-
import type { AgentKind } from './agent-adapter';
|
|
5
|
-
import type { HookEvent } from './hooks';
|
|
6
|
-
import { parseFleetEntry } from './sessions';
|
|
7
|
-
import type { FleetEntry } from './sessions';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Daemon state in one SQLite store: the restorable fleet, the hook-event
|
|
11
|
-
* debug trail, and the spawn-directory history. The statusline contract
|
|
12
|
-
* file (status.json) stays a plain file because reporters inside wrangled
|
|
13
|
-
* sessions read it without speaking to the daemon. An existing fleet.json
|
|
14
|
-
* seeds the fleet table once, so upgrading never loses a restorable fleet.
|
|
15
|
-
*/
|
|
16
|
-
export class StateStore {
|
|
17
|
-
private readonly db: Database;
|
|
18
|
-
|
|
19
|
-
constructor(dbPath: string, legacyFleetPath?: string) {
|
|
20
|
-
this.db = new Database(dbPath, { create: true });
|
|
21
|
-
|
|
22
|
-
this.db.run('PRAGMA journal_mode = WAL;');
|
|
23
|
-
|
|
24
|
-
this.db.run(`
|
|
25
|
-
CREATE TABLE IF NOT EXISTS fleet (
|
|
26
|
-
agent_session_id TEXT PRIMARY KEY,
|
|
27
|
-
name TEXT NOT NULL,
|
|
28
|
-
cwd TEXT NOT NULL,
|
|
29
|
-
pinned INTEGER NOT NULL DEFAULT 0,
|
|
30
|
-
last_attached INTEGER,
|
|
31
|
-
agent TEXT NOT NULL DEFAULT 'claude',
|
|
32
|
-
exited INTEGER NOT NULL DEFAULT 0
|
|
33
|
-
);
|
|
34
|
-
CREATE TABLE IF NOT EXISTS events (
|
|
35
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
36
|
-
ts TEXT NOT NULL,
|
|
37
|
-
atc_id TEXT NOT NULL,
|
|
38
|
-
event TEXT NOT NULL,
|
|
39
|
-
message TEXT,
|
|
40
|
-
session_id TEXT
|
|
41
|
-
);
|
|
42
|
-
CREATE TABLE IF NOT EXISTS spawn_history (
|
|
43
|
-
cwd TEXT PRIMARY KEY,
|
|
44
|
-
last_spawn INTEGER NOT NULL
|
|
45
|
-
);
|
|
46
|
-
CREATE TABLE IF NOT EXISTS prefs (
|
|
47
|
-
key TEXT PRIMARY KEY,
|
|
48
|
-
value TEXT NOT NULL
|
|
49
|
-
);
|
|
50
|
-
`);
|
|
51
|
-
|
|
52
|
-
// Stores created before pinning, attach recency, or agent kind existed
|
|
53
|
-
// lack the columns; the create above only applies to fresh databases.
|
|
54
|
-
const fleetColumns = new Set(
|
|
55
|
-
this.db
|
|
56
|
-
.query<{ name: string }, []>('PRAGMA table_info(fleet)')
|
|
57
|
-
.all()
|
|
58
|
-
.map((c) => c.name),
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// Stores created before the id column was agent-neutral carry it under
|
|
62
|
-
// its Claude-era name.
|
|
63
|
-
if (fleetColumns.has('claude_id')) {
|
|
64
|
-
this.db.run('ALTER TABLE fleet RENAME COLUMN claude_id TO agent_session_id');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!fleetColumns.has('pinned')) {
|
|
68
|
-
this.db.run('ALTER TABLE fleet ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!fleetColumns.has('last_attached')) {
|
|
72
|
-
this.db.run('ALTER TABLE fleet ADD COLUMN last_attached INTEGER');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!fleetColumns.has('agent')) {
|
|
76
|
-
this.db.run("ALTER TABLE fleet ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude'");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!fleetColumns.has('exited')) {
|
|
80
|
-
this.db.run('ALTER TABLE fleet ADD COLUMN exited INTEGER NOT NULL DEFAULT 0');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (legacyFleetPath !== undefined) {
|
|
84
|
-
this.adoptLegacyFleet(legacyFleetPath);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
loadFleet(): FleetEntry[] {
|
|
89
|
-
const rows = this.db
|
|
90
|
-
.query<
|
|
91
|
-
{
|
|
92
|
-
agent_session_id: string;
|
|
93
|
-
name: string;
|
|
94
|
-
cwd: string;
|
|
95
|
-
pinned: number;
|
|
96
|
-
last_attached: number | null;
|
|
97
|
-
agent: string | null;
|
|
98
|
-
exited: number;
|
|
99
|
-
},
|
|
100
|
-
[]
|
|
101
|
-
>('SELECT agent_session_id, name, cwd, pinned, last_attached, agent, exited FROM fleet')
|
|
102
|
-
.all();
|
|
103
|
-
|
|
104
|
-
const entries: FleetEntry[] = [];
|
|
105
|
-
|
|
106
|
-
for (const row of rows) {
|
|
107
|
-
entries.push({
|
|
108
|
-
agentSessionID: row.agent_session_id,
|
|
109
|
-
name: row.name,
|
|
110
|
-
cwd: row.cwd,
|
|
111
|
-
agent: toAgentKind(row.agent),
|
|
112
|
-
...(row.pinned === 0 ? {} : { pinned: true }),
|
|
113
|
-
...(row.last_attached === null ? {} : { lastAttachedAt: row.last_attached }),
|
|
114
|
-
...(row.exited === 0 ? {} : { exited: true }),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return entries;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// The latest hook-event timestamp per agent session id, from the event
|
|
122
|
-
// trail. Sessions that never reported an event are absent.
|
|
123
|
-
collectFleetRecency(): Map<string, string> {
|
|
124
|
-
const rows = this.db
|
|
125
|
-
.query<{ session_id: string; ts: string }, []>(
|
|
126
|
-
'SELECT session_id, MAX(ts) AS ts FROM events WHERE session_id IS NOT NULL GROUP BY session_id',
|
|
127
|
-
)
|
|
128
|
-
.all();
|
|
129
|
-
|
|
130
|
-
return new Map(rows.map((row) => [row.session_id, row.ts]));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
writeFleet(entries: readonly FleetEntry[]): void {
|
|
134
|
-
const replace = this.db.transaction((all: readonly FleetEntry[]) => {
|
|
135
|
-
this.db.run('DELETE FROM fleet');
|
|
136
|
-
|
|
137
|
-
const insert = this.db.query(
|
|
138
|
-
'INSERT OR REPLACE INTO fleet (agent_session_id, name, cwd, pinned, last_attached, agent, exited) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)',
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
for (const entry of all) {
|
|
142
|
-
const pinned = entry.pinned === true ? 1 : 0;
|
|
143
|
-
const exited = entry.exited === true ? 1 : 0;
|
|
144
|
-
|
|
145
|
-
insert.run(
|
|
146
|
-
entry.agentSessionID,
|
|
147
|
-
entry.name,
|
|
148
|
-
entry.cwd,
|
|
149
|
-
pinned,
|
|
150
|
-
entry.lastAttachedAt ?? null,
|
|
151
|
-
entry.agent,
|
|
152
|
-
exited,
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
replace(entries);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
recordEvent(e: HookEvent): void {
|
|
161
|
-
const rawMessage = e.payload['message'];
|
|
162
|
-
const rawSessionID = e.payload['session_id'] ?? e.payload['sessionId'];
|
|
163
|
-
const message = typeof rawMessage === 'string' ? rawMessage : null;
|
|
164
|
-
const sessionID = typeof rawSessionID === 'string' ? rawSessionID : null;
|
|
165
|
-
|
|
166
|
-
this.db
|
|
167
|
-
.query(
|
|
168
|
-
'INSERT INTO events (ts, atc_id, event, message, session_id) VALUES (?1, ?2, ?3, ?4, ?5)',
|
|
169
|
-
)
|
|
170
|
-
.run(new Date().toISOString(), e.atcId, e.event, message, sessionID);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
recordSpawnDir(cwd: string): void {
|
|
174
|
-
this.db
|
|
175
|
-
.query(
|
|
176
|
-
'INSERT INTO spawn_history (cwd, last_spawn) VALUES (?1, ?2) ON CONFLICT(cwd) DO UPDATE SET last_spawn = ?2',
|
|
177
|
-
)
|
|
178
|
-
.run(cwd, Date.now());
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
collectSpawnDirs(): string[] {
|
|
182
|
-
const rows = this.db
|
|
183
|
-
.query<{ cwd: string }, []>('SELECT cwd FROM spawn_history ORDER BY last_spawn DESC')
|
|
184
|
-
.all();
|
|
185
|
-
|
|
186
|
-
return rows.map((row) => row.cwd);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
loadLastUsedAgent(): AgentKind {
|
|
190
|
-
const row = this.db
|
|
191
|
-
.query<{ value: string }, []>("SELECT value FROM prefs WHERE key = 'last_used_agent'")
|
|
192
|
-
.get();
|
|
193
|
-
|
|
194
|
-
return toAgentKind(row?.value);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
writeLastUsedAgent(agent: AgentKind): void {
|
|
198
|
-
this.db
|
|
199
|
-
.query(
|
|
200
|
-
'INSERT INTO prefs (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value = ?2',
|
|
201
|
-
)
|
|
202
|
-
.run('last_used_agent', agent);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
stop(): void {
|
|
206
|
-
this.db.close();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private adoptLegacyFleet(legacyFleetPath: string): void {
|
|
210
|
-
const count = this.db.query<{ n: number }, []>('SELECT COUNT(*) AS n FROM fleet').get();
|
|
211
|
-
|
|
212
|
-
if (count === null || count.n > 0 || !existsSync(legacyFleetPath)) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const parsed: unknown = JSON.parse(readFileSync(legacyFleetPath, 'utf8'));
|
|
218
|
-
|
|
219
|
-
if (!Array.isArray(parsed)) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const entries: FleetEntry[] = [];
|
|
224
|
-
|
|
225
|
-
for (const entry of parsed) {
|
|
226
|
-
const parsedEntry = parseFleetEntry(entry);
|
|
227
|
-
|
|
228
|
-
if (parsedEntry !== undefined) {
|
|
229
|
-
entries.push(parsedEntry);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
this.writeFleet(entries);
|
|
234
|
-
} catch {}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|