forkit-connect 0.1.5 → 0.1.6

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,64 @@ 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 === 'WORKSPACE_PROJECT_BINDING_REQUIRED') {
539
+ return {
540
+ code: normalized,
541
+ message: 'Choose a governed workspace and project before registering this item.',
542
+ };
543
+ }
544
+ if (normalized === 'DRAFT_CREATION_NOT_ALLOWED_BY_BINDING') {
545
+ return {
546
+ code: normalized,
547
+ message: 'This binding does not currently allow draft creation. Update consent or switch scope first.',
548
+ };
549
+ }
550
+ if (normalized.startsWith('DRAFT_CREATE_FAILED:')) {
551
+ return {
552
+ code: normalized,
553
+ message: 'Forkit could not create the draft on the backend right now. Retry in a moment.',
554
+ };
555
+ }
556
+ return {
557
+ code: null,
558
+ message: normalized,
559
+ };
560
+ }
503
561
  function buildPassportHistory(binding, events) {
504
562
  const modelParts = parseModelKeyParts(binding.modelKey);
505
563
  const modelName = (0, heartbeat_1.readBoundModelName)(binding.modelKey);
@@ -1949,6 +2007,34 @@ function renderLauncherHtml(launcherToken) {
1949
2007
  align-items: start;
1950
2008
  }
1951
2009
 
2010
+ .review-resolution-actions {
2011
+ display: grid;
2012
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2013
+ gap: 10px;
2014
+ }
2015
+
2016
+ .review-resolution-actions[hidden] {
2017
+ display: none;
2018
+ }
2019
+
2020
+ .review-resolution-button {
2021
+ min-height: 44px;
2022
+ border-radius: 14px;
2023
+ border: 1px solid rgba(241, 235, 223, 0.12);
2024
+ background: rgba(255,255,255,0.04);
2025
+ color: #fff8ef;
2026
+ font-size: 0.88rem;
2027
+ font-weight: 600;
2028
+ cursor: pointer;
2029
+ padding: 0 12px;
2030
+ transition: background 160ms ease, border-color 160ms ease;
2031
+ }
2032
+
2033
+ .review-resolution-button:hover {
2034
+ background: rgba(255,255,255,0.08);
2035
+ border-color: rgba(157, 238, 245, 0.18);
2036
+ }
2037
+
1952
2038
  .quick-review-primary {
1953
2039
  min-height: 52px;
1954
2040
  border: 1px solid rgba(157, 238, 245, 0.2);
@@ -6391,6 +6477,10 @@ function renderLauncherHtml(launcherToken) {
6391
6477
  </div>
6392
6478
  </div>
6393
6479
  </div>
6480
+ <div class="review-resolution-actions" id="quick-review-resolution" hidden>
6481
+ <button class="review-resolution-button" id="quick-review-resolution-primary" type="button" hidden></button>
6482
+ <button class="review-resolution-button" id="quick-review-resolution-secondary" type="button" hidden></button>
6483
+ </div>
6394
6484
  </section>
6395
6485
  </div>
6396
6486
  </div>
@@ -6770,6 +6860,10 @@ function renderLauncherHtml(launcherToken) {
6770
6860
  <button class="secondary" id="discovery-review-defer" type="button" disabled>Defer 24h</button>
6771
6861
  <button class="danger" id="discovery-review-ignore" type="button" disabled>Ignore</button>
6772
6862
  </div>
6863
+ <div class="review-resolution-actions" id="discovery-review-resolution" hidden>
6864
+ <button class="review-resolution-button" id="discovery-review-resolution-primary" type="button" hidden></button>
6865
+ <button class="review-resolution-button" id="discovery-review-resolution-secondary" type="button" hidden></button>
6866
+ </div>
6773
6867
  </section>
6774
6868
 
6775
6869
  <ul class="discovery-note-list">
@@ -7274,6 +7368,9 @@ function renderLauncherHtml(launcherToken) {
7274
7368
  if (!match) return false;
7275
7369
  selectedPassportId = match.id;
7276
7370
  passportHistoryVisible = Boolean(options && options.showHistory);
7371
+ selectedWorkspaceScope = ALL_SCOPE_VALUE;
7372
+ selectedProjectScope = ALL_SCOPE_VALUE;
7373
+ renderGlobalLaunchHeaderAndCards();
7277
7374
  setView('passports', { skipRouteUpdate: true });
7278
7375
  renderPassportsRows();
7279
7376
  updateRoute('passports', { passport: match.gaid || match.id, ...(passportHistoryVisible ? { focus: 'history' } : {}) });
@@ -7298,6 +7395,29 @@ function renderLauncherHtml(launcherToken) {
7298
7395
  return false;
7299
7396
  }
7300
7397
 
7398
+ async function completeRegistrationSuccess(item, result) {
7399
+ const message = result && typeof result.message === 'string' && result.message.trim()
7400
+ ? result.message.trim()
7401
+ : 'Registration updated.';
7402
+ await refreshAll();
7403
+ const preferredPassportGaid = String(result && (result.passportGaid || result.gaid) || '').trim();
7404
+ if (preferredPassportGaid && focusPassportByGaid(preferredPassportGaid, { showHistory: false })) {
7405
+ setActivityMessage(message, 'ok');
7406
+ return true;
7407
+ }
7408
+ if (item && await openDiscoveryContext({
7409
+ ...item,
7410
+ passportGaid: preferredPassportGaid || item.passportGaid,
7411
+ matchedPassportGaid: preferredPassportGaid || item.matchedPassportGaid,
7412
+ })) {
7413
+ setActivityMessage(message, 'ok');
7414
+ return true;
7415
+ }
7416
+ setView('passports');
7417
+ setActivityMessage(message, 'ok');
7418
+ return false;
7419
+ }
7420
+
7301
7421
  function getSelectedDiscoveryItem(itemsOverride) {
7302
7422
  const items = Array.isArray(itemsOverride)
7303
7423
  ? itemsOverride
@@ -7357,6 +7477,204 @@ function renderLauncherHtml(launcherToken) {
7357
7477
  status.className = 'quick-review-status' + (tone ? ' ' + tone : '');
7358
7478
  }
7359
7479
 
7480
+ function resetReviewResolutionActions(prefix) {
7481
+ setReviewResolutionActions(prefix, []);
7482
+ }
7483
+
7484
+ function derivePassportResolutionQuery(item) {
7485
+ if (!item || typeof item !== 'object') return '';
7486
+ return [
7487
+ item.modelName,
7488
+ item.name,
7489
+ item.selector,
7490
+ item.sourceMeta,
7491
+ ].map((value) => String(value || '').trim()).find(Boolean) || '';
7492
+ }
7493
+
7494
+ function getReviewPrimaryLabel(item) {
7495
+ if (!item) return 'Review';
7496
+ if (item.kind === 'runtime') {
7497
+ return item.statusTone === 'error' ? 'Open runtime fix' : 'Open runtime review';
7498
+ }
7499
+ if (item.statusLabel === 'Draft created') {
7500
+ return 'Open draft';
7501
+ }
7502
+ if (item.passportGaid) {
7503
+ return 'Open passport';
7504
+ }
7505
+ if (item.matchedPassportGaid) {
7506
+ return 'Use existing passport';
7507
+ }
7508
+ if (item.actionLabel === 'Retry') {
7509
+ return 'Retry registration';
7510
+ }
7511
+ if (item.actionLabel === 'Open' || item.actionLabel === 'Review') {
7512
+ return 'Review in context';
7513
+ }
7514
+ return 'Approve & register';
7515
+ }
7516
+
7517
+ function getReviewActionSummary(item) {
7518
+ if (!item) return 'Review';
7519
+ if (item.kind === 'runtime') {
7520
+ return item.statusTone === 'error' ? 'Resolve runtime attention' : 'Open runtime lane';
7521
+ }
7522
+ if (item.statusLabel === 'Draft created') {
7523
+ return 'Continue the existing draft';
7524
+ }
7525
+ if (item.passportGaid) {
7526
+ return 'Open the linked passport';
7527
+ }
7528
+ if (item.matchedPassportGaid) {
7529
+ return 'Use the matching passport';
7530
+ }
7531
+ if (item.actionLabel === 'Retry') {
7532
+ return 'Retry registration here';
7533
+ }
7534
+ if (item.actionLabel === 'Open' || item.actionLabel === 'Review') {
7535
+ return 'Open full review';
7536
+ }
7537
+ return 'Approve and register here';
7538
+ }
7539
+
7540
+ function getReviewDetailText(item) {
7541
+ if (!item) return 'Review local metadata, then decide what happens next.';
7542
+ if (item.kind === 'runtime') {
7543
+ return item.statusTone === 'error'
7544
+ ? 'Runtime attention is required before linked models can keep registering cleanly.'
7545
+ : 'Open runtime review to confirm health, scope, and connected models.';
7546
+ }
7547
+ if (item.statusLabel === 'Draft created') {
7548
+ return 'A draft already exists. Continue it in Passports instead of creating another one.';
7549
+ }
7550
+ if (item.matchedPassportGaid && !item.passportGaid) {
7551
+ return 'Forkit Connect found a matching passport. Reuse it before creating anything new.';
7552
+ }
7553
+ if (item.passportGaid) {
7554
+ return 'This item is already linked. Open the passport to inspect lineage, scope, and runtime state.';
7555
+ }
7556
+ if (item.actionLabel === 'Retry') {
7557
+ return 'Registration failed earlier. Review the latest local metadata and retry when the scope is ready.';
7558
+ }
7559
+ return item.detailSummary || item.statusMeta || 'Review local metadata, then decide what happens next.';
7560
+ }
7561
+
7562
+ function formatLauncherActionFeedback(result, fallbackMessage) {
7563
+ const base = result && typeof result.message === 'string' && result.message.trim()
7564
+ ? result.message.trim()
7565
+ : fallbackMessage;
7566
+ const nextActions = result && Array.isArray(result.nextActions)
7567
+ ? result.nextActions.map((item) => String(item || '').trim()).filter(Boolean)
7568
+ : [];
7569
+ const resolutionActions = result && result.code === 'GOVERNED_PASSPORT_CAPACITY_REACHED'
7570
+ ? [{ id: 'passports', label: 'Use existing passport' }]
7571
+ : result && result.code === 'DRAFT_LIMIT_REACHED'
7572
+ ? [{ id: 'passports', label: 'Review existing drafts' }]
7573
+ : result && (result.code === 'WORKSPACE_PROJECT_BINDING_REQUIRED' || result.code === 'DRAFT_CREATION_NOT_ALLOWED_BY_BINDING')
7574
+ ? [{ id: 'scope', label: 'Set workspace' }]
7575
+ : result && result.code === 'SIMILAR_PASSPORT_EXISTS'
7576
+ ? [{ id: 'passports', label: 'Open matching passport' }]
7577
+ : [];
7578
+ return {
7579
+ message: nextActions.length ? (base + ' Next: ' + nextActions[0]) : base,
7580
+ activity: nextActions.length ? (base + ' Next: ' + nextActions.join(' ')) : base,
7581
+ actionSummary: result && result.code === 'GOVERNED_PASSPORT_CAPACITY_REACHED'
7582
+ ? 'Capacity full · use an existing passport or free space'
7583
+ : result && result.code === 'DRAFT_LIMIT_REACHED'
7584
+ ? 'Draft capacity full · use an existing passport, clear a draft, or upgrade'
7585
+ : result && result.code === 'SIMILAR_PASSPORT_EXISTS'
7586
+ ? 'Matching passport exists · reuse it instead of creating another'
7587
+ : result && (result.code === 'WORKSPACE_PROJECT_BINDING_REQUIRED' || result.code === 'DRAFT_CREATION_NOT_ALLOWED_BY_BINDING')
7588
+ ? 'Scope required · choose workspace and project'
7589
+ : null,
7590
+ resolutionActions,
7591
+ };
7592
+ }
7593
+
7594
+ function setReviewResolutionActions(prefix, actions) {
7595
+ const wrapper = document.getElementById(prefix + '-resolution');
7596
+ const primary = document.getElementById(prefix + '-resolution-primary');
7597
+ const secondary = document.getElementById(prefix + '-resolution-secondary');
7598
+ if (!wrapper || !primary || !secondary) return;
7599
+ const normalized = Array.isArray(actions) ? actions.slice(0, 2) : [];
7600
+ [primary, secondary].forEach((button, index) => {
7601
+ const action = normalized[index];
7602
+ if (!action) {
7603
+ button.hidden = true;
7604
+ button.dataset.action = '';
7605
+ button.textContent = '';
7606
+ return;
7607
+ }
7608
+ button.hidden = false;
7609
+ button.dataset.action = String(action.id || '').trim();
7610
+ button.textContent = String(action.label || '').trim();
7611
+ });
7612
+ wrapper.hidden = normalized.length === 0;
7613
+ }
7614
+
7615
+ async function handleReviewResolutionAction(prefix, action) {
7616
+ const item = prefix === 'quick-review' ? getQuickReviewItem() : getSelectedDiscoveryItem();
7617
+ if (action === 'passports') {
7618
+ if (!latestPassports) {
7619
+ await refreshPassports();
7620
+ }
7621
+ const preferredPassportGaid = String(item && (item.passportGaid || item.matchedPassportGaid) || '').trim();
7622
+ if (preferredPassportGaid && focusPassportByGaid(preferredPassportGaid, { showHistory: true })) {
7623
+ if (prefix === 'quick-review') {
7624
+ setQuickReviewPanelOpen(false);
7625
+ }
7626
+ setActivityMessage('Opened the matching local passport for review.', 'ok');
7627
+ return;
7628
+ }
7629
+ const query = derivePassportResolutionQuery(item);
7630
+ const searchInput = document.getElementById('passports-search');
7631
+ const statusFilter = document.getElementById('passports-status-filter');
7632
+ if (searchInput) {
7633
+ searchInput.value = query;
7634
+ }
7635
+ if (statusFilter) {
7636
+ statusFilter.value = '';
7637
+ }
7638
+ selectedWorkspaceScope = ALL_SCOPE_VALUE;
7639
+ selectedProjectScope = ALL_SCOPE_VALUE;
7640
+ renderGlobalLaunchHeaderAndCards();
7641
+ setView('passports');
7642
+ renderPassportsRows();
7643
+ const filtered = getPassportItemsFiltered();
7644
+ if (filtered.length > 0) {
7645
+ selectedPassportId = filtered[0].id;
7646
+ renderPassportsRows();
7647
+ setActivityMessage(
7648
+ query
7649
+ ? ('Showing local passports that match ' + query + '.')
7650
+ : 'Showing available local passports for reuse.',
7651
+ 'ok',
7652
+ );
7653
+ } else {
7654
+ setActivityMessage(
7655
+ query
7656
+ ? ('No local passport matches ' + query + ' yet. Check existing Forkit.dev records or free local capacity first.')
7657
+ : 'No local passports are available yet. Check existing Forkit.dev records or free local capacity first.',
7658
+ 'warn',
7659
+ );
7660
+ }
7661
+ if (searchInput) {
7662
+ searchInput.focus();
7663
+ searchInput.select();
7664
+ }
7665
+ if (prefix === 'quick-review') {
7666
+ setQuickReviewPanelOpen(false);
7667
+ }
7668
+ return;
7669
+ }
7670
+ if (action === 'scope' && item) {
7671
+ if (prefix === 'quick-review') {
7672
+ setQuickReviewPanelOpen(false);
7673
+ }
7674
+ await openRegistrationScopeDialog(item);
7675
+ }
7676
+ }
7677
+
7360
7678
  function setQuickReviewOptionsOpen(open) {
7361
7679
  const button = document.getElementById('quick-review-options');
7362
7680
  const menu = document.getElementById('quick-review-options-menu');
@@ -7459,6 +7777,7 @@ function renderLauncherHtml(launcherToken) {
7459
7777
  anchor.hidden = true;
7460
7778
  setQuickReviewPanelOpen(false);
7461
7779
  quickReviewScopeCacheKey = null;
7780
+ resetReviewResolutionActions('quick-review');
7462
7781
  return;
7463
7782
  }
7464
7783
 
@@ -7470,22 +7789,14 @@ function renderLauncherHtml(launcherToken) {
7470
7789
  setText('quick-review-title', item.name);
7471
7790
  setText('quick-review-badge', item.kind === 'runtime' ? 'Runtime' : item.kind === 'agent' ? 'Agent' : 'Model');
7472
7791
  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.');
7792
+ setText('quick-review-detail', getReviewDetailText(item));
7474
7793
  setQuickReviewStatus(item.statusLabel + (item.statusMeta ? ' · ' + item.statusMeta : ''), item.statusTone === 'muted' ? '' : item.statusTone);
7794
+ resetReviewResolutionActions('quick-review');
7475
7795
 
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';
7796
+ const primaryLabel = getReviewPrimaryLabel(item);
7482
7797
  setText(
7483
7798
  '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',
7799
+ getReviewActionSummary(item),
7489
7800
  );
7490
7801
  if (primaryButton) {
7491
7802
  primaryButton.textContent = primaryLabel;
@@ -7594,17 +7905,19 @@ function renderLauncherHtml(launcherToken) {
7594
7905
  });
7595
7906
  if (primaryButton) primaryButton.disabled = false;
7596
7907
  if (!result.ok) {
7597
- setQuickReviewStatus(result.message || 'Registration failed.', 'warn');
7598
- setActivityMessage(result.message || 'Registration failed.', 'warn');
7908
+ const feedback = formatLauncherActionFeedback(result, 'Registration failed.');
7909
+ setQuickReviewStatus(feedback.message, 'warn');
7910
+ setActivityMessage(feedback.activity, 'warn');
7911
+ if (feedback.actionSummary) {
7912
+ setText('quick-review-action-summary', feedback.actionSummary);
7913
+ }
7914
+ setReviewResolutionActions('quick-review', feedback.resolutionActions || []);
7599
7915
  return;
7600
7916
  }
7917
+ resetReviewResolutionActions('quick-review');
7601
7918
  setQuickReviewStatus(result.message || 'Registration updated.', 'ok');
7602
- setActivityMessage(result.message || 'Registration updated.', 'ok');
7603
- await refreshAll();
7604
7919
  setQuickReviewPanelOpen(false);
7605
- if (result.gaid || result.passportGaid) {
7606
- await openDiscoveryContext(item);
7607
- }
7920
+ await completeRegistrationSuccess(item, result);
7608
7921
  }
7609
7922
 
7610
7923
  async function submitQuickReviewDefer() {
@@ -7620,6 +7933,7 @@ function renderLauncherHtml(launcherToken) {
7620
7933
  setQuickReviewStatus(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7621
7934
  setActivityMessage(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7622
7935
  await refreshAll();
7936
+ resetReviewResolutionActions('quick-review');
7623
7937
  setQuickReviewOptionsOpen(false);
7624
7938
  }
7625
7939
 
@@ -7636,6 +7950,7 @@ function renderLauncherHtml(launcherToken) {
7636
7950
  setQuickReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7637
7951
  setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7638
7952
  await refreshAll();
7953
+ resetReviewResolutionActions('quick-review');
7639
7954
  setQuickReviewOptionsOpen(false);
7640
7955
  }
7641
7956
 
@@ -7703,26 +8018,23 @@ function renderLauncherHtml(launcherToken) {
7703
8018
  setText('discovery-review-detail', 'Registering creates or updates a Forkit Passport. Nothing is published automatically.');
7704
8019
  if (scopeWrap) scopeWrap.hidden = true;
7705
8020
  setDiscoveryReviewStatus('Select an item to continue.', '');
8021
+ resetReviewResolutionActions('discovery-review');
7706
8022
  setDiscoveryReviewButtons({ primaryLabel: 'Review', primaryEnabled: false, deferEnabled: false, ignoreEnabled: false });
7707
8023
  return;
7708
8024
  }
7709
8025
 
7710
8026
  const typeLabel = item.typeLabel || item.kind;
7711
8027
  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';
8028
+ const primaryLabel = getReviewPrimaryLabel(item);
7718
8029
 
7719
8030
  setText('discovery-review-kicker', typeLabel);
7720
8031
  setText('discovery-review-title', item.name);
7721
8032
  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.');
8033
+ setText('discovery-review-detail', getReviewDetailText(item));
7723
8034
  setDiscoveryReviewStatus(item.statusLabel + (item.statusMeta ? ' · ' + item.statusMeta : ''), item.statusTone === 'muted' ? '' : item.statusTone);
8035
+ resetReviewResolutionActions('discovery-review');
7724
8036
  setDiscoveryReviewButtons({
7725
- primaryLabel,
8037
+ primaryLabel: getReviewPrimaryLabel(item),
7726
8038
  primaryEnabled: item.inboxGroup !== 'ignored',
7727
8039
  deferEnabled: canReviewDefer(item),
7728
8040
  ignoreEnabled: canReviewIgnore(item),
@@ -7812,16 +8124,15 @@ function renderLauncherHtml(launcherToken) {
7812
8124
  });
7813
8125
  if (primaryButton) primaryButton.disabled = false;
7814
8126
  if (!result.ok) {
7815
- setDiscoveryReviewStatus(result.message || 'Registration failed.', 'warn');
7816
- setActivityMessage(result.message || 'Registration failed.', 'warn');
8127
+ const feedback = formatLauncherActionFeedback(result, 'Registration failed.');
8128
+ setDiscoveryReviewStatus(feedback.message, 'warn');
8129
+ setActivityMessage(feedback.activity, 'warn');
8130
+ setReviewResolutionActions('discovery-review', feedback.resolutionActions || []);
7817
8131
  return;
7818
8132
  }
8133
+ resetReviewResolutionActions('discovery-review');
7819
8134
  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
- }
8135
+ await completeRegistrationSuccess(item, result);
7825
8136
  }
7826
8137
 
7827
8138
  async function submitDiscoveryReviewDefer() {
@@ -7837,6 +8148,7 @@ function renderLauncherHtml(launcherToken) {
7837
8148
  setDiscoveryReviewStatus(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7838
8149
  setActivityMessage(result.message || 'Review deferred.', result.ok ? 'ok' : 'warn');
7839
8150
  await refreshAll();
8151
+ resetReviewResolutionActions('discovery-review');
7840
8152
  }
7841
8153
 
7842
8154
  async function submitDiscoveryReviewIgnore() {
@@ -7852,6 +8164,7 @@ function renderLauncherHtml(launcherToken) {
7852
8164
  setDiscoveryReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7853
8165
  setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
7854
8166
  await refreshAll();
8167
+ resetReviewResolutionActions('discovery-review');
7855
8168
  }
7856
8169
 
7857
8170
  function setProfileAvatar(rootId, imageId, initialsId, initials, avatarUrl) {
@@ -8562,6 +8875,30 @@ function renderLauncherHtml(launcherToken) {
8562
8875
  });
8563
8876
  }
8564
8877
 
8878
+ function focusScopeField(fieldId) {
8879
+ const field = document.getElementById(fieldId);
8880
+ if (field && typeof field.focus === 'function') {
8881
+ window.setTimeout(() => {
8882
+ field.focus();
8883
+ if (typeof field.select === 'function') {
8884
+ field.select();
8885
+ }
8886
+ }, 0);
8887
+ }
8888
+ }
8889
+
8890
+ function openScopeCreateWorkspaceFlow(statusMessage, tone) {
8891
+ toggleScopeCreatePanel('scope-create-workspace-panel', true);
8892
+ setScopeStatus(statusMessage || 'Create a governed workspace for this registration flow.', tone || '');
8893
+ focusScopeField('scope-create-workspace-name');
8894
+ }
8895
+
8896
+ function openScopeCreateProjectFlow(statusMessage, tone) {
8897
+ toggleScopeCreatePanel('scope-create-project-panel', true);
8898
+ setScopeStatus(statusMessage || 'Create a governed project inside the selected workspace.', tone || '');
8899
+ focusScopeField('scope-create-project-only-name');
8900
+ }
8901
+
8565
8902
  function setScopeCreateButtonsEnabled(enabled) {
8566
8903
  ['scope-open-create-workspace', 'scope-open-create-project'].forEach((id) => {
8567
8904
  const button = document.getElementById(id);
@@ -8574,11 +8911,11 @@ function renderLauncherHtml(launcherToken) {
8574
8911
  async function loadScopeProjects(workspaceId, selectedProjectId) {
8575
8912
  const projectSelect = document.getElementById('scope-project-select');
8576
8913
  const registerButton = document.getElementById('scope-register-button');
8577
- if (!projectSelect) return;
8914
+ if (!projectSelect) return false;
8578
8915
  if (!workspaceId) {
8579
8916
  setScopeOptions(projectSelect, [{ id: '', label: 'Account scope / no project' }], '');
8580
8917
  if (registerButton) registerButton.disabled = false;
8581
- return;
8918
+ return true;
8582
8919
  }
8583
8920
  setScopeOptions(projectSelect, [{ id: '', label: 'Loading projects...' }], '');
8584
8921
  try {
@@ -8591,21 +8928,37 @@ function renderLauncherHtml(launcherToken) {
8591
8928
  setScopeOptions(projectSelect, options, selectedProjectId || (options[0] && options[0].id) || '');
8592
8929
  if (!payload.ok) {
8593
8930
  setScopeStatus(payload.message || 'Project list unavailable. Create a project here, then retry.', 'warn');
8931
+ return false;
8594
8932
  } else if (!projects.length) {
8595
- setScopeStatus('No governed project exists in this workspace yet. Create one here to continue.', 'warn');
8933
+ openScopeCreateProjectFlow('No governed project exists in this workspace yet. Create one here to continue.', 'warn');
8934
+ return false;
8596
8935
  }
8597
8936
  if (registerButton) {
8598
8937
  registerButton.disabled = !projects.length;
8599
8938
  }
8939
+ return projects.length > 0;
8600
8940
  } catch {
8601
8941
  setScopeOptions(projectSelect, [{ id: '', label: 'Project list unavailable' }], '');
8602
8942
  setScopeStatus('Project list unavailable. Create a project here, then retry.', 'warn');
8603
8943
  if (registerButton) {
8604
8944
  registerButton.disabled = true;
8605
8945
  }
8946
+ return false;
8606
8947
  }
8607
8948
  }
8608
8949
 
8950
+ async function continuePendingScopeRegistration(statusMessage) {
8951
+ const item = pendingDiscoveryRegistration;
8952
+ if (!item) return false;
8953
+ const workspaceSelect = document.getElementById('scope-workspace-select');
8954
+ const projectSelect = document.getElementById('scope-project-select');
8955
+ const workspaceId = workspaceSelect ? String(workspaceSelect.value || '').trim() : '';
8956
+ const projectId = projectSelect ? String(projectSelect.value || '').trim() : '';
8957
+ if (!workspaceId || !projectId) return false;
8958
+ setScopeStatus(statusMessage || 'Continuing registration…', 'warn');
8959
+ return submitRegistrationScopeDialog();
8960
+ }
8961
+
8609
8962
  async function createScopeWorkspace() {
8610
8963
  const nameInput = document.getElementById('scope-create-workspace-name');
8611
8964
  const descriptionInput = document.getElementById('scope-create-workspace-description');
@@ -8638,12 +8991,20 @@ function renderLauncherHtml(launcherToken) {
8638
8991
  if (firstProjectInput) firstProjectInput.value = '';
8639
8992
  toggleScopeCreatePanel(null, false);
8640
8993
  const workspaceSelect = document.getElementById('scope-workspace-select');
8994
+ const projectSelect = document.getElementById('scope-project-select');
8641
8995
  const createdWorkspaceId = result.workspace && result.workspace.id ? result.workspace.id : '';
8996
+ const createdProjectId = result.project && result.project.id ? result.project.id : '';
8642
8997
  scopeAccessSnapshot = result.scope || scopeAccessSnapshot;
8643
8998
  if (workspaceSelect && createdWorkspaceId) {
8644
8999
  await openRegistrationScopeDialog(pendingDiscoveryRegistration);
8645
9000
  workspaceSelect.value = createdWorkspaceId;
8646
- await loadScopeProjects(createdWorkspaceId, result.project && result.project.id ? result.project.id : '');
9001
+ const hasProjects = await loadScopeProjects(createdWorkspaceId, createdProjectId);
9002
+ if (projectSelect && createdProjectId) {
9003
+ projectSelect.value = createdProjectId;
9004
+ }
9005
+ if (hasProjects && createdProjectId && await continuePendingScopeRegistration('Workspace and project created. Continuing registration...')) {
9006
+ return;
9007
+ }
8647
9008
  }
8648
9009
  setScopeStatus(result.message || 'Workspace created.', 'ok');
8649
9010
  }
@@ -8681,10 +9042,14 @@ function renderLauncherHtml(launcherToken) {
8681
9042
  if (nameInput) nameInput.value = '';
8682
9043
  if (descriptionInput) descriptionInput.value = '';
8683
9044
  toggleScopeCreatePanel(null, false);
8684
- await loadScopeProjects(workspaceId, result.project && result.project.id ? result.project.id : '');
9045
+ const createdProjectId = result.project && result.project.id ? result.project.id : '';
9046
+ const hasProjects = await loadScopeProjects(workspaceId, createdProjectId);
8685
9047
  const projectSelect = document.getElementById('scope-project-select');
8686
- if (projectSelect && result.project && result.project.id) {
8687
- projectSelect.value = result.project.id;
9048
+ if (projectSelect && createdProjectId) {
9049
+ projectSelect.value = createdProjectId;
9050
+ }
9051
+ if (hasProjects && createdProjectId && await continuePendingScopeRegistration('Project created. Continuing registration...')) {
9052
+ return;
8688
9053
  }
8689
9054
  setScopeStatus(result.message || 'Project created.', 'ok');
8690
9055
  }
@@ -8730,18 +9095,20 @@ function renderLauncherHtml(launcherToken) {
8730
9095
  if (governedMode && !options.length) {
8731
9096
  options.push({ id: '', label: 'No governed workspaces yet' });
8732
9097
  registerButton.disabled = true;
8733
- setScopeStatus('No governed workspace exists yet. Create one here to continue.', 'warn');
9098
+ openScopeCreateWorkspaceFlow('No governed workspace exists yet. Create one here to continue.', 'warn');
8734
9099
  }
8735
9100
  const selectedWorkspace = payload.currentWorkspaceId || (options[0] && options[0].id) || '';
8736
9101
  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
- );
9102
+ const hasProjects = await loadScopeProjects(workspaceSelect.value, payload.currentProjectId || '');
9103
+ registerButton.disabled = !payload.ok || (governedMode && (!String(workspaceSelect.value || '').trim() || !hasProjects));
9104
+ if (payload.ok && (!governedMode || hasProjects)) {
9105
+ setScopeStatus(governedMode ? 'Ready to register in governed scope.' : 'Ready to register.', '');
9106
+ } else if (!payload.ok) {
9107
+ setScopeStatus(
9108
+ payload.message || (governedMode ? 'Workspace list unavailable.' : 'Workspace list unavailable. Account scope is still available.'),
9109
+ 'warn',
9110
+ );
9111
+ }
8745
9112
  } catch {
8746
9113
  setScopeOptions(workspaceSelect, governedMode ? [] : [{ id: '', label: 'Account scope / no workspace' }], '');
8747
9114
  await loadScopeProjects(governedMode ? (workspaceSelect.value || '') : '', '');
@@ -8761,7 +9128,7 @@ function renderLauncherHtml(launcherToken) {
8761
9128
 
8762
9129
  async function submitRegistrationScopeDialog() {
8763
9130
  const item = pendingDiscoveryRegistration;
8764
- if (!item) return;
9131
+ if (!item) return false;
8765
9132
  const workspaceSelect = document.getElementById('scope-workspace-select');
8766
9133
  const projectSelect = document.getElementById('scope-project-select');
8767
9134
  const registerButton = document.getElementById('scope-register-button');
@@ -8769,7 +9136,7 @@ function renderLauncherHtml(launcherToken) {
8769
9136
  const projectId = projectSelect ? projectSelect.value : '';
8770
9137
  if (workspaceId && !projectId) {
8771
9138
  setScopeStatus('Select a project for the chosen workspace, or use account scope.', 'warn');
8772
- return;
9139
+ return false;
8773
9140
  }
8774
9141
  if (registerButton) registerButton.disabled = true;
8775
9142
  const endpoint = item.kind === 'agent' ? '/api/discovery/connect-agent' : '/api/discovery/connect-model';
@@ -8781,14 +9148,14 @@ function renderLauncherHtml(launcherToken) {
8781
9148
  });
8782
9149
  if (registerButton) registerButton.disabled = false;
8783
9150
  if (!result.ok) {
8784
- setScopeStatus(result.message || 'Registration failed.', 'warn');
8785
- setActivityMessage(result.message || 'Registration failed.', 'warn');
8786
- return;
9151
+ const feedback = formatLauncherActionFeedback(result, 'Registration failed.');
9152
+ setScopeStatus(feedback.message, 'warn');
9153
+ setActivityMessage(feedback.activity, 'warn');
9154
+ return false;
8787
9155
  }
8788
9156
  closeRegistrationScopeDialog();
8789
- setActivityMessage(result.message || 'Registration updated.', 'ok');
8790
- await refreshAll();
8791
- setView('passports');
9157
+ await completeRegistrationSuccess(item, result);
9158
+ return true;
8792
9159
  }
8793
9160
 
8794
9161
  const VIEW_TITLES = {
@@ -9640,6 +10007,18 @@ function renderLauncherHtml(launcherToken) {
9640
10007
  void submitQuickReviewPrimary();
9641
10008
  });
9642
10009
  }
10010
+ const quickReviewResolutionPrimary = document.getElementById('quick-review-resolution-primary');
10011
+ if (quickReviewResolutionPrimary) {
10012
+ quickReviewResolutionPrimary.addEventListener('click', () => {
10013
+ void handleReviewResolutionAction('quick-review', String(quickReviewResolutionPrimary.dataset.action || '').trim());
10014
+ });
10015
+ }
10016
+ const quickReviewResolutionSecondary = document.getElementById('quick-review-resolution-secondary');
10017
+ if (quickReviewResolutionSecondary) {
10018
+ quickReviewResolutionSecondary.addEventListener('click', () => {
10019
+ void handleReviewResolutionAction('quick-review', String(quickReviewResolutionSecondary.dataset.action || '').trim());
10020
+ });
10021
+ }
9643
10022
  const quickReviewOptionsButton = document.getElementById('quick-review-options');
9644
10023
  if (quickReviewOptionsButton) {
9645
10024
  quickReviewOptionsButton.addEventListener('click', (event) => {
@@ -9706,6 +10085,18 @@ function renderLauncherHtml(launcherToken) {
9706
10085
  void submitDiscoveryReviewPrimary();
9707
10086
  });
9708
10087
  }
10088
+ const discoveryReviewResolutionPrimary = document.getElementById('discovery-review-resolution-primary');
10089
+ if (discoveryReviewResolutionPrimary) {
10090
+ discoveryReviewResolutionPrimary.addEventListener('click', () => {
10091
+ void handleReviewResolutionAction('discovery-review', String(discoveryReviewResolutionPrimary.dataset.action || '').trim());
10092
+ });
10093
+ }
10094
+ const discoveryReviewResolutionSecondary = document.getElementById('discovery-review-resolution-secondary');
10095
+ if (discoveryReviewResolutionSecondary) {
10096
+ discoveryReviewResolutionSecondary.addEventListener('click', () => {
10097
+ void handleReviewResolutionAction('discovery-review', String(discoveryReviewResolutionSecondary.dataset.action || '').trim());
10098
+ });
10099
+ }
9709
10100
  const discoveryReviewDeferBtn = document.getElementById('discovery-review-defer');
9710
10101
  if (discoveryReviewDeferBtn) {
9711
10102
  discoveryReviewDeferBtn.addEventListener('click', () => {
@@ -9896,12 +10287,10 @@ function renderLauncherHtml(launcherToken) {
9896
10287
  });
9897
10288
  });
9898
10289
  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.', '');
10290
+ openScopeCreateWorkspaceFlow('Create a governed workspace for this registration flow.', '');
9901
10291
  });
9902
10292
  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.', '');
10293
+ openScopeCreateProjectFlow('Create a governed project inside the selected workspace.', '');
9905
10294
  });
9906
10295
  document.getElementById('scope-cancel-create-workspace').addEventListener('click', () => {
9907
10296
  toggleScopeCreatePanel(null, false);
@@ -10411,8 +10800,13 @@ function createLauncherApp(options) {
10411
10800
  });
10412
10801
  }
10413
10802
  catch (error) {
10414
- const message = error instanceof Error ? error.message : 'model_connect_failed';
10415
- response.status(400).json({ ok: false, message });
10803
+ const normalizedFailure = normalizeDiscoveryRegistrationFailure(error instanceof Error ? error.message : 'model_connect_failed');
10804
+ response.status(400).json({
10805
+ ok: false,
10806
+ code: normalizedFailure.code,
10807
+ message: normalizedFailure.message,
10808
+ nextActions: normalizedFailure.nextActions ?? [],
10809
+ });
10416
10810
  }
10417
10811
  });
10418
10812
  app.post('/api/discovery/connect-agent', async (request, response) => {
@@ -10445,15 +10839,14 @@ function createLauncherApp(options) {
10445
10839
  ? String(agentMetadata.registration_error_status)
10446
10840
  : safeStringRecordValue(agentMetadata, ['registration_error_status', 'registrationErrorStatus']);
10447
10841
  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.');
10842
+ const normalizedFailure = normalizeDiscoveryRegistrationFailure(registrationErrorCode
10843
+ || registrationErrorMessage
10844
+ || (registrationErrorStatus ? `DRAFT_CREATE_FAILED:${registrationErrorStatus}` : 'agent_connect_failed'));
10454
10845
  response.json({
10455
10846
  ok: false,
10456
- message,
10847
+ code: normalizedFailure.code,
10848
+ message: normalizedFailure.message,
10849
+ nextActions: normalizedFailure.nextActions ?? [],
10457
10850
  agentId: agent.agent_id,
10458
10851
  agentName: agent.agent_name,
10459
10852
  action: 'retry',
@@ -10480,8 +10873,13 @@ function createLauncherApp(options) {
10480
10873
  });
10481
10874
  }
10482
10875
  catch (error) {
10483
- const message = error instanceof Error ? error.message : 'agent_connect_failed';
10484
- response.status(400).json({ ok: false, message });
10876
+ const normalizedFailure = normalizeDiscoveryRegistrationFailure(error instanceof Error ? error.message : 'agent_connect_failed');
10877
+ response.status(400).json({
10878
+ ok: false,
10879
+ code: normalizedFailure.code,
10880
+ message: normalizedFailure.message,
10881
+ nextActions: normalizedFailure.nextActions ?? [],
10882
+ });
10485
10883
  }
10486
10884
  });
10487
10885
  app.post('/api/discovery/review/defer', (request, response) => {