ferrycat 0.3.2 → 0.4.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/daemon.js ADDED
@@ -0,0 +1,372 @@
1
+ /**
2
+ * FerryCat Controlled Daemon (Phase 3 Step 1, ADR-0005 Hardened).
3
+ *
4
+ * Provides a production-grade, background-ready execution orchestrator:
5
+ * - Short-transaction CAS leases on `daemon_locks` table.
6
+ * - Cryptographically random per-process owner token (prevents split-brain on identical configs).
7
+ * - Automatic process tree termination on lease loss (CK-04/05).
8
+ * - Safe heartbeat intervals clamped to <= lease_ttl_sec / 2.
9
+ * - Robust heartbeat exception recovery.
10
+ * - Declarative handler execution with args and environment forwarding.
11
+ * - Graceful shutdown on SIGINT/SIGTERM.
12
+ */
13
+ import crypto from 'node:crypto';
14
+ import fs from 'node:fs';
15
+ import os from 'node:os';
16
+ import path from 'node:path';
17
+ import { FerryCatEngine, expandTilde } from './engine.js';
18
+ import { FerryCatConfigFileSchema, } from './schemas.js';
19
+ import { executeTaskAndFulfill, terminateActiveChildrenSync } from './supervisor.js';
20
+ export function loadFerryCatConfig(customConfigPath) {
21
+ const candidatePaths = [];
22
+ if (customConfigPath) {
23
+ candidatePaths.push(path.resolve(expandTilde(customConfigPath)));
24
+ }
25
+ if (process.env.FERRYCAT_CONFIG) {
26
+ candidatePaths.push(path.resolve(expandTilde(process.env.FERRYCAT_CONFIG)));
27
+ }
28
+ candidatePaths.push(path.resolve(process.cwd(), 'ferrycat.config.json'));
29
+ candidatePaths.push(path.join(os.homedir(), '.ferrycat', 'config.json'));
30
+ for (const candidate of candidatePaths) {
31
+ if (fs.existsSync(candidate)) {
32
+ try {
33
+ const raw = fs.readFileSync(candidate, 'utf8');
34
+ const parsed = JSON.parse(raw);
35
+ const validated = FerryCatConfigFileSchema.parse(parsed);
36
+ return { config: validated, resolvedPath: candidate };
37
+ }
38
+ catch (err) {
39
+ throw new Error(`Failed to parse FerryCat configuration file at '${candidate}': ${err instanceof Error ? err.message : String(err)}`);
40
+ }
41
+ }
42
+ }
43
+ // Return empty valid default configuration if no file exists
44
+ return {
45
+ config: FerryCatConfigFileSchema.parse({}),
46
+ resolvedPath: null,
47
+ };
48
+ }
49
+ export function notifyOperator(title, message) {
50
+ try {
51
+ const timestamp = new Date().toISOString();
52
+ // Always emit structured line to stderr/stdout
53
+ process.stderr.write(`[FerryCat Daemon Notice] ${timestamp} | ${title}: ${message}\n`);
54
+ }
55
+ catch (err) {
56
+ if (err && typeof err === 'object' && 'code' in err && err.code === 'EPIPE') {
57
+ // EPIPE indicates downstream logger closed its pipe early; safe to swallow silently
58
+ return;
59
+ }
60
+ }
61
+ }
62
+ export class FerryCatDaemon {
63
+ engine;
64
+ config;
65
+ configPath;
66
+ lockId;
67
+ leaseTtlSec;
68
+ heartbeatIntervalMs;
69
+ pollIntervalMs;
70
+ ownerToken;
71
+ targetAgentFilter;
72
+ notifyEnabled;
73
+ isRunning = false;
74
+ isLeader = false;
75
+ heartbeatTimer = null;
76
+ pollTimer = null;
77
+ signalCleanupRegistered = false;
78
+ activeInFlightContract = null;
79
+ constructor(options = {}) {
80
+ const { config, resolvedPath } = loadFerryCatConfig(options.configPath);
81
+ this.config = config;
82
+ this.configPath = resolvedPath;
83
+ this.engine = new FerryCatEngine({ dbPath: options.dbPath });
84
+ this.lockId = options.lockId ?? config.daemon.lock_id ?? 'global';
85
+ // Ensure lease TTL is at least 5 seconds
86
+ const reqTtl = options.leaseTtlSec ?? config.daemon.lease_ttl_sec ?? 30;
87
+ this.leaseTtlSec = Math.max(5, reqTtl);
88
+ // Enforce heartbeat interval <= leaseTtl / 2 to prevent false expirations
89
+ const reqHeartbeat = options.heartbeatIntervalMs ?? config.daemon.heartbeat_interval_ms ?? 5000;
90
+ const maxHeartbeat = Math.floor((this.leaseTtlSec * 1000) / 2);
91
+ this.heartbeatIntervalMs = Math.min(reqHeartbeat, maxHeartbeat);
92
+ this.pollIntervalMs = options.pollIntervalMs ?? config.daemon.poll_interval_ms ?? 1000;
93
+ // Critical Finding 1: Never accept a static shared owner_token across daemon processes.
94
+ // Always generate a unique cryptographic token per runtime instance.
95
+ this.ownerToken = `daemon-${process.pid}-${crypto.randomBytes(8).toString('hex')}`;
96
+ this.targetAgentFilter = options.targetAgent;
97
+ this.notifyEnabled = options.notify ?? config.daemon.notify ?? true;
98
+ }
99
+ registerSignalHandlers() {
100
+ if (this.signalCleanupRegistered)
101
+ return;
102
+ this.signalCleanupRegistered = true;
103
+ const cleanup = (sig) => {
104
+ if (this.notifyEnabled) {
105
+ notifyOperator('Shutdown', `Received ${sig}, terminating children and releasing lease...`);
106
+ }
107
+ this.stop();
108
+ terminateActiveChildrenSync();
109
+ this.engine.releaseDaemonLease({ lock_id: this.lockId, owner_token: this.ownerToken });
110
+ this.engine.close();
111
+ process.exit(0);
112
+ };
113
+ process.once('SIGINT', () => cleanup('SIGINT'));
114
+ process.once('SIGTERM', () => cleanup('SIGTERM'));
115
+ }
116
+ acquireLease() {
117
+ try {
118
+ const res = this.engine.acquireDaemonLease({
119
+ lock_id: this.lockId,
120
+ owner_token: this.ownerToken,
121
+ ttl_sec: this.leaseTtlSec,
122
+ metadata: {
123
+ pid: process.pid,
124
+ hostname: os.hostname(),
125
+ platform: process.platform,
126
+ config_path: this.configPath,
127
+ },
128
+ });
129
+ if (res.ok) {
130
+ this.isLeader = true;
131
+ return true;
132
+ }
133
+ this.isLeader = false;
134
+ return false;
135
+ }
136
+ catch (err) {
137
+ this.isLeader = false;
138
+ if (this.notifyEnabled) {
139
+ notifyOperator('Lease Error', `Acquire lease error: ${err instanceof Error ? err.message : String(err)}`);
140
+ }
141
+ return false;
142
+ }
143
+ }
144
+ renewLease() {
145
+ if (!this.isLeader)
146
+ return false;
147
+ try {
148
+ const res = this.engine.renewDaemonLease({
149
+ lock_id: this.lockId,
150
+ owner_token: this.ownerToken,
151
+ ttl_sec: this.leaseTtlSec,
152
+ });
153
+ if (!res.ok) {
154
+ // Critical WorkBuddy P1 Fix: When lease is lost, don't kill active contract to FAILED.
155
+ // First voluntarily release in-flight contract back to queue tail so new leader can claim it!
156
+ this.isLeader = false;
157
+ if (this.activeInFlightContract) {
158
+ try {
159
+ this.engine.releaseContract({
160
+ contract_id: this.activeInFlightContract.contract_id,
161
+ agent_id: this.activeInFlightContract.target_agent,
162
+ claim_token: this.activeInFlightContract.claim_token,
163
+ reason: 'DAEMON_LEASE_LOST',
164
+ });
165
+ }
166
+ catch {
167
+ // best-effort release
168
+ }
169
+ this.activeInFlightContract = null;
170
+ }
171
+ terminateActiveChildrenSync();
172
+ if (this.notifyEnabled) {
173
+ notifyOperator('Lease Lost', `Failed to renew lease '${this.lockId}': ${res.message}. In-flight contract released and children terminated.`);
174
+ }
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+ catch (err) {
180
+ // WorkBuddy Finding: On SQLite/IO exception, demote to non-leader, backoff, and release in-flight contract
181
+ this.isLeader = false;
182
+ if (this.activeInFlightContract) {
183
+ try {
184
+ this.engine.releaseContract({
185
+ contract_id: this.activeInFlightContract.contract_id,
186
+ agent_id: this.activeInFlightContract.target_agent,
187
+ claim_token: this.activeInFlightContract.claim_token,
188
+ reason: 'DAEMON_HEARTBEAT_EXCEPTION',
189
+ });
190
+ }
191
+ catch {
192
+ // best-effort release
193
+ }
194
+ this.activeInFlightContract = null;
195
+ }
196
+ terminateActiveChildrenSync();
197
+ if (this.notifyEnabled) {
198
+ notifyOperator('Lease Error', `Heartbeat exception: ${err instanceof Error ? err.message : String(err)}. Contract released and children terminated.`);
199
+ }
200
+ return false;
201
+ }
202
+ }
203
+ releaseLease() {
204
+ if (this.isLeader) {
205
+ try {
206
+ this.engine.releaseDaemonLease({
207
+ lock_id: this.lockId,
208
+ owner_token: this.ownerToken,
209
+ });
210
+ }
211
+ catch {
212
+ // best-effort cleanup on shutdown
213
+ }
214
+ this.isLeader = false;
215
+ }
216
+ }
217
+ resolveHandler(targetAgent, taskType) {
218
+ const agentConfig = this.config.agents[targetAgent];
219
+ if (!agentConfig)
220
+ return null;
221
+ const typedTask = taskType;
222
+ if (agentConfig.handlers && agentConfig.handlers[typedTask]) {
223
+ return agentConfig.handlers[typedTask];
224
+ }
225
+ if (agentConfig.default_handler) {
226
+ return agentConfig.default_handler;
227
+ }
228
+ return null;
229
+ }
230
+ /**
231
+ * Process a single cycle: check lease, poll task, execute configured handler if matched.
232
+ */
233
+ async pollAndDispatchOnce() {
234
+ if (!this.isLeader) {
235
+ const acquired = this.acquireLease();
236
+ if (!acquired) {
237
+ return { processed: false, reason: 'LEASE_NOT_HELD' };
238
+ }
239
+ }
240
+ // Determine target agent(s) to poll
241
+ const targetAgentsToPoll = this.targetAgentFilter
242
+ ? [this.targetAgentFilter]
243
+ : Object.keys(this.config.agents);
244
+ if (targetAgentsToPoll.length === 0) {
245
+ // If no configured agents, poll without target agent filter
246
+ return this.pollAgentQueue(undefined);
247
+ }
248
+ for (const agent of targetAgentsToPoll) {
249
+ const res = await this.pollAgentQueue(agent);
250
+ if (res.processed) {
251
+ return res;
252
+ }
253
+ }
254
+ return { processed: false, reason: 'NO_TASK_AVAILABLE' };
255
+ }
256
+ async pollAgentQueue(agentId) {
257
+ // Determine configured task types for this agent to avoid claiming unhandled tasks (WorkBuddy P1)
258
+ let eligibleTaskTypes;
259
+ if (agentId && this.config.agents[agentId]) {
260
+ const agentCfg = this.config.agents[agentId];
261
+ if (!agentCfg.default_handler && agentCfg.handlers) {
262
+ eligibleTaskTypes = Object.keys(agentCfg.handlers);
263
+ if (eligibleTaskTypes.length === 0) {
264
+ // If agent has no handlers and no default_handler, skip polling this agent completely
265
+ return { processed: false, reason: 'NO_HANDLERS_CONFIGURED' };
266
+ }
267
+ }
268
+ }
269
+ const pollResult = this.engine.pollNextTask({
270
+ agent_id: agentId,
271
+ task_types: eligibleTaskTypes,
272
+ });
273
+ if (!pollResult.has_task || !pollResult.contract || pollResult.status !== 'CLAIMED') {
274
+ return { processed: false, reason: 'NO_TASK_AVAILABLE' };
275
+ }
276
+ const contract = pollResult.contract;
277
+ const handler = this.resolveHandler(contract.target_agent, contract.task_type);
278
+ if (!handler) {
279
+ // Fallback: If somehow claimed without handler (e.g. default_handler was missing), release back to queue cleanly
280
+ const warningMsg = `No handler configured in ferrycat.config.json for agent '${contract.target_agent}' task '${contract.task_type}'. Releasing contract back to queue.`;
281
+ if (this.notifyEnabled) {
282
+ notifyOperator('Unhandled Task', warningMsg);
283
+ }
284
+ this.engine.releaseContract({
285
+ contract_id: contract.contract_id,
286
+ agent_id: contract.target_agent,
287
+ claim_token: contract.claim_token,
288
+ reason: `NO_HANDLER_CONFIGURED: Task type '${contract.task_type}' not handled by this node.`,
289
+ });
290
+ return {
291
+ processed: false,
292
+ contract_id: contract.contract_id,
293
+ reason: 'NO_HANDLER_CONFIGURED',
294
+ };
295
+ }
296
+ if (this.notifyEnabled) {
297
+ notifyOperator('Task Claimed', `Agent '${contract.target_agent}' executing contract ${contract.contract_id} (${contract.task_type}: "${contract.title}") via ${handler.exec_file}`);
298
+ }
299
+ // Track active in-flight contract for graceful lease loss failover
300
+ this.activeInFlightContract = {
301
+ contract_id: contract.contract_id,
302
+ target_agent: contract.target_agent,
303
+ claim_token: contract.claim_token,
304
+ };
305
+ try {
306
+ // Forward configured args and pass_env (contract's own exec_ttl_sec is authoritative)
307
+ const execResult = await executeTaskAndFulfill(this.engine, contract, {
308
+ execFile: handler.exec_file,
309
+ passEnv: handler.pass_env,
310
+ args: handler.args,
311
+ });
312
+ if (this.notifyEnabled) {
313
+ notifyOperator('Task Finished', `Contract ${contract.contract_id} concluded with status '${execResult.status}' (ok=${execResult.ok})`);
314
+ }
315
+ return {
316
+ processed: true,
317
+ contract_id: contract.contract_id,
318
+ status: execResult.status,
319
+ };
320
+ }
321
+ finally {
322
+ this.activeInFlightContract = null;
323
+ }
324
+ }
325
+ async start() {
326
+ this.registerSignalHandlers();
327
+ this.isRunning = true;
328
+ // Heartbeat ticker loop for lease renewal
329
+ this.heartbeatTimer = setInterval(() => {
330
+ try {
331
+ if (this.isRunning && this.isLeader) {
332
+ this.renewLease();
333
+ }
334
+ }
335
+ catch (err) {
336
+ if (this.notifyEnabled) {
337
+ notifyOperator('Heartbeat Ticker Error', err instanceof Error ? err.message : String(err));
338
+ }
339
+ }
340
+ }, this.heartbeatIntervalMs);
341
+ // Main polling loop
342
+ const tick = async () => {
343
+ if (!this.isRunning)
344
+ return;
345
+ try {
346
+ await this.pollAndDispatchOnce();
347
+ }
348
+ catch (err) {
349
+ if (this.notifyEnabled) {
350
+ notifyOperator('Daemon Loop Error', err instanceof Error ? err.message : String(err));
351
+ }
352
+ }
353
+ if (this.isRunning) {
354
+ this.pollTimer = setTimeout(tick, this.pollIntervalMs);
355
+ }
356
+ };
357
+ tick();
358
+ }
359
+ stop() {
360
+ this.isRunning = false;
361
+ if (this.heartbeatTimer) {
362
+ clearInterval(this.heartbeatTimer);
363
+ this.heartbeatTimer = null;
364
+ }
365
+ if (this.pollTimer) {
366
+ clearTimeout(this.pollTimer);
367
+ this.pollTimer = null;
368
+ }
369
+ this.releaseLease();
370
+ }
371
+ }
372
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
package/dist/demo.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ export interface DemoOptions {
2
+ /** Keep the sandbox directory on disk and report its path instead of deleting it. */
3
+ keep?: boolean;
4
+ /** Suppress all human-readable output; the caller receives only the structured result. */
5
+ json?: boolean;
6
+ /** Sink for human-readable lines. Defaults to process.stdout. Ignored when `json` is true. */
7
+ write?: (line: string) => void;
8
+ /** Force ANSI colors on/off. Defaults to auto-detection (TTY and no NO_COLOR). */
9
+ color?: boolean;
10
+ /** Lifetime of the demo child process in ms (0 = finish immediately). */
11
+ execDelayMs?: number;
12
+ /** Execution TTL granted to the demo contract, in seconds. */
13
+ execTtlSec?: number;
14
+ }
15
+ export interface DemoStepRecord {
16
+ index: number;
17
+ total: number;
18
+ title: string;
19
+ fields: Record<string, string>;
20
+ }
21
+ export interface DemoResult {
22
+ ok: boolean;
23
+ sandbox: string;
24
+ sandbox_kept: boolean;
25
+ contract_id: string;
26
+ claim_token: string;
27
+ status: string;
28
+ verdict: 'PASSED' | 'CHANGES_REQUESTED';
29
+ issues: string[];
30
+ dirty_files: number;
31
+ heartbeat_renewals: number;
32
+ elapsed_ms: number;
33
+ steps: DemoStepRecord[];
34
+ warnings: string[];
35
+ }
36
+ export declare function runDemo(options?: DemoOptions): Promise<DemoResult>;