orionfold-relay 0.18.0 → 0.19.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/cli.js +188 -19
- package/package.json +1 -1
- package/src/lib/apps/app-schedule-id.ts +37 -0
- package/src/lib/apps/manifest-trigger-dispatch.ts +57 -0
- package/src/lib/apps/registry.ts +27 -1
- package/src/lib/packs/cli.ts +2 -1
- package/src/lib/packs/install.ts +133 -21
- package/src/lib/packs/templates/relay-agency-pro/base/blueprints/relay-agency-pro--client-audit-export.yaml +53 -0
- package/src/lib/packs/templates/relay-agency-pro/base/blueprints/relay-agency-pro--cre-renewal-engine.yaml +93 -0
- package/src/lib/packs/templates/relay-agency-pro/base/blueprints/relay-agency-pro--intake-pipeline.yaml +69 -0
- package/src/lib/packs/templates/relay-agency-pro/base/blueprints/relay-agency-pro--month-end-close.yaml +72 -0
- package/src/lib/packs/templates/relay-agency-pro/base/blueprints/relay-agency-pro--new-business.yaml +84 -0
- package/src/lib/packs/templates/relay-agency-pro/base/manifest.yaml +120 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--cre-renewal-analyst/SKILL.md +69 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--cre-renewal-analyst/profile.yaml +19 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--finance-controller/SKILL.md +32 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--finance-controller/profile.yaml +17 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--governance-auditor/SKILL.md +28 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--governance-auditor/profile.yaml +17 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--intake-coordinator/SKILL.md +28 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--intake-coordinator/profile.yaml +17 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--proposal-writer/SKILL.md +19 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--proposal-writer/profile.yaml +17 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--prospect-researcher/SKILL.md +20 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--prospect-researcher/profile.yaml +19 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--sensitive-client-analyst/SKILL.md +24 -0
- package/src/lib/packs/templates/relay-agency-pro/base/profiles/relay-agency-pro--sensitive-client-analyst/profile.yaml +21 -0
- package/src/lib/packs/templates/relay-agency-pro/pack.yaml +23 -0
- package/src/lib/plugins/examples/echo-server/plugin.yaml +1 -1
- package/src/lib/plugins/examples/finance-pack/plugin.yaml +1 -1
- package/src/lib/plugins/examples/reading-radar/plugin.yaml +1 -1
- package/src/lib/plugins/registry.ts +1 -1
- package/src/lib/plugins/sdk/types.ts +1 -1
- package/src/lib/schedules/scheduler.ts +118 -2
package/dist/cli.js
CHANGED
|
@@ -1186,7 +1186,7 @@ var CURRENT_PLUGIN_API_VERSION, CAPABILITY_VALUES, ORIGIN_VALUES, PrimitivesBund
|
|
|
1186
1186
|
var init_types = __esm({
|
|
1187
1187
|
"src/lib/plugins/sdk/types.ts"() {
|
|
1188
1188
|
"use strict";
|
|
1189
|
-
CURRENT_PLUGIN_API_VERSION = "0.
|
|
1189
|
+
CURRENT_PLUGIN_API_VERSION = "0.19";
|
|
1190
1190
|
CAPABILITY_VALUES = ["fs", "net", "child_process", "env"];
|
|
1191
1191
|
ORIGIN_VALUES = ["ainative-internal", "third-party"];
|
|
1192
1192
|
PrimitivesBundleManifestSchema = z.object({
|
|
@@ -3458,7 +3458,8 @@ async function deleteAppCascade(appId, options = {}) {
|
|
|
3458
3458
|
filesRemoved: false,
|
|
3459
3459
|
projectRemoved: false,
|
|
3460
3460
|
profilesRemoved: 0,
|
|
3461
|
-
blueprintsRemoved: 0
|
|
3461
|
+
blueprintsRemoved: 0,
|
|
3462
|
+
schedulesRemoved: 0
|
|
3462
3463
|
};
|
|
3463
3464
|
const resolvedApps = path2.resolve(appsDir);
|
|
3464
3465
|
const rootDir = path2.resolve(appsDir, appId);
|
|
@@ -3478,7 +3479,23 @@ async function deleteAppCascade(appId, options = {}) {
|
|
|
3478
3479
|
const filesRemoved = deleteApp(appId, appsDir);
|
|
3479
3480
|
const profilesRemoved = sweepNamespacedProfiles(profilesDir, appId);
|
|
3480
3481
|
const blueprintsRemoved = sweepNamespacedBlueprints(blueprintsDir, appId);
|
|
3481
|
-
|
|
3482
|
+
let schedulesRemoved = 0;
|
|
3483
|
+
try {
|
|
3484
|
+
const { db: db3 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
3485
|
+
const { schedules: schedules2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
3486
|
+
const { like: like8 } = await import("drizzle-orm");
|
|
3487
|
+
const result = db3.delete(schedules2).where(like8(schedules2.id, `app:${appId}:%`)).run();
|
|
3488
|
+
schedulesRemoved = result.changes;
|
|
3489
|
+
} catch (err2) {
|
|
3490
|
+
console.error(`[registry] schedule sweep failed for app "${appId}":`, err2);
|
|
3491
|
+
}
|
|
3492
|
+
return {
|
|
3493
|
+
projectRemoved,
|
|
3494
|
+
filesRemoved,
|
|
3495
|
+
profilesRemoved,
|
|
3496
|
+
blueprintsRemoved,
|
|
3497
|
+
schedulesRemoved
|
|
3498
|
+
};
|
|
3482
3499
|
}
|
|
3483
3500
|
var AppArtifactRefSchema, AppBlueprintRefSchema, KitIdSchema, BindingRefSchema, LeafKpiSourceSchema, RatioKpiSourceSchema, KpiSourceSchema, KpiSpecSchema, ViewSchema, AppTableRefSchema, AppScheduleRefSchema, AppManifestSchema, DOW, APPS_CACHE_TTL_MS, appsCache, appsDetailCache, SLUG_RE;
|
|
3484
3501
|
var init_registry = __esm({
|
|
@@ -12411,6 +12428,14 @@ var init_registry5 = __esm({
|
|
|
12411
12428
|
});
|
|
12412
12429
|
|
|
12413
12430
|
// src/lib/schedules/installer.ts
|
|
12431
|
+
var installer_exports = {};
|
|
12432
|
+
__export(installer_exports, {
|
|
12433
|
+
installPluginSchedules: () => installPluginSchedules,
|
|
12434
|
+
installSchedulesFromSpecs: () => installSchedulesFromSpecs,
|
|
12435
|
+
listInstalledPluginScheduleIds: () => listInstalledPluginScheduleIds,
|
|
12436
|
+
removeOrphanSchedules: () => removeOrphanSchedules,
|
|
12437
|
+
removePluginSchedules: () => removePluginSchedules
|
|
12438
|
+
});
|
|
12414
12439
|
import { and as and16, like as like5, notInArray } from "drizzle-orm";
|
|
12415
12440
|
function pluginScheduleId(pluginId, scheduleId) {
|
|
12416
12441
|
return `${PLUGIN_SCHEDULE_PREFIX}${pluginId}:${scheduleId}`;
|
|
@@ -12508,6 +12533,10 @@ function removeOrphanSchedules(pluginId, keepIds) {
|
|
|
12508
12533
|
db.delete(schedules).where(and16(like5(schedules.id, pattern), notInArray(schedules.id, keepIds))).run();
|
|
12509
12534
|
}
|
|
12510
12535
|
}
|
|
12536
|
+
function listInstalledPluginScheduleIds(pluginId) {
|
|
12537
|
+
const pattern = `${PLUGIN_SCHEDULE_PREFIX}${pluginId}:%`;
|
|
12538
|
+
return db.select({ id: schedules.id }).from(schedules).where(like5(schedules.id, pattern)).all().map((r) => r.id);
|
|
12539
|
+
}
|
|
12511
12540
|
var PLUGIN_SCHEDULE_PREFIX;
|
|
12512
12541
|
var init_installer = __esm({
|
|
12513
12542
|
"src/lib/schedules/installer.ts"() {
|
|
@@ -12862,7 +12891,7 @@ var init_registry6 = __esm({
|
|
|
12862
12891
|
init_registry5();
|
|
12863
12892
|
init_installer();
|
|
12864
12893
|
init_schedule_spec();
|
|
12865
|
-
SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.
|
|
12894
|
+
SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.18"]);
|
|
12866
12895
|
pluginCache = null;
|
|
12867
12896
|
lastLoadedPluginIds = /* @__PURE__ */ new Set();
|
|
12868
12897
|
PluginTableSchema = z16.object({
|
|
@@ -24900,6 +24929,7 @@ var init_engine = __esm({
|
|
|
24900
24929
|
var manifest_trigger_dispatch_exports = {};
|
|
24901
24930
|
__export(manifest_trigger_dispatch_exports, {
|
|
24902
24931
|
dispatchBlueprintForRow: () => dispatchBlueprintForRow,
|
|
24932
|
+
dispatchScheduledBlueprint: () => dispatchScheduledBlueprint,
|
|
24903
24933
|
evaluateManifestTriggers: () => evaluateManifestTriggers
|
|
24904
24934
|
});
|
|
24905
24935
|
async function evaluateManifestTriggers(tableId, rowId, rowData) {
|
|
@@ -24975,6 +25005,41 @@ async function dispatchBlueprintForRow(input) {
|
|
|
24975
25005
|
return null;
|
|
24976
25006
|
}
|
|
24977
25007
|
}
|
|
25008
|
+
async function dispatchScheduledBlueprint(input) {
|
|
25009
|
+
const { appId, blueprintId, scheduleId } = input;
|
|
25010
|
+
try {
|
|
25011
|
+
const { instantiateBlueprint: instantiateBlueprint2 } = await Promise.resolve().then(() => (init_instantiator(), instantiator_exports));
|
|
25012
|
+
const { executeWorkflow: executeWorkflow2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
25013
|
+
const { workflowId } = await instantiateBlueprint2(blueprintId, {}, appId);
|
|
25014
|
+
executeWorkflow2(workflowId).catch((err2) => {
|
|
25015
|
+
console.error(
|
|
25016
|
+
`[manifest-trigger-dispatch] executeWorkflow ${workflowId} failed:`,
|
|
25017
|
+
err2
|
|
25018
|
+
);
|
|
25019
|
+
});
|
|
25020
|
+
return { workflowId };
|
|
25021
|
+
} catch (err2) {
|
|
25022
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
25023
|
+
console.error(
|
|
25024
|
+
`[manifest-trigger-dispatch] scheduled dispatch failed for app=${appId} blueprint=${blueprintId}:`,
|
|
25025
|
+
err2
|
|
25026
|
+
);
|
|
25027
|
+
try {
|
|
25028
|
+
await db.insert(notifications).values({
|
|
25029
|
+
id: crypto.randomUUID(),
|
|
25030
|
+
taskId: null,
|
|
25031
|
+
type: "task_failed",
|
|
25032
|
+
title: `Schedule failure in app "${appId}"`,
|
|
25033
|
+
body: `Blueprint "${blueprintId}" failed for schedule "${scheduleId}": ${message}`,
|
|
25034
|
+
read: false,
|
|
25035
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
25036
|
+
});
|
|
25037
|
+
} catch (nerr) {
|
|
25038
|
+
console.error(`[manifest-trigger-dispatch] notification write failed:`, nerr);
|
|
25039
|
+
}
|
|
25040
|
+
return null;
|
|
25041
|
+
}
|
|
25042
|
+
}
|
|
24978
25043
|
function findMatchingSubscriptions(apps, tableId) {
|
|
24979
25044
|
const out = [];
|
|
24980
25045
|
for (const app of apps) {
|
|
@@ -25517,6 +25582,35 @@ var init_tables = __esm({
|
|
|
25517
25582
|
}
|
|
25518
25583
|
});
|
|
25519
25584
|
|
|
25585
|
+
// src/lib/apps/app-schedule-id.ts
|
|
25586
|
+
var app_schedule_id_exports = {};
|
|
25587
|
+
__export(app_schedule_id_exports, {
|
|
25588
|
+
APP_SCHEDULE_PREFIX: () => APP_SCHEDULE_PREFIX,
|
|
25589
|
+
appScheduleId: () => appScheduleId,
|
|
25590
|
+
isAppScheduleId: () => isAppScheduleId,
|
|
25591
|
+
parseAppScheduleId: () => parseAppScheduleId
|
|
25592
|
+
});
|
|
25593
|
+
function appScheduleId(appId, scheduleId) {
|
|
25594
|
+
return `${APP_SCHEDULE_PREFIX}${appId}:${scheduleId}`;
|
|
25595
|
+
}
|
|
25596
|
+
function isAppScheduleId(id) {
|
|
25597
|
+
return id.startsWith(APP_SCHEDULE_PREFIX);
|
|
25598
|
+
}
|
|
25599
|
+
function parseAppScheduleId(id) {
|
|
25600
|
+
if (!isAppScheduleId(id)) return null;
|
|
25601
|
+
const rest = id.slice(APP_SCHEDULE_PREFIX.length);
|
|
25602
|
+
const sep = rest.indexOf(":");
|
|
25603
|
+
if (sep <= 0 || sep === rest.length - 1) return null;
|
|
25604
|
+
return { appId: rest.slice(0, sep), scheduleId: rest.slice(sep + 1) };
|
|
25605
|
+
}
|
|
25606
|
+
var APP_SCHEDULE_PREFIX;
|
|
25607
|
+
var init_app_schedule_id = __esm({
|
|
25608
|
+
"src/lib/apps/app-schedule-id.ts"() {
|
|
25609
|
+
"use strict";
|
|
25610
|
+
APP_SCHEDULE_PREFIX = "app:";
|
|
25611
|
+
}
|
|
25612
|
+
});
|
|
25613
|
+
|
|
25520
25614
|
// src/lib/packs/install.ts
|
|
25521
25615
|
var install_exports = {};
|
|
25522
25616
|
__export(install_exports, {
|
|
@@ -25529,8 +25623,8 @@ import { execFileSync as execFileSync3 } from "child_process";
|
|
|
25529
25623
|
import yaml12 from "js-yaml";
|
|
25530
25624
|
import semver from "semver";
|
|
25531
25625
|
function relayCoreVersion() {
|
|
25532
|
-
if (semver.valid("0.
|
|
25533
|
-
return "0.
|
|
25626
|
+
if (semver.valid("0.19.0")) {
|
|
25627
|
+
return "0.19.0";
|
|
25534
25628
|
}
|
|
25535
25629
|
try {
|
|
25536
25630
|
const root = getAppRoot(import.meta.dirname, 3);
|
|
@@ -25577,6 +25671,26 @@ async function installPack(source, options = {}) {
|
|
|
25577
25671
|
}
|
|
25578
25672
|
}
|
|
25579
25673
|
const resolved = resolvePackLayer(pack);
|
|
25674
|
+
const declaredBlueprints = new Set(
|
|
25675
|
+
pack.manifest.blueprints.map((bp) => bp.id)
|
|
25676
|
+
);
|
|
25677
|
+
for (const sched of pack.manifest.schedules) {
|
|
25678
|
+
if (!sched.cron) {
|
|
25679
|
+
throw new PackValidationError(
|
|
25680
|
+
`Manifest schedule "${sched.id}" has no cron expression.`
|
|
25681
|
+
);
|
|
25682
|
+
}
|
|
25683
|
+
if (!sched.runs) {
|
|
25684
|
+
throw new PackValidationError(
|
|
25685
|
+
`Manifest schedule "${sched.id}" has no "runs" blueprint.`
|
|
25686
|
+
);
|
|
25687
|
+
}
|
|
25688
|
+
if (!declaredBlueprints.has(sched.runs)) {
|
|
25689
|
+
throw new PackValidationError(
|
|
25690
|
+
`Manifest schedule "${sched.id}" runs blueprint "${sched.runs}", which the manifest does not declare.`
|
|
25691
|
+
);
|
|
25692
|
+
}
|
|
25693
|
+
}
|
|
25580
25694
|
const { ensureAppProject: ensureAppProject2 } = await Promise.resolve().then(() => (init_compose_integration(), compose_integration_exports));
|
|
25581
25695
|
const { ensureCustomer: ensureCustomer2 } = await Promise.resolve().then(() => (init_customers(), customers_exports));
|
|
25582
25696
|
const { createTable: createTable2, addRows: addRows2, listTables: listTables2 } = await Promise.resolve().then(() => (init_tables(), tables_exports));
|
|
@@ -25627,7 +25741,44 @@ async function installPack(source, options = {}) {
|
|
|
25627
25741
|
});
|
|
25628
25742
|
customersSeeded += 1;
|
|
25629
25743
|
}
|
|
25630
|
-
const
|
|
25744
|
+
const scheduleLogicalToReal = /* @__PURE__ */ new Map();
|
|
25745
|
+
let schedulesRegistered = 0;
|
|
25746
|
+
if (pack.manifest.schedules.length > 0) {
|
|
25747
|
+
const { appScheduleId: appScheduleId2 } = await Promise.resolve().then(() => (init_app_schedule_id(), app_schedule_id_exports));
|
|
25748
|
+
const { installSchedulesFromSpecs: installSchedulesFromSpecs2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
|
|
25749
|
+
const specs = pack.manifest.schedules.map((sched) => {
|
|
25750
|
+
const compositeId = appScheduleId2(pack.meta.id, sched.id);
|
|
25751
|
+
scheduleLogicalToReal.set(sched.id, compositeId);
|
|
25752
|
+
const name = typeof sched.name === "string" ? sched.name : titleCase3(sched.id);
|
|
25753
|
+
return {
|
|
25754
|
+
id: compositeId,
|
|
25755
|
+
name: `${name} (${pack.meta.id})`,
|
|
25756
|
+
version: pack.meta.version,
|
|
25757
|
+
// Display-only: the scheduler branches on the app: id prefix and
|
|
25758
|
+
// dispatches the blueprint; this prompt is never sent to an agent.
|
|
25759
|
+
prompt: `App schedule for "${pack.meta.id}" \u2014 runs blueprint "${sched.runs}".`,
|
|
25760
|
+
cronExpression: sched.cron,
|
|
25761
|
+
recurs: true,
|
|
25762
|
+
type: "scheduled"
|
|
25763
|
+
};
|
|
25764
|
+
});
|
|
25765
|
+
installSchedulesFromSpecs2(specs);
|
|
25766
|
+
schedulesRegistered = specs.length;
|
|
25767
|
+
const { db: db3 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
25768
|
+
const { schedules: schedulesTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
25769
|
+
const { and: and24, inArray: inArray7, isNull: isNull7 } = await import("drizzle-orm");
|
|
25770
|
+
db3.update(schedulesTable).set({ projectId: pack.meta.id }).where(
|
|
25771
|
+
and24(
|
|
25772
|
+
inArray7(schedulesTable.id, specs.map((s) => s.id)),
|
|
25773
|
+
isNull7(schedulesTable.projectId)
|
|
25774
|
+
)
|
|
25775
|
+
).run();
|
|
25776
|
+
}
|
|
25777
|
+
const droppedManifest = rewriteTableRefs(
|
|
25778
|
+
pack.manifest,
|
|
25779
|
+
logicalToReal,
|
|
25780
|
+
scheduleLogicalToReal
|
|
25781
|
+
);
|
|
25631
25782
|
if (pack.meta.entitlement) {
|
|
25632
25783
|
droppedManifest.entitlement = pack.meta.entitlement;
|
|
25633
25784
|
}
|
|
@@ -25637,6 +25788,10 @@ async function installPack(source, options = {}) {
|
|
|
25637
25788
|
profilesDir,
|
|
25638
25789
|
blueprintsDir
|
|
25639
25790
|
);
|
|
25791
|
+
if (blueprintsDropped > 0) {
|
|
25792
|
+
const { reloadBlueprints: reloadBlueprints2 } = await Promise.resolve().then(() => (init_registry3(), registry_exports3));
|
|
25793
|
+
reloadBlueprints2();
|
|
25794
|
+
}
|
|
25640
25795
|
return {
|
|
25641
25796
|
packId: pack.meta.id,
|
|
25642
25797
|
packVersion: pack.meta.version,
|
|
@@ -25645,7 +25800,8 @@ async function installPack(source, options = {}) {
|
|
|
25645
25800
|
customersSeeded,
|
|
25646
25801
|
profilesDropped,
|
|
25647
25802
|
blueprintsDropped,
|
|
25648
|
-
rowsSeeded
|
|
25803
|
+
rowsSeeded,
|
|
25804
|
+
schedulesRegistered
|
|
25649
25805
|
};
|
|
25650
25806
|
} finally {
|
|
25651
25807
|
cleanup();
|
|
@@ -25706,33 +25862,46 @@ function readTableSeed(resolved, logicalId) {
|
|
|
25706
25862
|
}
|
|
25707
25863
|
return [];
|
|
25708
25864
|
}
|
|
25709
|
-
function rewriteTableRefs(manifest, logicalToReal) {
|
|
25865
|
+
function rewriteTableRefs(manifest, logicalToReal, scheduleLogicalToReal = /* @__PURE__ */ new Map()) {
|
|
25710
25866
|
const rewritten = {
|
|
25711
25867
|
...manifest,
|
|
25712
25868
|
tables: manifest.tables.map((t) => {
|
|
25713
25869
|
const real2 = logicalToReal.get(t.id);
|
|
25714
25870
|
return real2 ? { ...t, id: real2 } : t;
|
|
25871
|
+
}),
|
|
25872
|
+
// Row-insert triggers bind to tables by the SAME logical id. Dispatch
|
|
25873
|
+
// (manifest-trigger-dispatch) matches trigger.table against the REAL
|
|
25874
|
+
// UUID, so an unrewritten ref silently never fires.
|
|
25875
|
+
blueprints: manifest.blueprints.map((bp) => {
|
|
25876
|
+
const triggerTable = bp.trigger?.table;
|
|
25877
|
+
if (!triggerTable) return bp;
|
|
25878
|
+
const real2 = logicalToReal.get(triggerTable);
|
|
25879
|
+
return real2 ? { ...bp, trigger: { ...bp.trigger, table: real2 } } : bp;
|
|
25880
|
+
}),
|
|
25881
|
+
schedules: manifest.schedules.map((s) => {
|
|
25882
|
+
const real2 = scheduleLogicalToReal.get(s.id);
|
|
25883
|
+
return real2 ? { ...s, id: real2 } : s;
|
|
25715
25884
|
})
|
|
25716
25885
|
};
|
|
25717
25886
|
if (rewritten.view) {
|
|
25718
|
-
rewritten.view =
|
|
25719
|
-
|
|
25720
|
-
|
|
25721
|
-
);
|
|
25887
|
+
rewritten.view = rewriteViewRefs(rewritten.view, {
|
|
25888
|
+
table: logicalToReal,
|
|
25889
|
+
schedule: scheduleLogicalToReal
|
|
25890
|
+
});
|
|
25722
25891
|
}
|
|
25723
25892
|
return rewritten;
|
|
25724
25893
|
}
|
|
25725
|
-
function
|
|
25894
|
+
function rewriteViewRefs(view, maps) {
|
|
25726
25895
|
if (Array.isArray(view)) {
|
|
25727
|
-
return view.map((v) =>
|
|
25896
|
+
return view.map((v) => rewriteViewRefs(v, maps));
|
|
25728
25897
|
}
|
|
25729
25898
|
if (view && typeof view === "object") {
|
|
25730
25899
|
const out = {};
|
|
25731
25900
|
for (const [key, value] of Object.entries(view)) {
|
|
25732
|
-
if (key === "table" && typeof value === "string") {
|
|
25733
|
-
out[key] =
|
|
25901
|
+
if ((key === "table" || key === "schedule") && typeof value === "string") {
|
|
25902
|
+
out[key] = maps[key].get(value) ?? value;
|
|
25734
25903
|
} else {
|
|
25735
|
-
out[key] =
|
|
25904
|
+
out[key] = rewriteViewRefs(value, maps);
|
|
25736
25905
|
}
|
|
25737
25906
|
}
|
|
25738
25907
|
return out;
|
|
@@ -25832,7 +26001,7 @@ async function runAdd(source, licenseUrl, io) {
|
|
|
25832
26001
|
licenseUrl
|
|
25833
26002
|
});
|
|
25834
26003
|
io.log(
|
|
25835
|
-
`Installed ${report.packId}@${report.packVersion}: ${report.projectCreated ? "project created" : "project reused"}, ${report.tablesCreated} table(s) (${report.rowsSeeded} row(s)), ${report.customersSeeded} customer(s), ${report.profilesDropped} profile(s), ${report.blueprintsDropped} blueprint(s).`
|
|
26004
|
+
`Installed ${report.packId}@${report.packVersion}: ${report.projectCreated ? "project created" : "project reused"}, ${report.tablesCreated} table(s) (${report.rowsSeeded} row(s)), ${report.customersSeeded} customer(s), ${report.profilesDropped} profile(s), ${report.blueprintsDropped} blueprint(s), ${report.schedulesRegistered} schedule(s).`
|
|
25836
26005
|
);
|
|
25837
26006
|
return 0;
|
|
25838
26007
|
} catch (err2) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composite DB id for app-manifest schedules: `app:<appId>:<scheduleId>`.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the plugin-schedule convention (`plugin:<pluginId>:<specId>`,
|
|
5
|
+
* src/lib/schedules/installer.ts). A deterministic id makes the install-time
|
|
6
|
+
* upsert idempotent and lets the scheduler recover the owning app at fire
|
|
7
|
+
* time without a schema change.
|
|
8
|
+
*
|
|
9
|
+
* Kept in its own module (no DB imports) so both the pack installer and the
|
|
10
|
+
* scheduler can import it statically without touching the runtime-registry
|
|
11
|
+
* chain (TDR-032).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const APP_SCHEDULE_PREFIX = "app:";
|
|
15
|
+
|
|
16
|
+
export function appScheduleId(appId: string, scheduleId: string): string {
|
|
17
|
+
return `${APP_SCHEDULE_PREFIX}${appId}:${scheduleId}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isAppScheduleId(id: string): boolean {
|
|
21
|
+
return id.startsWith(APP_SCHEDULE_PREFIX);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Recover `{ appId, scheduleId }` from a composite id, or null when the id
|
|
26
|
+
* is not app-owned. appId is a clean slug (no colons), so the first two
|
|
27
|
+
* colon-separated segments are unambiguous.
|
|
28
|
+
*/
|
|
29
|
+
export function parseAppScheduleId(
|
|
30
|
+
id: string
|
|
31
|
+
): { appId: string; scheduleId: string } | null {
|
|
32
|
+
if (!isAppScheduleId(id)) return null;
|
|
33
|
+
const rest = id.slice(APP_SCHEDULE_PREFIX.length);
|
|
34
|
+
const sep = rest.indexOf(":");
|
|
35
|
+
if (sep <= 0 || sep === rest.length - 1) return null;
|
|
36
|
+
return { appId: rest.slice(0, sep), scheduleId: rest.slice(sep + 1) };
|
|
37
|
+
}
|
|
@@ -137,6 +137,63 @@ export async function dispatchBlueprintForRow(input: {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Cron-fired blueprint dispatch for app-manifest schedules
|
|
142
|
+
* (`manifest.schedules[].runs`). The schedule-side sibling of
|
|
143
|
+
* `dispatchBlueprintForRow` — same instantiate → execute chokepoint, same
|
|
144
|
+
* notification-on-failure contract, but with no row context: variables come
|
|
145
|
+
* entirely from the blueprint's declared defaults, so a pack author must
|
|
146
|
+
* give every required variable a default for a scheduled blueprint.
|
|
147
|
+
*
|
|
148
|
+
* Returns `{ workflowId }` on success, `null` on failure (already logged +
|
|
149
|
+
* recorded in `notifications`).
|
|
150
|
+
*/
|
|
151
|
+
export async function dispatchScheduledBlueprint(input: {
|
|
152
|
+
appId: string;
|
|
153
|
+
blueprintId: string;
|
|
154
|
+
scheduleId: string;
|
|
155
|
+
}): Promise<{ workflowId: string } | null> {
|
|
156
|
+
const { appId, blueprintId, scheduleId } = input;
|
|
157
|
+
try {
|
|
158
|
+
const { instantiateBlueprint } = await import(
|
|
159
|
+
"@/lib/workflows/blueprints/instantiator"
|
|
160
|
+
);
|
|
161
|
+
const { executeWorkflow } = await import("@/lib/workflows/engine");
|
|
162
|
+
|
|
163
|
+
const { workflowId } = await instantiateBlueprint(blueprintId, {}, appId);
|
|
164
|
+
|
|
165
|
+
// Fire-and-forget — workflow may run for minutes
|
|
166
|
+
executeWorkflow(workflowId).catch((err) => {
|
|
167
|
+
console.error(
|
|
168
|
+
`[manifest-trigger-dispatch] executeWorkflow ${workflowId} failed:`,
|
|
169
|
+
err
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return { workflowId };
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
176
|
+
console.error(
|
|
177
|
+
`[manifest-trigger-dispatch] scheduled dispatch failed for app=${appId} blueprint=${blueprintId}:`,
|
|
178
|
+
err
|
|
179
|
+
);
|
|
180
|
+
try {
|
|
181
|
+
await db.insert(notifications).values({
|
|
182
|
+
id: crypto.randomUUID(),
|
|
183
|
+
taskId: null,
|
|
184
|
+
type: "task_failed",
|
|
185
|
+
title: `Schedule failure in app "${appId}"`,
|
|
186
|
+
body: `Blueprint "${blueprintId}" failed for schedule "${scheduleId}": ${message}`,
|
|
187
|
+
read: false,
|
|
188
|
+
createdAt: new Date(),
|
|
189
|
+
});
|
|
190
|
+
} catch (nerr) {
|
|
191
|
+
console.error(`[manifest-trigger-dispatch] notification write failed:`, nerr);
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
140
197
|
interface MatchingSubscription {
|
|
141
198
|
appId: string;
|
|
142
199
|
blueprintId: string;
|
package/src/lib/apps/registry.ts
CHANGED
|
@@ -486,6 +486,8 @@ export interface DeleteAppCascadeResult {
|
|
|
486
486
|
profilesRemoved: number;
|
|
487
487
|
/** Number of `<appId>--*.yaml` blueprint files removed from the blueprints dir. */
|
|
488
488
|
blueprintsRemoved: number;
|
|
489
|
+
/** Number of `app:<appId>:*` schedule rows removed from the schedules table. */
|
|
490
|
+
schedulesRemoved: number;
|
|
489
491
|
}
|
|
490
492
|
|
|
491
493
|
export interface DeleteAppCascadeOptions {
|
|
@@ -553,6 +555,7 @@ export async function deleteAppCascade(
|
|
|
553
555
|
projectRemoved: false,
|
|
554
556
|
profilesRemoved: 0,
|
|
555
557
|
blueprintsRemoved: 0,
|
|
558
|
+
schedulesRemoved: 0,
|
|
556
559
|
};
|
|
557
560
|
|
|
558
561
|
const resolvedApps = path.resolve(appsDir);
|
|
@@ -577,5 +580,28 @@ export async function deleteAppCascade(
|
|
|
577
580
|
const profilesRemoved = sweepNamespacedProfiles(profilesDir, appId);
|
|
578
581
|
const blueprintsRemoved = sweepNamespacedBlueprints(blueprintsDir, appId);
|
|
579
582
|
|
|
580
|
-
|
|
583
|
+
// Sweep app-owned schedule rows (`app:<appId>:*`, registered by the pack
|
|
584
|
+
// installer) so an uninstalled app's schedules don't refire into nothing.
|
|
585
|
+
// Dynamic import — this module must stay out of the DB static import graph.
|
|
586
|
+
let schedulesRemoved = 0;
|
|
587
|
+
try {
|
|
588
|
+
const { db } = await import("@/lib/db");
|
|
589
|
+
const { schedules } = await import("@/lib/db/schema");
|
|
590
|
+
const { like } = await import("drizzle-orm");
|
|
591
|
+
const result = db
|
|
592
|
+
.delete(schedules)
|
|
593
|
+
.where(like(schedules.id, `app:${appId}:%`))
|
|
594
|
+
.run();
|
|
595
|
+
schedulesRemoved = result.changes;
|
|
596
|
+
} catch (err) {
|
|
597
|
+
console.error(`[registry] schedule sweep failed for app "${appId}":`, err);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
projectRemoved,
|
|
602
|
+
filesRemoved,
|
|
603
|
+
profilesRemoved,
|
|
604
|
+
blueprintsRemoved,
|
|
605
|
+
schedulesRemoved,
|
|
606
|
+
};
|
|
581
607
|
}
|
package/src/lib/packs/cli.ts
CHANGED
|
@@ -104,7 +104,8 @@ async function runAdd(
|
|
|
104
104
|
`${report.tablesCreated} table(s) (${report.rowsSeeded} row(s)), ` +
|
|
105
105
|
`${report.customersSeeded} customer(s), ` +
|
|
106
106
|
`${report.profilesDropped} profile(s), ` +
|
|
107
|
-
`${report.blueprintsDropped} blueprint(s)
|
|
107
|
+
`${report.blueprintsDropped} blueprint(s), ` +
|
|
108
|
+
`${report.schedulesRegistered} schedule(s).`
|
|
108
109
|
);
|
|
109
110
|
return 0;
|
|
110
111
|
} catch (err) {
|