@repogo/plugins 0.1.42 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -75
- package/dist/context.d.ts +20 -14
- package/dist/define.d.ts +54 -24
- package/dist/generated.d.ts +4 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +66 -20
- package/dist/remote.d.ts +7 -0
- package/dist/runtime.d.ts +15 -10
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +76 -60
- package/dist/workflow/contracts.d.ts +3 -1
- package/dist/workflow/definition.d.ts +1 -1
- package/dist/workflow/events.d.ts +1 -0
- package/dist/workflow-executor.js +139 -83
- package/package.json +2 -2
|
@@ -18,6 +18,7 @@ __export(exports_src, {
|
|
|
18
18
|
upload: () => upload,
|
|
19
19
|
toCallError: () => toCallError,
|
|
20
20
|
stream: () => stream,
|
|
21
|
+
remotePlugin: () => remotePlugin,
|
|
21
22
|
method: () => method,
|
|
22
23
|
definePlugin: () => definePlugin,
|
|
23
24
|
cached: () => cached,
|
|
@@ -43,36 +44,48 @@ function parseDuration(duration) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// src/define.ts
|
|
47
|
+
var permissionModes = ["approval-required", "auto-accept-edits", "full-access"];
|
|
48
|
+
function httpAddresses(option) {
|
|
49
|
+
return option === true ? { public: "key" } : option;
|
|
50
|
+
}
|
|
46
51
|
function definePlugin(plugin) {
|
|
47
52
|
return plugin;
|
|
48
53
|
}
|
|
49
54
|
function method(input, run, options = {}) {
|
|
55
|
+
if ("kind" in options)
|
|
56
|
+
throw new Error('method: kind is now effect: "read", "write" or "destructive"');
|
|
57
|
+
if ("confirm" in options)
|
|
58
|
+
throw new Error('method: confirm is now approve: "Delete {{id}}?"');
|
|
59
|
+
if (options.approveUpTo !== undefined && !permissionModes.includes(options.approveUpTo)) {
|
|
60
|
+
throw new Error(`method: approveUpTo is one of ${permissionModes.join(", ")}`);
|
|
61
|
+
}
|
|
62
|
+
if (options.approveUpTo !== undefined && !options.approve)
|
|
63
|
+
throw new Error("method: approveUpTo needs an approve question to ask");
|
|
50
64
|
return {
|
|
51
65
|
input,
|
|
52
66
|
run,
|
|
53
|
-
|
|
67
|
+
effect: options.effect ?? "write",
|
|
54
68
|
output: options.output,
|
|
55
69
|
description: options.description,
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
approve: options.approve,
|
|
71
|
+
approveUpTo: options.approveUpTo,
|
|
72
|
+
http: httpAddresses(options.http)
|
|
58
73
|
};
|
|
59
74
|
}
|
|
60
75
|
function cached(input, run, options) {
|
|
61
76
|
return {
|
|
62
77
|
input,
|
|
63
78
|
run,
|
|
64
|
-
|
|
79
|
+
effect: "read",
|
|
65
80
|
output: options.output,
|
|
66
81
|
description: options.description,
|
|
82
|
+
http: httpAddresses(options.http),
|
|
67
83
|
cache: { fresh: parseDuration(options.fresh), shared: options.shared ?? false, timeout: parseDuration(options.timeout ?? "1m") }
|
|
68
84
|
};
|
|
69
85
|
}
|
|
70
86
|
function stream(input, run, options = {}) {
|
|
71
|
-
return { input, run,
|
|
87
|
+
return { input, run, effect: "read", stream: true, description: options.description };
|
|
72
88
|
}
|
|
73
|
-
// src/uploads.ts
|
|
74
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
75
|
-
|
|
76
89
|
// node_modules/zod/v4/classic/external.js
|
|
77
90
|
var exports_external = {};
|
|
78
91
|
__export(exports_external, {
|
|
@@ -19492,13 +19505,6 @@ function bigint3(params) {
|
|
|
19492
19505
|
function date4(params) {
|
|
19493
19506
|
return _coercedDate(ZodDate, params);
|
|
19494
19507
|
}
|
|
19495
|
-
// src/uploads.ts
|
|
19496
|
-
var callFiles = new AsyncLocalStorage;
|
|
19497
|
-
function upload() {
|
|
19498
|
-
return exports_external.object({ id: exports_external.string(), name: exports_external.string(), type: exports_external.string(), size: exports_external.number(), path: exports_external.string() }).refine((file2) => (callFiles.getStore() ?? []).some((held) => held.id === file2.id && held.path === file2.path), {
|
|
19499
|
-
message: "Not a file uploaded for this call."
|
|
19500
|
-
});
|
|
19501
|
-
}
|
|
19502
19508
|
// src/errors.ts
|
|
19503
19509
|
class MethodError extends Error {
|
|
19504
19510
|
code;
|
|
@@ -19523,6 +19529,40 @@ function toCallError(error62) {
|
|
|
19523
19529
|
}
|
|
19524
19530
|
return { code: "INTERNAL", message: error62 instanceof Error ? error62.message : String(error62) };
|
|
19525
19531
|
}
|
|
19532
|
+
|
|
19533
|
+
// src/remote.ts
|
|
19534
|
+
var codes = { 400: "VALIDATION", 403: "FORBIDDEN", 404: "NOT_FOUND" };
|
|
19535
|
+
async function remotePlugin(url2, name) {
|
|
19536
|
+
const base = `${url2.replace(/\/$/, "")}/${name}`;
|
|
19537
|
+
const response = await fetch(base);
|
|
19538
|
+
if (!response.ok)
|
|
19539
|
+
throw new Error(`remotePlugin: ${base} answered ${response.status}`);
|
|
19540
|
+
const listing = await response.json();
|
|
19541
|
+
const methods = {};
|
|
19542
|
+
for (const m of listing.methods) {
|
|
19543
|
+
methods[m.name] = method(exports_external.fromJSONSchema(m.input), (input2) => call(`${base}/${m.name}`, input2), {
|
|
19544
|
+
effect: m.effect,
|
|
19545
|
+
description: m.description ?? undefined,
|
|
19546
|
+
output: m.output ? exports_external.fromJSONSchema(m.output) : undefined
|
|
19547
|
+
});
|
|
19548
|
+
}
|
|
19549
|
+
return { description: listing.description ?? undefined, methods };
|
|
19550
|
+
}
|
|
19551
|
+
async function call(url2, input2) {
|
|
19552
|
+
const response = await fetch(url2, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(input2) });
|
|
19553
|
+
const answer = await response.json().catch(() => null);
|
|
19554
|
+
if (response.ok)
|
|
19555
|
+
return answer;
|
|
19556
|
+
throw new MethodError(codes[response.status] ?? "INTERNAL", answer?.error ?? `${url2} answered ${response.status}`, answer?.fields ?? undefined);
|
|
19557
|
+
}
|
|
19558
|
+
// src/uploads.ts
|
|
19559
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
19560
|
+
var callFiles = new AsyncLocalStorage;
|
|
19561
|
+
function upload() {
|
|
19562
|
+
return exports_external.object({ id: exports_external.string(), name: exports_external.string(), type: exports_external.string(), size: exports_external.number(), path: exports_external.string() }).refine((file2) => (callFiles.getStore() ?? []).some((held) => held.id === file2.id && held.path === file2.path), {
|
|
19563
|
+
message: "Not a file uploaded for this call."
|
|
19564
|
+
});
|
|
19565
|
+
}
|
|
19526
19566
|
// src/workflow/scope.ts
|
|
19527
19567
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
19528
19568
|
var attemptScope = new AsyncLocalStorage2;
|
|
@@ -19669,6 +19709,7 @@ function carryable(value) {
|
|
|
19669
19709
|
|
|
19670
19710
|
// src/runtime.ts
|
|
19671
19711
|
import { AsyncLocalStorage as AsyncLocalStorage5 } from "async_hooks";
|
|
19712
|
+
import { existsSync } from "fs";
|
|
19672
19713
|
import { mkdir as mkdir3, readdir as readdir3 } from "fs/promises";
|
|
19673
19714
|
import { join as join3 } from "path";
|
|
19674
19715
|
|
|
@@ -19732,7 +19773,7 @@ function recordRead(tag) {
|
|
|
19732
19773
|
}
|
|
19733
19774
|
function assertWritable(tag) {
|
|
19734
19775
|
if (current.getStore()?.readOnly) {
|
|
19735
|
-
throw new MethodError("FORBIDDEN", `A
|
|
19776
|
+
throw new MethodError("FORBIDDEN", `A read can't change data (it tried to write ${tag}).`);
|
|
19736
19777
|
}
|
|
19737
19778
|
}
|
|
19738
19779
|
function recordWrite(tag) {
|
|
@@ -20108,14 +20149,14 @@ class AnswerCache {
|
|
|
20108
20149
|
checked_at integer not null
|
|
20109
20150
|
)`);
|
|
20110
20151
|
}
|
|
20111
|
-
async read(key, options,
|
|
20152
|
+
async read(key, options, fetch2) {
|
|
20112
20153
|
const tag = `${this.namespace}.cached.${Bun.hash(key).toString(36)}`;
|
|
20113
20154
|
recordRead(tag);
|
|
20114
20155
|
const [row] = await this.database.sql.unsafe(`select value, checked_at from cached_answers where key = $1`, [key]);
|
|
20115
20156
|
if (!row)
|
|
20116
|
-
return this.refresh(key, tag, options,
|
|
20157
|
+
return this.refresh(key, tag, options, fetch2);
|
|
20117
20158
|
if (Date.now() - row.checked_at >= options.fresh) {
|
|
20118
|
-
this.refresh(key, tag, options,
|
|
20159
|
+
this.refresh(key, tag, options, fetch2).catch((error62) => record2("error", [error62]));
|
|
20119
20160
|
}
|
|
20120
20161
|
return JSON.parse(row.value);
|
|
20121
20162
|
}
|
|
@@ -20123,12 +20164,12 @@ class AnswerCache {
|
|
|
20123
20164
|
while (this.inflight.size)
|
|
20124
20165
|
await Promise.allSettled([...this.inflight.values()]);
|
|
20125
20166
|
}
|
|
20126
|
-
refresh(key, tag, options,
|
|
20167
|
+
refresh(key, tag, options, fetch2) {
|
|
20127
20168
|
const running = this.inflight.get(key);
|
|
20128
20169
|
if (running)
|
|
20129
20170
|
return running;
|
|
20130
20171
|
const started = Date.now();
|
|
20131
|
-
const work = isolated(async () => withTimeout(Promise.resolve(
|
|
20172
|
+
const work = isolated(async () => withTimeout(Promise.resolve(fetch2()), options.timeout)).then(async (value) => {
|
|
20132
20173
|
await this.save(key, tag, value ?? null, started);
|
|
20133
20174
|
return value ?? null;
|
|
20134
20175
|
}, async (error62) => {
|
|
@@ -20529,32 +20570,32 @@ function kvMethods(name, kv, schema) {
|
|
|
20529
20570
|
const entry = exports_external.object({ key: exports_external.string(), value: schema, expiresAt: exports_external.string().nullable(), createdAt: exports_external.string(), updatedAt: exports_external.string() });
|
|
20530
20571
|
return {
|
|
20531
20572
|
get: {
|
|
20532
|
-
|
|
20573
|
+
effect: "read",
|
|
20533
20574
|
description: `Get one ${name} entry by key, or null`,
|
|
20534
20575
|
input: exports_external.object({ key }),
|
|
20535
20576
|
output: entry.nullable(),
|
|
20536
20577
|
run: ({ key: key2 }) => kv.entry(key2)
|
|
20537
20578
|
},
|
|
20538
20579
|
list: {
|
|
20539
|
-
|
|
20580
|
+
effect: "read",
|
|
20540
20581
|
description: `List ${name} entries in key order, optionally by prefix`,
|
|
20541
20582
|
input: exports_external.object({ prefix: exports_external.string().optional(), limit }),
|
|
20542
20583
|
output: exports_external.array(entry),
|
|
20543
20584
|
run: (input2) => kv.list(input2)
|
|
20544
20585
|
},
|
|
20545
20586
|
set: {
|
|
20546
|
-
|
|
20587
|
+
effect: "write",
|
|
20547
20588
|
description: `Set a ${name} key; ttl ("5m", "1h", "7d" or ms) makes it expire`,
|
|
20548
20589
|
input: exports_external.object({ key, value: schema, ttl }),
|
|
20549
20590
|
output: entry,
|
|
20550
20591
|
run: ({ key: key2, value, ttl: ttl2 }) => kv.set(key2, value, { ttl: ttl2 })
|
|
20551
20592
|
},
|
|
20552
20593
|
delete: {
|
|
20553
|
-
|
|
20594
|
+
effect: "destructive",
|
|
20554
20595
|
description: `Delete a ${name} key`,
|
|
20555
20596
|
input: exports_external.object({ key }),
|
|
20556
20597
|
output: exports_external.null(),
|
|
20557
|
-
|
|
20598
|
+
approve: "Delete this key?",
|
|
20558
20599
|
run: async ({ key: key2 }) => (await kv.delete(key2), null)
|
|
20559
20600
|
}
|
|
20560
20601
|
};
|
|
@@ -20564,45 +20605,46 @@ function streamMethods(name, stream2, schema) {
|
|
|
20564
20605
|
const line = exports_external.object({ id: exports_external.string(), key: exports_external.string(), offset: exports_external.number(), at: exports_external.string(), data: schema });
|
|
20565
20606
|
return {
|
|
20566
20607
|
tail: {
|
|
20567
|
-
|
|
20608
|
+
effect: "read",
|
|
20609
|
+
stream: true,
|
|
20568
20610
|
description: `Follow a ${name} key live: its last lines (100 unless last says), then each new one, until it ends`,
|
|
20569
20611
|
input: exports_external.object({ key, from: exports_external.number().int().min(0).optional(), last: exports_external.number().int().positive().max(500).optional() }),
|
|
20570
20612
|
run: ({ key: key2, from, last }, signal) => stream2.follow(key2, { from, last, signal })
|
|
20571
20613
|
},
|
|
20572
20614
|
read: {
|
|
20573
|
-
|
|
20615
|
+
effect: "read",
|
|
20574
20616
|
description: `Read a ${name} key's lines from an offset`,
|
|
20575
20617
|
input: exports_external.object({ key, from: exports_external.number().int().min(0).optional(), limit }),
|
|
20576
20618
|
output: exports_external.array(line),
|
|
20577
20619
|
run: ({ key: key2, ...options }) => stream2.read(key2, options)
|
|
20578
20620
|
},
|
|
20579
20621
|
keys: {
|
|
20580
|
-
|
|
20622
|
+
effect: "read",
|
|
20581
20623
|
description: `List ${name} keys, most recently written first`,
|
|
20582
20624
|
input: exports_external.object({ limit }),
|
|
20583
20625
|
output: exports_external.array(exports_external.object({ key: exports_external.string(), lines: exports_external.number(), bytes: exports_external.number(), endedAt: exports_external.string().nullable(), updatedAt: exports_external.string() })),
|
|
20584
20626
|
run: (input2) => stream2.keys(input2)
|
|
20585
20627
|
},
|
|
20586
20628
|
append: {
|
|
20587
|
-
|
|
20629
|
+
effect: "write",
|
|
20588
20630
|
description: `Append a line to a ${name} key`,
|
|
20589
20631
|
input: exports_external.object({ key, data: schema }),
|
|
20590
20632
|
output: line,
|
|
20591
20633
|
run: ({ key: key2, data }) => stream2.append(key2, data)
|
|
20592
20634
|
},
|
|
20593
20635
|
end: {
|
|
20594
|
-
|
|
20636
|
+
effect: "write",
|
|
20595
20637
|
description: `End a ${name} key: followers stop and appends fail`,
|
|
20596
20638
|
input: exports_external.object({ key }),
|
|
20597
20639
|
output: exports_external.null(),
|
|
20598
20640
|
run: async ({ key: key2 }) => (await stream2.end(key2), null)
|
|
20599
20641
|
},
|
|
20600
20642
|
delete: {
|
|
20601
|
-
|
|
20643
|
+
effect: "destructive",
|
|
20602
20644
|
description: `Delete a ${name} key and its lines`,
|
|
20603
20645
|
input: exports_external.object({ key }),
|
|
20604
20646
|
output: exports_external.null(),
|
|
20605
|
-
|
|
20647
|
+
approve: "Delete this stream and its lines?",
|
|
20606
20648
|
run: async ({ key: key2 }) => (await stream2.delete(key2), null)
|
|
20607
20649
|
}
|
|
20608
20650
|
};
|
|
@@ -20627,28 +20669,28 @@ function bucketMethods(name, bucket, schema) {
|
|
|
20627
20669
|
const revision = exports_external.number().int().optional();
|
|
20628
20670
|
return {
|
|
20629
20671
|
list: {
|
|
20630
|
-
|
|
20672
|
+
effect: "read",
|
|
20631
20673
|
description: `List ${name} files, newest first, optionally in one folder or with one status`,
|
|
20632
20674
|
input: exports_external.object({ folder: exports_external.string().optional(), status: status.optional(), limit }),
|
|
20633
20675
|
output: exports_external.array(file2),
|
|
20634
20676
|
run: (input2) => bucket.list(input2)
|
|
20635
20677
|
},
|
|
20636
20678
|
search: {
|
|
20637
|
-
|
|
20679
|
+
effect: "read",
|
|
20638
20680
|
description: `Search ${name} files by name, folder and meta`,
|
|
20639
20681
|
input: exports_external.object({ query: exports_external.string(), limit }),
|
|
20640
20682
|
output: exports_external.array(file2),
|
|
20641
20683
|
run: ({ query, limit: limit2 }) => bucket.search(query, { limit: limit2 })
|
|
20642
20684
|
},
|
|
20643
20685
|
get: {
|
|
20644
|
-
|
|
20686
|
+
effect: "read",
|
|
20645
20687
|
description: `Get one ${name} file by id, or null`,
|
|
20646
20688
|
input: exports_external.object({ id }),
|
|
20647
20689
|
output: file2.nullable(),
|
|
20648
20690
|
run: ({ id: id2 }) => bucket.get(id2)
|
|
20649
20691
|
},
|
|
20650
20692
|
put: {
|
|
20651
|
-
|
|
20693
|
+
effect: "write",
|
|
20652
20694
|
description: `Add a file to ${name}: its name, and text or an absolute path to copy`,
|
|
20653
20695
|
input: exports_external.object({
|
|
20654
20696
|
name: exports_external.string(),
|
|
@@ -20665,25 +20707,25 @@ function bucketMethods(name, bucket, schema) {
|
|
|
20665
20707
|
}
|
|
20666
20708
|
},
|
|
20667
20709
|
update: {
|
|
20668
|
-
|
|
20710
|
+
effect: "write",
|
|
20669
20711
|
description: `Change fields of a ${name} file's meta; null removes one`,
|
|
20670
20712
|
input: exports_external.object({ id, meta: exports_external.record(exports_external.string(), exports_external.unknown()), revision }),
|
|
20671
20713
|
output: file2,
|
|
20672
20714
|
run: ({ id: id2, meta: meta3, revision: revision2 }) => bucket.update(id2, meta3, { revision: revision2 })
|
|
20673
20715
|
},
|
|
20674
20716
|
move: {
|
|
20675
|
-
|
|
20717
|
+
effect: "write",
|
|
20676
20718
|
description: `Rename a ${name} file or move it to another folder`,
|
|
20677
20719
|
input: exports_external.object({ id, name: exports_external.string().optional(), folder: exports_external.string().optional(), revision }),
|
|
20678
20720
|
output: file2,
|
|
20679
20721
|
run: ({ id: id2, name: name2, folder, revision: revision2 }) => bucket.move(id2, { name: name2, folder }, { revision: revision2 })
|
|
20680
20722
|
},
|
|
20681
20723
|
delete: {
|
|
20682
|
-
|
|
20724
|
+
effect: "destructive",
|
|
20683
20725
|
description: `Delete a ${name} file and its meta`,
|
|
20684
20726
|
input: exports_external.object({ id }),
|
|
20685
20727
|
output: exports_external.null(),
|
|
20686
|
-
|
|
20728
|
+
approve: "Delete this file?",
|
|
20687
20729
|
run: async ({ id: id2 }) => (await bucket.delete(id2), null)
|
|
20688
20730
|
}
|
|
20689
20731
|
};
|
|
@@ -21092,7 +21134,7 @@ var RunMetadataSchema = exports_external.object({
|
|
|
21092
21134
|
root: exports_external.string().refine(isAbsolute2),
|
|
21093
21135
|
namespace: exports_external.string().regex(/^[a-z0-9][a-z0-9.-]*$/),
|
|
21094
21136
|
plugin: exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
21095
|
-
caller: exports_external.enum(["card", "agent", "cron", "code", "http"]),
|
|
21137
|
+
caller: exports_external.enum(["card", "agent", "cron", "code", "http", "device"]),
|
|
21096
21138
|
workflow: exports_external.string().regex(/^[a-z][a-zA-Z0-9_-]*$/),
|
|
21097
21139
|
input: exports_external.json(),
|
|
21098
21140
|
key: exports_external.string().nullable(),
|
|
@@ -21115,29 +21157,29 @@ function workflowMethods(plugin, namespace, name, definition, ctx, bridge, direc
|
|
|
21115
21157
|
return bridge({ operation, dir, plugin, namespace, workflow: name, root: ctx.root, caller: ctx.caller, input: input2, settings: workflowSettings(definition) });
|
|
21116
21158
|
};
|
|
21117
21159
|
const id = exports_external.object({ id: exports_external.string().uuid() }).strict();
|
|
21118
|
-
const spec = (method2,
|
|
21160
|
+
const spec = (method2, effect, input2, description, approve, output2) => ({
|
|
21119
21161
|
name: `${plugin}.${name}.${method2}`,
|
|
21120
21162
|
plugin,
|
|
21121
|
-
|
|
21163
|
+
effect,
|
|
21122
21164
|
input: input2,
|
|
21123
21165
|
description,
|
|
21124
|
-
|
|
21166
|
+
approve,
|
|
21125
21167
|
output: output2,
|
|
21126
21168
|
run: async (input3, raw) => {
|
|
21127
|
-
if (
|
|
21169
|
+
if (effect === "read")
|
|
21128
21170
|
recordRead(workflowTag(namespace));
|
|
21129
21171
|
const request = method2 === "start" ? { ...input3, input: raw.input } : input3;
|
|
21130
21172
|
return invoke(method2, request);
|
|
21131
21173
|
}
|
|
21132
21174
|
});
|
|
21133
21175
|
return [
|
|
21134
|
-
spec("list", "
|
|
21135
|
-
spec("get", "
|
|
21136
|
-
spec("start", "
|
|
21137
|
-
spec("retry", "
|
|
21138
|
-
spec("restart", "
|
|
21139
|
-
spec("cancel", "
|
|
21140
|
-
spec("cancelAll", "
|
|
21176
|
+
spec("list", "read", exports_external.object({ limit: exports_external.number().int().min(1).max(100).default(20), status: exports_external.string().optional(), cursor: exports_external.string().optional() }).strict(), "List workflow runs", undefined, exports_external.object({ items: exports_external.array(RunSummarySchema), cursor: exports_external.string().nullable() })),
|
|
21177
|
+
spec("get", "read", id, "Get one workflow run"),
|
|
21178
|
+
spec("start", "write", exports_external.object({ input: definition.input, key: exports_external.string().min(1).max(200).optional() }).strict(), definition.description ?? "Start a durable workflow", definition.approve, RunSummarySchema),
|
|
21179
|
+
spec("retry", "write", id, "Retry a safely failed query step", undefined, RunSummarySchema),
|
|
21180
|
+
spec("restart", "write", id, "Start a run that needs attention over from the beginning, on the current code", undefined, RunSummarySchema),
|
|
21181
|
+
spec("cancel", "write", id, "Request cancellation of a workflow", undefined, RunSummarySchema),
|
|
21182
|
+
spec("cancelAll", "write", exports_external.object({ status: exports_external.string().optional() }).strict(), "Cancel every unfinished run, or only those in one status", undefined, exports_external.object({ cancelled: exports_external.number().int() }))
|
|
21141
21183
|
];
|
|
21142
21184
|
}
|
|
21143
21185
|
function workflowSettings(definition) {
|
|
@@ -21197,6 +21239,8 @@ class Runtime {
|
|
|
21197
21239
|
continue;
|
|
21198
21240
|
if (only && !only.includes(entry.name))
|
|
21199
21241
|
continue;
|
|
21242
|
+
if (!existsSync(join3(pluginsDir, entry.name, "app.ts")))
|
|
21243
|
+
continue;
|
|
21200
21244
|
const module = await logContext.run({ plugin: entry.name }, () => import(join3(pluginsDir, entry.name, "app.ts")));
|
|
21201
21245
|
await this.add(entry.name, module.default);
|
|
21202
21246
|
}
|
|
@@ -21322,9 +21366,11 @@ class Runtime {
|
|
|
21322
21366
|
this.register({
|
|
21323
21367
|
name: fullName,
|
|
21324
21368
|
plugin: name,
|
|
21325
|
-
|
|
21369
|
+
effect: method2.effect,
|
|
21370
|
+
stream: method2.stream,
|
|
21326
21371
|
description: method2.description,
|
|
21327
|
-
|
|
21372
|
+
approve: method2.approve,
|
|
21373
|
+
approveUpTo: method2.approveUpTo,
|
|
21328
21374
|
http: method2.http,
|
|
21329
21375
|
input: method2.input,
|
|
21330
21376
|
output: method2.output,
|
|
@@ -21355,6 +21401,10 @@ class Runtime {
|
|
|
21355
21401
|
})))));
|
|
21356
21402
|
}
|
|
21357
21403
|
async call(name, input2 = {}, options = {}) {
|
|
21404
|
+
if (options.signal) {
|
|
21405
|
+
const { signal, ...rest } = options;
|
|
21406
|
+
return this.callSignal.run(signal, () => this.call(name, input2, rest));
|
|
21407
|
+
}
|
|
21358
21408
|
if (options.files) {
|
|
21359
21409
|
const { files, ...rest } = options;
|
|
21360
21410
|
const fields = input2 ?? {};
|
|
@@ -21378,17 +21428,17 @@ class Runtime {
|
|
|
21378
21428
|
return runWith(where, () => this.call(name, input2, options));
|
|
21379
21429
|
if (where !== logContext.getStore())
|
|
21380
21430
|
return logContext.run(where, () => this.call(name, input2, options));
|
|
21381
|
-
if ((options.expect === "
|
|
21431
|
+
if ((options.expect === "read" || currentAccess()?.readOnly) && method2.effect !== "read") {
|
|
21382
21432
|
throw new MethodError("FORBIDDEN", `${name} changes data, so it only runs when the user acts.`);
|
|
21383
21433
|
}
|
|
21384
21434
|
const raw = input2 ?? {};
|
|
21385
21435
|
const parsed = method2.input.parse(raw);
|
|
21386
|
-
if (method2.
|
|
21387
|
-
throw new MethodError("CONFIRM",
|
|
21388
|
-
if (method2.
|
|
21436
|
+
if (method2.approve && (options.confirmed === false || workflowScope.getStore()))
|
|
21437
|
+
throw new MethodError("CONFIRM", fillQuestion(method2.approve, parsed));
|
|
21438
|
+
if (method2.stream)
|
|
21389
21439
|
return { result: await this.sample(name, input2), reads: [], invalidates: [] };
|
|
21390
21440
|
const confirmed = this.callConfirmed.getStore() ?? false;
|
|
21391
|
-
const run = await tracked(method2.
|
|
21441
|
+
const run = await tracked(method2.effect === "read", (tags) => this.notifyInvalidated(tags), () => this.callConfirmed.run(confirmed || method2.approve !== undefined, async () => {
|
|
21392
21442
|
const result = await method2.run(parsed, raw);
|
|
21393
21443
|
await this.events.emit(name, { input: parsed, result });
|
|
21394
21444
|
return result;
|
|
@@ -21407,8 +21457,8 @@ class Runtime {
|
|
|
21407
21457
|
const method2 = this.methods.get(name);
|
|
21408
21458
|
if (!method2)
|
|
21409
21459
|
throw new MethodError("NOT_FOUND", `There is no method ${name}.`);
|
|
21410
|
-
if (method2.
|
|
21411
|
-
throw new MethodError("VALIDATION", `${name} is
|
|
21460
|
+
if (!method2.stream)
|
|
21461
|
+
throw new MethodError("VALIDATION", `${name} is not a stream.`);
|
|
21412
21462
|
const raw = input2 ?? {};
|
|
21413
21463
|
const parsed = method2.input.parse(raw);
|
|
21414
21464
|
const root = options.root ?? this.callRoot.getStore() ?? this.root;
|
|
@@ -21463,7 +21513,7 @@ class Runtime {
|
|
|
21463
21513
|
const statuses = [];
|
|
21464
21514
|
for (const entry of entries) {
|
|
21465
21515
|
const timezone = entry.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
21466
|
-
const signature = `${entry.plugin}.${entry.method} ${entry.schedule} ${timezone}`;
|
|
21516
|
+
const signature = `${entry.plugin}.${entry.method} ${entry.schedule} ${timezone} ${entry.root ?? ""}`;
|
|
21467
21517
|
try {
|
|
21468
21518
|
const job = kept.get(signature) ?? this.cronJob(entry, timezone, signature);
|
|
21469
21519
|
jobs.push(job);
|
|
@@ -21495,9 +21545,9 @@ class Runtime {
|
|
|
21495
21545
|
const method2 = this.methods.get(name);
|
|
21496
21546
|
if (!method2 || method2.plugin !== entry.plugin)
|
|
21497
21547
|
throw new Error(`${entry.method} isn't one of ${entry.plugin}'s methods`);
|
|
21498
|
-
if (method2.
|
|
21499
|
-
throw new Error(`${name} is a stream; a cron runs a
|
|
21500
|
-
if (method2.
|
|
21548
|
+
if (method2.stream)
|
|
21549
|
+
throw new Error(`${name} is a stream; a cron runs a read or a write`);
|
|
21550
|
+
if (method2.approve)
|
|
21501
21551
|
throw new Error(`${name} asks before it runs, so nothing may run it on a schedule`);
|
|
21502
21552
|
if (!method2.input.safeParse({}).success)
|
|
21503
21553
|
throw new Error(`${name} needs input; a cron calls it with {}`);
|
|
@@ -21513,6 +21563,7 @@ class Runtime {
|
|
|
21513
21563
|
return {
|
|
21514
21564
|
signature,
|
|
21515
21565
|
plugin: entry.plugin,
|
|
21566
|
+
root: entry.root,
|
|
21516
21567
|
method: name,
|
|
21517
21568
|
schedule: entry.schedule,
|
|
21518
21569
|
timezone,
|
|
@@ -21537,7 +21588,7 @@ class Runtime {
|
|
|
21537
21588
|
job.running = true;
|
|
21538
21589
|
job.lastRunAt = now;
|
|
21539
21590
|
const startedAt = Date.now();
|
|
21540
|
-
started.push(this.call(job.method, {}, { caller: "cron" }).then(() => {
|
|
21591
|
+
started.push(this.call(job.method, {}, { caller: "cron", root: job.root }).then(() => {
|
|
21541
21592
|
job.lastError = null;
|
|
21542
21593
|
}, (error62) => {
|
|
21543
21594
|
job.lastError = toCallError(error62).message;
|
|
@@ -21598,9 +21649,11 @@ class Runtime {
|
|
|
21598
21649
|
description: plugin.description ?? null,
|
|
21599
21650
|
methods: [...this.methods.values()].filter((method2) => method2.plugin === name).map((method2) => ({
|
|
21600
21651
|
name: method2.name,
|
|
21601
|
-
|
|
21652
|
+
effect: method2.effect,
|
|
21653
|
+
stream: method2.stream ?? false,
|
|
21602
21654
|
description: method2.description ?? null,
|
|
21603
|
-
|
|
21655
|
+
approve: method2.approve ?? null,
|
|
21656
|
+
approveUpTo: method2.approve ? method2.approveUpTo ?? "full-access" : null,
|
|
21604
21657
|
http: method2.http ?? null,
|
|
21605
21658
|
input: exports_external.toJSONSchema(method2.input, { io: "input", unrepresentable: "any" }),
|
|
21606
21659
|
output: method2.output ? exports_external.toJSONSchema(method2.output, { io: "output", unrepresentable: "any" }) : null
|
|
@@ -21674,6 +21727,9 @@ class Runtime {
|
|
|
21674
21727
|
register(method2) {
|
|
21675
21728
|
if (this.methods.has(method2.name))
|
|
21676
21729
|
throw new Error(`${method2.name} is defined twice; rename the method or the data`);
|
|
21730
|
+
if (method2.effect === "destructive" && !method2.approve) {
|
|
21731
|
+
throw new Error(`${method2.name} is destructive, so it needs an approve question, such as { approve: "Delete {{id}}?" }`);
|
|
21732
|
+
}
|
|
21677
21733
|
this.methods.set(method2.name, method2);
|
|
21678
21734
|
}
|
|
21679
21735
|
registerGenerated(plugin, name, methods) {
|
|
@@ -21690,32 +21746,32 @@ class Runtime {
|
|
|
21690
21746
|
const prefix = `${plugin}.${name}`;
|
|
21691
21747
|
const id = exports_external.object({ id: exports_external.string() });
|
|
21692
21748
|
const record3 = schema.extend({ id: exports_external.string(), createdAt: exports_external.string(), updatedAt: exports_external.string() });
|
|
21693
|
-
const crud = (method2,
|
|
21749
|
+
const crud = (method2, effect, input2, output2, description, run, approve) => this.register({
|
|
21694
21750
|
name: `${prefix}.${method2}`,
|
|
21695
21751
|
plugin,
|
|
21696
|
-
|
|
21752
|
+
effect,
|
|
21697
21753
|
description,
|
|
21698
|
-
|
|
21754
|
+
approve,
|
|
21699
21755
|
input: input2,
|
|
21700
21756
|
output: output2,
|
|
21701
21757
|
run: async (value, raw) => run(value, raw)
|
|
21702
21758
|
});
|
|
21703
|
-
crud("list", "
|
|
21759
|
+
crud("list", "read", exports_external.object({
|
|
21704
21760
|
limit: exports_external.number().int().positive().max(500).optional(),
|
|
21705
21761
|
offset: exports_external.number().int().min(0).optional(),
|
|
21706
21762
|
orderBy: exports_external.string().optional(),
|
|
21707
21763
|
order: exports_external.enum(["asc", "desc"]).optional()
|
|
21708
21764
|
}), exports_external.array(record3), `List ${name} records, newest first unless orderBy says`, (input2) => collection.list(input2));
|
|
21709
|
-
crud("get", "
|
|
21710
|
-
crud("create", "
|
|
21711
|
-
crud("update", "
|
|
21712
|
-
crud("delete", "
|
|
21765
|
+
crud("get", "read", id, record3.nullable(), `Get one ${name} record by id, or null`, (input2) => collection.get(input2.id));
|
|
21766
|
+
crud("create", "write", schema, record3, `Create a ${name} record`, (input2) => collection.create(input2));
|
|
21767
|
+
crud("update", "write", id.extend(schema.partial().shape), record3, `Update fields on a ${name} record`, ({ id: id2, ...fields }, raw) => collection.update(id2, Object.fromEntries(Object.entries(fields).filter(([key]) => Object.hasOwn(raw ?? {}, key)))));
|
|
21768
|
+
crud("delete", "destructive", id, exports_external.null(), `Delete a ${name} record by id`, async (input2) => {
|
|
21713
21769
|
await collection.delete(input2.id);
|
|
21714
21770
|
return null;
|
|
21715
21771
|
}, "Delete this record?");
|
|
21716
21772
|
}
|
|
21717
21773
|
}
|
|
21718
|
-
function
|
|
21774
|
+
function fillQuestion(question, input2) {
|
|
21719
21775
|
const fields = input2 ?? {};
|
|
21720
21776
|
return question.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (_, field2) => {
|
|
21721
21777
|
const value = Object.hasOwn(fields, field2) ? fields[field2] : undefined;
|
|
@@ -21738,9 +21794,9 @@ function cronState(job) {
|
|
|
21738
21794
|
function mcpFailure(server, tool, error62) {
|
|
21739
21795
|
switch (error62.code) {
|
|
21740
21796
|
case "NOT_READ_ONLY":
|
|
21741
|
-
return new MethodError("FORBIDDEN", `${server}.${tool} changes data, so it runs from a
|
|
21797
|
+
return new MethodError("FORBIDDEN", `${server}.${tool} changes data, so it runs from a write, not a read.`);
|
|
21742
21798
|
case "CONFIRM":
|
|
21743
|
-
return new MethodError("FORBIDDEN", `${server}.${tool} may destroy data, so it runs only from a method with
|
|
21799
|
+
return new MethodError("FORBIDDEN", `${server}.${tool} may destroy data, so it runs only from a method with an approve.`);
|
|
21744
21800
|
}
|
|
21745
21801
|
return new MethodError("INTERNAL", error62.message);
|
|
21746
21802
|
}
|
|
@@ -22848,12 +22904,12 @@ function runExecutor() {
|
|
|
22848
22904
|
return;
|
|
22849
22905
|
}
|
|
22850
22906
|
if (message.type === "hosted") {
|
|
22851
|
-
const
|
|
22907
|
+
const call2 = waiting.get(message.id);
|
|
22852
22908
|
waiting.delete(message.id);
|
|
22853
22909
|
if (message.error)
|
|
22854
|
-
|
|
22910
|
+
call2?.reject(new MethodError(message.error.code, message.error.message));
|
|
22855
22911
|
else
|
|
22856
|
-
|
|
22912
|
+
call2?.resolve(message.result);
|
|
22857
22913
|
return;
|
|
22858
22914
|
}
|
|
22859
22915
|
if (launched)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@repogo/plugins",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"description": "Write RepoGo plugins: collections and methods over SQLite that agents and cards call",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"typecheck": "tsc --noEmit"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"zod": "^4.
|
|
30
|
+
"zod": "^4.6.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/bun": "latest",
|