ftown-bridge 0.19.37 → 0.19.39

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 (89) hide show
  1. package/dist/browser-access.js +2 -1
  2. package/dist/browser-access.js.map +1 -1
  3. package/dist/centrifugo-client.d.ts +1 -4
  4. package/dist/centrifugo-client.js +0 -29
  5. package/dist/centrifugo-client.js.map +1 -1
  6. package/dist/command-rpc.d.ts +1 -3
  7. package/dist/command-rpc.js +20 -74
  8. package/dist/command-rpc.js.map +1 -1
  9. package/dist/create-ftown-session.d.ts +0 -1
  10. package/dist/create-ftown-session.js +7 -13
  11. package/dist/create-ftown-session.js.map +1 -1
  12. package/dist/ftown-home.js +2 -2
  13. package/dist/ftown-sessions-cli.js +5 -340
  14. package/dist/ftown-sessions-cli.js.map +1 -1
  15. package/dist/harness-registry.d.ts +3 -22
  16. package/dist/harness-registry.js +3 -17
  17. package/dist/harness-registry.js.map +1 -1
  18. package/dist/index.js +10 -65
  19. package/dist/index.js.map +1 -1
  20. package/dist/install-ftown-skill.d.ts +5 -0
  21. package/dist/install-ftown-skill.js +91 -9
  22. package/dist/install-ftown-skill.js.map +1 -1
  23. package/dist/local-api-server.d.ts +1 -18
  24. package/dist/local-api-server.js +10 -108
  25. package/dist/local-api-server.js.map +1 -1
  26. package/dist/provider-env-store.js +1 -1
  27. package/dist/provider-env-store.js.map +1 -1
  28. package/dist/retired-automation.d.ts +14 -0
  29. package/dist/retired-automation.js +18 -0
  30. package/dist/retired-automation.js.map +1 -0
  31. package/dist/retired-skill-assets.d.ts +2 -0
  32. package/dist/retired-skill-assets.js +21 -0
  33. package/dist/retired-skill-assets.js.map +1 -0
  34. package/dist/session-controller.d.ts +2 -1
  35. package/dist/session-controller.js +5 -1
  36. package/dist/session-controller.js.map +1 -1
  37. package/dist/session-resurrection.d.ts +8 -2
  38. package/dist/session-resurrection.js +53 -17
  39. package/dist/session-resurrection.js.map +1 -1
  40. package/dist/solo/contract.d.ts +1 -1
  41. package/dist/solo/contract.js +2 -12
  42. package/dist/solo/contract.js.map +1 -1
  43. package/dist/solo/hub-manager.js +1 -1
  44. package/dist/terminal-pump.d.ts +1 -2
  45. package/dist/terminal-pump.js +1 -2
  46. package/dist/terminal-pump.js.map +1 -1
  47. package/dist/types.d.ts +4 -112
  48. package/dist/wire-types.d.ts +4 -48
  49. package/dist/wire-types.js +2 -0
  50. package/dist/wire-types.js.map +1 -1
  51. package/package.json +1 -2
  52. package/pi-extension/API.md +4 -12
  53. package/pi-extension/ftown.js +0 -138
  54. package/skills/ftown/SKILL.md +5 -27
  55. package/skills/ftown/agents/openai.yaml +2 -2
  56. package/skills/ftown/references/orchestrator.md +10 -65
  57. package/skills/ftown/references/sessions.md +3 -16
  58. package/skills/ftown/references/workflows.md +1 -3
  59. package/dist/loop-controller.d.ts +0 -87
  60. package/dist/loop-controller.js +0 -133
  61. package/dist/loop-controller.js.map +0 -1
  62. package/dist/loop-run-store.d.ts +0 -10
  63. package/dist/loop-run-store.js +0 -153
  64. package/dist/loop-run-store.js.map +0 -1
  65. package/dist/loop-schedule.d.ts +0 -18
  66. package/dist/loop-schedule.js +0 -35
  67. package/dist/loop-schedule.js.map +0 -1
  68. package/dist/loop-scheduler.d.ts +0 -148
  69. package/dist/loop-scheduler.js +0 -543
  70. package/dist/loop-scheduler.js.map +0 -1
  71. package/dist/loop-store.d.ts +0 -38
  72. package/dist/loop-store.js +0 -153
  73. package/dist/loop-store.js.map +0 -1
  74. package/dist/loop-validation.d.ts +0 -15
  75. package/dist/loop-validation.js +0 -91
  76. package/dist/loop-validation.js.map +0 -1
  77. package/skills/factory/SKILL.md +0 -198
  78. package/skills/factory/factory-template/bin/dispatch.py +0 -478
  79. package/skills/factory/factory-template/factory.yaml +0 -43
  80. package/skills/factory/factory-template/skills/_protocol.md +0 -105
  81. package/skills/factory/factory-template/skills/design.md +0 -260
  82. package/skills/factory/factory-template/skills/digest.md +0 -78
  83. package/skills/factory/factory-template/skills/groom.md +0 -243
  84. package/skills/factory/factory-template/skills/implement.md +0 -316
  85. package/skills/factory/factory-template/skills/pr.md +0 -221
  86. package/skills/factory/factory-template/skills/qa.md +0 -226
  87. package/skills/factory/factory-template/skills/review.md +0 -232
  88. package/skills/factory/factory-template/skills/triage.md +0 -157
  89. package/skills/ftown/references/loops.md +0 -93
