@cleocode/contracts 2026.10.3 → 2026.10.4
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/dist/doctor.d.ts +3 -4
- package/dist/doctor.d.ts.map +1 -1
- package/dist/heavy-command-hook.d.ts +134 -5
- package/dist/heavy-command-hook.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/nexus-account.d.ts +63 -1
- package/dist/nexus-account.d.ts.map +1 -1
- package/dist/nexus-account.js +5 -0
- package/dist/nexus-account.js.map +1 -1
- package/dist/nexus-cloud.d.ts +4 -0
- package/dist/nexus-cloud.d.ts.map +1 -1
- package/dist/nexus-cloud.js +7 -0
- package/dist/nexus-cloud.js.map +1 -1
- package/dist/nexus-first-run.d.ts +145 -0
- package/dist/nexus-first-run.d.ts.map +1 -0
- package/dist/nexus-first-run.js +54 -0
- package/dist/nexus-first-run.js.map +1 -0
- package/dist/project-context.d.ts +10 -2
- package/dist/project-context.d.ts.map +1 -1
- package/dist/resource-governor.d.ts +57 -0
- package/dist/resource-governor.d.ts.map +1 -1
- package/dist/task.d.ts +8 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js.map +1 -1
- package/dist/tasks.d.ts +17 -17
- package/dist/workgraph.d.ts +28 -28
- package/package.json +2 -2
- package/src/doctor.ts +3 -4
- package/src/heavy-command-hook.ts +175 -5
- package/src/index.ts +25 -0
- package/src/nexus-account.ts +73 -0
- package/src/nexus-cloud.ts +7 -0
- package/src/nexus-first-run.ts +165 -0
- package/src/project-context.ts +10 -2
- package/src/resource-governor.ts +60 -0
- package/src/task.ts +8 -0
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
*
|
|
20
20
|
* - `rewrite` — DEFAULT. Wrap it in place as
|
|
21
21
|
* `cleo run --wait --passthrough --class <c> -- <cmd>`, when the harness
|
|
22
|
-
* runs in `bypassPermissions` or `auto` mode
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* runs in `bypassPermissions` or `auto` mode, or (Claude Code `default`,
|
|
23
|
+
* `acceptEdits`, `dontAsk`) when the user's allow rules already approve the
|
|
24
|
+
* command, and no deny or ask rule names it. Otherwise the hook warns: a
|
|
25
|
+
* rewrite would change which permission rules match.
|
|
25
26
|
* - `warn` — leave the command alone; add a context line naming the governed
|
|
26
27
|
* form.
|
|
27
28
|
* - `off` — do nothing (the installer also removes the hook entry).
|
|
@@ -124,9 +125,13 @@ export interface OpencodeHeavyCommandAnswer {
|
|
|
124
125
|
export interface PreToolUseHookOutput {
|
|
125
126
|
readonly hookSpecificOutput: {
|
|
126
127
|
readonly hookEventName: 'PreToolUse';
|
|
127
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Omitted unless needed: Codex rewrites need `allow`, Kimi's "re-run it"
|
|
130
|
+
* is a `deny`, and a Claude Code rewrite carries `allow` only when the
|
|
131
|
+
* user's allow rules already approve the original command (T13124).
|
|
132
|
+
*/
|
|
128
133
|
readonly permissionDecision?: 'allow' | 'deny';
|
|
129
|
-
/** Shown to the agent on `deny`. */
|
|
134
|
+
/** Shown to the agent on `deny`; written to Claude Code's debug log on `allow`. */
|
|
130
135
|
readonly permissionDecisionReason?: string;
|
|
131
136
|
/** Replaces the whole tool input. */
|
|
132
137
|
readonly updatedInput?: ShellToolInput;
|
|
@@ -134,3 +139,168 @@ export interface PreToolUseHookOutput {
|
|
|
134
139
|
readonly additionalContext?: string;
|
|
135
140
|
};
|
|
136
141
|
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Project-level delivery (T13124)
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* What delivering the hook to one provider did in one project.
|
|
149
|
+
*
|
|
150
|
+
* - `installed` / `updated` / `removed` / `unchanged` — the config file was
|
|
151
|
+
* written, refreshed, cleaned (mode `off`) or already current.
|
|
152
|
+
* - `skipped` — not applicable: the provider is not in use on this machine or
|
|
153
|
+
* project, or the project is the user's home directory (whose provider
|
|
154
|
+
* configs are user-global, which CLEO never writes).
|
|
155
|
+
* - `blocked` — a path the hook must live under exists but is not a
|
|
156
|
+
* directory (a stray `.codex` file, say); nothing was written.
|
|
157
|
+
* - `unsupported` — the provider cannot take a project-level hook (Kimi reads
|
|
158
|
+
* hooks only from its global config).
|
|
159
|
+
* - `needs-consent` — the config is the project's own (Codex's committable
|
|
160
|
+
* `.codex/hooks.json`, tracked or holding anything CLEO did not write), so
|
|
161
|
+
* CLEO does not write or hide it; `snippet` is the entry to add by hand.
|
|
162
|
+
* - `failed` — any other error, such as a config file that is not valid JSON
|
|
163
|
+
* (left untouched).
|
|
164
|
+
*/
|
|
165
|
+
export type HeavyHookDeliveryStatus =
|
|
166
|
+
| 'installed'
|
|
167
|
+
| 'updated'
|
|
168
|
+
| 'removed'
|
|
169
|
+
| 'unchanged'
|
|
170
|
+
| 'skipped'
|
|
171
|
+
| 'blocked'
|
|
172
|
+
| 'unsupported'
|
|
173
|
+
| 'needs-consent'
|
|
174
|
+
| 'failed';
|
|
175
|
+
|
|
176
|
+
/** One provider's result from syncing the hook into a project. */
|
|
177
|
+
export interface HeavyHookDeliveryOutcome {
|
|
178
|
+
/** The harness. */
|
|
179
|
+
readonly provider: HeavyCommandHookProvider;
|
|
180
|
+
/** What the sync did. */
|
|
181
|
+
readonly status: HeavyHookDeliveryStatus;
|
|
182
|
+
/** The config file (or plugin file) the hook lives in, absolute. */
|
|
183
|
+
readonly target: string;
|
|
184
|
+
/** Why it was skipped, blocked, unsupported or failed. */
|
|
185
|
+
readonly reason?: string;
|
|
186
|
+
/** The exact step that fixes a `blocked`, `unsupported`, `needs-consent` or `failed` outcome. */
|
|
187
|
+
readonly remedy?: string;
|
|
188
|
+
/** `needs-consent`: the exact JSON entry to add to the config by hand. */
|
|
189
|
+
readonly snippet?: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The hook's state for one provider in one project, as `cleo doctor` reports it.
|
|
194
|
+
*
|
|
195
|
+
* - `installed` — present and identical to what this CLEO would write.
|
|
196
|
+
* - `outdated` — present but written by another CLEO build, only in a legacy
|
|
197
|
+
* location, or visible to git as an untracked file (gh#1805: it widens
|
|
198
|
+
* `cleo verify`'s evidence scope); `cleo upgrade` refreshes it.
|
|
199
|
+
* - `missing` — the provider is in use and the hook is absent.
|
|
200
|
+
* - `blocked` — see {@link HeavyHookDeliveryStatus}.
|
|
201
|
+
* - `unreadable` — the config file exists but cannot be read or parsed.
|
|
202
|
+
* - `unsupported` — the provider cannot take a project-level hook.
|
|
203
|
+
* - `needs-consent` — see {@link HeavyHookDeliveryStatus}; `snippet` holds the entry.
|
|
204
|
+
* - `disabled` — `resources.heavyCommandHook` is `off`, and no CLEO hook is left.
|
|
205
|
+
* - `not-detected` — the provider is not in use here; nothing is expected.
|
|
206
|
+
*/
|
|
207
|
+
export type HeavyHookInstallState =
|
|
208
|
+
| 'installed'
|
|
209
|
+
| 'outdated'
|
|
210
|
+
| 'missing'
|
|
211
|
+
| 'blocked'
|
|
212
|
+
| 'unreadable'
|
|
213
|
+
| 'unsupported'
|
|
214
|
+
| 'needs-consent'
|
|
215
|
+
| 'disabled'
|
|
216
|
+
| 'not-detected';
|
|
217
|
+
|
|
218
|
+
/** One provider's hook state in one project. */
|
|
219
|
+
export interface HeavyHookInspection {
|
|
220
|
+
/** The harness. */
|
|
221
|
+
readonly provider: HeavyCommandHookProvider;
|
|
222
|
+
/** Whether the provider is in use on this machine or in this project. */
|
|
223
|
+
readonly detected: boolean;
|
|
224
|
+
/** The hook's state. */
|
|
225
|
+
readonly state: HeavyHookInstallState;
|
|
226
|
+
/** The config file (or plugin file) the hook lives in, absolute. */
|
|
227
|
+
readonly target: string;
|
|
228
|
+
/** One line saying what was found. */
|
|
229
|
+
readonly detail: string;
|
|
230
|
+
/** The exact step that fixes the state, when one is needed. */
|
|
231
|
+
readonly remedy?: string;
|
|
232
|
+
/** `needs-consent`: the exact JSON entry to add to the config by hand. */
|
|
233
|
+
readonly snippet?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Whether the `cleo` a hook finds on PATH can answer it.
|
|
238
|
+
*
|
|
239
|
+
* - `current` — it answers `cleo hook heavy-command`.
|
|
240
|
+
* - `older` — it predates `cleo hook` (and `cleo run`): the hook lets every
|
|
241
|
+
* command run ungoverned. The hook's own marker (exit 127, "Unknown
|
|
242
|
+
* command") or a direct probe says so.
|
|
243
|
+
* - `missing` — no `cleo` on PATH: the hook stays silent.
|
|
244
|
+
* - `unknown` — the probe failed some other way (a broken version-manager
|
|
245
|
+
* shim, a timeout, a crash). The hook fails open, so it governs nothing
|
|
246
|
+
* while this lasts.
|
|
247
|
+
*/
|
|
248
|
+
export type HeavyHookCliState = 'current' | 'older' | 'missing' | 'unknown';
|
|
249
|
+
|
|
250
|
+
/** The result of probing the `cleo` on PATH from a project directory. */
|
|
251
|
+
export interface HeavyHookCliProbe {
|
|
252
|
+
/** What the probe found. */
|
|
253
|
+
readonly state: HeavyHookCliState;
|
|
254
|
+
/** The `cleo` the hook resolves on PATH, or `null`. */
|
|
255
|
+
readonly path: string | null;
|
|
256
|
+
/** One line saying what was found. */
|
|
257
|
+
readonly detail: string;
|
|
258
|
+
/** The exact step that fixes (or diagnoses) a CLI that is not `current`. */
|
|
259
|
+
readonly remedy?: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Options for the project-level delivery functions (all injectable for tests). */
|
|
263
|
+
export interface HeavyHookDeliveryOptions {
|
|
264
|
+
/** Environment for provider detection (`PATH`, `HOME`, `CODEX_HOME`, …). Default `process.env`. */
|
|
265
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
266
|
+
/** Providers to consider. Default: every provider CLEO knows. */
|
|
267
|
+
readonly providers?: readonly HeavyCommandHookProvider[];
|
|
268
|
+
/**
|
|
269
|
+
* Inspection only: run the git checks (a hook file git can see, a shared
|
|
270
|
+
* Codex `hooks.json`). Default `true`; the session briefing passes `false`
|
|
271
|
+
* to skip their git spawns (`cleo doctor` keeps them).
|
|
272
|
+
*/
|
|
273
|
+
readonly gitChecks?: boolean;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The project-level delivery surface `@cleocode/adapters` exports at
|
|
278
|
+
* `@cleocode/adapters/heavy-command-hook`. `@cleocode/core` loads it at run
|
|
279
|
+
* time (adapters builds on core, so core cannot import it statically) for
|
|
280
|
+
* `cleo init`, `cleo upgrade`, `cleo doctor` and the session briefing.
|
|
281
|
+
*/
|
|
282
|
+
export interface HeavyHookDeliveryApi {
|
|
283
|
+
/**
|
|
284
|
+
* Install, refresh or (mode `off`) remove CLEO's hook for every provider in
|
|
285
|
+
* use, each independently. Never throws: every provider gets an outcome.
|
|
286
|
+
*/
|
|
287
|
+
readonly syncProjectHeavyCommandHooks: (
|
|
288
|
+
projectDir: string,
|
|
289
|
+
mode: HeavyCommandHookMode,
|
|
290
|
+
options?: HeavyHookDeliveryOptions,
|
|
291
|
+
) => Promise<readonly HeavyHookDeliveryOutcome[]>;
|
|
292
|
+
/** Read-only: the hook's state for every provider. Never throws. */
|
|
293
|
+
readonly inspectProjectHeavyCommandHooks: (
|
|
294
|
+
projectDir: string,
|
|
295
|
+
mode: HeavyCommandHookMode,
|
|
296
|
+
options?: HeavyHookDeliveryOptions,
|
|
297
|
+
) => readonly HeavyHookInspection[];
|
|
298
|
+
/**
|
|
299
|
+
* Whether the `cleo` on PATH (as the hook resolves it from `projectDir`)
|
|
300
|
+
* can answer the hook. Starts that `cleo` once. Never throws.
|
|
301
|
+
*/
|
|
302
|
+
readonly probeHeavyHookCli: (
|
|
303
|
+
projectDir: string,
|
|
304
|
+
options?: HeavyHookDeliveryOptions,
|
|
305
|
+
) => HeavyHookCliProbe;
|
|
306
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -780,6 +780,14 @@ export type {
|
|
|
780
780
|
HeavyCommandHookProvider,
|
|
781
781
|
HeavyCommandPlan,
|
|
782
782
|
HeavyCommandSegment,
|
|
783
|
+
HeavyHookCliProbe,
|
|
784
|
+
HeavyHookCliState,
|
|
785
|
+
HeavyHookDeliveryApi,
|
|
786
|
+
HeavyHookDeliveryOptions,
|
|
787
|
+
HeavyHookDeliveryOutcome,
|
|
788
|
+
HeavyHookDeliveryStatus,
|
|
789
|
+
HeavyHookInspection,
|
|
790
|
+
HeavyHookInstallState,
|
|
783
791
|
HookJsonValue,
|
|
784
792
|
OpencodeHeavyCommandAnswer,
|
|
785
793
|
PreToolUseHookInput,
|
|
@@ -2270,6 +2278,9 @@ export * from './render/index.js';
|
|
|
2270
2278
|
export type {
|
|
2271
2279
|
AdmissionResult,
|
|
2272
2280
|
GovernorMode,
|
|
2281
|
+
HeavyHeapSource,
|
|
2282
|
+
HeavyLeverChange,
|
|
2283
|
+
HeavyToolResourcePlan,
|
|
2273
2284
|
ResourceClass,
|
|
2274
2285
|
ResourceDeferral,
|
|
2275
2286
|
ResourceGrant,
|
|
@@ -3009,6 +3020,8 @@ export type {
|
|
|
3009
3020
|
NexusAccountErrorCode,
|
|
3010
3021
|
NexusAccountMe,
|
|
3011
3022
|
NexusAccountOrganization,
|
|
3023
|
+
NexusAccountSetup,
|
|
3024
|
+
NexusAccountSetupStep,
|
|
3012
3025
|
NexusAccountStatus,
|
|
3013
3026
|
NexusAccountUser,
|
|
3014
3027
|
NexusDeviceEndOutcome,
|
|
@@ -3092,6 +3105,18 @@ export {
|
|
|
3092
3105
|
nexusCloudUserSchema,
|
|
3093
3106
|
nexusCloudWhoamiSchema,
|
|
3094
3107
|
} from './nexus-cloud.js';
|
|
3108
|
+
// === Guided first run of `cleo login nexus` (T13102) ===
|
|
3109
|
+
export {
|
|
3110
|
+
NEXUS_FIRST_RUN_STATES,
|
|
3111
|
+
type NexusFirstRunBackup,
|
|
3112
|
+
type NexusFirstRunChoice,
|
|
3113
|
+
type NexusFirstRunOffer,
|
|
3114
|
+
type NexusFirstRunRestore,
|
|
3115
|
+
type NexusFirstRunResult,
|
|
3116
|
+
type NexusFirstRunState,
|
|
3117
|
+
type NexusNamedProject,
|
|
3118
|
+
type NexusProjectNameSource,
|
|
3119
|
+
} from './nexus-first-run.js';
|
|
3095
3120
|
export {
|
|
3096
3121
|
type CloudActivityItem,
|
|
3097
3122
|
type CloudActivityResult,
|
package/src/nexus-account.ts
CHANGED
|
@@ -101,6 +101,11 @@ export const NEXUS_ACCOUNT_ERROR_CODES = [
|
|
|
101
101
|
'E_NEXUS_VAULT_STORE_BUSY',
|
|
102
102
|
/** A project restore target already holds a different project. */
|
|
103
103
|
'E_NEXUS_VAULT_TARGET_OCCUPIED',
|
|
104
|
+
// Projects by name (`cleo cloud restore <name>`, T13102).
|
|
105
|
+
/** No project of the account has that name, label or id. */
|
|
106
|
+
'E_NEXUS_PROJECT_NOT_FOUND',
|
|
107
|
+
/** Several of the account's projects have that name or label; the error lists them. */
|
|
108
|
+
'E_NEXUS_PROJECT_AMBIGUOUS',
|
|
104
109
|
] as const;
|
|
105
110
|
|
|
106
111
|
/** One of {@link NEXUS_ACCOUNT_ERROR_CODES}. */
|
|
@@ -181,6 +186,72 @@ export interface NexusLoginDevice {
|
|
|
181
186
|
created: boolean | null;
|
|
182
187
|
}
|
|
183
188
|
|
|
189
|
+
/**
|
|
190
|
+
* A step of the account setup `cleo login nexus` runs right after enrolment
|
|
191
|
+
* (T13100), named when it fails:
|
|
192
|
+
*
|
|
193
|
+
* - `connect`: open the vault connection with the new device credential;
|
|
194
|
+
* - `escrow-read`: read the escrowed account master key (`GET /v1/account/keys/escrow`);
|
|
195
|
+
* - `escrow-mint`: on the account's first device, create the master key and escrow it
|
|
196
|
+
* (`PUT /v1/account/keys/escrow`; a 409 means another device won, and its key is read);
|
|
197
|
+
* - `certify`: certify this device under the master key (`PUT /v1/devices/:id/key`);
|
|
198
|
+
* - `trust`: evaluate and record the signer trust state (`GET /v1/devices/trust`,
|
|
199
|
+
* `<cleoHome>/nexus-vault.json`).
|
|
200
|
+
*/
|
|
201
|
+
export type NexusAccountSetupStep = 'connect' | 'escrow-read' | 'escrow-mint' | 'certify' | 'trust';
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* What `cleo login nexus` did to make the account ready for encrypted backups
|
|
205
|
+
* (T13100). Holds no key.
|
|
206
|
+
*
|
|
207
|
+
* - `ready`: this device holds the account master key and is certified under it;
|
|
208
|
+
* - `unsupported`: the server has no account key escrow; the login still succeeded;
|
|
209
|
+
* - `skipped`: a read-only device, which never mints or certifies;
|
|
210
|
+
* - `failed`: the login succeeded but the setup did not; `step` and `fix` say where and what to do.
|
|
211
|
+
*/
|
|
212
|
+
export type NexusAccountSetup =
|
|
213
|
+
| {
|
|
214
|
+
status: 'ready';
|
|
215
|
+
/**
|
|
216
|
+
* `fetched`: the escrowed key was opened; `minted`: this device created and escrowed it;
|
|
217
|
+
* `adopted`: another device escrowed first (409) and its key was read, never re-minted.
|
|
218
|
+
*/
|
|
219
|
+
escrow: 'fetched' | 'minted' | 'adopted';
|
|
220
|
+
/** `new`: this login certified the device; `existing`: it already was. */
|
|
221
|
+
certificate: 'new' | 'existing';
|
|
222
|
+
/** Version of the account master key. */
|
|
223
|
+
keyVersion: number;
|
|
224
|
+
/** One human line. */
|
|
225
|
+
summary: string;
|
|
226
|
+
}
|
|
227
|
+
| {
|
|
228
|
+
status: 'unsupported';
|
|
229
|
+
/** `E_NEXUS_VAULT_UNSUPPORTED`. */
|
|
230
|
+
code: NexusAccountErrorCode;
|
|
231
|
+
/** The remedy. */
|
|
232
|
+
fix: string;
|
|
233
|
+
/** One human line. */
|
|
234
|
+
summary: string;
|
|
235
|
+
}
|
|
236
|
+
| {
|
|
237
|
+
status: 'skipped';
|
|
238
|
+
/** One human line. */
|
|
239
|
+
summary: string;
|
|
240
|
+
}
|
|
241
|
+
| {
|
|
242
|
+
status: 'failed';
|
|
243
|
+
/** The step that failed. */
|
|
244
|
+
step: NexusAccountSetupStep;
|
|
245
|
+
/** Stable error code (a {@link NexusAccountErrorCode} when the flow raised one). */
|
|
246
|
+
code: string;
|
|
247
|
+
/** What went wrong. */
|
|
248
|
+
message: string;
|
|
249
|
+
/** The remedy. */
|
|
250
|
+
fix: string;
|
|
251
|
+
/** One human line naming the step and the remedy. */
|
|
252
|
+
summary: string;
|
|
253
|
+
};
|
|
254
|
+
|
|
184
255
|
/** Result of `cleo login nexus` (never carries the token). */
|
|
185
256
|
export interface NexusLoginResult {
|
|
186
257
|
/** API origin the session belongs to. */
|
|
@@ -199,6 +270,8 @@ export interface NexusLoginResult {
|
|
|
199
270
|
device?: NexusLoginDevice;
|
|
200
271
|
/** Scopes of the stored device credential; present only with device credentials. */
|
|
201
272
|
scopes?: string[];
|
|
273
|
+
/** The account key setup run right after enrolment (T13100); present only with device credentials. */
|
|
274
|
+
account?: NexusAccountSetup;
|
|
202
275
|
}
|
|
203
276
|
|
|
204
277
|
/** Result of `cleo logout nexus`. */
|
package/src/nexus-cloud.ts
CHANGED
|
@@ -167,8 +167,15 @@ const projectFields = {
|
|
|
167
167
|
export const nexusCloudProjectListItemSchema = z.object({
|
|
168
168
|
...projectFields,
|
|
169
169
|
role: z.string(),
|
|
170
|
+
/**
|
|
171
|
+
* The project name sealed with the project data key (`Project.encryptedName`), base64;
|
|
172
|
+
* only a holder of the account key can open it (T13102).
|
|
173
|
+
*/
|
|
174
|
+
encryptedName: z.string().nullable().optional(),
|
|
170
175
|
streamId: z.string().optional(),
|
|
171
176
|
headSeq: z.number().int().nonnegative().nullable().optional(),
|
|
177
|
+
/** The stream's head snapshot; null when nothing was ever pushed (T13102: restorable or not). */
|
|
178
|
+
headCheckpointId: z.string().nullable().optional(),
|
|
172
179
|
openConflicts: z.number().int().nonnegative().optional(),
|
|
173
180
|
replicas: z.array(nexusCloudReplicaSchema).default([]),
|
|
174
181
|
devices: nexusCloudDeviceCountsSchema.optional(),
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guided first run of `cleo login nexus` (T13102, owner decision 2026-10-02:
|
|
3
|
+
* onboarding = A + B + C' + D, so that `cleo login` is the only setup step in
|
|
4
|
+
* practice).
|
|
5
|
+
*
|
|
6
|
+
* After a successful sign-in, login looks at where it runs:
|
|
7
|
+
*
|
|
8
|
+
* - Inside a CLEO project this machine has not linked: it offers to link the
|
|
9
|
+
* project and take the first encrypted backup. `--yes` does it without asking,
|
|
10
|
+
* a terminal asks, and a non-interactive (agent) run never asks: it reports
|
|
11
|
+
* the exact next command. When Cleo Nexus already holds a backup of the
|
|
12
|
+
* project that this copy never synced (a fresh clone on a new machine), the
|
|
13
|
+
* offer is to restore that backup here instead; a backup would be refused.
|
|
14
|
+
* - Outside a CLEO project: it lists the account's projects, by name, with
|
|
15
|
+
* the exact `cleo cloud restore` command for each one that has a backup
|
|
16
|
+
* this machine does not hold yet.
|
|
17
|
+
*
|
|
18
|
+
* `cleo cloud restore <name>` resolves a project name or label (or an id) to
|
|
19
|
+
* the server's project id; an ambiguous name lists the candidates.
|
|
20
|
+
*
|
|
21
|
+
* This file is types + const data only (arch gate 10).
|
|
22
|
+
*
|
|
23
|
+
* @task T13102
|
|
24
|
+
* @epic T12322
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { NexusProjectLink } from './nexus-account.js';
|
|
28
|
+
import type { CloudWarning } from './nexus-cloud.js';
|
|
29
|
+
import type { CloudVaultSnapshot } from './nexus-vault.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How the guided first run ended.
|
|
33
|
+
*
|
|
34
|
+
* - `backed-up`: the project was linked (or already linked) and its first backup pushed
|
|
35
|
+
* (or the cloud already held this exact state).
|
|
36
|
+
* - `link-failed`: linking failed; nothing was pushed.
|
|
37
|
+
* - `backup-failed`: the project is linked, but the backup failed.
|
|
38
|
+
* - `restored`: the cloud held a backup this copy never synced; it was restored here and linked.
|
|
39
|
+
* - `restore-failed`: restoring that backup failed (for example, local rows it would overwrite).
|
|
40
|
+
* - `offered`: a non-interactive run inside an unlinked project; nothing was done and
|
|
41
|
+
* `nextCommand` says what to run (link and back up, or restore; see `offer`).
|
|
42
|
+
* - `declined`: the user answered no at the prompt; `nextCommand` says what to run later.
|
|
43
|
+
* - `already-linked`: the project is linked and attached from this device; nothing to do.
|
|
44
|
+
* - `projects`: outside a CLEO project; `projects` lists the account's projects.
|
|
45
|
+
* - `skipped`: the first run does not apply (`reason` says why).
|
|
46
|
+
*/
|
|
47
|
+
export const NEXUS_FIRST_RUN_STATES = [
|
|
48
|
+
'backed-up',
|
|
49
|
+
'link-failed',
|
|
50
|
+
'backup-failed',
|
|
51
|
+
'restored',
|
|
52
|
+
'restore-failed',
|
|
53
|
+
'offered',
|
|
54
|
+
'declined',
|
|
55
|
+
'already-linked',
|
|
56
|
+
'projects',
|
|
57
|
+
'skipped',
|
|
58
|
+
] as const;
|
|
59
|
+
|
|
60
|
+
/** One of {@link NEXUS_FIRST_RUN_STATES}. */
|
|
61
|
+
export type NexusFirstRunState = (typeof NEXUS_FIRST_RUN_STATES)[number];
|
|
62
|
+
|
|
63
|
+
/** Where a project's display name came from. */
|
|
64
|
+
export type NexusProjectNameSource = 'encrypted-name' | 'label' | 'id';
|
|
65
|
+
|
|
66
|
+
/** One of the account's projects, as the first run and `cleo cloud restore <name>` see it. */
|
|
67
|
+
export interface NexusNamedProject {
|
|
68
|
+
/** The server's project id (the tracked local project id of whoever linked it). */
|
|
69
|
+
projectId: string;
|
|
70
|
+
/**
|
|
71
|
+
* Display name: the plaintext label, else the id. (`encryptedName` is shown only through a
|
|
72
|
+
* reader for its format, which cleo-nexus T098 will specify; none is wired in yet.)
|
|
73
|
+
*/
|
|
74
|
+
name: string;
|
|
75
|
+
/** Where {@link NexusNamedProject.name} came from. */
|
|
76
|
+
nameSource: NexusProjectNameSource;
|
|
77
|
+
/** The plaintext label the server holds, if any. */
|
|
78
|
+
label: string | null;
|
|
79
|
+
/** The owning organization's name, when the server sent it. */
|
|
80
|
+
organizationName: string | null;
|
|
81
|
+
/** The newest sync over every replica, when the server sent it. */
|
|
82
|
+
lastSyncAt: string | null;
|
|
83
|
+
/**
|
|
84
|
+
* The project's stream holds a snapshot to restore. `null` when the server did not say
|
|
85
|
+
* (an older server without `headCheckpointId` on the project list).
|
|
86
|
+
*/
|
|
87
|
+
hasBackup: boolean | null;
|
|
88
|
+
/** A replica of the project is attached from this device. */
|
|
89
|
+
onThisDevice: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The exact command that restores it into the current directory, by its id (an id never
|
|
92
|
+
* changes; for agents and scripts). `null` when there is nothing to restore here (no
|
|
93
|
+
* backup, or it is already on this device).
|
|
94
|
+
*/
|
|
95
|
+
restoreCommand: string | null;
|
|
96
|
+
/**
|
|
97
|
+
* The same restore by name, for a person to type: set only when the name resolves to this
|
|
98
|
+
* project alone (and cannot be read as a flag or an id), else `null`.
|
|
99
|
+
*/
|
|
100
|
+
restoreByNameCommand: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The first backup the guided run pushed. */
|
|
104
|
+
export interface NexusFirstRunBackup {
|
|
105
|
+
/** `pushed`: a new snapshot; `up-to-date`: the cloud already held this state. */
|
|
106
|
+
status: 'pushed' | 'up-to-date';
|
|
107
|
+
/** The snapshot, when the server named one. */
|
|
108
|
+
snapshot: CloudVaultSnapshot | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The backup the guided run restored into the project (`restored` only). */
|
|
112
|
+
export interface NexusFirstRunRestore {
|
|
113
|
+
/** `restored`: the snapshot was activated; `up-to-date`: this copy already held it. */
|
|
114
|
+
status: 'restored' | 'up-to-date';
|
|
115
|
+
/** The snapshot, when the server named one. */
|
|
116
|
+
snapshot: CloudVaultSnapshot | null;
|
|
117
|
+
/** Tables verified by count and hash. */
|
|
118
|
+
tables: number;
|
|
119
|
+
/** The local safety backup taken first, if any. */
|
|
120
|
+
safetyBackup: string | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* What the run offered inside an unlinked project: `link-and-backup` (the
|
|
125
|
+
* project is new to the cloud, or this copy already synced with it), or
|
|
126
|
+
* `restore` (the cloud holds a backup this copy never synced).
|
|
127
|
+
*/
|
|
128
|
+
export type NexusFirstRunOffer = 'link-and-backup' | 'restore';
|
|
129
|
+
|
|
130
|
+
/** One option of a choice the first run leaves to the user. */
|
|
131
|
+
export interface NexusFirstRunChoice {
|
|
132
|
+
/** The exact command. */
|
|
133
|
+
command: string;
|
|
134
|
+
/** What it does, in one line. */
|
|
135
|
+
effect: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The guided first run's outcome, carried as `data.firstRun` of the `cleo login nexus` envelope. */
|
|
139
|
+
export interface NexusFirstRunResult {
|
|
140
|
+
/** How it ended. */
|
|
141
|
+
state: NexusFirstRunState;
|
|
142
|
+
/** Why it was skipped (`skipped` only), e.g. a read-only device. */
|
|
143
|
+
reason: string | null;
|
|
144
|
+
/** The CLEO project login ran in, or `null` outside one. */
|
|
145
|
+
projectRoot: string | null;
|
|
146
|
+
/** The project's link after the run, when it is linked. */
|
|
147
|
+
link: NexusProjectLink | null;
|
|
148
|
+
/** What was offered inside an unlinked project, or `null` elsewhere. */
|
|
149
|
+
offer: NexusFirstRunOffer | null;
|
|
150
|
+
/** The backup pushed (`backed-up` only). */
|
|
151
|
+
backup: NexusFirstRunBackup | null;
|
|
152
|
+
/** The backup restored (`restored` only). */
|
|
153
|
+
restore: NexusFirstRunRestore | null;
|
|
154
|
+
/** The account's projects (`projects` only), newest first. */
|
|
155
|
+
projects: NexusNamedProject[];
|
|
156
|
+
/** The exact command to run next, when there is one (never asks: agents run it). */
|
|
157
|
+
nextCommand: string | null;
|
|
158
|
+
/**
|
|
159
|
+
* When the next step is a choice only the user can make (`nextCommand` is `null`), each
|
|
160
|
+
* option as `{ command, effect }`; empty otherwise.
|
|
161
|
+
*/
|
|
162
|
+
choices: NexusFirstRunChoice[];
|
|
163
|
+
/** Non-fatal problems met on the way (secret-free). */
|
|
164
|
+
warnings: CloudWarning[];
|
|
165
|
+
}
|
package/src/project-context.ts
CHANGED
|
@@ -126,10 +126,18 @@ export interface ProjectContext {
|
|
|
126
126
|
command?: string;
|
|
127
127
|
/**
|
|
128
128
|
* Affected-scope test command template for `tool:test-affected` (D11150,
|
|
129
|
-
* T12635)
|
|
130
|
-
*
|
|
129
|
+
* T12635) and a scope-aware `tool:test` (T12959): `{projects}`,
|
|
130
|
+
* `{filters}`, `{workspaces}` and `{packages}` expand per affected
|
|
131
|
+
* package, e.g. `pnpm exec vitest run {projects}`. When absent, one is
|
|
132
|
+
* derived from a workspace-wide `command` where that is mechanical
|
|
133
|
+
* (`pnpm -r … test`, `npm … test --workspaces`, `turbo run test`; T13125).
|
|
131
134
|
*/
|
|
132
135
|
affectedCommand?: string;
|
|
136
|
+
/**
|
|
137
|
+
* `false` makes `tool:test` always run the full suite instead of the
|
|
138
|
+
* affected scope (T12959).
|
|
139
|
+
*/
|
|
140
|
+
preferAffected?: boolean;
|
|
133
141
|
testFilePatterns?: string[];
|
|
134
142
|
directories?: {
|
|
135
143
|
unit?: string;
|
package/src/resource-governor.ts
CHANGED
|
@@ -139,6 +139,66 @@ export function isResourceGrant(r: AdmissionResult): r is ResourceGrant {
|
|
|
139
139
|
return r.deferred === false;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Where the heap ceiling of a heavy tool run came from (T13122).
|
|
144
|
+
*
|
|
145
|
+
* - `default` — no heap in the caller's `NODE_OPTIONS`; CLEO's default applies.
|
|
146
|
+
* - `inherited` — the caller's `NODE_OPTIONS` heap fits the run's budget and is
|
|
147
|
+
* kept; the worker count shrinks so the run still fits.
|
|
148
|
+
* - `clamped` — the caller's `NODE_OPTIONS` heap exceeds the run's budget (a
|
|
149
|
+
* shell-profile export is not a per-project choice) and was lowered to it.
|
|
150
|
+
* - `override` — `CLEO_HEAVY_HEAP_MB` set it explicitly; never clamped.
|
|
151
|
+
*/
|
|
152
|
+
export type HeavyHeapSource = 'default' | 'inherited' | 'clamped' | 'override';
|
|
153
|
+
|
|
154
|
+
/** One inherited environment value CLEO replaced because it exceeded the plan (T13122). */
|
|
155
|
+
export interface HeavyLeverChange {
|
|
156
|
+
/** Variable name, e.g. `VITEST_MAX_WORKERS` or `NODE_OPTIONS`. */
|
|
157
|
+
readonly name: string;
|
|
158
|
+
/** The inherited value, as the caller's environment carried it. */
|
|
159
|
+
readonly from: string;
|
|
160
|
+
/** The value the tool was spawned with. */
|
|
161
|
+
readonly to: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The resource plan a memory-bound tool (`test`, `build`, and since T13123
|
|
166
|
+
* `typecheck`, `lint`) was spawned with, and why (T13122). Reported by
|
|
167
|
+
* `cleo verify` (on the `tool` evidence atom and in a resource-kill message)
|
|
168
|
+
* and by `cleo run`, so an operator can see when an inherited value was
|
|
169
|
+
* clamped. A single-process tool's plan has one worker.
|
|
170
|
+
*
|
|
171
|
+
* The invariant it describes: `workspaceConcurrency × workers × heapMb` stays
|
|
172
|
+
* within `budgetMb`, unless an explicit `CLEO_HEAVY_*` override asked for more
|
|
173
|
+
* (`overBudget`).
|
|
174
|
+
*/
|
|
175
|
+
export interface HeavyToolResourcePlan {
|
|
176
|
+
/** V8 old-space ceiling given to every Node process in the tool's tree, in MiB. */
|
|
177
|
+
readonly heapMb: number;
|
|
178
|
+
/** Where {@link heapMb} came from. */
|
|
179
|
+
readonly heapSource: HeavyHeapSource;
|
|
180
|
+
/** The heap the caller's `NODE_OPTIONS` asked for, in MiB; `null` when it set none. */
|
|
181
|
+
readonly inheritedHeapMb: number | null;
|
|
182
|
+
/** Worker count given to every runner that reads one (`VITEST_MAX_WORKERS`, `GOMAXPROCS`, …). */
|
|
183
|
+
readonly workers: number;
|
|
184
|
+
/** `plan` when derived from the heap and budget; `override` when `CLEO_HEAVY_WORKERS` set it. */
|
|
185
|
+
readonly workersSource: 'plan' | 'override';
|
|
186
|
+
/** Workspace packages allowed to run their script at once (`npm_config_workspace_concurrency`). */
|
|
187
|
+
readonly workspaceConcurrency: number;
|
|
188
|
+
/** Heap budget for the whole run, in MiB: the default worker count times the default heap. */
|
|
189
|
+
readonly budgetMb: number;
|
|
190
|
+
/** Total RAM the budget derives from, in MiB. */
|
|
191
|
+
readonly totalRamMb: number;
|
|
192
|
+
/** Inherited values replaced because they exceeded the plan. Empty when nothing was clamped. */
|
|
193
|
+
readonly clamped: readonly HeavyLeverChange[];
|
|
194
|
+
/** Inherited worker or concurrency values kept because they were within the plan, as `NAME=value`. */
|
|
195
|
+
readonly kept: readonly string[];
|
|
196
|
+
/** `true` when explicit `CLEO_HEAVY_*` overrides put the run over {@link budgetMb}. */
|
|
197
|
+
readonly overBudget: boolean;
|
|
198
|
+
/** One line naming the heap and worker count chosen and why. */
|
|
199
|
+
readonly summary: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
142
202
|
/** Explicit local user-manager connection for resource-controlled process launch. */
|
|
143
203
|
export interface SystemdControlContext {
|
|
144
204
|
/** Absolute existing runtime directory used only by the manager probe and launcher. */
|
package/src/task.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import type { AcceptanceGate, AcceptanceGateResult } from './acceptance-gate.js';
|
|
25
25
|
import type { AcRow } from './data-accessor.js';
|
|
26
|
+
import type { HeavyToolResourcePlan } from './resource-governor.js';
|
|
26
27
|
import type { TaskStatus } from './status-registry.js';
|
|
27
28
|
|
|
28
29
|
export type { TaskStatus };
|
|
@@ -365,6 +366,13 @@ export type EvidenceAtom =
|
|
|
365
366
|
treeHash?: string;
|
|
366
367
|
/** `true` when the result was served from the tool cache (no spawn). */
|
|
367
368
|
cacheHit?: boolean;
|
|
369
|
+
/**
|
|
370
|
+
* The heap, worker count and workspace concurrency a memory-bound tool
|
|
371
|
+
* (`test`, `build`, `typecheck`, `lint`) was spawned with, and why
|
|
372
|
+
* (T13122, T13123) — including any inherited `NODE_OPTIONS` heap or
|
|
373
|
+
* worker count that was clamped to the budget. Absent for other tools.
|
|
374
|
+
*/
|
|
375
|
+
resources?: HeavyToolResourcePlan;
|
|
368
376
|
}
|
|
369
377
|
| { kind: 'url'; url: string }
|
|
370
378
|
| { kind: 'note'; note: string }
|