toolroll 0.9.51 → 0.9.53
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 +20 -0
- package/README.md +55 -80
- package/dist/agentconfig.d.ts +11 -36
- package/dist/agentconfig.js +3 -62
- package/dist/approval-policy.d.ts +1 -1
- package/dist/approval-policy.js +1 -1
- package/dist/backup.d.ts +8 -0
- package/dist/backup.js +10 -1
- package/dist/board.d.ts +3 -19
- package/dist/board.js +4 -42
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +18 -18
- package/dist/browser-workspace.d.ts +3 -1
- package/dist/builder.d.ts +9 -40
- package/dist/builder.js +50 -257
- package/dist/chat-actions.js +3 -14
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-decide.js +1 -3
- package/dist/chat-delivery.d.ts +0 -2
- package/dist/chat-delivery.js +0 -1
- package/dist/chat-quiet.js +2 -2
- package/dist/chat-task-actions.d.ts +1 -1
- package/dist/chat-task-actions.js +0 -2
- package/dist/claim.d.ts +79 -244
- package/dist/claim.js +688 -1023
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +10 -2
- package/dist/contracts/cli.d.ts +0 -13
- package/dist/contracts/cli.js +0 -2
- package/dist/contracts/console-api.d.ts +5 -10
- package/dist/contracts/console-api.js +4 -10
- package/dist/contracts/flow.d.ts +50 -0
- package/dist/contracts/flow.js +26 -1
- package/dist/contracts/lead-tools.d.ts +0 -13
- package/dist/contracts/lead-tools.js +3 -3
- package/dist/contracts/store-json.d.ts +1 -25
- package/dist/contracts/store-json.js +3 -13
- package/dist/converse.d.ts +1 -11
- package/dist/converse.js +6 -38
- package/dist/coordinator.d.ts +0 -1
- package/dist/coordinator.js +3 -5
- package/dist/database-reclaim.d.ts +4 -0
- package/dist/database-reclaim.js +13 -0
- package/dist/demo.d.ts +1 -1
- package/dist/demo.js +17 -27
- package/dist/desktop-update-gate.js +0 -1
- package/dist/desktop-update.d.ts +1 -1
- package/dist/desktop-update.js +1 -3
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/dispose.d.ts +2 -3
- package/dist/dispose.js +17 -92
- package/dist/envelope.js +0 -3
- package/dist/exec.d.ts +0 -64
- package/dist/exec.js +3 -356
- package/dist/exhaustion.d.ts +22 -76
- package/dist/exhaustion.js +14 -140
- package/dist/export.js +4 -4
- package/dist/flow-engine.js +1 -1
- package/dist/flow-schedule.d.ts +154 -0
- package/dist/flow-schedule.js +321 -0
- package/dist/flow-share.js +1 -1
- package/dist/flow-triggers.d.ts +6 -1
- package/dist/flow-triggers.js +65 -10
- package/dist/flows-cli.js +8 -4
- package/dist/flows-ui.js +2 -0
- package/dist/flows.js +1 -1
- package/dist/guides.js +9 -29
- package/dist/invoke.d.ts +1 -24
- package/dist/invoke.js +17 -259
- package/dist/lead-status.js +2 -4
- package/dist/lead-voice.js +1 -1
- package/dist/live-bus.d.ts +3 -3
- package/dist/live-bus.js +3 -3
- package/dist/mate-doors.d.ts +1 -2
- package/dist/mate-doors.js +12 -25
- package/dist/mate-tools.d.ts +2 -2
- package/dist/mate-tools.js +10 -22
- package/dist/modes.d.ts +6 -7
- package/dist/modes.js +7 -13
- package/dist/monitoring.js +32 -8
- package/dist/notification-delivery.d.ts +19 -0
- package/dist/notification-delivery.js +37 -0
- package/dist/operate-remote-arguments.js +2 -6
- package/dist/operate-remote.js +6 -7
- package/dist/operate.d.ts +0 -6
- package/dist/operate.js +283 -2378
- package/dist/phase-routing.d.ts +13 -23
- package/dist/phase-routing.js +13 -35
- package/dist/plan-auto.js +2 -2
- package/dist/planner-source.js +2 -1
- package/dist/policy.d.ts +1 -4
- package/dist/policy.js +0 -8
- package/dist/process-recovery-eligibility.js +2 -3
- package/dist/process-recovery-settlement.js +1 -0
- package/dist/project-delete.js +3 -15
- package/dist/proposal.d.ts +4 -24
- package/dist/proposal.js +3 -88
- package/dist/provider-auth.d.ts +3 -3
- package/dist/provider-auth.js +4 -6
- package/dist/provider.d.ts +11 -25
- package/dist/provider.js +4 -36
- package/dist/push.js +1 -1
- package/dist/recipe-ui.js +6 -6
- package/dist/recipes.d.ts +3 -3
- package/dist/recipes.js +27 -23
- package/dist/retention.js +6 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +1 -4
- package/dist/scope.d.ts +11 -65
- package/dist/scope.js +17 -82
- package/dist/serve.js +16 -25
- package/dist/server/chat.js +7 -30
- package/dist/server/flows.js +11 -10
- package/dist/server/guards.js +0 -1
- package/dist/server/pages-assets.js +1 -1
- package/dist/server/pages.js +0 -2
- package/dist/server/people-tokens.js +0 -4
- package/dist/server/route-table.d.ts +1 -1
- package/dist/server/route-table.js +2 -17
- package/dist/server/settings.js +4 -9
- package/dist/server/shared.d.ts +29 -212
- package/dist/server/shared.js +73 -780
- package/dist/server/task-views.js +9 -76
- package/dist/server/tasks.d.ts +1 -50
- package/dist/server/tasks.js +36 -946
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.d.ts +0 -2
- package/dist/slack-chat.js +0 -1
- package/dist/store.d.ts +142 -1382
- package/dist/store.js +900 -4083
- package/dist/surface.js +10 -25
- package/dist/task-composer.d.ts +1 -1
- package/dist/task-composer.js +1 -1
- package/dist/task-control.d.ts +3 -10
- package/dist/task-control.js +2 -7
- package/dist/teammate-desk.js +1 -1
- package/dist/teammates-ui.js +1 -1
- package/dist/telegram-mate.d.ts +0 -3
- package/dist/telegram-mate.js +0 -1
- package/dist/templates.d.ts +11 -7
- package/dist/templates.js +12 -9
- package/dist/toolroll-update.d.ts +24 -3
- package/dist/toolroll-update.js +159 -16
- package/dist/webhooks.d.ts +10 -22
- package/dist/webhooks.js +43 -81
- package/dist/work-index.js +3 -7
- package/dist/workspace-revision.d.ts +28 -1
- package/dist/workspace-revision.js +276 -49
- package/dist/workspace-ui.js +1 -1
- package/package.json +1 -3
- package/scripts/recovery-canary.mjs +3 -3
- package/dist/contest.d.ts +0 -318
- package/dist/contest.js +0 -753
- package/dist/held.d.ts +0 -150
- package/dist/held.js +0 -799
- package/dist/liveness.d.ts +0 -30
- package/dist/liveness.js +0 -42
- package/dist/pricing.d.ts +0 -45
- package/dist/pricing.js +0 -77
- package/dist/routine.d.ts +0 -228
- package/dist/routine.js +0 -764
- package/dist/supervisor.mjs +0 -297
package/dist/scope.d.ts
CHANGED
|
@@ -123,14 +123,6 @@ export declare function canonicalChainJson(entries: readonly ChainEntry[]): stri
|
|
|
123
123
|
* ":profile:"), so a chain digest can never collide with a profile
|
|
124
124
|
* digest, truncated to the same 128 bits. */
|
|
125
125
|
export declare function chainDigestOf(entries: readonly ChainEntry[]): string;
|
|
126
|
-
/**
|
|
127
|
-
* One chain entry's BINDING digest (E3d): the profile digest + auth mode
|
|
128
|
-
* under their own domain. A run row pins this at admission (or base-cycle
|
|
129
|
-
* open), and the dispatch proof re-derives it from the approved chain at
|
|
130
|
-
* the run's index — so nothing downstream can swap WHICH entry a run
|
|
131
|
-
* spends as, in model or in credential.
|
|
132
|
-
*/
|
|
133
|
-
export declare function entryDigestOf(entry: ChainEntry): string;
|
|
134
126
|
/** Strict re-hydration of a stored chain — every entry proved through the
|
|
135
127
|
* single-profile rehydrator; anything unexpected is null. */
|
|
136
128
|
export declare function chainFromJson(json: string | null): ChainEntry[] | null;
|
|
@@ -182,7 +174,7 @@ export declare function profileFromJson(json: string | null): ExecutionProfile |
|
|
|
182
174
|
export type { AcceptanceCriterion, AcceptanceProblem };
|
|
183
175
|
/**
|
|
184
176
|
* Parse a rubric from already-JSON-parsed input (the planner's handoff, a
|
|
185
|
-
* console form, a
|
|
177
|
+
* console form, a template or a schedule's terms): fail closed, every
|
|
186
178
|
* problem reported at once, each naming its path — the acceptance contract
|
|
187
179
|
* (contracts/scope.ts) reads it. An absent or empty `value` parses to `[]`
|
|
188
180
|
* with no problems: whether that is ALLOWED is a question for the caller
|
|
@@ -281,13 +273,10 @@ export type Scope = SavedScopeTerms & {
|
|
|
281
273
|
profileJson?: string | null;
|
|
282
274
|
approvedProfileJson?: string | null;
|
|
283
275
|
digestVersion?: number;
|
|
284
|
-
/** v30 fallback chains
|
|
285
|
-
*
|
|
286
|
-
* `
|
|
287
|
-
*
|
|
288
|
-
* legacy single-profile scope — every scope until an operator configures a
|
|
289
|
-
* fallback chain. The runtime re-derives the active entry from
|
|
290
|
-
* `approvedChainJson`, never from mutable config. */
|
|
276
|
+
/** v30-v114 fallback chains (history): the working and sealed chain
|
|
277
|
+
* snapshots and which kind the approval sealed. Never written since v115
|
|
278
|
+
* (a re-approval writes `profile` and clears the sealed chain); an old
|
|
279
|
+
* chain approval still decodes and verifies. */
|
|
291
280
|
proposedChainJson?: string | null;
|
|
292
281
|
approvedChainJson?: string | null;
|
|
293
282
|
approvalKind?: "profile" | "chain";
|
|
@@ -336,8 +325,8 @@ export type ScopeInput = {
|
|
|
336
325
|
goal: string;
|
|
337
326
|
outOfScope?: string | null;
|
|
338
327
|
touches?: readonly string[];
|
|
339
|
-
/** v24: an EXPLICIT profile skips resolution in the store (
|
|
340
|
-
* firings and the demo's illustrative scopes use this road). */
|
|
328
|
+
/** v24: an EXPLICIT profile skips resolution in the store (a schedule's
|
|
329
|
+
* approved firings and the demo's illustrative scopes use this road). */
|
|
341
330
|
profile?: ExecutionProfile;
|
|
342
331
|
/** A task-level permission choice. When absent, the task's stored choice
|
|
343
332
|
* (if any), then the installation default, decides the concrete profile. */
|
|
@@ -347,9 +336,6 @@ export type ScopeInput = {
|
|
|
347
336
|
qualityMode?: QualityMode;
|
|
348
337
|
/** v69: a prepared commit to install as this attempt — no agent runs. */
|
|
349
338
|
candidate?: string | null;
|
|
350
|
-
/** v47: the task's declared risk. When absent, the task's stored choice
|
|
351
|
-
* (if any), else routine. A durable TASK choice, like qualityMode. */
|
|
352
|
-
riskLevel?: RiskLevel;
|
|
353
339
|
/** Integer micro-dollars per build attempt; digest-bound when present. */
|
|
354
340
|
budgetMicrousd?: number | null;
|
|
355
341
|
/** The mode road's escalated filing default (C7): the resolved profile
|
|
@@ -504,7 +490,7 @@ export declare function authenticateAccount(store: Store, by: string, secret: st
|
|
|
504
490
|
* v100: someone who signed in with the identity provider moments ago has no
|
|
505
491
|
* password to type again. The console marks such a request with this, for
|
|
506
492
|
* that person only, and an EMPTY password then stands for the fresh sign-in
|
|
507
|
-
* at every step-up that reaches `authenticateApprover` (scope
|
|
493
|
+
* at every step-up that reaches `authenticateApprover` (scope
|
|
508
494
|
* approval, chat-action review, the console's own ceremonies). A typed
|
|
509
495
|
* password is always checked, and nothing else stands in.
|
|
510
496
|
*/
|
|
@@ -574,12 +560,10 @@ export declare function approvalOf(scope: Scope | null): Approval;
|
|
|
574
560
|
/**
|
|
575
561
|
* THE STRICT STORED-SCOPE PROJECTION (v48 integrity): the ONE reading of
|
|
576
562
|
* a filed scope that a seal and every consent surface believe. Nothing
|
|
577
|
-
* lenient stands in for it — the working profile
|
|
563
|
+
* lenient stands in for it — the working profile and route are
|
|
578
564
|
* re-parsed from their raw bytes with exact keys, safe integers, and
|
|
579
|
-
* timer-safe clocks; the
|
|
580
|
-
* the
|
|
581
|
-
* chain filing the profile IS the chain's entry zero; and the row's
|
|
582
|
-
* digest re-derives, complete, from these very values. A legacy row (no
|
|
565
|
+
* timer-safe clocks; the route's build and repair legs ARE the profile's
|
|
566
|
+
* exact pairs; and the row's digest re-derives, complete, from these very values. A legacy row (no
|
|
583
567
|
* route era) has no authority a person can newly agree to. One
|
|
584
568
|
* disagreement is the words, and nothing — no nonce, no password field,
|
|
585
569
|
* no approve action, no seal — is exposed on it.
|
|
@@ -587,7 +571,6 @@ export declare function approvalOf(scope: Scope | null): Approval;
|
|
|
587
571
|
export type ScopeAuthority = {
|
|
588
572
|
ok: true;
|
|
589
573
|
profile: ExecutionProfile;
|
|
590
|
-
chain: ChainEntry[] | null;
|
|
591
574
|
route: PhaseRoute;
|
|
592
575
|
digest: string;
|
|
593
576
|
authMode: AuthMode | null;
|
|
@@ -637,40 +620,3 @@ export declare function acceptanceWords(criteria: readonly AcceptanceCriterion[]
|
|
|
637
620
|
export declare function scopeRouteWords(scope: Pick<Scope, "proposedRouteJson">, readiness?: ReadinessLookup): string[];
|
|
638
621
|
/** The scope, in the words an operator has to be able to agree or disagree with. */
|
|
639
622
|
export declare function describeScope(scope: Scope, readiness?: ReadinessLookup): string[];
|
|
640
|
-
/** The chain's approval-card lines; empty for a single-profile scope. */
|
|
641
|
-
export declare function chainWords(chain: ChainEntry[] | null): string[];
|
|
642
|
-
/**
|
|
643
|
-
* EVERY rendered term of one watched attempt — what the form shows is what
|
|
644
|
-
* the password signs, byte for byte. The subset the dispatch proof
|
|
645
|
-
* re-derives (scopeDigest, profileDigest, profileJson, repo, head) is read
|
|
646
|
-
* back by the builder and the coordinator; the rest are the product terms
|
|
647
|
-
* the ceremony renders in words: the budget as a STOP THRESHOLD, the turn
|
|
648
|
-
* cap, the per-turn clock whose expiry is SESSION-FATAL, and the absolute
|
|
649
|
-
* expiry. Continuation carries the parent attempt and the follow-up text
|
|
650
|
-
* INSIDE the signed terms (v3 R7).
|
|
651
|
-
*/
|
|
652
|
-
export type AttendedTerms = {
|
|
653
|
-
taskId: string;
|
|
654
|
-
/** v28: HOW liveness is renewed — a SIGNED term, because liveness is an
|
|
655
|
-
* admission predicate, not decoration. "console-visible" = any open
|
|
656
|
-
* console page of this server renews use. Legacy terms lack the field
|
|
657
|
-
* and beat-all refuses them (they were signed as page-bound). */
|
|
658
|
-
attentionMode: "console-visible";
|
|
659
|
-
scopeDigest: string;
|
|
660
|
-
profileDigest: string;
|
|
661
|
-
profileJson: string;
|
|
662
|
-
repo: string;
|
|
663
|
-
runner: string;
|
|
664
|
-
runnerGeneration: number;
|
|
665
|
-
head: string;
|
|
666
|
-
maxSessionTurns: number;
|
|
667
|
-
budgetMicrousd: number;
|
|
668
|
-
turnTimeoutSeconds: number;
|
|
669
|
-
absoluteExpiry: string;
|
|
670
|
-
parentRun?: number | null;
|
|
671
|
-
followup?: string | null;
|
|
672
|
-
};
|
|
673
|
-
/** Deterministic bytes: sorted keys, undefined dropped — the signed text. */
|
|
674
|
-
export declare function attendedTermsJson(terms: AttendedTerms): string;
|
|
675
|
-
/** The composite digest one password signs (ruling 12), domain-separated. */
|
|
676
|
-
export declare function attendedDigestOf(terms: AttendedTerms): string;
|
package/dist/scope.js
CHANGED
|
@@ -36,7 +36,7 @@ import { z } from "zod";
|
|
|
36
36
|
import {} from "./contracts/acceptance-terms.js";
|
|
37
37
|
import { parseContract } from "./contracts/contract.js";
|
|
38
38
|
import { readAcceptance, readScopeTerms, storedRubricSchema } from "./contracts/scope.js";
|
|
39
|
-
import { NO_READINESS, exactModelId,
|
|
39
|
+
import { NO_READINESS, exactModelId, legOf, projectRoute, routeDigestOf, routeFromJson, routeJsonProblem, routeWords, } from "./phase-routing.js";
|
|
40
40
|
/** Same shape as a runner's credential, for the same reasons. */
|
|
41
41
|
function mintToken() {
|
|
42
42
|
return randomBytes(32).toString("base64url");
|
|
@@ -129,9 +129,10 @@ export function profileDigestOf(profile) {
|
|
|
129
129
|
.digest("hex")
|
|
130
130
|
.slice(0, 32);
|
|
131
131
|
}
|
|
132
|
-
// ---- fallback chains (v30)
|
|
133
|
-
//
|
|
134
|
-
//
|
|
132
|
+
// ---- fallback chains (v30-v114; removed in v115) --------------------------
|
|
133
|
+
// History only: an approval sealed over a fallback chain still decodes and
|
|
134
|
+
// its digest still re-derives, so old tasks verify and render. Nothing
|
|
135
|
+
// files, seals, or dispatches a chain any more.
|
|
135
136
|
export const CHAIN_DIGEST_VERSION = 1;
|
|
136
137
|
/** The stored snapshot bytes for a chain: the version rides IN the
|
|
137
138
|
* snapshot, exactly like a single profile's. */
|
|
@@ -148,19 +149,6 @@ export function chainDigestOf(entries) {
|
|
|
148
149
|
.digest("hex")
|
|
149
150
|
.slice(0, 32);
|
|
150
151
|
}
|
|
151
|
-
/**
|
|
152
|
-
* One chain entry's BINDING digest (E3d): the profile digest + auth mode
|
|
153
|
-
* under their own domain. A run row pins this at admission (or base-cycle
|
|
154
|
-
* open), and the dispatch proof re-derives it from the approved chain at
|
|
155
|
-
* the run's index — so nothing downstream can swap WHICH entry a run
|
|
156
|
-
* spends as, in model or in credential.
|
|
157
|
-
*/
|
|
158
|
-
export function entryDigestOf(entry) {
|
|
159
|
-
return createHash("sha256")
|
|
160
|
-
.update(`standing-orders:chain-entry:v${CHAIN_DIGEST_VERSION}:${profileDigestOf(entry.profile)}:${entry.authMode}`, "utf8")
|
|
161
|
-
.digest("hex")
|
|
162
|
-
.slice(0, 32);
|
|
163
|
-
}
|
|
164
152
|
/** Strict re-hydration of a stored chain — every entry proved through the
|
|
165
153
|
* single-profile rehydrator; anything unexpected is null. */
|
|
166
154
|
export function chainFromJson(json) {
|
|
@@ -409,7 +397,7 @@ export function profileFromJson(json) {
|
|
|
409
397
|
}
|
|
410
398
|
/**
|
|
411
399
|
* Parse a rubric from already-JSON-parsed input (the planner's handoff, a
|
|
412
|
-
* console form, a
|
|
400
|
+
* console form, a template or a schedule's terms): fail closed, every
|
|
413
401
|
* problem reported at once, each naming its path — the acceptance contract
|
|
414
402
|
* (contracts/scope.ts) reads it. An absent or empty `value` parses to `[]`
|
|
415
403
|
* with no problems: whether that is ALLOWED is a question for the caller
|
|
@@ -569,7 +557,7 @@ route) {
|
|
|
569
557
|
.slice(0, 32);
|
|
570
558
|
}
|
|
571
559
|
export function propose(store, input) {
|
|
572
|
-
const { taskId, goal, outOfScope = null, touches = [], budgetMicrousd = null, acceptance = [], qualityMode = "default", now, mutation = {}, profile, permissionMode, posture, proposedVia = null
|
|
560
|
+
const { taskId, goal, outOfScope = null, touches = [], budgetMicrousd = null, acceptance = [], qualityMode = "default", now, mutation = {}, profile, permissionMode, posture, proposedVia = null } = input;
|
|
573
561
|
const draft = { goal, outOfScope, touches: [...touches], budgetMicrousd, acceptance: [...acceptance], qualityMode, candidate: input.candidate ?? null };
|
|
574
562
|
const previous = store.getScope(taskId);
|
|
575
563
|
const scope = {
|
|
@@ -590,7 +578,6 @@ export function propose(store, input) {
|
|
|
590
578
|
...(profile === undefined ? {} : { profile }),
|
|
591
579
|
...(permissionMode === undefined ? {} : { permissionMode }),
|
|
592
580
|
...(input.qualityMode === undefined ? {} : { qualityMode: input.qualityMode }),
|
|
593
|
-
...(riskLevel === undefined || !isRiskLevel(riskLevel) ? {} : { riskLevel }),
|
|
594
581
|
...(posture === undefined ? {} : { posture }),
|
|
595
582
|
proposedVia,
|
|
596
583
|
});
|
|
@@ -755,7 +742,7 @@ export function authenticateAccount(store, by, secret) {
|
|
|
755
742
|
* v100: someone who signed in with the identity provider moments ago has no
|
|
756
743
|
* password to type again. The console marks such a request with this, for
|
|
757
744
|
* that person only, and an EMPTY password then stands for the fresh sign-in
|
|
758
|
-
* at every step-up that reaches `authenticateApprover` (scope
|
|
745
|
+
* at every step-up that reaches `authenticateApprover` (scope
|
|
759
746
|
* approval, chat-action review, the console's own ceremonies). A typed
|
|
760
747
|
* password is always checked, and nothing else stands in.
|
|
761
748
|
*/
|
|
@@ -990,16 +977,10 @@ export function scopeAuthorityOf(scope, env = {}) {
|
|
|
990
977
|
if (profile === null) {
|
|
991
978
|
return { ok: false, reason: "profile", problem: profileJson === null ? "the scope carries no agent profile" : "the scope's agent profile cannot be read exactly (unknown keys, an unsafe number, or a clock no timer can hold)" };
|
|
992
979
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
if (
|
|
996
|
-
return { ok: false, reason: "chain", problem: "
|
|
997
|
-
}
|
|
998
|
-
if (chain !== null) {
|
|
999
|
-
const base = chain[0];
|
|
1000
|
-
if (base === undefined || profileDigestOf(base.profile) !== profileDigestOf(profile)) {
|
|
1001
|
-
return { ok: false, reason: "parity", problem: "the scope's agent profile is not its fallback chain's first entry" };
|
|
1002
|
-
}
|
|
980
|
+
// A scope filed with fallback agents (v30-v114) holds no authority now
|
|
981
|
+
// that fallback chains are gone: saving it again chooses its agents.
|
|
982
|
+
if (scope.proposedChainJson != null) {
|
|
983
|
+
return { ok: false, reason: "chain", problem: "this scope named fallback agents, which Toolroll no longer uses — save the scope again to choose its agents" };
|
|
1003
984
|
}
|
|
1004
985
|
const routeJson = scope.proposedRouteJson ?? null;
|
|
1005
986
|
const route = routeFromJson(routeJson);
|
|
@@ -1009,7 +990,7 @@ export function scopeAuthorityOf(scope, env = {}) {
|
|
|
1009
990
|
const parity = routeParityProblem(route, profile, { riskLevel: scope.riskLevel ?? "routine", qualityMode: scope.qualityMode ?? "default" });
|
|
1010
991
|
if (parity !== null)
|
|
1011
992
|
return { ok: false, reason: "parity", problem: parity };
|
|
1012
|
-
const digest = digestOf({ goal: scope.goal, outOfScope: scope.outOfScope, touches: scope.touches, budgetMicrousd: scope.budgetMicrousd, acceptance: scope.acceptance, candidate: scope.candidate ?? null, qualityMode: scope.qualityMode ?? "default" },
|
|
993
|
+
const digest = digestOf({ goal: scope.goal, outOfScope: scope.outOfScope, touches: scope.touches, budgetMicrousd: scope.budgetMicrousd, acceptance: scope.acceptance, candidate: scope.candidate ?? null, qualityMode: scope.qualityMode ?? "default" }, profile, route);
|
|
1013
994
|
if (digest !== scope.digest) {
|
|
1014
995
|
return { ok: false, reason: "digest", problem: "the scope's reference does not re-derive from its own terms, agents, and route — file it again" };
|
|
1015
996
|
}
|
|
@@ -1018,31 +999,15 @@ export function scopeAuthorityOf(scope, env = {}) {
|
|
|
1018
999
|
// strict reader — a present mode file that says neither word closes
|
|
1019
1000
|
// the door in its words, never the default a lenient read would coerce
|
|
1020
1001
|
// it to (the spawn reads the same way, so nothing sealed here spends
|
|
1021
|
-
// on a credential the operator never named).
|
|
1022
|
-
// its base entry's pinned mode and must agree with the live file too.
|
|
1002
|
+
// on a credential the operator never named).
|
|
1023
1003
|
let authMode = null;
|
|
1024
1004
|
if (env.authMode !== undefined) {
|
|
1025
1005
|
const read = env.authMode(profile.provider);
|
|
1026
1006
|
if (!read.ok)
|
|
1027
1007
|
return { ok: false, reason: "auth-mode", problem: read.problem };
|
|
1028
1008
|
authMode = read.mode;
|
|
1029
|
-
// PINNED AND LIVE AUTH AGREE, or nothing (final authority closure): a
|
|
1030
|
-
// chain filed under one credential for its base entry, read beside a
|
|
1031
|
-
// live mode file that now says the other, is a scope whose signed
|
|
1032
|
-
// words no longer describe what would spend — a subscription chain
|
|
1033
|
-
// approved after the operator moved to an API key would bill the
|
|
1034
|
-
// key. It holds no authority until it is filed again under today's
|
|
1035
|
-
// mode.
|
|
1036
|
-
const base = chain === null ? null : chain[0] ?? null;
|
|
1037
|
-
if (base !== null && base.authMode !== authMode) {
|
|
1038
|
-
return {
|
|
1039
|
-
ok: false,
|
|
1040
|
-
reason: "auth-mode",
|
|
1041
|
-
problem: `the scope's fallback chain pins its base entry to ${base.authMode === "subscription" ? "your subscription" : "your API key"} for ${profile.provider}, but the live auth mode is now ${authMode === "subscription" ? "your subscription" : "your API key"} — re-file the scope under today's mode`,
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
1009
|
}
|
|
1045
|
-
return { ok: true, profile,
|
|
1010
|
+
return { ok: true, profile, route, digest, authMode };
|
|
1046
1011
|
}
|
|
1047
1012
|
/** The rubric's approval-card lines (v39): one per criterion, the id in
|
|
1048
1013
|
* front (the exact string a proof must answer by) and its required
|
|
@@ -1075,32 +1040,13 @@ export function describeScope(scope, readiness = NO_READINESS) {
|
|
|
1075
1040
|
...(scope.budgetMicrousd === null
|
|
1076
1041
|
? []
|
|
1077
1042
|
: [` budget $${(scope.budgetMicrousd / 1_000_000).toFixed(2)} per build attempt — the agent is stopped at this figure`]),
|
|
1078
|
-
// The
|
|
1079
|
-
//
|
|
1080
|
-
// the WHOLE ordered chain — so the card says every entry, credential
|
|
1081
|
-
// included, before anyone signs.
|
|
1082
|
-
...chainWords(chainFromJson(scope.proposedChainJson ?? null)),
|
|
1083
|
-
// The ROUTE, in the words the yes agrees to (v47): risk, posture, and
|
|
1084
|
-
// every leg with its reason and readiness — before anyone signs.
|
|
1085
|
-
...(scope.riskLevel === undefined || scope.riskLevel === "routine" ? [] : [` risk ${scope.riskLevel}`]),
|
|
1043
|
+
// The ROUTE, in the words the yes agrees to (v47): posture and every
|
|
1044
|
+
// leg with its reason and readiness — before anyone signs.
|
|
1086
1045
|
...scopeRouteWords(scope, readiness),
|
|
1087
1046
|
` reference ${scope.digest}`,
|
|
1088
1047
|
` approved ${describeApproval(approval)}`,
|
|
1089
1048
|
];
|
|
1090
1049
|
}
|
|
1091
|
-
/** The chain's approval-card lines; empty for a single-profile scope. */
|
|
1092
|
-
export function chainWords(chain) {
|
|
1093
|
-
if (chain === null || chain.length < 2)
|
|
1094
|
-
return [];
|
|
1095
|
-
const credential = (mode) => mode === "subscription" ? "your subscription" : "your API key";
|
|
1096
|
-
const lines = [];
|
|
1097
|
-
const base = chain[0];
|
|
1098
|
-
lines.push(` runs on ${base.profile.provider} (${base.profile.model}) — ${credential(base.authMode)}`);
|
|
1099
|
-
for (const entry of chain.slice(1)) {
|
|
1100
|
-
lines.push(` if that runs out falls back to ${entry.profile.provider} (${entry.profile.model}) — ${credential(entry.authMode)}${entry.authMode === "api-key" ? "; spend moves to that account" : ""}`);
|
|
1101
|
-
}
|
|
1102
|
-
return lines;
|
|
1103
|
-
}
|
|
1104
1050
|
function describeApproval(approval) {
|
|
1105
1051
|
if (approval.approved)
|
|
1106
1052
|
return `yes, by ${approval.by} at ${approval.at}`;
|
|
@@ -1109,14 +1055,3 @@ function describeApproval(approval) {
|
|
|
1109
1055
|
}
|
|
1110
1056
|
return "no — nothing will build this until somebody agrees to it";
|
|
1111
1057
|
}
|
|
1112
|
-
/** Deterministic bytes: sorted keys, undefined dropped — the signed text. */
|
|
1113
|
-
export function attendedTermsJson(terms) {
|
|
1114
|
-
return canonicalJson(terms);
|
|
1115
|
-
}
|
|
1116
|
-
/** The composite digest one password signs (ruling 12), domain-separated. */
|
|
1117
|
-
export function attendedDigestOf(terms) {
|
|
1118
|
-
return createHash("sha256")
|
|
1119
|
-
.update(`standing-orders:attended:${attendedTermsJson(terms)}`)
|
|
1120
|
-
.digest("hex")
|
|
1121
|
-
.slice(0, 32);
|
|
1122
|
-
}
|
package/dist/serve.js
CHANGED
|
@@ -115,7 +115,7 @@ import { createFlowsHandlers } from './server/flows.js';
|
|
|
115
115
|
import { createPagesHandlers } from './server/pages.js';
|
|
116
116
|
import { createPeopleHandlers } from './server/people-tokens.js';
|
|
117
117
|
import { createSettingsHandlers } from './server/settings.js';
|
|
118
|
-
import {
|
|
118
|
+
import { BODY_CAP, chromeScript, decisionsFor, DEMO_BANNER, DEMO_BANNER_SHORT, escape, focusDocument, form, KBD_HELP, LEAD_BY_DEFAULT_FACT, loginHref, matchTaskPath, mateBrowserMessages, mateChatVersion, NO_PROJECT, NO_TOUCH_FRAGMENTS, NONCE_CAP, NONCE_TTL_MS, page, PersistentSessions, pinnedTheme, projectChatHref, QUEUE_VIEW, redactedPath, redirect, refuse, requestContext, respond, safeReturn, screen, SENSITIVE_INPUT, SESSION_ABSOLUTE_MS, SESSION_IDLE_MS, shell, SHUTDOWN_WAIT_MS, sidebarScript, SIGN_IN_LINK_MS, SIGN_IN_LINK_PATH, ssoStepUps, TASK_FORM_BODY_CAP, taskChatHref, teamProposalCardParts, wrongHostPage } from "./server/shared.js";
|
|
119
119
|
import { createTasksHandlers } from './server/tasks.js';
|
|
120
120
|
import { DEFAULT_GUARD_POLICY, passwordGuardOf, provenPasswordAccount, SourceBudget, withPasswordSource } from "./sign-in-guard.js";
|
|
121
121
|
import { sourceKey } from "./source-key.js";
|
|
@@ -429,7 +429,7 @@ export function createDecisionServer(options) {
|
|
|
429
429
|
/** One-click connections on their way: the service's sign-in page and back, 15 minutes at most. */
|
|
430
430
|
const connectVisits = new Map();
|
|
431
431
|
/** Flows open in a browser (v88): who's here, and a nudge when one changes. */
|
|
432
|
-
// Live views push on write (live-bus.ts): the revision
|
|
432
|
+
// Live views push on write (live-bus.ts): each commit moves the workspace revision, the rooms hear it.
|
|
433
433
|
const liveBus = createLiveBus();
|
|
434
434
|
const liveFollower = followWorkspace(store, () => workspaceRevision.current(), liveBus, { file: store.databaseFile() });
|
|
435
435
|
const flowRooms = createFlowRooms(flow => flowFingerprint(store, flow), { bus: liveBus });
|
|
@@ -738,7 +738,7 @@ export function createDecisionServer(options) {
|
|
|
738
738
|
}
|
|
739
739
|
return handlePost(route, url, who, request, response, body);
|
|
740
740
|
};
|
|
741
|
-
if (method === "GET"
|
|
741
|
+
if (method === "GET")
|
|
742
742
|
return projectAuthority.run({ actor: who.name, repo: target.repo }, execute);
|
|
743
743
|
// Request acceptance is separate from durable work completion. Never
|
|
744
744
|
// persist a body, query string, token, password, or arbitrary URL.
|
|
@@ -797,18 +797,16 @@ export function createDecisionServer(options) {
|
|
|
797
797
|
const task = matchTaskPath(url.pathname, "(?:/([a-z-]+))?$");
|
|
798
798
|
if (task !== null)
|
|
799
799
|
return { repo: store.lookupRef(task.taskId)?.repo ?? null, taskId: task.taskId, runId: null, action: `task ${task.verb || "view"}` };
|
|
800
|
-
const resource = /^\/(r|d|i
|
|
800
|
+
const resource = /^\/(r|d|i)\/([0-9]{1,15})(?:\/([a-z-]+)(?:\/[0-9]+)?)?$/.exec(url.pathname);
|
|
801
801
|
if (resource !== null) {
|
|
802
802
|
const id = Number(resource[2]);
|
|
803
|
-
if (resource[1] === "routines")
|
|
804
|
-
return { repo: store.getRoutine(id)?.repo ?? null, taskId: null, runId: null, action: `routine ${resource[3] ?? "view"}` };
|
|
805
803
|
const runId = resource[1] === "r" ? id : resource[1] === "d" ? store.getDecision(id)?.run ?? null : store.openIncidents().find(one => one.id === id)?.run ?? null;
|
|
806
804
|
const run = runId === null ? null : store.getRun(runId);
|
|
807
805
|
const ref = run === null ? null : store.refForId(run.taskRef);
|
|
808
806
|
return { repo: ref?.repo ?? null, taskId: ref?.externalId ?? null, runId, action: `${resource[1] === "d" ? "decision" : resource[1] === "i" ? "incident" : "run"} ${resource[3] ?? "view"}` };
|
|
809
807
|
}
|
|
810
|
-
const known = new Set(["/recipes/prepare", "/recipes/preview", "/recipes/import", "/recipes/save", "/recipes/launch", "/tasks/add", "/queue/move", "/queue/note", "/
|
|
811
|
-
const placed =
|
|
808
|
+
const known = new Set(["/recipes/prepare", "/recipes/preview", "/recipes/import", "/recipes/save", "/recipes/launch", "/tasks/add", "/queue/move", "/queue/note", "/people/invite", "/people/invite-revoke", "/people/revoke", "/people/projects", "/projects/select", "/projects/open", "/mode/confirm", "/mode/sign", "/mode/revoke", "/settings/chat-approval/save", "/settings/chat-approval/off"]);
|
|
809
|
+
const placed = url.pathname === "/tasks/add" ? body?.get("repo")?.trim() : null;
|
|
812
810
|
return { repo: url.pathname.startsWith("/people/") ? null : placed ? canonicalProject(placed) ?? placed : projectOf(who, request) ?? null, taskId: null, runId: null,
|
|
813
811
|
action: known.has(url.pathname) ? url.pathname.slice(1).replaceAll("/", " ") : "console request" };
|
|
814
812
|
}
|
|
@@ -1170,7 +1168,7 @@ export function createDecisionServer(options) {
|
|
|
1170
1168
|
{ label: "board", href: "/board?scope=all" },
|
|
1171
1169
|
{ label: "queue", href: QUEUE_VIEW },
|
|
1172
1170
|
{ label: "workbench", href: "/workbench" },
|
|
1173
|
-
{ label: "
|
|
1171
|
+
{ label: "flows", href: "/flows" },
|
|
1174
1172
|
{ label: "done", href: "/done" },
|
|
1175
1173
|
{ label: "review cockpit", href: "/review" },
|
|
1176
1174
|
{ label: "task list", href: "/tasks" },
|
|
@@ -1188,7 +1186,7 @@ export function createDecisionServer(options) {
|
|
|
1188
1186
|
entries.push({ label: `${one.id} — ${one.title}`, href: `/t/${encodeURIComponent(one.id)}` });
|
|
1189
1187
|
}
|
|
1190
1188
|
const saturated = open.length > 200;
|
|
1191
|
-
const safeEntries = restricted() ? entries.filter(one => ["/work", "/work?view=needs-you", "/", "/board", "/
|
|
1189
|
+
const safeEntries = restricted() ? entries.filter(one => ["/work", "/work?view=needs-you", "/", "/board", "/flows", "/done", "/review", "/tasks", "/runs", "/projects", "/ledger"].includes(one.href) || one.href.startsWith("/t/")) : entries;
|
|
1192
1190
|
const json = JSON.stringify(saturated ? [...safeEntries, { label: "… more in the task list", href: "/tasks" }] : safeEntries)
|
|
1193
1191
|
.replace(/</g, "\\u003c");
|
|
1194
1192
|
return `<script type="application/json" id="palette-index">${json}</script>`;
|
|
@@ -1343,7 +1341,7 @@ export function createDecisionServer(options) {
|
|
|
1343
1341
|
// A page with its own view keeps its script; the chat page runs the result script.
|
|
1344
1342
|
const functional = conversation === null || extras.view ? (s.functional?.script ?? '') : RESULT_REVIEW_SCRIPT;
|
|
1345
1343
|
const document = shell(s.title, s.body, { chrome: s.chrome, sensitive });
|
|
1346
|
-
return page(response, status, browserWorkspaceDocument(document, workspace, nonce, functional +
|
|
1344
|
+
return page(response, status, browserWorkspaceDocument(document, workspace, nonce, functional + MOBILE_VIEWPORT_SCRIPT), nonce, true);
|
|
1347
1345
|
}
|
|
1348
1346
|
// A one-time secret on screen (a token, an invite link, a pairing code): the
|
|
1349
1347
|
// page carries no script of any kind, so it leaves the workspace for a
|
|
@@ -1352,13 +1350,9 @@ export function createDecisionServer(options) {
|
|
|
1352
1350
|
return page(response, status, focusDocument(s.title, s.body));
|
|
1353
1351
|
const chromeLayer = !sensitive && s.chrome !== undefined;
|
|
1354
1352
|
const functional = s.functional?.script ?? "";
|
|
1355
|
-
// Sensitive pages strip the palette and keys but keep the
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
const sensitiveChrome = sensitive && s.chrome !== undefined
|
|
1359
|
-
? beatScript(!restricted()) + sidebarScript()
|
|
1360
|
-
: "";
|
|
1361
|
-
const script = functional + (chromeLayer ? chromeScript(!restricted()) + WORKSPACE_MOTION_SCRIPT : sensitiveChrome);
|
|
1353
|
+
// Sensitive pages strip the palette and keys but keep the sidebar toggle.
|
|
1354
|
+
const sensitiveChrome = sensitive && s.chrome !== undefined ? sidebarScript() : "";
|
|
1355
|
+
const script = functional + (chromeLayer ? chromeScript() + WORKSPACE_MOTION_SCRIPT : sensitiveChrome);
|
|
1362
1356
|
const nonce = script === "" ? undefined : randomBytes(16).toString("base64");
|
|
1363
1357
|
const body = chromeLayer
|
|
1364
1358
|
? `${s.body}\n${paletteTagCached(s.chrome?.project ?? null)}\n${KBD_HELP}`
|
|
@@ -1370,8 +1364,8 @@ export function createDecisionServer(options) {
|
|
|
1370
1364
|
...(s.refreshSeconds === undefined ? {} : { refreshSeconds: s.refreshSeconds }),
|
|
1371
1365
|
...(nonce === undefined ? {} : { live: { nonce, script, fallbackRefresh: s.functional?.fetches === true } }),
|
|
1372
1366
|
});
|
|
1373
|
-
//
|
|
1374
|
-
//
|
|
1367
|
+
// Pages that ship the chrome layer keep connect-src (v28 granted it for
|
|
1368
|
+
// the since-removed watched-session beat), beside pages whose own
|
|
1375
1369
|
// functional script polls.
|
|
1376
1370
|
return page(response, status, html, nonce, s.functional?.fetches === true || chromeLayer || sensitiveChrome !== "");
|
|
1377
1371
|
}
|
|
@@ -1462,7 +1456,7 @@ export function createDecisionServer(options) {
|
|
|
1462
1456
|
: {
|
|
1463
1457
|
modeBanner: {
|
|
1464
1458
|
name: liveMode.name,
|
|
1465
|
-
words: `${liveMode.name} mode until ${liveMode.absoluteExpiry.slice(0, 16).replace("T", " ")} UTC${liveModeTerms.autoApproveFiling ? ` — every scope ${liveMode.signedBy} files builds without further ceremony` : ""}${liveModeTerms.permissionDefault === "escalated" ? " — FULL permissions by default" : ""}${liveModeTerms.
|
|
1459
|
+
words: `${liveMode.name} mode until ${liveMode.absoluteExpiry.slice(0, 16).replace("T", " ")} UTC${liveModeTerms.autoApproveFiling ? ` — every scope ${liveMode.signedBy} files builds without further ceremony` : ""}${liveModeTerms.permissionDefault === "escalated" ? " — FULL permissions by default" : ""}${liveModeTerms.publication === "automerge" ? " — merges fire themselves on green" : ""}`,
|
|
1466
1460
|
},
|
|
1467
1461
|
}),
|
|
1468
1462
|
...(!restricted() && !unscopedMode && managedRepos().length > 0 ? { chat: true } : {}),
|
|
@@ -1535,8 +1529,6 @@ export function createDecisionServer(options) {
|
|
|
1535
1529
|
};
|
|
1536
1530
|
async function handlePost(route, url, who, request, response, posted) {
|
|
1537
1531
|
const early = () => dispatchConsole(route, { url, who, request, response, route, now: clock(), project: projectOf(who, request) ?? null, chosenProject: projectOf(who, request) ?? null, posted });
|
|
1538
|
-
if (route.id === 'session.attended-beats')
|
|
1539
|
-
return early();
|
|
1540
1532
|
const denied = authorizeMutation(request, who, readForm(posted, CONSOLE_FORMS.mutationGuard));
|
|
1541
1533
|
if (denied !== null)
|
|
1542
1534
|
return refuse(response, who, denied.status, denied.message);
|
|
@@ -1550,8 +1542,7 @@ export function createDecisionServer(options) {
|
|
|
1550
1542
|
// THE CENTRAL VIEWER GATE (modes chain, D2/E2): consequential POSTs
|
|
1551
1543
|
// require ACTIVE approver standing — cookie and bearer alike — with
|
|
1552
1544
|
// an EXACT allowlist of session-local acts, never a prefix. /login,
|
|
1553
|
-
// /logout and /join answer before this handler
|
|
1554
|
-
// guards itself (approver-only) above.
|
|
1545
|
+
// /logout and /join answer before this handler.
|
|
1555
1546
|
if (who.role === "viewer") {
|
|
1556
1547
|
if (route.viewer !== true) {
|
|
1557
1548
|
return refuse(response, who, 403, "your login can watch, not act — ask an approver to upgrade you");
|
package/dist/server/chat.js
CHANGED
|
@@ -23,7 +23,7 @@ import { MATE_MESSAGE_MAX_CHARS, runMateTurn } from "../mate.js";
|
|
|
23
23
|
import { livePin, modelOptions } from "../model-catalog.js";
|
|
24
24
|
import { verifyApproverByPassword, verifyApproverStanding } from "../principal.js";
|
|
25
25
|
import { projectName } from "../project.js";
|
|
26
|
-
import {
|
|
26
|
+
import { fileTaskProposal } from "../proposal.js";
|
|
27
27
|
import { providerName } from "../provider-auth.js";
|
|
28
28
|
import { validModelId } from "../provider.js";
|
|
29
29
|
import { loadOrCreateVapidKeys, validatePushEndpoint } from "../push.js";
|
|
@@ -763,7 +763,7 @@ export function createChatHandlers(runtime) {
|
|
|
763
763
|
noteMate(who.session.csrf, null, "that proposal was already acted on");
|
|
764
764
|
return redirect(response, chatReturnWithLatest(back));
|
|
765
765
|
}
|
|
766
|
-
const outcome = confirmMateProposal(store, principal, id, now, { chatProvider: teamChatProvider, confirm: body.get("confirm") === "yes", via: "web", evidenceRoot,
|
|
766
|
+
const outcome = confirmMateProposal(store, principal, id, now, { chatProvider: teamChatProvider, confirm: body.get("confirm") === "yes", via: "web", evidenceRoot, ...(body.has("nonce") ? { actionReview: { nonce: body.get("nonce") ?? "", password: body.get("token") ?? "" } } : {}) });
|
|
767
767
|
if (cardJson) {
|
|
768
768
|
if (!outcome.ok)
|
|
769
769
|
return cardAnswer(outcome.reason === "standing" ? 403 : outcome.reason === "not-yours" ? 404 : 409, false, outcome.said);
|
|
@@ -1005,43 +1005,20 @@ export function createChatHandlers(runtime) {
|
|
|
1005
1005
|
// Re-validated at the act: the door runs every field check again, and
|
|
1006
1006
|
// the fields are scanned for secrets before they become durable.
|
|
1007
1007
|
const acceptanceFields = candidate.draft.acceptance.flatMap(c => [c.statement, ...(c.how === null ? [] : [c.how])]);
|
|
1008
|
-
const fields = candidate.draft.
|
|
1009
|
-
? [candidate.draft.title, candidate.draft.goal, candidate.draft.outOfScope ?? "", ...candidate.draft.touches, ...acceptanceFields]
|
|
1010
|
-
: [candidate.draft.name, candidate.draft.goal, candidate.draft.outOfScope ?? "", ...candidate.draft.touches, ...acceptanceFields];
|
|
1008
|
+
const fields = [candidate.draft.title, candidate.draft.goal, candidate.draft.outOfScope ?? "", ...candidate.draft.touches, ...acceptanceFields];
|
|
1011
1009
|
if (scanForSecrets(fields.join("\n")).length > 0) {
|
|
1012
1010
|
chat.candidates.delete(key);
|
|
1013
1011
|
return refuse(response, who, 400, "that draft contains something credential-shaped — discarded", "/chat");
|
|
1014
1012
|
}
|
|
1015
1013
|
const filedVia = `chat:${enabled.config.provider}`;
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
title: candidate.draft.title,
|
|
1019
|
-
repo: candidate.repoPath,
|
|
1020
|
-
goal: candidate.draft.goal,
|
|
1021
|
-
outOfScope: candidate.draft.outOfScope,
|
|
1022
|
-
touches: candidate.draft.touches,
|
|
1023
|
-
acceptance: candidate.draft.acceptance,
|
|
1024
|
-
filedVia, filedBy: { name: who.name, kind: "person" },
|
|
1025
|
-
admittedRepos: managedRepos(),
|
|
1026
|
-
}, now);
|
|
1027
|
-
if (!made.ok) {
|
|
1028
|
-
candidate.state = "pending";
|
|
1029
|
-
return refuse(response, who, 400, `the door refused it: ${made.message}`, "/chat");
|
|
1030
|
-
}
|
|
1031
|
-
chat.candidates.delete(key);
|
|
1032
|
-
return redirect(response, taskHref(made.id));
|
|
1033
|
-
}
|
|
1034
|
-
const made = fileRoutineProposal(store, {
|
|
1035
|
-
name: candidate.draft.name,
|
|
1014
|
+
const made = fileTaskProposal(store, {
|
|
1015
|
+
title: candidate.draft.title,
|
|
1036
1016
|
repo: candidate.repoPath,
|
|
1037
1017
|
goal: candidate.draft.goal,
|
|
1038
1018
|
outOfScope: candidate.draft.outOfScope,
|
|
1039
1019
|
touches: candidate.draft.touches,
|
|
1040
1020
|
acceptance: candidate.draft.acceptance,
|
|
1041
|
-
|
|
1042
|
-
schedule: candidate.draft.schedule,
|
|
1043
|
-
costCeilingUsd: null,
|
|
1044
|
-
filedVia, createdBy: who.name,
|
|
1021
|
+
filedVia, filedBy: { name: who.name, kind: "person" },
|
|
1045
1022
|
admittedRepos: managedRepos(),
|
|
1046
1023
|
}, now);
|
|
1047
1024
|
if (!made.ok) {
|
|
@@ -1049,7 +1026,7 @@ export function createChatHandlers(runtime) {
|
|
|
1049
1026
|
return refuse(response, who, 400, `the door refused it: ${made.message}`, "/chat");
|
|
1050
1027
|
}
|
|
1051
1028
|
chat.candidates.delete(key);
|
|
1052
|
-
return redirect(response,
|
|
1029
|
+
return redirect(response, taskHref(made.id));
|
|
1053
1030
|
}
|
|
1054
1031
|
const chatAckPost = /^\/chat\/ack\/([0-9]{1,15})$/.exec(url.pathname);
|
|
1055
1032
|
if (chatAckPost !== null) {
|
package/dist/server/flows.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import {} from "node:http";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveAgentAuthority } from "../agentconfig.js";
|
|
7
7
|
import { checkResponse, CONSOLE_FORMS, readForm } from "../contracts/console-api.js";
|
|
8
8
|
import { stageReferenceProblems } from "../contracts/stage-output.js";
|
|
9
9
|
import { LIMITS } from "../decision.js";
|
|
@@ -17,7 +17,7 @@ import { assignFlowCard, commentOnFlowCard, watchFlowCard } from "../flow-people
|
|
|
17
17
|
import { saveScript } from "../flow-scripts.js";
|
|
18
18
|
import { chooseFlowCard } from "../flow-send.js";
|
|
19
19
|
import { exportFlow, fetchFlowFile, FlowFileError, importFlow, parseFlowFile, planFlowImport } from "../flow-share.js";
|
|
20
|
-
import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, renewFlowHook, saveHooksBase, saveLinearKey, saveLinearSigningSecret, shareFlowButton, stopSharingFlowButton } from "../flow-triggers.js";
|
|
20
|
+
import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey, saveLinearSigningSecret, shareFlowButton, stopSharingFlowButton } from "../flow-triggers.js";
|
|
21
21
|
import { FLOW_IMPORT_SCRIPT, flowFallbackHtml, flowImportHtml, flowsListHtml, flowView } from "../flows-ui.js";
|
|
22
22
|
import { FLOW_TEMPLATES, FlowContractError, validateFlowDefinition, withZoneNames } from "../flows.js";
|
|
23
23
|
import { keyStatus } from "../keys.js";
|
|
@@ -169,11 +169,12 @@ export function createFlowsHandlers(runtime) {
|
|
|
169
169
|
const revision = who.via === "cookie" ? who.session.projectRevision : 0;
|
|
170
170
|
const render = (html) => sendScreen(response, 200, screen("Workflow recipes", html, { chrome: chromeFor(project, "recipes"), functional: { script: recipeScript() } }));
|
|
171
171
|
if (url.pathname === "/recipes") {
|
|
172
|
-
const recent = project === null ? [] : store.handle.prepare("SELECT document,task_id,
|
|
172
|
+
const recent = project === null ? [] : store.handle.prepare("SELECT document,task_id,flow_id FROM workflow_preview WHERE repo=? AND (task_id IS NOT NULL OR flow_id IS NOT NULL) ORDER BY created_at DESC LIMIT 12").all(project).map(row => {
|
|
173
173
|
const d = importRecipe(String(row["document"]));
|
|
174
174
|
const task = row["task_id"] === null ? null : store.getTask(String(row["task_id"]));
|
|
175
|
-
const
|
|
176
|
-
|
|
175
|
+
const flow = row["flow_id"] === null ? null : store.getFlow(Number(row["flow_id"]));
|
|
176
|
+
const on = flow !== null && flow.state === "active" && store.flowTriggers(flow.id).some(one => one.kind === "schedule" && one.state === "active");
|
|
177
|
+
return { name: d.name, href: task !== null ? taskHref(task.id) : `/flows/${Number(row["flow_id"])}`, state: task?.state ?? (flow === null || flow.state !== "active" ? "Archived" : on ? "Scheduled" : "Paused") };
|
|
177
178
|
});
|
|
178
179
|
return render(recipeLibraryHtml(starterRecipes(), project === null ? [] : savedRecipes(store, who.name, project), project, csrf, revision, recent));
|
|
179
180
|
}
|
|
@@ -215,7 +216,7 @@ export function createFlowsHandlers(runtime) {
|
|
|
215
216
|
if (url.pathname === "/recipes/preview" && preview !== null) {
|
|
216
217
|
if (preview.document.version === 2 || url.searchParams.get("purpose") === "recipe")
|
|
217
218
|
return render(recipeDefinitionPreviewHtml(preview, csrf, revision));
|
|
218
|
-
const agents =
|
|
219
|
+
const agents = resolveAgentAuthority(store, project, preview.document.acceptance, now);
|
|
219
220
|
const workers = store.listRunners().filter(one => one.retiredAt === null && one.repos.includes(project) && runnerAlive(one, now));
|
|
220
221
|
const mode = store.activeMode(project, now);
|
|
221
222
|
const readiness = [
|
|
@@ -498,7 +499,7 @@ export function createFlowsHandlers(runtime) {
|
|
|
498
499
|
return answer(404, { ok: false, said: "That trigger isn't on this flow." });
|
|
499
500
|
const verb = triggerPost[3];
|
|
500
501
|
if (verb === "pause" || verb === "resume") {
|
|
501
|
-
store
|
|
502
|
+
setFlowTriggerOn(store, trigger, verb === "resume", now);
|
|
502
503
|
return settle(verb === "pause" ? "Trigger paused." : "Trigger on again.");
|
|
503
504
|
}
|
|
504
505
|
if (verb === "remove") {
|
|
@@ -722,8 +723,8 @@ export function createFlowsHandlers(runtime) {
|
|
|
722
723
|
const sourceVersion = body.get("sourceRevision") ?? "1";
|
|
723
724
|
if (!/^[1-9][0-9]{0,8}$/.test(sourceVersion))
|
|
724
725
|
throw new RecipeError("Choose a valid recipe version.");
|
|
725
|
-
const original = ["custom", "imported", "task-copy"
|
|
726
|
-
if (original === null && !["custom", "imported", "task-copy"
|
|
726
|
+
const original = ["custom", "imported", "task-copy"].includes(source) ? null : findRecipe(store, who.name, project, source, Number(sourceVersion));
|
|
727
|
+
if (original === null && !["custom", "imported", "task-copy"].includes(source))
|
|
727
728
|
throw new RecipeError("That recipe version is not available in this project.", 404);
|
|
728
729
|
let document;
|
|
729
730
|
try {
|
|
@@ -753,7 +754,7 @@ export function createFlowsHandlers(runtime) {
|
|
|
753
754
|
if (context !== undefined)
|
|
754
755
|
context.createdTask = made.taskId;
|
|
755
756
|
}
|
|
756
|
-
return redirect(response, made.taskId === null ? `/
|
|
757
|
+
return redirect(response, made.taskId === null ? `/flows/${made.flowId}` : taskHref(made.taskId));
|
|
757
758
|
}
|
|
758
759
|
catch (error) {
|
|
759
760
|
if (error instanceof RecipeError)
|
package/dist/server/guards.js
CHANGED
|
@@ -152,7 +152,6 @@ export function createSharedGuards(runtime) {
|
|
|
152
152
|
case 'run': return runRepo(id);
|
|
153
153
|
case 'decision': return runRepo(runtime.store.getDecision(id)?.run);
|
|
154
154
|
case 'incident': return runRepo(runtime.store.openIncidents().find(one => one.id === id)?.run);
|
|
155
|
-
case 'routine': return runtime.store.getRoutine(id)?.repo ?? null;
|
|
156
155
|
case 'flow': return runtime.store.getFlow(id)?.repo ?? null;
|
|
157
156
|
case 'proposal':
|
|
158
157
|
case 'coding': return runtime.actionTarget(url, who, request, body === null ? null : readForm(body, CONSOLE_FORMS.ledgerTarget)).repo;
|