runline 0.24.0 → 0.26.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/core/engine.js +15 -2
- package/dist/plugin/schema.d.ts +13 -1
- package/dist/plugin/schema.js +115 -19
- package/dist/plugin/types.d.ts +11 -0
- package/package.json +1 -1
package/dist/core/engine.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Worker } from "node:worker_threads";
|
|
4
4
|
import { applyEnvOverrides, updateConnectionConfig } from "../config/loader.js";
|
|
5
|
-
import { formatValidationError, helpInputs, isTypedInputSchema, validateTypedInput, } from "../plugin/schema.js";
|
|
5
|
+
import { formatValidationError, helpInputs, isTypedInputSchema, normalizeActionInput, validateTypedInput, } from "../plugin/schema.js";
|
|
6
6
|
/**
|
|
7
7
|
* How many bodies one worker may run before it is retired.
|
|
8
8
|
*
|
|
@@ -161,6 +161,16 @@ export class ExecutionEngine {
|
|
|
161
161
|
try {
|
|
162
162
|
acquired = this.acquire(shape, () => new Worker(buildWorkerSource(pluginNames, helpData), {
|
|
163
163
|
eval: true,
|
|
164
|
+
// Empty by construction: without an `env` option the worker
|
|
165
|
+
// inherits a copy of the host's entire environment, and
|
|
166
|
+
// Runline lives inside credential-bearing hosts (vex's
|
|
167
|
+
// server process, the pi agent process). Bodies hold a real
|
|
168
|
+
// `require` and a live `process`, so an inherited env is one
|
|
169
|
+
// line of agent code away from every host secret. Nothing
|
|
170
|
+
// in the worker needs an environment: actions execute
|
|
171
|
+
// host-side, connection configs are hydrated host-side, and
|
|
172
|
+
// ferrosearch is required by absolute path.
|
|
173
|
+
env: {},
|
|
164
174
|
resourceLimits: { maxOldGenerationSizeMb: memoryLimitMb },
|
|
165
175
|
}));
|
|
166
176
|
}
|
|
@@ -295,7 +305,10 @@ export class ExecutionEngine {
|
|
|
295
305
|
reply({ t: "run", runId, body });
|
|
296
306
|
});
|
|
297
307
|
}
|
|
298
|
-
async invokeAction(path,
|
|
308
|
+
async invokeAction(path, rawArgs) {
|
|
309
|
+
// A call written with no arguments reaches the action as `{}`, not
|
|
310
|
+
// `undefined`, so plugins can read `input.foo` without guarding.
|
|
311
|
+
const args = normalizeActionInput(rawArgs);
|
|
299
312
|
const resolved = this.registry.resolveAction(path);
|
|
300
313
|
if (!resolved) {
|
|
301
314
|
throw new Error(`Unknown action: ${path}${this.selfPrefixHint(path)}`);
|
package/dist/plugin/schema.d.ts
CHANGED
|
@@ -15,5 +15,17 @@ export declare function legacyFields(schema: InputSchema | undefined): LegacyInp
|
|
|
15
15
|
export declare function connectionFields(schema: ConnectionSchema | undefined): LegacyConnectionSchema;
|
|
16
16
|
export declare function helpInputs(schema: InputSchema | undefined): Record<string, HelpInput>;
|
|
17
17
|
export declare function validateLegacyInput(schema: LegacyInputSchema, input: unknown): ValidationResult;
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A call written with no arguments is a call with an empty argument set.
|
|
20
|
+
*
|
|
21
|
+
* `linear.user.me()` is the most natural way to invoke an action that
|
|
22
|
+
* takes nothing, and validating `undefined` against an object schema
|
|
23
|
+
* rejected it every time. It also made a genuinely missing input report
|
|
24
|
+
* "must be object" instead of naming the field.
|
|
25
|
+
*
|
|
26
|
+
* Only `undefined` is normalized: an explicit `null` or `42` is an
|
|
27
|
+
* argument, and a wrong one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizeActionInput(input: unknown): unknown;
|
|
30
|
+
export declare function validateTypedInput(schema: TypedInputSchema, rawInput: unknown): ValidationResult;
|
|
19
31
|
export declare function formatValidationError(path: string, result: ValidationResult): string;
|
package/dist/plugin/schema.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { Check, Errors } from "typebox/value";
|
|
2
|
+
/**
|
|
3
|
+
* How deep `describe` will follow a schema. Nesting past this is
|
|
4
|
+
* vanishingly rare, and a self-referential schema would otherwise
|
|
5
|
+
* recurse until the stack gave out. A field cut off here says so, so
|
|
6
|
+
* that "no properties" and "stopped looking" stay distinguishable.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_DESCRIBE_DEPTH = 5;
|
|
2
9
|
export function isTypedInputSchema(schema) {
|
|
3
10
|
if (!schema || typeof schema !== "object")
|
|
4
11
|
return false;
|
|
5
12
|
const candidate = schema;
|
|
6
|
-
|
|
13
|
+
// A bare union is a typed schema too. Missing that here would classify
|
|
14
|
+
// it as legacy and iterate its JSON Schema keywords as field names.
|
|
15
|
+
return typeof candidate.type === "string" || Boolean(branchesOf(candidate));
|
|
7
16
|
}
|
|
8
17
|
export function legacyFields(schema) {
|
|
9
18
|
if (!schema || isTypedInputSchema(schema))
|
|
@@ -47,19 +56,79 @@ export function helpInputs(schema) {
|
|
|
47
56
|
const metadata = schema;
|
|
48
57
|
if (metadata.type !== "object")
|
|
49
58
|
return {};
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
return describeProperties(metadata, 0);
|
|
60
|
+
}
|
|
61
|
+
function describeProperties(schema, depth) {
|
|
62
|
+
const required = new Set(schema.required ?? []);
|
|
63
|
+
return Object.fromEntries(Object.entries(schema.properties ?? {}).map(([key, field]) => [
|
|
52
64
|
key,
|
|
53
|
-
|
|
54
|
-
type: baseType(field),
|
|
55
|
-
displayType: displayType(field),
|
|
56
|
-
required: required.has(key),
|
|
57
|
-
description: field.description,
|
|
58
|
-
enum: enumValues(field),
|
|
59
|
-
const: field.const,
|
|
60
|
-
},
|
|
65
|
+
describeField(field, required.has(key), depth),
|
|
61
66
|
]));
|
|
62
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Describe one input, following arrays into their item shape, objects
|
|
70
|
+
* into their properties, and unions into each branch.
|
|
71
|
+
*
|
|
72
|
+
* A parameter typed only as "array" tells an agent nothing about what to
|
|
73
|
+
* put in it, so the shape has to be guessed and the first call is a coin
|
|
74
|
+
* flip. "object | object" is the same dead end wearing a union: two
|
|
75
|
+
* shapes are on offer and neither is shown. Reporting the nested shape
|
|
76
|
+
* is the difference between describe being documentation and being a
|
|
77
|
+
* type name.
|
|
78
|
+
*/
|
|
79
|
+
function describeField(field, required, depth) {
|
|
80
|
+
const described = {
|
|
81
|
+
type: baseType(field),
|
|
82
|
+
displayType: displayType(field),
|
|
83
|
+
required,
|
|
84
|
+
description: field.description,
|
|
85
|
+
enum: enumValues(field),
|
|
86
|
+
const: field.const,
|
|
87
|
+
};
|
|
88
|
+
const branches = unionBranches(field);
|
|
89
|
+
if (depth >= MAX_DESCRIBE_DEPTH) {
|
|
90
|
+
// Only claim truncation where something was actually left unread.
|
|
91
|
+
if (field.items || field.properties || branches)
|
|
92
|
+
described.truncated = true;
|
|
93
|
+
return described;
|
|
94
|
+
}
|
|
95
|
+
if (field.items) {
|
|
96
|
+
// An array element is not "required"; the flag has no meaning here.
|
|
97
|
+
described.items = describeField(field.items, false, depth + 1);
|
|
98
|
+
}
|
|
99
|
+
if (field.properties) {
|
|
100
|
+
described.properties = describeProperties(field, depth + 1);
|
|
101
|
+
}
|
|
102
|
+
if (branches) {
|
|
103
|
+
described.variants = branches.map((branch) => describeField(branch, false, depth + 1));
|
|
104
|
+
}
|
|
105
|
+
return described;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The branches of a union worth describing individually.
|
|
109
|
+
*
|
|
110
|
+
* Two unions need no expansion. A union of literals is already fully
|
|
111
|
+
* described by `enum`, and a union of bare scalars is already fully
|
|
112
|
+
* described by `displayType` — `string | null` is Linear's clearable
|
|
113
|
+
* pattern and repeating it as two empty branches only buries the unions
|
|
114
|
+
* that do carry shape.
|
|
115
|
+
*/
|
|
116
|
+
function unionBranches(field) {
|
|
117
|
+
const branches = branchesOf(field);
|
|
118
|
+
if (!branches)
|
|
119
|
+
return undefined;
|
|
120
|
+
if (enumValues(field))
|
|
121
|
+
return undefined;
|
|
122
|
+
return branches.some(hasShape) ? branches : undefined;
|
|
123
|
+
}
|
|
124
|
+
/** Whether a schema says more than its type name does. */
|
|
125
|
+
function hasShape(schema) {
|
|
126
|
+
return Boolean(schema.properties ||
|
|
127
|
+
schema.items ||
|
|
128
|
+
branchesOf(schema) ||
|
|
129
|
+
schema.enum?.length ||
|
|
130
|
+
schema.description);
|
|
131
|
+
}
|
|
63
132
|
export function validateLegacyInput(schema, input) {
|
|
64
133
|
const provided = input && typeof input === "object"
|
|
65
134
|
? input
|
|
@@ -86,7 +155,22 @@ export function validateLegacyInput(schema, input) {
|
|
|
86
155
|
}
|
|
87
156
|
return validationResult({ missing, unknown, typeErrors });
|
|
88
157
|
}
|
|
89
|
-
|
|
158
|
+
/**
|
|
159
|
+
* A call written with no arguments is a call with an empty argument set.
|
|
160
|
+
*
|
|
161
|
+
* `linear.user.me()` is the most natural way to invoke an action that
|
|
162
|
+
* takes nothing, and validating `undefined` against an object schema
|
|
163
|
+
* rejected it every time. It also made a genuinely missing input report
|
|
164
|
+
* "must be object" instead of naming the field.
|
|
165
|
+
*
|
|
166
|
+
* Only `undefined` is normalized: an explicit `null` or `42` is an
|
|
167
|
+
* argument, and a wrong one.
|
|
168
|
+
*/
|
|
169
|
+
export function normalizeActionInput(input) {
|
|
170
|
+
return input === undefined ? {} : input;
|
|
171
|
+
}
|
|
172
|
+
export function validateTypedInput(schema, rawInput) {
|
|
173
|
+
const input = normalizeActionInput(rawInput);
|
|
90
174
|
if (Check(schema, input))
|
|
91
175
|
return validationResult({});
|
|
92
176
|
return validationResult({
|
|
@@ -130,9 +214,20 @@ function validationResult(input) {
|
|
|
130
214
|
errors,
|
|
131
215
|
};
|
|
132
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* The alternatives of a union, however the schema spells it. TypeBox
|
|
219
|
+
* emits `anyOf`; hand-written schemas in the plugins use `oneOf`. Every
|
|
220
|
+
* reader here has to treat them the same, so they are unified once, and
|
|
221
|
+
* an empty branch list is reported as no union at all.
|
|
222
|
+
*/
|
|
223
|
+
function branchesOf(schema) {
|
|
224
|
+
const branches = schema.anyOf ?? schema.oneOf;
|
|
225
|
+
return branches?.length ? branches : undefined;
|
|
226
|
+
}
|
|
133
227
|
function displayType(schema) {
|
|
134
|
-
|
|
135
|
-
|
|
228
|
+
const branches = branchesOf(schema);
|
|
229
|
+
if (branches)
|
|
230
|
+
return branches.map(displayType).join(" | ");
|
|
136
231
|
if (schema.enum?.length)
|
|
137
232
|
return schema.enum.map(String).join(" | ");
|
|
138
233
|
if (schema.const !== undefined)
|
|
@@ -140,8 +235,9 @@ function displayType(schema) {
|
|
|
140
235
|
return schema.type ?? "unknown";
|
|
141
236
|
}
|
|
142
237
|
function baseType(schema) {
|
|
143
|
-
|
|
144
|
-
|
|
238
|
+
const branches = branchesOf(schema);
|
|
239
|
+
if (branches) {
|
|
240
|
+
const types = [...new Set(branches.map(baseType))];
|
|
145
241
|
return types.length === 1 ? types[0] : types.join(" | ");
|
|
146
242
|
}
|
|
147
243
|
if (schema.type)
|
|
@@ -153,9 +249,9 @@ function baseType(schema) {
|
|
|
153
249
|
function enumValues(schema) {
|
|
154
250
|
if (schema.enum?.length)
|
|
155
251
|
return schema.enum;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
252
|
+
const branches = branchesOf(schema);
|
|
253
|
+
if (branches?.every((s) => s.const !== undefined)) {
|
|
254
|
+
return branches.map((s) => s.const);
|
|
159
255
|
}
|
|
160
256
|
return undefined;
|
|
161
257
|
}
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -24,6 +24,17 @@ export interface HelpInput {
|
|
|
24
24
|
description?: string;
|
|
25
25
|
enum?: unknown[];
|
|
26
26
|
const?: unknown;
|
|
27
|
+
/** Element shape, for array inputs. */
|
|
28
|
+
items?: HelpInput;
|
|
29
|
+
/** Nested field shapes, for object inputs. */
|
|
30
|
+
properties?: Record<string, HelpInput>;
|
|
31
|
+
/**
|
|
32
|
+
* Branches of a union, where at least one carries shape the display
|
|
33
|
+
* type cannot express. Absent for unions of literals or bare scalars.
|
|
34
|
+
*/
|
|
35
|
+
variants?: HelpInput[];
|
|
36
|
+
/** Set when nesting ran past the describe depth limit and was not read. */
|
|
37
|
+
truncated?: boolean;
|
|
27
38
|
}
|
|
28
39
|
export type ActionAccess = "read" | "write";
|
|
29
40
|
export interface ActionDef {
|