@stigmer/cli 3.14.1 → 3.15.0

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.
@@ -149,8 +149,8 @@ function countingClient(preferences: StubPreferences): { client: Stigmer; calls:
149
149
  return { client, calls: () => calls };
150
150
  }
151
151
 
152
- /** prepareAgentExec options as `run` passes them: cloud + harness opt-in. */
153
- const CLOUD_RUN_OPTIONS = { cloudBackend: true, applyAccountHarnessDefault: true } as const;
152
+ /** prepareAgentExec options as `run` passes them: the kind is served here + harness opt-in. */
153
+ const SERVED_RUN_OPTIONS = { accountPreferencesAvailable: true, applyAccountHarnessDefault: true } as const;
154
154
 
155
155
  describe("prepareAgentExec env injection", () => {
156
156
  it("injects STIGMER_ORG_ID when absent", async () => {
@@ -193,7 +193,7 @@ describe("prepareAgentExec account-preference model fill (oss#293 Phase 1.5)", (
193
193
  clientWithPreference("claude-sonnet-4.6"),
194
194
  "acme",
195
195
  undefined,
196
- { cloudBackend: true },
196
+ { accountPreferencesAvailable: true },
197
197
  );
198
198
  expect(prepared.model).toBe("claude-sonnet-4.6");
199
199
  });
@@ -204,20 +204,22 @@ describe("prepareAgentExec account-preference model fill (oss#293 Phase 1.5)", (
204
204
  clientWithPreference("claude-sonnet-4.6"),
205
205
  "acme",
206
206
  undefined,
207
- { cloudBackend: true },
207
+ { accountPreferencesAvailable: true },
208
208
  );
209
209
  expect(prepared.model).toBe("gpt-5.3");
210
210
  });
211
211
 
212
- it("never consults identity on a local backend", async () => {
213
- // The failing stub doubles as a call detector: local mode must not even
214
- // attempt whoAmI, so a rejecting client cannot affect the result.
212
+ it("never consults identity where the kind is not served", async () => {
213
+ // The failing stub doubles as a call detector: a backend that does not
214
+ // serve identity accounts must not even attempt whoAmI, so a rejecting
215
+ // client cannot affect the result. (20260911.11 A3: the gate is the
216
+ // kind's tier, not the backend type — client/edition.ts resourceServedOn.)
215
217
  const prepared = await prepareAgentExec(
216
218
  BASE_FLAGS,
217
219
  clientWithFailingWhoAmI(),
218
220
  "acme",
219
221
  undefined,
220
- { cloudBackend: false },
222
+ { accountPreferencesAvailable: false },
221
223
  );
222
224
  expect(prepared.model).toBe("");
223
225
  });
@@ -228,7 +230,7 @@ describe("prepareAgentExec account-preference model fill (oss#293 Phase 1.5)", (
228
230
  clientWithFailingWhoAmI(),
229
231
  "acme",
230
232
  undefined,
231
- { cloudBackend: true },
233
+ { accountPreferencesAvailable: true },
232
234
  );
233
235
  expect(prepared.model).toBe("");
234
236
  });
@@ -239,7 +241,7 @@ describe("prepareAgentExec account-preference model fill (oss#293 Phase 1.5)", (
239
241
  clientWithPreference(""),
240
242
  "acme",
241
243
  undefined,
242
- { cloudBackend: true },
244
+ { accountPreferencesAvailable: true },
243
245
  );
244
246
  expect(prepared.model).toBe("");
245
247
  });
@@ -252,7 +254,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
252
254
  clientWithPreferences({ defaultHarness: "cursor" }),
253
255
  "acme",
254
256
  undefined,
255
- CLOUD_RUN_OPTIONS,
257
+ SERVED_RUN_OPTIONS,
256
258
  );
257
259
  expect(prepared.harness).toBe("cursor");
258
260
  });
@@ -265,7 +267,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
265
267
  clientWithPreferences({ defaultHarness: "cursor" }),
266
268
  "acme",
267
269
  undefined,
268
- CLOUD_RUN_OPTIONS,
270
+ SERVED_RUN_OPTIONS,
269
271
  );
270
272
  expect(prepared.harness).toBe("native");
271
273
  });
@@ -279,7 +281,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
279
281
  clientWithPreferences({ defaultHarness: "cursor" }),
280
282
  "acme",
281
283
  undefined,
282
- { cloudBackend: true },
284
+ { accountPreferencesAvailable: true },
283
285
  );
