@hachej/boring-automation 0.1.79 → 0.1.80
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/server/index.d.ts +1 -0
- package/dist/server/index.js +179 -159
- package/package.json +4 -4
package/dist/server/index.d.ts
CHANGED
|
@@ -172,6 +172,7 @@ interface BoringAutomationServerPluginOptions {
|
|
|
172
172
|
store?: AutomationStore;
|
|
173
173
|
dispatcherResolver?: WorkspaceAgentDispatcherResolver;
|
|
174
174
|
actorResolver?: (request: FastifyRequest) => Promise<VerifiedAutomationActor> | VerifiedAutomationActor;
|
|
175
|
+
storeForRequest?: (request: FastifyRequest, actor: VerifiedAutomationActor) => Promise<AutomationStore> | AutomationStore;
|
|
175
176
|
}
|
|
176
177
|
declare function createBoringAutomationServerPlugin(options?: BoringAutomationServerPluginOptions): WorkspaceServerPlugin;
|
|
177
178
|
declare function defaultBoringAutomationServerPlugin(options?: BoringAutomationServerPluginOptions, ctx?: Pick<WorkspaceAgentServerPluginContext, "workspaceRoot"> & Partial<Pick<WorkspaceAgentServerPluginContext, "trusted">>): WorkspaceServerPlugin;
|
package/dist/server/index.js
CHANGED
|
@@ -515,6 +515,162 @@ function clone(value) {
|
|
|
515
515
|
return JSON.parse(JSON.stringify(value));
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
+
// src/server/postgresStore.ts
|
|
519
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
520
|
+
var PostgresAutomationStore = class {
|
|
521
|
+
constructor(sql, actor, clock = () => /* @__PURE__ */ new Date()) {
|
|
522
|
+
this.sql = sql;
|
|
523
|
+
this.actor = actor;
|
|
524
|
+
this.clock = clock;
|
|
525
|
+
}
|
|
526
|
+
sql;
|
|
527
|
+
actor;
|
|
528
|
+
clock;
|
|
529
|
+
async listAutomations() {
|
|
530
|
+
const rows = await this.sql`
|
|
531
|
+
SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
532
|
+
FROM boring_automation_automations
|
|
533
|
+
WHERE workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
534
|
+
ORDER BY created_at, id
|
|
535
|
+
`;
|
|
536
|
+
return rows.map(toAutomation);
|
|
537
|
+
}
|
|
538
|
+
async getAutomation(id) {
|
|
539
|
+
const rows = await this.sql`
|
|
540
|
+
SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
541
|
+
FROM boring_automation_automations
|
|
542
|
+
WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
543
|
+
`;
|
|
544
|
+
return rows[0] ? toAutomation(rows[0]) : null;
|
|
545
|
+
}
|
|
546
|
+
async createAutomation(input) {
|
|
547
|
+
const now = this.clock().toISOString();
|
|
548
|
+
const id = randomUUID2();
|
|
549
|
+
const prompt = input.prompt ?? "";
|
|
550
|
+
const rows = await this.sql`
|
|
551
|
+
INSERT INTO boring_automation_automations (id, workspace_id, owner_user_id, title, enabled, cron, timezone, model, prompt, created_at, updated_at)
|
|
552
|
+
VALUES (${id}, ${this.actor.workspaceId}, ${this.actor.userId}, ${input.title}, ${input.enabled ?? true}, ${input.cron}, ${input.timezone}, ${input.model}, ${prompt}, ${now}, ${now})
|
|
553
|
+
RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
554
|
+
`;
|
|
555
|
+
return toAutomation(rows[0]);
|
|
556
|
+
}
|
|
557
|
+
async updateAutomation(id, patch) {
|
|
558
|
+
const current = await this.getAutomation(id);
|
|
559
|
+
if (!current) throw automationNotFound(id);
|
|
560
|
+
const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
|
|
561
|
+
const rows = await this.sql`
|
|
562
|
+
UPDATE boring_automation_automations
|
|
563
|
+
SET title = ${next.title}, enabled = ${next.enabled}, cron = ${next.cron}, timezone = ${next.timezone}, model = ${next.model}, updated_at = ${next.updatedAt}
|
|
564
|
+
WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
565
|
+
RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
566
|
+
`;
|
|
567
|
+
if (!rows[0]) throw automationNotFound(id);
|
|
568
|
+
return toAutomation(rows[0]);
|
|
569
|
+
}
|
|
570
|
+
async deleteAutomation(id) {
|
|
571
|
+
const result = await this.sql`
|
|
572
|
+
DELETE FROM boring_automation_automations
|
|
573
|
+
WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
574
|
+
`;
|
|
575
|
+
if (result.count === 0) throw automationNotFound(id);
|
|
576
|
+
}
|
|
577
|
+
async getPrompt(automationId) {
|
|
578
|
+
const automation = await this.getAutomation(automationId);
|
|
579
|
+
if (!automation) throw automationNotFound(automationId);
|
|
580
|
+
const rows = await this.sql`
|
|
581
|
+
SELECT prompt FROM boring_automation_automations
|
|
582
|
+
WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
583
|
+
`;
|
|
584
|
+
return rows[0]?.prompt ?? "";
|
|
585
|
+
}
|
|
586
|
+
async updatePrompt(automationId, body) {
|
|
587
|
+
const result = await this.sql`
|
|
588
|
+
UPDATE boring_automation_automations SET prompt = ${body}, updated_at = ${this.clock().toISOString()}
|
|
589
|
+
WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
590
|
+
`;
|
|
591
|
+
if (result.count === 0) throw automationNotFound(automationId);
|
|
592
|
+
}
|
|
593
|
+
async reconcileOrphanedRuns(automationId) {
|
|
594
|
+
await this.sql`
|
|
595
|
+
UPDATE boring_automation_runs
|
|
596
|
+
SET status = 'failed', completed_at = ${this.clock().toISOString()}, error = 'Automation host restarted before the run completed', updated_at = ${this.clock().toISOString()}
|
|
597
|
+
WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
|
|
598
|
+
`;
|
|
599
|
+
}
|
|
600
|
+
async beginRun(input) {
|
|
601
|
+
const automation = await this.getAutomation(input.automationId);
|
|
602
|
+
if (!automation) throw automationNotFound(input.automationId);
|
|
603
|
+
const active = await this.sql`
|
|
604
|
+
SELECT id FROM boring_automation_runs
|
|
605
|
+
WHERE automation_id = ${input.automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
|
|
606
|
+
LIMIT 1
|
|
607
|
+
`;
|
|
608
|
+
if (active[0]) throw runAlreadyActive(input.automationId);
|
|
609
|
+
try {
|
|
610
|
+
const now = input.createdAt ?? this.clock().toISOString();
|
|
611
|
+
const id = randomUUID2();
|
|
612
|
+
const rows = await this.sql`
|
|
613
|
+
INSERT INTO boring_automation_runs (id, automation_id, workspace_id, owner_user_id, session_id, status, trigger, scheduled_for, started_at, completed_at, duration_ms, input_tokens, output_tokens, total_tokens, prompt_snapshot, model_snapshot, error, created_at, updated_at)
|
|
614
|
+
VALUES (${id}, ${input.automationId}, ${this.actor.workspaceId}, ${this.actor.userId}, NULL, 'queued', ${input.trigger}, ${input.scheduledFor ?? null}, NULL, NULL, NULL, NULL, NULL, NULL, ${input.promptSnapshot}, ${input.modelSnapshot}, NULL, ${now}, ${now})
|
|
615
|
+
RETURNING *
|
|
616
|
+
`;
|
|
617
|
+
return toRun(rows[0]);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
if (isUniqueViolation(error)) {
|
|
620
|
+
if (constraintName(error) === "boring_automation_runs_active_once_idx") throw runAlreadyActive(input.automationId);
|
|
621
|
+
if (input.trigger === "scheduled" && input.scheduledFor) throw runAlreadyRecorded(input.automationId, input.scheduledFor);
|
|
622
|
+
}
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async updateRunLifecycle(runId, patch) {
|
|
627
|
+
const current = await this.findRun(runId);
|
|
628
|
+
if (!current) throw runNotFound(runId);
|
|
629
|
+
const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
|
|
630
|
+
const rows = await this.sql`
|
|
631
|
+
UPDATE boring_automation_runs
|
|
632
|
+
SET session_id = ${next.sessionId}, status = ${next.status}, started_at = ${next.startedAt}, completed_at = ${next.completedAt}, duration_ms = ${next.durationMs}, input_tokens = ${next.inputTokens}, output_tokens = ${next.outputTokens}, total_tokens = ${next.totalTokens}, error = ${next.error}, updated_at = ${next.updatedAt}
|
|
633
|
+
WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
634
|
+
RETURNING *
|
|
635
|
+
`;
|
|
636
|
+
if (!rows[0]) throw runNotFound(runId);
|
|
637
|
+
return toRun(rows[0]);
|
|
638
|
+
}
|
|
639
|
+
async listRuns(automationId) {
|
|
640
|
+
const rows = await this.sql`
|
|
641
|
+
SELECT * FROM boring_automation_runs
|
|
642
|
+
WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
643
|
+
ORDER BY created_at DESC, id DESC
|
|
644
|
+
`;
|
|
645
|
+
return rows.map(toRun);
|
|
646
|
+
}
|
|
647
|
+
async findRun(runId) {
|
|
648
|
+
const rows = await this.sql`
|
|
649
|
+
SELECT * FROM boring_automation_runs
|
|
650
|
+
WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
651
|
+
`;
|
|
652
|
+
return rows[0] ? toRun(rows[0]) : null;
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
function toAutomation(row) {
|
|
656
|
+
return { id: row.id, title: row.title, enabled: row.enabled, cron: row.cron, timezone: row.timezone, model: row.model, promptRef: `hosted:${row.id}`, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
|
|
657
|
+
}
|
|
658
|
+
function toRun(row) {
|
|
659
|
+
return { id: row.id, automationId: row.automation_id, sessionId: row.session_id, status: row.status, trigger: row.trigger, scheduledFor: nullableIso(row.scheduled_for), startedAt: nullableIso(row.started_at), completedAt: nullableIso(row.completed_at), durationMs: row.duration_ms, inputTokens: row.input_tokens, outputTokens: row.output_tokens, totalTokens: row.total_tokens, promptSnapshot: row.prompt_snapshot, modelSnapshot: row.model_snapshot, error: row.error, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
|
|
660
|
+
}
|
|
661
|
+
function iso(value) {
|
|
662
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
663
|
+
}
|
|
664
|
+
function nullableIso(value) {
|
|
665
|
+
return value === null ? null : iso(value);
|
|
666
|
+
}
|
|
667
|
+
function isUniqueViolation(error) {
|
|
668
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "23505");
|
|
669
|
+
}
|
|
670
|
+
function constraintName(error) {
|
|
671
|
+
return error && typeof error === "object" && "constraint_name" in error ? String(error.constraint_name) : void 0;
|
|
672
|
+
}
|
|
673
|
+
|
|
518
674
|
// src/server/manualRunExecutor.ts
|
|
519
675
|
var ManualRunExecutor = class {
|
|
520
676
|
constructor(options) {
|
|
@@ -934,172 +1090,25 @@ async function runBoringAutomationMigrations(sql) {
|
|
|
934
1090
|
`);
|
|
935
1091
|
}
|
|
936
1092
|
|
|
937
|
-
// src/server/postgresStore.ts
|
|
938
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
939
|
-
var PostgresAutomationStore = class {
|
|
940
|
-
constructor(sql, actor, clock = () => /* @__PURE__ */ new Date()) {
|
|
941
|
-
this.sql = sql;
|
|
942
|
-
this.actor = actor;
|
|
943
|
-
this.clock = clock;
|
|
944
|
-
}
|
|
945
|
-
sql;
|
|
946
|
-
actor;
|
|
947
|
-
clock;
|
|
948
|
-
async listAutomations() {
|
|
949
|
-
const rows = await this.sql`
|
|
950
|
-
SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
951
|
-
FROM boring_automation_automations
|
|
952
|
-
WHERE workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
953
|
-
ORDER BY created_at, id
|
|
954
|
-
`;
|
|
955
|
-
return rows.map(toAutomation);
|
|
956
|
-
}
|
|
957
|
-
async getAutomation(id) {
|
|
958
|
-
const rows = await this.sql`
|
|
959
|
-
SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
960
|
-
FROM boring_automation_automations
|
|
961
|
-
WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
962
|
-
`;
|
|
963
|
-
return rows[0] ? toAutomation(rows[0]) : null;
|
|
964
|
-
}
|
|
965
|
-
async createAutomation(input) {
|
|
966
|
-
const now = this.clock().toISOString();
|
|
967
|
-
const id = randomUUID2();
|
|
968
|
-
const prompt = input.prompt ?? "";
|
|
969
|
-
const rows = await this.sql`
|
|
970
|
-
INSERT INTO boring_automation_automations (id, workspace_id, owner_user_id, title, enabled, cron, timezone, model, prompt, created_at, updated_at)
|
|
971
|
-
VALUES (${id}, ${this.actor.workspaceId}, ${this.actor.userId}, ${input.title}, ${input.enabled ?? true}, ${input.cron}, ${input.timezone}, ${input.model}, ${prompt}, ${now}, ${now})
|
|
972
|
-
RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
973
|
-
`;
|
|
974
|
-
return toAutomation(rows[0]);
|
|
975
|
-
}
|
|
976
|
-
async updateAutomation(id, patch) {
|
|
977
|
-
const current = await this.getAutomation(id);
|
|
978
|
-
if (!current) throw automationNotFound(id);
|
|
979
|
-
const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
|
|
980
|
-
const rows = await this.sql`
|
|
981
|
-
UPDATE boring_automation_automations
|
|
982
|
-
SET title = ${next.title}, enabled = ${next.enabled}, cron = ${next.cron}, timezone = ${next.timezone}, model = ${next.model}, updated_at = ${next.updatedAt}
|
|
983
|
-
WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
984
|
-
RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
|
|
985
|
-
`;
|
|
986
|
-
if (!rows[0]) throw automationNotFound(id);
|
|
987
|
-
return toAutomation(rows[0]);
|
|
988
|
-
}
|
|
989
|
-
async deleteAutomation(id) {
|
|
990
|
-
const result = await this.sql`
|
|
991
|
-
DELETE FROM boring_automation_automations
|
|
992
|
-
WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
993
|
-
`;
|
|
994
|
-
if (result.count === 0) throw automationNotFound(id);
|
|
995
|
-
}
|
|
996
|
-
async getPrompt(automationId) {
|
|
997
|
-
const automation = await this.getAutomation(automationId);
|
|
998
|
-
if (!automation) throw automationNotFound(automationId);
|
|
999
|
-
const rows = await this.sql`
|
|
1000
|
-
SELECT prompt FROM boring_automation_automations
|
|
1001
|
-
WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
1002
|
-
`;
|
|
1003
|
-
return rows[0]?.prompt ?? "";
|
|
1004
|
-
}
|
|
1005
|
-
async updatePrompt(automationId, body) {
|
|
1006
|
-
const result = await this.sql`
|
|
1007
|
-
UPDATE boring_automation_automations SET prompt = ${body}, updated_at = ${this.clock().toISOString()}
|
|
1008
|
-
WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
1009
|
-
`;
|
|
1010
|
-
if (result.count === 0) throw automationNotFound(automationId);
|
|
1011
|
-
}
|
|
1012
|
-
async reconcileOrphanedRuns(automationId) {
|
|
1013
|
-
await this.sql`
|
|
1014
|
-
UPDATE boring_automation_runs
|
|
1015
|
-
SET status = 'failed', completed_at = ${this.clock().toISOString()}, error = 'Automation host restarted before the run completed', updated_at = ${this.clock().toISOString()}
|
|
1016
|
-
WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
|
|
1017
|
-
`;
|
|
1018
|
-
}
|
|
1019
|
-
async beginRun(input) {
|
|
1020
|
-
const automation = await this.getAutomation(input.automationId);
|
|
1021
|
-
if (!automation) throw automationNotFound(input.automationId);
|
|
1022
|
-
const active = await this.sql`
|
|
1023
|
-
SELECT id FROM boring_automation_runs
|
|
1024
|
-
WHERE automation_id = ${input.automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
|
|
1025
|
-
LIMIT 1
|
|
1026
|
-
`;
|
|
1027
|
-
if (active[0]) throw runAlreadyActive(input.automationId);
|
|
1028
|
-
try {
|
|
1029
|
-
const now = input.createdAt ?? this.clock().toISOString();
|
|
1030
|
-
const id = randomUUID2();
|
|
1031
|
-
const rows = await this.sql`
|
|
1032
|
-
INSERT INTO boring_automation_runs (id, automation_id, workspace_id, owner_user_id, session_id, status, trigger, scheduled_for, started_at, completed_at, duration_ms, input_tokens, output_tokens, total_tokens, prompt_snapshot, model_snapshot, error, created_at, updated_at)
|
|
1033
|
-
VALUES (${id}, ${input.automationId}, ${this.actor.workspaceId}, ${this.actor.userId}, NULL, 'queued', ${input.trigger}, ${input.scheduledFor ?? null}, NULL, NULL, NULL, NULL, NULL, NULL, ${input.promptSnapshot}, ${input.modelSnapshot}, NULL, ${now}, ${now})
|
|
1034
|
-
RETURNING *
|
|
1035
|
-
`;
|
|
1036
|
-
return toRun(rows[0]);
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
if (isUniqueViolation(error)) {
|
|
1039
|
-
if (constraintName(error) === "boring_automation_runs_active_once_idx") throw runAlreadyActive(input.automationId);
|
|
1040
|
-
if (input.trigger === "scheduled" && input.scheduledFor) throw runAlreadyRecorded(input.automationId, input.scheduledFor);
|
|
1041
|
-
}
|
|
1042
|
-
throw error;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
async updateRunLifecycle(runId, patch) {
|
|
1046
|
-
const current = await this.findRun(runId);
|
|
1047
|
-
if (!current) throw runNotFound(runId);
|
|
1048
|
-
const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
|
|
1049
|
-
const rows = await this.sql`
|
|
1050
|
-
UPDATE boring_automation_runs
|
|
1051
|
-
SET session_id = ${next.sessionId}, status = ${next.status}, started_at = ${next.startedAt}, completed_at = ${next.completedAt}, duration_ms = ${next.durationMs}, input_tokens = ${next.inputTokens}, output_tokens = ${next.outputTokens}, total_tokens = ${next.totalTokens}, error = ${next.error}, updated_at = ${next.updatedAt}
|
|
1052
|
-
WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
1053
|
-
RETURNING *
|
|
1054
|
-
`;
|
|
1055
|
-
if (!rows[0]) throw runNotFound(runId);
|
|
1056
|
-
return toRun(rows[0]);
|
|
1057
|
-
}
|
|
1058
|
-
async listRuns(automationId) {
|
|
1059
|
-
const rows = await this.sql`
|
|
1060
|
-
SELECT * FROM boring_automation_runs
|
|
1061
|
-
WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
1062
|
-
ORDER BY created_at DESC, id DESC
|
|
1063
|
-
`;
|
|
1064
|
-
return rows.map(toRun);
|
|
1065
|
-
}
|
|
1066
|
-
async findRun(runId) {
|
|
1067
|
-
const rows = await this.sql`
|
|
1068
|
-
SELECT * FROM boring_automation_runs
|
|
1069
|
-
WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
|
|
1070
|
-
`;
|
|
1071
|
-
return rows[0] ? toRun(rows[0]) : null;
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
function toAutomation(row) {
|
|
1075
|
-
return { id: row.id, title: row.title, enabled: row.enabled, cron: row.cron, timezone: row.timezone, model: row.model, promptRef: `hosted:${row.id}`, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
|
|
1076
|
-
}
|
|
1077
|
-
function toRun(row) {
|
|
1078
|
-
return { id: row.id, automationId: row.automation_id, sessionId: row.session_id, status: row.status, trigger: row.trigger, scheduledFor: nullableIso(row.scheduled_for), startedAt: nullableIso(row.started_at), completedAt: nullableIso(row.completed_at), durationMs: row.duration_ms, inputTokens: row.input_tokens, outputTokens: row.output_tokens, totalTokens: row.total_tokens, promptSnapshot: row.prompt_snapshot, modelSnapshot: row.model_snapshot, error: row.error, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
|
|
1079
|
-
}
|
|
1080
|
-
function iso(value) {
|
|
1081
|
-
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
1082
|
-
}
|
|
1083
|
-
function nullableIso(value) {
|
|
1084
|
-
return value === null ? null : iso(value);
|
|
1085
|
-
}
|
|
1086
|
-
function isUniqueViolation(error) {
|
|
1087
|
-
return Boolean(error && typeof error === "object" && "code" in error && error.code === "23505");
|
|
1088
|
-
}
|
|
1089
|
-
function constraintName(error) {
|
|
1090
|
-
return error && typeof error === "object" && "constraint_name" in error ? String(error.constraint_name) : void 0;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
1093
|
// src/server/index.ts
|
|
1094
1094
|
function createBoringAutomationServerPlugin(options = {}) {
|
|
1095
1095
|
const store = options.store ?? createDefaultStore(options.workspaceRoot);
|
|
1096
|
-
const manualRunExecutor = options.dispatcherResolver && options.actorResolver ? new ManualRunExecutor({
|
|
1097
|
-
|
|
1096
|
+
const manualRunExecutor = options.dispatcherResolver && options.actorResolver ? new ManualRunExecutor({
|
|
1097
|
+
store,
|
|
1098
|
+
storeForRequest: options.storeForRequest,
|
|
1099
|
+
dispatcherResolver: options.dispatcherResolver,
|
|
1100
|
+
actorResolver: options.actorResolver
|
|
1101
|
+
}) : void 0;
|
|
1102
|
+
const dueRunService = manualRunExecutor && !options.storeForRequest ? new DueRunService({ store, executor: manualRunExecutor }) : void 0;
|
|
1098
1103
|
return defineServerPlugin({
|
|
1099
1104
|
id: BORING_AUTOMATION_PLUGIN_ID,
|
|
1100
1105
|
label: BORING_AUTOMATION_PLUGIN_LABEL,
|
|
1101
1106
|
routes: async (app) => {
|
|
1102
|
-
await automationRoutes(app, { store,
|
|
1107
|
+
await automationRoutes(app, { store, storeForRequest: options.storeForRequest ? async (request) => {
|
|
1108
|
+
const actor = options.actorResolver ? await options.actorResolver(request) : void 0;
|
|
1109
|
+
if (!actor) throw new Error("automation actor resolver is unavailable");
|
|
1110
|
+
return await options.storeForRequest(request, actor);
|
|
1111
|
+
} : void 0, manualRunExecutor, dueRunService });
|
|
1103
1112
|
}
|
|
1104
1113
|
});
|
|
1105
1114
|
}
|
|
@@ -1109,6 +1118,17 @@ function createDefaultStore(workspaceRoot) {
|
|
|
1109
1118
|
}
|
|
1110
1119
|
function defaultBoringAutomationServerPlugin(options, ctx) {
|
|
1111
1120
|
const trusted = ctx?.trusted;
|
|
1121
|
+
if (!options?.store && trusted?.sql && trusted.workspaceAgentDispatcherResolver && trusted.actorResolver) {
|
|
1122
|
+
const sql = trusted.sql;
|
|
1123
|
+
const fallbackStore = new PostgresAutomationStore(sql, { workspaceId: "unbound", userId: "unbound" });
|
|
1124
|
+
return createBoringAutomationServerPlugin({
|
|
1125
|
+
...options,
|
|
1126
|
+
store: fallbackStore,
|
|
1127
|
+
storeForRequest: async (_request, actor) => new PostgresAutomationStore(sql, actor),
|
|
1128
|
+
dispatcherResolver: options?.dispatcherResolver ?? trusted.workspaceAgentDispatcherResolver,
|
|
1129
|
+
actorResolver: options?.actorResolver ?? trusted.actorResolver
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1112
1132
|
return createBoringAutomationServerPlugin({
|
|
1113
1133
|
...options,
|
|
1114
1134
|
workspaceRoot: options?.workspaceRoot ?? ctx?.workspaceRoot,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-automation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.80",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
"fastify": "^5.9.0",
|
|
44
44
|
"react": "^18.0.0 || ^19.0.0",
|
|
45
45
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
46
|
-
"@hachej/boring-workspace": "0.1.
|
|
46
|
+
"@hachej/boring-workspace": "0.1.80"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"croner": "^10.0.1",
|
|
50
50
|
"lucide-react": "^1.21.0",
|
|
51
51
|
"postgres": "^3.4.7",
|
|
52
52
|
"zod": "^3.23.0",
|
|
53
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
53
|
+
"@hachej/boring-ui-kit": "0.1.80"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"tsup": "^8.4.0",
|
|
67
67
|
"typescript": "~6.0.3",
|
|
68
68
|
"vitest": "^4.1.9",
|
|
69
|
-
"@hachej/boring-workspace": "0.1.
|
|
69
|
+
"@hachej/boring-workspace": "0.1.80"
|
|
70
70
|
},
|
|
71
71
|
"peerDependenciesMeta": {
|
|
72
72
|
"fastify": {
|