@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21
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 +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- package/engine/jev/jev-client.ts +0 -173
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run in the board's state: where it stands (status and phase), why it
|
|
3
|
+
* stopped, the run's own facts, the budget, the stuck work that needs a
|
|
4
|
+
* reply, the tickets left out for a person, and the run list's summaries.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Why the run stopped and what to do, in words. A billing stop, or a stop
|
|
10
|
+
* after the same step crashed again and again, won't go on; any other stop
|
|
11
|
+
* picks up again when the run is started with its run id.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* stoppedText({ reason: 'overage in use', billing: true })
|
|
15
|
+
* // 'Stopped for billing: overage in use. This run will not go on; ...'
|
|
16
|
+
*/
|
|
17
|
+
export const stoppedText = ({
|
|
18
|
+
reason,
|
|
19
|
+
billing,
|
|
20
|
+
crashed,
|
|
21
|
+
}: {
|
|
22
|
+
reason: string
|
|
23
|
+
billing: boolean
|
|
24
|
+
/** The same run-level step crashed again and again. */
|
|
25
|
+
crashed?: boolean
|
|
26
|
+
}): string => {
|
|
27
|
+
if (billing) {
|
|
28
|
+
return `Stopped for billing: ${reason}. This run will not go on; start a new run once per-token billing is off.`
|
|
29
|
+
}
|
|
30
|
+
if (crashed === true) {
|
|
31
|
+
return `Stopped after crashes: ${reason} This run will not go on; read the engine's log for why it crashed, then start a new run.`
|
|
32
|
+
}
|
|
33
|
+
return `The run stopped: ${reason}. Start it again with the same run id to pick up where it stopped.`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Where a run stands, as one word for the header row and the panel. */
|
|
37
|
+
export const RunStatusSchema = z.enum([
|
|
38
|
+
'starting',
|
|
39
|
+
'intake',
|
|
40
|
+
'building',
|
|
41
|
+
'final_review',
|
|
42
|
+
'limit_wait',
|
|
43
|
+
'usage_line_wait',
|
|
44
|
+
'done',
|
|
45
|
+
'refused',
|
|
46
|
+
'nothing_to_do',
|
|
47
|
+
'stuck',
|
|
48
|
+
'stopped',
|
|
49
|
+
'ended_with_error',
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
export type RunStatus = z.infer<typeof RunStatusSchema>
|
|
53
|
+
|
|
54
|
+
/** The part of a run's status the journal sets directly. */
|
|
55
|
+
export const RunPhaseSchema = z.enum([
|
|
56
|
+
'starting',
|
|
57
|
+
'intake',
|
|
58
|
+
'building',
|
|
59
|
+
'final_review',
|
|
60
|
+
'done',
|
|
61
|
+
'refused',
|
|
62
|
+
'nothing_to_do',
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
export type RunPhase = z.infer<typeof RunPhaseSchema>
|
|
66
|
+
|
|
67
|
+
/** Stuck work waiting for a one-word reply on the spec issue. */
|
|
68
|
+
export const NeedsYouSchema = z.object({
|
|
69
|
+
/** `ticket-<n>`, `final`, or `run` (the run stuck on its budget). */
|
|
70
|
+
key: z.string(),
|
|
71
|
+
ticket: z.number().int().nullable(),
|
|
72
|
+
subject: z.string(),
|
|
73
|
+
reason: z.string(),
|
|
74
|
+
detail: z.string(),
|
|
75
|
+
/** What was tried before it got stuck, oldest first. */
|
|
76
|
+
tried: z.array(z.string()),
|
|
77
|
+
/** The exact replies to post as a comment on the spec issue. */
|
|
78
|
+
replies: z.array(z.string()),
|
|
79
|
+
since: z.string(),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export type NeedsYou = z.infer<typeof NeedsYouSchema>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The engine's default run budget in tokens (its
|
|
86
|
+
* `DEFAULT_RUN_BUDGET_TOKENS`, in `engine/src/limits/run-budget.ts`), for a
|
|
87
|
+
* run whose engine config sets no `run_budget_tokens`. The plugin never
|
|
88
|
+
* loads the engine's code, so the number is kept here too.
|
|
89
|
+
*/
|
|
90
|
+
export const DEFAULT_RUN_BUDGET_TOKENS = 9_000_000
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The engine's default `models.confidence_bar` (in
|
|
94
|
+
* `engine/src/config/engine-config-schema.ts`): the decision model is sharp at or
|
|
95
|
+
* over it, split under it. Kept here for the same reason.
|
|
96
|
+
*/
|
|
97
|
+
export const DEFAULT_CONFIDENCE_BAR = 0.8
|
|
98
|
+
|
|
99
|
+
export const EngineEndedSchema = z.object({
|
|
100
|
+
ok: z.boolean(),
|
|
101
|
+
message: z.string(),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
export type EngineEnded = z.infer<typeof EngineEndedSchema>
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The run stopped because going on was unsafe (wrong credentials or plan,
|
|
108
|
+
* a rejected rate limit, overage, ...). Starting it again with the same run
|
|
109
|
+
* id picks the step up again, except after a billing stop, which won't go on.
|
|
110
|
+
*/
|
|
111
|
+
export const RunStoppedSchema = z.object({
|
|
112
|
+
reason: z.string(),
|
|
113
|
+
role: z.string().nullable(),
|
|
114
|
+
ticket: z.number().int().nullable(),
|
|
115
|
+
/** The session would bill per token; this run won't go on. */
|
|
116
|
+
billing: z.boolean(),
|
|
117
|
+
/** The same step crashed again and again; this run won't go on. */
|
|
118
|
+
crashed: z.boolean().default(false),
|
|
119
|
+
since: z.string(),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
export type RunStopped = z.infer<typeof RunStoppedSchema>
|
|
123
|
+
|
|
124
|
+
export const RunInfoSchema = z.object({
|
|
125
|
+
run_id: z.string(),
|
|
126
|
+
/** `null` for a demo run until the engine names its spec. */
|
|
127
|
+
spec_number: z.number().int().nullable(),
|
|
128
|
+
spec_title: z.string().nullable(),
|
|
129
|
+
demo: z.boolean(),
|
|
130
|
+
branch: z.string().nullable(),
|
|
131
|
+
phase: RunPhaseSchema,
|
|
132
|
+
status: RunStatusSchema,
|
|
133
|
+
pr_url: z.string().nullable(),
|
|
134
|
+
pr_number: z.number().int().nullable(),
|
|
135
|
+
/** Why intake refused the run, one line per problem. */
|
|
136
|
+
refusal: z.array(z.string()),
|
|
137
|
+
started_at: z.string(),
|
|
138
|
+
last_time: z.string().nullable(),
|
|
139
|
+
engine_ended: EngineEndedSchema.nullable(),
|
|
140
|
+
/** Set by `run_stopped`, cleared once the engine moves on. */
|
|
141
|
+
stopped: RunStoppedSchema.nullable(),
|
|
142
|
+
log_path: z.string().nullable(),
|
|
143
|
+
/**
|
|
144
|
+
* Luca's version the run started on (#460); `null` when its journal
|
|
145
|
+
* doesn't say. Defaulted, so an older board state still parses.
|
|
146
|
+
*/
|
|
147
|
+
started_on: z.string().nullable().default(null),
|
|
148
|
+
/**
|
|
149
|
+
* Set once the run resumed on a different Luca version than it started
|
|
150
|
+
* on, naming both. The run keeps going. Defaulted, so an older board
|
|
151
|
+
* state still parses.
|
|
152
|
+
*/
|
|
153
|
+
version_note: z.string().nullable().default(null),
|
|
154
|
+
/**
|
|
155
|
+
* The spec's owner (its issue's author), from intake (#503): only their
|
|
156
|
+
* replies count. `null` when the journal doesn't say. Defaulted, so an
|
|
157
|
+
* older board state still parses.
|
|
158
|
+
*/
|
|
159
|
+
spec_author: z.string().nullable().default(null),
|
|
160
|
+
/** The spec's repo as `owner/repo`, from its URL; `null` when not known. */
|
|
161
|
+
spec_repo: z.string().nullable().default(null),
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
export type RunInfo = z.infer<typeof RunInfoSchema>
|
|
165
|
+
|
|
166
|
+
/** The run phases after which no engine is needed. */
|
|
167
|
+
export const OVER_PHASES: ReadonlySet<RunPhase> = new Set<RunPhase>([
|
|
168
|
+
'done',
|
|
169
|
+
'refused',
|
|
170
|
+
'nothing_to_do',
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Whether a run may still need an engine: not ended, not over. The
|
|
175
|
+
* server's engine check and the panel's "no sign of life" both go by it.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* needsEngine({ state }) // true while the run builds
|
|
179
|
+
*/
|
|
180
|
+
export const needsEngine = ({ state }: { state: { run: RunInfo } }): boolean =>
|
|
181
|
+
state.run.engine_ended === null && !OVER_PHASES.has(state.run.phase)
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* An open ticket the run left out for a person (#499): one labeled
|
|
185
|
+
* `ready-for-human`, or one waiting on such a ticket. Not a problem, so the
|
|
186
|
+
* board lists it calmly and nobody is asked to act.
|
|
187
|
+
*/
|
|
188
|
+
export const PersonTicketSchema = z.object({
|
|
189
|
+
number: z.number().int(),
|
|
190
|
+
title: z.string(),
|
|
191
|
+
/** Why, in words: `for a person`, or `waits on #12, which is for a person`. */
|
|
192
|
+
why: z.string(),
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
export type PersonTicket = z.infer<typeof PersonTicketSchema>
|
|
196
|
+
|
|
197
|
+
/** One run in the panel's run list. */
|
|
198
|
+
export const RunSummarySchema = z.object({
|
|
199
|
+
run_id: z.string(),
|
|
200
|
+
spec_number: z.number().int().nullable(),
|
|
201
|
+
spec_title: z.string().nullable(),
|
|
202
|
+
demo: z.boolean(),
|
|
203
|
+
status: RunStatusSchema,
|
|
204
|
+
started_at: z.string(),
|
|
205
|
+
needs_you: z.number().int().min(0),
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
export type RunSummary = z.infer<typeof RunSummarySchema>
|
|
209
|
+
|
|
210
|
+
/** How many of the newest runs get a tab; the older ones are folded. */
|
|
211
|
+
export const RECENT_RUN_TABS = 6
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Splits runs (newest first) into the ones with a tab and the folded older
|
|
215
|
+
* ones. A picked older run keeps a tab too, after the newest ones.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* const { shown, folded } = foldRuns({ runs, selected: picked })
|
|
219
|
+
*/
|
|
220
|
+
export const foldRuns = ({
|
|
221
|
+
runs,
|
|
222
|
+
selected,
|
|
223
|
+
}: {
|
|
224
|
+
runs: RunSummary[]
|
|
225
|
+
selected: string | null
|
|
226
|
+
}): { shown: RunSummary[]; folded: RunSummary[] } => {
|
|
227
|
+
const shown = runs.slice(0, RECENT_RUN_TABS)
|
|
228
|
+
const older = runs.slice(RECENT_RUN_TABS)
|
|
229
|
+
const picked = older.find((run) => run.run_id === selected)
|
|
230
|
+
return picked
|
|
231
|
+
? {
|
|
232
|
+
shown: [...shown, picked],
|
|
233
|
+
folded: older.filter((run) => run !== picked),
|
|
234
|
+
}
|
|
235
|
+
: { shown, folded: older }
|
|
236
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A ticket's card in the board's state: its steps and stage, its findings,
|
|
3
|
+
* the check that is open, the step the engine is in, and the card itself.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
import { LiveAgentSchema, LiveWaitSchema } from './board-state-live'
|
|
8
|
+
import { PlanUsedSchema } from './board-state-usage'
|
|
9
|
+
|
|
10
|
+
/** The steps of a ticket, shown as dots on its card. */
|
|
11
|
+
export const STEP_NAMES = [
|
|
12
|
+
'tests',
|
|
13
|
+
'red check',
|
|
14
|
+
'code',
|
|
15
|
+
'checks',
|
|
16
|
+
'review',
|
|
17
|
+
] as const
|
|
18
|
+
|
|
19
|
+
/** Refactor tickets skip the first two steps: no new test can fail first. */
|
|
20
|
+
export const REFACTOR_SKIPS_STEPS = 2
|
|
21
|
+
|
|
22
|
+
/** The step index a ticket has when every step is done. */
|
|
23
|
+
export const ALL_STEPS_DONE = STEP_NAMES.length
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* What a ticket is really doing (#549): `building` its first test-writer and
|
|
27
|
+
* implementer pass, `reviewing` only while a reviewer works, `testing` the red
|
|
28
|
+
* check or the checks, `fixing` any fix round, `joining` the run branch and
|
|
29
|
+
* the checks there. Every older stage is still one, so a stored board state
|
|
30
|
+
* from before still parses.
|
|
31
|
+
*/
|
|
32
|
+
export const TicketStageSchema = z.enum([
|
|
33
|
+
'blocked',
|
|
34
|
+
'building',
|
|
35
|
+
'reviewing',
|
|
36
|
+
'testing',
|
|
37
|
+
'fixing',
|
|
38
|
+
'joining',
|
|
39
|
+
'stuck',
|
|
40
|
+
'done',
|
|
41
|
+
'skipped',
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
export type TicketStage = z.infer<typeof TicketStageSchema>
|
|
45
|
+
|
|
46
|
+
export const FindingCountsSchema = z.object({
|
|
47
|
+
blocker: z.number().int().min(0),
|
|
48
|
+
should_fix: z.number().int().min(0),
|
|
49
|
+
nit: z.number().int().min(0),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export type FindingCounts = z.infer<typeof FindingCountsSchema>
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A check whose failure went back to an agent: its fix loop is open. `review`
|
|
56
|
+
* is a ticket review that asked for changes: the next fixer starts a review
|
|
57
|
+
* fix round.
|
|
58
|
+
*/
|
|
59
|
+
export const OpenCheckSchema = z.enum(['red_check', 'gates', 'review'])
|
|
60
|
+
|
|
61
|
+
export type OpenCheck = z.infer<typeof OpenCheckSchema>
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What the engine is doing right now for a ticket or the run, from its
|
|
65
|
+
* `step_started`, such as "running the baseline tests"; `since` is that
|
|
66
|
+
* record's time. Cleared by the step's `step_ended`.
|
|
67
|
+
*/
|
|
68
|
+
export const CurrentStepSchema = z.object({
|
|
69
|
+
text: z.string(),
|
|
70
|
+
since: z.string(),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export type CurrentStep = z.infer<typeof CurrentStepSchema>
|
|
74
|
+
|
|
75
|
+
/** The baseline tests' step (`run_baseline_tests`) in words. */
|
|
76
|
+
export const BASELINE_STEP_TEXT = 'running the baseline tests'
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What a ticket waits for between steps (#559): `baseline` another
|
|
80
|
+
* ticket's shared baseline tests, `join` the join queue, `start` the start
|
|
81
|
+
* queue, each on `on_ticket`. From the engine's `ticket_waiting`.
|
|
82
|
+
*/
|
|
83
|
+
export const WaitForSchema = z.enum(['baseline', 'join', 'start'])
|
|
84
|
+
|
|
85
|
+
export type WaitFor = z.infer<typeof WaitForSchema>
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A ticket between steps, since `since`: from `ticket_waiting` with what it
|
|
89
|
+
* waits for, or from its last `step_ended` with `for` and `on_ticket` null
|
|
90
|
+
* (an old journal's wait, worked out from the board). Cleared by the
|
|
91
|
+
* ticket's next `step_started`.
|
|
92
|
+
*/
|
|
93
|
+
export const TicketWaitSchema = z.object({
|
|
94
|
+
for: WaitForSchema.nullable(),
|
|
95
|
+
on_ticket: z.number().int().nullable(),
|
|
96
|
+
since: z.string(),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
export type TicketWait = z.infer<typeof TicketWaitSchema>
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A time span in words: seconds under a minute, then minutes and seconds,
|
|
103
|
+
* then hours and minutes.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* elapsedText({ ms: 125_000 }) // '2m 5s'
|
|
107
|
+
* elapsedText({ ms: 3_790_000 }) // '1h 3m'
|
|
108
|
+
*/
|
|
109
|
+
export const elapsedText = ({ ms }: { ms: number }): string => {
|
|
110
|
+
const seconds = Math.max(0, Math.floor(ms / 1000))
|
|
111
|
+
const hours = Math.floor(seconds / 3600)
|
|
112
|
+
const minutes = Math.floor((seconds % 3600) / 60)
|
|
113
|
+
if (hours > 0) return `${hours}h ${minutes}m`
|
|
114
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`
|
|
115
|
+
return `${seconds}s`
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The current step in words, with how long it has been running.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* currentStepText({
|
|
123
|
+
* step: { text: 'running the checks', since: '2026-09-23T12:00:00.000Z' },
|
|
124
|
+
* now: Date.parse('2026-09-23T12:02:05.000Z'),
|
|
125
|
+
* }) // 'running the checks (2m 5s)'
|
|
126
|
+
*/
|
|
127
|
+
export const currentStepText = ({
|
|
128
|
+
step,
|
|
129
|
+
now,
|
|
130
|
+
}: {
|
|
131
|
+
step: CurrentStep
|
|
132
|
+
now: number
|
|
133
|
+
}): string =>
|
|
134
|
+
`${step.text} (${elapsedText({ ms: now - Date.parse(step.since) })})`
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The decision model's answer to an agent's `agent_model` ask: the model it
|
|
138
|
+
* picked and how sure it was, either `null` when the answer lacks it.
|
|
139
|
+
*/
|
|
140
|
+
export const ModelAnswerSchema = z.object({
|
|
141
|
+
role: z.string(),
|
|
142
|
+
pick: z.string().nullable(),
|
|
143
|
+
confidence: z.number().nullable(),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
export type ModelAnswer = z.infer<typeof ModelAnswerSchema>
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The model the card's latest agent runs on (#630), from its
|
|
150
|
+
* `agent_started`, and why: its `model_reason` (`null` in older journals)
|
|
151
|
+
* and its `agent_model` answer, if any.
|
|
152
|
+
*/
|
|
153
|
+
export const AgentModelSchema = z.object({
|
|
154
|
+
role: z.string(),
|
|
155
|
+
name: z.string(),
|
|
156
|
+
reason: z.string().nullable(),
|
|
157
|
+
answer: ModelAnswerSchema.nullable(),
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
export type AgentModel = z.infer<typeof AgentModelSchema>
|
|
161
|
+
|
|
162
|
+
export const TicketCardSchema = z.object({
|
|
163
|
+
number: z.number().int(),
|
|
164
|
+
title: z.string(),
|
|
165
|
+
refactor: z.boolean(),
|
|
166
|
+
/** Tickets of this run this one waits on. */
|
|
167
|
+
blockers: z.array(z.number().int()),
|
|
168
|
+
/** The engine has begun work on it (its worktree exists). */
|
|
169
|
+
started: z.boolean(),
|
|
170
|
+
stage: TicketStageSchema,
|
|
171
|
+
/**
|
|
172
|
+
* Index into STEP_NAMES of the current step: -1 not started, 5 when every
|
|
173
|
+
* step is done. A stuck or skipped ticket keeps the step it stopped at.
|
|
174
|
+
*/
|
|
175
|
+
step: z.number().int().min(-1).max(ALL_STEPS_DONE),
|
|
176
|
+
/** One-word (or two) activity, such as "coding" or "reviewing". */
|
|
177
|
+
activity: z.string(),
|
|
178
|
+
/** The role working on it now, `null` when no agent is. */
|
|
179
|
+
role: z.string().nullable(),
|
|
180
|
+
/**
|
|
181
|
+
* The latest agent's model and why; `null` before one starts, or when
|
|
182
|
+
* its journal names no model. Defaulted, so an older board state still
|
|
183
|
+
* parses.
|
|
184
|
+
*/
|
|
185
|
+
model: AgentModelSchema.nullable().default(null),
|
|
186
|
+
/**
|
|
187
|
+
* The decision model's latest `agent_model` answer, kept until its
|
|
188
|
+
* agent starts. Defaulted, so an older board state still parses.
|
|
189
|
+
*/
|
|
190
|
+
model_answer: ModelAnswerSchema.nullable().default(null),
|
|
191
|
+
/**
|
|
192
|
+
* Each role's last used `agent_model` answer, by role, for its
|
|
193
|
+
* follow-ups, which ask nothing new. Defaulted, so an older board state
|
|
194
|
+
* still parses.
|
|
195
|
+
*/
|
|
196
|
+
model_answers: z.record(z.string(), ModelAnswerSchema).default({}),
|
|
197
|
+
/**
|
|
198
|
+
* The engine's step on it now, `null` between steps. Defaulted, so a
|
|
199
|
+
* board state from before it still parses.
|
|
200
|
+
*/
|
|
201
|
+
current_step: CurrentStepSchema.nullable().default(null),
|
|
202
|
+
/**
|
|
203
|
+
* The ticket between steps, `null` in a step or before its first.
|
|
204
|
+
* Defaulted, so a board state from before it still parses.
|
|
205
|
+
*/
|
|
206
|
+
wait: TicketWaitSchema.nullable().default(null),
|
|
207
|
+
/** Fix rounds in the current fix loop; back to 0 once its check passes. */
|
|
208
|
+
fix_round: z.number().int().min(0),
|
|
209
|
+
review_round: z.number().int().min(0),
|
|
210
|
+
/** Review fix rounds so far (up to LOOP_CAP): fixes after a review. */
|
|
211
|
+
review_fix_round: z.number().int().min(0),
|
|
212
|
+
/** The check whose failure the next follow-up answers, if any. */
|
|
213
|
+
open_check: OpenCheckSchema.nullable(),
|
|
214
|
+
/** The role whose latest turn failed, until an agent finishes again. */
|
|
215
|
+
failed_turn: z.string().nullable(),
|
|
216
|
+
tests: z
|
|
217
|
+
.object({
|
|
218
|
+
failing: z.number().int().min(0),
|
|
219
|
+
total: z.number().int().min(0),
|
|
220
|
+
})
|
|
221
|
+
.nullable(),
|
|
222
|
+
/**
|
|
223
|
+
* Its own baseline's test counts, kept so a ticket that reuses this one's
|
|
224
|
+
* baseline (`baseline_reused`) gets them after `tests` has moved on.
|
|
225
|
+
* Defaulted, so a board state from before it still parses.
|
|
226
|
+
*/
|
|
227
|
+
baseline_tests: z
|
|
228
|
+
.object({
|
|
229
|
+
failing: z.number().int().min(0),
|
|
230
|
+
total: z.number().int().min(0),
|
|
231
|
+
})
|
|
232
|
+
.nullable()
|
|
233
|
+
.default(null),
|
|
234
|
+
findings: FindingCountsSchema.nullable(),
|
|
235
|
+
/** Every token the ticket's agents read or wrote, cache reads included. */
|
|
236
|
+
tokens: z.number().min(0),
|
|
237
|
+
/** The same tokens by role. */
|
|
238
|
+
agent_tokens: z.record(z.string(), z.number().min(0)),
|
|
239
|
+
/** What was tried on it so far (failed checks, fix rounds), oldest first. */
|
|
240
|
+
tried: z.array(z.string()),
|
|
241
|
+
/** How much of the plan the ticket used, per window; empty until known. */
|
|
242
|
+
plan_used: z.array(PlanUsedSchema),
|
|
243
|
+
/**
|
|
244
|
+
* Its agent's entry in the run's live file (#628), `null` with none.
|
|
245
|
+
* Laid over the state on each read, never journaled. Defaulted, so a
|
|
246
|
+
* board state from before it still parses.
|
|
247
|
+
*/
|
|
248
|
+
live: LiveAgentSchema.nullable().default(null),
|
|
249
|
+
/**
|
|
250
|
+
* Its check's wait for a prepare slot or a test slot (#632), from the
|
|
251
|
+
* live file the same way, `null` with none. Defaulted, so a board
|
|
252
|
+
* state from before it still parses.
|
|
253
|
+
*/
|
|
254
|
+
slot_wait: LiveWaitSchema.nullable().default(null),
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
export type TicketCard = z.infer<typeof TicketCardSchema>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan usage in the board's state: the color level of a percent, plan
|
|
3
|
+
* windows in words and in their shown order, how much of a window something
|
|
4
|
+
* used, and the plan's latest reading.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
|
|
8
|
+
/** Plan usage is yellow from this percent. */
|
|
9
|
+
export const USAGE_WARN_FROM = 60
|
|
10
|
+
|
|
11
|
+
/** Plan usage is red above this percent. */
|
|
12
|
+
export const USAGE_HIGH_ABOVE = 85
|
|
13
|
+
|
|
14
|
+
export const UsageLevelSchema = z.enum(['ok', 'warn', 'high'])
|
|
15
|
+
|
|
16
|
+
export type UsageLevel = z.infer<typeof UsageLevelSchema>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The color level of a plan usage percent: `ok` (green) below 60, `warn`
|
|
20
|
+
* (yellow) from 60 to 85, `high` (red) above 85.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* usageLevel({ percent: 59 }) // 'ok'
|
|
24
|
+
* usageLevel({ percent: 85 }) // 'warn'
|
|
25
|
+
* usageLevel({ percent: 86 }) // 'high'
|
|
26
|
+
*/
|
|
27
|
+
export const usageLevel = ({ percent }: { percent: number }): UsageLevel => {
|
|
28
|
+
if (percent > USAGE_HIGH_ABOVE) return 'high'
|
|
29
|
+
if (percent >= USAGE_WARN_FROM) return 'warn'
|
|
30
|
+
return 'ok'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Plan windows in words; any other window keeps its raw name. */
|
|
34
|
+
const WINDOW_WORDS: Record<string, string> = {
|
|
35
|
+
five_hour: 'five-hour',
|
|
36
|
+
seven_day: 'weekly',
|
|
37
|
+
seven_day_opus: 'weekly Opus',
|
|
38
|
+
seven_day_sonnet: 'weekly Sonnet',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A plan window's name in words.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* windowWords({ window: 'seven_day_opus' }) // 'weekly Opus'
|
|
46
|
+
* windowWords({ window: 'overage' }) // 'overage'
|
|
47
|
+
*/
|
|
48
|
+
export const windowWords = ({ window }: { window: string }): string =>
|
|
49
|
+
WINDOW_WORDS[window] ?? window
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The order windows are shown in: five-hour, weekly, the other weekly ones
|
|
53
|
+
* (Opus, Sonnet, ...), then the rest.
|
|
54
|
+
*/
|
|
55
|
+
export const windowRank = ({ window }: { window: string }): number => {
|
|
56
|
+
if (window === 'five_hour') return 0
|
|
57
|
+
if (window === 'seven_day') return 1
|
|
58
|
+
if (window.startsWith('seven_day')) return 2
|
|
59
|
+
return 3
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** How much of one plan window something used, as a whole percent. */
|
|
63
|
+
export const PlanUsedSchema = z.object({
|
|
64
|
+
/** The window in words, such as "five-hour" or "weekly". */
|
|
65
|
+
window: z.string(),
|
|
66
|
+
percent: z.number(),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
export type PlanUsed = z.infer<typeof PlanUsedSchema>
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The plan used, in words, windows in their shown order.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* planUsedText({ used, sign: '+' }) // 'five-hour +1%, weekly +1%'
|
|
76
|
+
* planUsedText({ used, sign: '' }) // 'five-hour 3%, weekly 1%'
|
|
77
|
+
*/
|
|
78
|
+
export const planUsedText = ({
|
|
79
|
+
used,
|
|
80
|
+
sign,
|
|
81
|
+
}: {
|
|
82
|
+
used: PlanUsed[]
|
|
83
|
+
sign: '+' | ''
|
|
84
|
+
}): string =>
|
|
85
|
+
used.map(({ window, percent }) => `${window} ${sign}${percent}%`).join(', ')
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The plan's usage, from the rate-limit readings in agents' sessions. Each
|
|
89
|
+
* window keeps its latest reading; `null` until one arrives.
|
|
90
|
+
*/
|
|
91
|
+
export const UsageSchema = z.object({
|
|
92
|
+
five_hour_percent: z.number().nullable(),
|
|
93
|
+
weekly_percent: z.number().nullable(),
|
|
94
|
+
five_hour_level: UsageLevelSchema.nullable(),
|
|
95
|
+
weekly_level: UsageLevelSchema.nullable(),
|
|
96
|
+
/** When the five-hour window resets. */
|
|
97
|
+
resets_at: z.string().nullable(),
|
|
98
|
+
read_at: z.string(),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
export type Usage = z.infer<typeof UsageSchema>
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The words on the plan usage: its windows are the whole account's, every
|
|
105
|
+
* run's and chat's, never one run's.
|
|
106
|
+
*/
|
|
107
|
+
export const USAGE_LABEL = 'plan usage (account-wide)'
|