@repogo/plugins 0.1.43 → 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 +166 -42
- package/dist/context.d.ts +14 -12
- package/dist/define.d.ts +33 -11
- package/dist/generated.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +61 -17
- package/dist/remote.d.ts +7 -0
- package/dist/runtime.d.ts +7 -5
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +25 -19
- 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 +83 -39
- 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,20 +44,32 @@ 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 = {}) {
|
|
50
55
|
if ("kind" in options)
|
|
51
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");
|
|
52
64
|
return {
|
|
53
65
|
input,
|
|
54
66
|
run,
|
|
55
67
|
effect: options.effect ?? "write",
|
|
56
68
|
output: options.output,
|
|
57
69
|
description: options.description,
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
approve: options.approve,
|
|
71
|
+
approveUpTo: options.approveUpTo,
|
|
72
|
+
http: httpAddresses(options.http)
|
|
60
73
|
};
|
|
61
74
|
}
|
|
62
75
|
function cached(input, run, options) {
|
|
@@ -66,15 +79,13 @@ function cached(input, run, options) {
|
|
|
66
79
|
effect: "read",
|
|
67
80
|
output: options.output,
|
|
68
81
|
description: options.description,
|
|
82
|
+
http: httpAddresses(options.http),
|
|
69
83
|
cache: { fresh: parseDuration(options.fresh), shared: options.shared ?? false, timeout: parseDuration(options.timeout ?? "1m") }
|
|
70
84
|
};
|
|
71
85
|
}
|
|
72
86
|
function stream(input, run, options = {}) {
|
|
73
87
|
return { input, run, effect: "read", stream: true, description: options.description };
|
|
74
88
|
}
|
|
75
|
-
// src/uploads.ts
|
|
76
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
77
|
-
|
|
78
89
|
// node_modules/zod/v4/classic/external.js
|
|
79
90
|
var exports_external = {};
|
|
80
91
|
__export(exports_external, {
|
|
@@ -19494,13 +19505,6 @@ function bigint3(params) {
|
|
|
19494
19505
|
function date4(params) {
|
|
19495
19506
|
return _coercedDate(ZodDate, params);
|
|
19496
19507
|
}
|
|
19497
|
-
// src/uploads.ts
|
|
19498
|
-
var callFiles = new AsyncLocalStorage;
|
|
19499
|
-
function upload() {
|
|
19500
|
-
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), {
|
|
19501
|
-
message: "Not a file uploaded for this call."
|
|
19502
|
-
});
|
|
19503
|
-
}
|
|
19504
19508
|
// src/errors.ts
|
|
19505
19509
|
class MethodError extends Error {
|
|
19506
19510
|
code;
|
|
@@ -19525,6 +19529,40 @@ function toCallError(error62) {
|
|
|
19525
19529
|
}
|
|
19526
19530
|
return { code: "INTERNAL", message: error62 instanceof Error ? error62.message : String(error62) };
|
|
19527
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
|
+
}
|
|
19528
19566
|
// src/workflow/scope.ts
|
|
19529
19567
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
19530
19568
|
var attemptScope = new AsyncLocalStorage2;
|
|
@@ -20111,14 +20149,14 @@ class AnswerCache {
|
|
|
20111
20149
|
checked_at integer not null
|
|
20112
20150
|
)`);
|
|
20113
20151
|
}
|
|
20114
|
-
async read(key, options,
|
|
20152
|
+
async read(key, options, fetch2) {
|
|
20115
20153
|
const tag = `${this.namespace}.cached.${Bun.hash(key).toString(36)}`;
|
|
20116
20154
|
recordRead(tag);
|
|
20117
20155
|
const [row] = await this.database.sql.unsafe(`select value, checked_at from cached_answers where key = $1`, [key]);
|
|
20118
20156
|
if (!row)
|
|
20119
|
-
return this.refresh(key, tag, options,
|
|
20157
|
+
return this.refresh(key, tag, options, fetch2);
|
|
20120
20158
|
if (Date.now() - row.checked_at >= options.fresh) {
|
|
20121
|
-
this.refresh(key, tag, options,
|
|
20159
|
+
this.refresh(key, tag, options, fetch2).catch((error62) => record2("error", [error62]));
|
|
20122
20160
|
}
|
|
20123
20161
|
return JSON.parse(row.value);
|
|
20124
20162
|
}
|
|
@@ -20126,12 +20164,12 @@ class AnswerCache {
|
|
|
20126
20164
|
while (this.inflight.size)
|
|
20127
20165
|
await Promise.allSettled([...this.inflight.values()]);
|
|
20128
20166
|
}
|
|
20129
|
-
refresh(key, tag, options,
|
|
20167
|
+
refresh(key, tag, options, fetch2) {
|
|
20130
20168
|
const running = this.inflight.get(key);
|
|
20131
20169
|
if (running)
|
|
20132
20170
|
return running;
|
|
20133
20171
|
const started = Date.now();
|
|
20134
|
-
const work = isolated(async () => withTimeout(Promise.resolve(
|
|
20172
|
+
const work = isolated(async () => withTimeout(Promise.resolve(fetch2()), options.timeout)).then(async (value) => {
|
|
20135
20173
|
await this.save(key, tag, value ?? null, started);
|
|
20136
20174
|
return value ?? null;
|
|
20137
20175
|
}, async (error62) => {
|
|
@@ -20557,7 +20595,7 @@ function kvMethods(name, kv, schema) {
|
|
|
20557
20595
|
description: `Delete a ${name} key`,
|
|
20558
20596
|
input: exports_external.object({ key }),
|
|
20559
20597
|
output: exports_external.null(),
|
|
20560
|
-
|
|
20598
|
+
approve: "Delete this key?",
|
|
20561
20599
|
run: async ({ key: key2 }) => (await kv.delete(key2), null)
|
|
20562
20600
|
}
|
|
20563
20601
|
};
|
|
@@ -20606,7 +20644,7 @@ function streamMethods(name, stream2, schema) {
|
|
|
20606
20644
|
description: `Delete a ${name} key and its lines`,
|
|
20607
20645
|
input: exports_external.object({ key }),
|
|
20608
20646
|
output: exports_external.null(),
|
|
20609
|
-
|
|
20647
|
+
approve: "Delete this stream and its lines?",
|
|
20610
20648
|
run: async ({ key: key2 }) => (await stream2.delete(key2), null)
|
|
20611
20649
|
}
|
|
20612
20650
|
};
|
|
@@ -20687,7 +20725,7 @@ function bucketMethods(name, bucket, schema) {
|
|
|
20687
20725
|
description: `Delete a ${name} file and its meta`,
|
|
20688
20726
|
input: exports_external.object({ id }),
|
|
20689
20727
|
output: exports_external.null(),
|
|
20690
|
-
|
|
20728
|
+
approve: "Delete this file?",
|
|
20691
20729
|
run: async ({ id: id2 }) => (await bucket.delete(id2), null)
|
|
20692
20730
|
}
|
|
20693
20731
|
};
|
|
@@ -21096,7 +21134,7 @@ var RunMetadataSchema = exports_external.object({
|
|
|
21096
21134
|
root: exports_external.string().refine(isAbsolute2),
|
|
21097
21135
|
namespace: exports_external.string().regex(/^[a-z0-9][a-z0-9.-]*$/),
|
|
21098
21136
|
plugin: exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
21099
|
-
caller: exports_external.enum(["card", "agent", "cron", "code", "http"]),
|
|
21137
|
+
caller: exports_external.enum(["card", "agent", "cron", "code", "http", "device"]),
|
|
21100
21138
|
workflow: exports_external.string().regex(/^[a-z][a-zA-Z0-9_-]*$/),
|
|
21101
21139
|
input: exports_external.json(),
|
|
21102
21140
|
key: exports_external.string().nullable(),
|
|
@@ -21119,13 +21157,13 @@ function workflowMethods(plugin, namespace, name, definition, ctx, bridge, direc
|
|
|
21119
21157
|
return bridge({ operation, dir, plugin, namespace, workflow: name, root: ctx.root, caller: ctx.caller, input: input2, settings: workflowSettings(definition) });
|
|
21120
21158
|
};
|
|
21121
21159
|
const id = exports_external.object({ id: exports_external.string().uuid() }).strict();
|
|
21122
|
-
const spec = (method2, effect, input2, description,
|
|
21160
|
+
const spec = (method2, effect, input2, description, approve, output2) => ({
|
|
21123
21161
|
name: `${plugin}.${name}.${method2}`,
|
|
21124
21162
|
plugin,
|
|
21125
21163
|
effect,
|
|
21126
21164
|
input: input2,
|
|
21127
21165
|
description,
|
|
21128
|
-
|
|
21166
|
+
approve,
|
|
21129
21167
|
output: output2,
|
|
21130
21168
|
run: async (input3, raw) => {
|
|
21131
21169
|
if (effect === "read")
|
|
@@ -21137,7 +21175,7 @@ function workflowMethods(plugin, namespace, name, definition, ctx, bridge, direc
|
|
|
21137
21175
|
return [
|
|
21138
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() })),
|
|
21139
21177
|
spec("get", "read", id, "Get one workflow run"),
|
|
21140
|
-
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.
|
|
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),
|
|
21141
21179
|
spec("retry", "write", id, "Retry a safely failed query step", undefined, RunSummarySchema),
|
|
21142
21180
|
spec("restart", "write", id, "Start a run that needs attention over from the beginning, on the current code", undefined, RunSummarySchema),
|
|
21143
21181
|
spec("cancel", "write", id, "Request cancellation of a workflow", undefined, RunSummarySchema),
|
|
@@ -21331,7 +21369,8 @@ class Runtime {
|
|
|
21331
21369
|
effect: method2.effect,
|
|
21332
21370
|
stream: method2.stream,
|
|
21333
21371
|
description: method2.description,
|
|
21334
|
-
|
|
21372
|
+
approve: method2.approve,
|
|
21373
|
+
approveUpTo: method2.approveUpTo,
|
|
21335
21374
|
http: method2.http,
|
|
21336
21375
|
input: method2.input,
|
|
21337
21376
|
output: method2.output,
|
|
@@ -21362,6 +21401,10 @@ class Runtime {
|
|
|
21362
21401
|
})))));
|
|
21363
21402
|
}
|
|
21364
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
|
+
}
|
|
21365
21408
|
if (options.files) {
|
|
21366
21409
|
const { files, ...rest } = options;
|
|
21367
21410
|
const fields = input2 ?? {};
|
|
@@ -21390,12 +21433,12 @@ class Runtime {
|
|
|
21390
21433
|
}
|
|
21391
21434
|
const raw = input2 ?? {};
|
|
21392
21435
|
const parsed = method2.input.parse(raw);
|
|
21393
|
-
if (method2.
|
|
21394
|
-
throw new MethodError("CONFIRM",
|
|
21436
|
+
if (method2.approve && (options.confirmed === false || workflowScope.getStore()))
|
|
21437
|
+
throw new MethodError("CONFIRM", fillQuestion(method2.approve, parsed));
|
|
21395
21438
|
if (method2.stream)
|
|
21396
21439
|
return { result: await this.sample(name, input2), reads: [], invalidates: [] };
|
|
21397
21440
|
const confirmed = this.callConfirmed.getStore() ?? false;
|
|
21398
|
-
const run = await tracked(method2.effect === "read", (tags) => this.notifyInvalidated(tags), () => this.callConfirmed.run(confirmed || method2.
|
|
21441
|
+
const run = await tracked(method2.effect === "read", (tags) => this.notifyInvalidated(tags), () => this.callConfirmed.run(confirmed || method2.approve !== undefined, async () => {
|
|
21399
21442
|
const result = await method2.run(parsed, raw);
|
|
21400
21443
|
await this.events.emit(name, { input: parsed, result });
|
|
21401
21444
|
return result;
|
|
@@ -21504,7 +21547,7 @@ class Runtime {
|
|
|
21504
21547
|
throw new Error(`${entry.method} isn't one of ${entry.plugin}'s methods`);
|
|
21505
21548
|
if (method2.stream)
|
|
21506
21549
|
throw new Error(`${name} is a stream; a cron runs a read or a write`);
|
|
21507
|
-
if (method2.
|
|
21550
|
+
if (method2.approve)
|
|
21508
21551
|
throw new Error(`${name} asks before it runs, so nothing may run it on a schedule`);
|
|
21509
21552
|
if (!method2.input.safeParse({}).success)
|
|
21510
21553
|
throw new Error(`${name} needs input; a cron calls it with {}`);
|
|
@@ -21609,7 +21652,8 @@ class Runtime {
|
|
|
21609
21652
|
effect: method2.effect,
|
|
21610
21653
|
stream: method2.stream ?? false,
|
|
21611
21654
|
description: method2.description ?? null,
|
|
21612
|
-
|
|
21655
|
+
approve: method2.approve ?? null,
|
|
21656
|
+
approveUpTo: method2.approve ? method2.approveUpTo ?? "full-access" : null,
|
|
21613
21657
|
http: method2.http ?? null,
|
|
21614
21658
|
input: exports_external.toJSONSchema(method2.input, { io: "input", unrepresentable: "any" }),
|
|
21615
21659
|
output: method2.output ? exports_external.toJSONSchema(method2.output, { io: "output", unrepresentable: "any" }) : null
|
|
@@ -21683,8 +21727,8 @@ class Runtime {
|
|
|
21683
21727
|
register(method2) {
|
|
21684
21728
|
if (this.methods.has(method2.name))
|
|
21685
21729
|
throw new Error(`${method2.name} is defined twice; rename the method or the data`);
|
|
21686
|
-
if (method2.effect === "destructive" && !method2.
|
|
21687
|
-
throw new Error(`${method2.name} is destructive, so it needs
|
|
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}}?" }`);
|
|
21688
21732
|
}
|
|
21689
21733
|
this.methods.set(method2.name, method2);
|
|
21690
21734
|
}
|
|
@@ -21702,12 +21746,12 @@ class Runtime {
|
|
|
21702
21746
|
const prefix = `${plugin}.${name}`;
|
|
21703
21747
|
const id = exports_external.object({ id: exports_external.string() });
|
|
21704
21748
|
const record3 = schema.extend({ id: exports_external.string(), createdAt: exports_external.string(), updatedAt: exports_external.string() });
|
|
21705
|
-
const crud = (method2, effect, input2, output2, description, run,
|
|
21749
|
+
const crud = (method2, effect, input2, output2, description, run, approve) => this.register({
|
|
21706
21750
|
name: `${prefix}.${method2}`,
|
|
21707
21751
|
plugin,
|
|
21708
21752
|
effect,
|
|
21709
21753
|
description,
|
|
21710
|
-
|
|
21754
|
+
approve,
|
|
21711
21755
|
input: input2,
|
|
21712
21756
|
output: output2,
|
|
21713
21757
|
run: async (value, raw) => run(value, raw)
|
|
@@ -21727,7 +21771,7 @@ class Runtime {
|
|
|
21727
21771
|
}, "Delete this record?");
|
|
21728
21772
|
}
|
|
21729
21773
|
}
|
|
21730
|
-
function
|
|
21774
|
+
function fillQuestion(question, input2) {
|
|
21731
21775
|
const fields = input2 ?? {};
|
|
21732
21776
|
return question.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (_, field2) => {
|
|
21733
21777
|
const value = Object.hasOwn(fields, field2) ? fields[field2] : undefined;
|
|
@@ -21752,7 +21796,7 @@ function mcpFailure(server, tool, error62) {
|
|
|
21752
21796
|
case "NOT_READ_ONLY":
|
|
21753
21797
|
return new MethodError("FORBIDDEN", `${server}.${tool} changes data, so it runs from a write, not a read.`);
|
|
21754
21798
|
case "CONFIRM":
|
|
21755
|
-
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.`);
|
|
21756
21800
|
}
|
|
21757
21801
|
return new MethodError("INTERNAL", error62.message);
|
|
21758
21802
|
}
|
|
@@ -22860,12 +22904,12 @@ function runExecutor() {
|
|
|
22860
22904
|
return;
|
|
22861
22905
|
}
|
|
22862
22906
|
if (message.type === "hosted") {
|
|
22863
|
-
const
|
|
22907
|
+
const call2 = waiting.get(message.id);
|
|
22864
22908
|
waiting.delete(message.id);
|
|
22865
22909
|
if (message.error)
|
|
22866
|
-
|
|
22910
|
+
call2?.reject(new MethodError(message.error.code, message.error.message));
|
|
22867
22911
|
else
|
|
22868
|
-
|
|
22912
|
+
call2?.resolve(message.result);
|
|
22869
22913
|
return;
|
|
22870
22914
|
}
|
|
22871
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",
|