forkit-connect 0.1.5 → 0.1.7

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/launcher.js CHANGED
@@ -500,6 +500,74 @@ function safeStringRecordValue(record, keys) {
500
500
  }
501
501
  return null;
502
502
  }
503
+ function normalizeDiscoveryRegistrationFailure(message) {
504
+ const normalized = String(message || '').trim();
505
+ if (!normalized) {
506
+ return {
507
+ code: null,
508
+ message: 'Registration failed.',
509
+ };
510
+ }
511
+ if (normalized === 'GOVERNED_PASSPORT_CAPACITY_REACHED') {
512
+ return {
513
+ code: normalized,
514
+ message: 'Governed passport capacity is full for this account.',
515
+ nextActions: [
516
+ 'Use an existing published passport for runtime review or C2 work.',
517
+ 'Free capacity or upgrade before creating another governed registration.',
518
+ 'If you only need local observation for now, keep the item in review instead of registering it yet.',
519
+ ],
520
+ };
521
+ }
522
+ if (normalized === 'DRAFT_LIMIT_REACHED') {
523
+ return {
524
+ code: normalized,
525
+ message: 'Draft capacity is full for this account.',
526
+ nextActions: [
527
+ 'Clear an old draft or upgrade before creating another draft.',
528
+ 'Use an existing passport if you only need runtime review or C2 testing.',
529
+ ],
530
+ };
531
+ }
532
+ if (normalized === 'SIMILAR_PASSPORT_EXISTS') {
533
+ return {
534
+ code: normalized,
535
+ message: 'A similar passport already exists in Forkit. Review existing records before creating a new one.',
536
+ };
537
+ }
538
+ if (normalized === 'INVALID_DRAFT_VISIBILITY') {
539
+ return {
540
+ code: normalized,
541
+ message: 'This registration needs to start as a private draft before it can be published.',
542
+ nextActions: [
543
+ 'Keep the item in review or open the draft path first.',
544
+ 'Choose publish only when Forkit is ready to finish the passport.',
545
+ ],
546
+ };
547
+ }
548
+ if (normalized === 'WORKSPACE_PROJECT_BINDING_REQUIRED') {
549
+ return {
550
+ code: normalized,
551
+ message: 'Choose a governed workspace and project before registering this item.',
552
+ };
553
+ }
554
+ if (normalized === 'DRAFT_CREATION_NOT_ALLOWED_BY_BINDING') {
555
+ return {
556
+ code: normalized,
557
+ message: 'This binding does not currently allow draft creation. Update consent or switch scope first.',
558
+ };
559
+ }
560
+ if (normalized.startsWith('DRAFT_CREATE_FAILED:')) {
561
+ return {
562
+ code: normalized,
563
+ message: 'Forkit could not create the draft on the backend right now. Retry in a moment.',
564
+ };
565
+ }
566
+ return {
567
+ code: null,
568
+ message: normalized,
569
+ };
570
+ }
503
571
  function buildPassportHistory(binding, events) {
504
572
  const modelParts = parseModelKeyParts(binding.modelKey);
505
573
  const modelName = (0, heartbeat_1.readBoundModelName)(binding.modelKey);
@@ -1949,6 +2017,34 @@ function renderLauncherHtml(launcherToken) {
1949
2017
  align-items: start;
1950
2018
  }
1951
2019
 
2020
+ .review-resolution-actions {
2021
+ display: grid;
2022
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2023
+ gap: 10px;
2024
+ }
2025
+
2026
+ .review-resolution-actions[hidden] {
2027
+ display: none;
2028
+ }
2029
+
2030
+ .review-resolution-button {
2031
+ min-height: 44px;
2032
+ border-radius: 14px;
2033
+ border: 1px solid rgba(241, 235, 223, 0.12);
2034
+ background: rgba(255,255,255,0.04);
2035
+ color: #fff8ef;
2036
+ font-size: 0.88rem;
2037
+ font-weight: 600;
2038
+ cursor: pointer;
2039
+ padding: 0 12px;
2040
+ transition: background 160ms ease, border-color 160ms ease;
2041
+ }
2042
+
2043
+ .review-resolution-button:hover {
2044
+ background: rgba(255,255,255,0.08);
2045
+ border-color: rgba(157, 238, 245, 0.18);
2046
+ }
2047
+
1952
2048
  .quick-review-primary {
1953
2049
  min-height: 52px;
1954
2050
  border: 1px solid rgba(157, 238, 245, 0.2);
@@ -6391,6 +6487,10 @@ function renderLauncherHtml(launcherToken) {
6391
6487
  </div>
6392
6488
  </div>
6393
6489
  </div>
6490
+ <div class="review-resolution-actions" id="quick-review-resolution" hidden>
6491
+ <button class="review-resolution-button" id="quick-review-resolution-primary" type="button" hidden></button>
6492
+ <button class="review-resolution-button" id="quick-review-resolution-secondary" type="button" hidden></button>
6493
+ </div>
6394
6494
  </section>
6395
6495
  </div>
6396
6496
  </div>
@@ -6770,6 +6870,10 @@ function renderLauncherHtml(launcherToken) {
6770
6870
  <button class="secondary" id="discovery-review-defer" type="button" disabled>Defer 24h</button>
6771
6871
  <button class="danger" id="discovery-review-ignore" type="button" disabled>Ignore</button>
6772
6872
  </div>
6873
+ <div class="review-resolution-actions" id="discovery-review-resolution" hidden>
6874
+ <button class="review-resolution-button" id="discovery-review-resolution-primary" type="button" hidden></button>
6875
+ <button class="review-resolution-button" id="discovery-review-resolution-secondary" type="button" hidden></button>
6876
+ </div>
6773
6877
  </section>
6774
6878
 
6775
6879
  <ul class="discovery-note-list">
@@ -7274,6 +7378,9 @@ function renderLauncherHtml(launcherToken) {
7274
7378
  if (!match) return false;
7275
7379
  selectedPassportId = match.id;
7276
7380
  passportHistoryVisible = Boolean(options && options.showHistory);
7381
+ selectedWorkspaceScope = ALL_SCOPE_VALUE;
7382
+ selectedProjectScope = ALL_SCOPE_VALUE;
7383
+ renderGlobalLaunchHeaderAndCards();
7277
7384
  setView('passports', { skipRouteUpdate: true });
7278
7385
  renderPassportsRows();
7279
7386
  updateRoute('passports', { passport: match.gaid || match.id, ...(passportHistoryVisible ? { focus: 'history' } : {}) });
@@ -7298,6 +7405,29 @@ function renderLauncherHtml(launcherToken) {
7298
7405
  return false;
7299
7406
  }
7300
7407
 
7408
+ async function completeRegistrationSuccess(item, result) {
7409
+ const message = result && typeof result.message === 'string' && result.message.trim()
7410
+ ? result.message.trim()
7411
+ : 'Registration updated.';
7412
+ await refreshAll();
7413
+ const preferredPassportGaid = String(result && (result.passportGaid || result.gaid) || '').trim();
7414
+ if (preferredPassportGaid && focusPassportByGaid(preferredPassportGaid, { showHistory: false })) {
7415
+ setActivityMessage(message, 'ok');
7416
+ return true;
7417
+ }
7418
+ if (item && await openDiscoveryContext({
7419
+ ...item,
7420
+ passportGaid: preferredPassportGaid || item.passportGaid,
7421
+ matchedPassportGaid: preferredPassportGaid || item.matchedPassportGaid,
7422
+ })) {
7423
+ setActivityMessage(message, 'ok');
7424
+ return true;
7425
+ }
7426
+ setView('passports');
7427
+ setActivityMessage(message, 'ok');
7428
+ return false;
7429
+ }
7430
+
7301
7431
  function getSelectedDiscoveryItem(itemsOverride) {
7302
7432
  const items = Array.isArray(itemsOverride)
7303
7433
  ? itemsOverride
@@ -7357,6 +7487,206 @@ function renderLauncherHtml(launcherToken) {
7357
7487
  status.className = 'quick-review-status' + (tone ? ' ' + tone : '');
7358
7488
  }
7359
7489
 
7490
+ function resetReviewResolutionActions(prefix) {
7491
+ setReviewResolutionActions(prefix, []);
7492
+ }
7493
+
7494
+ function derivePassportResolutionQuery(item) {
7495
+ if (!item || typeof item !== 'object') return '';
7496
+ return [
7497
+ item.modelName,
7498
+ item.name,
7499
+ item.selector,
7500
+ item.sourceMeta,
7501
+ ].map((value) => String(value || '').trim()).find(Boolean) || '';
7502
+ }
7503
+
7504
+ function getReviewPrimaryLabel(item) {
7505
+ if (!item) return 'Review';
7506
+ if (item.kind === 'runtime') {
7507
+ return item.statusTone === 'error' ? 'Open runtime fix' : 'Open runtime review';
7508
+ }
7509
+ if (item.statusLabel === 'Draft created') {
7510
+ return 'Open draft';
7511
+ }
7512
+ if (item.passportGaid) {
7513
+ return 'Open passport';
7514
+ }
7515
+ if (item.matchedPassportGaid) {
7516
+ return 'Use existing passport';
7517
+ }
7518
+ if (item.actionLabel === 'Retry') {
7519
+ return 'Retry registration';
7520
+ }
7521
+ if (item.actionLabel === 'Open' || item.actionLabel === 'Review') {
7522
+ return 'Review in context';
7523
+ }
7524
+ return 'Approve & register';
7525
+ }
7526
+
7527
+ function getReviewActionSummary(item) {
7528
+ if (!item) return 'Review';
7529
+ if (item.kind === 'runtime') {
7530
+ return item.statusTone === 'error' ? 'Resolve runtime attention' : 'Open runtime lane';
7531
+ }
7532
+ if (item.statusLabel === 'Draft created') {
7533
+ return 'Continue the existing draft';
7534
+ }
7535
+ if (item.passportGaid) {
7536
+ return 'Open the linked passport';
7537
+ }
7538
+ if (item.matchedPassportGaid) {
7539
+ return 'Use the matching passport';
7540
+ }
7541
+ if (item.actionLabel === 'Retry') {
7542
+ return 'Retry registration here';
7543
+ }
7544
+ if (item.actionLabel === 'Open' || item.actionLabel === 'Review') {
7545
+ return 'Open full review';
7546
+ }
7547
+ return 'Approve and register here';
7548
+ }
7549
+
7550
+ function getReviewDetailText(item) {
7551
+ if (!item) return 'Review local metadata, then decide what happens next.';
7552
+ if (item.kind === 'runtime') {
7553
+ return item.statusTone === 'error'
7554
+ ? 'Runtime attention is required before linked models can keep registering cleanly.'
7555
+ : 'Open runtime review to confirm health, scope, and connected models.';
7556
+ }
7557
+ if (item.statusLabel === 'Draft created') {
7558
+ return 'A draft already exists. Continue it in Passports instead of creating another one.';
7559
+ }
7560
+ if (item.matchedPassportGaid && !item.passportGaid) {
7561
+ return 'Forkit Connect found a matching passport. Reuse it before creating anything new.';
7562
+ }
7563
+ if (item.passportGaid) {
7564
+ return 'This item is already linked. Open the passport to inspect lineage, scope, and runtime state.';
7565
+ }
7566
+ if (item.actionLabel === 'Retry') {
7567
+ return 'Registration failed earlier. Review the latest local metadata and retry when the scope is ready.';
7568
+ }
7569
+ return item.detailSummary || item.statusMeta || 'Review local metadata, then decide what happens next.';
7570
+ }
7571
+
7572
+ function formatLauncherActionFeedback(result, fallbackMessage) {
7573
+ const base = result && typeof result.message === 'string' && result.message.trim()
7574
+ ? result.message.trim()
7575
+ : fallbackMessage;
7576
+ const nextActions = result && Array.isArray(result.nextActions)
7577
+ ? result.nextActions.map((item) => String(item || '').trim()).filter(Boolean)
7578
+ : [];
7579
+ const resolutionActions = result && result.code === 'GOVERNED_PASSPORT_CAPACITY_REACHED'
7580
+ ? [{ id: 'passports', label: 'Use existing passport' }]
7581
+ : result && result.code === 'DRAFT_LIMIT_REACHED'
7582
+ ? [{ id: 'passports', label: 'Review existing drafts' }]
7583
+ : result && (result.code === 'WORKSPACE_PROJECT_BINDING_REQUIRED' || result.code === 'DRAFT_CREATION_NOT_ALLOWED_BY_BINDING')
7584
+ ? [{ id: 'scope', label: 'Set workspace' }]
7585
+ : result && result.code === 'SIMILAR_PASSPORT_EXISTS'
7586
+ ? [{ id: 'passports', label: 'Open matching passport' }]
7587
+ : [];
7588
+ return {
7589
+ message: nextActions.length ? (base + ' Next: ' + nextActions[0]) : base,
7590
+ activity: nextActions.length ? (base + ' Next: ' + nextActions.join(' ')) : base,
7591
+ actionSummary: result && result.code === 'GOVERNED_PASSPORT_CAPACITY_REACHED'
7592
+ ? 'Capacity full · use an existing passport or free space'
7593
+ : result && result.code === 'DRAFT_LIMIT_REACHED'
7594
+ ? 'Draft capacity full · use an existing passport, clear a draft, or upgrade'
7595
+ : result && result.code === 'SIMILAR_PASSPORT_EXISTS'
7596
+ ? 'Matching passport exists · reuse it instead of creating another'
7597
+ : result && result.code === 'INVALID_DRAFT_VISIBILITY'
7598
+ ? 'Draft first · keep this registration private until it is ready to publish'
7599
+ : result && (result.code === 'WORKSPACE_PROJECT_BINDING_REQUIRED' || result.code === 'DRAFT_CREATION_NOT_ALLOWED_BY_BINDING')
7600
+ ? 'Scope required · choose workspace and project'
7601
+ : null,
7602
+ resolutionActions,
7603
+ };
7604
+ }
7605
+
7606
+ function setReviewResolutionActions(prefix, actions) {
7607
+ const wrapper = document.getElementById(prefix + '-resolution');
7608
+ const primary = document.getElementById(prefix + '-resolution-primary');
7609
+ const secondary = document.getElementById(prefix + '-resolution-secondary');
7610
+ if (!wrapper || !primary || !secondary) return;
7611
+ const normalized = Array.isArray(actions) ? actions.slice(0, 2) : [];
7612
+ [primary, secondary].forEach((button, index) => {
7613
+ const action = normalized[index];
7614
+ if (!action) {
7615
+ button.hidden = true;
7616
+ button.dataset.action = '';
7617
+ button.textContent = '';
7618
+ return;
7619
+ }
7620
+ button.hidden = false;
7621
+ button.dataset.action = String(action.id || '').trim();
7622
+ button.textContent = String(action.label || '').trim();
7623
+ });
7624
+ wrapper.hidden = normalized.length === 0;
7625
+ }
7626
+
7627
+ async function handleReviewResolutionAction(prefix, action) {
7628
+ const item = prefix === 'quick-review' ? getQuickReviewItem() : getSelectedDiscoveryItem();
7629
+ if (action === 'passports') {
7630
+ if (!latestPassports) {
7631
+ await refreshPassports();
7632
+ }
7633
+ const preferredPassportGaid = String(item && (item.passportGaid || item.matchedPassportGaid) || '').trim();
7634
+ if (preferredPassportGaid && focusPassportByGaid(preferredPassportGaid, { showHistory: true })) {
7635
+ if (prefix === 'quick-review') {
7636
+ setQuickReviewPanelOpen(false);
7637
+ }
7638
+ setActivityMessage('Opened the matching local passport for review.', 'ok');
7639
+ return;
7640
+ }
7641
+ const query = derivePassportResolutionQuery(item);
7642
+ const searchInput = document.getElementById('passports-search');
7643
+ const statusFilter = document.getElementById('passports-status-filter');
7644
+ if (searchInput) {
7645
+ searchInput.value = query;
7646
+ }
7647
+ if (statusFilter) {
7648
+ statusFilter.value = '';
7649
+ }
7650
+ selectedWorkspaceScope = ALL_SCOPE_VALUE;
7651
+ selectedProjectScope = ALL_SCOPE_VALUE;
7652
+ renderGlobalLaunchHeaderAndCards();
7653
+ setView('passports');
7654
+ renderPassportsRows();
7655
+ const filtered = getPassportItemsFiltered();
7656
+ if (filtered.length > 0) {
7657
+ selectedPassportId = filtered[0].id;
7658
+ renderPassportsRows();
7659
+ setActivityMessage(
7660
+ query
7661
+ ? ('Showing local passports that match ' + query + '.')
7662
+ : 'Showing available local passports for reuse.',
7663
+ 'ok',
7664
+ );
7665
+ } else {
7666
+ setActivityMessage(
7667
+ query
7668
+ ? ('No local passport matches ' + query + ' yet. Check existing Forkit.dev records or free local capacity first.')
7669
+ : 'No local passports are available yet. Check existing Forkit.dev records or free local capacity first.',
7670
+ 'warn',
7671
+ );
7672
+ }
7673
+ if (searchInput) {
7674
+ searchInput.focus();
7675
+ searchInput.select();
7676
+ }
7677
+ if (prefix === 'quick-review') {
7678
+ setQuickReviewPanelOpen(false);
7679
+ }
7680
+ return;
7681
+ }
7682
+ if (action === 'scope' && item) {
7683
+ if (prefix === 'quick-review') {
7684
+ setQuickReviewPanelOpen(false);
7685
+ }
7686
+ await openRegistrationScopeDialog(item);
7687
+ }
7688
+ }
7689
+
7360
7690
  function setQuickReviewOptionsOpen(open) {
7361
7691
  const button = document.getElementById('quick-review-options');
7362
7692
  const menu = document.getElementById('quick-review-options-menu');
@@ -7459,6 +7789,7 @@ function renderLauncherHtml(launcherToken) {
7459
7789
  anchor.hidden = true;
7460
7790
  setQuickReviewPanelOpen(false);
7461
7791
  quickReviewScopeCacheKey = null;
7792
+ resetReviewResolutionActions('quick-review');
7462
7793
  return;
7463
7794
  }
7464
7795
 
@@ -7470,22 +7801,14 @@ function renderLauncherHtml(launcherToken) {
7470
7801
  setText('quick-review-title', item.name);
7471
7802
  setText('quick-review-badge', item.kind === 'runtime' ? 'Runtime' : item.kind === 'agent' ? 'Agent' : 'Model');
7472
7803
  setText('quick-review-meta', item.subtitle + ' · ' + item.source);
7473
- setText('quick-review-detail', item.detailSummary || item.statusMeta || 'Review local metadata, then decide what happens next.');
7804
+ setText('quick-review-detail', getReviewDetailText(item));
7474
7805
  setQuickReviewStatus(item.statusLabel + (item.statusMeta ? ' · ' + item.statusMeta : ''), item.statusTone === 'muted' ? '' : item.statusTone);
7806
+ resetReviewResolutionActions('quick-review');
7475
7807
 
7476
- const opensExisting = item.kind === 'runtime' || item.actionLabel === 'Open' || item.actionLabel === 'Review';
7477
- const primaryLabel = item.kind === 'runtime'
7478
- ? 'Open runtime review'
7479
- : opensExisting
7480
- ? (item.passportGaid || item.matchedPassportGaid ? 'Open passport' : 'Review in context')
7481
- : 'Approve & register';
7808
+ const primaryLabel = getReviewPrimaryLabel(item);
7482
7809
  setText(
7483
7810
  'quick-review-action-summary',
7484
- item.kind === 'runtime'
7485
- ? 'Open runtime lane'
7486
- : opensExisting
7487
- ? (item.passportGaid || item.matchedPassportGaid ? 'Open linked passport' : 'Open full review')
7488
- : 'Approve and register here',
7811
+ getReviewActionSummary(item),
7489
7812
  );
7490
7813
  if (primaryButton) {
7491
7814
  primaryButton.textContent = primaryLabel;
@@ -7594,17 +7917,19 @@ function renderLauncherHtml(launcherToken) {
7594
7917
  });
7595
7918
  if (primaryButton) primaryButton.disabled = false;
7596
7919
  if (!result.ok) {
7597
- setQuickReviewStatus(result.message || 'Registration failed.', 'warn');
7598
- setActivityMessage(result.message || 'Registration failed.', 'warn');
7920
+ const feedback = formatLauncherActionFeedback(result, 'Registration failed.');
7921
+ setQuickReviewStatus(feedback.message, 'warn');
7922
+ setActivityMessage(feedback.activity, 'warn');
7923
+ if (feedback.actionSummary) {
7924
+ setText('quick-review-action-summary', feedback.actionSummary);
7925
+ }
7926
+ setReviewResolutionActions('quick-review', feedback.resolutionActions || []);
7599
7927
  return;
7600
7928
  }
7929
+ resetReviewResolutionActions('quick-review');
7601
7930
  setQuickReviewStatus(result.message || 'Registration updated.', 'ok');
7602
- setActivityMessage(result.message || 'Registration updated.', 'ok');
7603
- await refreshAll();
7604
7931
  setQuickReviewPanelOpen(false);
7605
- if (result.gaid || result.passportGaid) {
7606
- await openDiscoveryContext(item);
7607
- }
7932
+ await completeRegistrationSuccess(item, result);
7608
7933
  }
7609
7934
 
7610
7935
  async function submitQuickReviewDefer() {
@@ -7620,6 +7945,7 @@ function renderLauncherHtml(launcherToken) {
7620
7945
  setQuickReviewStatus(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7621
7946
  setActivityMessage(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7622
7947
  await refreshAll();
7948
+ resetReviewResolutionActions('quick-review');
7623
7949
  setQuickReviewOptionsOpen(false);
7624
7950
  }
7625
7951
 
@@ -7636,6 +7962,7 @@ function renderLauncherHtml(launcherToken) {
7636
7962
  setQuickReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7637
7963
  setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7638
7964
  await refreshAll();
7965
+ resetReviewResolutionActions('quick-review');
7639
7966
  setQuickReviewOptionsOpen(false);
7640
7967
  }
7641
7968
 
@@ -7703,26 +8030,23 @@ function renderLauncherHtml(launcherToken) {
7703
8030
  setText('discovery-review-detail', 'Registering creates or updates a Forkit Passport. Nothing is published automatically.');
7704
8031
  if (scopeWrap) scopeWrap.hidden = true;
7705
8032
  setDiscoveryReviewStatus('Select an item to continue.', '');
8033
+ resetReviewResolutionActions('discovery-review');
7706
8034
  setDiscoveryReviewButtons({ primaryLabel: 'Review', primaryEnabled: false, deferEnabled: false, ignoreEnabled: false });
7707
8035
  return;
7708
8036
  }
7709
8037
 
7710
8038
  const typeLabel = item.typeLabel || item.kind;
7711
8039
  const needsScope = canReviewRegister(item);
7712
- const opensExisting = item.actionLabel === 'Open' || item.actionLabel === 'Review';
7713
- const primaryLabel = item.kind === 'runtime'
7714
- ? 'Open runtime review'
7715
- : opensExisting
7716
- ? (item.passportGaid || item.matchedPassportGaid ? 'Open passport' : 'Review in context')
7717
- : 'Approve and register';
8040
+ const primaryLabel = getReviewPrimaryLabel(item);
7718
8041
 
7719
8042
  setText('discovery-review-kicker', typeLabel);
7720
8043
  setText('discovery-review-title', item.name);
7721
8044
  setText('discovery-review-meta', item.subtitle + ' · ' + item.source);
7722
- setText('discovery-review-detail', item.detailSummary || item.statusMeta || 'Review local metadata, then decide what happens next.');
8045
+ setText('discovery-review-detail', getReviewDetailText(item));
7723
8046
  setDiscoveryReviewStatus(item.statusLabel + (item.statusMeta ? ' · ' + item.statusMeta : ''), item.statusTone === 'muted' ? '' : item.statusTone);
8047
+ resetReviewResolutionActions('discovery-review');
7724
8048
  setDiscoveryReviewButtons({
7725
- primaryLabel,
8049
+ primaryLabel: getReviewPrimaryLabel(item),
7726
8050
  primaryEnabled: item.inboxGroup !== 'ignored',
7727
8051
  deferEnabled: canReviewDefer(item),
7728
8052
  ignoreEnabled: canReviewIgnore(item),
@@ -7812,16 +8136,15 @@ function renderLauncherHtml(launcherToken) {
7812
8136
  });
7813
8137
  if (primaryButton) primaryButton.disabled = false;
7814
8138
  if (!result.ok) {
7815
- setDiscoveryReviewStatus(result.message || 'Registration failed.', 'warn');
7816
- setActivityMessage(result.message || 'Registration failed.', 'warn');
8139
+ const feedback = formatLauncherActionFeedback(result, 'Registration failed.');
8140
+ setDiscoveryReviewStatus(feedback.message, 'warn');
8141
+ setActivityMessage(feedback.activity, 'warn');
8142
+ setReviewResolutionActions('discovery-review', feedback.resolutionActions || []);
7817
8143
  return;
7818
8144
  }
8145
+ resetReviewResolutionActions('discovery-review');
7819
8146
  setDiscoveryReviewStatus(result.message || 'Registration updated.', 'ok');
7820
- setActivityMessage(result.message || 'Registration updated.', 'ok');
7821
- await refreshAll();
7822
- if (result.gaid || result.passportGaid) {
7823
- await openDiscoveryContext(item);
7824
- }
8147
+ await completeRegistrationSuccess(item, result);
7825
8148
  }
7826
8149
 
7827
8150
  async function submitDiscoveryReviewDefer() {
@@ -7837,6 +8160,7 @@ function renderLauncherHtml(launcherToken) {
7837
8160
  setDiscoveryReviewStatus(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7838
8161
  setActivityMessage(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7839
8162
  await refreshAll();
8163
+ resetReviewResolutionActions('discovery-review');
7840
8164
  }
7841
8165
 
7842
8166
  async function submitDiscoveryReviewIgnore() {
@@ -7852,6 +8176,7 @@ function renderLauncherHtml(launcherToken) {
7852
8176
  setDiscoveryReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7853
8177
  setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7854
8178
  await refreshAll();
8179
+ resetReviewResolutionActions('discovery-review');
7855
8180
  }
7856
8181
 
7857
8182
  function setProfileAvatar(rootId, imageId, initialsId, initials, avatarUrl) {
@@ -8562,6 +8887,30 @@ function renderLauncherHtml(launcherToken) {
8562
8887
  });
8563
8888
  }
8564
8889
 
8890
+ function focusScopeField(fieldId) {
8891
+ const field = document.getElementById(fieldId);
8892
+ if (field && typeof field.focus === 'function') {
8893
+ window.setTimeout(() => {
8894
+ field.focus();
8895
+ if (typeof field.select === 'function') {
8896
+ field.select();
8897
+ }
8898
+ }, 0);
8899
+ }
8900
+ }
8901
+
8902
+ function openScopeCreateWorkspaceFlow(statusMessage, tone) {
8903
+ toggleScopeCreatePanel('scope-create-workspace-panel', true);
8904
+ setScopeStatus(statusMessage || 'Create a governed workspace for this registration flow.', tone || '');
8905
+ focusScopeField('scope-create-workspace-name');
8906
+ }
8907
+
8908
+ function openScopeCreateProjectFlow(statusMessage, tone) {
8909
+ toggleScopeCreatePanel('scope-create-project-panel', true);
8910
+ setScopeStatus(statusMessage || 'Create a governed project inside the selected workspace.', tone || '');
8911
+ focusScopeField('scope-create-project-only-name');
8912
+ }
8913
+
8565
8914
  function setScopeCreateButtonsEnabled(enabled) {
8566
8915
  ['scope-open-create-workspace', 'scope-open-create-project'].forEach((id) => {
8567
8916
  const button = document.getElementById(id);
@@ -8574,11 +8923,11 @@ function renderLauncherHtml(launcherToken) {
8574
8923
  async function loadScopeProjects(workspaceId, selectedProjectId) {
8575
8924
  const projectSelect = document.getElementById('scope-project-select');
8576
8925
  const registerButton = document.getElementById('scope-register-button');
8577
- if (!projectSelect) return;
8926
+ if (!projectSelect) return false;
8578
8927
  if (!workspaceId) {
8579
8928
  setScopeOptions(projectSelect, [{ id: '', label: 'Account scope / no project' }], '');
8580
8929
  if (registerButton) registerButton.disabled = false;
8581
- return;
8930
+ return true;
8582
8931
  }
8583
8932
  setScopeOptions(projectSelect, [{ id: '', label: 'Loading projects...' }], '');
8584
8933
  try {
@@ -8591,21 +8940,37 @@ function renderLauncherHtml(launcherToken) {
8591
8940
  setScopeOptions(projectSelect, options, selectedProjectId || (options[0] && options[0].id) || '');
8592
8941
  if (!payload.ok) {
8593
8942
  setScopeStatus(payload.message || 'Project list unavailable. Create a project here, then retry.', 'warn');
8943
+ return false;
8594
8944
  } else if (!projects.length) {
8595
- setScopeStatus('No governed project exists in this workspace yet. Create one here to continue.', 'warn');
8945
+ openScopeCreateProjectFlow('No governed project exists in this workspace yet. Create one here to continue.', 'warn');
8946
+ return false;
8596
8947
  }
8597
8948
  if (registerButton) {
8598
8949
  registerButton.disabled = !projects.length;
8599
8950
  }
8951
+ return projects.length > 0;
8600
8952
  } catch {
8601
8953
  setScopeOptions(projectSelect, [{ id: '', label: 'Project list unavailable' }], '');
8602
8954
  setScopeStatus('Project list unavailable. Create a project here, then retry.', 'warn');
8603
8955
  if (registerButton) {
8604
8956
  registerButton.disabled = true;
8605
8957
  }
8958
+ return false;
8606
8959
  }
8607
8960
  }
8608
8961
 
8962
+ async function continuePendingScopeRegistration(statusMessage) {
8963
+ const item = pendingDiscoveryRegistration;
8964
+ if (!item) return false;
8965
+ const workspaceSelect = document.getElementById('scope-workspace-select');
8966
+ const projectSelect = document.getElementById('scope-project-select');
8967
+ const workspaceId = workspaceSelect ? String(workspaceSelect.value || '').trim() : '';
8968
+ const projectId = projectSelect ? String(projectSelect.value || '').trim() : '';
8969
+ if (!workspaceId || !projectId) return false;
8970
+ setScopeStatus(statusMessage || 'Continuing registration…', 'warn');
8971
+ return submitRegistrationScopeDialog();
8972
+ }
8973
+
8609
8974
  async function createScopeWorkspace() {
8610
8975
  const nameInput = document.getElementById('scope-create-workspace-name');
8611
8976
  const descriptionInput = document.getElementById('scope-create-workspace-description');
@@ -8638,12 +9003,20 @@ function renderLauncherHtml(launcherToken) {
8638
9003
  if (firstProjectInput) firstProjectInput.value = '';
8639
9004
  toggleScopeCreatePanel(null, false);
8640
9005
  const workspaceSelect = document.getElementById('scope-workspace-select');
9006
+ const projectSelect = document.getElementById('scope-project-select');
8641
9007
  const createdWorkspaceId = result.workspace && result.workspace.id ? result.workspace.id : '';
9008
+ const createdProjectId = result.project && result.project.id ? result.project.id : '';
8642
9009
  scopeAccessSnapshot = result.scope || scopeAccessSnapshot;
8643
9010
  if (workspaceSelect && createdWorkspaceId) {
8644
9011
  await openRegistrationScopeDialog(pendingDiscoveryRegistration);
8645
9012
  workspaceSelect.value = createdWorkspaceId;
8646
- await loadScopeProjects(createdWorkspaceId, result.project && result.project.id ? result.project.id : '');
9013
+ const hasProjects = await loadScopeProjects(createdWorkspaceId, createdProjectId);
9014
+ if (projectSelect && createdProjectId) {
9015
+ projectSelect.value = createdProjectId;
9016
+ }
9017
+ if (hasProjects && createdProjectId && await continuePendingScopeRegistration('Workspace and project created. Continuing registration...')) {
9018
+ return;
9019
+ }
8647
9020
  }
8648
9021
  setScopeStatus(result.message || 'Workspace created.', 'ok');
8649
9022
  }
@@ -8681,10 +9054,14 @@ function renderLauncherHtml(launcherToken) {
8681
9054
  if (nameInput) nameInput.value = '';
8682
9055
  if (descriptionInput) descriptionInput.value = '';
8683
9056
  toggleScopeCreatePanel(null, false);
8684
- await loadScopeProjects(workspaceId, result.project && result.project.id ? result.project.id : '');
9057
+ const createdProjectId = result.project && result.project.id ? result.project.id : '';
9058
+ const hasProjects = await loadScopeProjects(workspaceId, createdProjectId);
8685
9059
  const projectSelect = document.getElementById('scope-project-select');
8686
- if (projectSelect && result.project && result.project.id) {
8687
- projectSelect.value = result.project.id;
9060
+ if (projectSelect && createdProjectId) {
9061
+ projectSelect.value = createdProjectId;
9062
+ }
9063
+ if (hasProjects && createdProjectId && await continuePendingScopeRegistration('Project created. Continuing registration...')) {
9064
+ return;
8688
9065
  }
8689
9066
  setScopeStatus(result.message || 'Project created.', 'ok');
8690
9067
  }
@@ -8730,18 +9107,20 @@ function renderLauncherHtml(launcherToken) {
8730
9107
  if (governedMode && !options.length) {
8731
9108
  options.push({ id: '', label: 'No governed workspaces yet' });
8732
9109
  registerButton.disabled = true;
8733
- setScopeStatus('No governed workspace exists yet. Create one here to continue.', 'warn');
9110
+ openScopeCreateWorkspaceFlow('No governed workspace exists yet. Create one here to continue.', 'warn');
8734
9111
  }
8735
9112
  const selectedWorkspace = payload.currentWorkspaceId || (options[0] && options[0].id) || '';
8736
9113
  setScopeOptions(workspaceSelect, options, selectedWorkspace);
8737
- await loadScopeProjects(workspaceSelect.value, payload.currentProjectId || '');
8738
- registerButton.disabled = !payload.ok || (governedMode && !String(workspaceSelect.value || '').trim());
8739
- setScopeStatus(
8740
- payload.ok
8741
- ? (governedMode ? 'Ready to register in governed scope.' : 'Ready to register.')
8742
- : (payload.message || (governedMode ? 'Workspace list unavailable.' : 'Workspace list unavailable. Account scope is still available.')),
8743
- payload.ok ? '' : 'warn',
8744
- );
9114
+ const hasProjects = await loadScopeProjects(workspaceSelect.value, payload.currentProjectId || '');
9115
+ registerButton.disabled = !payload.ok || (governedMode && (!String(workspaceSelect.value || '').trim() || !hasProjects));
9116
+ if (payload.ok && (!governedMode || hasProjects)) {
9117
+ setScopeStatus(governedMode ? 'Ready to register in governed scope.' : 'Ready to register.', '');
9118
+ } else if (!payload.ok) {
9119
+ setScopeStatus(
9120
+ payload.message || (governedMode ? 'Workspace list unavailable.' : 'Workspace list unavailable. Account scope is still available.'),
9121
+ 'warn',
9122
+ );
9123
+ }
8745
9124
  } catch {
8746
9125
  setScopeOptions(workspaceSelect, governedMode ? [] : [{ id: '', label: 'Account scope / no workspace' }], '');
8747
9126
  await loadScopeProjects(governedMode ? (workspaceSelect.value || '') : '', '');
@@ -8761,7 +9140,7 @@ function renderLauncherHtml(launcherToken) {
8761
9140
 
8762
9141
  async function submitRegistrationScopeDialog() {
8763
9142
  const item = pendingDiscoveryRegistration;
8764
- if (!item) return;
9143
+ if (!item) return false;
8765
9144
  const workspaceSelect = document.getElementById('scope-workspace-select');
8766
9145
  const projectSelect = document.getElementById('scope-project-select');
8767
9146
  const registerButton = document.getElementById('scope-register-button');
@@ -8769,7 +9148,7 @@ function renderLauncherHtml(launcherToken) {
8769
9148
  const projectId = projectSelect ? projectSelect.value : '';
8770
9149
  if (workspaceId && !projectId) {
8771
9150
  setScopeStatus('Select a project for the chosen workspace, or use account scope.', 'warn');
8772
- return;
9151
+ return false;
8773
9152
  }
8774
9153
  if (registerButton) registerButton.disabled = true;
8775
9154
  const endpoint = item.kind === 'agent' ? '/api/discovery/connect-agent' : '/api/discovery/connect-model';
@@ -8781,14 +9160,14 @@ function renderLauncherHtml(launcherToken) {
8781
9160
  });
8782
9161
  if (registerButton) registerButton.disabled = false;
8783
9162
  if (!result.ok) {
8784
- setScopeStatus(result.message || 'Registration failed.', 'warn');
8785
- setActivityMessage(result.message || 'Registration failed.', 'warn');
8786
- return;
9163
+ const feedback = formatLauncherActionFeedback(result, 'Registration failed.');
9164
+ setScopeStatus(feedback.message, 'warn');
9165
+ setActivityMessage(feedback.activity, 'warn');
9166
+ return false;
8787
9167
  }
8788
9168
  closeRegistrationScopeDialog();
8789
- setActivityMessage(result.message || 'Registration updated.', 'ok');
8790
- await refreshAll();
8791
- setView('passports');
9169
+ await completeRegistrationSuccess(item, result);
9170
+ return true;
8792
9171
  }
8793
9172
 
8794
9173
  const VIEW_TITLES = {
@@ -9640,6 +10019,18 @@ function renderLauncherHtml(launcherToken) {
9640
10019
  void submitQuickReviewPrimary();
9641
10020
  });
9642
10021
  }
10022
+ const quickReviewResolutionPrimary = document.getElementById('quick-review-resolution-primary');
10023
+ if (quickReviewResolutionPrimary) {
10024
+ quickReviewResolutionPrimary.addEventListener('click', () => {
10025
+ void handleReviewResolutionAction('quick-review', String(quickReviewResolutionPrimary.dataset.action || '').trim());
10026
+ });
10027
+ }
10028
+ const quickReviewResolutionSecondary = document.getElementById('quick-review-resolution-secondary');
10029
+ if (quickReviewResolutionSecondary) {
10030
+ quickReviewResolutionSecondary.addEventListener('click', () => {
10031
+ void handleReviewResolutionAction('quick-review', String(quickReviewResolutionSecondary.dataset.action || '').trim());
10032
+ });
10033
+ }
9643
10034
  const quickReviewOptionsButton = document.getElementById('quick-review-options');
9644
10035
  if (quickReviewOptionsButton) {
9645
10036
  quickReviewOptionsButton.addEventListener('click', (event) => {
@@ -9706,6 +10097,18 @@ function renderLauncherHtml(launcherToken) {
9706
10097
  void submitDiscoveryReviewPrimary();
9707
10098
  });
9708
10099
  }
10100
+ const discoveryReviewResolutionPrimary = document.getElementById('discovery-review-resolution-primary');
10101
+ if (discoveryReviewResolutionPrimary) {
10102
+ discoveryReviewResolutionPrimary.addEventListener('click', () => {
10103
+ void handleReviewResolutionAction('discovery-review', String(discoveryReviewResolutionPrimary.dataset.action || '').trim());
10104
+ });
10105
+ }
10106
+ const discoveryReviewResolutionSecondary = document.getElementById('discovery-review-resolution-secondary');
10107
+ if (discoveryReviewResolutionSecondary) {
10108
+ discoveryReviewResolutionSecondary.addEventListener('click', () => {
10109
+ void handleReviewResolutionAction('discovery-review', String(discoveryReviewResolutionSecondary.dataset.action || '').trim());
10110
+ });
10111
+ }
9709
10112
  const discoveryReviewDeferBtn = document.getElementById('discovery-review-defer');
9710
10113
  if (discoveryReviewDeferBtn) {
9711
10114
  discoveryReviewDeferBtn.addEventListener('click', () => {
@@ -9896,12 +10299,10 @@ function renderLauncherHtml(launcherToken) {
9896
10299
  });
9897
10300
  });
9898
10301
  document.getElementById('scope-open-create-workspace').addEventListener('click', () => {
9899
- toggleScopeCreatePanel('scope-create-workspace-panel', true);
9900
- setScopeStatus('Create a governed workspace for this registration flow.', '');
10302
+ openScopeCreateWorkspaceFlow('Create a governed workspace for this registration flow.', '');
9901
10303
  });
9902
10304
  document.getElementById('scope-open-create-project').addEventListener('click', () => {
9903
- toggleScopeCreatePanel('scope-create-project-panel', true);
9904
- setScopeStatus('Create a governed project inside the selected workspace.', '');
10305
+ openScopeCreateProjectFlow('Create a governed project inside the selected workspace.', '');
9905
10306
  });
9906
10307
  document.getElementById('scope-cancel-create-workspace').addEventListener('click', () => {
9907
10308
  toggleScopeCreatePanel(null, false);
@@ -10411,8 +10812,13 @@ function createLauncherApp(options) {
10411
10812
  });
10412
10813
  }
10413
10814
  catch (error) {
10414
- const message = error instanceof Error ? error.message : 'model_connect_failed';
10415
- response.status(400).json({ ok: false, message });
10815
+ const normalizedFailure = normalizeDiscoveryRegistrationFailure(error instanceof Error ? error.message : 'model_connect_failed');
10816
+ response.status(400).json({
10817
+ ok: false,
10818
+ code: normalizedFailure.code,
10819
+ message: normalizedFailure.message,
10820
+ nextActions: normalizedFailure.nextActions ?? [],
10821
+ });
10416
10822
  }
10417
10823
  });
10418
10824
  app.post('/api/discovery/connect-agent', async (request, response) => {
@@ -10445,15 +10851,14 @@ function createLauncherApp(options) {
10445
10851
  ? String(agentMetadata.registration_error_status)
10446
10852
  : safeStringRecordValue(agentMetadata, ['registration_error_status', 'registrationErrorStatus']);
10447
10853
  if (trackingStatus === 'registration_failed') {
10448
- const message = registrationErrorMessage
10449
- || (registrationErrorCode
10450
- ? ('Agent registration failed: ' + registrationErrorCode + '.')
10451
- : registrationErrorStatus
10452
- ? ('Agent registration failed with backend status ' + registrationErrorStatus + '.')
10453
- : 'Agent registration failed.');
10854
+ const normalizedFailure = normalizeDiscoveryRegistrationFailure(registrationErrorCode
10855
+ || registrationErrorMessage
10856
+ || (registrationErrorStatus ? `DRAFT_CREATE_FAILED:${registrationErrorStatus}` : 'agent_connect_failed'));
10454
10857
  response.json({
10455
10858
  ok: false,
10456
- message,
10859
+ code: normalizedFailure.code,
10860
+ message: normalizedFailure.message,
10861
+ nextActions: normalizedFailure.nextActions ?? [],
10457
10862
  agentId: agent.agent_id,
10458
10863
  agentName: agent.agent_name,
10459
10864
  action: 'retry',
@@ -10480,8 +10885,13 @@ function createLauncherApp(options) {
10480
10885
  });
10481
10886
  }
10482
10887
  catch (error) {
10483
- const message = error instanceof Error ? error.message : 'agent_connect_failed';
10484
- response.status(400).json({ ok: false, message });
10888
+ const normalizedFailure = normalizeDiscoveryRegistrationFailure(error instanceof Error ? error.message : 'agent_connect_failed');
10889
+ response.status(400).json({
10890
+ ok: false,
10891
+ code: normalizedFailure.code,
10892
+ message: normalizedFailure.message,
10893
+ nextActions: normalizedFailure.nextActions ?? [],
10894
+ });
10485
10895
  }
10486
10896
  });
10487
10897
  app.post('/api/discovery/review/defer', (request, response) => {