toolroll 0.9.83 → 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 +69 -0
- package/README.md +94 -1192
- 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/assignment-ui.js +5 -1
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -31
- package/dist/browser-crew.js +5 -1
- package/dist/browser-workspace.d.ts +12 -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 +4 -2
- package/dist/contracts/console-api.js +7 -2
- 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/control-setup.d.ts +9 -0
- package/dist/control-setup.js +31 -4
- package/dist/control-ui.d.ts +7 -1
- package/dist/control-ui.js +3 -2
- package/dist/desktop-update.d.ts +1 -1
- package/dist/dispatch.d.ts +15 -0
- package/dist/dispatch.js +26 -4
- package/dist/first-run.d.ts +5 -2
- package/dist/first-run.js +13 -2
- 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/folder-look.d.ts +3 -0
- package/dist/folder-look.js +8 -4
- package/dist/guides.js +4 -3
- 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/model-catalog.d.ts +5 -0
- package/dist/model-catalog.js +11 -2
- package/dist/model-choices.d.ts +188 -0
- package/dist/model-choices.js +405 -0
- package/dist/model-names.d.ts +6 -0
- package/dist/model-names.js +34 -0
- package/dist/models-ui.d.ts +69 -5
- package/dist/models-ui.js +53 -10
- package/dist/needs-you.d.ts +8 -0
- package/dist/needs-you.js +8 -0
- package/dist/operate.d.ts +22 -2
- package/dist/operate.js +107 -41
- package/dist/project-activity.d.ts +20 -0
- package/dist/project-activity.js +23 -0
- package/dist/project-admission.d.ts +4 -1
- package/dist/project-admission.js +23 -2
- package/dist/project-cli.d.ts +1 -0
- package/dist/project-cli.js +36 -19
- package/dist/project-identity.d.ts +9 -2
- package/dist/project-identity.js +80 -33
- package/dist/project-knowledge.d.ts +9 -9
- 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/serve.js +60 -13
- package/dist/server/chat.js +5 -4
- package/dist/server/chrome.d.ts +3 -0
- package/dist/server/chrome.js +5 -2
- package/dist/server/console.css +17 -2
- package/dist/server/flows.js +4 -0
- package/dist/server/http.d.ts +1 -0
- package/dist/server/http.js +6 -0
- package/dist/server/pages.js +53 -7
- package/dist/server/people-tokens.js +40 -20
- package/dist/server/render-chat.js +3 -2
- package/dist/server/render-pages.d.ts +12 -2
- package/dist/server/render-pages.js +36 -16
- package/dist/server/render-people.d.ts +14 -2
- package/dist/server/render-people.js +35 -12
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +20 -7
- package/dist/server/render-settings.js +1 -0
- package/dist/server/render-tasks.d.ts +1 -0
- package/dist/server/render-tasks.js +22 -19
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/route-table.js +9 -1
- package/dist/server/settings.d.ts +58 -1
- package/dist/server/settings.js +216 -70
- package/dist/server/signup-gate.d.ts +9 -0
- package/dist/server/signup-gate.js +40 -0
- package/dist/server/task-views.js +6 -0
- package/dist/server/tasks.js +54 -8
- package/dist/settings-kit-script.d.ts +21 -0
- package/dist/settings-kit-script.js +67 -0
- package/dist/settings-kit.d.ts +137 -0
- package/dist/settings-kit.js +186 -0
- package/dist/settings-proofs.d.ts +64 -0
- package/dist/settings-proofs.js +101 -0
- package/dist/setup-guide.js +4 -2
- package/dist/spend-report.d.ts +87 -0
- package/dist/spend-report.js +265 -0
- package/dist/spend-ui.d.ts +29 -8
- package/dist/spend-ui.js +74 -39
- package/dist/store.d.ts +46 -3
- package/dist/store.js +165 -6
- package/dist/task-sizing.d.ts +5 -3
- package/dist/task-sizing.js +4 -4
- 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/task-source.d.ts
CHANGED
|
@@ -51,8 +51,20 @@ export type TaskOrigin = {
|
|
|
51
51
|
url: string | null;
|
|
52
52
|
host: string | null;
|
|
53
53
|
} | null;
|
|
54
|
+
/** A youiyoux change or options request (youiyoux-intake.ts): its id, its kind, and where the person pointed. */
|
|
55
|
+
request?: {
|
|
56
|
+
id: string;
|
|
57
|
+
kind: "change" | "options";
|
|
58
|
+
route: string | null;
|
|
59
|
+
component: string | null;
|
|
60
|
+
at: string | null;
|
|
61
|
+
} | null;
|
|
54
62
|
};
|
|
55
|
-
/** A task's origin, or null when it came from neither a flow
|
|
63
|
+
/** A task's origin, or null when it came from neither a flow, an outside event nor a youiyoux request. */
|
|
56
64
|
export declare function originOf(store: Store, taskId: string, canSee: (repo: string) => boolean): TaskOrigin | null;
|
|
65
|
+
/** A youiyoux request by its kind: "youiyoux change request r-…" or "youiyoux options request r-…". */
|
|
66
|
+
export declare function requestName(request: NonNullable<TaskOrigin["request"]>): string;
|
|
67
|
+
/** Where a youiyoux request pointed, in words: "Button on /flows (src/button.tsx:12)". */
|
|
68
|
+
export declare function requestWords(request: NonNullable<TaskOrigin["request"]>): string;
|
|
57
69
|
/** The one line an approval shows about an outside event that started its work: context, never a term agreed to. */
|
|
58
70
|
export declare function originNote(origin: TaskOrigin | null | undefined): string | null;
|
package/dist/task-source.js
CHANGED
|
@@ -71,8 +71,11 @@ export function cardReference(source) {
|
|
|
71
71
|
const snapshot = snapshotOf(source ?? null);
|
|
72
72
|
return snapshot === null ? "" : referenceWords(snapshot);
|
|
73
73
|
}
|
|
74
|
-
/** A task's origin, or null when it came from neither a flow
|
|
74
|
+
/** A task's origin, or null when it came from neither a flow, an outside event nor a youiyoux request. */
|
|
75
75
|
export function originOf(store, taskId, canSee) {
|
|
76
|
+
const request = store.youiyouxOriginOf(taskId);
|
|
77
|
+
if (request !== null)
|
|
78
|
+
return { flow: null, source: null, request: { id: request.request, kind: request.kind, route: request.route, component: request.component, at: request.at } };
|
|
76
79
|
const kept = store.taskSourceOf(taskId);
|
|
77
80
|
const flowId = kept?.flow ?? Number(/^flow:(\d+)$/.exec(store.filedViaOf(taskId) ?? "")?.[1] ?? NaN);
|
|
78
81
|
const snapshot = kept === null ? null : readSnapshot(kept.sourceJson);
|
|
@@ -84,8 +87,21 @@ export function originOf(store, taskId, canSee) {
|
|
|
84
87
|
source: snapshot === null ? null : { label: snapshot.label, proved: provedWords(snapshot), url: snapshot.external.url, host: hostOf(snapshot.external.url) },
|
|
85
88
|
};
|
|
86
89
|
}
|
|
90
|
+
/** A youiyoux request by its kind: "youiyoux change request r-…" or "youiyoux options request r-…". */
|
|
91
|
+
export function requestName(request) {
|
|
92
|
+
return `youiyoux ${request.kind === "options" ? "options" : "change"} request ${request.id}`;
|
|
93
|
+
}
|
|
94
|
+
/** Where a youiyoux request pointed, in words: "Button on /flows (src/button.tsx:12)". */
|
|
95
|
+
export function requestWords(request) {
|
|
96
|
+
const what = [request.component, request.route === null ? null : `on ${request.route}`].filter(one => one !== null).join(" ");
|
|
97
|
+
return `${what === "" ? "" : what}${request.at === null ? "" : `${what === "" ? "" : " "}(${request.at})`}`;
|
|
98
|
+
}
|
|
87
99
|
/** The one line an approval shows about an outside event that started its work: context, never a term agreed to. */
|
|
88
100
|
export function originNote(origin) {
|
|
101
|
+
if (origin?.request != null) {
|
|
102
|
+
const where = requestWords(origin.request);
|
|
103
|
+
return `From ${requestName(origin.request)}${where === "" ? "" : `: ${where}`}. Whoever can write the project's folder wrote these words; read them all before you approve.`;
|
|
104
|
+
}
|
|
89
105
|
if (origin?.source == null)
|
|
90
106
|
return null;
|
|
91
107
|
const where = origin.source.host === null ? "" : ` · ${origin.source.host}`;
|
package/dist/telegram-lead.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CHAT_ACTIONS, sharedActionAllowsChallenge, sharedActionPayload } from "./chat-actions.js";
|
|
2
|
-
import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, NO_PHONE_LINK, NO_TASK_LINK,
|
|
2
|
+
import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, NO_PHONE_LINK, NO_TASK_LINK, phoneProposalLink, confirmedLink, phoneProposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
|
|
3
3
|
export { channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps } from "./chat-channel.js";
|
|
4
4
|
/**
|
|
5
5
|
* The paired phone as one more way to use the same assistant.
|
|
@@ -207,7 +207,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
|
|
|
207
207
|
return { effects, confirmed: false, ignored: true };
|
|
208
208
|
}
|
|
209
209
|
const who = verified.who;
|
|
210
|
-
const preview =
|
|
210
|
+
const preview = phoneProposalPreview(store, proposal, actionRepos);
|
|
211
211
|
const origin = options.phoneOrigin?.() ?? null;
|
|
212
212
|
if (preview.closed) {
|
|
213
213
|
store.consumeTelegramProposalActions(proposal.id, now);
|
|
@@ -239,7 +239,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
|
|
|
239
239
|
// A forged keyboard on a handoff card: nothing acts; the card is
|
|
240
240
|
// repainted with its current link (or the honest absence of one).
|
|
241
241
|
store.consumeTelegramProposalActions(proposal.id, now);
|
|
242
|
-
const wanted =
|
|
242
|
+
const wanted = phoneProposalLink(store, proposal, actionRepos, "telegram");
|
|
243
243
|
const link = phoneLinkButton(origin, wanted);
|
|
244
244
|
ack(link === null ? "this step finishes on the computer" : "open the button below to finish this step");
|
|
245
245
|
edit(handoffCardText(preview.text, linkNote(origin, wanted)), keyboardWith(null, link));
|
|
@@ -545,7 +545,7 @@ async function runTelegramConversation(row, args) {
|
|
|
545
545
|
// or went away carries the current truth, never a stored URL.
|
|
546
546
|
const proposal = part.kind === "card" && part.proposal !== null ? store.getLeadProposal(part.proposal) : null;
|
|
547
547
|
const origin = proposal === null ? null : options.phoneOrigin?.() ?? null;
|
|
548
|
-
const wanted = proposal === null ? null :
|
|
548
|
+
const wanted = proposal === null ? null : phoneProposalLink(store, proposal, repos);
|
|
549
549
|
const keyboard = keyboardWith(part.keyboard, phoneLinkButton(origin, wanted));
|
|
550
550
|
// The lead's own words carry their bold anchors and labelled links as entities, never as Markdown characters.
|
|
551
551
|
const voiced = part.kind === "reply" ? telegramReply(part.text) : null;
|
|
@@ -625,7 +625,7 @@ async function runTelegramConversation(row, args) {
|
|
|
625
625
|
for (const image of selected)
|
|
626
626
|
parts.push({ kind: "image", text: image.caption, taskId: image.taskId, run: image.run, artifact: image.artifact, sha256: image.sha256 });
|
|
627
627
|
for (const proposal of proposals) {
|
|
628
|
-
const preview =
|
|
628
|
+
const preview = phoneProposalPreview(store, proposal, repos);
|
|
629
629
|
const keyboard = preview.buttons ? mintCardTokens(store, binding, proposal.id, now).keyboard : null;
|
|
630
630
|
parts.push({ kind: "card", text: preview.text, proposal: proposal.id, keyboard });
|
|
631
631
|
}
|
|
@@ -763,11 +763,13 @@ async function runTelegramConversation(row, args) {
|
|
|
763
763
|
react: () => transport("setMessageReaction", { chat_id: chatId, message_id: Number(row.messageId), reaction: [{ type: "emoji", emoji: WARM_EMOJI }] }),
|
|
764
764
|
typing: () => transport("sendChatAction", { chat_id: chatId, action: "typing" }),
|
|
765
765
|
});
|
|
766
|
+
const replyImage = row.replyTo === null ? undefined : store.telegramMessageBindings(binding, row.replyTo).find(one => one.taskId === row.taskId && one.run === row.sourceRun && one.image !== undefined)?.image;
|
|
766
767
|
let outcome;
|
|
767
768
|
try {
|
|
768
769
|
outcome = await runLeadTurn({
|
|
769
770
|
store, who, session, thread, config, key: null, message: row.text, requestId: row.request,
|
|
770
771
|
...(row.context === null ? {} : { context: row.context }),
|
|
772
|
+
...(replyImage === undefined ? {} : { replyImage }),
|
|
771
773
|
...(options.subscriptionRunner === undefined ? {} : { subscriptionRunner: options.subscriptionRunner }),
|
|
772
774
|
clock, evidenceRoot: options.evidenceRoot, revalidate, mediaDelivery: "documents", channel: "telegram", onProgress: warm.onProgress,
|
|
773
775
|
});
|
package/dist/telegram.js
CHANGED
|
@@ -1852,7 +1852,7 @@ function applyConversation(context, update, effects) {
|
|
|
1852
1852
|
if (tasks.length === 1) {
|
|
1853
1853
|
taskId = tasks[0];
|
|
1854
1854
|
sourceRun = bindings.find(one => one.run !== null)?.run ?? null;
|
|
1855
|
-
context_ = replyContextFor(taskId, sourceRun);
|
|
1855
|
+
context_ = replyContextFor(taskId, sourceRun, bindings.find(one => one.image !== undefined)?.image);
|
|
1856
1856
|
}
|
|
1857
1857
|
}
|
|
1858
1858
|
// No reply target: the task this chat chose to talk about, if any. The
|
package/dist/toolroll-update.js
CHANGED
|
@@ -37,7 +37,7 @@ import { removeTerminalUpdateGate } from "./terminal-catalog.js";
|
|
|
37
37
|
import { assertCodingUpdateStopped, backupCodingCatalog, codingCatalogExists, releaseStaleCodingOwner, removeCodingUpdateGate } from "./coding-update.js";
|
|
38
38
|
import { installLaunchdService, launchdPlist, stopLaunchdService, writeFileDurably } from "./daemon.js";
|
|
39
39
|
import { NAME } from "./names.js";
|
|
40
|
-
import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS } from "./store.js";
|
|
40
|
+
import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS } from "./store.js";
|
|
41
41
|
import { isNewer, REGISTRY } from "./releases.js";
|
|
42
42
|
import { markNeverIndex } from "./never-index.js";
|
|
43
43
|
import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
|
|
@@ -409,7 +409,7 @@ const HISTORY_RULES = {
|
|
|
409
409
|
// v126: a receipt's state, acceptance time and held item, a trigger's credential generation and turned-away count,
|
|
410
410
|
// and who made a card read none, 0, or none. A table with an earlier rule (flow_trigger's v115 move) keeps it too.
|
|
411
411
|
// v128: a lead message's pending flag reads waiting exactly for a team queue's unadmitted operator message.
|
|
412
|
-
...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS))
|
|
412
|
+
...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS))
|
|
413
413
|
.map(([table, rule]) => [table, { ...EARLIER_RULES[table], ...rule }])),
|
|
414
414
|
};
|
|
415
415
|
/** The tables a migration may remove whole (v115's removed features). */
|
package/dist/webhooks.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare function effectivePrimary(env: Record<string, string | undefined>
|
|
|
35
35
|
legacyWarning?: string;
|
|
36
36
|
};
|
|
37
37
|
export declare function loadConsoleUrl(env: Record<string, string | undefined>, dir: string): string | null;
|
|
38
|
+
/** Where `up` last served its console, kept beside the database so `onboard` names the same address (operate.ts). */
|
|
39
|
+
export declare const UP_CONSOLE_FILE = "up-console.json";
|
|
40
|
+
export declare function readUpConsoleUrl(dir: string): string | null;
|
|
41
|
+
/** This machine's own console, where `up` last served it: only a plain http address on a loopback name (127.0.0.1,
|
|
42
|
+
* localhost, ::1), never a host guessed from it; null otherwise. */
|
|
43
|
+
export declare function localConsoleUrl(dir: string): string | null;
|
|
38
44
|
/**
|
|
39
45
|
* The origin a phone link may open, or null. The SAME console-url setting
|
|
40
46
|
* chat links use — read again on every call, never cached, so a changed or
|
package/dist/webhooks.js
CHANGED
|
@@ -107,6 +107,31 @@ export function loadConsoleUrl(env, dir) {
|
|
|
107
107
|
const configured = envValue(env, "CONSOLE_URL") ?? readTrimmed(join(dir, CONSOLE_FILE));
|
|
108
108
|
return configured === undefined || configured === null || configured === "" ? null : configured.replace(/\/+$/, "");
|
|
109
109
|
}
|
|
110
|
+
/** Where `up` last served its console, kept beside the database so `onboard` names the same address (operate.ts). */
|
|
111
|
+
export const UP_CONSOLE_FILE = "up-console.json";
|
|
112
|
+
export function readUpConsoleUrl(dir) {
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(readFileSync(join(dir, UP_CONSOLE_FILE), "utf8"));
|
|
115
|
+
return typeof parsed.url === "string" && /^https?:\/\/[^\s]+$/.test(parsed.url) ? parsed.url.replace(/\/+$/, "") : null;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** This machine's own console, where `up` last served it: only a plain http address on a loopback name (127.0.0.1,
|
|
122
|
+
* localhost, ::1), never a host guessed from it; null otherwise. */
|
|
123
|
+
export function localConsoleUrl(dir) {
|
|
124
|
+
const served = readUpConsoleUrl(dir);
|
|
125
|
+
if (served === null)
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
const url = new URL(served);
|
|
129
|
+
return url.protocol === "http:" && isLoopbackHost(url.hostname) && url.hostname !== "0.0.0.0" && url.hostname !== "[::]" ? served : null;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
110
135
|
/** A dotted IPv4 that names this machine or no machine: loopback, unspecified. */
|
|
111
136
|
function isLocalIpv4(address) {
|
|
112
137
|
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(address) || address === "0.0.0.0";
|
package/dist/work-index.d.ts
CHANGED
|
@@ -114,6 +114,10 @@ export type WorkStateRow = {
|
|
|
114
114
|
members: number[];
|
|
115
115
|
};
|
|
116
116
|
export declare function workStateRows(store: Store, now: Date, access: WorkSummaryAccess): WorkStateRow[];
|
|
117
|
+
/** Activity uses the Tasks list's exact family admission and saved completion rules.
|
|
118
|
+
* Only the requested project enters the projection. The extra row signals truncation;
|
|
119
|
+
* attempts are aggregated in SQLite, never loaded as an unbounded history. */
|
|
120
|
+
export declare function workOutcomeRows(store: Store, now: Date, access: WorkSummaryAccess, project: string, since: string, limit: number): Record<string, unknown>[];
|
|
117
121
|
/** Families whose headline is Ready for review, newest first: the Tasks
|
|
118
122
|
* list's own rows, so a completed family (root or any revision), a failed
|
|
119
123
|
* check and a superseded version never count. */
|
package/dist/work-index.js
CHANGED
|
@@ -453,6 +453,38 @@ export function workStateRows(store, now, access) {
|
|
|
453
453
|
leadClaim: n(row, 'lead_claim') === 1, liveRun: row['live_run'] == null ? null : n(row, 'live_run'), unfinished: row['unfinished'] == null ? null : n(row, 'unfinished'),
|
|
454
454
|
resultRun: row['result_id'] == null ? null : n(row, 'result_id'), waits: Number(row['rank']) === 2, version: String(row['version']), at: String(row['ask_at'] ?? ''), askRun: row['ask_run'] == null ? null : n(row, 'ask_run'), members: JSON.parse(String(row['members'] ?? '[]')).map(Number) }));
|
|
455
455
|
}
|
|
456
|
+
/** Activity uses the Tasks list's exact family admission and saved completion rules.
|
|
457
|
+
* Only the requested project enters the projection. The extra row signals truncation;
|
|
458
|
+
* attempts are aggregated in SQLite, never loaded as an unbounded history. */
|
|
459
|
+
export function workOutcomeRows(store, now, access, project, since, limit) {
|
|
460
|
+
return store.handle.prepare(`${registerValidators(store)}, selected AS MATERIALIZED (
|
|
461
|
+
SELECT * FROM ranked WHERE code='ready-to-check' OR code='history-problem'
|
|
462
|
+
OR (code='complete' AND checked_at >= $since AND checked_at <= $now)
|
|
463
|
+
ORDER BY sort_at DESC,root_ref DESC LIMIT $outcomeLimit
|
|
464
|
+
), amounts AS (
|
|
465
|
+
-- reported-usage.ts runAmountOf, row for row: only evidenced billing on a run whose cost was reported is known;
|
|
466
|
+
-- an API run counts its priced spend as dollars (a safe integer), a subscription run its reported cost as usage,
|
|
467
|
+
-- never dollars. TOTAL sums as a float, so no family's sum can overflow.
|
|
468
|
+
SELECT m.root_ref,r.cost_usd,s.microusd,
|
|
469
|
+
CASE WHEN r.billing_basis IS NOT 'evidence' OR COALESCE(r.cost_unknown,1)<>0 THEN 'unknown'
|
|
470
|
+
WHEN s.billing='subscription' THEN 'subscription'
|
|
471
|
+
WHEN s.billing='api-key' AND typeof(s.microusd)='integer' AND s.microusd>=0 AND s.microusd<=9007199254740991 THEN 'api'
|
|
472
|
+
ELSE 'unknown' END amount
|
|
473
|
+
FROM selected f JOIN members m ON m.root_ref=f.root_ref JOIN run r ON r.task_ref=m.ref_id LEFT JOIN run_spend s ON s.run=r.id
|
|
474
|
+
), attempts AS (
|
|
475
|
+
SELECT root_ref,COUNT(*) runs,COUNT(CASE WHEN amount='api' THEN 1 END) api_runs,
|
|
476
|
+
TOTAL(CASE WHEN amount='api' THEN microusd END) api_microusd,
|
|
477
|
+
COUNT(CASE WHEN amount='subscription' THEN 1 END) subscription_runs,
|
|
478
|
+
SUM(CASE WHEN amount='subscription' AND typeof(cost_usd) IN ('integer','real') AND cost_usd>=0 THEN cost_usd END) subscription_usd
|
|
479
|
+
FROM amounts GROUP BY root_ref
|
|
480
|
+
) SELECT f.root_id,f.id task_id,f.root_title title,f.code,f.finished_at,f.result_id,f.result_role,
|
|
481
|
+
f.version_count,f.checked_at,f.checked_actor,f.checked_detail,
|
|
482
|
+
(SELECT lead FROM task_act WHERE task_ref=f.root_ref AND act='completed' AND at=f.checked_at ORDER BY id DESC LIMIT 1) completed_by_lead,
|
|
483
|
+
(SELECT status FROM run_check WHERE run=f.result_id) check_status,
|
|
484
|
+
COALESCE(a.runs,0) runs,COALESCE(a.api_runs,0) api_runs,a.api_microusd,COALESCE(a.subscription_runs,0) subscription_runs,a.subscription_usd
|
|
485
|
+
FROM selected f LEFT JOIN attempts a ON a.root_ref=f.root_ref ORDER BY f.sort_at DESC,f.root_ref DESC`)
|
|
486
|
+
.all({ ...parameters(now, access, { project }, custodyReadings(store)), $since: since, $outcomeLimit: limit + 1 });
|
|
487
|
+
}
|
|
456
488
|
/** Families whose headline is Ready for review, newest first: the Tasks
|
|
457
489
|
* list's own rows, so a completed family (root or any revision), a failed
|
|
458
490
|
* check and a superseded version never count. */
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* youiyoux change and options requests become tasks (docs/guide/getting-started.md, "Change requests from youiyoux").
|
|
3
|
+
*
|
|
4
|
+
* youiyoux is a design-system tool: in its hub a person points at a component on a screenshot of their app and asks
|
|
5
|
+
* for a change, and it saves the request as `<project>/.youi/requests/<id>.json` (`youi.request/v1`). A project that
|
|
6
|
+
* uses youiyoux (a `youi.project.json` at its root, or a `.youi/requests` folder) has this intake on with no step of
|
|
7
|
+
* its own, unless a person turned it off in Settings → Integrations.
|
|
8
|
+
*
|
|
9
|
+
* On the worker's flow pass this reads the project's main checkout — the path from Toolroll's own records, resolved
|
|
10
|
+
* with realpath — and files each open change request with a task exactly once, keyed by the project and the request
|
|
11
|
+
* id in `youiyoux_request`: never again, whatever the file says later. Everything in the folder is outside text,
|
|
12
|
+
* written by whoever can write the project's folder:
|
|
13
|
+
*
|
|
14
|
+
* - the folders and files must be real (never a link), a file a single link to itself, at most 64 KB, read without
|
|
15
|
+
* following a link or waiting on a pipe, and at most 200 are read in one pass;
|
|
16
|
+
* - a file whose name, schema or id is wrong is counted (the integration says so) and never filed;
|
|
17
|
+
* - text that looks like a key or password is left out (recorded once, never filed), as flow triggers do;
|
|
18
|
+
* - a path the task may touch is kept only when youiyoux knew them all and every one is a plain relative path;
|
|
19
|
+
* - the task is filed through the one filing door (proposal.ts) by the "youiyoux" automation, unplanned, and waits
|
|
20
|
+
* for a person: its provenance (`filed_via = 'youiyoux'`) keeps every seal no person looked at off it (store.ts,
|
|
21
|
+
* the outside-request quarantine), so no operating mode, grant, schedule or automatic seal approves it. A person
|
|
22
|
+
* approves it in Toolroll, or with their own two taps in their paired chat where they turned approving from chat
|
|
23
|
+
* on (chat-decide.ts), bound to the exact scope the card showed.
|
|
24
|
+
*
|
|
25
|
+
* Beside each request it writes `<id>.task.json` (`youi.request-task/v1`): the task and its status, and, when the
|
|
26
|
+
* console has an address (the one the installation names, else this machine's own console where `up` serves it, on
|
|
27
|
+
* loopback), the task's link and `home`, where a person approves all waiting work. It is written when filed, whenever
|
|
28
|
+
* the status changes, and once more when that address changes. Beside an options request's options file (below), it is
|
|
29
|
+
* the only file it writes in the checkout: written whole (a temporary file in the same folder, then renamed over),
|
|
30
|
+
* refused when the folder or the target is a link, never the request itself. The status is also kept in the store;
|
|
31
|
+
* nothing Toolroll decides reads the file back.
|
|
32
|
+
*
|
|
33
|
+
* Status words (task-status.ts headline → youiyoux): Queued → queued; Planning, Building and Waiting (work going on
|
|
34
|
+
* by itself) → building; Needs you → needs-you; Ready for review → ready; Complete → done; Failed → failed;
|
|
35
|
+
* Stopped → stopped.
|
|
36
|
+
*
|
|
37
|
+
* An options request (`kind: "options"`) asks an agent for two to four options for one component, which youiyoux shows
|
|
38
|
+
* on its Screens canvas. It is filed the same way, once, waiting for a person, and only as youiyoux's contract has it:
|
|
39
|
+
* its offer names `p-<its own hex>`, that proposal's file `.youi/proposals/p-<hex>.json`, and the task may touch that
|
|
40
|
+
* one file (anything else is counted as a file that couldn't be read, and never filed). The proposal it names is kept
|
|
41
|
+
* when filed; the request is never read for it again. Then (runYouiyoux, the worker's pass):
|
|
42
|
+
*
|
|
43
|
+
* - When the task is Ready for review (or Complete), its result is read: the file at the result's commit, the commit
|
|
44
|
+
* Toolroll recorded for the build (never the checkout the agent had, which it controls and the pool hands on), read
|
|
45
|
+
* with the hardened git (teardown-git.ts). It must be a plain file in that commit (never a link), at most 64 KB, a
|
|
46
|
+
* `youi.proposal/v1` file whose id is the proposal, which answers this request, still open (an agent proposes; only a
|
|
47
|
+
* person decides), by an agent, with no key-like text. Then it is copied, byte for byte, into the project's main
|
|
48
|
+
* checkout as `.youi/proposals/<id>.json` the way the status file is written (folders real, never through a link;
|
|
49
|
+
* a temporary file, then linked into place, which never replaces a file already there: a person may have decided on
|
|
50
|
+
* it). A file missing from the result, or one that isn't safe, is said in the status file and never copied; a newer
|
|
51
|
+
* result is read again.
|
|
52
|
+
* - Only a file Toolroll copied is read for a decision: one already at that name is left as it was (a person may
|
|
53
|
+
* have decided on it) and never read for one, unless it is byte for byte the result's (an earlier pass's copy).
|
|
54
|
+
* - When that copy says a person chose an option or dismissed them all (youiyoux's hub writes that), and it is still
|
|
55
|
+
* the proposal copied (only its status, choice, decidedBy, decidedAt, request and updatedAt may differ), decided by
|
|
56
|
+
* a person, at a time, the task is marked complete by Toolroll, as landed work is (result-completion.ts LANDED_ACTOR), without
|
|
57
|
+
* merging or publishing its branch: its deliverable was the options, and youiyoux has used them. Only the exact
|
|
58
|
+
* result whose file was copied, and only through every guard a person's completion passes (landed-cleanup.ts
|
|
59
|
+
* completionProblem): otherwise it stays Ready for review and the ledger says why. The ledger keeps who decided
|
|
60
|
+
* what, as youiyoux wrote it (outside text, cleaned). A decision never approves, publishes or merges anything.
|
|
61
|
+
*/
|
|
62
|
+
import type { Store } from "./store.js";
|
|
63
|
+
import type { Headline } from "./task-status.js";
|
|
64
|
+
import { type Git } from "./landed-cleanup.js";
|
|
65
|
+
import { YOUIYOUX_FILED_VIA, type YouiyouxRequest, type YouiyouxRequestTask, type YouiyouxTaskStatus } from "./contracts/youiyoux-request.js";
|
|
66
|
+
export { YOUIYOUX_FILED_VIA };
|
|
67
|
+
/** Who files the tasks: an automation, like a flow trigger. */
|
|
68
|
+
export declare const YOUIYOUX_ACTOR = "youiyoux";
|
|
69
|
+
export declare const REQUEST_BYTES: number;
|
|
70
|
+
export declare const REQUESTS_PER_PASS = 200;
|
|
71
|
+
export declare const FILED_PER_PASS = 10;
|
|
72
|
+
/** The ledger's word for a decision youiyoux's hub made on a task's options. */
|
|
73
|
+
export declare const OPTIONS_DECIDED_ACTION = "youiyoux options decided";
|
|
74
|
+
export declare const OPTIONS_COPIED_ACTION = "youiyoux options copied";
|
|
75
|
+
/** The flow pass runs every 15 seconds; the folder is read at most this often. */
|
|
76
|
+
export declare const INTAKE_EVERY_MS = 30000;
|
|
77
|
+
export declare const STATUS_OF_HEADLINE: Readonly<Record<Headline, YouiyouxTaskStatus>>;
|
|
78
|
+
/** Whether a project uses youiyoux: a `youi.project.json` at its root, or a `.youi` folder. Only their presence is
|
|
79
|
+
* read here (a link counts, so the integration can say it is Broken); requestsFolder decides what may be read. */
|
|
80
|
+
export declare function usesYouiyoux(repo: string): boolean;
|
|
81
|
+
/** The paths a request may touch: every one a plain relative path inside the project, or none at all. */
|
|
82
|
+
export declare function safeTouches(touches: readonly string[]): string[] | null;
|
|
83
|
+
export type RequestTask = {
|
|
84
|
+
title: string;
|
|
85
|
+
goal: string;
|
|
86
|
+
outOfScope: string | null;
|
|
87
|
+
touches: string[];
|
|
88
|
+
acceptance: {
|
|
89
|
+
id: string;
|
|
90
|
+
statement: string;
|
|
91
|
+
how: null;
|
|
92
|
+
evidence: string[];
|
|
93
|
+
}[];
|
|
94
|
+
/** Where the person pointed, for the task page: the route, the component, and `file:line` when youiyoux knew a plain one. */
|
|
95
|
+
pointed: {
|
|
96
|
+
route: string;
|
|
97
|
+
component: string;
|
|
98
|
+
at: string | null;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/** The task a request asks for, in Toolroll's terms. Text is cleaned here; whether it may be filed is decided by the caller. */
|
|
102
|
+
export declare function requestTask(request: YouiyouxRequest): RequestTask;
|
|
103
|
+
/** A person turns the intake off or back on for a project; the ledger keeps who. */
|
|
104
|
+
export declare function setYouiyouxIntake(store: Store, repo: string, on: boolean, by: string, now: Date): void;
|
|
105
|
+
export type YouiyouxState = {
|
|
106
|
+
/** The project uses youiyoux, as the worker's last pass found. */
|
|
107
|
+
uses: boolean;
|
|
108
|
+
/** Requests become tasks: it uses youiyoux and nobody turned it off. */
|
|
109
|
+
on: boolean;
|
|
110
|
+
/** A person turned it off. */
|
|
111
|
+
off: boolean;
|
|
112
|
+
/** Filed tasks not yet done, failed or stopped. */
|
|
113
|
+
queued: number;
|
|
114
|
+
filed: number;
|
|
115
|
+
invalid: number;
|
|
116
|
+
problem: string | null;
|
|
117
|
+
checkedAt: string | null;
|
|
118
|
+
};
|
|
119
|
+
/** How the intake stands for one project, from the store alone: a page reads it on every render, so it never reads the
|
|
120
|
+
* project's folder (one that doesn't answer must not hold the console). Whether it uses youiyoux is the last pass's. */
|
|
121
|
+
export declare function youiyouxState(store: Store, repo: string): YouiyouxState;
|
|
122
|
+
/** The one line `toolroll onboard` says for a project that uses youiyoux; null when it doesn't. */
|
|
123
|
+
export declare function youiyouxOnboardLine(store: Store, repo: string): string | null;
|
|
124
|
+
/** The console the links name: the address the installation names (CONSOLE_URL or the console-url file), else, with
|
|
125
|
+
* none named, this machine's own console where `up` serves it (loopback http only, never a guessed host); null without
|
|
126
|
+
* either. Either way http(s) with no credentials, query or fragment. */
|
|
127
|
+
export declare function consoleBase(env: Record<string, string | undefined>, dir: string | null): string | null;
|
|
128
|
+
/** The task's page in that console; null without one. */
|
|
129
|
+
export declare function taskLink(env: Record<string, string | undefined>, dir: string | null, taskId: string): string | null;
|
|
130
|
+
/**
|
|
131
|
+
* Write `<id>.task.json` beside its request, whole. The folders are checked again now (a link is refused), the request
|
|
132
|
+
* must still be a plain file, the target must be a plain file or absent, and the content goes to a fresh temporary file
|
|
133
|
+
* in the same folder (created exclusively, never through a link) that is then renamed over the target.
|
|
134
|
+
*/
|
|
135
|
+
export declare function writeStatusFile(repo: string, body: YouiyouxRequestTask): {
|
|
136
|
+
ok: true;
|
|
137
|
+
} | {
|
|
138
|
+
ok: false;
|
|
139
|
+
problem: string;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Put an options file into the project's main checkout as `.youi/proposals/<id>.json`, whole, and never over a file
|
|
143
|
+
* already there (a person may have decided on it). The folders are checked as the status file's are (`.youi/proposals`
|
|
144
|
+
* is made, as a real folder, when missing), the content goes to a fresh temporary file in the same folder (created
|
|
145
|
+
* exclusively, never through a link), the folders are checked again, and the temporary file is linked into place, which
|
|
146
|
+
* fails rather than replace anything at that name, a link included. `same`: that exact content was already there;
|
|
147
|
+
* `taken`: something else was, and is left as it was.
|
|
148
|
+
*/
|
|
149
|
+
export declare function writeProposalFile(repo: string, id: string, text: string): {
|
|
150
|
+
ok: true;
|
|
151
|
+
made: "written" | "same" | "taken";
|
|
152
|
+
} | {
|
|
153
|
+
ok: false;
|
|
154
|
+
problem: string;
|
|
155
|
+
};
|
|
156
|
+
/** `git`: the hardened git an options request's result is read with (landed-cleanup.ts gitOf), and `evidenceRoot`, where
|
|
157
|
+
* results' saved evidence is (a completion is bound to the receipt read with it). Without either, options wait. */
|
|
158
|
+
export type YouiyouxIo = {
|
|
159
|
+
dir: string | null;
|
|
160
|
+
env: Record<string, string | undefined>;
|
|
161
|
+
git?: Git;
|
|
162
|
+
evidenceRoot?: string | null;
|
|
163
|
+
};
|
|
164
|
+
/** `copied`: options files that went to the hub; `decided`: decisions in the hub Toolroll acted on. */
|
|
165
|
+
export type YouiyouxPass = {
|
|
166
|
+
filed: number;
|
|
167
|
+
skipped: number;
|
|
168
|
+
invalid: number;
|
|
169
|
+
updated: number;
|
|
170
|
+
copied: number;
|
|
171
|
+
decided: number;
|
|
172
|
+
problems: string[];
|
|
173
|
+
};
|
|
174
|
+
export declare const NO_YOUIYOUX_PASS: Readonly<YouiyouxPass>;
|
|
175
|
+
/** File one request's task, exactly once: the claim and the filing are one transaction (two passes, in two processes
|
|
176
|
+
* or one, make one task). */
|
|
177
|
+
export declare function fileYouiyouxRequest(store: Store, repo: string, request: YouiyouxRequest, now: Date): {
|
|
178
|
+
made: "filed";
|
|
179
|
+
taskId: string;
|
|
180
|
+
} | {
|
|
181
|
+
made: "seen";
|
|
182
|
+
} | {
|
|
183
|
+
made: "skipped";
|
|
184
|
+
note: string;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* One message for what a pass filed, however many: the person's paired chats draw it as one card (chat-decide.ts) that
|
|
188
|
+
* lists each new task, with Approve all and each one's own Approve and Open, and a phone push links to what needs
|
|
189
|
+
* them. Keyed by the pass's first task, so a pass says it once; the tasks are the ones filed with it (one project, one
|
|
190
|
+
* moment: requestPassOf). Titles are cleaned outside text, as filed.
|
|
191
|
+
*/
|
|
192
|
+
export declare function noteYouiyouxFiled(store: Store, taskIds: readonly string[], now: Date): void;
|
|
193
|
+
/** What a pass's tasks were asked for, in a word: "change request", "options request", or "request" for both. */
|
|
194
|
+
export declare function requestKindWord(store: Store, taskIds: readonly string[]): string;
|
|
195
|
+
/** The tasks one pass filed beside this one (one project, one moment), in request order; empty for any other task. */
|
|
196
|
+
export declare function requestPassOf(store: Store, taskId: string): string[];
|
|
197
|
+
/**
|
|
198
|
+
* One project's pass: read new requests and file them, then write each filed task's status where it changed (no
|
|
199
|
+
* options moved: runYouiyoux does that too). The folder read is the project in Toolroll's records that `given`
|
|
200
|
+
* resolves to; anything else is left alone.
|
|
201
|
+
*/
|
|
202
|
+
export declare function runYouiyouxIntake(store: Store, given: string, now: Date, io: YouiyouxIo, options?: {
|
|
203
|
+
force?: boolean;
|
|
204
|
+
}): YouiyouxPass;
|
|
205
|
+
/** The worker's pass for one project (flow-cadence.ts): the intake, then each options request's options, both ways. */
|
|
206
|
+
export declare function runYouiyoux(store: Store, given: string, now: Date, io: YouiyouxIo, options?: {
|
|
207
|
+
force?: boolean;
|
|
208
|
+
}): Promise<YouiyouxPass>;
|
|
209
|
+
export type OptionsRow = {
|
|
210
|
+
request: string;
|
|
211
|
+
taskId: string;
|
|
212
|
+
proposal: string;
|
|
213
|
+
state: string | null;
|
|
214
|
+
run: number | null;
|
|
215
|
+
digest: string | null;
|
|
216
|
+
};
|
|
217
|
+
/** The digest of an options file apart from what a decision changes: the proposal itself, every other key included. */
|
|
218
|
+
export declare function proposalIdentity(raw: unknown): string | null;
|
|
219
|
+
/** Why an agent's options file can't go to the hub as it stands, or null. Toolroll checks what makes it safe to copy
|
|
220
|
+
* and this request's; youiyoux checks each option against the component's contract when it reads it. */
|
|
221
|
+
export declare function proposalProblem(text: string, proposal: string, request: string): string | null;
|
|
222
|
+
export type Decision = {
|
|
223
|
+
status: "chosen" | "dismissed";
|
|
224
|
+
choice: string | null;
|
|
225
|
+
label: string | null;
|
|
226
|
+
by: string | null;
|
|
227
|
+
at: string | null;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* A person decided in the hub: mark the task complete as landed work is, bound to the exact result whose options were
|
|
231
|
+
* copied, through every guard a person's completion passes, or leave it Ready for review and say why. Once per request,
|
|
232
|
+
* in one transaction with the read. Waits (false) while the task isn't Ready, Complete or cancelled.
|
|
233
|
+
*/
|
|
234
|
+
export declare function actOnDecision(store: Store, repo: string, row: OptionsRow, decision: Decision, now: Date, root: string): boolean;
|