ferrycat 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -14
- package/README_zh.md +93 -14
- package/bin/ferrycat.js +20 -1
- package/dist/cli.js +196 -14
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +137 -1
- package/dist/daemon.js +673 -10
- package/dist/daemon.js.map +1 -1
- package/dist/delivery.d.ts +48 -0
- package/dist/delivery.js +158 -0
- package/dist/delivery.js.map +1 -0
- package/dist/demo.d.ts +36 -0
- package/dist/demo.js +343 -0
- package/dist/demo.js.map +1 -0
- package/dist/engine.d.ts +15 -1
- package/dist/engine.js +287 -29
- package/dist/engine.js.map +1 -1
- package/dist/fingerprint.d.ts +60 -4
- package/dist/fingerprint.js +438 -9
- package/dist/fingerprint.js.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +62 -0
- package/dist/init.js +356 -0
- package/dist/init.js.map +1 -0
- package/dist/schemas.d.ts +64 -0
- package/dist/schemas.js +30 -0
- package/dist/schemas.js.map +1 -1
- package/dist/supervisor.d.ts +10 -0
- package/dist/supervisor.js +34 -26
- package/dist/supervisor.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/package.json +16 -4
- package/server.json +3 -3
package/dist/daemon.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type FerryCatConfigFile, type HandlerDefinition } from './schemas.js';
|
|
|
3
3
|
export interface DaemonOptions {
|
|
4
4
|
configPath?: string;
|
|
5
5
|
dbPath?: string;
|
|
6
|
+
journalPath?: string;
|
|
6
7
|
targetAgent?: string;
|
|
7
8
|
lockId?: string;
|
|
8
9
|
leaseTtlSec?: number;
|
|
@@ -11,15 +12,86 @@ export interface DaemonOptions {
|
|
|
11
12
|
once?: boolean;
|
|
12
13
|
notify?: boolean;
|
|
13
14
|
}
|
|
15
|
+
export type DaemonJournalEvent = 'TASK_DISPATCHING' | 'CHILD_SPAWNED' | 'TASK_SETTLED' | 'CRASH_RECOVERED';
|
|
16
|
+
export type JournalRecoveryAction = 'RELEASED_TO_QUEUE' | 'MARKED_FAILED_MAX_RELEASES' | 'MARKED_FAILED_MUTATION_UNSAFE' | 'MARKED_FAILED_MISSING_CLAIM_TOKEN' | 'QUARANTINED_ORPHAN_ALIVE' | 'ALREADY_SETTLED';
|
|
17
|
+
export interface DaemonJournalEntry {
|
|
18
|
+
seq: number;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
event: DaemonJournalEvent;
|
|
21
|
+
daemon_owner_token: string;
|
|
22
|
+
daemon_pid: number;
|
|
23
|
+
/** R3: wall-clock start time of the writing daemon process, used to disambiguate PID reuse. */
|
|
24
|
+
daemon_started_at?: number;
|
|
25
|
+
lock_id: string;
|
|
26
|
+
contract_id: string;
|
|
27
|
+
target_agent: string;
|
|
28
|
+
task_type?: string;
|
|
29
|
+
claim_token?: string;
|
|
30
|
+
exec_file?: string;
|
|
31
|
+
child_pid?: number | null;
|
|
32
|
+
child_started_at?: number;
|
|
33
|
+
final_status?: string;
|
|
34
|
+
previous_owner_token?: string;
|
|
35
|
+
recovery_action?: JournalRecoveryAction;
|
|
36
|
+
}
|
|
37
|
+
export interface JournalRecoveryItem {
|
|
38
|
+
contract_id: string;
|
|
39
|
+
target_agent: string;
|
|
40
|
+
claim_token?: string;
|
|
41
|
+
previous_owner_token: string;
|
|
42
|
+
previous_pid: number;
|
|
43
|
+
child_pid?: number | null;
|
|
44
|
+
action: JournalRecoveryAction;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Non-destructive OS liveness probe via `process.kill(pid, 0)`.
|
|
48
|
+
* Never sends a termination signal (respecting CK-05 PID-reuse safety), only checks whether
|
|
49
|
+
* the OS kernel still reports an active process for `pid`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isProcessAlive(pid?: number | null): boolean;
|
|
14
52
|
export declare function loadFerryCatConfig(customConfigPath?: string): {
|
|
15
53
|
config: FerryCatConfigFile;
|
|
16
54
|
resolvedPath: string | null;
|
|
17
55
|
};
|
|
18
|
-
export
|
|
56
|
+
export type NotifyLevel = 'info' | 'warn' | 'error';
|
|
57
|
+
export interface NotifyOptions {
|
|
58
|
+
level?: NotifyLevel;
|
|
59
|
+
/** Stable machine-readable event name forwarded to the webhook payload. */
|
|
60
|
+
event?: string;
|
|
61
|
+
/** Explicit webhook endpoint; falls back to FERRYCAT_WEBHOOK_URL when omitted. */
|
|
62
|
+
webhookUrl?: string;
|
|
63
|
+
/** Set false to suppress the native OS toast (e.g. in CI or headless runs). */
|
|
64
|
+
native?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface WebhookPayload {
|
|
67
|
+
event: string;
|
|
68
|
+
title: string;
|
|
69
|
+
message: string;
|
|
70
|
+
timestamp: string;
|
|
71
|
+
level: NotifyLevel;
|
|
72
|
+
}
|
|
73
|
+
export declare function setNotifyWebhook(url: string | undefined): void;
|
|
74
|
+
export declare function getNotifyWebhook(): string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the platform-native notification command (FC-403).
|
|
77
|
+
* Windows uses a WinRT Toast driven by the PowerShell host's own AppId so no module install is
|
|
78
|
+
* required; macOS uses `display notification`; Linux uses libnotify's `notify-send`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function resolveNativeCommand(title: string, message: string, platform?: NodeJS.Platform): {
|
|
81
|
+
cmd: string;
|
|
82
|
+
args: string[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Deliver one webhook payload. Exported so tests can await the real HTTP round-trip; production
|
|
86
|
+
* call sites use the fire-and-forget wrapper below.
|
|
87
|
+
*/
|
|
88
|
+
export declare function dispatchWebhook(url: string, payload: WebhookPayload, timeoutMs?: number): Promise<boolean>;
|
|
89
|
+
export declare function notifyOperator(title: string, message: string, opts?: NotifyOptions): void;
|
|
19
90
|
export declare class FerryCatDaemon {
|
|
20
91
|
readonly engine: FerryCatEngine;
|
|
21
92
|
readonly config: FerryCatConfigFile;
|
|
22
93
|
readonly configPath: string | null;
|
|
94
|
+
readonly journalPath: string;
|
|
23
95
|
readonly lockId: string;
|
|
24
96
|
readonly leaseTtlSec: number;
|
|
25
97
|
readonly heartbeatIntervalMs: number;
|
|
@@ -27,8 +99,16 @@ export declare class FerryCatDaemon {
|
|
|
27
99
|
readonly ownerToken: string;
|
|
28
100
|
readonly targetAgentFilter?: string;
|
|
29
101
|
readonly notifyEnabled: boolean;
|
|
102
|
+
readonly webhookUrl?: string;
|
|
103
|
+
/** R3: wall-clock start of this daemon instance, journaled so a successor can disambiguate PID reuse. */
|
|
104
|
+
readonly daemonStartedAt: number;
|
|
105
|
+
journalDegraded: boolean;
|
|
106
|
+
lastJournalError: string | null;
|
|
30
107
|
private isRunning;
|
|
31
108
|
private isLeader;
|
|
109
|
+
private journalSeq;
|
|
110
|
+
private settledSinceLastCompact;
|
|
111
|
+
private journalDegradedReported;
|
|
32
112
|
private heartbeatTimer;
|
|
33
113
|
private pollTimer;
|
|
34
114
|
private signalCleanupRegistered;
|
|
@@ -36,6 +116,62 @@ export declare class FerryCatDaemon {
|
|
|
36
116
|
constructor(options?: DaemonOptions);
|
|
37
117
|
registerSignalHandlers(): void;
|
|
38
118
|
acquireLease(): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Synchronously appends a write-ahead record to `daemon-journal.jsonl` (Phase 3 Step 2).
|
|
121
|
+
* Guaranteed to flush before child spawn and after task completion so sudden SIGKILL / power loss
|
|
122
|
+
* leaves an auditable breadcrumb for the next daemon leader to self-heal.
|
|
123
|
+
*/
|
|
124
|
+
appendJournalEntry(partial: Omit<DaemonJournalEntry, 'seq' | 'timestamp' | 'daemon_owner_token' | 'daemon_pid' | 'daemon_started_at' | 'lock_id'>): DaemonJournalEntry;
|
|
125
|
+
/**
|
|
126
|
+
* Reads all valid JSONL records from this daemon's journal file, tolerating partial/corrupt tail lines.
|
|
127
|
+
*
|
|
128
|
+
* R6 (upgrade compatibility): before per-lock_id journal files existed, every daemon wrote into a
|
|
129
|
+
* single shared `daemon-journal.jsonl`. When this instance is a non-`global` lock whose own file does
|
|
130
|
+
* not exist yet, fall back to that legacy file and keep only the records belonging to this lock_id,
|
|
131
|
+
* so in-flight contracts written by the previous version are still recovered instead of silently
|
|
132
|
+
* stranded as CLAIMED until the TTL sweep kills them.
|
|
133
|
+
*/
|
|
134
|
+
readJournalEntries(): DaemonJournalEntry[];
|
|
135
|
+
/**
|
|
136
|
+
* Scans `daemon-journal.jsonl` upon acquiring leadership and heals any in-flight contracts
|
|
137
|
+
* orphaned by a crashed predecessor daemon instance (Phase 3 Step 2, FC-404).
|
|
138
|
+
*
|
|
139
|
+
* Safety invariants:
|
|
140
|
+
* - CK-05 (PID reuse safety): never sends kill signals to bare numeric PIDs from old runs.
|
|
141
|
+
* - Liveness quarantine: probes `isProcessAlive(child_pid)` and `isProcessAlive(daemon_pid)`.
|
|
142
|
+
* If the predecessor daemon or orphaned child is still alive (e.g. GC pause / lease expiry),
|
|
143
|
+
* quarantines the contract as `FAILED` (`QUARANTINED_ORPHAN_ALIVE`) instead of re-queueing
|
|
144
|
+
* to prevent concurrent double-execution on the same workspace.
|
|
145
|
+
* - Mutation task protection: `implementation` and `test_generation` tasks interrupted mid-flight
|
|
146
|
+
* are never auto-requeued (`MARKED_FAILED_MUTATION_UNSAFE`) to protect working-tree integrity.
|
|
147
|
+
* - Read-only tasks (`code_review` / `architecture_audit`) whose predecessor & child are confirmed
|
|
148
|
+
* dead are released back to the `SUBMITTED` queue (or marked `FAILED` if `MAX_RELEASES` is reached).
|
|
149
|
+
*/
|
|
150
|
+
recoverFromJournal(): JournalRecoveryItem[];
|
|
151
|
+
compactJournalIfSettled(): boolean;
|
|
152
|
+
/**
|
|
153
|
+
* R7: operator-facing snapshot of the daemon's lease and journal health.
|
|
154
|
+
* Exposes `journalDegraded` / `lastJournalError`, which were previously only internal fields
|
|
155
|
+
* with no reader — a silently degraded write-ahead journal now shows up here and on stderr.
|
|
156
|
+
*/
|
|
157
|
+
getStatus(): {
|
|
158
|
+
lock_id: string;
|
|
159
|
+
is_leader: boolean;
|
|
160
|
+
owner_token: string;
|
|
161
|
+
daemon_pid: number;
|
|
162
|
+
daemon_started_at: number;
|
|
163
|
+
journal_path: string;
|
|
164
|
+
journal_degraded: boolean;
|
|
165
|
+
last_journal_error: string | null;
|
|
166
|
+
journal_entries: number;
|
|
167
|
+
lease: {
|
|
168
|
+
owner_token: string;
|
|
169
|
+
heartbeat_at: number;
|
|
170
|
+
expires_at: number;
|
|
171
|
+
metadata: unknown;
|
|
172
|
+
} | null;
|
|
173
|
+
lease_held_by_self: boolean;
|
|
174
|
+
};
|
|
39
175
|
renewLease(): boolean;
|
|
40
176
|
releaseLease(): void;
|
|
41
177
|
resolveHandler(targetAgent: string, taskType: string): HandlerDefinition | null;
|