toolroll 0.9.84 → 0.9.85
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/CHANGELOG.md +42 -0
- package/README.md +94 -1193
- package/dist/agent-fence.d.ts +3 -3
- package/dist/agent-fence.js +3 -3
- package/dist/agent-onboard.d.ts +2 -0
- package/dist/agent-onboard.js +3 -0
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +31 -31
- package/dist/browser-workspace.d.ts +5 -0
- package/dist/chat-channel.d.ts +10 -1
- package/dist/chat-channel.js +35 -5
- package/dist/chat-decide.d.ts +16 -4
- package/dist/chat-decide.js +239 -31
- package/dist/chat-messages.d.ts +5 -0
- package/dist/chat-messages.js +1 -1
- package/dist/chat-quiet.js +3 -2
- package/dist/chat-review.d.ts +4 -0
- package/dist/chat-review.js +13 -4
- package/dist/chat-terms.d.ts +9 -0
- package/dist/chat-terms.js +46 -0
- package/dist/contracts/console-api.d.ts +2 -1
- package/dist/contracts/console-api.js +2 -1
- package/dist/contracts/lead-tools.d.ts +30 -0
- package/dist/contracts/lead-tools.js +3 -0
- package/dist/contracts/youiyoux-request.d.ts +248 -0
- package/dist/contracts/youiyoux-request.js +152 -0
- package/dist/flow-cadence.d.ts +8 -2
- package/dist/flow-cadence.js +16 -3
- package/dist/flow-card-task.d.ts +9 -0
- package/dist/flow-card-task.js +15 -6
- package/dist/integrations-ui.js +9 -4
- package/dist/integrations.d.ts +9 -1
- package/dist/integrations.js +43 -2
- package/dist/landed-cleanup.d.ts +4 -0
- package/dist/landed-cleanup.js +3 -2
- package/dist/lead-capabilities.js +2 -1
- package/dist/lead-cli.js +3 -1
- package/dist/lead-tools.d.ts +2 -0
- package/dist/lead-tools.js +30 -4
- package/dist/lead.d.ts +2 -0
- package/dist/lead.js +3 -3
- package/dist/operate.d.ts +1 -2
- package/dist/operate.js +11 -15
- package/dist/project-activity.d.ts +20 -0
- package/dist/project-activity.js +23 -0
- package/dist/project-cli.d.ts +1 -0
- package/dist/project-cli.js +36 -19
- package/dist/project-knowledge.d.ts +3 -3
- package/dist/project-outcomes.d.ts +119 -0
- package/dist/project-outcomes.js +79 -0
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +41 -9
- package/dist/result-review.d.ts +9 -0
- package/dist/result-review.js +45 -5
- package/dist/server/console.css +12 -0
- package/dist/server/pages.js +11 -1
- package/dist/server/render-chat.js +3 -2
- package/dist/server/render-pages.d.ts +2 -2
- package/dist/server/render-pages.js +7 -2
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +20 -7
- package/dist/server/render-tasks.d.ts +1 -0
- package/dist/server/render-tasks.js +15 -5
- package/dist/server/route-table.js +1 -0
- package/dist/server/settings.js +18 -1
- package/dist/server/task-views.js +6 -0
- package/dist/server/tasks.js +42 -6
- package/dist/store.d.ts +46 -3
- package/dist/store.js +165 -6
- package/dist/task-source.d.ts +13 -1
- package/dist/task-source.js +17 -1
- package/dist/telegram-lead.js +7 -5
- package/dist/telegram.js +1 -1
- package/dist/toolroll-update.js +2 -2
- package/dist/webhooks.d.ts +6 -0
- package/dist/webhooks.js +25 -0
- package/dist/work-index.d.ts +4 -0
- package/dist/work-index.js +32 -0
- package/dist/youiyoux-intake.d.ts +234 -0
- package/dist/youiyoux-intake.js +908 -0
- package/package.json +11 -8
package/dist/chat-review.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Store } from "./store.js";
|
|
2
2
|
import type { VerifiedApprover } from "./principal.js";
|
|
3
|
+
import { type FeedbackImage } from "./result-review.js";
|
|
3
4
|
export type ReviewSnapshot = {
|
|
4
5
|
task: string;
|
|
5
6
|
root: string;
|
|
@@ -9,11 +10,13 @@ export type ReviewSnapshot = {
|
|
|
9
10
|
sha: string;
|
|
10
11
|
source: string;
|
|
11
12
|
title: string;
|
|
13
|
+
images?: FeedbackImage[];
|
|
12
14
|
notes: {
|
|
13
15
|
id: number;
|
|
14
16
|
note: string;
|
|
15
17
|
path: string | null;
|
|
16
18
|
line: number | null;
|
|
19
|
+
image?: FeedbackImage | null;
|
|
17
20
|
}[];
|
|
18
21
|
};
|
|
19
22
|
type Problem = {
|
|
@@ -32,6 +35,7 @@ export type ReviewRequest = {
|
|
|
32
35
|
path: string | null;
|
|
33
36
|
line: number | null;
|
|
34
37
|
notes: number[];
|
|
38
|
+
image?: FeedbackImage | null;
|
|
35
39
|
};
|
|
36
40
|
export declare function reviewInputProblem(note: string | null, path: string | null, line: number | null): string | null;
|
|
37
41
|
/** Chat and the result form write the same comments and call the same revision service.
|
package/dist/chat-review.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { selectResultImages } from "./chat-evidence.js";
|
|
1
2
|
import { readVerifiedArtifact } from "./evidence.js";
|
|
2
3
|
import { hasForbiddenControls, validateNote } from "./decision.js";
|
|
3
|
-
import { isRevisionFeedback, revisionSourceOf } from "./result-review.js";
|
|
4
|
-
import { createResultRevision } from "./result-actions.js";
|
|
4
|
+
import { parseFeedbackImage, isRevisionFeedback, revisionSourceOf } from "./result-review.js";
|
|
5
|
+
import { createResultRevision, feedbackImageProblem } from "./result-actions.js";
|
|
5
6
|
/** Read precisely the result named, never silently substitute a newer version. */
|
|
6
7
|
export function readChatResult(store, who, evidenceRoot, task, run) {
|
|
7
8
|
const ref = store.lookupRef(task);
|
|
@@ -21,11 +22,13 @@ export function readChatResult(store, who, evidenceRoot, task, run) {
|
|
|
21
22
|
const proven = readVerifiedArtifact(evidenceRoot, artifact);
|
|
22
23
|
if (!proven.ok)
|
|
23
24
|
return { ok: false, message: "The saved changes could not be verified. Restore the result before adding feedback." };
|
|
25
|
+
const images = selectResultImages(store, who, evidenceRoot, task, found.id);
|
|
24
26
|
return { ok: true, snapshot: {
|
|
25
27
|
task, root: family.root.id, execution: family.current.id, run: found.id,
|
|
26
28
|
artifact: artifact.id, sha: artifact.sha256, source: revisionSourceOf(store.getScope(task)?.digest ?? null),
|
|
27
29
|
title: store.getTask(task)?.title ?? task,
|
|
28
|
-
|
|
30
|
+
images: images.ok ? images.selection.images.map(one => ({ run: found.id, artifact: one.artifact, sha256: one.sha256 })) : [],
|
|
31
|
+
notes: store.liveDiffComments(found.id).filter(isRevisionFeedback).map(one => ({ id: one.id, note: one.note, path: one.path, line: one.line, image: one.image ?? null })),
|
|
29
32
|
} };
|
|
30
33
|
}
|
|
31
34
|
export function reviewInputProblem(note, path, line) {
|
|
@@ -65,9 +68,15 @@ export function applyChatReview(store, who, evidenceRoot, request, now, allowMod
|
|
|
65
68
|
return fail("Some feedback changed or was already used. Review it again.");
|
|
66
69
|
if (operation === "note" && (note === null || notes.length > 0))
|
|
67
70
|
return fail("A saved note needs feedback text and cannot consume other notes.");
|
|
71
|
+
const image = parseFeedbackImage(request.image);
|
|
72
|
+
if (image === false || (image !== null && note === null))
|
|
73
|
+
return fail("Choose a screenshot and write its feedback again.");
|
|
74
|
+
const imageProblem = feedbackImageProblem(store, evidenceRoot, live.run, image);
|
|
75
|
+
if (imageProblem !== null)
|
|
76
|
+
return fail(imageProblem);
|
|
68
77
|
const batch = [...notes].sort((a, b) => a - b);
|
|
69
78
|
if (note !== null) {
|
|
70
|
-
const id = store.addDiffComment({ artifactId: live.artifact, runId: live.run, path, line, note, author: who.name }, now);
|
|
79
|
+
const id = store.addDiffComment({ artifactId: live.artifact, runId: live.run, path, line, note, author: who.name, image }, now);
|
|
71
80
|
if (id === null)
|
|
72
81
|
return fail("The note could not be saved.");
|
|
73
82
|
batch.push(id);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Scope } from "./scope.js";
|
|
2
|
+
/** What a plan's yes allows an agent to do, in a few plain words (the approval sheet's "You're allowing"). */
|
|
3
|
+
export declare function permissionPlainWords(profile: Scope["profile"] | null | undefined): string | null;
|
|
4
|
+
export type ShownScope = Pick<Scope, "goal" | "outOfScope" | "touches" | "acceptance" | "profile" | "budgetMicrousd">;
|
|
5
|
+
/** Every word of the terms a yes agrees to, as the card shows them: the goal, what's excluded, the files (or plainly any
|
|
6
|
+
* file), each requirement with the evidence it needs, and what the agent may do and spend. Nothing is shortened. */
|
|
7
|
+
export declare function scopeTermsText(scope: ShownScope): string;
|
|
8
|
+
/** The digest of the terms shown, bound to the scope's own digest: a changed word, limit or route is another digest. */
|
|
9
|
+
export declare function shownTermsDigest(scope: ShownScope & Pick<Scope, "digest">): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scope's terms as a chat card shows them whole, and the digest of exactly those words. A task filed from outside
|
|
3
|
+
* text (a youiyoux change request) approves from chat only on a card that showed every word of its terms, nothing cut:
|
|
4
|
+
* the card binds that digest to its button, and the seal (store.ts, the outside-request quarantine) recomputes it from
|
|
5
|
+
* the live scope and refuses a tap that names anything else. A leaf: the store and the chat road both read it.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { isAcpProfile } from "./acp-agents.js";
|
|
9
|
+
/** What a plan's yes allows an agent to do, in a few plain words (the approval sheet's "You're allowing"). */
|
|
10
|
+
export function permissionPlainWords(profile) {
|
|
11
|
+
if (profile === null || profile === undefined)
|
|
12
|
+
return null;
|
|
13
|
+
const full = "full access, nothing asks first";
|
|
14
|
+
if (profile.provider === "claude") {
|
|
15
|
+
return profile.permissionArgv === "bypassPermissions" ? full
|
|
16
|
+
: profile.permissionArgv === "auto" ? "file edits and routine commands; anything risky stops"
|
|
17
|
+
: "file edits only; commands are refused";
|
|
18
|
+
}
|
|
19
|
+
if (profile.provider === "gemini")
|
|
20
|
+
return profile.approvalArgv === "yolo" ? full : "file edits only; other tools are refused";
|
|
21
|
+
if (isAcpProfile(profile))
|
|
22
|
+
return "full access inside the OS fence, nothing asks first";
|
|
23
|
+
return profile.sandboxMode === "danger-full-access" ? full : "file edits and commands inside the project only";
|
|
24
|
+
}
|
|
25
|
+
const money = (micro) => `$${(micro / 1_000_000).toFixed(2)}`;
|
|
26
|
+
/** Every word of the terms a yes agrees to, as the card shows them: the goal, what's excluded, the files (or plainly any
|
|
27
|
+
* file), each requirement with the evidence it needs, and what the agent may do and spend. Nothing is shortened. */
|
|
28
|
+
export function scopeTermsText(scope) {
|
|
29
|
+
const allows = [permissionPlainWords(scope.profile) ?? "agent permissions not known", scope.budgetMicrousd === null ? "no attempt limit" : `up to ${money(scope.budgetMicrousd)} per attempt`];
|
|
30
|
+
return [
|
|
31
|
+
"Goal:", scope.goal,
|
|
32
|
+
"",
|
|
33
|
+
"Not this:", scope.outOfScope ?? "Nothing excluded.",
|
|
34
|
+
"",
|
|
35
|
+
`Files: ${scope.touches.length === 0 ? "any file (no file list)" : scope.touches.join(", ")}`,
|
|
36
|
+
"",
|
|
37
|
+
"Done when:",
|
|
38
|
+
...(scope.acceptance.length === 0 ? ["You decide when you review the result."] : scope.acceptance.map(one => `• ${one.statement} (${one.evidence.join(", ")})`)),
|
|
39
|
+
"",
|
|
40
|
+
`Starting allows: ${allows.join(" · ")}`,
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
/** The digest of the terms shown, bound to the scope's own digest: a changed word, limit or route is another digest. */
|
|
44
|
+
export function shownTermsDigest(scope) {
|
|
45
|
+
return createHash("sha256").update(JSON.stringify({ v: 1, scope: scope.digest, shown: scopeTermsText(scope) })).digest("hex");
|
|
46
|
+
}
|
|
@@ -102,6 +102,7 @@ export declare const CONSOLE_FORMS: {
|
|
|
102
102
|
readonly data: FormContract<"password">;
|
|
103
103
|
readonly flowsOn: FormContract<"repo" | "starter">;
|
|
104
104
|
readonly integrationsTest: FormContract<"key">;
|
|
105
|
+
readonly integrationsYouiyoux: FormContract<"on" | "repo">;
|
|
105
106
|
readonly monitoring: FormContract<"folder" | "header-name" | "header-value" | "password" | "rotate" | "traces" | "webhook">;
|
|
106
107
|
readonly toolsConnect: FormContract<"access" | "also" | "kit" | "password" | "repo" | "service" | "shown" | "template">;
|
|
107
108
|
readonly toolsChange: FormContract<"action" | "catalog" | "import" | "name" | "password" | "repo" | "secret" | "secrets" | "shown" | "target" | "transport" | "value">;
|
|
@@ -168,7 +169,7 @@ export declare const CONSOLE_FORMS: {
|
|
|
168
169
|
readonly chatAck: FormContract<"nonce" | "token">;
|
|
169
170
|
readonly recipes: FormContract<"document" | "next" | "preview" | "projectRevision" | "purpose" | "recipe" | "recipeRevision" | "repo" | "source" | "sourceRevision">;
|
|
170
171
|
readonly runNote: FormContract<"note">;
|
|
171
|
-
readonly diffComment: FormContract<"batch" | "intent" | "line" | "note" | "path" | "request" | "return" | "source" | "tab">;
|
|
172
|
+
readonly diffComment: FormContract<"batch" | "comment" | "image" | "intent" | "line" | "note" | "path" | "request" | "return" | "source" | "tab">;
|
|
172
173
|
readonly editorLinks: FormContract<"on" | "return">;
|
|
173
174
|
readonly revise: FormContract<"batch" | "return" | "source">;
|
|
174
175
|
readonly followUp: FormContract<"level" | "return">;
|
|
@@ -111,6 +111,7 @@ export const CONSOLE_FORMS = {
|
|
|
111
111
|
data: formContract("POST /settings/data", ["password"]),
|
|
112
112
|
flowsOn: formContract("POST /settings/flows/on", ["repo", "starter"]),
|
|
113
113
|
integrationsTest: formContract("POST /settings/integrations/test", ["key"]),
|
|
114
|
+
integrationsYouiyoux: formContract("POST /settings/integrations/youiyoux", ["repo", "on"]),
|
|
114
115
|
monitoring: formContract("POST /settings/monitoring", ["password", "webhook", "folder", "traces", "header-name", "header-value", "rotate"]),
|
|
115
116
|
toolsConnect: formContract("POST /settings/tools/connect", ["repo", "also", "service", "template", "kit", "shown", "password", "access"]),
|
|
116
117
|
toolsChange: formContract("POST /settings/tools/change", ["repo", "shown", "action", "name", "password", "catalog", "transport", "target", "secrets", "import", "secret", "value"]),
|
|
@@ -182,7 +183,7 @@ export const CONSOLE_FORMS = {
|
|
|
182
183
|
// A recipe's own answers and editor fields are read by recipes.ts from the whole form (`sent`; item 17).
|
|
183
184
|
recipes: formContract("POST /recipes/prepare, /recipes/preview, /recipes/import, /recipes/launch, /recipes/save", ["repo", "projectRevision", "recipe", "recipeRevision", "document", "purpose", "source", "sourceRevision", "preview", "next"]),
|
|
184
185
|
runNote: formContract("POST /r/<run>/note", ["note"]),
|
|
185
|
-
diffComment: formContract("POST /r/<run>/comment", ["return", "tab", "intent", "batch", "source", "note", "path", "line", "request"]),
|
|
186
|
+
diffComment: formContract("POST /r/<run>/comment", ["return", "tab", "intent", "batch", "source", "note", "path", "line", "request", "image", "comment"]),
|
|
186
187
|
editorLinks: formContract("POST /session/editor-links", ["on", "return"]),
|
|
187
188
|
revise: formContract("POST /r/<run>/revise", ["return", "batch", "source"]),
|
|
188
189
|
followUp: formContract("POST /r/<run>/checks|add-tests", ["return", "level"]),
|
|
@@ -36,6 +36,10 @@ export declare const LEAD_TOOL_INPUTS: {
|
|
|
36
36
|
readonly get_brief: z.ZodObject<{
|
|
37
37
|
repo: z.ZodOptional<z.ZodString>;
|
|
38
38
|
}, z.core.$strict>;
|
|
39
|
+
readonly get_project_activity: z.ZodObject<{
|
|
40
|
+
repo: z.ZodString;
|
|
41
|
+
offset: z.ZodOptional<z.ZodInt>;
|
|
42
|
+
}, z.core.$strict>;
|
|
39
43
|
readonly get_project_context: z.ZodObject<{
|
|
40
44
|
repo: z.ZodString;
|
|
41
45
|
query: z.ZodString;
|
|
@@ -130,6 +134,7 @@ export declare const LEAD_TOOL_INPUTS: {
|
|
|
130
134
|
path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
131
135
|
line: z.ZodOptional<z.ZodNullable<z.ZodInt>>;
|
|
132
136
|
saved_notes: z.ZodOptional<z.ZodArray<z.ZodInt>>;
|
|
137
|
+
image: z.ZodOptional<z.ZodNullable<z.ZodInt>>;
|
|
133
138
|
}, z.core.$strict>;
|
|
134
139
|
readonly recap: z.ZodObject<{
|
|
135
140
|
since: z.ZodOptional<z.ZodString>;
|
|
@@ -762,6 +767,10 @@ export declare const LEAD_TOOL_READ_INPUTS: {
|
|
|
762
767
|
readonly get_brief: z.ZodObject<{
|
|
763
768
|
repo: z.ZodOptional<z.ZodString>;
|
|
764
769
|
}, z.core.$strict>;
|
|
770
|
+
readonly get_project_activity: z.ZodObject<{
|
|
771
|
+
repo: z.ZodString;
|
|
772
|
+
offset: z.ZodOptional<z.ZodInt>;
|
|
773
|
+
}, z.core.$strict>;
|
|
765
774
|
readonly get_project_context: z.ZodObject<{
|
|
766
775
|
repo: z.ZodString;
|
|
767
776
|
query: z.ZodString;
|
|
@@ -856,6 +865,7 @@ export declare const LEAD_TOOL_READ_INPUTS: {
|
|
|
856
865
|
path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
857
866
|
line: z.ZodOptional<z.ZodNullable<z.ZodInt>>;
|
|
858
867
|
saved_notes: z.ZodOptional<z.ZodArray<z.ZodInt>>;
|
|
868
|
+
image: z.ZodOptional<z.ZodNullable<z.ZodInt>>;
|
|
859
869
|
}, z.core.$strict>;
|
|
860
870
|
readonly recap: z.ZodObject<{
|
|
861
871
|
since: z.ZodOptional<z.ZodString>;
|
|
@@ -1556,6 +1566,26 @@ export declare const LEAD_TOOL_OUTPUTS: {
|
|
|
1556
1566
|
projects: z.ZodArray<z.ZodUnknown>;
|
|
1557
1567
|
omissions: z.ZodObject<{}, z.core.$loose>;
|
|
1558
1568
|
}, z.core.$loose>;
|
|
1569
|
+
readonly get_project_activity: z.ZodObject<{
|
|
1570
|
+
asOf: z.ZodString;
|
|
1571
|
+
since: z.ZodString;
|
|
1572
|
+
days: z.ZodNumber;
|
|
1573
|
+
limit: z.ZodNumber;
|
|
1574
|
+
truncated: z.ZodBoolean;
|
|
1575
|
+
ready: z.ZodObject<{
|
|
1576
|
+
count: z.ZodNumber;
|
|
1577
|
+
oldestFinishedAgeMs: z.ZodNullable<z.ZodNumber>;
|
|
1578
|
+
dated: z.ZodNumber;
|
|
1579
|
+
}, z.core.$strip>;
|
|
1580
|
+
completed: z.ZodObject<{
|
|
1581
|
+
count: z.ZodNumber;
|
|
1582
|
+
}, z.core.$loose>;
|
|
1583
|
+
families: z.ZodArray<z.ZodUnknown>;
|
|
1584
|
+
familiesTotal: z.ZodNumber;
|
|
1585
|
+
familiesOffset: z.ZodNumber;
|
|
1586
|
+
nextOffset: z.ZodNullable<z.ZodNumber>;
|
|
1587
|
+
incompleteHistory: z.ZodNumber;
|
|
1588
|
+
}, z.core.$loose>;
|
|
1559
1589
|
readonly get_project_context: z.ZodObject<{
|
|
1560
1590
|
version: z.ZodLiteral<1>;
|
|
1561
1591
|
readOnly: z.ZodBoolean;
|
|
@@ -52,6 +52,7 @@ export const LISTED_TASK_STATES = ["queued", "running", "done", "failed", "cance
|
|
|
52
52
|
const ACTION_OPERATIONS = Object.keys(CHAT_ACTIONS).filter(one => !one.startsWith("flow_") && !one.startsWith("subagent_"));
|
|
53
53
|
export const LEAD_TOOL_INPUTS = {
|
|
54
54
|
get_brief: z.strictObject({ repo: repoId.optional() }),
|
|
55
|
+
get_project_activity: z.strictObject({ repo: repoId, offset: offset.optional() }),
|
|
55
56
|
get_project_context: z.strictObject({ repo: repoId, query: text("contextQuery").min(1), mode: z.enum(["search", "impact"]).optional() }),
|
|
56
57
|
get_action_status: z.strictObject({ proposal: id }),
|
|
57
58
|
get_actions: z.strictObject({}),
|
|
@@ -77,6 +78,7 @@ export const LEAD_TOOL_INPUTS = {
|
|
|
77
78
|
note: z.string().max(LIMITS.note).nullable().optional(), path: text("diffPath").nullable().optional(),
|
|
78
79
|
line: z.int().min(1).max(1_000_000).nullable().optional(),
|
|
79
80
|
saved_notes: z.array(id).max(100).optional(),
|
|
81
|
+
image: id.nullable().optional().describe("Screenshot artifact id from get_result_images; attaches the exact verified image to this feedback. null leaves off the screenshot a reply targets."),
|
|
80
82
|
}),
|
|
81
83
|
recap: z.strictObject({ since: text("recapSince").optional() }),
|
|
82
84
|
list_repos: z.strictObject({}),
|
|
@@ -196,6 +198,7 @@ export const decisionOutput = z.looseObject({ repo: str, decision: int, task: st
|
|
|
196
198
|
export const queueColumnsOutput = { columns: rows({ column: str, tasks: list }) };
|
|
197
199
|
export const LEAD_TOOL_OUTPUTS = {
|
|
198
200
|
get_brief: catchUpOutput,
|
|
201
|
+
get_project_activity: z.looseObject({ asOf: str, since: str, days: int, limit: int, truncated: z.boolean(), ready: z.object({ count: int, oldestFinishedAgeMs: nullable(int), dated: int }), completed: z.looseObject({ count: int }), families: list, familiesTotal: int, familiesOffset: int, nextOffset: nullable(int), incompleteHistory: int }),
|
|
199
202
|
get_project_context: repositoryContextOutput,
|
|
200
203
|
get_action_status: z.looseObject({ proposal: int, operation: str, state: str, outcome: z.unknown(), finishedAt: nullable(str) }),
|
|
201
204
|
get_actions: z.looseObject({ actions: rows({ operation: str, label: str, secureReview: z.boolean(), inputs: z.array(str) }), notice: str }),
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A youiyoux request as Toolroll reads it (youiyoux-intake.ts): the published `youi.request/v1` schema (youiyoux's
|
|
3
|
+
* schema/youi.request.schema.json), held to its own limits, and the `youi.proposal/v1` options file an options request's
|
|
4
|
+
* agent writes (schema/youi.proposal.schema.json). Unknown keys are allowed, as the published schemas allow them;
|
|
5
|
+
* everything Toolroll reads is checked here, and every string is outside text all the same.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { type ContractResult } from "./contract.js";
|
|
9
|
+
export declare const REQUEST_ID: RegExp;
|
|
10
|
+
/** An options file's id: `p-` and the same 10 hex as the options request it answers. */
|
|
11
|
+
export declare const PROPOSAL_ID: RegExp;
|
|
12
|
+
/** The provenance every task filed from a request carries (task_ref.filed_via): immutable, and the seal reads it. */
|
|
13
|
+
export declare const YOUIYOUX_FILED_VIA = "youiyoux";
|
|
14
|
+
/** The one chat card a pass's filings make for a project (youiyoux-intake.ts), drawn by chat-decide.ts: the new tasks,
|
|
15
|
+
* Approve all and each one's own Approve and Open. */
|
|
16
|
+
export declare const YOUIYOUX_CARD_KIND = "youiyoux-requests";
|
|
17
|
+
/** Where a person sees and approves everything waiting on them in the console (flow-triggers.ts NEEDS_YOU_HREF). */
|
|
18
|
+
export declare const YOUIYOUX_HOME_PATH = "/work?view=needs-you";
|
|
19
|
+
export declare const YOUIYOUX_EVIDENCE: readonly ["screenshot", "check", "changed-path", "manual-review"];
|
|
20
|
+
/** What an options request offers its agent: the one file to write (`.youi/proposals/<proposal>.json`), the props an
|
|
21
|
+
* option may set (enum values from `values`), and whether it may give the component new text. */
|
|
22
|
+
export declare const youiyouxOfferSchema: z.ZodObject<{
|
|
23
|
+
proposal: z.ZodString;
|
|
24
|
+
file: z.ZodString;
|
|
25
|
+
choices: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
26
|
+
type: z.ZodEnum<{
|
|
27
|
+
boolean: "boolean";
|
|
28
|
+
enum: "enum";
|
|
29
|
+
number: "number";
|
|
30
|
+
string: "string";
|
|
31
|
+
}>;
|
|
32
|
+
values: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
33
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
34
|
+
}, z.core.$strip>>;
|
|
35
|
+
text: z.ZodBoolean;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
export type YouiyouxOffer = z.infer<typeof youiyouxOfferSchema>;
|
|
38
|
+
export declare const youiyouxTaskSchema: z.ZodObject<{
|
|
39
|
+
title: z.ZodString;
|
|
40
|
+
goal: z.ZodString;
|
|
41
|
+
touches: z.ZodArray<z.ZodString>;
|
|
42
|
+
touchesKnown: z.ZodBoolean;
|
|
43
|
+
why: z.ZodOptional<z.ZodString>;
|
|
44
|
+
not: z.ZodArray<z.ZodString>;
|
|
45
|
+
acceptance: z.ZodArray<z.ZodObject<{
|
|
46
|
+
statement: z.ZodString;
|
|
47
|
+
evidence: z.ZodArray<z.ZodEnum<{
|
|
48
|
+
"changed-path": "changed-path";
|
|
49
|
+
check: "check";
|
|
50
|
+
"manual-review": "manual-review";
|
|
51
|
+
screenshot: "screenshot";
|
|
52
|
+
}>>;
|
|
53
|
+
}, z.core.$strip>>;
|
|
54
|
+
offer: z.ZodOptional<z.ZodObject<{
|
|
55
|
+
proposal: z.ZodString;
|
|
56
|
+
file: z.ZodString;
|
|
57
|
+
choices: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
58
|
+
type: z.ZodEnum<{
|
|
59
|
+
boolean: "boolean";
|
|
60
|
+
enum: "enum";
|
|
61
|
+
number: "number";
|
|
62
|
+
string: "string";
|
|
63
|
+
}>;
|
|
64
|
+
values: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
65
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
66
|
+
}, z.core.$strip>>;
|
|
67
|
+
text: z.ZodBoolean;
|
|
68
|
+
}, z.core.$strip>>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export declare const youiyouxRequestSchema: z.ZodObject<{
|
|
71
|
+
schema: z.ZodLiteral<"youi.request/v1">;
|
|
72
|
+
id: z.ZodString;
|
|
73
|
+
project: z.ZodString;
|
|
74
|
+
kind: z.ZodEnum<{
|
|
75
|
+
change: "change";
|
|
76
|
+
comment: "comment";
|
|
77
|
+
options: "options";
|
|
78
|
+
}>;
|
|
79
|
+
status: z.ZodEnum<{
|
|
80
|
+
addressed: "addressed";
|
|
81
|
+
closed: "closed";
|
|
82
|
+
open: "open";
|
|
83
|
+
}>;
|
|
84
|
+
body: z.ZodString;
|
|
85
|
+
author: z.ZodObject<{
|
|
86
|
+
kind: z.ZodEnum<{
|
|
87
|
+
agent: "agent";
|
|
88
|
+
person: "person";
|
|
89
|
+
}>;
|
|
90
|
+
name: z.ZodString;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
createdAt: z.ZodString;
|
|
93
|
+
updatedAt: z.ZodString;
|
|
94
|
+
target: z.ZodObject<{
|
|
95
|
+
route: z.ZodString;
|
|
96
|
+
scope: z.ZodEnum<{
|
|
97
|
+
element: "element";
|
|
98
|
+
here: "here";
|
|
99
|
+
}>;
|
|
100
|
+
name: z.ZodString;
|
|
101
|
+
file: z.ZodOptional<z.ZodString>;
|
|
102
|
+
line: z.ZodOptional<z.ZodInt>;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
task: z.ZodOptional<z.ZodObject<{
|
|
105
|
+
title: z.ZodString;
|
|
106
|
+
goal: z.ZodString;
|
|
107
|
+
touches: z.ZodArray<z.ZodString>;
|
|
108
|
+
touchesKnown: z.ZodBoolean;
|
|
109
|
+
why: z.ZodOptional<z.ZodString>;
|
|
110
|
+
not: z.ZodArray<z.ZodString>;
|
|
111
|
+
acceptance: z.ZodArray<z.ZodObject<{
|
|
112
|
+
statement: z.ZodString;
|
|
113
|
+
evidence: z.ZodArray<z.ZodEnum<{
|
|
114
|
+
"changed-path": "changed-path";
|
|
115
|
+
check: "check";
|
|
116
|
+
"manual-review": "manual-review";
|
|
117
|
+
screenshot: "screenshot";
|
|
118
|
+
}>>;
|
|
119
|
+
}, z.core.$strip>>;
|
|
120
|
+
offer: z.ZodOptional<z.ZodObject<{
|
|
121
|
+
proposal: z.ZodString;
|
|
122
|
+
file: z.ZodString;
|
|
123
|
+
choices: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
124
|
+
type: z.ZodEnum<{
|
|
125
|
+
boolean: "boolean";
|
|
126
|
+
enum: "enum";
|
|
127
|
+
number: "number";
|
|
128
|
+
string: "string";
|
|
129
|
+
}>;
|
|
130
|
+
values: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
131
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
132
|
+
}, z.core.$strip>>;
|
|
133
|
+
text: z.ZodBoolean;
|
|
134
|
+
}, z.core.$strip>>;
|
|
135
|
+
}, z.core.$strip>>;
|
|
136
|
+
thread: z.ZodArray<z.ZodUnknown>;
|
|
137
|
+
}, z.core.$strip>;
|
|
138
|
+
export type YouiyouxRequest = z.infer<typeof youiyouxRequestSchema>;
|
|
139
|
+
export type YouiyouxTask = z.infer<typeof youiyouxTaskSchema>;
|
|
140
|
+
export declare function readYouiyouxRequest(raw: unknown): ContractResult<YouiyouxRequest>;
|
|
141
|
+
/** The one file an options request's agent may write: `.youi/proposals/p-<the request's hex>.json`. */
|
|
142
|
+
export declare const proposalIdOf: (request: string) => string;
|
|
143
|
+
export declare const proposalFileOf: (proposal: string) => string;
|
|
144
|
+
/** Why an options request can't be filed as youiyoux's contract has it, or null: it carries a task with an offer whose
|
|
145
|
+
* proposal is `p-<its own hex>`, whose file is that proposal's, and the task may touch that one file and nothing else. */
|
|
146
|
+
export declare function optionsProblem(request: YouiyouxRequest): string | null;
|
|
147
|
+
export declare const PROPOSAL_STATUSES: readonly ["open", "chosen", "dismissed"];
|
|
148
|
+
export declare const youiyouxProposalSchema: z.ZodObject<{
|
|
149
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
150
|
+
schema: z.ZodLiteral<"youi.proposal/v1">;
|
|
151
|
+
id: z.ZodString;
|
|
152
|
+
project: z.ZodString;
|
|
153
|
+
route: z.ZodString;
|
|
154
|
+
target: z.ZodObject<{
|
|
155
|
+
name: z.ZodString;
|
|
156
|
+
element: z.ZodOptional<z.ZodString>;
|
|
157
|
+
file: z.ZodOptional<z.ZodString>;
|
|
158
|
+
line: z.ZodOptional<z.ZodInt>;
|
|
159
|
+
column: z.ZodOptional<z.ZodInt>;
|
|
160
|
+
box: z.ZodObject<{
|
|
161
|
+
x: z.ZodNumber;
|
|
162
|
+
y: z.ZodNumber;
|
|
163
|
+
width: z.ZodNumber;
|
|
164
|
+
height: z.ZodNumber;
|
|
165
|
+
}, z.core.$strip>;
|
|
166
|
+
page: z.ZodOptional<z.ZodObject<{
|
|
167
|
+
width: z.ZodNumber;
|
|
168
|
+
height: z.ZodNumber;
|
|
169
|
+
}, z.core.$strip>>;
|
|
170
|
+
}, z.core.$strip>;
|
|
171
|
+
ask: z.ZodString;
|
|
172
|
+
author: z.ZodObject<{
|
|
173
|
+
kind: z.ZodEnum<{
|
|
174
|
+
agent: "agent";
|
|
175
|
+
person: "person";
|
|
176
|
+
}>;
|
|
177
|
+
name: z.ZodString;
|
|
178
|
+
}, z.core.$strip>;
|
|
179
|
+
createdAt: z.ZodString;
|
|
180
|
+
updatedAt: z.ZodString;
|
|
181
|
+
options: z.ZodArray<z.ZodObject<{
|
|
182
|
+
id: z.ZodEnum<{
|
|
183
|
+
a: "a";
|
|
184
|
+
b: "b";
|
|
185
|
+
c: "c";
|
|
186
|
+
d: "d";
|
|
187
|
+
}>;
|
|
188
|
+
label: z.ZodString;
|
|
189
|
+
why: z.ZodString;
|
|
190
|
+
set: z.ZodArray<z.ZodObject<{
|
|
191
|
+
prop: z.ZodString;
|
|
192
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
193
|
+
}, z.core.$strip>>;
|
|
194
|
+
text: z.ZodNullable<z.ZodString>;
|
|
195
|
+
}, z.core.$strip>>;
|
|
196
|
+
status: z.ZodEnum<{
|
|
197
|
+
chosen: "chosen";
|
|
198
|
+
dismissed: "dismissed";
|
|
199
|
+
open: "open";
|
|
200
|
+
}>;
|
|
201
|
+
choice: z.ZodOptional<z.ZodEnum<{
|
|
202
|
+
a: "a";
|
|
203
|
+
b: "b";
|
|
204
|
+
c: "c";
|
|
205
|
+
d: "d";
|
|
206
|
+
}>>;
|
|
207
|
+
decidedBy: z.ZodOptional<z.ZodObject<{
|
|
208
|
+
kind: z.ZodEnum<{
|
|
209
|
+
agent: "agent";
|
|
210
|
+
person: "person";
|
|
211
|
+
}>;
|
|
212
|
+
name: z.ZodString;
|
|
213
|
+
}, z.core.$strip>>;
|
|
214
|
+
decidedAt: z.ZodOptional<z.ZodString>;
|
|
215
|
+
request: z.ZodOptional<z.ZodString>;
|
|
216
|
+
for: z.ZodString;
|
|
217
|
+
}, z.core.$strip>;
|
|
218
|
+
export type YouiyouxProposal = z.infer<typeof youiyouxProposalSchema>;
|
|
219
|
+
export declare function readYouiyouxProposal(raw: unknown): ContractResult<YouiyouxProposal>;
|
|
220
|
+
/** What Toolroll writes beside a request (`youi.request-task/v1`): the task it became, how that task stands, and, when
|
|
221
|
+
* the console has an address, the task's link and `home`, where a person sees and approves all waiting work. */
|
|
222
|
+
export declare const YOUIYOUX_TASK_STATUSES: readonly ["queued", "needs-you", "building", "ready", "done", "failed", "stopped"];
|
|
223
|
+
export type YouiyouxTaskStatus = (typeof YOUIYOUX_TASK_STATUSES)[number];
|
|
224
|
+
/** A link youiyoux opens: http or https, at most 2,000 characters, with no user name or password in it. */
|
|
225
|
+
export declare const youiyouxLinkSchema: z.ZodString;
|
|
226
|
+
export declare const youiyouxRequestTaskSchema: z.ZodObject<{
|
|
227
|
+
schema: z.ZodLiteral<"youi.request-task/v1">;
|
|
228
|
+
request: z.ZodString;
|
|
229
|
+
runner: z.ZodLiteral<"Toolroll">;
|
|
230
|
+
task: z.ZodObject<{
|
|
231
|
+
id: z.ZodString;
|
|
232
|
+
url: z.ZodNullable<z.ZodString>;
|
|
233
|
+
}, z.core.$strict>;
|
|
234
|
+
home: z.ZodOptional<z.ZodString>;
|
|
235
|
+
status: z.ZodEnum<{
|
|
236
|
+
building: "building";
|
|
237
|
+
done: "done";
|
|
238
|
+
failed: "failed";
|
|
239
|
+
"needs-you": "needs-you";
|
|
240
|
+
queued: "queued";
|
|
241
|
+
ready: "ready";
|
|
242
|
+
stopped: "stopped";
|
|
243
|
+
}>;
|
|
244
|
+
detail: z.ZodString;
|
|
245
|
+
updatedAt: z.ZodString;
|
|
246
|
+
}, z.core.$strict>;
|
|
247
|
+
export type YouiyouxRequestTask = z.infer<typeof youiyouxRequestTaskSchema>;
|
|
248
|
+
export declare function readYouiyouxRequestTask(raw: unknown): ContractResult<YouiyouxRequestTask>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A youiyoux request as Toolroll reads it (youiyoux-intake.ts): the published `youi.request/v1` schema (youiyoux's
|
|
3
|
+
* schema/youi.request.schema.json), held to its own limits, and the `youi.proposal/v1` options file an options request's
|
|
4
|
+
* agent writes (schema/youi.proposal.schema.json). Unknown keys are allowed, as the published schemas allow them;
|
|
5
|
+
* everything Toolroll reads is checked here, and every string is outside text all the same.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { parseContract } from "./contract.js";
|
|
9
|
+
export const REQUEST_ID = /^r-[0-9a-f]{10}$/;
|
|
10
|
+
/** An options file's id: `p-` and the same 10 hex as the options request it answers. */
|
|
11
|
+
export const PROPOSAL_ID = /^p-[0-9a-f]{10}$/;
|
|
12
|
+
/** The provenance every task filed from a request carries (task_ref.filed_via): immutable, and the seal reads it. */
|
|
13
|
+
export const YOUIYOUX_FILED_VIA = "youiyoux";
|
|
14
|
+
/** The one chat card a pass's filings make for a project (youiyoux-intake.ts), drawn by chat-decide.ts: the new tasks,
|
|
15
|
+
* Approve all and each one's own Approve and Open. */
|
|
16
|
+
export const YOUIYOUX_CARD_KIND = "youiyoux-requests";
|
|
17
|
+
/** Where a person sees and approves everything waiting on them in the console (flow-triggers.ts NEEDS_YOU_HREF). */
|
|
18
|
+
export const YOUIYOUX_HOME_PATH = "/work?view=needs-you";
|
|
19
|
+
const text = (max) => z.string().min(1).max(max);
|
|
20
|
+
const who = z.object({ kind: z.enum(["person", "agent"]), name: text(100) });
|
|
21
|
+
// The published schema's date-time: an ISO timestamp with a zone.
|
|
22
|
+
const when = z.string().max(40).regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/);
|
|
23
|
+
export const YOUIYOUX_EVIDENCE = ["screenshot", "check", "changed-path", "manual-review"];
|
|
24
|
+
/** A component prop as youiyoux names one: what an option may set. */
|
|
25
|
+
const PROP = /^[A-Za-z_$][\w$]{0,63}$/;
|
|
26
|
+
const value = z.union([z.string().max(2000), z.number().finite(), z.boolean()]);
|
|
27
|
+
/** What an options request offers its agent: the one file to write (`.youi/proposals/<proposal>.json`), the props an
|
|
28
|
+
* option may set (enum values from `values`), and whether it may give the component new text. */
|
|
29
|
+
export const youiyouxOfferSchema = z.object({
|
|
30
|
+
proposal: z.string().regex(PROPOSAL_ID),
|
|
31
|
+
file: text(500),
|
|
32
|
+
choices: z.record(z.string().regex(PROP), z.object({
|
|
33
|
+
type: z.enum(["enum", "string", "boolean", "number"]),
|
|
34
|
+
values: z.array(text(200)).max(100).optional(),
|
|
35
|
+
default: value.optional(),
|
|
36
|
+
})).refine(choices => Object.keys(choices).length <= 50, "at most 50 props"),
|
|
37
|
+
text: z.boolean(),
|
|
38
|
+
});
|
|
39
|
+
export const youiyouxTaskSchema = z.object({
|
|
40
|
+
title: text(120),
|
|
41
|
+
goal: text(4000),
|
|
42
|
+
touches: z.array(text(500)).max(50),
|
|
43
|
+
touchesKnown: z.boolean(),
|
|
44
|
+
why: text(500).optional(),
|
|
45
|
+
not: z.array(text(300)).max(10),
|
|
46
|
+
acceptance: z.array(z.object({ statement: text(300), evidence: z.array(z.enum(YOUIYOUX_EVIDENCE)).min(1) })).min(1).max(10),
|
|
47
|
+
/** An options request's: what its agent writes (optionsProblem holds it to the request). */
|
|
48
|
+
offer: youiyouxOfferSchema.optional(),
|
|
49
|
+
});
|
|
50
|
+
export const youiyouxRequestSchema = z.object({
|
|
51
|
+
schema: z.literal("youi.request/v1"),
|
|
52
|
+
id: z.string().regex(REQUEST_ID),
|
|
53
|
+
project: text(200),
|
|
54
|
+
kind: z.enum(["comment", "change", "options"]),
|
|
55
|
+
status: z.enum(["open", "addressed", "closed"]),
|
|
56
|
+
body: text(4000),
|
|
57
|
+
author: who,
|
|
58
|
+
createdAt: when,
|
|
59
|
+
updatedAt: when,
|
|
60
|
+
// Where the person pointed: the page, the component, and (when youiyoux knew it) the file and line that draw it.
|
|
61
|
+
target: z.object({ route: text(300), scope: z.enum(["here", "element"]), name: text(200), file: text(500).optional(), line: z.int().positive().max(Number.MAX_SAFE_INTEGER).optional() }),
|
|
62
|
+
task: youiyouxTaskSchema.optional(),
|
|
63
|
+
thread: z.array(z.unknown()).max(500),
|
|
64
|
+
});
|
|
65
|
+
export function readYouiyouxRequest(raw) {
|
|
66
|
+
return parseContract(youiyouxRequestSchema, raw);
|
|
67
|
+
}
|
|
68
|
+
/** The one file an options request's agent may write: `.youi/proposals/p-<the request's hex>.json`. */
|
|
69
|
+
export const proposalIdOf = (request) => `p-${request.slice(2)}`;
|
|
70
|
+
export const proposalFileOf = (proposal) => `.youi/proposals/${proposal}.json`;
|
|
71
|
+
/** Why an options request can't be filed as youiyoux's contract has it, or null: it carries a task with an offer whose
|
|
72
|
+
* proposal is `p-<its own hex>`, whose file is that proposal's, and the task may touch that one file and nothing else. */
|
|
73
|
+
export function optionsProblem(request) {
|
|
74
|
+
if (request.kind !== "options")
|
|
75
|
+
return null;
|
|
76
|
+
const offer = request.task?.offer;
|
|
77
|
+
if (request.task === undefined || offer === undefined)
|
|
78
|
+
return "it asks for options with no offer";
|
|
79
|
+
const proposal = proposalIdOf(request.id);
|
|
80
|
+
if (offer.proposal !== proposal)
|
|
81
|
+
return `its offer is for ${offer.proposal}, not ${proposal}`;
|
|
82
|
+
if (offer.file !== proposalFileOf(proposal))
|
|
83
|
+
return `its offer's file isn't ${proposalFileOf(proposal)}`;
|
|
84
|
+
if (!request.task.touchesKnown || request.task.touches.length !== 1 || request.task.touches[0] !== offer.file)
|
|
85
|
+
return "its task may touch more than its options file";
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/** `youi.proposal/v1`: the options an agent puts on youiyoux's Screens canvas (youiyoux's packages/core/src/proposals.ts,
|
|
89
|
+
* mirrored). Toolroll checks only the shape and what it reads; youiyoux checks each option against the component's
|
|
90
|
+
* contract when it reads the file. */
|
|
91
|
+
const OPTION_IDS = ["a", "b", "c", "d"];
|
|
92
|
+
export const PROPOSAL_STATUSES = ["open", "chosen", "dismissed"];
|
|
93
|
+
export const youiyouxProposalSchema = z.object({
|
|
94
|
+
$schema: z.string().max(500).optional(),
|
|
95
|
+
schema: z.literal("youi.proposal/v1"),
|
|
96
|
+
id: z.string().regex(PROPOSAL_ID),
|
|
97
|
+
project: text(200),
|
|
98
|
+
route: text(300),
|
|
99
|
+
target: z.object({
|
|
100
|
+
name: text(200), element: text(200).optional(), file: text(500).optional(),
|
|
101
|
+
line: z.int().positive().max(Number.MAX_SAFE_INTEGER).optional(), column: z.int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
102
|
+
box: z.object({ x: z.number().finite(), y: z.number().finite(), width: z.number().finite().nonnegative(), height: z.number().finite().nonnegative() }),
|
|
103
|
+
page: z.object({ width: z.number().finite().positive(), height: z.number().finite().positive() }).optional(),
|
|
104
|
+
}),
|
|
105
|
+
ask: text(1000),
|
|
106
|
+
author: who,
|
|
107
|
+
createdAt: when,
|
|
108
|
+
updatedAt: when,
|
|
109
|
+
options: z.array(z.object({
|
|
110
|
+
id: z.enum(OPTION_IDS),
|
|
111
|
+
label: text(60),
|
|
112
|
+
why: text(500),
|
|
113
|
+
set: z.array(z.object({ prop: z.string().regex(PROP), value })).max(20),
|
|
114
|
+
text: text(2000).nullable(),
|
|
115
|
+
}).refine(option => option.set.length > 0 || option.text !== null, "an option changes a prop or the text")).min(2).max(4),
|
|
116
|
+
status: z.enum(PROPOSAL_STATUSES),
|
|
117
|
+
choice: z.enum(OPTION_IDS).optional(),
|
|
118
|
+
decidedBy: who.optional(),
|
|
119
|
+
decidedAt: when.optional(),
|
|
120
|
+
request: z.string().regex(REQUEST_ID).optional(),
|
|
121
|
+
for: z.string().regex(REQUEST_ID),
|
|
122
|
+
}).refine(doc => new Set(doc.options.map(option => option.id)).size === doc.options.length, "each option has its own letter")
|
|
123
|
+
.refine(doc => doc.choice === undefined || doc.options.some(option => option.id === doc.choice), "the choice is one of its options");
|
|
124
|
+
export function readYouiyouxProposal(raw) {
|
|
125
|
+
return parseContract(youiyouxProposalSchema, raw);
|
|
126
|
+
}
|
|
127
|
+
/** What Toolroll writes beside a request (`youi.request-task/v1`): the task it became, how that task stands, and, when
|
|
128
|
+
* the console has an address, the task's link and `home`, where a person sees and approves all waiting work. */
|
|
129
|
+
export const YOUIYOUX_TASK_STATUSES = ["queued", "needs-you", "building", "ready", "done", "failed", "stopped"];
|
|
130
|
+
/** A link youiyoux opens: http or https, at most 2,000 characters, with no user name or password in it. */
|
|
131
|
+
export const youiyouxLinkSchema = z.string().max(2000).refine(value => {
|
|
132
|
+
try {
|
|
133
|
+
const url = new URL(value);
|
|
134
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "";
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}, "an http or https address with no credentials");
|
|
140
|
+
export const youiyouxRequestTaskSchema = z.strictObject({
|
|
141
|
+
schema: z.literal("youi.request-task/v1"),
|
|
142
|
+
request: z.string().regex(REQUEST_ID),
|
|
143
|
+
runner: z.literal("Toolroll"),
|
|
144
|
+
task: z.strictObject({ id: z.string().min(1).max(120), url: youiyouxLinkSchema.nullable() }),
|
|
145
|
+
home: youiyouxLinkSchema.optional(),
|
|
146
|
+
status: z.enum(YOUIYOUX_TASK_STATUSES),
|
|
147
|
+
detail: z.string().max(200),
|
|
148
|
+
updatedAt: when,
|
|
149
|
+
});
|
|
150
|
+
export function readYouiyouxRequestTask(raw) {
|
|
151
|
+
return parseContract(youiyouxRequestTaskSchema, raw);
|
|
152
|
+
}
|