@danypops/papyrus 0.46.1 → 0.46.2
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/package.json +1 -1
- package/src/handlers/playbooks.ts +6 -2
- package/src/handlers/shared.ts +110 -23
- package/src/handlers/tasks.ts +3 -1
package/package.json
CHANGED
|
@@ -44,6 +44,10 @@ import {
|
|
|
44
44
|
} from "./shared.ts";
|
|
45
45
|
|
|
46
46
|
const OWNER = "playbooks";
|
|
47
|
+
const jsonObjectProp = {
|
|
48
|
+
type: ["object", "string"],
|
|
49
|
+
description: "A JSON object; a JSON-encoded object string is also accepted for tool-calling compatibility.",
|
|
50
|
+
} as const;
|
|
47
51
|
|
|
48
52
|
export interface PlaybooksVehicleDeps {
|
|
49
53
|
artifacts: ArtifactStore;
|
|
@@ -126,7 +130,7 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
|
|
|
126
130
|
"preview",
|
|
127
131
|
"Renders a Playbook's whole composition tree as text, with no side effects.",
|
|
128
132
|
"read",
|
|
129
|
-
{ id: stringProp, name: stringProp, arguments:
|
|
133
|
+
{ id: stringProp, name: stringProp, arguments: jsonObjectProp },
|
|
130
134
|
[],
|
|
131
135
|
(input) => {
|
|
132
136
|
normalizeJsonEncodedField(input, "arguments");
|
|
@@ -138,7 +142,7 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
|
|
|
138
142
|
"invoke",
|
|
139
143
|
"Compiles the Playbook's steps and composition tree into real Tasks wired with dependsOn, and focuses the first one -- one step surfaces at a time as it becomes focused, exactly like any other Task. `arguments` supplies known values as {name: value}; if a declared REQUIRED argument is still missing, nothing is created and missingArguments is returned instead -- ask the human for these (discuss tool, live:true) and invoke again, never guess. Drive the returned entryTaskId forward with the tasks tool (start/submit/complete).",
|
|
140
144
|
"local-write",
|
|
141
|
-
{ id: stringProp, name: stringProp, run_id: stringProp, arguments:
|
|
145
|
+
{ id: stringProp, name: stringProp, run_id: stringProp, arguments: jsonObjectProp, project_root: stringProp },
|
|
142
146
|
[],
|
|
143
147
|
(input) => {
|
|
144
148
|
normalizeJsonEncodedField(input, "arguments");
|
package/src/handlers/shared.ts
CHANGED
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
bindVehicleOperation,
|
|
8
8
|
defineVehicleOperation,
|
|
9
9
|
defineVehicleSchema,
|
|
10
|
+
type JsonSchema,
|
|
10
11
|
type VehicleContentBlock,
|
|
11
12
|
VehicleError,
|
|
12
13
|
type VehicleLimits,
|
|
13
14
|
type VehicleOperationContext,
|
|
14
15
|
type VehicleSchemaCodec,
|
|
16
|
+
type VehicleSchemaIssue,
|
|
15
17
|
} from "@danypops/vehicle-core";
|
|
16
18
|
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
17
19
|
import type { Artifact } from "../artifact/artifact.ts";
|
|
@@ -21,33 +23,118 @@ import { InvalidSessionSecretError } from "../session-identity/session-identity-
|
|
|
21
23
|
import { TaskCreateIdempotencyConflictError } from "../stores/task-create-request-store.ts";
|
|
22
24
|
import { TaskDependencyCycleError, TaskExecutionBoundExceededError, type TaskExecutionPlan } from "../task/task-execution.ts";
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
interface OperationSchemaNode {
|
|
27
|
+
readonly type?: string | readonly string[];
|
|
28
|
+
readonly enum?: readonly unknown[];
|
|
29
|
+
readonly properties?: Readonly<Record<string, OperationSchemaNode>>;
|
|
30
|
+
readonly required?: readonly string[];
|
|
31
|
+
readonly additionalProperties?: boolean | OperationSchemaNode;
|
|
32
|
+
readonly items?: OperationSchemaNode;
|
|
33
|
+
readonly minLength?: number;
|
|
34
|
+
readonly maxLength?: number;
|
|
35
|
+
readonly minimum?: number;
|
|
36
|
+
readonly maximum?: number;
|
|
37
|
+
readonly minItems?: number;
|
|
38
|
+
readonly maxItems?: number;
|
|
39
|
+
readonly description?: string;
|
|
40
|
+
readonly [key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function schemaIssue(path: readonly (string | number)[], message: string): VehicleSchemaIssue[] {
|
|
44
|
+
return [{ path, message }];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function matchesSchemaType(value: unknown, type: string): boolean {
|
|
48
|
+
if (type === "object") return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
+
if (type === "array") return Array.isArray(value);
|
|
50
|
+
if (type === "string") return typeof value === "string";
|
|
51
|
+
if (type === "number") return typeof value === "number" && Number.isFinite(value);
|
|
52
|
+
if (type === "integer") return typeof value === "number" && Number.isInteger(value);
|
|
53
|
+
if (type === "boolean") return typeof value === "boolean";
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateSchemaValue(value: unknown, schema: OperationSchemaNode, path: readonly (string | number)[]): VehicleSchemaIssue[] {
|
|
58
|
+
const label = path.length === 0 ? "input" : String(path.at(-1));
|
|
59
|
+
const declaredTypes = typeof schema.type === "string" ? [schema.type] : (schema.type ?? []);
|
|
60
|
+
const type = declaredTypes.find((candidate) => matchesSchemaType(value, candidate));
|
|
61
|
+
if (declaredTypes.length > 0 && type === undefined) {
|
|
62
|
+
const accepted = declaredTypes.map((candidate) =>
|
|
63
|
+
candidate === "integer" ? "an integer" : `${candidate === "object" ? "an" : "a"} ${candidate}`,
|
|
64
|
+
);
|
|
65
|
+
return schemaIssue(path, `${label} must be ${accepted.join(" or ")}`);
|
|
66
|
+
}
|
|
67
|
+
if (type === "object") {
|
|
68
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return schemaIssue(path, `${label} must be an object`);
|
|
69
|
+
const record = value as Record<string, unknown>;
|
|
70
|
+
for (const key of schema.required ?? []) {
|
|
71
|
+
if (!(key in record)) {
|
|
72
|
+
const acceptedShape = schema.description ? `; ${schema.description}` : "";
|
|
73
|
+
return schemaIssue([...path, key], `${key} is required${acceptedShape}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const [key, child] of Object.entries(schema.properties ?? {})) {
|
|
77
|
+
if (!(key in record)) continue;
|
|
78
|
+
const issues = validateSchemaValue(record[key], child, [...path, key]);
|
|
79
|
+
if (issues.length > 0) return issues;
|
|
80
|
+
}
|
|
81
|
+
for (const key of Object.keys(record)) {
|
|
82
|
+
if (key in (schema.properties ?? {})) continue;
|
|
83
|
+
if (schema.additionalProperties === false) return schemaIssue([...path, key], `${key} is not allowed`);
|
|
84
|
+
if (typeof schema.additionalProperties === "object") {
|
|
85
|
+
const issues = validateSchemaValue(record[key], schema.additionalProperties, [...path, key]);
|
|
86
|
+
if (issues.length > 0) return issues;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else if (type === "array") {
|
|
90
|
+
const entries = value as unknown[];
|
|
91
|
+
if (schema.minItems !== undefined && entries.length < schema.minItems) {
|
|
92
|
+
return schemaIssue(path, `${label} must contain at least ${schema.minItems} item(s)`);
|
|
93
|
+
}
|
|
94
|
+
if (schema.maxItems !== undefined && entries.length > schema.maxItems) {
|
|
95
|
+
return schemaIssue(path, `${label} cannot contain more than ${schema.maxItems} item(s)`);
|
|
96
|
+
}
|
|
97
|
+
if (schema.items) {
|
|
98
|
+
for (const [index, entry] of entries.entries()) {
|
|
99
|
+
const issues = validateSchemaValue(entry, schema.items, [...path, index]);
|
|
100
|
+
if (issues.length > 0) return issues;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else if (type === "string") {
|
|
104
|
+
const text = value as string;
|
|
105
|
+
if (schema.minLength !== undefined && text.length < schema.minLength) {
|
|
106
|
+
return schemaIssue(path, `${label} must contain at least ${schema.minLength} character(s)`);
|
|
107
|
+
}
|
|
108
|
+
if (schema.maxLength !== undefined && text.length > schema.maxLength) {
|
|
109
|
+
return schemaIssue(path, `${label} cannot exceed ${schema.maxLength} character(s)`);
|
|
110
|
+
}
|
|
111
|
+
} else if (type === "number" || type === "integer") {
|
|
112
|
+
const number = value as number;
|
|
113
|
+
if (schema.minimum !== undefined && number < schema.minimum) {
|
|
114
|
+
return schemaIssue(path, `${label} must be at least ${schema.minimum}`);
|
|
115
|
+
}
|
|
116
|
+
if (schema.maximum !== undefined && number > schema.maximum) {
|
|
117
|
+
return schemaIssue(path, `${label} cannot exceed ${schema.maximum}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
121
|
+
return schemaIssue(path, `${label} must be one of ${schema.enum.join(", ")}`);
|
|
122
|
+
}
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** VehicleRegistry executes this codec before resolving or dispatching an operation. Keep the
|
|
127
|
+
* recursive runtime checks aligned with the same JSON Schema clients and tools_man receive. */
|
|
30
128
|
export function looseObjectSchema(
|
|
31
|
-
properties: Record<string,
|
|
129
|
+
properties: Readonly<Record<string, OperationSchemaNode>>,
|
|
32
130
|
required: readonly string[] = [],
|
|
33
131
|
): VehicleSchemaCodec<Record<string, unknown>> {
|
|
132
|
+
const schema = { type: "object", properties, required: [...required], additionalProperties: false } as const;
|
|
34
133
|
return defineVehicleSchema<Record<string, unknown>>({
|
|
35
|
-
jsonSchema:
|
|
134
|
+
jsonSchema: schema as unknown as JsonSchema,
|
|
36
135
|
safeParse(value) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
const input = value as Record<string, unknown>;
|
|
41
|
-
for (const key of required) {
|
|
42
|
-
if (!(key in input)) return { success: false, issues: [{ path: [key], message: `${key} is required` }] };
|
|
43
|
-
}
|
|
44
|
-
for (const [key, schema] of Object.entries(properties)) {
|
|
45
|
-
if (!schema.enum || !(key in input)) continue;
|
|
46
|
-
if (!schema.enum.includes(input[key] as string)) {
|
|
47
|
-
return { success: false, issues: [{ path: [key], message: `${key} must be one of ${schema.enum.join(", ")}` }] };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return { success: true, value: input };
|
|
136
|
+
const issues = validateSchemaValue(value, schema, []);
|
|
137
|
+
return issues.length > 0 ? { success: false, issues } : { success: true, value: value as Record<string, unknown> };
|
|
51
138
|
},
|
|
52
139
|
});
|
|
53
140
|
}
|
|
@@ -249,7 +336,7 @@ export function buildWorkflowRunContent(
|
|
|
249
336
|
|
|
250
337
|
export type OperationSchemaProperties = Record<
|
|
251
338
|
string,
|
|
252
|
-
{ type: string; enum?: readonly string[]; description?: string; [key: string]: unknown }
|
|
339
|
+
{ type: string | readonly string[]; enum?: readonly string[]; description?: string; [key: string]: unknown }
|
|
253
340
|
>;
|
|
254
341
|
|
|
255
342
|
export type DefineOperation = (
|
package/src/handlers/tasks.ts
CHANGED
|
@@ -81,6 +81,7 @@ const gateProp = {
|
|
|
81
81
|
description: "Validation gates run by tasks.run_gates and tasks.complete.",
|
|
82
82
|
items: {
|
|
83
83
|
type: "object",
|
|
84
|
+
description: "Accepted gate shape: {type, target, expect?, timeoutMs?}.",
|
|
84
85
|
properties: {
|
|
85
86
|
type: { type: "string", enum: GATE_TYPES, description: "Gate evaluator." },
|
|
86
87
|
target: { type: "string", minLength: 1, description: "Path, command, text target, or test command." },
|
|
@@ -109,6 +110,7 @@ const checklistProp = {
|
|
|
109
110
|
minItems: 1,
|
|
110
111
|
items: {
|
|
111
112
|
type: "object",
|
|
113
|
+
description: "Accepted proof shape: {type, target, expect?}.",
|
|
112
114
|
properties: {
|
|
113
115
|
type: { type: "string", enum: PROOF_TYPES },
|
|
114
116
|
target: { type: "string", minLength: 1 },
|
|
@@ -286,7 +288,7 @@ export function registerTasksVehicleOperations(registry: VehicleRegistry, deps:
|
|
|
286
288
|
|
|
287
289
|
define(
|
|
288
290
|
"create",
|
|
289
|
-
|
|
291
|
+
'Creates a Task -- work: desired outcomes, gates, checklists, and dependencies. Gates are {type, target, expect?, timeoutMs?}; for example [{type: "command", target: "bun run typecheck", timeoutMs: 60000}]. Checklist criteria are {proof: [{type, target, expect?}]}. project_root is required (no ambient cwd server-side). Prefer parent_name/depends_on_names over parent_id/depends_on -- resolved server-side.',
|
|
290
292
|
"local-write",
|
|
291
293
|
{
|
|
292
294
|
title: stringProp,
|