ftown 0.19.66 → 0.19.68

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 (48) hide show
  1. package/dist/browser-access.js +1 -1
  2. package/dist/browser-access.js.map +1 -1
  3. package/dist/command-rpc.js +20 -0
  4. package/dist/command-rpc.js.map +1 -1
  5. package/dist/index.js +13 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/local-api-server.d.ts +3 -0
  8. package/dist/local-api-server.js +43 -0
  9. package/dist/local-api-server.js.map +1 -1
  10. package/dist/mail-delivery.d.ts +2 -0
  11. package/dist/mail-delivery.js +18 -7
  12. package/dist/mail-delivery.js.map +1 -1
  13. package/dist/mail-store.d.ts +9 -1
  14. package/dist/mail-store.js +71 -18
  15. package/dist/mail-store.js.map +1 -1
  16. package/dist/mcp/fleet.js +1 -1
  17. package/dist/mcp/fleet.js.map +1 -1
  18. package/dist/mcp/security-events.js +1 -1
  19. package/dist/mcp/security-events.js.map +1 -1
  20. package/dist/mcp/server.js +21 -0
  21. package/dist/mcp/server.js.map +1 -1
  22. package/dist/preview-chat-protocol.d.ts +50 -0
  23. package/dist/preview-chat-protocol.js +9 -0
  24. package/dist/preview-chat-protocol.js.map +1 -0
  25. package/dist/preview-chat.d.ts +18 -0
  26. package/dist/preview-chat.js +155 -0
  27. package/dist/preview-chat.js.map +1 -0
  28. package/dist/pull-request-events.d.ts +40 -0
  29. package/dist/pull-request-events.js +205 -0
  30. package/dist/pull-request-events.js.map +1 -0
  31. package/dist/pull-request-status.d.ts +16 -1
  32. package/dist/pull-request-status.js +80 -34
  33. package/dist/pull-request-status.js.map +1 -1
  34. package/dist/security/bridge/audit.js +1 -1
  35. package/dist/security/bridge/audit.js.map +1 -1
  36. package/dist/security/bridge/permissions.js +10 -0
  37. package/dist/security/bridge/permissions.js.map +1 -1
  38. package/dist/session-controller.d.ts +32 -0
  39. package/dist/session-controller.js +57 -2
  40. package/dist/session-controller.js.map +1 -1
  41. package/dist/session-wire.js +1 -1
  42. package/dist/session-wire.js.map +1 -1
  43. package/dist/sessions-commands.js +63 -0
  44. package/dist/sessions-commands.js.map +1 -1
  45. package/dist/types.d.ts +3 -1
  46. package/package.json +2 -2
  47. package/skills/ftown/SKILL.md +17 -3
  48. package/skills/ftown/references/sessions.md +27 -0
