@awak-app/simy-cli 0.5.62 → 0.5.63

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.5.62",
3
+ "version": "0.5.63",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -199,6 +199,7 @@ export async function startAgent({
199
199
  let organizationState = "unknown";
200
200
  let organizationRefresh = null;
201
201
  let pendingOrganization = null;
202
+ let desktopLoginPending = false;
202
203
  await cleanupExpiredRuns();
203
204
  for (const task of await restoreLocalTasks({ root: taskStorageRoot })) {
204
205
  localTaskRegistry.create(task);
@@ -469,7 +470,9 @@ export async function startAgent({
469
470
  })();
470
471
  return organizationRefresh;
471
472
  };
472
- const requestDesktopLogin = (organizationId = null) => {
473
+ const requestDesktopLogin = (organizationId = null, { passive = false } = {}) => {
474
+ // Reopening the verification page must keep the original nonce and scope.
475
+ if (!organizationId && pendingOrganization) return loginUrl?.toString() ?? null;
473
476
  let organizationSlug = null;
474
477
  if (organizationId) {
475
478
  if (!isSessionValid(session, Date.now(), apiOrigin)) throw new Error("Sign in before switching organizations.");
@@ -486,8 +489,10 @@ export async function startAgent({
486
489
  notifyDesktopStatusChange();
487
490
  } else if (isSessionValid(session, Date.now(), apiOrigin)) {
488
491
  if (!enforceDesktopAccess || desktopAccess.snapshot().state !== 'signed_out') return null;
489
- authNonce = randomBytes(16).toString("base64url");
490
- loginUrl = null;
492
+ if (!passive || !loginUrl) {
493
+ authNonce = randomBytes(16).toString("base64url");
494
+ loginUrl = null;
495
+ }
491
496
  }
492
497
  if (!loginUrl) {
493
498
  loginUrl = new URL("/local-cli/connect", apiOrigin);
@@ -501,6 +506,7 @@ export async function startAgent({
501
506
  loginUrl.searchParams.set('org_slug', organizationSlug);
502
507
  }
503
508
  }
509
+ if (!passive) { desktopLoginPending = true; notifyDesktopStatusChange(); }
504
510
  return loginUrl.toString();
505
511
  };
506
512
 
@@ -691,6 +697,8 @@ export async function startAgent({
691
697
  if (session?.device_id && session.token) reclaimableSession = session;
692
698
  session = null;
693
699
  loginUrl = null;
700
+ pendingOrganization = null;
701
+ desktopLoginPending = false;
694
702
  refreshAvailableCapabilities();
695
703
  requestDesktopLogin();
696
704
  }
@@ -1194,6 +1202,7 @@ export async function startAgent({
1194
1202
  await writeSession(apiOrigin, session, sessionRoot);
1195
1203
  authNonce = randomBytes(16).toString("base64url");
1196
1204
  pendingOrganization = null;
1205
+ desktopLoginPending = false;
1197
1206
  sqmEnabled = await readSqmEnabled(sessionRoot, apiOrigin, session);
1198
1207
  if (changingScope) sqmLocalReviewWorker?.resetHistory?.();
1199
1208
  organizationState = "unknown";
@@ -2156,7 +2165,7 @@ export async function startAgent({
2156
2165
  }
2157
2166
 
2158
2167
  if (!isSessionValid(session, Date.now(), apiOrigin)) {
2159
- requestDesktopLogin();
2168
+ requestDesktopLogin(null, { passive: true });
2160
2169
  if (!quiet) {
2161
2170
  console.log(`Sign in to SIMY: ${loginUrl.toString()}`);
2162
2171
  console.log(`Session file: ${sessionPath(apiOrigin, sessionRoot)}`);
@@ -2242,6 +2251,7 @@ export async function startAgent({
2242
2251
  version: CLI_VERSION,
2243
2252
  webOrigin: apiOrigin,
2244
2253
  auth,
2254
+ authorization: { pending: Boolean(pendingOrganization || desktopLoginPending) },
2245
2255
  ...(enforceDesktopAccess ? { access: { ...desktopAccess.snapshot(), revision: desktopScopeRevision() } } : {}),
2246
2256
  connection: connectionStatus.snapshot({ authenticated: auth.state === "signed_in" }),
2247
2257
  tasks: desktopTaskStatus([registry, directRegistry, localTaskRegistry]),
@@ -2256,7 +2266,8 @@ export async function startAgent({
2256
2266
  codex,
2257
2267
  claude,
2258
2268
  providers: { codex, claude },
2259
- loginUrl: auth.state === "signed_in" ? null : requestDesktopLogin(),
2269
+ // URLs stay inside the host; native UI reopens the current request by action.
2270
+ loginUrl: auth.state === "signed_in" ? null : requestDesktopLogin(null, { passive: true }),
2260
2271
  };
2261
2272
  };
2262
2273
  return {
@@ -2270,9 +2281,10 @@ export async function startAgent({
2270
2281
  shutdown,
2271
2282
  webOrigin: apiOrigin,
2272
2283
  get loginUrl() {
2273
- return requestDesktopLogin();
2284
+ return requestDesktopLogin(null, { passive: true });
2274
2285
  },
2275
2286
  requestDesktopLogin,
2287
+ getPendingAuthorizationUrl: () => pendingOrganization || desktopLoginPending ? loginUrl?.toString() ?? null : null,
2276
2288
  async refreshDesktopAccess() {
2277
2289
  const previous = desktopAccess.snapshot().state;
2278
2290
  const result = await desktopAccess.refresh(true);
@@ -2284,7 +2296,7 @@ export async function startAgent({
2284
2296
  getDesktopAccess: () => ({ ...desktopAccess.snapshot(), revision: desktopScopeRevision() }),
2285
2297
  requireDesktopAccess: authorizeDesktopWork,
2286
2298
  refreshOrganizations,
2287
- cancelOrganizationSelection() { authNonce = randomBytes(16).toString("base64url"); pendingOrganization = null; loginUrl = null; refreshAvailableCapabilities(); notifyDesktopStatusChange(); },
2299
+ cancelOrganizationSelection() { authNonce = randomBytes(16).toString("base64url"); pendingOrganization = null; desktopLoginPending = false; loginUrl = null; refreshAvailableCapabilities(); notifyDesktopStatusChange(); },
2288
2300
  async setSqmEnabled(enabled) {
2289
2301
  if (enabled) await authorizeDesktopWork();
2290
2302
  if (!isSessionValid(session, Date.now(), apiOrigin) || pendingOrganization) throw new Error("Sign in to an organization first.");
@@ -13,7 +13,7 @@ import {
13
13
  } from "./durable-local-task-steps-contract.js";
14
14
  import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
15
15
 
16
- export const CLI_VERSION = "0.5.62";
16
+ export const CLI_VERSION = "0.5.63";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function withCliContract(
@@ -7,7 +7,7 @@ export function validDesktopAccessPath(path, slug) {
7
7
  return path === expected || path === `${expected}/`;
8
8
  }
9
9
  export const desktopAccessReady = value => value?.state === 'ready';
10
- const recoveryActions = new Set(['refresh', 'reconnect', 'login', 'organization_select', 'organization_cancel', 'organizations_refresh',
10
+ const recoveryActions = new Set(['refresh', 'reconnect', 'login', 'authorization_open', 'organization_select', 'organization_cancel', 'organizations_refresh',
11
11
  'check_update', 'update', 'quit', 'delivery_stop', 'delivery_pause', 'delivery_reject',
12
12
  'desktop_access_refresh', 'desktop_access_settings', 'desktop_access_members']);
13
13
  export function desktopActionAllowed(access, command) {
@@ -39,6 +39,7 @@ const silentLogger = { log() {}, error() {} };
39
39
  const MAX_DESKTOP_FRAME_BYTES = 8 * 1024 * 1024;
40
40
 
41
41
  function desktopActionError(action, error) {
42
+ if (action === 'authorization_open' || error?.code === 'desktop_browser_unavailable') return "Could not open your browser. Open the verification link again, or check your default browser.";
42
43
  if (error?.code === "desktop_access_required") return "Sign in to an organization with SQM and Developer access before using SIMY.";
43
44
  if (action === "session_deadline_decide") return error?.code === "conflict"
44
45
  ? "Deadline changed; refresh before deciding." : "The deadline is unavailable. Refresh this conversation before deciding.";
@@ -436,16 +437,22 @@ export async function startDesktopHost({ input = process.stdin, output = process
436
437
  else if (action === "organizations_refresh") await agent.refreshOrganizations();
437
438
  else if (action === "sqm_set_enabled") await agent.setSqmEnabled(command.enabled);
438
439
  else if (action === "organization_cancel") agent.cancelOrganizationSelection();
439
- else if (action === "login" || action === "organization_select") {
440
+ else if (action === "login" || action === "organization_select" || action === "authorization_open") {
440
441
  if (action === "organization_select" && (typeof command.organizationId !== "string" || !command.organizationId)) throw new Error("Select an organization first.");
441
442
  if (action === "organization_select" && activeQueuedAction && !["refresh", "reconnect", "task_detail", "organizations_refresh", "check_update"].includes(activeQueuedAction)) {
442
443
  throw Object.assign(new Error("Wait for the current action to finish before switching organizations."), { code: "desktop_organization_busy" });
443
444
  }
444
- const url = await agent.requestDesktopLogin(action === "organization_select" ? command.organizationId : null);
445
+ const url = action === "authorization_open" ? agent.getPendingAuthorizationUrl?.()
446
+ : await agent.requestDesktopLogin(action === "organization_select" ? command.organizationId : null);
447
+ if (action === 'authorization_open' && !url) throw new Error('No pending authorization.');
445
448
  if (url) {
446
449
  if (new URL(url).origin !== webOrigin) throw new Error("Invalid login origin.");
447
- const result = await (dependencies.openLogin || openAuthorizationUrl)(url, { logger: silentLogger });
448
- if (!result.opened) { agent.cancelOrganizationSelection?.(); throw new Error("Could not open the login page."); }
450
+ // Keep the pending request after an OS/browser failure so the user can
451
+ // use the visible fallback without silently canceling authorization.
452
+ try {
453
+ const result = await (dependencies.openLogin || openAuthorizationUrl)(url, { logger: silentLogger });
454
+ if (!result.opened) throw new Error('Browser unavailable.');
455
+ } catch { throw Object.assign(new Error('Browser unavailable.'), { code: 'desktop_browser_unavailable' }); }
449
456
  }
450
457
  } else if (action === "check_update") await updater.checkNow();
451
458
  else if (action === "update") {
@@ -560,7 +567,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
560
567
  emit({ type: "result", id: command.id, ok: false, message: desktopActionError(command.action, error) });
561
568
  }
562
569
  };
563
- if (["login", "organization_select", "organization_cancel"].includes(command.action)) {
570
+ if (["login", "organization_select", "organization_cancel", "authorization_open"].includes(command.action)) {
564
571
  authorizationQueue = authorizationQueue.then(execute).catch(() => {});
565
572
  } else {
566
573
  queue = queue.then(async () => {