284
286
  expect(prepared.harness).toBe("");
285
287
  });
@@ -290,7 +292,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
290
292
  STUB_CLIENT,
291
293
  "acme",
292
294
  undefined,
293
- { cloudBackend: false },
295
+ { accountPreferencesAvailable: false },
294
296
  );
295
297
  expect(prepared.harness).toBe("cursor");
296
298
  });
@@ -303,7 +305,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
303
305
  clientWithPreferences({ defaultHarness: "devin" }),
304
306
  "acme",
305
307
  undefined,
306
- CLOUD_RUN_OPTIONS,
308
+ SERVED_RUN_OPTIONS,
307
309
  );
308
310
  expect(prepared.harness).toBe("");
309
311
  });
@@ -314,7 +316,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
314
316
  clientWithFailingWhoAmI(),
315
317
  "acme",
316
318
  undefined,
317
- CLOUD_RUN_OPTIONS,
319
+ SERVED_RUN_OPTIONS,
318
320
  );
319
321
  expect(prepared.harness).toBe("");
320
322
  expect(prepared.model).toBe("");
@@ -327,7 +329,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
327
329
  clientWithFailingWhoAmI(),
328
330
  "acme",
329
331
  undefined,
330
- { cloudBackend: false, applyAccountHarnessDefault: true },
332
+ { accountPreferencesAvailable: false, applyAccountHarnessDefault: true },
331
333
  );
332
334
  expect(prepared.harness).toBe("");
333
335
  });
@@ -338,7 +340,7 @@ describe("prepareAgentExec harness resolution (oss#293)", () => {
338
340
  clientWithPreferences({}),
339
341
  "acme",
340
342
  undefined,
341
- CLOUD_RUN_OPTIONS,
343
+ SERVED_RUN_OPTIONS,
342
344
  );
343
345
  expect(prepared.harness).toBe("");
344
346
  });
@@ -356,7 +358,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
356
358
  clientWithPreferences(BOTH_MODELS),
357
359
  "acme",
358
360
  undefined,
359
- CLOUD_RUN_OPTIONS,
361
+ SERVED_RUN_OPTIONS,
360
362
  );
361
363
  expect(prepared.model).toBe("composer-2.5");
362
364
  });
@@ -367,7 +369,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
367
369
  clientWithPreferences({ ...BOTH_MODELS, defaultHarness: "cursor" }),
368
370
  "acme",
369
371
  undefined,
370
- CLOUD_RUN_OPTIONS,
372
+ SERVED_RUN_OPTIONS,
371
373
  );
372
374
  expect(prepared.harness).toBe("cursor");
373
375
  expect(prepared.model).toBe("composer-2.5");
@@ -379,7 +381,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
379
381
  clientWithPreferences(BOTH_MODELS),
380
382
  "acme",
381
383
  undefined,
382
- CLOUD_RUN_OPTIONS,
384
+ SERVED_RUN_OPTIONS,
383
385
  );
384
386
  expect(explicit.model).toBe("claude-sonnet-4.6");
385
387
 
@@ -388,7 +390,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
388
390
  clientWithPreferences(BOTH_MODELS),
389
391
  "acme",
390
392
  undefined,
391
- CLOUD_RUN_OPTIONS,
393
+ SERVED_RUN_OPTIONS,
392
394
  );
393
395
  expect(unset.model).toBe("claude-sonnet-4.6");
394
396
  });
@@ -399,7 +401,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
399
401
  clientWithPreferences(BOTH_MODELS),
400
402
  "acme",
401
403
  undefined,
402
- CLOUD_RUN_OPTIONS,
404
+ SERVED_RUN_OPTIONS,
403
405
  );
404
406
  expect(prepared.model).toBe("gpt-5.3");
405
407
  });
@@ -412,7 +414,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
412
414
  clientWithPreferences(BOTH_MODELS),
413
415
  "acme",
414
416
  undefined,
415
- { cloudBackend: true },
417
+ { accountPreferencesAvailable: true },
416
418
  );
417
419
  expect(prepared.harness).toBe("cursor");
418
420
  expect(prepared.model).toBe("composer-2.5");
@@ -420,7 +422,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
420
422
 