@@ -0,0 +1,9 @@
1
+ /** Shared preview conversation wire contract. No terminal transcript or iframe messages. */
2
+ export const PREVIEW_CHAT_TEXT_BYTES = 4096;
3
+ export const PREVIEW_CHAT_PAGE_BYTES = 65536;
4
+ export const PREVIEW_CHAT_PAGE_TURNS = 25;
5
+ export const PREVIEW_CHAT_MAX_TURNS = 1000;
6
+ export const PREVIEW_CHAT_STORE_BYTES = 4 * 1024 * 1024;
7
+ export const PREVIEW_CHAT_POLL_MS = 1500;
8
+ export const PREVIEW_CHAT_AGENT_SHELLS = ['claude', 'cursor', 'codex', 'zai', 'kimi', 'opencode', 'deepseek', 'fireworks', 'grok', 'pi', 'kimi-code', 'muse'];
9
+ //# sourceMappingURL=preview-chat-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-chat-protocol.js","sourceRoot":"","sources":["../src/preview-chat-protocol.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAC5C,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAC7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAC1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAC3C,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,CAAU,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { PreviewChatErrorCode, PreviewChatInput, PreviewChatPage, PreviewChatTurn } from './preview-chat-protocol.js';
2
+ export declare class PreviewChatError extends Error {
3
+ readonly code: PreviewChatErrorCode;
4
+ constructor(code: PreviewChatErrorCode, message: string);
5
+ }
6
+ export declare function validateChatInput(input: PreviewChatInput): void;
7
+ /** Caller serializes all methods with the session lifecycle queue. */
8
+ export declare class PreviewChatStore {
9
+ private readonly sessionDir;
10
+ constructor(sessionDir: (id: string) => string);
11
+ private path;
12
+ private load;
13
+ private save;
14
+ find(id: string, input: PreviewChatInput, role: PreviewChatTurn['role']): Promise<PreviewChatTurn | null>;
15
+ reserve(id: string, input: PreviewChatInput, role: PreviewChatTurn['role']): Promise<PreviewChatTurn>;
16
+ markQueued(id: string, turnId: string): Promise<PreviewChatTurn>;
17
+ list(id: string, cursor?: string): Promise<PreviewChatPage>;
18
+ }
@@ -0,0 +1,155 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { open, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { PREVIEW_CHAT_MAX_TURNS, PREVIEW_CHAT_PAGE_BYTES, PREVIEW_CHAT_PAGE_TURNS, PREVIEW_CHAT_STORE_BYTES, PREVIEW_CHAT_TEXT_BYTES } from './preview-chat-protocol.js';
6
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7
+ const session = /^[A-Za-z0-9_-]{1,200}$/;
8
+ export class PreviewChatError extends Error {
9
+ code;
10
+ constructor(code, message) {
11
+ super(message);
12
+ this.code = code;
13
+ }
14
+ }
15
+ export function validateChatInput(input) {
16
+ if (!input || typeof input.id !== 'string' || !uuid.test(input.id) || typeof input.text !== 'string' || !input.text.trim()
17
+ || /[\x00-\x08\x0b-\x1f\x7f-\x9f]/.test(input.text))
18
+ throw new PreviewChatError('invalid', 'Invalid conversation message');
19
+ if (Buffer.byteLength(input.text, 'utf8') > PREVIEW_CHAT_TEXT_BYTES)
20
+ throw new PreviewChatError('too_large', 'Conversation message is too large');
21
+ }
22
+ const turnSchema = z.object({ id: z.string().regex(uuid), sequence: z.number().int().positive(), text: z.string(), createdAt: z.iso.datetime(),
23
+ role: z.enum(['user', 'agent']), delivery: z.enum(['unconfirmed', 'queued', 'recorded']) }).strict();
24
+ const ledgerSchema = z.object({ schema: z.literal(1), turns: z.array(turnSchema).max(PREVIEW_CHAT_MAX_TURNS) }).strict();
25
+ /** Caller serializes all methods with the session lifecycle queue. */
26
+ export class PreviewChatStore {
27
+ sessionDir;
28
+ constructor(sessionDir) {
29
+ this.sessionDir = sessionDir;
30
+ }
31
+ path(id) { return join(this.sessionDir(id), 'preview-chat.json'); }
32
+ async load(id) {
33
+ if (!session.test(id))
34
+ throw new PreviewChatError('invalid', 'Invalid session');
35
+ let raw;
36
+ try {
37
+ const file = await open(this.path(id), 'r');
38
+ try {
39
+ if (!(await file.stat()).isFile())
40
+ throw new Error('invalid file');
41
+ const buffer = Buffer.alloc(PREVIEW_CHAT_STORE_BYTES + 1);
42
+ let used = 0;
43
+ while (used < buffer.length) {
44
+ const { bytesRead } = await file.read(buffer, used, buffer.length - used, used);
45
+ if (!bytesRead)
46
+ break;
47
+ used += bytesRead;
48
+ }
49
+ if (used > PREVIEW_CHAT_STORE_BYTES)
50
+ throw new Error('oversize');
51
+ raw = new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, used));
52
+ }
53
+ finally {
54
+ await file.close();
55
+ }
56
+ }
57
+ catch (error) {
58
+ if (error.code === 'ENOENT')
59
+ return [];
60
+ throw new PreviewChatError('unavailable', 'Conversation storage unavailable');
61
+ }
62
+ try {
63
+ if (Buffer.byteLength(raw) > PREVIEW_CHAT_STORE_BYTES)
64
+ throw new Error('oversize');
65
+ const value = ledgerSchema.parse(JSON.parse(raw));
66
+ const ids = new Set();
67
+ return value.turns.map((turn, index) => {
68
+ validateChatInput(turn);
69
+ if (turn.sequence !== index + 1 || ids.has(turn.id.toLowerCase()) || (turn.role === 'user' ? turn.delivery === 'recorded' : turn.delivery !== 'recorded'))
70
+ throw new Error('invalid turn');
71
+ ids.add(turn.id.toLowerCase());
72
+ if (turn.role === 'agent')
73
+ return { ...turn, role: 'agent', delivery: 'recorded' };
74
+ if (turn.delivery === 'unconfirmed' || turn.delivery === 'queued')
75
+ return { ...turn, role: 'user', delivery: turn.delivery };
76
+ throw new Error('invalid turn');
77
+ });
78
+ }
79
+ catch {
80
+ throw new PreviewChatError('unavailable', 'Conversation storage unavailable');
81
+ }
82
+ }
83
+ async save(id, turns) {
84
+ const data = JSON.stringify({ schema: 1, turns });
85
+ if (turns.length > PREVIEW_CHAT_MAX_TURNS || Buffer.byteLength(data) > PREVIEW_CHAT_STORE_BYTES)
86
+ throw new PreviewChatError('capacity_exceeded', 'Conversation is full');
87
+ const path = this.path(id);
88
+ const temp = `${path}.${randomUUID()}.tmp`;
89
+ try {
90
+ await writeFile(temp, data, { flag: 'wx', mode: 0o600 });
91
+ await rename(temp, path);
92
+ }
93
+ catch {
94
+ await rm(temp, { force: true }).catch(() => { });
95
+ throw new PreviewChatError('unavailable', 'Conversation storage unavailable');
96
+ }
97
+ }
98
+ async find(id, input, role) {
99
+ validateChatInput(input);
100
+ const found = (await this.load(id)).find(turn => turn.id.toLowerCase() === input.id.toLowerCase());
101
+ if (found && (found.role !== role || found.text !== input.text))
102
+ throw new PreviewChatError('conflict', 'Message ID already used');
103
+ return found ?? null;
104
+ }
105
+ async reserve(id, input, role) {
106
+ validateChatInput(input);
107
+ const turns = await this.load(id);
108
+ const existing = turns.find(turn => turn.id.toLowerCase() === input.id.toLowerCase());
109
+ if (existing) {
110
+ if (existing.role !== role || existing.text !== input.text)
111
+ throw new PreviewChatError('conflict', 'Message ID already used');
112
+ return existing;
113
+ }
114
+ const base = { id: input.id, text: input.text, sequence: turns.length + 1, createdAt: new Date().toISOString() };
115
+ const turn = role === 'user' ? { ...base, role, delivery: 'unconfirmed' } : { ...base, role, delivery: 'recorded' };
116
+ await this.save(id, [...turns, turn]);
117
+ return turn;
118
+ }
119
+ async markQueued(id, turnId) {
120
+ const turns = await this.load(id);
121
+ const index = turns.findIndex(turn => turn.id.toLowerCase() === turnId.toLowerCase());
122
+ if (index < 0 || turns[index].role !== 'user')
123
+ throw new PreviewChatError('unavailable', 'Conversation storage unavailable');
124
+ const turn = turns[index];
125
+ if (turn.role !== 'user')
126
+ throw new PreviewChatError('unavailable', 'Conversation storage unavailable');
127
+ const queued = { ...turn, delivery: 'queued' };
128
+ turns[index] = queued;
129
+ await this.save(id, turns);
130
+ return queued;
131
+ }
132
+ async list(id, cursor) {
133
+ const turns = await this.load(id);
134
+ const prefix = `${id}:`;
135
+ let offset = 0;
136
+ if (cursor !== undefined) {
137
+ if (!cursor.startsWith(prefix) || !/^(0|[1-9]\d*)$/.test(cursor.slice(prefix.length)))
138
+ throw new PreviewChatError('invalid', 'Invalid conversation cursor');
139
+ offset = Number(cursor.slice(prefix.length));
140
+ if (!Number.isSafeInteger(offset) || offset > turns.length)
141
+ throw new PreviewChatError('invalid', 'Invalid conversation cursor');
142
+ }
143
+ const page = { sessionId: id, turns: [], cursor: `${id}:${offset}`, hasMore: offset < turns.length };
144
+ for (const turn of turns.slice(offset, offset + PREVIEW_CHAT_PAGE_TURNS)) {
145
+ const candidate = { sessionId: id, turns: [...page.turns, turn], cursor: `${id}:${turn.sequence}`, hasMore: turn.sequence < turns.length };
146
+ if (Buffer.byteLength(JSON.stringify(candidate), 'utf8') > PREVIEW_CHAT_PAGE_BYTES)
147
+ break;
148
+ Object.assign(page, candidate);
149
+ }
150
+ if (page.hasMore && !page.turns.length)
151
+ throw new PreviewChatError('unavailable', 'Conversation page unavailable');
152
+ return page;
153
+ }
154
+ }
155
+ //# sourceMappingURL=preview-chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-chat.js","sourceRoot":"","sources":["../src/preview-chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { SessionStore } from './session-store.js';
2
+ import type { Session } from './types.js';
3
+ import type { PullRequestStatusService } from './pull-request-status.js';
4
+ import type { MailStore } from './mail-store.js';
5
+ type Timer = ReturnType<typeof setTimeout>;
6
+ interface Options {
7
+ bridgeId: string;
8
+ store: Pick<SessionStore, 'listSessions' | 'loadSession' | 'saveSession'>;
9
+ withSessionMutation: (id: string, task: () => Promise<void>) => Promise<void>;
10
+ statusService: Pick<PullRequestStatusService, 'getObservations' | 'setBackgroundUrls'>;
11
+ mailStore: Pick<MailStore, 'commitPullRequestObservation'>;
12
+ notifyCommitted: (session: Session, sender: string) => Promise<void>;
13
+ now?: () => number;
14
+ setTimeout?: (fn: () => void, ms: number) => Timer;
15
+ clearTimeout?: (timer: Timer) => void;
16
+ }
17
+ /** Polls only observed changes using the same cache and quota as dashboard reads. */
18
+ export declare class PullRequestEventWatcher {
19
+ private readonly options;
20
+ private stopped;
21
+ private started;
22
+ private epoch;
23
+ private flight?;
24
+ private timer?;
25
+ private group;
26
+ private baseline;
27
+ private cursor;
28
+ private pendingWake;
29
+ private nextDelay;
30
+ private readonly now;
31
+ private readonly setTimer;
32
+ private readonly clearTimer;
33
+ constructor(options: Options);
34
+ start(): void;
35
+ private schedule;
36
+ stop(): Promise<void>;
37
+ runOnce(): Promise<void>;
38
+ private scan;
39
+ }
40
+ export {};
@@ -0,0 +1,205 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { normalizePullRequestUrl } from './pull-request-url.js';
3
+ function canonical(value) {
4
+ if (!value)
5
+ return undefined;
6
+ try {
7
+ const url = normalizePullRequestUrl(value);
8
+ return Number(url.slice(url.lastIndexOf('/') + 1)) <= 2_147_483_647 ? url : undefined;
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ }
14
+ function eligible(session, bridgeId) {
15
+ return !!session && session.bridgeId === bridgeId && session.shellType !== 'shell' && !!canonical(session.pullRequestUrl);
16
+ }
17
+ /** Polls only observed changes using the same cache and quota as dashboard reads. */
18
+ export class PullRequestEventWatcher {
19
+ options;
20
+ stopped = false;
21
+ started = false;
22
+ epoch = 0;
23
+ flight;
24
+ timer;
25
+ group = [];
26
+ baseline = new Map();
27
+ cursor = '';
28
+ pendingWake = new Map();
29
+ nextDelay = 60_000;
30
+ now;
31
+ setTimer;
32
+ clearTimer;
33
+ constructor(options) {
34
+ this.options = options;
35
+ this.now = options.now ?? Date.now;
36
+ this.setTimer = options.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
37
+ this.clearTimer = options.clearTimeout ?? (timer => clearTimeout(timer));
38
+ }
39
+ start() {
40
+ if (this.started || this.stopped)
41
+ return;
42
+ this.started = true;
43
+ void this.runOnce().catch(error => console.error('[PR events] Scan failed:', error)).finally(() => this.schedule());
44
+ }
45
+ schedule() {
46
+ if (!this.started || this.stopped || this.timer)
47
+ return;
48
+ this.timer = this.setTimer(() => { this.timer = undefined; void this.runOnce().catch(error => console.error('[PR events] Scan failed:', error)).finally(() => this.schedule()); }, Math.max(60_000, this.nextDelay));
49
+ this.timer.unref?.();
50
+ }
51
+ async stop() {
52
+ this.stopped = true;
53
+ this.epoch++;
54
+ if (this.timer) {
55
+ this.clearTimer(this.timer);
56
+ this.timer = undefined;
57
+ }
58
+ await this.flight?.catch(() => { });
59
+ this.options.statusService.setBackgroundUrls([]);
60
+ }
61
+ runOnce() {
62
+ if (this.stopped)
63
+ return Promise.resolve();
64
+ if (this.flight)
65
+ return this.flight;
66
+ const epoch = this.epoch;
67
+ this.flight = this.scan(epoch).finally(() => { this.flight = undefined; });
68
+ return this.flight;
69
+ }
70
+ async scan(epoch) {
71
+ const { store, bridgeId, withSessionMutation, statusService, mailStore, notifyCommitted } = this.options;
72
+ const active = () => !this.stopped && this.epoch === epoch;
73
+ const links = [];
74
+ for (const candidate of await store.listSessions()) {
75
+ if (!active())
76
+ return;
77
+ if (!eligible(candidate, bridgeId))
78
+ continue;
79
+ try {
80
+ await withSessionMutation(candidate.id, async () => {
81
+ if (!active())
82
+ return;
83
+ const session = await store.loadSession(candidate.id);
84
+ if (!eligible(session, bridgeId))
85
+ return;
86
+ const url = canonical(session.pullRequestUrl);
87
+ const linkId = session.pullRequestLinkId ?? randomUUID();
88
+ if (!session.pullRequestLinkId)
89
+ await store.saveSession({ ...session, pullRequestLinkId: linkId });
90
+ links.push({ id: session.id, url, key: url.toLowerCase(), linkId });
91
+ });
92
+ }
93
+ catch {
94
+ // One damaged session must not prevent unrelated links from being scanned.
95
+ console.error(`[PR events] Could not inspect session ${candidate.id}`);
96
+ }
97
+ }
98
+ if (!active())
99
+ return;
100
+ // A committed message survives a failed wake; retry without depending on a fresh gh result.
101
+ for (const [id, pending] of this.pendingWake) {
102
+ let sessionForWake;
103
+ try {
104
+ await withSessionMutation(id, async () => {
105
+ if (!active())
106
+ return;
107
+ const session = await store.loadSession(id);
108
+ if (eligible(session, bridgeId) && canonical(session.pullRequestUrl)?.toLowerCase() === pending.key && session.pullRequestLinkId === pending.linkId)
109
+ sessionForWake = session;
110
+ });
111
+ }
112
+ catch {
113
+ // Keep the retry pending; other sessions and status groups can still progress.
114
+ console.error(`[PR events] Could not inspect session ${id}`);
115
+ continue;
116
+ }
117
+ if (!active())
118
+ return;
119
+ if (!sessionForWake) {
120
+ this.pendingWake.delete(id);
121
+ continue;
122
+ }
123
+ try {
124
+ await notifyCommitted(sessionForWake, 'FTown PR updates');
125
+ this.pendingWake.delete(id);
126
+ }
127
+ catch { /* Retry on the next scan. */ }
128
+ }
129
+ const urls = [...new Map(links.map(link => [link.key, link.url])).entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
130
+ const keys = new Set(urls.map(([key]) => key));
131
+ if (this.group.some(key => !keys.has(key))) {
132
+ this.group = [];
133
+ this.baseline.clear();
134
+ }
135
+ if (!this.group.length && urls.length) {
136
+ let start = urls.findIndex(([key]) => key > this.cursor);
137
+ if (start < 0)
138
+ start = 0;
139
+ const selected = [...urls.slice(start), ...urls.slice(0, start)].slice(0, 20);
140
+ this.group = selected.map(([key]) => key);
141
+ // Capture attempt ordinals before pinning: a quota-blocked read must not advance the group.
142
+ const before = await statusService.getObservations(selected.map(([, url]) => url));
143
+ if (!active())
144
+ return;
145
+ this.baseline = new Map(before.observations.map(item => [item.url.toLowerCase(), item.attempt]));
146
+ statusService.setBackgroundUrls(selected.map(([, url]) => url));
147
+ }
148
+ if (!this.group.length) {
149
+ statusService.setBackgroundUrls([]);
150
+ this.nextDelay = 60_000;
151
+ return;
152
+ }
153
+ const selectedUrls = this.group.map(key => urls.find(([entry]) => entry === key)?.[1]).filter((url) => !!url);
154
+ const result = await statusService.getObservations(selectedUrls);
155
+ if (!active())
156
+ return;
157
+ this.nextDelay = Math.max(60_000, Date.parse(result.nextRefreshAt) - this.now() || 60_000);
158
+ const observed = new Map(result.observations.map(item => [item.url.toLowerCase(), item]));
159
+ for (const link of links) {
160
+ const observation = observed.get(link.key);
161
+ if (!observation || observation.stale || observation.reason || !observation.checkedAt || !['in_progress', 'failed', 'passed', 'merged'].includes(observation.state))
162
+ continue;
163
+ let sessionForWake;
164
+ let attemptingCommit = false;
165
+ try {
166
+ await withSessionMutation(link.id, async () => {
167
+ if (!active())
168
+ return;
169
+ const session = await store.loadSession(link.id);
170
+ if (!eligible(session, bridgeId) || canonical(session.pullRequestUrl)?.toLowerCase() !== link.key || session.pullRequestLinkId !== link.linkId)
171
+ return;
172
+ attemptingCommit = true;
173
+ await mailStore.commitPullRequestObservation(session.id, link.linkId, observation);
174
+ if (active())
175
+ sessionForWake = session;
176
+ });
177
+ }
178
+ catch {
179
+ // A failed atomic commit may have renamed successfully before reporting an error.
180
+ if (active() && attemptingCommit)
181
+ this.pendingWake.set(link.id, { key: link.key, linkId: link.linkId });
182
+ console.error(`[PR events] Could not commit observation for session ${link.id}`);
183
+ continue;
184
+ }
185
+ if (active() && sessionForWake) {
186
+ try {
187
+ await notifyCommitted(sessionForWake, 'FTown PR updates');
188
+ this.pendingWake.delete(link.id);
189
+ }
190
+ catch {
191
+ this.pendingWake.set(link.id, { key: link.key, linkId: link.linkId });
192
+ }
193
+ }
194
+ }
195
+ if (this.group.every(key => { const observation = observed.get(key); return !!observation && ((!observation.stale && !!observation.checkedAt && !observation.reason) || observation.attempt !== (this.baseline.get(key) ?? 0)); })) {
196
+ this.cursor = this.group.at(-1);
197
+ this.group = [];
198
+ this.baseline.clear();
199
+ statusService.setBackgroundUrls([]);
200
+ // Keep discovering new links and rotating other groups at the shared quota cadence.
201
+ this.nextDelay = 60_000;
202
+ }
203
+ }
204
+ }
205
+ //# sourceMappingURL=pull-request-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull-request-events.js","sourceRoot":"","sources":["../src/pull-request-events.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,14 @@
1
- import type { PullRequestStatusesResponse } from './pull-request-status-protocol.js';
1
+ import type { PullRequestStatus, PullRequestStatusesResponse } from './pull-request-status-protocol.js';
2
2
  type RunnerResult = {
3
3
  stdout: string;
4
4
  stderr: string;
5
5
  exitCode: number;
6
6
  };
7
7
  type Runner = (input: string) => Promise<RunnerResult>;
8
+ export interface PullRequestObservation extends PullRequestStatus {
9
+ headOid?: string;
10
+ attempt: number;
11
+ }
8
12
  /** Run a single bounded gh process; credentials stay in gh's environment. */
9
13
  export declare function runGh(input: string): Promise<RunnerResult>;
10
14
  /** Process-wide, demand-driven PR status cache and quota gate. */
@@ -16,8 +20,19 @@ export declare class PullRequestStatusService {
16
20
  private cooldown;
17
21
  private flight;
18
22
  private sequence;
23
+ private attemptSequence;
24
+ private background;
25
+ private inFlight;
19
26
  constructor(now?: () => number, runner?: Runner);
27
+ private entry;
28
+ private evict;
29
+ setBackgroundUrls(urls: readonly string[]): void;
20
30
  get(urls: readonly string[]): Promise<Pick<PullRequestStatusesResponse, 'statuses' | 'nextRefreshAt'>>;
31
+ getObservations(urls: readonly string[]): Promise<{
32
+ observations: PullRequestObservation[];
33
+ nextRefreshAt: string;
34
+ }>;
35
+ private read;
21
36
  private refresh;
22
37
  }
23
38
  export {};