ftown 0.25.2 → 0.26.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/dist/command-rpc.d.ts +3 -0
- package/dist/command-rpc.js +41 -0
- package/dist/command-rpc.js.map +1 -1
- package/dist/coordination/board.d.ts +12 -0
- package/dist/coordination/board.js +240 -0
- package/dist/coordination/board.js.map +1 -0
- package/dist/coordination/card-store.d.ts +9 -0
- package/dist/coordination/card-store.js +188 -0
- package/dist/coordination/card-store.js.map +1 -0
- package/dist/coordination/contract.d.ts +236 -0
- package/dist/coordination/contract.js +63 -0
- package/dist/coordination/contract.js.map +1 -0
- package/dist/coordination/format.d.ts +5 -0
- package/dist/coordination/format.js +60 -0
- package/dist/coordination/format.js.map +1 -0
- package/dist/coordination/tree-events.d.ts +31 -0
- package/dist/coordination/tree-events.js +226 -0
- package/dist/coordination/tree-events.js.map +1 -0
- package/dist/coordination/wiring.d.ts +48 -0
- package/dist/coordination/wiring.js +143 -0
- package/dist/coordination/wiring.js.map +1 -0
- package/dist/coordination-cli.d.ts +34 -0
- package/dist/coordination-cli.js +460 -0
- package/dist/coordination-cli.js.map +1 -0
- package/dist/ftown-cli.js +12 -2
- package/dist/ftown-cli.js.map +1 -1
- package/dist/index.js +33 -7
- package/dist/index.js.map +1 -1
- package/dist/local-api-server.d.ts +6 -1
- package/dist/local-api-server.js +282 -5
- package/dist/local-api-server.js.map +1 -1
- package/dist/mail-delivery.d.ts +16 -2
- package/dist/mail-delivery.js +331 -77
- package/dist/mail-delivery.js.map +1 -1
- package/dist/mail-store.d.ts +7 -0
- package/dist/mail-store.js +71 -1
- package/dist/mail-store.js.map +1 -1
- package/dist/pull-request-events.d.ts +5 -0
- package/dist/pull-request-events.js +21 -1
- package/dist/pull-request-events.js.map +1 -1
- package/dist/quota/agy.d.ts +16 -0
- package/dist/quota/agy.js +287 -0
- package/dist/quota/agy.js.map +1 -0
- package/dist/quota/annotate.d.ts +5 -0
- package/dist/quota/annotate.js +56 -0
- package/dist/quota/annotate.js.map +1 -0
- package/dist/quota/claude.d.ts +27 -0
- package/dist/quota/claude.js +265 -0
- package/dist/quota/claude.js.map +1 -0
- package/dist/quota/codex.d.ts +25 -0
- package/dist/quota/codex.js +378 -0
- package/dist/quota/codex.js.map +1 -0
- package/dist/quota/format.d.ts +6 -0
- package/dist/quota/format.js +113 -0
- package/dist/quota/format.js.map +1 -0
- package/dist/quota/grok.d.ts +97 -0
- package/dist/quota/grok.js +563 -0
- package/dist/quota/grok.js.map +1 -0
- package/dist/quota/models-agy.d.ts +10 -0
- package/dist/quota/models-agy.js +144 -0
- package/dist/quota/models-agy.js.map +1 -0
- package/dist/quota/models-claude.d.ts +10 -0
- package/dist/quota/models-claude.js +227 -0
- package/dist/quota/models-claude.js.map +1 -0
- package/dist/quota/models-codex.d.ts +18 -0
- package/dist/quota/models-codex.js +305 -0
- package/dist/quota/models-codex.js.map +1 -0
- package/dist/quota/models-grok.d.ts +19 -0
- package/dist/quota/models-grok.js +272 -0
- package/dist/quota/models-grok.js.map +1 -0
- package/dist/quota/models-muse.d.ts +13 -0
- package/dist/quota/models-muse.js +200 -0
- package/dist/quota/models-muse.js.map +1 -0
- package/dist/quota/models-opencode.d.ts +51 -0
- package/dist/quota/models-opencode.js +400 -0
- package/dist/quota/models-opencode.js.map +1 -0
- package/dist/quota/muse.d.ts +38 -0
- package/dist/quota/muse.js +225 -0
- package/dist/quota/muse.js.map +1 -0
- package/dist/quota/opencode.d.ts +7 -0
- package/dist/quota/opencode.js +47 -0
- package/dist/quota/opencode.js.map +1 -0
- package/dist/quota/routing-config.d.ts +18 -0
- package/dist/quota/routing-config.js +438 -0
- package/dist/quota/routing-config.js.map +1 -0
- package/dist/quota/routing-service.d.ts +24 -0
- package/dist/quota/routing-service.js +118 -0
- package/dist/quota/routing-service.js.map +1 -0
- package/dist/quota/routing.d.ts +10 -0
- package/dist/quota/routing.js +180 -0
- package/dist/quota/routing.js.map +1 -0
- package/dist/quota/types.d.ts +304 -0
- package/dist/quota/types.js +45 -0
- package/dist/quota/types.js.map +1 -0
- package/dist/quota-cli.d.ts +80 -0
- package/dist/quota-cli.js +462 -0
- package/dist/quota-cli.js.map +1 -0
- package/dist/security/bridge/permissions.js +24 -2
- package/dist/security/bridge/permissions.js.map +1 -1
- package/dist/types.d.ts +7 -3
- package/dist/wire-types.d.ts +17 -1
- package/dist/wire-types.js +1 -1
- package/dist/wire-types.js.map +1 -1
- package/package.json +3 -2
- package/skills/ftown/SKILL.md +31 -5
- package/skills/ftown/references/orchestrator.md +17 -7
- package/skills/ftown/references/quota.md +177 -0
- package/skills/ftown/references/sessions.md +45 -3
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { appendFile, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { TREE_EVENTS_COMPACT_AT, TREE_EVENTS_FILE, TREE_EVENTS_MAX_WAIT_MS, TREE_EVENTS_RETAIN, TREE_EVENT_KINDS, TREE_EVENT_SUMMARY_MAX, } from './contract.js';
|
|
5
|
+
const ID_PATTERN = /^[A-Za-z0-9_-]{1,200}$/;
|
|
6
|
+
const SESSION_NAME_MAX = 200;
|
|
7
|
+
const REF_MAX = 500;
|
|
8
|
+
const READ_LIMIT_DEFAULT = 100;
|
|
9
|
+
const READ_LIMIT_MAX = 500;
|
|
10
|
+
function clampInt(value, fallback, min, max) {
|
|
11
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
12
|
+
return fallback;
|
|
13
|
+
return Math.min(max, Math.max(min, Math.trunc(value)));
|
|
14
|
+
}
|
|
15
|
+
/** Replaces control characters and whitespace with spaces, then collapses runs of spaces. */
|
|
16
|
+
function cleanLine(text) {
|
|
17
|
+
return (typeof text === 'string' ? text : '')
|
|
18
|
+
.replace(/[\u0000-\u001f\u007f\s]/g, ' ')
|
|
19
|
+
.replace(/\s+/g, ' ')
|
|
20
|
+
.trim();
|
|
21
|
+
}
|
|
22
|
+
/** Hard-truncates to `max` characters total, marking the cut with an ellipsis. */
|
|
23
|
+
function truncate(text, max) {
|
|
24
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
25
|
+
}
|
|
26
|
+
/** Accepts only lines that parse to a well-formed tree event; anything else (truncated tail included) is skipped. */
|
|
27
|
+
function parseEvent(line) {
|
|
28
|
+
if (!line.trim())
|
|
29
|
+
return null;
|
|
30
|
+
let value;
|
|
31
|
+
try {
|
|
32
|
+
value = JSON.parse(line);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
38
|
+
return null;
|
|
39
|
+
const record = value;
|
|
40
|
+
const { seq, ts, sessionId, sessionName, kind, summary, ref } = record;
|
|
41
|
+
if (typeof seq !== 'number' || !Number.isInteger(seq) || seq < 1)
|
|
42
|
+
return null;
|
|
43
|
+
if (typeof ts !== 'string' || typeof sessionId !== 'string' || typeof sessionName !== 'string' || typeof summary !== 'string')
|
|
44
|
+
return null;
|
|
45
|
+
if (typeof kind !== 'string' || !TREE_EVENT_KINDS.includes(kind))
|
|
46
|
+
return null;
|
|
47
|
+
if (ref !== undefined && typeof ref !== 'string')
|
|
48
|
+
return null;
|
|
49
|
+
return { seq, ts, sessionId, sessionName, kind: kind, summary, ...(ref !== undefined ? { ref } : {}) };
|
|
50
|
+
}
|
|
51
|
+
/** Per-root JSONL log of tree events (one JSON object per line, seq survives compaction). */
|
|
52
|
+
export class TreeEventLog {
|
|
53
|
+
deps;
|
|
54
|
+
locks = new Map();
|
|
55
|
+
/** Highest seq seen per root, cached after the first file read. */
|
|
56
|
+
heads = new Map();
|
|
57
|
+
/** Valid event lines per root, cached alongside the head to trigger compaction without recounting. */
|
|
58
|
+
counts = new Map();
|
|
59
|
+
/** Wake callbacks of long-poll waiters, keyed by root. */
|
|
60
|
+
waiters = new Map();
|
|
61
|
+
constructor(deps) {
|
|
62
|
+
this.deps = deps;
|
|
63
|
+
}
|
|
64
|
+
file(rootId) {
|
|
65
|
+
return join(this.deps.sessionDir(rootId), TREE_EVENTS_FILE);
|
|
66
|
+
}
|
|
67
|
+
requireValidId(rootId) {
|
|
68
|
+
if (!ID_PATTERN.test(rootId))
|
|
69
|
+
throw new Error('Invalid session id');
|
|
70
|
+
}
|
|
71
|
+
withLock(rootId, fn) {
|
|
72
|
+
const previous = this.locks.get(rootId) ?? Promise.resolve();
|
|
73
|
+
const next = previous.then(fn, fn);
|
|
74
|
+
const settled = next.catch(() => undefined);
|
|
75
|
+
this.locks.set(rootId, settled);
|
|
76
|
+
void settled.then(() => { if (this.locks.get(rootId) === settled)
|
|
77
|
+
this.locks.delete(rootId); });
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
/** Parses the log, keeping only well-formed events; a truncated/corrupt tail is tolerated. */
|
|
81
|
+
async events(rootId) {
|
|
82
|
+
let content;
|
|
83
|
+
try {
|
|
84
|
+
content = await readFile(this.file(rootId), 'utf8');
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error.code === 'ENOENT')
|
|
88
|
+
return [];
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
const events = [];
|
|
92
|
+
for (const line of content.split('\n')) {
|
|
93
|
+
const event = parseEvent(line);
|
|
94
|
+
if (event)
|
|
95
|
+
events.push(event);
|
|
96
|
+
}
|
|
97
|
+
return events;
|
|
98
|
+
}
|
|
99
|
+
/** Cold-fills the head cache. Must run while holding the per-root lock, so it can never
|
|
100
|
+
* overwrite a newer head written by a concurrent append. */
|
|
101
|
+
async stateLocked(rootId) {
|
|
102
|
+
const cachedHead = this.heads.get(rootId);
|
|
103
|
+
const cachedCount = this.counts.get(rootId);
|
|
104
|
+
if (cachedHead !== undefined && cachedCount !== undefined)
|
|
105
|
+
return { head: cachedHead, count: cachedCount };
|
|
106
|
+
const events = await this.events(rootId);
|
|
107
|
+
const head = events.reduce((max, event) => Math.max(max, event.seq), 0);
|
|
108
|
+
this.heads.set(rootId, head);
|
|
109
|
+
this.counts.set(rootId, events.length);
|
|
110
|
+
return { head, count: events.length };
|
|
111
|
+
}
|
|
112
|
+
cleaned(input) {
|
|
113
|
+
if (!ID_PATTERN.test(input.sessionId))
|
|
114
|
+
throw new Error('Invalid session id');
|
|
115
|
+
if (typeof input.kind !== 'string' || !TREE_EVENT_KINDS.includes(input.kind)) {
|
|
116
|
+
throw new Error('Invalid event kind');
|
|
117
|
+
}
|
|
118
|
+
const summary = truncate(cleanLine(input.summary), TREE_EVENT_SUMMARY_MAX);
|
|
119
|
+
const sessionName = cleanLine(input.sessionName).slice(0, SESSION_NAME_MAX);
|
|
120
|
+
const ref = input.ref === undefined ? undefined : cleanLine(input.ref);
|
|
121
|
+
return { sessionId: input.sessionId, sessionName, kind: input.kind, summary, ...(ref ? { ref: ref.slice(0, REF_MAX) } : {}) };
|
|
122
|
+
}
|
|
123
|
+
async append(rootId, input) {
|
|
124
|
+
this.requireValidId(rootId);
|
|
125
|
+
const fields = this.cleaned(input);
|
|
126
|
+
const event = { seq: 0, ts: new Date(this.deps.now()).toISOString(), ...fields };
|
|
127
|
+
await this.withLock(rootId, async () => {
|
|
128
|
+
const { head, count } = await this.stateLocked(rootId);
|
|
129
|
+
event.seq = head + 1;
|
|
130
|
+
const dir = this.deps.sessionDir(rootId);
|
|
131
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
132
|
+
await appendFile(this.file(rootId), `${JSON.stringify(event)}\n`, { mode: 0o600 });
|
|
133
|
+
this.heads.set(rootId, event.seq);
|
|
134
|
+
this.counts.set(rootId, count + 1);
|
|
135
|
+
if (count + 1 > TREE_EVENTS_COMPACT_AT) {
|
|
136
|
+
// The event is already durable: a failed compaction must not fail the append.
|
|
137
|
+
// The count stays above the threshold, so the next append retries compaction.
|
|
138
|
+
await this.compact(rootId).catch((error) => {
|
|
139
|
+
console.warn(`[Bridge] Tree event compaction failed for session ${rootId}:`, error);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
for (const wake of Array.from(this.waiters.get(rootId) ?? []))
|
|
144
|
+
wake();
|
|
145
|
+
return event;
|
|
146
|
+
}
|
|
147
|
+
/** Rewrites the log down to the newest TREE_EVENTS_RETAIN events via tmp file + rename. Seqs are preserved. */
|
|
148
|
+
async compact(rootId) {
|
|
149
|
+
const retained = (await this.events(rootId)).sort((a, b) => a.seq - b.seq).slice(-TREE_EVENTS_RETAIN);
|
|
150
|
+
const file = this.file(rootId);
|
|
151
|
+
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
152
|
+
try {
|
|
153
|
+
const handle = await open(tmp, 'wx', 0o600);
|
|
154
|
+
try {
|
|
155
|
+
await handle.writeFile(retained.map((event) => JSON.stringify(event)).join('\n') + (retained.length ? '\n' : ''), 'utf8');
|
|
156
|
+
await handle.sync();
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
await handle.close();
|
|
160
|
+
}
|
|
161
|
+
await rename(tmp, file);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
await rm(tmp, { force: true }).catch(() => undefined);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
this.counts.set(rootId, retained.length);
|
|
168
|
+
}
|
|
169
|
+
async read(rootId, opts = {}) {
|
|
170
|
+
this.requireValidId(rootId);
|
|
171
|
+
const since = clampInt(opts.since, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
172
|
+
const limit = clampInt(opts.limit, READ_LIMIT_DEFAULT, 1, READ_LIMIT_MAX);
|
|
173
|
+
const matching = (await this.events(rootId)).filter((event) => event.seq > since).sort((a, b) => a.seq - b.seq);
|
|
174
|
+
const events = matching.slice(0, limit);
|
|
175
|
+
return { events, cursor: events.length ? events[events.length - 1].seq : since };
|
|
176
|
+
}
|
|
177
|
+
async wait(rootId, since, timeoutMs, signal) {
|
|
178
|
+
this.requireValidId(rootId);
|
|
179
|
+
const start = clampInt(since, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
180
|
+
const ms = clampInt(timeoutMs, 0, 0, TREE_EVENTS_MAX_WAIT_MS);
|
|
181
|
+
// The wake callback is registered and armed before the first read, so an append
|
|
182
|
+
// committing while that read is in flight cannot be lost.
|
|
183
|
+
let wake;
|
|
184
|
+
const waiters = this.waiters.get(rootId) ?? new Set();
|
|
185
|
+
const waiter = () => wake?.();
|
|
186
|
+
waiters.add(waiter);
|
|
187
|
+
this.waiters.set(rootId, waiters);
|
|
188
|
+
let timer;
|
|
189
|
+
let removeAbort;
|
|
190
|
+
try {
|
|
191
|
+
if (signal?.aborted)
|
|
192
|
+
return { events: [], cursor: start, timedOut: true };
|
|
193
|
+
const settled = new Promise((resolve) => {
|
|
194
|
+
wake = () => resolve(false);
|
|
195
|
+
timer = setTimeout(() => resolve(true), ms);
|
|
196
|
+
timer.unref?.();
|
|
197
|
+
if (signal) {
|
|
198
|
+
const onAbort = () => resolve(true);
|
|
199
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
200
|
+
removeAbort = () => signal.removeEventListener('abort', onAbort);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
const immediate = await this.read(rootId, { since: start });
|
|
204
|
+
if (immediate.events.length)
|
|
205
|
+
return immediate;
|
|
206
|
+
const timedOut = await settled;
|
|
207
|
+
if (timedOut)
|
|
208
|
+
return { events: [], cursor: start, timedOut: true };
|
|
209
|
+
// Woken by an append: return the page as of now (empty if the append was for an already-seen seq).
|
|
210
|
+
return await this.read(rootId, { since: start });
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
if (timer)
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
removeAbort?.();
|
|
216
|
+
waiters.delete(waiter);
|
|
217
|
+
if (!waiters.size)
|
|
218
|
+
this.waiters.delete(rootId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async head(rootId) {
|
|
222
|
+
this.requireValidId(rootId);
|
|
223
|
+
return this.withLock(rootId, async () => (await this.stateLocked(rootId)).head);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=tree-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-events.js","sourceRoot":"","sources":["../../src/coordination/tree-events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Activity } from '../attention-events.js';
|
|
2
|
+
import type { PullRequestState } from '../pull-request-status-protocol.js';
|
|
3
|
+
import type { Session } from '../types.js';
|
|
4
|
+
import type { BoardDeps, BoardServiceApi, CardStoreApi, CardStoreDeps, DigestProvider, MailEventSink, TreeEventKind, TreeEventLogApi, TreeEventLogDeps } from './contract.js';
|
|
5
|
+
export interface CoordinationSessionStore {
|
|
6
|
+
loadSession(id: string): Promise<Session | null>;
|
|
7
|
+
saveSession(session: Session): Promise<void>;
|
|
8
|
+
listSessions(): Promise<Session[]>;
|
|
9
|
+
sessionDir(id: string): string;
|
|
10
|
+
}
|
|
11
|
+
export interface CoordinationAttentionStore {
|
|
12
|
+
list(id: string): Promise<{
|
|
13
|
+
activity: Activity;
|
|
14
|
+
openCount: number;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export interface CoordinationPrStatusResult {
|
|
18
|
+
state: PullRequestState;
|
|
19
|
+
stale: boolean;
|
|
20
|
+
checkedAt: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface CoordinationMailStore {
|
|
23
|
+
unreadCount(id: string): Promise<number>;
|
|
24
|
+
}
|
|
25
|
+
export interface CoordinationDeps {
|
|
26
|
+
store: CoordinationSessionStore;
|
|
27
|
+
attention: CoordinationAttentionStore;
|
|
28
|
+
prStatus: (url: string) => Promise<CoordinationPrStatusResult | null>;
|
|
29
|
+
mail: CoordinationMailStore;
|
|
30
|
+
publishSession: (s: Session) => void;
|
|
31
|
+
now: () => number;
|
|
32
|
+
/** Optional factory seams for unit tests using *Api fakes. */
|
|
33
|
+
createCards?: (deps: CardStoreDeps) => CardStoreApi;
|
|
34
|
+
createEvents?: (deps: TreeEventLogDeps) => TreeEventLogApi;
|
|
35
|
+
createBoard?: (deps: BoardDeps) => BoardServiceApi;
|
|
36
|
+
}
|
|
37
|
+
export interface Coordination {
|
|
38
|
+
cards: CardStoreApi;
|
|
39
|
+
events: TreeEventLogApi;
|
|
40
|
+
board: BoardServiceApi;
|
|
41
|
+
digest: DigestProvider;
|
|
42
|
+
mailSink: MailEventSink;
|
|
43
|
+
emit(session: Session, kind: TreeEventKind, summary: string, ref?: string): Promise<void>;
|
|
44
|
+
onSessionExit(sessionId: string): Promise<void>;
|
|
45
|
+
onSessionStart(sessionId: string): void;
|
|
46
|
+
}
|
|
47
|
+
export declare function rootOf(session: Pick<Session, 'id' | 'parentSessionId'>): string;
|
|
48
|
+
export declare function createCoordination(deps: CoordinationDeps): Coordination;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { BoardService } from './board.js';
|
|
2
|
+
import { CardStore } from './card-store.js';
|
|
3
|
+
import { TreeEventLog } from './tree-events.js';
|
|
4
|
+
export function rootOf(session) {
|
|
5
|
+
return session.parentSessionId ?? session.id;
|
|
6
|
+
}
|
|
7
|
+
function sameBlockedOn(a, b) {
|
|
8
|
+
if (a === undefined && b === undefined)
|
|
9
|
+
return true;
|
|
10
|
+
if (a === undefined || b === undefined)
|
|
11
|
+
return false;
|
|
12
|
+
return a.kind === b.kind && a.text === b.text && a.ref === b.ref;
|
|
13
|
+
}
|
|
14
|
+
function statusChanged(current, previous) {
|
|
15
|
+
if (!previous)
|
|
16
|
+
return true;
|
|
17
|
+
if (current.state !== previous.state)
|
|
18
|
+
return true;
|
|
19
|
+
if (current.headline !== previous.headline)
|
|
20
|
+
return true;
|
|
21
|
+
return !sameBlockedOn(current.blockedOn, previous.blockedOn);
|
|
22
|
+
}
|
|
23
|
+
function formatStatusSummary(card) {
|
|
24
|
+
const base = `${card.state}: ${card.headline}`;
|
|
25
|
+
if (card.state === 'blocked' && card.blockedOn) {
|
|
26
|
+
return `${base} — blocked on ${card.blockedOn.kind}: ${card.blockedOn.text}`;
|
|
27
|
+
}
|
|
28
|
+
return base;
|
|
29
|
+
}
|
|
30
|
+
export function createCoordination(deps) {
|
|
31
|
+
const events = (deps.createEvents ?? ((d) => new TreeEventLog(d)))({
|
|
32
|
+
sessionDir: (id) => deps.store.sessionDir(id),
|
|
33
|
+
now: deps.now,
|
|
34
|
+
});
|
|
35
|
+
const exitedSessions = new Set();
|
|
36
|
+
function onSessionStart(sessionId) {
|
|
37
|
+
exitedSessions.delete(sessionId);
|
|
38
|
+
}
|
|
39
|
+
async function emit(session, kind, summary, ref) {
|
|
40
|
+
if (kind === 'exit') {
|
|
41
|
+
if (exitedSessions.has(session.id))
|
|
42
|
+
return;
|
|
43
|
+
exitedSessions.add(session.id);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const rootId = rootOf(session);
|
|
47
|
+
await events.append(rootId, {
|
|
48
|
+
sessionId: session.id,
|
|
49
|
+
sessionName: session.name,
|
|
50
|
+
kind,
|
|
51
|
+
summary,
|
|
52
|
+
...(ref !== undefined ? { ref } : {}),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn('[Coordination] Failed to emit tree event:', error instanceof Error ? error.message : String(error));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function onSessionExit(sessionId) {
|
|
60
|
+
if (exitedSessions.has(sessionId))
|
|
61
|
+
return;
|
|
62
|
+
await deps.store
|
|
63
|
+
.loadSession(sessionId)
|
|
64
|
+
.then((session) => {
|
|
65
|
+
if (session && (session.status === 'completed' || session.status === 'error')) {
|
|
66
|
+
return emit(session, 'exit', `exited: ${session.status}`);
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.catch((error) => {
|
|
70
|
+
console.warn('[Coordination] Failed to load session on exit:', error instanceof Error ? error.message : String(error));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const cards = (deps.createCards ?? ((d) => new CardStore(d)))({
|
|
74
|
+
loadSession: (id) => deps.store.loadSession(id),
|
|
75
|
+
saveSession: (session) => deps.store.saveSession(session),
|
|
76
|
+
now: deps.now,
|
|
77
|
+
onChange: async (saved, previous) => {
|
|
78
|
+
try {
|
|
79
|
+
deps.publishSession(saved);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.warn('[Coordination] Failed to publish session update:', error instanceof Error ? error.message : String(error));
|
|
83
|
+
}
|
|
84
|
+
if (saved.card && statusChanged(saved.card, previous)) {
|
|
85
|
+
await emit(saved, 'status', formatStatusSummary(saved.card));
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const board = (deps.createBoard ?? ((d) => new BoardService(d)))({
|
|
90
|
+
loadSession: (id) => deps.store.loadSession(id),
|
|
91
|
+
listSessions: () => deps.store.listSessions(),
|
|
92
|
+
attention: async (id) => {
|
|
93
|
+
const page = await deps.attention.list(id);
|
|
94
|
+
return { activity: page.activity, openCount: page.openCount };
|
|
95
|
+
},
|
|
96
|
+
prStatus: (url) => deps.prStatus(url),
|
|
97
|
+
unreadMail: (id) => deps.mail.unreadCount(id),
|
|
98
|
+
events,
|
|
99
|
+
now: deps.now,
|
|
100
|
+
});
|
|
101
|
+
const digestCursors = new Map();
|
|
102
|
+
const digest = {
|
|
103
|
+
async digestFor(recipientSessionId) {
|
|
104
|
+
try {
|
|
105
|
+
const sessions = await deps.store.listSessions();
|
|
106
|
+
const hasChildren = sessions.some((s) => s.parentSessionId === recipientSessionId && s.id !== recipientSessionId);
|
|
107
|
+
if (!hasChildren)
|
|
108
|
+
return null;
|
|
109
|
+
let since = digestCursors.get(recipientSessionId);
|
|
110
|
+
if (since === undefined) {
|
|
111
|
+
since = await events.head(recipientSessionId);
|
|
112
|
+
digestCursors.set(recipientSessionId, since);
|
|
113
|
+
}
|
|
114
|
+
const result = await board.digest(recipientSessionId, since);
|
|
115
|
+
digestCursors.set(recipientSessionId, result.cursor);
|
|
116
|
+
return result.line;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
const mailSink = {
|
|
124
|
+
async onAccepted(message, recipient) {
|
|
125
|
+
try {
|
|
126
|
+
if (message.type !== 'result')
|
|
127
|
+
return;
|
|
128
|
+
const sender = await deps.store.loadSession(message.from);
|
|
129
|
+
if (!sender)
|
|
130
|
+
return;
|
|
131
|
+
if (rootOf(sender) !== recipient.id)
|
|
132
|
+
return;
|
|
133
|
+
const firstLine = (message.body.split(/\r?\n/, 1)[0] ?? '').slice(0, 200);
|
|
134
|
+
await emit(sender, 'result', firstLine);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.warn('[Coordination] Failed to handle accepted mail event:', error instanceof Error ? error.message : String(error));
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
return { cards, events, board, digest, mailSink, emit, onSessionExit, onSessionStart };
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=wiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiring.js","sourceRoot":"","sources":["../../src/coordination/wiring.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Board, type BoardWaitCondition, type CardPatch, type CardProgress, type TreeEvent } from './coordination/contract.js';
|
|
2
|
+
/** One local-API call; injectable so tests need no bridge. */
|
|
3
|
+
export type CoordinationApiFn = (method: string, path: string, body?: unknown) => Promise<unknown>;
|
|
4
|
+
export interface CoordinationCliDeps {
|
|
5
|
+
api: CoordinationApiFn;
|
|
6
|
+
now: () => number;
|
|
7
|
+
env: NodeJS.ProcessEnv;
|
|
8
|
+
log: (text: string) => void;
|
|
9
|
+
errorLog: (text: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare const WAIT_DEFAULT_TIMEOUT_SECONDS = 300;
|
|
12
|
+
export declare const WAIT_MAX_TIMEOUT_SECONDS = 300;
|
|
13
|
+
/** Usage problem: message printed, exit code 2. */
|
|
14
|
+
export declare class UsageError extends Error {
|
|
15
|
+
readonly usage?: string | undefined;
|
|
16
|
+
constructor(message: string, usage?: string | undefined);
|
|
17
|
+
}
|
|
18
|
+
export declare function parseProgress(value: string): CardProgress;
|
|
19
|
+
export interface StatusSetFlags {
|
|
20
|
+
state?: string;
|
|
21
|
+
headline?: string;
|
|
22
|
+
/** string value, false for --no-progress, undefined when absent */
|
|
23
|
+
progress?: string | boolean;
|
|
24
|
+
on?: string;
|
|
25
|
+
text?: string;
|
|
26
|
+
/** string value, false for --no-next, undefined when absent */
|
|
27
|
+
next?: string | boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildCardPatch(flags: StatusSetFlags): CardPatch;
|
|
30
|
+
/** Pure wait-condition evaluation over a fetched board and the events that arrived. */
|
|
31
|
+
export declare function evaluateWaitCondition(condition: BoardWaitCondition, board: Board, events: readonly TreeEvent[]): boolean;
|
|
32
|
+
export declare function runStatus(argv: string[], deps?: Partial<CoordinationCliDeps>): Promise<number>;
|
|
33
|
+
export declare function runBoard(argv: string[], deps?: Partial<CoordinationCliDeps>): Promise<number>;
|
|
34
|
+
export declare function runEvents(argv: string[], deps?: Partial<CoordinationCliDeps>): Promise<number>;
|