@catalyst-cloud/cli 0.15.2 → 0.15.3
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 +7 -0
- package/README.md +2 -2
- package/dist/cli.js +5 -17
- package/dist/oauth.js +1 -1
- package/dist/onboard-accounts.js +67 -171
- package/dist/onboard-checklist.js +309 -0
- package/dist/onboard-existing.js +1 -1
- package/dist/onboard-first-ticket.js +199 -0
- package/dist/onboard-headless.js +2 -2
- package/dist/onboard-housekeeping.js +93 -0
- package/dist/onboard-next.js +108 -9
- package/dist/onboard-ready.js +61 -0
- package/dist/onboard-runtime.js +60 -27
- package/dist/onboard-standalone-copy.js +73 -66
- package/dist/onboard-ui.js +162 -89
- package/dist/onboard-values.js +47 -0
- package/dist/onboard.js +49 -13
- package/dist/ready-copy.js +87 -0
- package/dist/ready.js +1 -57
- package/dist/setup-onboard-copy.js +237 -41
- package/dist/setup-render.js +73 -16
- package/dist/setup.js +6 -3
- package/package.json +1 -1
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +3 -3
- package/skills/catalyst-onboard/references/replacing-a-credential.md +9 -15
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -11
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -1
- package/skills/run-this-project/SKILL.md +2 -2
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +2 -2
- package/skills/whats-happening/references/coding-accounts.md +1 -1
- package/skills/whats-happening/scripts/snapshot.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.3
|
|
4
|
+
|
|
5
|
+
- Setup can finish once the steps needed to run work are ready. It lists optional next steps and keeps your first-ticket choice when you resume.
|
|
6
|
+
- Setup shows one progress display with numbered steps and the reason a step needs attention.
|
|
7
|
+
- When the Connect accounts page is available, setup uses it to finish account connections. Any usable AI account counts without a test request.
|
|
8
|
+
- Setup offers API keys by default. Subscription account options appear only when your workspace enables them.
|
|
9
|
+
|
|
3
10
|
## 0.15.2
|
|
4
11
|
|
|
5
12
|
- `catalyst setup` is the command to set up Catalyst. It runs the same steps as `catalyst onboard`, which still works.
|
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ The command is `catalyst`. `catalyst-skills` still works as a deprecated alias,
|
|
|
111
111
|
|
|
112
112
|
## What this is
|
|
113
113
|
|
|
114
|
-
A set of skills that let your coding agent run your own Catalyst Cloud account (https://staging.catalystcloud.dev) from your seat: what is happening, what needs you, and what to do about it. They read your account through the Catalyst Cloud SDK, write to it through the account's agent proxy, and never compose a URL or run a tool of their own; every read, write and
|
|
114
|
+
A set of skills that let your coding agent run your own Catalyst Cloud account (https://staging.catalystcloud.dev) from your seat: what is happening, what needs you, and what to do about it. They read your account through the Catalyst Cloud SDK, write to it through the account's agent proxy, and never compose a URL or run a tool of their own; every read, write and live watch is a `catalyst` verb with `--help`. Every skill is plain Markdown under `skills/<name>/SKILL.md` in this repository, and your own login — a keyless device-code session, or a personal key — is the only credential. Your agent acts as you: the asks it raises and the comments it posts carry your name, and "what needs me" means you.
|
|
115
115
|
|
|
116
116
|
## What connecting does
|
|
117
117
|
|
|
@@ -231,7 +231,7 @@ Some of these only read, and some write: a comment, a card move, an ask, a relea
|
|
|
231
231
|
|
|
232
232
|
## What a key cannot see yet
|
|
233
233
|
|
|
234
|
-
Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst explain --history <ticket>`: phase attempts, remediation rounds, park state) and coding-account status (`catalyst accounts`: provider, declared and observed state,
|
|
234
|
+
Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst explain --history <ticket>`: phase attempts, remediation rounds, park state) and coding-account status (`catalyst accounts`: provider, declared and observed state, usage limits, walls, quarantine — never a credential; enrolling or pausing one is `<your cloud>/settings/coding-accounts`). It also releases a parked or held ticket once its cause is fixed: `catalyst release <ticket> --because <what changed>` (the `unstick` skill runs it), recorded against your name and shown in the ticket's history. And it declares what your containers need: `catalyst environment` reads the account-wide declaration, `environment propose --file <path> --approve` proposes and approves it in one compare-and-set, and the values behind the names stay in the cloud — reading needs any active seat, proposing and approving need an admin or owner one. An admin or owner can put a repository's values in from the terminal. `catalyst secret import .env --repo owner/name` stores every name in the file and lists the declared names that still have no value. `catalyst secret set NAME --repo owner/name --command 'op read op://Vault/item/field'` runs the command on your machine and stores its output; with no `--command` it reads the value from stdin, or asks for it without echoing. No value is ever printed, and the cloud's audit records the command, not its output. That is the account's own declaration — a repository's own `.catalyst/catalyst.toml`, which `catalyst-onboard` already reports on, is a separate thing. `catalyst env inventory` lists the environment variable names a repository needs — never a value; `env check` checks the TOML syntax and environment variable table in `.catalyst/catalyst.toml` offline; `env migrate [catalyst.env.json]` prints a TOML environment table from a legacy declaration, with all names optional and values omitted. These commands need no login (the `what-this-repo-needs` skill walks through them). Two things it cannot do, and the skills say so by name rather than guess:
|
|
235
235
|
|
|
236
236
|
- Read pull-request labels or the reviewer's reaction. The mirror does not carry them; GitHub's own page does.
|
|
237
237
|
- Compute a flow number — cycle time, throughput, or how long pull requests have been open. Nothing serves those yet, so say they are not computed rather than counting something else and calling it that.
|
package/dist/cli.js
CHANGED
|
@@ -44,12 +44,12 @@ import { cmdRepo } from "./repo.js";
|
|
|
44
44
|
import { cmdLegacy } from "./legacy.js";
|
|
45
45
|
import { cmdIdentity } from "./identity.js";
|
|
46
46
|
import { cmdConnections } from "./connections.js";
|
|
47
|
-
import { cmdOnboard, onboardErrorJournal,
|
|
47
|
+
import { cmdOnboard, onboardErrorJournal, } from "./onboard.js";
|
|
48
48
|
import { createClackOnboardUi, shouldUseOnboardUi, } from "./onboard-ui.js";
|
|
49
49
|
import { createOnboardRuntime } from "./onboard-runtime.js";
|
|
50
50
|
import { argvRequestsHeadless, headlessOnboardDeps, headlessUsageRefusal, onboardHeadlessRequested, runOnboardHeadless, } from "./onboard-headless.js";
|
|
51
51
|
import { savedOnboardBaseUrl } from "./onboard-next.js";
|
|
52
|
-
import { onboardingReadyReport, observeCloudOnboarding, } from "./onboard-ready.js";
|
|
52
|
+
import { onboardingReadyReport, onboardReadyStep, observeCloudOnboarding, } from "./onboard-ready.js";
|
|
53
53
|
import { selectedOnboardTeam } from "./onboard-existing.js";
|
|
54
54
|
export { CONFIG_MODE, DEFAULT_BASE_URL, FIRST_STAMPED_VERSION, LEGACY_PACKAGE_NAME, PACKAGE_NAME, PROVENANCE_MARKER, CliError, MeError, UsageError, configPathFor, contractPathFor, defaultCtx, defaultReplicaDbFor, defaultSkillsDirFor, fetchMe, formatMode, installedBundleVersion, installSkills, loadConfig, modernCliPath, normalizeBaseUrl, parseArgs, parseChangelogEntry, parseProvenanceVersion, readChangelog, readManifest, resolveSkillsDir, saveConfig, skillsSourceDir, updateNoticeLine, writeConfig, };
|
|
55
55
|
export const CUSTOMER_SKILLS = [
|
|
@@ -306,6 +306,7 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
306
306
|
renderer: createSetupRenderer(process.stdout, ctx.env),
|
|
307
307
|
disposeRenderer: true,
|
|
308
308
|
signinTimeoutMs: deps.onboardSigninTimeoutMs,
|
|
309
|
+
version: manifest.version,
|
|
309
310
|
});
|
|
310
311
|
}
|
|
311
312
|
catch (error) {
|
|
@@ -328,6 +329,7 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
328
329
|
renderer: createSetupRenderer(output, ctx.env),
|
|
329
330
|
disposeRenderer: true,
|
|
330
331
|
signinTimeoutMs: deps.onboardSigninTimeoutMs,
|
|
332
|
+
version: manifest.version,
|
|
331
333
|
interactive: false,
|
|
332
334
|
verbose: args.flags.verbose === true,
|
|
333
335
|
});
|
|
@@ -395,21 +397,7 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
395
397
|
localSync: journal?.localSync ?? args.flags["local-sync"] === true,
|
|
396
398
|
observe: observeCloudOnboarding,
|
|
397
399
|
});
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
state: report.state === "complete"
|
|
401
|
-
? "done"
|
|
402
|
-
: failed
|
|
403
|
-
? "failed"
|
|
404
|
-
: "waiting",
|
|
405
|
-
reason: report.state === "complete"
|
|
406
|
-
? undefined
|
|
407
|
-
: "onboarding_checks_pending",
|
|
408
|
-
evidence: {
|
|
409
|
-
checks: report.checks.length,
|
|
410
|
-
passed: report.checks.filter((check) => check.state === "pass").length,
|
|
411
|
-
},
|
|
412
|
-
};
|
|
400
|
+
return onboardReadyStep(report, journal);
|
|
413
401
|
},
|
|
414
402
|
legacy: deps.legacy,
|
|
415
403
|
openBrowser: deps.openBrowser,
|
package/dist/oauth.js
CHANGED
|
@@ -156,7 +156,7 @@ export async function deviceFlowLogin(ctx, baseUrl, deps = {}) {
|
|
|
156
156
|
throw new Error("browser-unavailable");
|
|
157
157
|
await deps.openBrowser(auth.verification_uri_complete ?? auth.verification_uri);
|
|
158
158
|
browserOpened = true;
|
|
159
|
-
ctx.stdout("Opened your browser to that page
|
|
159
|
+
ctx.stdout("Opened your browser to that page. Approve there, or use the code above.");
|
|
160
160
|
}
|
|
161
161
|
catch {
|
|
162
162
|
// a browser that will not open is not a failure; the code and URL still work
|
package/dist/onboard-accounts.js
CHANGED
|
@@ -3,7 +3,6 @@ import { readExistingOnboardJson } from "./onboard-existing.js";
|
|
|
3
3
|
import { pollConsent } from "./onboard-consent.js";
|
|
4
4
|
import { verifyOnboardRoutes } from "./onboard-capabilities.js";
|
|
5
5
|
const listPath = "/api/v1/coding-accounts";
|
|
6
|
-
const validatePath = "/api/v1/coding-accounts/:slot/validate";
|
|
7
6
|
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
8
7
|
? value
|
|
9
8
|
: null;
|
|
@@ -94,195 +93,92 @@ async function inventory(ctx, journal, signal) {
|
|
|
94
93
|
}
|
|
95
94
|
return { slots, identity };
|
|
96
95
|
}
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
96
|
+
/** Why one account cannot take work, or null when it can. The Connect page's AI-account rule
|
|
97
|
+
* (the server's isUsableCodingAccount): any provider, any member's account, and a canceled
|
|
98
|
+
* subscription keeps working until its paid access ends. */
|
|
99
|
+
function unusableCause(row, now) {
|
|
100
|
+
if (row.declaredState !== "active")
|
|
101
|
+
return "coding_account_inactive";
|
|
102
|
+
if (row.revokedAtMs !== null ||
|
|
103
|
+
(row.accessEndsAtMs !== null && row.accessEndsAtMs <= now))
|
|
104
|
+
return "coding_account_ended";
|
|
105
|
+
if (row.needsCredential)
|
|
106
|
+
return "coding_account_needs_login";
|
|
107
|
+
if (row.quarantined)
|
|
108
|
+
return "coding_account_quarantined";
|
|
109
|
+
if (row.walled)
|
|
110
|
+
return "coding_account_walled";
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
export function usableAiAccount(row, now) {
|
|
114
|
+
return unusableCause(row, now) === null;
|
|
115
|
+
}
|
|
116
|
+
/** CTC-4680: the step is done when the workspace has at least one usable AI account, from any
|
|
117
|
+
* provider. With --coding-account, that named account is the one that must be usable. */
|
|
99
118
|
export function onboardAccountsAdapter(input = {}) {
|
|
100
|
-
|
|
101
|
-
let attempted = false;
|
|
102
|
-
const candidate = (slots, now) => slots
|
|
119
|
+
const usable = (slots, now) => slots
|
|
103
120
|
.filter((row) => (input.slot === undefined || row.accountSlot === input.slot) &&
|
|
104
|
-
row
|
|
105
|
-
row.declaredState === "active" &&
|
|
106
|
-
row.ownedByMe &&
|
|
107
|
-
!row.needsCredential &&
|
|
108
|
-
!row.walled &&
|
|
109
|
-
!row.quarantined &&
|
|
110
|
-
row.renewalStatus !== "canceled" &&
|
|
111
|
-
row.revokedAtMs === null &&
|
|
112
|
-
(row.accessEndsAtMs === null || row.accessEndsAtMs > now))
|
|
121
|
+
usableAiAccount(row, now))
|
|
113
122
|
.sort((a, b) => a.accountSlot.localeCompare(b.accountSlot))[0];
|
|
114
123
|
const check = async (ctx, journal, signal) => {
|
|
115
124
|
const live = await inventory(ctx, journal, signal);
|
|
116
125
|
if ("reason" in live)
|
|
117
126
|
return waiting(live.reason);
|
|
118
|
-
const selected =
|
|
119
|
-
if (
|
|
120
|
-
equal(live.identity, proof) &&
|
|
121
|
-
live.slots.some((row) => row.accountSlot === proof.slot &&
|
|
122
|
-
row.provider === proof.provider &&
|
|
123
|
-
row.declaredState === "active" &&
|
|
124
|
-
row.ownedByMe &&
|
|
125
|
-
!row.needsCredential &&
|
|
126
|
-
!row.walled &&
|
|
127
|
-
!row.quarantined &&
|
|
128
|
-
row.renewalStatus !== "canceled" &&
|
|
129
|
-
row.revokedAtMs === null &&
|
|
130
|
-
(row.accessEndsAtMs === null ||
|
|
131
|
-
row.accessEndsAtMs > ctx.now().getTime())) &&
|
|
132
|
-
proof.checkedAtMs <= ctx.now().getTime() + 5000 &&
|
|
133
|
-
proof.checkedAtMs >= ctx.now().getTime() - 30000)
|
|
127
|
+
const selected = usable(live.slots, ctx.now().getTime());
|
|
128
|
+
if (selected)
|
|
134
129
|
return {
|
|
135
130
|
state: "done",
|
|
136
131
|
evidence: {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
checkedAt: new Date(proof.checkedAtMs).toISOString(),
|
|
132
|
+
provider: selected.provider,
|
|
133
|
+
checkedAt: ctx.now().toISOString(),
|
|
140
134
|
},
|
|
141
135
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
?
|
|
147
|
-
:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
? "codex_provider_access_unverified"
|
|
154
|
-
: "account_provider_access_unverified");
|
|
155
|
-
if (attempted)
|
|
156
|
-
return waiting("account_provider_access_unverified");
|
|
157
|
-
const cfg = loadConfig(ctx.home);
|
|
158
|
-
if (!cfg?.user || !["owner", "admin"].includes(cfg.user.role))
|
|
159
|
-
return waiting("account_validation_admin_required");
|
|
160
|
-
return { state: "pending" };
|
|
136
|
+
if (input.slot !== undefined) {
|
|
137
|
+
const named = live.slots.find((row) => row.accountSlot === input.slot);
|
|
138
|
+
// A named account speaks for itself: other accounts in the workspace may be fine.
|
|
139
|
+
return waiting(named
|
|
140
|
+
? (unusableCause(named, ctx.now().getTime()) ?? "coding_account_not_found")
|
|
141
|
+
: "coding_account_not_found");
|
|
142
|
+
}
|
|
143
|
+
if (input.waitForAccount &&
|
|
144
|
+
["owner", "admin"].includes(live.identity.role))
|
|
145
|
+
return { state: "pending" };
|
|
146
|
+
return waiting(live.slots.length ? "ai_account_not_usable" : "account_enrollment_required");
|
|
161
147
|
};
|
|
162
148
|
return {
|
|
163
149
|
check,
|
|
164
150
|
act: async (ctx, journal, external) => {
|
|
165
|
-
if (attempted)
|
|
166
|
-
return waiting("account_provider_access_unverified");
|
|
167
151
|
const initial = await inventory(ctx, journal, external);
|
|
168
152
|
if ("reason" in initial)
|
|
169
153
|
return waiting(initial.reason);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (!equal(binding(ctx, journal), live.identity) ||
|
|
199
|
-
!cfg?.user ||
|
|
200
|
-
!["owner", "admin"].includes(cfg.user.role))
|
|
201
|
-
return waiting("account_identity_unverified");
|
|
202
|
-
const expiry = cfg.auth
|
|
203
|
-
? Date.parse(cfg.auth.expiresAt) - ctx.now().getTime()
|
|
204
|
-
: 0;
|
|
205
|
-
const bearer = cfg.key ||
|
|
206
|
-
(cfg.auth && Number.isFinite(expiry) && expiry > 30000
|
|
207
|
-
? cfg.auth.accessToken
|
|
208
|
-
: undefined);
|
|
209
|
-
if (!bearer)
|
|
210
|
-
return waiting("account_login_refresh_required");
|
|
211
|
-
input.message?.("Checking one stored Claude account with a one-token provider request. This may use Claude quota. Codex credentials are not refreshed. Phase eligibility is checked separately.");
|
|
212
|
-
// A display callback must not be able to switch the identity used for this request.
|
|
213
|
-
if (!equal(binding(ctx, journal), live.identity))
|
|
214
|
-
return waiting("account_identity_unverified");
|
|
215
|
-
const deadline = new AbortController();
|
|
216
|
-
const signal = external
|
|
217
|
-
? AbortSignal.any([external, deadline.signal])
|
|
218
|
-
: deadline.signal;
|
|
219
|
-
const timer = setTimeout(() => deadline.abort(), 15000);
|
|
220
|
-
let remove = () => { };
|
|
221
|
-
try {
|
|
222
|
-
return await new Promise((resolve) => {
|
|
223
|
-
const stopped = () => resolve(waiting(external?.aborted
|
|
224
|
-
? "interrupted"
|
|
225
|
-
: "account_validation_unavailable"));
|
|
226
|
-
if (signal.aborted) {
|
|
227
|
-
stopped();
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
signal.addEventListener("abort", stopped, { once: true });
|
|
231
|
-
remove = () => signal.removeEventListener("abort", stopped);
|
|
232
|
-
Promise.resolve()
|
|
233
|
-
.then(async () => {
|
|
234
|
-
if (signal.aborted)
|
|
235
|
-
return waiting("interrupted");
|
|
236
|
-
// UI callbacks can queue a config change before this deferred send executes.
|
|
237
|
-
if (!equal(binding(ctx, journal), live.identity))
|
|
238
|
-
return waiting("account_identity_unverified");
|
|
239
|
-
attempted = true;
|
|
240
|
-
const response = await ctx.fetch(`${support.origin}/api/v1/coding-accounts/${encodeURIComponent(selected.accountSlot)}/validate`, {
|
|
241
|
-
method: "POST",
|
|
242
|
-
redirect: "error",
|
|
243
|
-
signal,
|
|
244
|
-
headers: {
|
|
245
|
-
authorization: `Bearer ${bearer}`,
|
|
246
|
-
accept: "application/json",
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
if (response.status !== 200)
|
|
250
|
-
return waiting("account_validation_unavailable");
|
|
251
|
-
const body = object(await response.json());
|
|
252
|
-
if (signal.aborted ||
|
|
253
|
-
!equal(binding(ctx, journal), live.identity))
|
|
254
|
-
return waiting("account_identity_unverified");
|
|
255
|
-
const now = ctx.now().getTime();
|
|
256
|
-
if (!body ||
|
|
257
|
-
body.provider !== selected.provider ||
|
|
258
|
-
typeof body.checkedAtMs !== "number" ||
|
|
259
|
-
!Number.isSafeInteger(body.checkedAtMs) ||
|
|
260
|
-
body.checkedAtMs > now + 5000 ||
|
|
261
|
-
body.checkedAtMs < now - 30000 ||
|
|
262
|
-
!["working", "walled", "rejected", "inconclusive"].includes(String(body.result)))
|
|
263
|
-
return waiting("account_validation_unverified");
|
|
264
|
-
if (body.result !== "working")
|
|
265
|
-
return waiting(body.result === "walled"
|
|
266
|
-
? "account_provider_walled"
|
|
267
|
-
: body.result === "rejected"
|
|
268
|
-
? "account_provider_rejected"
|
|
269
|
-
: "account_provider_access_unverified");
|
|
270
|
-
proof = {
|
|
271
|
-
...live.identity,
|
|
272
|
-
slot: selected.accountSlot,
|
|
273
|
-
provider: selected.provider,
|
|
274
|
-
checkedAtMs: body.checkedAtMs,
|
|
275
|
-
};
|
|
276
|
-
input.message?.("Claude provider access was verified. This does not reserve a runner or prove that a phase can start.");
|
|
277
|
-
return check(ctx, journal, signal);
|
|
278
|
-
})
|
|
279
|
-
.then((result) => (signal.aborted ? stopped() : resolve(result)), () => stopped());
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
finally {
|
|
283
|
-
clearTimeout(timer);
|
|
284
|
-
remove();
|
|
285
|
-
}
|
|
154
|
+
if (!input.waitForAccount ||
|
|
155
|
+
!["owner", "admin"].includes(initial.identity.role))
|
|
156
|
+
return check(ctx, journal, external);
|
|
157
|
+
let enrolled = initial.slots.length > 0;
|
|
158
|
+
const result = await input.waitForAccount(() => pollConsent({
|
|
159
|
+
timeoutMs: input.accountWaitMs,
|
|
160
|
+
signal: external,
|
|
161
|
+
sleep: input.sleep,
|
|
162
|
+
readStatus: async (signal) => {
|
|
163
|
+
const current = await inventory(ctx, journal, signal);
|
|
164
|
+
if ("reason" in current)
|
|
165
|
+
return { outcome: "waiting", reason: current.reason };
|
|
166
|
+
enrolled = current.slots.length > 0;
|
|
167
|
+
return {
|
|
168
|
+
outcome: usable(current.slots, ctx.now().getTime())
|
|
169
|
+
? "connected"
|
|
170
|
+
: "absent",
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
}));
|
|
174
|
+
if (result.state === "done")
|
|
175
|
+
return check(ctx, journal, external);
|
|
176
|
+
// A wait that ran out holds the step and offers another try (round 5).
|
|
177
|
+
return waiting(result.reason !== "consent_timeout"
|
|
178
|
+
? (result.reason ?? "account_enrollment_required")
|
|
179
|
+
: enrolled
|
|
180
|
+
? "ai_account_not_usable"
|
|
181
|
+
: "account_enrollment_required");
|
|
286
182
|
},
|
|
287
183
|
};
|
|
288
184
|
}
|