@@ -1,133 +0,0 @@
1
- import { createLoop as createLoopInStore, deleteLoop as deleteLoopInStore, getLoop as getLoopInStore, listLoops as listLoopsInStore, mutateLoopRuntime as mutateLoopRuntimeInStore, updateLoop as updateLoopInStore, } from './loop-store.js';
2
- import { deleteLoopRunRecords as deleteLoopRunRecordsInStore, listLoopRunRecordsWithFallback as listLoopRunRecordsWithFallbackInStore, } from './loop-run-store.js';
3
- import { validateLoopDraft, validateLoopPatch } from './loop-validation.js';
4
- const defaultLoopStore = {
5
- createLoop: createLoopInStore,
6
- getLoop: getLoopInStore,
7
- listLoops: listLoopsInStore,
8
- updateLoop: updateLoopInStore,
9
- deleteLoop: deleteLoopInStore,
10
- mutateLoopRuntime: mutateLoopRuntimeInStore,
11
- };
12
- const defaultLoopRunStore = {
13
- deleteLoopRunRecords: deleteLoopRunRecordsInStore,
14
- listLoopRunRecordsWithFallback: listLoopRunRecordsWithFallbackInStore,
15
- };
16
- export class LoopController {
17
- deps;
18
- loopStore;
19
- loopRunStore;
20
- constructor(deps) {
21
- this.deps = deps;
22
- this.loopStore = deps.loopStore ?? defaultLoopStore;
23
- this.loopRunStore = deps.loopRunStore ?? defaultLoopRunStore;
24
- }
25
- /** Create a loop owned by this bridge. bridgeId is forced to THIS bridge —
26
- * a loop is always owned by its runner, whatever the caller sent. */
27
- async create(input) {
28
- const candidate = { ...input, bridgeId: this.deps.bridgeId };
29
- const error = validateLoopDraft(candidate);
30
- if (error) {
31
- return { ok: false, code: 'invalid', message: error };
32
- }
33
- const draft = {
34
- name: candidate.name.trim(),
35
- bridgeId: this.deps.bridgeId,
36
- schedule: candidate.schedule,
37
- harness: candidate.harness,
38
- workdir: candidate.workdir,
39
- task: candidate.task,
40
- model: candidate.model,
41
- enabled: candidate.enabled,
42
- overlapPolicy: candidate.overlapPolicy,
43
- retention: candidate.retention,
44
- preflight: candidate.preflight,
45
- postflight: candidate.postflight,
46
- maxRuntimeMs: candidate.maxRuntimeMs,
47
- group: candidate.group,
48
- };
49
- try {
50
- const loop = this.loopStore.createLoop(draft);
51
- await this.deps.publishLoopUpdate(loop);
52
- return { ok: true, loop };
53
- }
54
- catch (err) {
55
- const message = err instanceof Error ? err.message : String(err);
56
- return { ok: false, code: 'failed', message };
57
- }
58
- }
59
- list() {
60
- return this.loopStore.listLoops();
61
- }
62
- get(loopId) {
63
- const loop = this.loopStore.getLoop(loopId);
64
- if (!loop) {
65
- return { ok: false, code: 'not_found', message: 'Loop not found' };
66
- }
67
- return { ok: true, loop };
68
- }
69
- async update(loopId, patch) {
70
- const error = validateLoopPatch(patch);
71
- if (error) {
72
- return { ok: false, code: 'invalid', message: error };
73
- }
74
- try {
75
- const loop = this.loopStore.updateLoop(loopId, patch);
76
- if (!loop) {
77
- return { ok: false, code: 'not_found', message: 'Loop not found' };
78
- }
79
- await this.deps.publishLoopUpdate(loop);
80
- return { ok: true, loop };
81
- }
82
- catch (err) {
83
- const message = err instanceof Error ? err.message : String(err);
84
- return { ok: false, code: 'failed', message };
85
- }
86
- }
87
- async delete(loopId) {
88
- const existingLoop = this.loopStore.getLoop(loopId);
89
- const removed = this.loopStore.deleteLoop(loopId);
90
- if (removed) {
91
- // Stop any in-flight run and drop scheduler tracking so a just-deleted
92
- // loop never leaves a live AI session with nothing left to finalize/prune it.
93
- if (existingLoop)
94
- this.deps.scheduler.onLoopDeleted(existingLoop);
95
- this.loopRunStore.deleteLoopRunRecords(loopId);
96
- await this.deps.publishLoopRemoved(loopId);
97
- }
98
- return { removed };
99
- }
100
- async runNow(loopId) {
101
- const loop = this.loopStore.getLoop(loopId);
102
- if (!loop) {
103
- return { fired: false, reason: 'not_found' };
104
- }
105
- // A skip-policy loop with a live run cannot be manually fired either —
106
- // report overlap synchronously (the async tick would otherwise swallow it).
107
- if (loop.overlapPolicy === 'skip' &&
108
- loop.lastStatus === 'running' &&
109
- loop.lastSessionId &&
110
- this.deps.isSessionRunning(loop.lastSessionId)) {
111
- return { fired: false, reason: 'overlap' };
112
- }
113
- // Reload-check-write via mutateLoopRuntime: if the loop was deleted
114
- // between the getLoop() above and this write, this returns null and
115
- // nothing is written/published — a stale in-memory snapshot must never
116
- // be upserted back, or a deleted loop resurrects.
117
- const updated = this.loopStore.mutateLoopRuntime(loopId, (l) => {
118
- l.runNowRequested = true;
119
- l.updatedAt = new Date().toISOString();
120
- });
121
- if (!updated) {
122
- return { fired: false, reason: 'not_found' };
123
- }
124
- await this.deps.publishLoopUpdate(updated);
125
- this.deps.scheduler.kick();
126
- return { fired: true, loop: updated };
127
- }
128
- async runs(loopId) {
129
- const sessions = await this.deps.listWireSessions();
130
- return this.loopRunStore.listLoopRunRecordsWithFallback(loopId, sessions, (sessionId) => this.deps.loadTerminalLog(sessionId));
131
- }
132
- }
133
- //# sourceMappingURL=loop-controller.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loop-controller.js","sourceRoot":"","sources":["../src/loop-controller.ts"],"names":[],"mappings":"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"}
@@ -1,10 +0,0 @@
1
- import type { Loop, LoopRunRecord, Session } from './types.js';
2
- /** Inject the instance ".ftown home" once at startup (see above). */
3
- export declare function configureLoopRunStoreHome(home: string | undefined): void;
4
- export declare function listLoopRunRecords(loopId: string): LoopRunRecord[];
5
- export declare function getLoopRunRecord(runId: string): LoopRunRecord | undefined;
6
- export declare function upsertLoopRunRecord(record: LoopRunRecord): LoopRunRecord;
7
- export declare function deleteLoopRunRecords(loopId: string): void;
8
- export declare function pruneLoopRunRecords(loopId: string, keep: number | null, preserveIds?: Iterable<string | undefined>): void;
9
- export declare function recordForSession(loop: Loop, session: Session, startedAt?: string): LoopRunRecord;
10
- export declare function listLoopRunRecordsWithFallback(loopId: string, sessions: Session[], loadLog?: (sessionId: string) => Promise<string>): Promise<LoopRunRecord[]>;
@@ -1,153 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { isLoopHarness } from './harness-registry.js';
5
- /**
6
- * Instance ".ftown home" for `loop-runs.json`, injected once at bridge startup
7
- * via `configureLoopRunStoreHome()` (index.ts passes `resolveFtownHome(dataDir)`):
8
- * the DEFAULT data dir keeps `$HOME/.ftown`, a non-default `--data-dir` gets its
9
- * own home so a co-resident bridge's loop-runs.json is never touched. Unconfigured
10
- * (unit tests / default install) it falls back to `join(homedir(), '.ftown')`
11
- * resolved at call time — a test's $HOME override still redirects every read/write.
12
- */
13
- let configuredFtownHome;
14
- /** Inject the instance ".ftown home" once at startup (see above). */
15
- export function configureLoopRunStoreHome(home) {
16
- configuredFtownHome = home;
17
- }
18
- function ftownHome() {
19
- return configuredFtownHome ?? join(homedir(), '.ftown');
20
- }
21
- function runStorePath() {
22
- return join(ftownHome(), 'loop-runs.json');
23
- }
24
- /** Legacy 'skipped' run records were persisted before skip diagnostics moved onto the
25
- * Loop object (lastSkipAt/lastSkipReason). Filtering them out here — on every load —
26
- * shrinks existing bloated loop-runs.json files as soon as the store is next saved. */
27
- function loadFile() {
28
- try {
29
- const path = runStorePath();
30
- if (!existsSync(path))
31
- return { runs: [] };
32
- const parsed = JSON.parse(readFileSync(path, 'utf8'));
33
- const runs = Array.isArray(parsed.runs) ? parsed.runs : [];
34
- return { runs: runs.filter((run) => run.status !== 'skipped') };
35
- }
36
- catch {
37
- return { runs: [] };
38
- }
39
- }
40
- function saveFile(data) {
41
- mkdirSync(ftownHome(), { recursive: true, mode: 0o700 });
42
- const path = runStorePath();
43
- const tmp = `${path}.tmp`;
44
- writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
45
- renameSync(tmp, path);
46
- }
47
- export function listLoopRunRecords(loopId) {
48
- return loadFile()
49
- .runs.filter((run) => run.loopId === loopId)
50
- .sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
51
- }
52
- export function getLoopRunRecord(runId) {
53
- return loadFile().runs.find((run) => run.id === runId || run.sessionId === runId);
54
- }
55
- export function upsertLoopRunRecord(record) {
56
- const data = loadFile();
57
- const index = data.runs.findIndex((run) => run.id === record.id);
58
- if (index === -1)
59
- data.runs.push(record);
60
- else
61
- data.runs[index] = record;
62
- saveFile(data);
63
- return record;
64
- }
65
- export function deleteLoopRunRecords(loopId) {
66
- const data = loadFile();
67
- const remaining = data.runs.filter((run) => run.loopId !== loopId);
68
- if (remaining.length === data.runs.length)
69
- return;
70
- data.runs = remaining;
71
- saveFile(data);
72
- }
73
- export function pruneLoopRunRecords(loopId, keep, preserveIds = []) {
74
- if (keep == null)
75
- return;
76
- const preserve = new Set([...preserveIds].filter((id) => Boolean(id)));
77
- const data = loadFile();
78
- const loopRuns = data.runs
79
- .filter((run) => run.loopId === loopId)
80
- .sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
81
- const kept = new Set();
82
- for (const run of loopRuns) {
83
- if (preserve.has(run.id) || (run.sessionId && preserve.has(run.sessionId)))
84
- kept.add(run.id);
85
- }
86
- for (const run of loopRuns) {
87
- if (kept.size >= keep && !kept.has(run.id))
88
- continue;
89
- kept.add(run.id);
90
- }
91
- data.runs = data.runs.filter((run) => run.loopId !== loopId || kept.has(run.id));
92
- saveFile(data);
93
- }
94
- export function recordForSession(loop, session, startedAt = session.createdAt) {
95
- return {
96
- id: session.id,
97
- loopId: loop.id,
98
- bridgeId: loop.bridgeId,
99
- sessionId: session.id,
100
- name: session.name,
101
- status: session.status === 'error' ? 'error' : session.status === 'completed' ? 'ok' : 'running',
102
- startedAt,
103
- updatedAt: new Date().toISOString(),
104
- harness: loop.harness,
105
- workdir: session.workingDir ?? loop.workdir,
106
- task: session.prompt ?? loop.task,
107
- model: session.model ?? loop.model,
108
- sessionStatus: session.status,
109
- errorReason: session.errorReason,
110
- };
111
- }
112
- function fallbackRecord(loopId, session) {
113
- return {
114
- id: session.id,
115
- loopId,
116
- bridgeId: session.bridgeId,
117
- sessionId: session.id,
118
- name: session.name,
119
- status: session.status === 'error' ? 'error' : session.status === 'completed' ? 'ok' : 'running',
120
- startedAt: session.createdAt,
121
- updatedAt: session.updatedAt,
122
- harness: isLoopHarness(session.shellType) ? session.shellType : undefined,
123
- workdir: session.workingDir,
124
- task: session.prompt,
125
- model: session.model,
126
- sessionStatus: session.status,
127
- errorReason: session.errorReason,
128
- };
129
- }
130
- export async function listLoopRunRecordsWithFallback(loopId, sessions, loadLog) {
131
- const records = listLoopRunRecords(loopId);
132
- const seenSessionIds = new Set(records.map((run) => run.sessionId).filter(Boolean));
133
- const seenIds = new Set(records.map((run) => run.id));
134
- for (const session of sessions) {
135
- if (session.loopId !== loopId)
136
- continue;
137
- if (seenIds.has(session.id) || seenSessionIds.has(session.id))
138
- continue;
139
- const record = fallbackRecord(loopId, session);
140
- if (loadLog && (session.status === 'completed' || session.status === 'error')) {
141
- const log = await loadLog(session.id);
142
- const tail = log.length > 65_536 ? log.slice(-65_536) : log;
143
- record.logTail = tail;
144
- record.logBytes = Buffer.byteLength(log, 'utf8');
145
- record.logTruncated = Buffer.byteLength(log, 'utf8') > Buffer.byteLength(tail, 'utf8');
146
- record.finishedAt = session.updatedAt;
147
- record.durationMs = Math.max(0, Date.parse(session.updatedAt) - Date.parse(session.createdAt));
148
- }
149
- records.push(record);
150
- }
151
- return records.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
152
- }
153
- //# sourceMappingURL=loop-run-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loop-run-store.js","sourceRoot":"","sources":["../src/loop-run-store.ts"],"names":[],"mappings":"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"}
@@ -1,18 +0,0 @@
1
- import type { Loop, LoopSchedule } from './types.js';
2
- /**
3
- * Epoch ms of the next fire strictly after fromMs.
4
- *
5
- * - interval: fromMs + everyMs, floored at a 1000ms minimum cadence so a
6
- * misconfigured sub-second loop cannot busy-spin the scheduler.
7
- * - cron: the next occurrence strictly after `fromMs` in the loop's timezone.
8
- * Throws on a malformed cron expression (callers validate before persisting).
9
- */
10
- export declare function computeNextRun(schedule: LoopSchedule, fromMs: number): number;
11
- /**
12
- * Whether a loop should fire on a tick at `nowMs`.
13
- *
14
- * A manual run request bypasses both the enabled flag and the schedule (it is a
15
- * one-shot override cleared on the next fire). Otherwise the loop must be
16
- * enabled, have a computed nextRunAt, and that target must be at or before now.
17
- */
18
- export declare function isDue(loop: Loop, nowMs: number): boolean;
@@ -1,35 +0,0 @@
1
- import cronParser from 'cron-parser';
2
- /**
3
- * Epoch ms of the next fire strictly after fromMs.
4
- *
5
- * - interval: fromMs + everyMs, floored at a 1000ms minimum cadence so a
6
- * misconfigured sub-second loop cannot busy-spin the scheduler.
7
- * - cron: the next occurrence strictly after `fromMs` in the loop's timezone.
8
- * Throws on a malformed cron expression (callers validate before persisting).
9
- */
10
- export function computeNextRun(schedule, fromMs) {
11
- if (schedule.kind === 'interval')
12
- return fromMs + Math.max(1000, schedule.everyMs);
13
- const it = cronParser.parseExpression(schedule.expression, {
14
- currentDate: new Date(fromMs),
15
- tz: schedule.tz,
16
- });
17
- return it.next().toDate().getTime();
18
- }
19
- /**
20
- * Whether a loop should fire on a tick at `nowMs`.
21
- *
22
- * A manual run request bypasses both the enabled flag and the schedule (it is a
23
- * one-shot override cleared on the next fire). Otherwise the loop must be
24
- * enabled, have a computed nextRunAt, and that target must be at or before now.
25
- */
26
- export function isDue(loop, nowMs) {
27
- if (loop.runNowRequested)
28
- return true; // manual fire bypasses enabled + schedule
29
- if (!loop.enabled)
30
- return false;
31
- if (!loop.nextRunAt)
32
- return false;
33
- return Date.parse(loop.nextRunAt) <= nowMs;
34
- }
35
- //# sourceMappingURL=loop-schedule.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loop-schedule.js","sourceRoot":"","sources":["../src/loop-schedule.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAIrC;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,QAAsB,EAAE,MAAc;IACnE,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACnF,MAAM,EAAE,GAAG,UAAU,CAAC,eAAe,CAAC,QAAQ,CAAC,UAAU,EAAE;QACzD,WAAW,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC;QAC7B,EAAE,EAAE,QAAQ,CAAC,EAAE;KAChB,CAAC,CAAC;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC;AACtC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,IAAU,EAAE,KAAa;IAC7C,IAAI,IAAI,CAAC,eAAe;QAAE,OAAO,IAAI,CAAC,CAAC,0CAA0C;IACjF,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,CAAC,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC;AAC7C,CAAC"}
@@ -1,148 +0,0 @@
1
- import { type LoopRuntimeMutator } from './loop-store.js';
2
- import type { CreateFtownSessionInput } from './create-ftown-session.js';
3
- import type { RemoveFtownSessionOptions } from './remove-ftown-session.js';
4
- import type { Loop, LoopRunRecord, Session } from './types.js';
5
- /** Base tick cadence; also the finalize grace so a just-spawned PTY is not mistaken for exited. */
6
- export declare const LOOP_TICK_INTERVAL_MS = 30000;
7
- export interface FlightResult {
8
- stdout: string;
9
- stderr: string;
10
- exitCode: number;
11
- }
12
- /** Preflight/postflight primitive: promisified child_process.exec (captures exit code + timeout). */
13
- export type RunFlight = (command: string, cwd: string | undefined, timeoutMs?: number, extraEnv?: Record<string, string>) => Promise<FlightResult>;
14
- /** In-process flight spawn — wraps createFtownSession(sessionDeps, input) in index.ts. */
15
- export type SpawnSession = (input: CreateFtownSessionInput) => Promise<Session>;
16
- /** In-process run removal — wraps removeFtownSession({store,runner,centrifugo,userId}, id, opts) in index.ts. */
17
- export type RemoveSession = (id: string, options?: RemoveFtownSessionOptions) => Promise<Session | null>;
18
- export interface SchedulerStore {
19
- loadSession(id: string): Promise<Session | null>;
20
- loadTerminalLog(id: string): Promise<string>;
21
- listSessions(): Promise<Session[]>;
22
- }
23
- export interface SchedulerRunner {
24
- isRunning(id: string): boolean;
25
- stop(id: string): boolean;
26
- }
27
- export interface SchedulerCentrifugo {
28
- publishLoopUpdate(userId: string, loop: Loop): Promise<void>;
29
- }
30
- export interface LoopStoreApi {
31
- listLoops(): Loop[];
32
- /** Fresh-read → mutate scheduler-owned fields → save; null when deleted concurrently. */
33
- mutateLoopRuntime(id: string, fn: LoopRuntimeMutator): Loop | null;
34
- }
35
- export interface LoopRunRecordStoreApi {
36
- upsertLoopRunRecord(record: LoopRunRecord): LoopRunRecord;
37
- pruneLoopRunRecords(loopId: string, keep: number | null, preserveIds?: Iterable<string | undefined>): void;
38
- }
39
- export interface SchedulerDeps {
40
- store: SchedulerStore;
41
- runner: SchedulerRunner;
42
- centrifugo: SchedulerCentrifugo;
43
- userId: string;
44
- /** Built in index.ts as (input) => createFtownSession(sessionDeps, input) — the direct in-process call. */
45
- spawnSession: SpawnSession;
46
- /** Built in index.ts as (id, opts) => removeFtownSession({store,runner,centrifugo,userId}, id, opts). */
47
- removeSession: RemoveSession;
48
- /** Loop persistence. Defaults to the real ~/.ftown/loops.json store. */
49
- loops?: LoopStoreApi;
50
- /** Durable loop-run log persistence. Defaults to the real ~/.ftown/loop-runs.json store. */
51
- runRecords?: LoopRunRecordStoreApi;
52
- /** Flight runner. Defaults to the exec-based runFlightCommand. */
53
- runFlight?: RunFlight;
54
- /** Clock seam. Defaults to Date.now. */
55
- now?: () => number;
56
- }
57
- /**
58
- * Runs child_process.exec and normalizes the result to { stdout, stderr,
59
- * exitCode }. Never rejects — the exit code is the signal (a timeout maps to
60
- * 124, any other failure to the real exit code or 1).
61
- *
62
- * The hard budget is enforced by SIGKILL on the whole process GROUP, NOT by
63
- * exec's built-in `timeout`. exec's timeout only sends SIGTERM to the spawned
64
- * `/bin/sh`; a detached grandchild that keeps the stdout pipe open, or a child
65
- * that traps SIGTERM, would keep the exec callback from ever firing — and since
66
- * the scheduler awaits every flight inside a single re-entrancy-guarded tick,
67
- * ONE such flight would wedge the entire scheduler permanently. Spawning
68
- * `detached` makes the child its own group leader, so `kill(-pid, SIGKILL)`
69
- * takes down the whole tree and the flight can never exceed its budget.
70
- */
71
- export declare function runFlightCommand(command: string, cwd: string | undefined, timeoutMs?: number, extraEnv?: Record<string, string>): Promise<FlightResult>;
72
- /**
73
- * The scheduled-loops engine. On each 30s tick it FINALIZES each loop's
74
- * in-flight run(s) (Phase A) before deciding whether to FIRE a new one
75
- * (Phase B). All side effects go through injected collaborators so it is
76
- * unit-testable without a live bridge, real fs or real timers (mirrors
77
- * workflow-runner.ts).
78
- *
79
- * Persistence rule: the scheduler NEVER writes a whole detached Loop back
80
- * across an await. Every runtime-field change goes through
81
- * store.mutateLoopRuntime (fresh-read → mutate → save), so a loop deleted or
82
- * user-edited during a long flight is neither resurrected nor clobbered.
83
- */
84
- export declare class LoopScheduler {
85
- private readonly store;
86
- private readonly runner;
87
- private readonly centrifugo;
88
- private readonly userId;
89
- private readonly spawnSession;
90
- private readonly removeSession;
91
- private readonly loops;
92
- private readonly runRecords;
93
- private readonly runFlight;
94
- private readonly now;
95
- /** Re-entrancy guard: tick N+1 never overlaps N. */
96
- private tickRunning;
97
- /** Set once start() runs (after reconcileOnStart). kick() no-ops before this so an
98
- * early run_loop_now cannot trigger an un-reconciled tick that stampedes overdue loops. */
99
- private started;
100
- /** Per-loop in-memory fire lock, shared by tick + run_loop_now/kick. */
101
- private readonly firingLoops;
102
- /** loopId -> (runSessionId -> fire-time ms). Every run THIS process spawned, so under
103
- * overlapPolicy:'allow' each concurrent run is finalized/postflighted/maxRuntime-checked
104
- * independently — not just the newest. Rebuilt lazily from the persisted primary on restart. */
105
- private readonly inFlight;
106
- private timer;
107
- constructor(deps: SchedulerDeps);
108
- start(): void;
109
- stop(): void;
110
- /** Immediate, guarded, out-of-band tick (used by run_loop_now). No-op until start()
111
- * has run, so a kick that races startup cannot fire before reconcileOnStart. */
112
- kick(): void;
113
- /** Drop scheduler tracking for a deleted loop and stop any run it left alive, so a
114
- * just-deleted loop never leaks a live AI session with nothing left to finalize it. */
115
- onLoopDeleted(loop: Loop): void;
116
- /**
117
- * Missed-schedule policy, run once before the first tick: for every loop whose
118
- * nextRunAt is missing or already past, recompute it from now (skip missed
119
- * occurrences; never stampede overdue loops). runNowRequested is preserved so a
120
- * manual override survives a restart and still fires on the first tick. A loop
121
- * with a corrupt persisted schedule is skipped here (logged) and reported as an
122
- * error on its first fire.
123
- */
124
- reconcileOnStart(now?: number): Promise<void>;
125
- tick(now?: number): Promise<void>;
126
- private processLoop;
127
- /** Phase A: finalize each in-flight run once its PTY is confirmed gone (past grace)
128
- * or over its per-run maxRuntime budget. Under 'allow' this walks every tracked run,
129
- * not just the newest, so none is orphaned. */
130
- private finalizePhase;
131
- /** Seed the persisted primary run into in-memory tracking after a restart (when this
132
- * process has spawned nothing yet for the loop), so a run left 'running' by a prior
133
- * process is still finalized. */
134
- private ensureTracked;
135
- private track;
136
- /** Phase B: fire the loop if due, honoring the per-loop lock and the overlap policy. */
137
- private firePhase;
138
- /** Advance the schedule up front (so failures/skips never stampede), then preflight → flight. */
139
- private fireLoop;
140
- /** Resolve one finished run to ok/error, update the loop badge (only if this is the
141
- * loop's tracked/latest run), then run postflight + retention for it. */
142
- private finalizeRun;
143
- private runPostflight;
144
- /** Keep the newest N run-sessions for this loop; prune older finished ones. */
145
- private pruneRuns;
146
- private persist;
147
- private publish;
148
- }