@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
|
@@ -2,10 +2,12 @@ import groupBy from 'lodash/groupBy'
|
|
|
2
2
|
import sortBy from 'lodash/sortBy'
|
|
3
3
|
import uniq from 'lodash/uniq'
|
|
4
4
|
|
|
5
|
+
import { criteriaProblems } from './criteria-checks'
|
|
5
6
|
import type {
|
|
6
7
|
IntakeOutcome,
|
|
7
8
|
IntakeProblem,
|
|
8
9
|
IntakeRead,
|
|
10
|
+
LeftOutTicket,
|
|
9
11
|
TicketSnapshot,
|
|
10
12
|
} from './intake-schemas'
|
|
11
13
|
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
type EngineConfig,
|
|
16
18
|
} from '../config/engine-config'
|
|
17
19
|
import {
|
|
20
|
+
HUMAN_LABEL,
|
|
18
21
|
READY_LABEL,
|
|
19
22
|
REFACTOR_LABEL,
|
|
20
23
|
RELEASE_LABEL_PREFIX,
|
|
@@ -94,6 +97,88 @@ export const outsideBlockerNumbers = ({
|
|
|
94
97
|
|
|
95
98
|
type Finding = { ticket: number | null; message: string }
|
|
96
99
|
|
|
100
|
+
/** Whether a ticket is for a person (#499): it has `ready-for-human`. */
|
|
101
|
+
export const isForAPerson = ({ issue }: { issue: TrackerIssue }): boolean =>
|
|
102
|
+
issue.labels.includes(HUMAN_LABEL)
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The open tickets intake leaves out of the run (#499), by number: each one
|
|
106
|
+
* for a person, and each other one blocked by one, directly or through
|
|
107
|
+
* other open tickets of the spec. A ticket for a person stops the walk: what
|
|
108
|
+
* blocks it doesn't matter to the tickets that wait on it. Pure.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* leftOutTickets({ open: [agent11, human12, agent13BlockedBy12] })
|
|
112
|
+
* // [{ number: 12, reason: 'for_a_person', ... },
|
|
113
|
+
* // { number: 13, reason: 'waits_on_person', waits_on: [12], through: [], ... }]
|
|
114
|
+
*/
|
|
115
|
+
export const leftOutTickets = ({
|
|
116
|
+
open,
|
|
117
|
+
}: {
|
|
118
|
+
/** The spec's open sub-tickets. */
|
|
119
|
+
open: TrackerIssue[]
|
|
120
|
+
}): LeftOutTicket[] => {
|
|
121
|
+
const byNumber = new Map(open.map((issue) => [issue.number, issue]))
|
|
122
|
+
const blockersOf = (issue: TrackerIssue): TrackerIssue[] =>
|
|
123
|
+
blockerNumbers({ issue }).flatMap((number) => {
|
|
124
|
+
const blocker = byNumber.get(number)
|
|
125
|
+
return blocker === undefined ? [] : [blocker]
|
|
126
|
+
})
|
|
127
|
+
const found = new Map<number, number[]>()
|
|
128
|
+
// The tickets for a person that `issue` waits on. A blocker loop among
|
|
129
|
+
// agent tickets refuses the run, so a loop just stops the walk here.
|
|
130
|
+
const waitsOn = (issue: TrackerIssue, walking: number[]): number[] => {
|
|
131
|
+
const known = found.get(issue.number)
|
|
132
|
+
if (known !== undefined) return known
|
|
133
|
+
if (walking.includes(issue.number)) return []
|
|
134
|
+
const people = sortBy(
|
|
135
|
+
uniq(
|
|
136
|
+
blockersOf(issue).flatMap((blocker) =>
|
|
137
|
+
isForAPerson({ issue: blocker })
|
|
138
|
+
? [blocker.number]
|
|
139
|
+
: waitsOn(blocker, [...walking, issue.number])
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
found.set(issue.number, people)
|
|
144
|
+
return people
|
|
145
|
+
}
|
|
146
|
+
return sortBy(open, 'number').flatMap((issue): LeftOutTicket[] => {
|
|
147
|
+
const { number, title, url } = issue
|
|
148
|
+
if (isForAPerson({ issue })) {
|
|
149
|
+
return [
|
|
150
|
+
{
|
|
151
|
+
number,
|
|
152
|
+
title,
|
|
153
|
+
url,
|
|
154
|
+
reason: 'for_a_person',
|
|
155
|
+
waits_on: [],
|
|
156
|
+
through: [],
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
const waits_on = waitsOn(issue, [])
|
|
161
|
+
if (waits_on.length === 0) return []
|
|
162
|
+
const through = blockersOf(issue)
|
|
163
|
+
.filter(
|
|
164
|
+
(blocker) =>
|
|
165
|
+
!isForAPerson({ issue: blocker }) &&
|
|
166
|
+
waitsOn(blocker, []).length > 0
|
|
167
|
+
)
|
|
168
|
+
.map((blocker) => blocker.number)
|
|
169
|
+
return [
|
|
170
|
+
{
|
|
171
|
+
number,
|
|
172
|
+
title,
|
|
173
|
+
url,
|
|
174
|
+
reason: 'waits_on_person',
|
|
175
|
+
waits_on,
|
|
176
|
+
through: sortBy(through),
|
|
177
|
+
},
|
|
178
|
+
]
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
97
182
|
/**
|
|
98
183
|
* Walks blockers depth-first, blockers before the tickets they block. Returns
|
|
99
184
|
* that order and every loop found, as a path such as `[12, 13, 12]`.
|
|
@@ -142,14 +227,61 @@ const groupFindings = ({
|
|
|
142
227
|
}))
|
|
143
228
|
}
|
|
144
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Intake's checks on the engine config, as messages for a person: a test
|
|
232
|
+
* command, and one with bun's per-test results unless every ticket in
|
|
233
|
+
* `tickets` is a refactor ticket (the red check reads per-test results,
|
|
234
|
+
* which only bun's give today). Pure. Empty when the config is fine.
|
|
235
|
+
*
|
|
236
|
+
* Intake runs it on the open tickets agents build; a resume runs it on the
|
|
237
|
+
* repo's config file before taking its new build fields (#516).
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* configProblems({ config: { ...config, checks: {} }, tickets: [] })
|
|
241
|
+
* // ['The engine config (.luca/config.json) has no test command at checks.test.']
|
|
242
|
+
*/
|
|
243
|
+
export const configProblems = ({
|
|
244
|
+
config,
|
|
245
|
+
tickets,
|
|
246
|
+
}: {
|
|
247
|
+
config: EngineConfig
|
|
248
|
+
tickets: { labels: string[] }[]
|
|
249
|
+
}): string[] => {
|
|
250
|
+
const tests = testCommands({ config })
|
|
251
|
+
if (tests.length === 0) {
|
|
252
|
+
return [
|
|
253
|
+
`The engine config (${ENGINE_CONFIG_FILE}) has no test command at checks.test.`,
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
if (
|
|
257
|
+
!tests.some(({ results }) => results === 'bun') &&
|
|
258
|
+
tickets.some((ticket) => !ticket.labels.includes(REFACTOR_LABEL))
|
|
259
|
+
) {
|
|
260
|
+
return [
|
|
261
|
+
`The engine config (${ENGINE_CONFIG_FILE}) has no test command with bun results at checks.test, so the red check can't prove new tests fail first. Add a \`bun test\` command, or label every open ticket \`${REFACTOR_LABEL}\`.`,
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
return []
|
|
265
|
+
}
|
|
266
|
+
|
|
145
267
|
/**
|
|
146
268
|
* Intake's checks: is the spec, and every open ticket in it, ready to build?
|
|
147
269
|
*
|
|
148
270
|
* Pure. Every miss refuses the whole run, and every problem is collected (not
|
|
149
271
|
* just the first), grouped by the spec or ticket it is on. Config and spec
|
|
150
|
-
* problems refuse even an empty spec; otherwise a spec with
|
|
151
|
-
* has nothing to do. When everything passes, the result is the
|
|
152
|
-
* run builds from, with tickets in blocker order.
|
|
272
|
+
* problems refuse even an empty spec; otherwise a spec with nothing left to
|
|
273
|
+
* build has nothing to do. When everything passes, the result is the
|
|
274
|
+
* snapshot the run builds from, with tickets in blocker order.
|
|
275
|
+
*
|
|
276
|
+
* Tickets for a person (#499): an open ticket with `ready-for-human` (even
|
|
277
|
+
* with `ready-for-agent` too) is left out of the run, unchecked. An agent
|
|
278
|
+
* ticket that waits on one, directly or through other tickets, is left out
|
|
279
|
+
* too, but still checked, so a later run can build it. The run builds the
|
|
280
|
+
* rest. A ticket with neither ready label is still refused.
|
|
281
|
+
*
|
|
282
|
+
* Each open agent ticket's acceptance criteria must hold no placeholders, no
|
|
283
|
+
* untestable asks (#541), and nothing about behaviour that doesn't change
|
|
284
|
+
* (#653), per `criteriaProblems`; the spec's text isn't checked.
|
|
153
285
|
*/
|
|
154
286
|
export const checkIntake = ({
|
|
155
287
|
config,
|
|
@@ -160,28 +292,19 @@ export const checkIntake = ({
|
|
|
160
292
|
}): IntakeOutcome => {
|
|
161
293
|
const { spec, sub_tickets, outside_blockers } = intake_read
|
|
162
294
|
const findings: Finding[] = []
|
|
295
|
+
const open = sortBy(
|
|
296
|
+
sub_tickets.filter((ticket) => ticket.state === 'open'),
|
|
297
|
+
'number'
|
|
298
|
+
)
|
|
299
|
+
const left_out = leftOutTickets({ open })
|
|
300
|
+
const leftOut = new Set(left_out.map(({ number }) => number))
|
|
301
|
+
// Every open ticket an agent builds: in this run, or once a person is done.
|
|
302
|
+
const forAgents = open.filter((issue) => !isForAPerson({ issue }))
|
|
163
303
|
const onSpec = (message: string) =>
|
|
164
304
|
findings.push({ ticket: spec.number, message })
|
|
165
305
|
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
findings.push({
|
|
169
|
-
ticket: null,
|
|
170
|
-
message: `The engine config (${ENGINE_CONFIG_FILE}) has no test command at checks.test.`,
|
|
171
|
-
})
|
|
172
|
-
} else if (
|
|
173
|
-
!tests.some(({ results }) => results === 'bun') &&
|
|
174
|
-
sub_tickets.some(
|
|
175
|
-
(ticket) =>
|
|
176
|
-
ticket.state === 'open' &&
|
|
177
|
-
!ticket.labels.includes(REFACTOR_LABEL)
|
|
178
|
-
)
|
|
179
|
-
) {
|
|
180
|
-
// The red check reads per-test results, which only bun's give today.
|
|
181
|
-
findings.push({
|
|
182
|
-
ticket: null,
|
|
183
|
-
message: `The engine config (${ENGINE_CONFIG_FILE}) has no test command with bun results at checks.test, so the red check can't prove new tests fail first. Add a \`bun test\` command, or label every open ticket \`${REFACTOR_LABEL}\`.`,
|
|
184
|
-
})
|
|
306
|
+
for (const message of configProblems({ config, tickets: forAgents })) {
|
|
307
|
+
findings.push({ ticket: null, message })
|
|
185
308
|
}
|
|
186
309
|
if (spec.state !== 'open') onSpec('The spec is closed.')
|
|
187
310
|
if (section({ body: spec.body, heading: 'Testing Decisions' }) === '') {
|
|
@@ -196,10 +319,6 @@ export const checkIntake = ({
|
|
|
196
319
|
)
|
|
197
320
|
}
|
|
198
321
|
|
|
199
|
-
const open = sortBy(
|
|
200
|
-
sub_tickets.filter((ticket) => ticket.state === 'open'),
|
|
201
|
-
'number'
|
|
202
|
-
)
|
|
203
322
|
const closedTickets = sortBy(
|
|
204
323
|
sub_tickets
|
|
205
324
|
.filter((ticket) => ticket.state === 'closed')
|
|
@@ -211,7 +330,7 @@ export const checkIntake = ({
|
|
|
211
330
|
outside_blockers.map((issue) => [issue.number, issue])
|
|
212
331
|
)
|
|
213
332
|
|
|
214
|
-
const tickets: TicketSnapshot[] =
|
|
333
|
+
const tickets: TicketSnapshot[] = forAgents.map((ticket) => {
|
|
215
334
|
const onTicket = (message: string) =>
|
|
216
335
|
findings.push({ ticket: ticket.number, message })
|
|
217
336
|
if (section({ body: ticket.body, heading: 'What to build' }) === '') {
|
|
@@ -228,6 +347,12 @@ export const checkIntake = ({
|
|
|
228
347
|
if (criteria.length === 0) {
|
|
229
348
|
onTicket('The ticket has no checkbox under "Acceptance criteria".')
|
|
230
349
|
}
|
|
350
|
+
for (const message of criteriaProblems({
|
|
351
|
+
ticket: ticket.number,
|
|
352
|
+
criteria,
|
|
353
|
+
})) {
|
|
354
|
+
onTicket(message)
|
|
355
|
+
}
|
|
231
356
|
if (!ticket.labels.includes(READY_LABEL)) {
|
|
232
357
|
onTicket(`The ticket does not have the ${READY_LABEL} label.`)
|
|
233
358
|
}
|
|
@@ -273,10 +398,18 @@ export const checkIntake = ({
|
|
|
273
398
|
if (findings.length > 0) {
|
|
274
399
|
return { outcome: 'refused', problems: groupFindings({ findings }) }
|
|
275
400
|
}
|
|
276
|
-
|
|
277
|
-
|
|
401
|
+
const byNumber = new Map(
|
|
402
|
+
tickets
|
|
403
|
+
.filter((ticket) => !leftOut.has(ticket.number))
|
|
404
|
+
.map((ticket) => [ticket.number, ticket])
|
|
405
|
+
)
|
|
406
|
+
if (byNumber.size === 0) {
|
|
407
|
+
return {
|
|
408
|
+
outcome: 'nothing_to_do',
|
|
409
|
+
closed_tickets: closedTickets,
|
|
410
|
+
left_out,
|
|
411
|
+
}
|
|
278
412
|
}
|
|
279
|
-
const byNumber = new Map(tickets.map((ticket) => [ticket.number, ticket]))
|
|
280
413
|
return {
|
|
281
414
|
outcome: 'passed',
|
|
282
415
|
snapshot: {
|
|
@@ -290,6 +423,7 @@ export const checkIntake = ({
|
|
|
290
423
|
},
|
|
291
424
|
tickets: order.flatMap((number) => byNumber.get(number) ?? []),
|
|
292
425
|
closed_tickets: closedTickets,
|
|
426
|
+
left_out,
|
|
293
427
|
},
|
|
294
428
|
}
|
|
295
429
|
}
|
|
@@ -62,11 +62,41 @@ export const TicketSnapshotSchema = z.object({
|
|
|
62
62
|
|
|
63
63
|
export type TicketSnapshot = z.infer<typeof TicketSnapshotSchema>
|
|
64
64
|
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Why intake left an open ticket out of the run (#499): `for_a_person`, as
|
|
67
|
+
* it has the `ready-for-human` label, or `waits_on_person`, as it is
|
|
68
|
+
* blocked, directly or through other tickets, by an open one that has.
|
|
69
|
+
*/
|
|
70
|
+
export const LeftOutReasonSchema = z.enum(['for_a_person', 'waits_on_person'])
|
|
71
|
+
|
|
72
|
+
export type LeftOutReason = z.infer<typeof LeftOutReasonSchema>
|
|
73
|
+
|
|
74
|
+
/** An open ticket intake left out of the run, and why (#499). */
|
|
75
|
+
export const LeftOutTicketSchema = z.object({
|
|
76
|
+
number: z.number().int().positive(),
|
|
77
|
+
title: z.string(),
|
|
78
|
+
url: z.string().default(''),
|
|
79
|
+
reason: LeftOutReasonSchema,
|
|
80
|
+
/** For `waits_on_person`: the tickets for a person it waits on. */
|
|
81
|
+
waits_on: z.array(z.number().int().positive()).default([]),
|
|
82
|
+
/**
|
|
83
|
+
* For `waits_on_person`: its own blockers that wait on a person too,
|
|
84
|
+
* when it waits through them rather than (or as well as) directly.
|
|
85
|
+
*/
|
|
86
|
+
through: z.array(z.number().int().positive()).default([]),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
export type LeftOutTicket = z.infer<typeof LeftOutTicketSchema>
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The spec and its open tickets, in blocker order (blockers first), and
|
|
93
|
+
* the open tickets left out for a person (#499), by number.
|
|
94
|
+
*/
|
|
66
95
|
export const IntakeSnapshotSchema = z.object({
|
|
67
96
|
spec: SpecSnapshotSchema,
|
|
68
97
|
tickets: z.array(TicketSnapshotSchema),
|
|
69
98
|
closed_tickets: z.array(z.number().int().positive()),
|
|
99
|
+
left_out: z.array(LeftOutTicketSchema).default([]),
|
|
70
100
|
})
|
|
71
101
|
|
|
72
102
|
export type IntakeSnapshot = z.infer<typeof IntakeSnapshotSchema>
|
|
@@ -74,5 +104,10 @@ export type IntakeSnapshot = z.infer<typeof IntakeSnapshotSchema>
|
|
|
74
104
|
/** The result of intake's checks. */
|
|
75
105
|
export type IntakeOutcome =
|
|
76
106
|
| { outcome: 'refused'; problems: IntakeProblem[] }
|
|
77
|
-
| {
|
|
107
|
+
| {
|
|
108
|
+
outcome: 'nothing_to_do'
|
|
109
|
+
closed_tickets: number[]
|
|
110
|
+
/** Open tickets left out for a person (#499): none left to build. */
|
|
111
|
+
left_out: LeftOutTicket[]
|
|
112
|
+
}
|
|
78
113
|
| { outcome: 'passed'; snapshot: IntakeSnapshot }
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import mapValues from 'lodash/mapValues'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import type { JevAnswer, JevFailureReason, JevRequest } from './jev-schemas'
|
|
5
|
+
|
|
6
|
+
/** Workers AI's REST API: `<base>/<account id>/ai/run/<model id>`. */
|
|
7
|
+
const WORKERS_AI_ACCOUNTS_URL = 'https://api.cloudflare.com/client/v4/accounts'
|
|
8
|
+
|
|
9
|
+
/** How much of an error body a failed call keeps. */
|
|
10
|
+
const ERROR_SNIPPET_CHARS = 300
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The decision model's answers by question id, and the model that gave
|
|
14
|
+
* them; or why there are none. Never a throw.
|
|
15
|
+
*/
|
|
16
|
+
export type DecisionModelReply =
|
|
17
|
+
| {
|
|
18
|
+
ok: true
|
|
19
|
+
answers: Record<string, JevAnswer>
|
|
20
|
+
/** The model that answered; left out, the client's own. */
|
|
21
|
+
model?: string
|
|
22
|
+
}
|
|
23
|
+
| { ok: false; reason: JevFailureReason; error: string }
|
|
24
|
+
|
|
25
|
+
/** Asks the decision model one request. The signal cuts the call short. */
|
|
26
|
+
export type DecisionModelClient = {
|
|
27
|
+
/** The model it asks, such as `@cf/cloudflare/clef`; journaled on each ask. */
|
|
28
|
+
model: string
|
|
29
|
+
ask: (args: {
|
|
30
|
+
request: JevRequest
|
|
31
|
+
signal: AbortSignal
|
|
32
|
+
}) => Promise<DecisionModelReply>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The part of `fetch` the client uses, so tests can hand in a fake. */
|
|
36
|
+
export type DecisionModelFetch = (
|
|
37
|
+
url: string,
|
|
38
|
+
init: RequestInit
|
|
39
|
+
) => Promise<Response>
|
|
40
|
+
|
|
41
|
+
/** The Cloudflare account and Workers AI token the client calls with. */
|
|
42
|
+
export type ClefCredentials = { account_id: string; api_token: string }
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Clef's output: its answers by question id, and the model's short name.
|
|
46
|
+
* Workers AI wraps it as `{ result, success, errors, messages }`, so it is
|
|
47
|
+
* read inside `result` or bare.
|
|
48
|
+
*/
|
|
49
|
+
const ClefOutputSchema = z.object({
|
|
50
|
+
model: z.string().min(1).nullish().catch(null),
|
|
51
|
+
answers: z.record(z.string(), z.unknown()),
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const EnvelopeSchema = z.object({ result: ClefOutputSchema })
|
|
55
|
+
|
|
56
|
+
/** A Cloudflare v4 error body: `{ success: false, errors: [{ code, message }] }`. */
|
|
57
|
+
const ErrorEnvelopeSchema = z.object({
|
|
58
|
+
errors: z.array(z.object({ message: z.string() })).min(1),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The answer fields the engine knows: Clef's `choice` (a criteria key),
|
|
63
|
+
* `score` (a place on the scale), and `noul` (the probability of yes), each
|
|
64
|
+
* with a `confidence` except noul. Jev's `probability` and a plain `value`
|
|
65
|
+
* read too. A field that doesn't fit falls back to `null`.
|
|
66
|
+
*/
|
|
67
|
+
const LooseAnswerSchema = z.object({
|
|
68
|
+
choice: z.string().nullish().catch(null),
|
|
69
|
+
score: z.union([z.string(), z.number()]).nullish().catch(null),
|
|
70
|
+
noul: z.number().nullish().catch(null),
|
|
71
|
+
probability: z.number().nullish().catch(null),
|
|
72
|
+
value: z.union([z.string(), z.number()]).nullish().catch(null),
|
|
73
|
+
confidence: z.number().nullish().catch(null),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const normalizeAnswer = (raw: unknown): JevAnswer => {
|
|
77
|
+
const parsed = LooseAnswerSchema.safeParse(raw)
|
|
78
|
+
if (!parsed.success) return { value: null, confidence: null, raw }
|
|
79
|
+
const { choice, score, noul, probability, value, confidence } = parsed.data
|
|
80
|
+
return {
|
|
81
|
+
value: choice ?? score ?? noul ?? probability ?? value ?? null,
|
|
82
|
+
confidence: confidence ?? null,
|
|
83
|
+
raw,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const isAbort = ({
|
|
88
|
+
error,
|
|
89
|
+
signal,
|
|
90
|
+
}: {
|
|
91
|
+
error: unknown
|
|
92
|
+
signal: AbortSignal
|
|
93
|
+
}): boolean =>
|
|
94
|
+
signal.aborted ||
|
|
95
|
+
(error instanceof Error &&
|
|
96
|
+
(error.name === 'AbortError' || error.name === 'TimeoutError'))
|
|
97
|
+
|
|
98
|
+
/** Removes the token from any text, so it never lands in the journal. */
|
|
99
|
+
const hideToken = ({ text, token }: { text: string; token: string }): string =>
|
|
100
|
+
text.split(token).join('[token]')
|
|
101
|
+
|
|
102
|
+
const parseJson = (text: string): unknown => {
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(text)
|
|
105
|
+
} catch {
|
|
106
|
+
return undefined
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** What a failed call's body says: Cloudflare's error messages, or its start. */
|
|
111
|
+
const errorBodyText = (text: string): string => {
|
|
112
|
+
const envelope = ErrorEnvelopeSchema.safeParse(parseJson(text))
|
|
113
|
+
return envelope.success
|
|
114
|
+
? envelope.data.errors.map(({ message }) => message).join('; ')
|
|
115
|
+
: text.slice(0, ERROR_SNIPPET_CHARS)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The URL that runs `model` on Workers AI for `account_id`. The model id's
|
|
120
|
+
* `@` and `/` stay as they are, as Cloudflare's docs write them. Pure.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* clefUrl({ account_id: 'abc', model: '@cf/cloudflare/clef' })
|
|
124
|
+
* // 'https://api.cloudflare.com/client/v4/accounts/abc/ai/run/@cf/cloudflare/clef'
|
|
125
|
+
*/
|
|
126
|
+
export const clefUrl = ({
|
|
127
|
+
account_id,
|
|
128
|
+
model,
|
|
129
|
+
}: {
|
|
130
|
+
account_id: string
|
|
131
|
+
model: string
|
|
132
|
+
}): string =>
|
|
133
|
+
`${WORKERS_AI_ACCOUNTS_URL}/${encodeURIComponent(account_id)}/ai/run/${model}`
|
|
134
|
+
|
|
135
|
+
/** The name Clef wants in the body: the model id's last part, `clef` or `clef-flash`. */
|
|
136
|
+
const shortName = (model: string): string => model.split('/').at(-1) ?? model
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The decision model client for Cloudflare's **Clef** on Workers AI (#534).
|
|
140
|
+
* It never throws: missing credentials, a timeout, a bad status, or a bad
|
|
141
|
+
* body each come back as a failed reply, and no error text ever holds the
|
|
142
|
+
* token. HTTP 401 and 403 are `rejected` (the token is wrong or lacks Workers
|
|
143
|
+
* AI), which turns the decision model off for the run; anything else is an
|
|
144
|
+
* `error` or a `timeout`, and the next ask tries again.
|
|
145
|
+
*
|
|
146
|
+
* @param credentials - The account id and token (see
|
|
147
|
+
* `loadDecisionModelCredentials`); `null` or an empty one is
|
|
148
|
+
* `missing_credentials`, and then `fetch` is never called.
|
|
149
|
+
* @param model - The Workers AI model id, such as `@cf/cloudflare/clef`.
|
|
150
|
+
* @param fetch - Defaults to the global `fetch`.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* const clef = createClefClient({ credentials: { account_id, api_token }, model: '@cf/cloudflare/clef' })
|
|
154
|
+
* const reply = await clef.ask({ request, signal: AbortSignal.timeout(10_000) })
|
|
155
|
+
* if (reply.ok) console.log(reply.answers)
|
|
156
|
+
*/
|
|
157
|
+
export const createClefClient = ({
|
|
158
|
+
credentials,
|
|
159
|
+
model,
|
|
160
|
+
fetch: fetchClef,
|
|
161
|
+
}: {
|
|
162
|
+
credentials: ClefCredentials | null
|
|
163
|
+
model: string
|
|
164
|
+
fetch?: DecisionModelFetch
|
|
165
|
+
}): DecisionModelClient => ({
|
|
166
|
+
model,
|
|
167
|
+
ask: async ({ request, signal }) => {
|
|
168
|
+
if (
|
|
169
|
+
credentials === null ||
|
|
170
|
+
credentials.account_id === '' ||
|
|
171
|
+
credentials.api_token === ''
|
|
172
|
+
) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
reason: 'missing_credentials',
|
|
176
|
+
error: 'No Cloudflare account id or token is set.',
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const token = credentials.api_token
|
|
180
|
+
try {
|
|
181
|
+
const response = await (fetchClef ?? fetch)(
|
|
182
|
+
clefUrl({ account_id: credentials.account_id, model }),
|
|
183
|
+
{
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: {
|
|
186
|
+
authorization: `Bearer ${token}`,
|
|
187
|
+
'content-type': 'application/json',
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
model: shortName(model),
|
|
191
|
+
state: request.state,
|
|
192
|
+
questions: request.questions,
|
|
193
|
+
}),
|
|
194
|
+
signal,
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
const text = await response.text()
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
const said = hideToken({ text: errorBodyText(text), token })
|
|
200
|
+
const rejected =
|
|
201
|
+
response.status === 401 || response.status === 403
|
|
202
|
+
return rejected
|
|
203
|
+
? {
|
|
204
|
+
ok: false,
|
|
205
|
+
reason: 'rejected',
|
|
206
|
+
error: `Cloudflare turned the token down (HTTP ${response.status}): ${said}`,
|
|
207
|
+
}
|
|
208
|
+
: {
|
|
209
|
+
ok: false,
|
|
210
|
+
reason: 'error',
|
|
211
|
+
error: `Clef answered ${response.status}: ${said}`,
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const json = parseJson(text)
|
|
215
|
+
if (json === undefined) {
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
reason: 'error',
|
|
219
|
+
error: 'Clef answered with a body that is not JSON.',
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const wrapped = EnvelopeSchema.safeParse(json)
|
|
223
|
+
const output = wrapped.success
|
|
224
|
+
? { success: true as const, data: wrapped.data.result }
|
|
225
|
+
: ClefOutputSchema.safeParse(json)
|
|
226
|
+
if (!output.success) {
|
|
227
|
+
return {
|
|
228
|
+
ok: false,
|
|
229
|
+
reason: 'error',
|
|
230
|
+
error: hideToken({
|
|
231
|
+
text: `Clef answered with no answers: ${z.prettifyError(output.error)}`,
|
|
232
|
+
token,
|
|
233
|
+
}),
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
model: output.data.model ?? model,
|
|
239
|
+
answers: mapValues(output.data.answers, normalizeAnswer),
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (isAbort({ error, signal })) {
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
reason: 'timeout',
|
|
246
|
+
error: 'Clef did not answer in time.',
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
ok: false,
|
|
251
|
+
reason: 'error',
|
|
252
|
+
error: hideToken({
|
|
253
|
+
text: `Clef call failed: ${String(error)}`,
|
|
254
|
+
token,
|
|
255
|
+
}),
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
})
|