@interactive-inc/claude-funnel 0.60.1 → 0.64.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 +2 -2
- package/dist/bin.js +428 -761
- package/dist/{channels-2g_BU1N0.d.ts → channels-CRGb6B5_.d.ts} +17 -16
- package/dist/claude.d.ts +5 -7
- package/dist/claude.js +143 -36
- package/dist/{connector-descriptor-6SXJoszo.d.ts → connector-descriptor-BFIhyTfa.d.ts} +49 -10
- package/dist/connector-diagnostics-recorder-COtNEmUp.js +42 -0
- package/dist/connectors/discord.d.ts +31 -37
- package/dist/connectors/discord.js +3 -3
- package/dist/connectors/gh.d.ts +37 -33
- package/dist/connectors/gh.js +3 -3
- package/dist/connectors/schedule.d.ts +9 -57
- package/dist/connectors/schedule.js +3 -3
- package/dist/connectors/slack.d.ts +106 -132
- package/dist/connectors/slack.js +4 -3
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +1 -1
- package/dist/discord-connector-DIFkYBbi.js +250 -0
- package/dist/discord-connector-schema-D-bOVAKt.d.ts +22 -0
- package/dist/docs.js +1 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/{file-process-guard-C_PLxfUX.d.ts → file-process-guard-tVcgckH6.d.ts} +6 -6
- package/dist/{file-system-o51IsM0W.d.ts → file-system-VhwwXZbm.d.ts} +8 -0
- package/dist/flume-source-listener-BNyAII7N.d.ts +133 -0
- package/dist/{funnel-diagnostics-CSiJmPlZ.js → funnel-diagnostics-Cvk6Sk4x.js} +193 -43
- package/dist/{funnel-diagnostics-DpXOsCty.d.ts → funnel-diagnostics-b9ar0Ing.d.ts} +67 -5
- package/dist/{funnel-docs-BxXZ9Ksx.js → funnel-docs-C-ge0MuB.js} +42 -6
- package/dist/{funnel-doctor-CZf_0Luq.d.ts → funnel-doctor-CnRQi4kM.d.ts} +2 -2
- package/dist/{funnel-doctor-DiJCjHsg.js → funnel-doctor-XrI2GBH8.js} +1 -1
- package/dist/funnel-error-0t1MK1R6.js +75 -0
- package/dist/{funnel-recovery-DnLrdWO9.d.ts → funnel-recovery-CMhY8Jfk.d.ts} +1 -1
- package/dist/gateway/daemon.js +167 -527
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.js +3 -3
- package/dist/gh-connector-BUGCOEWS.js +187 -0
- package/dist/{gh-connector-schema-Rzwc1c1N.js → gh-connector-schema-CAqIhzGr.js} +7 -0
- package/dist/gh-connector-schema-DWQaB6gX.d.ts +16 -0
- package/dist/{index-CgY8NdMz.d.ts → index-Ds6sHhA-.d.ts} +37 -19
- package/dist/index.d.ts +182 -22
- package/dist/index.js +363 -173
- package/dist/{local-config-json-schema-JyLqOQNX.js → local-config-json-schema-DexV8vX3.js} +24 -4
- package/dist/local-config.d.ts +39 -2
- package/dist/local-config.js +53 -2
- package/dist/logger.js +1 -1
- package/dist/loopback-fetch-CVNuN3YZ.js +40 -0
- package/dist/{local-config-sync-Dh1Croqe.d.ts → memory-token-prompter-BoV8Hf-n.d.ts} +30 -3
- package/dist/node-file-system-BOXIHW_Q.js +174 -0
- package/dist/{profiles-DSzTeKQw.js → profiles-ZHLONml4.js} +49 -49
- package/dist/{profiles-Cy5wXQ0L.d.ts → profiles-cVZQkM69.d.ts} +3 -3
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +1 -1
- package/dist/recovery.d.ts +1 -1
- package/dist/recovery.js +1 -1
- package/dist/resolve-connector-token-DxDG9mhf.js +22 -0
- package/dist/{schedule-connector-L4uzg5M8.js → schedule-connector-9k3gOIgl.js} +54 -55
- package/dist/schedule-connector-schema-Z0RXLgPI.d.ts +49 -0
- package/dist/settings-reader-BNxjsxCB.d.ts +27 -0
- package/dist/{settings-store-CUKSeTXC.js → settings-store-C2QdOH-t.js} +23 -4
- package/dist/slack-connector-CxpWagbT.js +388 -0
- package/dist/slack-event-processor-BhCf5Wiy.d.ts +95 -0
- package/dist/slack-event-processor-xFDG3US0.js +176 -0
- package/dist/slot-fields-D-pvMgTK.js +249 -0
- package/dist/{memory-diagnostic-log-CI60kNfB.js → sqlite-diagnostic-log-DOTPW-tG.js} +373 -249
- package/dist/{yaml-render-93pX7EF7.js → yaml-render--J1_3BSA.js} +25 -21
- package/package.json +2 -4
- package/dist/discord-connector-BL36yvbL.js +0 -250
- package/dist/gateway-base-url-Dy4Ykuoh.js +0 -14
- package/dist/gh-connector-DpiixfQZ.js +0 -226
- package/dist/http-client-oICicjuO.d.ts +0 -18
- package/dist/memory-token-prompter-B4sjyaAq.d.ts +0 -57
- package/dist/memory-token-prompter-CZde7e6y.js +0 -61
- package/dist/node-file-system-Blr8pAir.js +0 -48
- package/dist/settings-reader-BIFB_j2f.d.ts +0 -18
- package/dist/slack-connector-DQIFPdBF.js +0 -484
- package/dist/slot-fields-CMoRpwuy.js +0 -45
- /package/dist/{connector-adapter-DU9Rvyec.js → connector-adapter-Dvs8N7ew.js} +0 -0
- /package/dist/{connector-listener-DR3aKOuK.js → connector-listener-mPGZYa8e.js} +0 -0
- /package/dist/{diagnostic-sql-reader-C9zR-Csp.js → diagnostic-sql-reader-oXZnWFf_.js} +0 -0
- /package/dist/{discord-connector-schema-B_N6IXLz.js → discord-connector-schema-B4YpWpR3.js} +0 -0
- /package/dist/{error-message-of-Byi4y0Uf.js → error-message-of-ColuYmAk.js} +0 -0
- /package/dist/{funnel-log-sqlite-sink-kqJbx2H7.js → funnel-log-sqlite-sink-DLYkY0pZ.js} +0 -0
- /package/dist/{funnel-recovery-BFdPjL6Z.js → funnel-recovery-DKnEutUS.js} +0 -0
- /package/dist/{node-http-client-lowp60Oa.js → node-http-client-u00atiKx.js} +0 -0
- /package/dist/{schedule-connector-schema-CfyuMCMh.js → schedule-connector-schema-DKEPZnVv.js} +0 -0
- /package/dist/{settings-reader-CtQ-Ix8_.js → settings-reader-9FcX3qS1.js} +0 -0
- /package/dist/{settings-schema-D1xcOqRu.d.ts → settings-schema-BL_c2Udm.d.ts} +0 -0
- /package/dist/{slack-connector-schema-C1zEf4TG.js → slack-connector-schema-Dem8to4P.js} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { t as NodeFunnelFileSystem } from "./node-file-system-
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { t as NodeFunnelFileSystem } from "./node-file-system-BOXIHW_Q.js";
|
|
2
|
+
import { t as errorMessageOf } from "./error-message-of-ColuYmAk.js";
|
|
3
|
+
import { t as FunnelConnectorListener } from "./connector-listener-mPGZYa8e.js";
|
|
4
|
+
import { n as scheduleConnectorSchema, r as scheduleEntrySchema } from "./schedule-connector-schema-DKEPZnVv.js";
|
|
5
|
+
import { t as FunnelConnectorDiagnosticsRecorder } from "./connector-diagnostics-recorder-COtNEmUp.js";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
//#region lib/engine/connectors/match-cron.ts
|
|
@@ -87,7 +88,7 @@ const defaultFs = new NodeFunnelFileSystem();
|
|
|
87
88
|
* connectorDir) so this store does not know about the funnel directory layout
|
|
88
89
|
* (`channels/<id>/connectors/<id>/state.json` lives outside this class).
|
|
89
90
|
*/
|
|
90
|
-
var
|
|
91
|
+
var FunnelScheduleStateStore = class {
|
|
91
92
|
path;
|
|
92
93
|
fs;
|
|
93
94
|
constructor(deps) {
|
|
@@ -116,50 +117,75 @@ const MAX_CATCHUP_MINUTES = 1440;
|
|
|
116
117
|
var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
117
118
|
config;
|
|
118
119
|
lastFiredStore;
|
|
119
|
-
channelId;
|
|
120
120
|
logger;
|
|
121
|
-
|
|
121
|
+
diagnostics;
|
|
122
122
|
now;
|
|
123
123
|
onFired;
|
|
124
124
|
timer = null;
|
|
125
125
|
stopped = false;
|
|
126
|
+
tickScheduled = false;
|
|
126
127
|
constructor(deps) {
|
|
127
128
|
super();
|
|
128
129
|
this.config = deps.config;
|
|
129
130
|
this.lastFiredStore = deps.lastFiredStore;
|
|
130
|
-
this.channelId = deps.channelId ?? null;
|
|
131
131
|
this.logger = deps.logger;
|
|
132
|
-
this.
|
|
132
|
+
this.diagnostics = new FunnelConnectorDiagnosticsRecorder({
|
|
133
|
+
type: "schedule",
|
|
134
|
+
connectorId: deps.config.id,
|
|
135
|
+
channelId: deps.channelId ?? null,
|
|
136
|
+
log: deps.diagnosticLog
|
|
137
|
+
});
|
|
133
138
|
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
134
139
|
this.onFired = deps.onFired ?? null;
|
|
135
140
|
}
|
|
136
141
|
async start(notify) {
|
|
137
142
|
this.stopped = false;
|
|
138
|
-
this.
|
|
143
|
+
this.tickScheduled = true;
|
|
144
|
+
this.diagnostics.recordConnection("started", "");
|
|
139
145
|
const scheduleNext = () => {
|
|
140
146
|
if (this.stopped) return;
|
|
141
147
|
const date = this.now();
|
|
142
148
|
const msUntilNextMinute = 6e4 - (date.getSeconds() * 1e3 + date.getMilliseconds());
|
|
143
149
|
this.timer = setTimeout(async () => {
|
|
144
150
|
if (this.stopped) return;
|
|
145
|
-
|
|
151
|
+
try {
|
|
152
|
+
await this.tick(notify);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
this.recordTickError(error);
|
|
155
|
+
}
|
|
146
156
|
scheduleNext();
|
|
147
157
|
}, msUntilNextMinute);
|
|
148
158
|
this.timer.unref();
|
|
159
|
+
this.tickScheduled = true;
|
|
149
160
|
};
|
|
150
|
-
|
|
161
|
+
try {
|
|
162
|
+
await this.tick(notify);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
this.recordTickError(error);
|
|
165
|
+
}
|
|
166
|
+
this.diagnostics.recordConnection("connected", "");
|
|
151
167
|
scheduleNext();
|
|
152
168
|
}
|
|
153
169
|
async stop() {
|
|
154
170
|
this.stopped = true;
|
|
171
|
+
this.tickScheduled = false;
|
|
155
172
|
if (this.timer) {
|
|
156
173
|
clearTimeout(this.timer);
|
|
157
174
|
this.timer = null;
|
|
158
175
|
}
|
|
159
|
-
this.recordConnection("
|
|
176
|
+
this.diagnostics.recordConnection("disconnected", "");
|
|
177
|
+
this.diagnostics.recordConnection("stopped", "");
|
|
178
|
+
}
|
|
179
|
+
recordTickError(error) {
|
|
180
|
+
const message = errorMessageOf(error);
|
|
181
|
+
this.diagnostics.recordConnection("error", `tick: ${message}`);
|
|
182
|
+
this.logger?.error("schedule tick failed", {
|
|
183
|
+
connector: this.config.name,
|
|
184
|
+
error: message
|
|
185
|
+
});
|
|
160
186
|
}
|
|
161
187
|
isAlive() {
|
|
162
|
-
return !this.stopped && this.
|
|
188
|
+
return !this.stopped && this.tickScheduled;
|
|
163
189
|
}
|
|
164
190
|
async tick(notify) {
|
|
165
191
|
const now = this.truncateToMinute(this.now());
|
|
@@ -209,14 +235,20 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
209
235
|
};
|
|
210
236
|
if (catchup) meta.catchup = "true";
|
|
211
237
|
const eventId = `${entry.id}@${firedAt.toISOString()}`;
|
|
212
|
-
this.recordRaw(eventId,
|
|
238
|
+
this.diagnostics.recordRaw(eventId, JSON.stringify({
|
|
239
|
+
schedule_id: entry.id,
|
|
240
|
+
cron: entry.cron,
|
|
241
|
+
prompt: entry.prompt,
|
|
242
|
+
fired_at: firedAt.toISOString(),
|
|
243
|
+
catchup
|
|
244
|
+
}));
|
|
213
245
|
try {
|
|
214
246
|
await notify(entry.prompt, meta);
|
|
215
247
|
} catch (error) {
|
|
216
|
-
this.recordProcessed(eventId,
|
|
248
|
+
this.diagnostics.recordProcessed(eventId, "emitted:delivery-failed", entry.prompt);
|
|
217
249
|
throw error;
|
|
218
250
|
}
|
|
219
|
-
this.recordProcessed(eventId,
|
|
251
|
+
this.diagnostics.recordProcessed(eventId, "emitted", entry.prompt);
|
|
220
252
|
if (this.onFired) try {
|
|
221
253
|
await this.onFired(entry, firedAt);
|
|
222
254
|
} catch (error) {
|
|
@@ -263,7 +295,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
263
295
|
}
|
|
264
296
|
logInvalidCron(entry, error) {
|
|
265
297
|
const message = errorMessageOf(error);
|
|
266
|
-
this.recordConnection("error", `invalid cron "${entry.cron}" (entry ${entry.id}): ${message}`);
|
|
298
|
+
this.diagnostics.recordConnection("error", `invalid cron "${entry.cron}" (entry ${entry.id}): ${message}`);
|
|
267
299
|
this.logger?.error("invalid cron expression in schedule", {
|
|
268
300
|
connector: this.config.name,
|
|
269
301
|
id: entry.id,
|
|
@@ -276,40 +308,6 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
276
308
|
copy.setSeconds(0, 0);
|
|
277
309
|
return copy;
|
|
278
310
|
}
|
|
279
|
-
recordRaw(eventId, entry, firedAt, catchup) {
|
|
280
|
-
this.diagnosticLog?.recordRaw({
|
|
281
|
-
eventId,
|
|
282
|
-
type: "schedule",
|
|
283
|
-
connectorId: this.config.id,
|
|
284
|
-
channelId: this.channelId,
|
|
285
|
-
payload: JSON.stringify({
|
|
286
|
-
schedule_id: entry.id,
|
|
287
|
-
cron: entry.cron,
|
|
288
|
-
prompt: entry.prompt,
|
|
289
|
-
fired_at: firedAt.toISOString(),
|
|
290
|
-
catchup
|
|
291
|
-
})
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
recordProcessed(eventId, entry, outcome) {
|
|
295
|
-
this.diagnosticLog?.recordProcessed({
|
|
296
|
-
eventId,
|
|
297
|
-
type: "schedule",
|
|
298
|
-
connectorId: this.config.id,
|
|
299
|
-
channelId: this.channelId,
|
|
300
|
-
outcome,
|
|
301
|
-
payload: entry.prompt
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
recordConnection(status, detail) {
|
|
305
|
-
this.diagnosticLog?.recordConnection({
|
|
306
|
-
type: "schedule",
|
|
307
|
-
connectorId: this.config.id,
|
|
308
|
-
channelId: this.channelId,
|
|
309
|
-
status,
|
|
310
|
-
detail
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
311
|
};
|
|
314
312
|
//#endregion
|
|
315
313
|
//#region lib/engine/connectors/schedule-connector.ts
|
|
@@ -335,14 +333,15 @@ const scheduleConnector = (options = {}) => ({
|
|
|
335
333
|
const parsed = scheduleConnectorSchema.parse(config);
|
|
336
334
|
return new FunnelScheduleListener({
|
|
337
335
|
config: parsed,
|
|
338
|
-
lastFiredStore: new
|
|
336
|
+
lastFiredStore: new FunnelScheduleStateStore({
|
|
339
337
|
path: join(deps.connectorDir(deps.channelId, parsed.id), "state.json"),
|
|
340
338
|
fs: deps.fs
|
|
341
339
|
}),
|
|
342
340
|
channelId: deps.channelId,
|
|
343
341
|
logger: deps.logger,
|
|
344
342
|
diagnosticLog: deps.diagnosticLog,
|
|
345
|
-
onFired: options.onFired
|
|
343
|
+
onFired: options.onFired,
|
|
344
|
+
now: () => deps.clock.now()
|
|
346
345
|
});
|
|
347
346
|
},
|
|
348
347
|
createAdapter: null,
|
|
@@ -409,4 +408,4 @@ const scheduleConnector = (options = {}) => ({
|
|
|
409
408
|
}
|
|
410
409
|
});
|
|
411
410
|
//#endregion
|
|
412
|
-
export { matchCron as i, FunnelScheduleListener as n,
|
|
411
|
+
export { matchCron as i, FunnelScheduleListener as n, FunnelScheduleStateStore as r, scheduleConnector as t };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region lib/engine/connectors/schedule-connector-schema.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Catch-up behavior when the daemon was down past one or more matching minutes.
|
|
6
|
+
*
|
|
7
|
+
* - `latest`: fire once with the most recent missed match (default; preserves prior behavior).
|
|
8
|
+
* - `all`: fire once per missed minute, oldest first (capped at 24 h).
|
|
9
|
+
* - `skip`: never fire missed matches; only fire when the current minute matches.
|
|
10
|
+
*/
|
|
11
|
+
declare const scheduleCatchupPolicySchema: z.ZodEnum<{
|
|
12
|
+
latest: "latest";
|
|
13
|
+
all: "all";
|
|
14
|
+
skip: "skip";
|
|
15
|
+
}>;
|
|
16
|
+
type ScheduleCatchupPolicy = z.infer<typeof scheduleCatchupPolicySchema>;
|
|
17
|
+
declare const scheduleEntrySchema: z.ZodObject<{
|
|
18
|
+
id: z.ZodString;
|
|
19
|
+
cron: z.ZodString;
|
|
20
|
+
prompt: z.ZodString;
|
|
21
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
22
|
+
catchupPolicy: z.ZodDefault<z.ZodEnum<{
|
|
23
|
+
latest: "latest";
|
|
24
|
+
all: "all";
|
|
25
|
+
skip: "skip";
|
|
26
|
+
}>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
type ScheduleEntry = z.infer<typeof scheduleEntrySchema>;
|
|
29
|
+
declare const scheduleConnectorSchema: z.ZodObject<{
|
|
30
|
+
id: z.ZodString;
|
|
31
|
+
name: z.ZodString;
|
|
32
|
+
type: z.ZodLiteral<"schedule">;
|
|
33
|
+
entries: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
34
|
+
id: z.ZodString;
|
|
35
|
+
cron: z.ZodString;
|
|
36
|
+
prompt: z.ZodString;
|
|
37
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
38
|
+
catchupPolicy: z.ZodDefault<z.ZodEnum<{
|
|
39
|
+
latest: "latest";
|
|
40
|
+
all: "all";
|
|
41
|
+
skip: "skip";
|
|
42
|
+
}>>;
|
|
43
|
+
}, z.core.$strip>>>;
|
|
44
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
45
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
type ScheduleConnectorConfig = z.infer<typeof scheduleConnectorSchema>;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { scheduleConnectorSchema as a, scheduleCatchupPolicySchema as i, ScheduleConnectorConfig as n, scheduleEntrySchema as o, ScheduleEntry as r, ScheduleCatchupPolicy as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { a as Settings } from "./settings-schema-BL_c2Udm.js";
|
|
2
|
+
|
|
3
|
+
//#region lib/engine/id/id-generator.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* ID generator boundary. Default NodeFunnelIdGenerator wraps `crypto.randomUUID()`;
|
|
6
|
+
* MemoryFunnelIdGenerator emits `<prefix>-1, <prefix>-2, ...` for deterministic tests.
|
|
7
|
+
*/
|
|
8
|
+
declare abstract class FunnelIdGenerator {
|
|
9
|
+
abstract generate(): string;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region lib/engine/settings/settings-reader.d.ts
|
|
13
|
+
declare abstract class FunnelSettingsReader {
|
|
14
|
+
abstract read(): Settings;
|
|
15
|
+
abstract write(settings: Settings): void;
|
|
16
|
+
/**
|
|
17
|
+
* Atomic read-modify-write. Implementations must serialize against
|
|
18
|
+
* concurrent processes touching the same file (the Node store does so via
|
|
19
|
+
* an exclusive lockfile; Memory stores are single-threaded). Engine
|
|
20
|
+
* classes must use `update` for any mutation that depends on prior state,
|
|
21
|
+
* otherwise a concurrent CLI invocation or `fnl claude` launch can lose
|
|
22
|
+
* the edit through a read-modify-write race.
|
|
23
|
+
*/
|
|
24
|
+
abstract update<T>(mutator: (settings: Settings) => T): T;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { FunnelIdGenerator as n, FunnelSettingsReader as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as NodeFunnelFileSystem } from "./node-file-system-
|
|
2
|
-
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-
|
|
1
|
+
import { t as NodeFunnelFileSystem } from "./node-file-system-BOXIHW_Q.js";
|
|
2
|
+
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-9FcX3qS1.js";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -29,11 +29,11 @@ const baseConnectorConfigSchema = z.object({
|
|
|
29
29
|
//#region lib/engine/settings/settings-schema.ts
|
|
30
30
|
/**
|
|
31
31
|
* Connectors are stored loosely here: settings validates only the common base
|
|
32
|
-
* fields and preserves every type-specific key verbatim (`.
|
|
32
|
+
* fields and preserves every type-specific key verbatim (`.loose()`).
|
|
33
33
|
* Core does not enumerate connector types, so strict per-type validation happens
|
|
34
34
|
* at the registry/descriptor layer (CRUD time), not on every settings read.
|
|
35
35
|
*/
|
|
36
|
-
const storedConnectorSchema = baseConnectorConfigSchema.
|
|
36
|
+
const storedConnectorSchema = baseConnectorConfigSchema.loose();
|
|
37
37
|
/**
|
|
38
38
|
* Routing mode when multiple WS clients are subscribed to the same channel.
|
|
39
39
|
*
|
|
@@ -192,6 +192,25 @@ var FunnelSettingsStore = class extends FunnelSettingsReader {
|
|
|
192
192
|
};
|
|
193
193
|
this.fs.writeSecretFileSync(this.path, `${JSON.stringify(versioned, null, 2)}\n`);
|
|
194
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Run `mutator` against a freshly-read settings object inside an exclusive
|
|
197
|
+
* file lock, then persist the result. Use this instead of bare `read()` +
|
|
198
|
+
* `write()` for any logical edit (add channel, set token, rename profile),
|
|
199
|
+
* so two concurrent CLI invocations or `fnl claude` launches cannot lose
|
|
200
|
+
* each other's updates via a read-modify-write race. The mutator may
|
|
201
|
+
* mutate `settings` in place and/or return a value; the value is returned
|
|
202
|
+
* to the caller. A thrown error from the mutator skips the write but still
|
|
203
|
+
* releases the lock.
|
|
204
|
+
*/
|
|
205
|
+
update(mutator) {
|
|
206
|
+
this.fs.mkdirSync(dirname(this.path), { recursive: true });
|
|
207
|
+
return this.fs.withFileLock(`${this.path}.lock`, () => {
|
|
208
|
+
const settings = this.read();
|
|
209
|
+
const result = mutator(settings);
|
|
210
|
+
this.write(settings);
|
|
211
|
+
return result;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
195
214
|
};
|
|
196
215
|
//#endregion
|
|
197
216
|
export { resolveFunnelDir as a, channelConfigSchema as c, settingsSchema as d, baseConnectorConfigSchema as f, SETTINGS_PATH as i, channelDeliveryModeSchema as l, FUNNEL_DIR as n, resolveFunnelPort as o, NodeFunnelIdGenerator as p, FunnelSettingsStore as r, SETTINGS_VERSION as s, DEFAULT_GATEWAY_PORT as t, profileConfigSchema as u };
|