421
423
  it("serves both fills from one whoAmI round trip, and none when both flags are explicit", async () => {
422
424
  const single = countingClient({ ...BOTH_MODELS, defaultHarness: "cursor" });
423
- const filled = await prepareAgentExec(BASE_FLAGS, single.client, "acme", undefined, CLOUD_RUN_OPTIONS);
425
+ const filled = await prepareAgentExec(BASE_FLAGS, single.client, "acme", undefined, SERVED_RUN_OPTIONS);
424
426
  expect(filled.harness).toBe("cursor");
425
427
  expect(filled.model).toBe("composer-2.5");
426
428
  expect(single.calls(), "harness + model fills share one whoAmI").toBe(1);
@@ -431,7 +433,7 @@ describe("prepareAgentExec harness-aware model fill (oss#293)", () => {
431
433
  skipped.client,
432
434
  "acme",
433
435
  undefined,
434
- CLOUD_RUN_OPTIONS,
436
+ SERVED_RUN_OPTIONS,
435
437
  );
436
438
  expect(explicit.harness).toBe("native");
437
439
  expect(explicit.model).toBe("gpt-5.3");
@@ -93,18 +93,20 @@ export interface PreparedRun {
93
93
  /** Optional behavior switches for {@link prepareAgentExec}. */
94
94
  export interface PrepareAgentExecOptions {
95
95
  /**
96
- * When `true` (the caller's backend is Stigmer Cloud) and `--model` is
97
- * omitted, the model is filled from the caller's account preference
98
- * (`IdentityAccountPreferences.default_*_model` for the resolved harness)
99
- * via `whoAmI()`. Local mode has no IdentityAccount, so callers pass
100
- * `false` there and the omitted model keeps resolving to the platform
101
- * default.
96
+ * When `true` and `--model` is omitted, the model is filled from the
97
+ * caller's account preference (`IdentityAccountPreferences.default_*_model`
98
+ * for the resolved harness) via `whoAmI()`. The caller answers this from
99
+ * the connected server's edition and the kind's tier —
100
+ * `client.isResourceAvailable(ApiResourceKind.identity_account)` — never
101
+ * from the config's backend type (20260911.11 A3): a server that does not
102
+ * serve identity accounts is not even asked, and the omitted model keeps
103
+ * resolving to the platform default.
102
104
  */
103
- readonly cloudBackend?: boolean;
105
+ readonly accountPreferencesAvailable?: boolean;
104
106
  /**
105
107
  * When `true` and `--harness` is omitted, the harness is filled from the
106
108
  * caller's account preference (`IdentityAccountPreferences.default_harness`)
107
- * on cloud. `run` opts in; `draft` deliberately does not: draft executes the
109
+ * where the kind is served. `run` opts in; `draft` deliberately does not: draft executes the
108
110
  * seedpack's system creator agents (a utility flow, not the user's own
109
111
  * conversation), which are built and exercised on the native harness —
110
112
  * silently rerouting them onto the cursor engine (different toolset,
@@ -133,27 +135,38 @@ export async function prepareAgentExec(
133
135
  validateHarness(flags.harness);
134
136
 
135
137
  // Layered seeds (oss#293, DD-003): explicit flag > account preference
136
- // (cloud only) > platform default. Harness resolves first because the model
138
+ // (where the server serves identity accounts) > platform default. Harness resolves first because the model
137
139
  // fill is harness-aware: a cursor session fills from default_cursor_model,
138
140
  // everything else from default_native_model. `run` and `draft` always
139
141
  // create a NEW session (threading lives in `resume`, which does not pass
140
142
  // through here), so neither fill can ever contradict an existing session's
141
143
  // immutable harness. One whoAmI round trip serves both fills, and it is
142
144
  // skipped entirely when explicit flags leave nothing to fill.
143
- const wantsHarnessFill = flags.harness === "" && options?.applyAccountHarnessDefault === true;
145
+ const wantsHarnessFill =
146
+ flags.harness === "" && options?.applyAccountHarnessDefault === true;
144
147
  const wantsModelFill = flags.model === "";
145
148
  const accountDefaults =
146
- options?.cloudBackend === true && (wantsHarnessFill || wantsModelFill)
149
+ options?.accountPreferencesAvailable === true &&
150
+ (wantsHarnessFill || wantsModelFill)
147
151
  ? await resolveAccountExecutionDefaults(client)
148
152
  : NO_ACCOUNT_DEFAULTS;
149
- const harness = flags.harness !== "" ? flags.harness : wantsHarnessFill ? accountDefaults.harness : "";
153
+ const harness =
154
+ flags.harness !== ""
155
+ ? flags.harness
156
+ : wantsHarnessFill
157
+ ? accountDefaults.harness
158
+ : "";
150
159
  const model = wantsModelFill
151
160
  ? harness === "cursor"
152
161
  ? accountDefaults.cursorModel
153
162
  : accountDefaults.nativeModel
154
163
  : flags.model;
155
164
 
156
- const workspaceEntries = parseWorkspaceEntries(flags.workspace, flags.branch, flags.commit);
165
+ const workspaceEntries = parseWorkspaceEntries(
166
+ flags.workspace,
167
+ flags.branch,
168
+ flags.commit,
169
+ );
157
170
 
158
171
  const runtimeEnv = loadAndMergeEnv({
159
172
  envFlags: flags.env,
@@ -197,7 +210,11 @@ interface AccountExecutionDefaults {
197
210
  readonly cursorModel: string;
198
211
  }
199
212
 
200
- const NO_ACCOUNT_DEFAULTS: AccountExecutionDefaults = { harness: "", nativeModel: "", cursorModel: "" };
213
+ const NO_ACCOUNT_DEFAULTS: AccountExecutionDefaults = {
214
+ harness: "",
215
+ nativeModel: "",
216
+ cursorModel: "",
217
+ };
201
218
 
202
219
  /**
203
220
  * Best-effort read of the caller's execution defaults
@@ -206,12 +223,19 @@ const NO_ACCOUNT_DEFAULTS: AccountExecutionDefaults = { harness: "", nativeModel
206
223
  * IdentityAccount — resolves to no defaults and the run proceeds exactly as
207
224
  * unfilled flags do today: a missing preference must never fail a run.
208
225
  *
226
+ * This reads `whoAmI` directly and never the SDK's ensureMyIdentityAccount:
227
+ * a run must not create an account as a side effect, so NOT_FOUND here means
228
+ * "no preferences yet". Provisioning belongs to `auth login` and `auth
229
+ * whoami`, the commands whose purpose is the caller's identity.
230
+ *
209
231
  * The persisted harness value is allowlist-guarded to the shipped engines
210
232
  * (the same guard the web launcher's useAccountExecutionDefaults applies):
211
233
  * a client must not trust stored data it did not write, so anything outside
212
234
  * native/cursor is treated as undeclared rather than stamped on the wire.
213
235
  */
214
- async function resolveAccountExecutionDefaults(client: Stigmer): Promise<AccountExecutionDefaults> {
236
+ async function resolveAccountExecutionDefaults(
237
+ client: Stigmer,
238
+ ): Promise<AccountExecutionDefaults> {
215
239
  try {
216
240
  const account = await client.identityAccount.whoAmI();
217
241
  const prefs = account.spec?.preferences;
@@ -257,7 +281,9 @@ export function parseApprovalAction(value: string): ApprovalAction {
257
281
  */
258
282
  export function validateMode(mode: string): asserts mode is RunMode {
259
283
  if (mode !== "" && mode !== "agent" && mode !== "plan") {
260
- throw new UsageError(`invalid --mode value "${mode}": must be "agent" or "plan"`);
284
+ throw new UsageError(
285
+ `invalid --mode value "${mode}": must be "agent" or "plan"`,
286
+ );
261
287
  }
262
288
  }
263
289
 
@@ -267,7 +293,9 @@ export function validateMode(mode: string): asserts mode is RunMode {
267
293
  * server refuses fast on models without a registry fast variant; this check
268
294
  * only catches spelling errors before a network round trip.
269
295
  */
270
- export function validateServiceTier(tier: string): asserts tier is ServiceTierFlag {
296
+ export function validateServiceTier(
297
+ tier: string,
298
+ ): asserts tier is ServiceTierFlag {
271
299
  if (tier !== "" && tier !== "standard" && tier !== "fast") {
272
300
  throw new UsageError(
273
301
  `invalid --service-tier value "${tier}": must be "standard" or "fast"`,
@@ -298,7 +326,9 @@ export function validateThinking(mode: string): asserts mode is ThinkingFlag {
298
326
  * them here is honest, and this check catches spelling errors before a
299
327
  * network round trip.
300
328
  */
301
- export function validateHarness(harness: string): asserts harness is HarnessFlag {
329
+ export function validateHarness(
330
+ harness: string,
331
+ ): asserts harness is HarnessFlag {
302
332
  if (harness !== "" && harness !== "native" && harness !== "cursor") {
303
333
  throw new UsageError(
304
334
  `invalid --harness value "${harness}": must be "native" or "cursor"`,