@mulmoclaude/core 0.2.9 → 0.2.10
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/collection/core/actionVisible.d.ts +34 -0
- package/dist/collection/core/calendarGrid.d.ts +120 -0
- package/dist/collection/core/deriveAll.d.ts +38 -0
- package/dist/collection/core/derivedFormula.d.ts +18 -0
- package/dist/collection/core/draft.d.ts +18 -0
- package/dist/collection/core/enumColors.d.ts +33 -0
- package/dist/collection/core/errorMessage.d.ts +4 -0
- package/dist/collection/core/itemLabel.d.ts +12 -0
- package/dist/collection/core/presentCollection.d.ts +13 -0
- package/dist/collection/core/promptSafety.d.ts +1 -0
- package/dist/collection/core/schema.d.ts +382 -0
- package/dist/collection/core/shortHexId.d.ts +8 -0
- package/dist/collection/core/sortItems.d.ts +29 -0
- package/dist/collection/core/uiTypes.d.ts +106 -0
- package/dist/collection/index.cjs +794 -0
- package/dist/collection/index.cjs.map +1 -0
- package/dist/collection/index.d.ts +14 -0
- package/dist/collection/paths.cjs +44 -0
- package/dist/collection/paths.cjs.map +1 -0
- package/dist/collection/server/atomic.d.ts +1 -0
- package/dist/collection/server/delete.d.ts +38 -0
- package/dist/collection/server/derive.d.ts +8 -0
- package/dist/collection/server/discoveredCollection.d.ts +18 -0
- package/dist/collection/server/discovery.d.ts +239 -0
- package/dist/collection/server/host.d.ts +77 -0
- package/dist/collection/server/index.cjs +51 -0
- package/dist/collection/server/io.d.ts +120 -0
- package/dist/collection/server/paths.d.ts +52 -0
- package/dist/collection/server/spawn.d.ts +55 -0
- package/dist/collection/server/util.d.ts +3 -0
- package/dist/collection/server/validate.d.ts +19 -0
- package/dist/collection/server/views.d.ts +20 -0
- package/dist/collection-watchers/config.d.ts +49 -0
- package/dist/collection-watchers/index.cjs +553 -0
- package/dist/collection-watchers/index.cjs.map +1 -0
- package/dist/collection-watchers/index.d.ts +3 -0
- package/dist/collection-watchers/reconciler.d.ts +33 -0
- package/dist/collection-watchers/watcher.d.ts +34 -0
- package/dist/deriveAll-VRWrs3SF.cjs +413 -0
- package/dist/deriveAll-VRWrs3SF.cjs.map +1 -0
- package/dist/feeds/index.cjs +8 -0
- package/dist/feeds/index.d.ts +1 -0
- package/dist/feeds/ingestTypes.d.ts +57 -0
- package/dist/feeds/paths.cjs +48 -0
- package/dist/feeds/paths.cjs.map +1 -0
- package/dist/feeds/paths.d.ts +22 -0
- package/dist/feeds/server/fetch/httpClient.d.ts +8 -0
- package/dist/feeds/server/fetch/rssParser.d.ts +12 -0
- package/dist/feeds/server/index.cjs +839 -0
- package/dist/feeds/server/index.cjs.map +1 -0
- package/dist/feeds/server/index.js +4 -5
- package/dist/feeds/server/index.js.map +1 -1
- package/dist/feeds/server/pathResolver.d.ts +6 -0
- package/dist/feeds/server/projectItem.d.ts +8 -0
- package/dist/feeds/server/refreshResult.d.ts +17 -0
- package/dist/feeds/server/retrievers/httpJson.d.ts +3 -0
- package/dist/feeds/server/retrievers/index.d.ts +12 -0
- package/dist/feeds/server/retrievers/registerAll.d.ts +0 -0
- package/dist/feeds/server/retrievers/rss.d.ts +3 -0
- package/dist/feeds/server/scheduledRefresh.d.ts +1 -1
- package/dist/file-change/index.cjs +72 -0
- package/dist/file-change/index.cjs.map +1 -0
- package/dist/file-change/index.d.ts +43 -0
- package/dist/ingestTypes-DDOVhOYC.cjs +32 -0
- package/dist/ingestTypes-DDOVhOYC.cjs.map +1 -0
- package/dist/notifier/engine.d.ts +72 -0
- package/dist/notifier/index.cjs +21 -0
- package/dist/notifier/index.d.ts +3 -0
- package/dist/notifier/store.d.ts +18 -0
- package/dist/notifier/types.d.ts +118 -0
- package/dist/notifier/validate.d.ts +17 -0
- package/dist/notifier-bS8IEeLA.cjs +577 -0
- package/dist/notifier-bS8IEeLA.cjs.map +1 -0
- package/dist/rolldown-runtime-D6vf50IK.cjs +28 -0
- package/dist/scheduler/adapter.d.ts +48 -0
- package/dist/scheduler/index.cjs +352 -0
- package/dist/scheduler/index.cjs.map +1 -0
- package/dist/scheduler/index.d.ts +2 -0
- package/dist/scheduler/task-manager.d.ts +51 -0
- package/dist/server-Db-x7OXz.cjs +1993 -0
- package/dist/server-Db-x7OXz.cjs.map +1 -0
- package/dist/skill-bridge/index.cjs +88 -0
- package/dist/skill-bridge/index.cjs.map +1 -0
- package/dist/skill-bridge/index.d.ts +30 -0
- package/dist/whisper/client.cjs +241 -0
- package/dist/whisper/client.cjs.map +1 -0
- package/dist/whisper/client.d.ts +35 -0
- package/dist/whisper/ffmpeg.d.ts +6 -0
- package/dist/whisper/index.cjs +433 -0
- package/dist/whisper/index.cjs.map +1 -0
- package/dist/whisper/index.d.ts +5 -0
- package/dist/whisper/internal.d.ts +11 -0
- package/dist/whisper/models.d.ts +49 -0
- package/dist/whisper/sidecar.d.ts +8 -0
- package/dist/whisper/whisper.d.ts +28 -0
- package/dist/workspace-setup/assets.d.ts +10 -0
- package/dist/workspace-setup/index.d.ts +3 -0
- package/dist/workspace-setup/index.js +560 -0
- package/dist/workspace-setup/index.js.map +1 -0
- package/dist/workspace-setup/slug.d.ts +6 -0
- package/dist/workspace-setup/slug.js +13 -0
- package/dist/workspace-setup/slug.js.map +1 -0
- package/dist/workspace-setup/sync.d.ts +94 -0
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CollectionItem, CollectionSchema } from '../../../collection/index.js';
|
|
2
|
+
import { DeclarativeIngestSpec } from '../../ingestTypes.js';
|
|
3
|
+
import { FeedState } from '../state.js';
|
|
4
|
+
export interface RetrieveResult {
|
|
5
|
+
/** Projected records, keyed by primaryKey (the engine upserts them). */
|
|
6
|
+
items: CollectionItem[];
|
|
7
|
+
/** Updated retriever cursor to persist (incremental fetches). */
|
|
8
|
+
cursor: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export type RetrieveFn = (ingest: DeclarativeIngestSpec, schema: CollectionSchema, state: FeedState) => Promise<RetrieveResult>;
|
|
11
|
+
export declare function registerRetriever(kind: string, retriever: RetrieveFn): void;
|
|
12
|
+
export declare function getRetriever(kind: string): RetrieveFn | undefined;
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SystemTaskDef } from '../../scheduler/adapter.js';
|
|
2
2
|
export declare const FEED_REFRESH_TASK_ID = "system:feed-refresh";
|
|
3
|
-
export declare const DEFAULT_FEED_REFRESH_INTERVAL_MS
|
|
3
|
+
export declare const DEFAULT_FEED_REFRESH_INTERVAL_MS: number;
|
|
4
4
|
/**
|
|
5
5
|
* The hourly "refresh due collections" task, shared by every host.
|
|
6
6
|
*
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
//#region src/file-change/index.ts
|
|
7
|
+
var config = null;
|
|
8
|
+
/** Wire the publisher to a host. Call once at startup, before any write route. */
|
|
9
|
+
function configureFileChangePublisher(cfg) {
|
|
10
|
+
config = cfg;
|
|
11
|
+
}
|
|
12
|
+
/** Clear the binding — test-only. */
|
|
13
|
+
function resetFileChangePublisher() {
|
|
14
|
+
config = null;
|
|
15
|
+
}
|
|
16
|
+
/** The plugin-scoped live-refresh channel a View subscribes to. */
|
|
17
|
+
function pluginFileChannel(scope, posixPath) {
|
|
18
|
+
return `plugin:${scope}:file:${posixPath}`;
|
|
19
|
+
}
|
|
20
|
+
/** Publish a file-change for a workspace-relative path: the primary channel (if any)
|
|
21
|
+
* + every matching plugin scope, with the post-write mtime. No-op until configured. */
|
|
22
|
+
async function publishFileChange(relativePath) {
|
|
23
|
+
const cfg = config;
|
|
24
|
+
if (!cfg) return;
|
|
25
|
+
const root = node_path.default.resolve(cfg.workspaceRoot) + node_path.default.sep;
|
|
26
|
+
const absPath = node_path.default.join(root, relativePath);
|
|
27
|
+
if (!absPath.startsWith(root)) {
|
|
28
|
+
cfg.warn?.("ignoring file-change for path outside workspace", { path: relativePath });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
let mtimeMs;
|
|
32
|
+
try {
|
|
33
|
+
({mtimeMs} = await (0, node_fs_promises.stat)(absPath));
|
|
34
|
+
} catch (err) {
|
|
35
|
+
cfg.warn?.("stat failed; falling back to Date.now()", {
|
|
36
|
+
path: relativePath,
|
|
37
|
+
error: errMsg(err)
|
|
38
|
+
});
|
|
39
|
+
mtimeMs = Date.now();
|
|
40
|
+
}
|
|
41
|
+
const posixPath = cfg.toPosix(relativePath);
|
|
42
|
+
const payload = {
|
|
43
|
+
path: posixPath,
|
|
44
|
+
mtimeMs
|
|
45
|
+
};
|
|
46
|
+
if (cfg.primaryChannel) safePublish(cfg, cfg.primaryChannel(posixPath), payload, "primary publish failed");
|
|
47
|
+
for (const { scope, matches } of cfg.pluginScopes ?? []) {
|
|
48
|
+
if (!matches(posixPath)) continue;
|
|
49
|
+
safePublish(cfg, pluginFileChannel(scope, posixPath), payload, `${scope} plugin forward failed`);
|
|
50
|
+
}
|
|
51
|
+
cfg.onPublished?.(posixPath, payload);
|
|
52
|
+
}
|
|
53
|
+
function safePublish(cfg, channel, payload, failMsg) {
|
|
54
|
+
try {
|
|
55
|
+
cfg.publish(channel, payload);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
cfg.warn?.(`${failMsg}; subscribers will miss this event`, {
|
|
58
|
+
channel,
|
|
59
|
+
error: errMsg(err)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function errMsg(err) {
|
|
64
|
+
return err instanceof Error ? err.message : String(err);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
exports.configureFileChangePublisher = configureFileChangePublisher;
|
|
68
|
+
exports.pluginFileChannel = pluginFileChannel;
|
|
69
|
+
exports.publishFileChange = publishFileChange;
|
|
70
|
+
exports.resetFileChangePublisher = resetFileChangePublisher;
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/file-change/index.ts"],"sourcesContent":["// @mulmoclaude/core/file-change — shared \"this workspace file changed\"\n// broadcaster. A host write route calls `publishFileChange(relPath)` after a\n// successful write; subscribed UI tabs refetch. Extracted so MulmoClaude and\n// MulmoTerminal forward to the SAME plugin-scoped channels (the live-refresh\n// contract the markdown/html Views subscribe to) without duplicating the logic.\n//\n// Everything host-specific is INJECTED via `configureFileChangePublisher` (the\n// pubsub, the workspace root, the path→posix normaliser, the host's own primary\n// channel, the plugin-scope matchers, and any side-effect), so this package owns\n// only the orchestration + the `plugin:<scope>:file:<path>` channel format and\n// touches neither host's pubsub-channel config nor its frontend subscribers.\nimport { stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/** Payload published on every file-change channel. `mtimeMs` is the post-write\n * modified time — monotonic, suitable for cache-busting (`?v=`) and out-of-order\n * drops. */\nexport interface FileChannelPayload {\n path: string;\n mtimeMs: number;\n}\n\n/** A plugin View that wants live-refresh: when `matches(posixPath)` is true, the\n * change is forwarded to `plugin:<scope>:file:<path>` (the channel the View's\n * runtime subscribes to). */\nexport interface FileChangeScope {\n scope: string;\n matches: (posixPath: string) => boolean;\n}\n\nexport interface FileChangePublisherConfig {\n /** Host pubsub publish. */\n publish: (channel: string, payload: FileChannelPayload) => void;\n /** Workspace root — joined with the relative path to stat the post-write mtime. */\n workspaceRoot: string;\n /** Normalise a workspace-relative path to POSIX (drives both `payload.path` and\n * every channel suffix, so they can't drift on mixed separators). */\n toPosix: (relativePath: string) => string;\n /** The host's primary file channel (e.g. a Files explorer's `file:<path>`).\n * Omit when the host has no general file subscriber (e.g. MulmoTerminal). */\n primaryChannel?: (posixPath: string) => string;\n /** Plugin Views to forward to. */\n pluginScopes?: FileChangeScope[];\n /** Optional side-effect after publishing (e.g. a topic-index regen). Receives the\n * posix path + payload; may itself call `publishFileChange` for derived files. */\n onPublished?: (posixPath: string, payload: FileChannelPayload) => void;\n /** Optional warn logger; a publish/stat failure logs but never throws (callers\n * fire-and-forget, so a throw would be an unhandled rejection). */\n warn?: (message: string, data?: Record<string, unknown>) => void;\n}\n\nlet config: FileChangePublisherConfig | null = null;\n\n/** Wire the publisher to a host. Call once at startup, before any write route. */\nexport function configureFileChangePublisher(cfg: FileChangePublisherConfig): void {\n config = cfg;\n}\n\n/** Clear the binding — test-only. */\nexport function resetFileChangePublisher(): void {\n config = null;\n}\n\n/** The plugin-scoped live-refresh channel a View subscribes to. */\nexport function pluginFileChannel(scope: string, posixPath: string): string {\n return `plugin:${scope}:file:${posixPath}`;\n}\n\n/** Publish a file-change for a workspace-relative path: the primary channel (if any)\n * + every matching plugin scope, with the post-write mtime. No-op until configured. */\nexport async function publishFileChange(relativePath: string): Promise<void> {\n const cfg = config;\n if (!cfg) return;\n // `relativePath` comes from the host's write routes. Contain it before doing\n // anything: a path that escapes the workspace is dropped entirely — we\n // neither stat an arbitrary file nor broadcast an out-of-workspace path to\n // subscribers or host side-effects. Defence-in-depth, since this is the\n // shared package both hosts use. `root` carries a trailing separator and the\n // guard is a single `startsWith` early-return — the canonical containment\n // shape (so the check is unambiguous for both readers and static analysis).\n const root = path.resolve(cfg.workspaceRoot) + path.sep;\n const absPath = path.join(root, relativePath);\n if (!absPath.startsWith(root)) {\n cfg.warn?.(\"ignoring file-change for path outside workspace\", { path: relativePath });\n return;\n }\n let mtimeMs: number;\n try {\n ({ mtimeMs } = await stat(absPath));\n } catch (err) {\n cfg.warn?.(\"stat failed; falling back to Date.now()\", { path: relativePath, error: errMsg(err) });\n mtimeMs = Date.now();\n }\n const posixPath = cfg.toPosix(relativePath);\n const payload: FileChannelPayload = { path: posixPath, mtimeMs };\n\n if (cfg.primaryChannel) {\n safePublish(cfg, cfg.primaryChannel(posixPath), payload, \"primary publish failed\");\n }\n for (const { scope, matches } of cfg.pluginScopes ?? []) {\n if (!matches(posixPath)) continue;\n safePublish(cfg, pluginFileChannel(scope, posixPath), payload, `${scope} plugin forward failed`);\n }\n cfg.onPublished?.(posixPath, payload);\n}\n\nfunction safePublish(cfg: FileChangePublisherConfig, channel: string, payload: FileChannelPayload, failMsg: string): void {\n try {\n cfg.publish(channel, payload);\n } catch (err) {\n cfg.warn?.(`${failMsg}; subscribers will miss this event`, { channel, error: errMsg(err) });\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n"],"mappings":";;;;;;AAmDA,IAAI,SAA2C;;AAG/C,SAAgB,6BAA6B,KAAsC;CACjF,SAAS;AACX;;AAGA,SAAgB,2BAAiC;CAC/C,SAAS;AACX;;AAGA,SAAgB,kBAAkB,OAAe,WAA2B;CAC1E,OAAO,UAAU,MAAM,QAAQ;AACjC;;;AAIA,eAAsB,kBAAkB,cAAqC;CAC3E,MAAM,MAAM;CACZ,IAAI,CAAC,KAAK;CAQV,MAAM,OAAO,UAAA,QAAK,QAAQ,IAAI,aAAa,IAAI,UAAA,QAAK;CACpD,MAAM,UAAU,UAAA,QAAK,KAAK,MAAM,YAAY;CAC5C,IAAI,CAAC,QAAQ,WAAW,IAAI,GAAG;EAC7B,IAAI,OAAO,mDAAmD,EAAE,MAAM,aAAa,CAAC;EACpF;CACF;CACA,IAAI;CACJ,IAAI;EACF,CAAC,CAAE,WAAY,OAAA,GAAA,iBAAA,KAAA,CAAW,OAAO;CACnC,SAAS,KAAK;EACZ,IAAI,OAAO,2CAA2C;GAAE,MAAM;GAAc,OAAO,OAAO,GAAG;EAAE,CAAC;EAChG,UAAU,KAAK,IAAI;CACrB;CACA,MAAM,YAAY,IAAI,QAAQ,YAAY;CAC1C,MAAM,UAA8B;EAAE,MAAM;EAAW;CAAQ;CAE/D,IAAI,IAAI,gBACN,YAAY,KAAK,IAAI,eAAe,SAAS,GAAG,SAAS,wBAAwB;CAEnF,KAAK,MAAM,EAAE,OAAO,aAAa,IAAI,gBAAgB,CAAC,GAAG;EACvD,IAAI,CAAC,QAAQ,SAAS,GAAG;EACzB,YAAY,KAAK,kBAAkB,OAAO,SAAS,GAAG,SAAS,GAAG,MAAM,uBAAuB;CACjG;CACA,IAAI,cAAc,WAAW,OAAO;AACtC;AAEA,SAAS,YAAY,KAAgC,SAAiB,SAA6B,SAAuB;CACxH,IAAI;EACF,IAAI,QAAQ,SAAS,OAAO;CAC9B,SAAS,KAAK;EACZ,IAAI,OAAO,GAAG,QAAQ,qCAAqC;GAAE;GAAS,OAAO,OAAO,GAAG;EAAE,CAAC;CAC5F;AACF;AAEA,SAAS,OAAO,KAAsB;CACpC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Payload published on every file-change channel. `mtimeMs` is the post-write
|
|
2
|
+
* modified time — monotonic, suitable for cache-busting (`?v=`) and out-of-order
|
|
3
|
+
* drops. */
|
|
4
|
+
export interface FileChannelPayload {
|
|
5
|
+
path: string;
|
|
6
|
+
mtimeMs: number;
|
|
7
|
+
}
|
|
8
|
+
/** A plugin View that wants live-refresh: when `matches(posixPath)` is true, the
|
|
9
|
+
* change is forwarded to `plugin:<scope>:file:<path>` (the channel the View's
|
|
10
|
+
* runtime subscribes to). */
|
|
11
|
+
export interface FileChangeScope {
|
|
12
|
+
scope: string;
|
|
13
|
+
matches: (posixPath: string) => boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface FileChangePublisherConfig {
|
|
16
|
+
/** Host pubsub publish. */
|
|
17
|
+
publish: (channel: string, payload: FileChannelPayload) => void;
|
|
18
|
+
/** Workspace root — joined with the relative path to stat the post-write mtime. */
|
|
19
|
+
workspaceRoot: string;
|
|
20
|
+
/** Normalise a workspace-relative path to POSIX (drives both `payload.path` and
|
|
21
|
+
* every channel suffix, so they can't drift on mixed separators). */
|
|
22
|
+
toPosix: (relativePath: string) => string;
|
|
23
|
+
/** The host's primary file channel (e.g. a Files explorer's `file:<path>`).
|
|
24
|
+
* Omit when the host has no general file subscriber (e.g. MulmoTerminal). */
|
|
25
|
+
primaryChannel?: (posixPath: string) => string;
|
|
26
|
+
/** Plugin Views to forward to. */
|
|
27
|
+
pluginScopes?: FileChangeScope[];
|
|
28
|
+
/** Optional side-effect after publishing (e.g. a topic-index regen). Receives the
|
|
29
|
+
* posix path + payload; may itself call `publishFileChange` for derived files. */
|
|
30
|
+
onPublished?: (posixPath: string, payload: FileChannelPayload) => void;
|
|
31
|
+
/** Optional warn logger; a publish/stat failure logs but never throws (callers
|
|
32
|
+
* fire-and-forget, so a throw would be an unhandled rejection). */
|
|
33
|
+
warn?: (message: string, data?: Record<string, unknown>) => void;
|
|
34
|
+
}
|
|
35
|
+
/** Wire the publisher to a host. Call once at startup, before any write route. */
|
|
36
|
+
export declare function configureFileChangePublisher(cfg: FileChangePublisherConfig): void;
|
|
37
|
+
/** Clear the binding — test-only. */
|
|
38
|
+
export declare function resetFileChangePublisher(): void;
|
|
39
|
+
/** The plugin-scoped live-refresh channel a View subscribes to. */
|
|
40
|
+
export declare function pluginFileChannel(scope: string, posixPath: string): string;
|
|
41
|
+
/** Publish a file-change for a workspace-relative path: the primary channel (if any)
|
|
42
|
+
* + every matching plugin scope, with the post-write mtime. No-op until configured. */
|
|
43
|
+
export declare function publishFileChange(relativePath: string): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const require_deriveAll = require("./deriveAll-VRWrs3SF.cjs");
|
|
2
|
+
require("./collection/index.cjs");
|
|
3
|
+
//#region src/feeds/ingestTypes.ts
|
|
4
|
+
var AGENT_INGEST_KIND = "agent";
|
|
5
|
+
var FEED_SCHEDULE_SET = new Set(require_deriveAll.FEED_SCHEDULES);
|
|
6
|
+
function isFeedSchedule(value) {
|
|
7
|
+
return typeof value === "string" && FEED_SCHEDULE_SET.has(value);
|
|
8
|
+
}
|
|
9
|
+
/** Default cap on stored records per feed when `ingest.maxItems` is
|
|
10
|
+
* omitted. Keeps high-volume feeds (news / podcasts) bounded. */
|
|
11
|
+
var DEFAULT_FEED_MAX_ITEMS = 100;
|
|
12
|
+
//#endregion
|
|
13
|
+
Object.defineProperty(exports, "AGENT_INGEST_KIND", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function() {
|
|
16
|
+
return AGENT_INGEST_KIND;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports, "DEFAULT_FEED_MAX_ITEMS", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function() {
|
|
22
|
+
return DEFAULT_FEED_MAX_ITEMS;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(exports, "isFeedSchedule", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function() {
|
|
28
|
+
return isFeedSchedule;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=ingestTypes-DDOVhOYC.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingestTypes-DDOVhOYC.cjs","names":[],"sources":["../src/feeds/ingestTypes.ts"],"sourcesContent":["// Declarative retrieval config for the \"Feeds\" mechanism. A Feed is a\n// CollectionSchema plus this `ingest` block, registered as data (NOT as\n// a skill) under `<workspace>/feeds/<slug>/schema.json`. The host's\n// retrieval engine reads it to periodically refill the collection's\n// records via the shared collections io layer.\n//\n// The ingest vocab (INGEST_KINDS / FEED_SCHEDULES + their literal-union types)\n// lives in the sibling `../collection` subpath alongside the schema contract, so\n// the package's schema validator can enforce it. Re-exported here so the feeds\n// engine's existing importers resolve them unchanged.\nimport { type CollectionIngest, INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule } from \"../collection/index.js\";\n//\n// Two flavours: the declarative kinds (`rss`/`atom`/`http-json`) fetch-and-map,\n// and `agent` dispatches a hidden worker. The `code` kind (LLM-generated\n// deterministic transform) is still reserved for a future retriever.\n\n// The agent ingest kind. Defined HERE (not imported from `../collection`) on\n// purpose: the engine only needs the literal to branch, and re-importing it as a\n// VALUE keeps this module free of a value dependency on the collection vocab.\n// Core owns its own copy for the schema validator; this matches the same literal.\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\nexport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule };\n\nconst FEED_SCHEDULE_SET: ReadonlySet<string> = new Set(FEED_SCHEDULES);\n\nexport function isFeedSchedule(value: unknown): value is FeedSchedule {\n return typeof value === \"string\" && FEED_SCHEDULE_SET.has(value);\n}\n\n/** Default cap on stored records per feed when `ingest.maxItems` is\n * omitted. Keeps high-volume feeds (news / podcasts) bounded. */\nexport const DEFAULT_FEED_MAX_ITEMS = 100;\n\n/** Declarative field map: target collection field name → source path\n * into the raw item (dot/bracket path, e.g. `\"title\"` or\n * `\"data.name\"`). */\nexport type IngestFieldMap = Record<string, string>;\n\n/** Declarative retrieval (`rss`/`atom`/`http-json`): the host fetches `url`\n * and projects each item through `map`. The canonical schema (in\n * `../collection`) only promises the minimal `CollectionIngest`; this\n * feeds-only subtype narrows those + adds the retrieval fields the engine\n * needs. */\nexport interface DeclarativeIngestSpec extends CollectionIngest {\n /** Which declarative retriever handles this feed. */\n kind: IngestKind;\n /** Endpoint to fetch (http/https). */\n url: string;\n /** Refresh cadence. */\n schedule: FeedSchedule;\n /** `http-json` only: dot/bracket path to the array of items in the\n * response (e.g. `\"hourly[]\"` or `\"data.results[]\"`). Ignored for\n * `rss`/`atom`, which yield items natively. */\n itemsAt?: string;\n /** target field → source path. Projects each raw item into a record\n * whose keys match the schema's `fields`. */\n map: IngestFieldMap;\n /** Optional source path used to derive the primaryKey value when the\n * mapped record's primaryKey is empty (e.g. `\"feedId\"`). Falls back\n * to a content hash of the record. */\n idFrom?: string;\n /** Cap on stored records. After each fetch the feed keeps only the\n * newest `maxItems` (ordered by the schema's first `date` field) and\n * deletes the rest. Defaults to {@link DEFAULT_FEED_MAX_ITEMS} when\n * omitted; `0` disables the cap (keep everything). Pruning is skipped\n * when the schema has no `date` field to order by. */\n maxItems?: number;\n}\n\n/** Agent-performed retrieval (`kind: \"agent\"`). No `url`/`map`: the host seeds\n * a hidden background worker (origin `system`) in `role` with `template` + a\n * summary of every record, and the worker edits the records itself via the\n * collections io layer. Valid on any collection (primarily skill-backed). */\nexport interface AgentIngestSpec extends CollectionIngest {\n kind: AgentIngestKind;\n /** Refresh cadence (same vocabulary as declarative feeds). */\n schedule: FeedSchedule;\n /** Role id the scheduled hidden worker runs in. */\n role: string;\n /** Skill-relative template path (under `templates/`) seeding the worker. */\n template: string;\n}\n\n/** The `ingest` block carried on a `CollectionSchema`, discriminated on `kind`. */\nexport type IngestSpec = DeclarativeIngestSpec | AgentIngestSpec;\n"],"mappings":";;;AAoBA,IAAa,oBAAoB;AAKjC,IAAM,oBAAyC,IAAI,IAAI,kBAAA,cAAc;AAErE,SAAgB,eAAe,OAAuC;CACpE,OAAO,OAAO,UAAU,YAAY,kBAAkB,IAAI,KAAK;AACjE;;;AAIA,IAAa,yBAAyB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { WriteJson } from './store.js';
|
|
2
|
+
import { NotifierEntry, NotifierEvent, NotifierHistoryEntry, NotifierSeverity, PublishInput } from './types.js';
|
|
3
|
+
export { NOTIFIER_LIMITS, validatePublishInput } from './validate.js';
|
|
4
|
+
/** Minimal logger the engine needs. The host passes its structured
|
|
5
|
+
* logger; absent one, failures are swallowed (the engine never throws
|
|
6
|
+
* on a fan-out/persist-best-effort path). */
|
|
7
|
+
export interface NotifierLogger {
|
|
8
|
+
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
9
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface NotifierConfig {
|
|
12
|
+
/** Atomic JSON writer (the host's `writeJsonAtomic`). */
|
|
13
|
+
writeJson: WriteJson;
|
|
14
|
+
/** Fan-out sink — the host binds this to `pubsub.publish(channel, event)`. */
|
|
15
|
+
publishEvent: (event: NotifierEvent) => void;
|
|
16
|
+
/** Optional logger. */
|
|
17
|
+
log?: NotifierLogger;
|
|
18
|
+
}
|
|
19
|
+
/** Wire the engine's I/O deps. Call once at startup, before the first
|
|
20
|
+
* mutation. Does NOT set file paths — those are set independently via
|
|
21
|
+
* `setNotifierFilePaths` so a host can bind production paths at module
|
|
22
|
+
* load and a test can override them without re-supplying the deps. */
|
|
23
|
+
export declare function configureNotifier(injected: NotifierConfig): void;
|
|
24
|
+
type NotifierEventListener = (event: NotifierEvent) => void;
|
|
25
|
+
/** Register an in-process listener for engine events. Returns an
|
|
26
|
+
* unsubscribe function the caller can use during teardown. */
|
|
27
|
+
export declare function onEvent(listener: NotifierEventListener): () => void;
|
|
28
|
+
/** Point the engine at its active/history files. Resets the write
|
|
29
|
+
* queue, so callers must not have in-flight mutations. The host calls
|
|
30
|
+
* this once with the workspace paths; tests call it per-case with temp
|
|
31
|
+
* files. */
|
|
32
|
+
export declare function setNotifierFilePaths(paths: {
|
|
33
|
+
active: string;
|
|
34
|
+
history: string;
|
|
35
|
+
}): void;
|
|
36
|
+
/** Test-only: clear config + queue so each suite starts clean. */
|
|
37
|
+
export declare function resetNotifier(): void;
|
|
38
|
+
export declare function publish<TPluginData = unknown>(input: PublishInput<TPluginData>): Promise<{
|
|
39
|
+
id: string;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function clear(entryId: string): Promise<void>;
|
|
42
|
+
export declare function cancel(entryId: string): Promise<void>;
|
|
43
|
+
/** In-place update for an active entry. Only the fields present on
|
|
44
|
+
* `patch` are rewritten; `id`, `pluginPkg`, `lifecycle`, and
|
|
45
|
+
* `createdAt` stay fixed. Emits a single `"updated"` event with the
|
|
46
|
+
* post-mutation entry — no history record is written because the
|
|
47
|
+
* entry is still active, just with refreshed content.
|
|
48
|
+
*
|
|
49
|
+
* No-ops (no throw) when the id is unknown, the entry belongs to a
|
|
50
|
+
* different plugin, or the merged shape would violate
|
|
51
|
+
* `validatePublishInput`. The silent skip matches `clearForPlugin`'s
|
|
52
|
+
* isolation semantics; validation failures are logged for diagnosis. */
|
|
53
|
+
export declare function updateForPlugin<TPluginData = unknown>(pluginPkg: string, entryId: string, patch: {
|
|
54
|
+
severity?: NotifierSeverity;
|
|
55
|
+
title?: string;
|
|
56
|
+
body?: string;
|
|
57
|
+
navigateTarget?: string;
|
|
58
|
+
pluginData?: TPluginData;
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
/** Plugin-scoped point lookup. Returns the entry by id, but only if it
|
|
61
|
+
* belongs to the caller's plugin; otherwise undefined. Cross-plugin
|
|
62
|
+
* reads return undefined for isolation — same property as
|
|
63
|
+
* `clearForPlugin` / `updateForPlugin`. */
|
|
64
|
+
export declare function getForPlugin(pluginPkg: string, entryId: string): Promise<NotifierEntry | undefined>;
|
|
65
|
+
/** Plugin-scoped clear. Same as `clear` but no-ops if the entry's
|
|
66
|
+
* `pluginPkg` doesn't match the caller's, so a plugin can't dismiss
|
|
67
|
+
* another plugin's notification by guessing or scraping its id. */
|
|
68
|
+
export declare function clearForPlugin(pluginPkg: string, entryId: string): Promise<void>;
|
|
69
|
+
export declare function get(entryId: string): Promise<NotifierEntry | undefined>;
|
|
70
|
+
export declare function listFor(pluginPkg: string): Promise<NotifierEntry[]>;
|
|
71
|
+
export declare function listAll(): Promise<NotifierEntry[]>;
|
|
72
|
+
export declare function listHistory(): Promise<NotifierHistoryEntry[]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_notifier = require("../notifier-bS8IEeLA.cjs");
|
|
3
|
+
exports.HISTORY_CAP = require_notifier.HISTORY_CAP;
|
|
4
|
+
exports.NOTIFIER_LIFECYCLES = require_notifier.NOTIFIER_LIFECYCLES;
|
|
5
|
+
exports.NOTIFIER_LIMITS = require_notifier.NOTIFIER_LIMITS;
|
|
6
|
+
exports.NOTIFIER_SEVERITIES = require_notifier.NOTIFIER_SEVERITIES;
|
|
7
|
+
exports.cancel = require_notifier.cancel;
|
|
8
|
+
exports.clear = require_notifier.clear;
|
|
9
|
+
exports.clearForPlugin = require_notifier.clearForPlugin;
|
|
10
|
+
exports.configureNotifier = require_notifier.configureNotifier;
|
|
11
|
+
exports.get = require_notifier.get;
|
|
12
|
+
exports.getForPlugin = require_notifier.getForPlugin;
|
|
13
|
+
exports.listAll = require_notifier.listAll;
|
|
14
|
+
exports.listFor = require_notifier.listFor;
|
|
15
|
+
exports.listHistory = require_notifier.listHistory;
|
|
16
|
+
exports.onEvent = require_notifier.onEvent;
|
|
17
|
+
exports.publish = require_notifier.publish;
|
|
18
|
+
exports.resetNotifier = require_notifier.resetNotifier;
|
|
19
|
+
exports.setNotifierFilePaths = require_notifier.setNotifierFilePaths;
|
|
20
|
+
exports.updateForPlugin = require_notifier.updateForPlugin;
|
|
21
|
+
exports.validatePublishInput = require_notifier.validatePublishInput;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NotifierFile, NotifierHistoryFile } from './types.js';
|
|
2
|
+
/** Injected atomic JSON writer — the host's `writeJsonAtomic`. */
|
|
3
|
+
export type WriteJson = (filePath: string, data: unknown) => Promise<void>;
|
|
4
|
+
/** Read the active-entries file. Returns an empty store when the file
|
|
5
|
+
* doesn't exist yet (first ever call on a fresh workspace). Any other
|
|
6
|
+
* read or parse failure throws — the caller has to decide whether to
|
|
7
|
+
* surface or recover, since silently treating "malformed file" as
|
|
8
|
+
* "no entries" would lose data. */
|
|
9
|
+
export declare function loadActive(filePath: string): Promise<NotifierFile>;
|
|
10
|
+
/** Write the active-entries file via the injected atomic writer so a
|
|
11
|
+
* half-written file is never visible to readers. The caller serialises
|
|
12
|
+
* writes (engine.ts queues mutations) — this function makes no
|
|
13
|
+
* concurrency guarantees of its own. */
|
|
14
|
+
export declare function saveActive(writeJson: WriteJson, filePath: string, state: NotifierFile): Promise<void>;
|
|
15
|
+
/** Read the history file. Empty array on first run. Same parse-error
|
|
16
|
+
* policy as `loadActive`. */
|
|
17
|
+
export declare function loadHistory(filePath: string): Promise<NotifierHistoryFile>;
|
|
18
|
+
export declare function saveHistory(writeJson: WriteJson, filePath: string, state: NotifierHistoryFile): Promise<void>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Two notification shapes, distinguished by who fires the close call:
|
|
2
|
+
*
|
|
3
|
+
* `fyi` — informational. The host (bell panel) clears it when the
|
|
4
|
+
* user dismisses the row. No deep-link target.
|
|
5
|
+
* `action` — pending obligation. The plugin clears it when the
|
|
6
|
+
* underlying domain state changes (the user paid the tax,
|
|
7
|
+
* viewed the digest, etc.). The bell row navigates to
|
|
8
|
+
* `navigateTarget` on click.
|
|
9
|
+
*
|
|
10
|
+
* The engine reads `lifecycle` only to enforce two publish-time rules
|
|
11
|
+
* (everything downstream — pubsub fan-out, persistence, history — is
|
|
12
|
+
* lifecycle-blind):
|
|
13
|
+
*
|
|
14
|
+
* 1. `action` requires a non-empty `navigateTarget`. Without one,
|
|
15
|
+
* clicking the row does nothing and the entry is a degraded fyi.
|
|
16
|
+
* 2. `action` cannot use `info` severity. A low-priority obligation
|
|
17
|
+
* is incoherent — fyi if it's a ping, `nudge`/`urgent` if it's a
|
|
18
|
+
* real obligation worth a landing page.
|
|
19
|
+
*
|
|
20
|
+
* Both rules are mirrored in the HTTP layer so plugin-runtime callers
|
|
21
|
+
* and HTTP callers hit the same wall. */
|
|
22
|
+
export declare const NOTIFIER_LIFECYCLES: readonly ["fyi", "action"];
|
|
23
|
+
export type NotifierLifecycle = (typeof NOTIFIER_LIFECYCLES)[number];
|
|
24
|
+
/** Severity drives badge color (gray / amber / red, worst-wins) and
|
|
25
|
+
* in a future iteration channel routing. Mostly stored verbatim by
|
|
26
|
+
* the engine; the one engine-visible interaction is the rule that
|
|
27
|
+
* `action` lifecycle cannot pair with `info` severity (see
|
|
28
|
+
* `NotifierLifecycle` above). */
|
|
29
|
+
export declare const NOTIFIER_SEVERITIES: readonly ["info", "nudge", "urgent"];
|
|
30
|
+
export type NotifierSeverity = (typeof NOTIFIER_SEVERITIES)[number];
|
|
31
|
+
export interface NotifierEntry<TPluginData = unknown> {
|
|
32
|
+
/** Engine-assigned UUID. Generated synchronously inside `publish()`
|
|
33
|
+
* so the caller can use it before persistence completes. */
|
|
34
|
+
id: string;
|
|
35
|
+
/** Plugin namespace (e.g. `"encore"`, `"debug__system"`). The
|
|
36
|
+
* engine never inspects it — used only for `listFor()` filtering
|
|
37
|
+
* and as a UI grouping key. */
|
|
38
|
+
pluginPkg: string;
|
|
39
|
+
severity: NotifierSeverity;
|
|
40
|
+
lifecycle?: NotifierLifecycle;
|
|
41
|
+
title: string;
|
|
42
|
+
body?: string;
|
|
43
|
+
/** Optional in-app deep-link target (relative URL). The bell popup
|
|
44
|
+
* routes here on row click, with `¬ificationId=<id>` appended
|
|
45
|
+
* so the landing page can identify which entry to clear. The
|
|
46
|
+
* engine doesn't read this — it's a UI hint stored on the entry. */
|
|
47
|
+
navigateTarget?: string;
|
|
48
|
+
/** Opaque to the engine. Round-trips through JSON unchanged; only
|
|
49
|
+
* the originating plugin's UI knows the shape. */
|
|
50
|
+
pluginData?: TPluginData;
|
|
51
|
+
/** ISO-8601 timestamp set at `publish()` time. */
|
|
52
|
+
createdAt: string;
|
|
53
|
+
}
|
|
54
|
+
/** A history entry — a `NotifierEntry` after it has been cleared or
|
|
55
|
+
* cancelled, with the terminal type and timestamp recorded. The
|
|
56
|
+
* bell popup's "History" section renders these read-only. */
|
|
57
|
+
export interface NotifierHistoryEntry<TPluginData = unknown> extends NotifierEntry<TPluginData> {
|
|
58
|
+
terminalType: "cleared" | "cancelled";
|
|
59
|
+
terminalAt: string;
|
|
60
|
+
}
|
|
61
|
+
/** Caller-supplied input for `publish()`. The engine fills in `id`
|
|
62
|
+
* and `createdAt`; everything else flows through verbatim.
|
|
63
|
+
*
|
|
64
|
+
* Two publish-time rules apply to `action` lifecycle (see
|
|
65
|
+
* `NotifierLifecycle`):
|
|
66
|
+
*
|
|
67
|
+
* - `navigateTarget` MUST be a non-empty string.
|
|
68
|
+
* - `severity` MUST NOT be `"info"`.
|
|
69
|
+
*
|
|
70
|
+
* Violations cause `publish()` to throw. Currently expressed as
|
|
71
|
+
* runtime validation rather than a discriminated-union type, so the
|
|
72
|
+
* fields below are all individually optional / loose at the
|
|
73
|
+
* type-level. */
|
|
74
|
+
export interface PublishInput<TPluginData = unknown> {
|
|
75
|
+
pluginPkg: string;
|
|
76
|
+
severity: NotifierSeverity;
|
|
77
|
+
title: string;
|
|
78
|
+
body?: string;
|
|
79
|
+
lifecycle?: NotifierLifecycle;
|
|
80
|
+
navigateTarget?: string;
|
|
81
|
+
pluginData?: TPluginData;
|
|
82
|
+
}
|
|
83
|
+
/** On-disk shape of `~/mulmoclaude/data/notifier/active.json`. Holds
|
|
84
|
+
* only entries that haven't been cleared or cancelled — the file is
|
|
85
|
+
* a snapshot, not an event log. */
|
|
86
|
+
export interface NotifierFile {
|
|
87
|
+
entries: Record<string, NotifierEntry>;
|
|
88
|
+
}
|
|
89
|
+
/** On-disk shape of `~/mulmoclaude/data/notifier/history.json`. Array
|
|
90
|
+
* of terminated entries newest-first, capped at `HISTORY_CAP` with
|
|
91
|
+
* FIFO eviction (push at index 0, slice from the tail). */
|
|
92
|
+
export interface NotifierHistoryFile {
|
|
93
|
+
entries: NotifierHistoryEntry[];
|
|
94
|
+
}
|
|
95
|
+
/** History size cap. The bell popup's History section renders this
|
|
96
|
+
* many entries; older ones fall off when new terminations land. */
|
|
97
|
+
export declare const HISTORY_CAP = 50;
|
|
98
|
+
/** Pub-sub event published on the host's notifier channel after every
|
|
99
|
+
* successful state change. Discriminated union — subscribers switch
|
|
100
|
+
* on `type` to keep TypeScript narrowing the rest of the payload.
|
|
101
|
+
*
|
|
102
|
+
* `updated` carries the post-mutation entry — the receiver swaps
|
|
103
|
+
* the matching `id` in their local active set. Reserved for in-
|
|
104
|
+
* place edits via `updateForPlugin`; no history record is written
|
|
105
|
+
* because the entry is still active, just with refreshed content. */
|
|
106
|
+
export type NotifierEvent = {
|
|
107
|
+
type: "published";
|
|
108
|
+
entry: NotifierEntry;
|
|
109
|
+
} | {
|
|
110
|
+
type: "cleared";
|
|
111
|
+
id: string;
|
|
112
|
+
} | {
|
|
113
|
+
type: "cancelled";
|
|
114
|
+
id: string;
|
|
115
|
+
} | {
|
|
116
|
+
type: "updated";
|
|
117
|
+
entry: NotifierEntry;
|
|
118
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PublishInput } from './types.js';
|
|
2
|
+
/** Hard caps on publish-input fields. The engine reads each entry on
|
|
3
|
+
* every list/get call (no in-memory cache), so unbounded fields hurt
|
|
4
|
+
* every reader. Caps chosen to be generous for legitimate UX copy
|
|
5
|
+
* while bounding active.json growth: a notification fundamentally is
|
|
6
|
+
* a short blurb, not a document. */
|
|
7
|
+
export declare const NOTIFIER_LIMITS: {
|
|
8
|
+
readonly titleMax: 200;
|
|
9
|
+
readonly bodyMax: 4000;
|
|
10
|
+
readonly navigateTargetMax: 1000;
|
|
11
|
+
readonly pluginDataMaxBytes: number;
|
|
12
|
+
};
|
|
13
|
+
/** Validate a `PublishInput`. Returns `null` if OK, or a
|
|
14
|
+
* human-readable error string. Order matters — shape/size errors are
|
|
15
|
+
* reported before lifecycle/severity coherence errors so the message
|
|
16
|
+
* the caller sees points at the most fundamental problem first. */
|
|
17
|
+
export declare function validatePublishInput(input: PublishInput): string | null;
|