forkit-connect 0.1.12 → 0.1.14

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/cli.js CHANGED
@@ -90,7 +90,7 @@ const PUBLIC_COMMANDS = [
90
90
  ['workspace', 'List, select, or inspect optional governed workspace/project scope'],
91
91
  ['runtime', 'Register or review governed runtimes for the current repo/worktree'],
92
92
  ['register', 'Register ready local models into the current scope'],
93
- ['ignore', 'Ignore one detected local model'],
93
+ ['ignore', 'Deny one detected local model on this device'],
94
94
  ['doctor', 'Run local environment diagnostics'],
95
95
  ];
96
96
  const ADVANCED_COMMAND_GROUPS = [
@@ -139,7 +139,7 @@ function usage() {
139
139
  console.log(' --no-api-key Skip automatic runtime API key creation');
140
140
  console.log(' --dry-run Show the inferred runtime payload without creating it');
141
141
  console.log(' --all-ready Register every ready local model in the current scope');
142
- console.log(' --ignore-model <name> Ignore detected model by name with --ignore-digest');
142
+ console.log(' --ignore-model <name> Deny a detected model on this device with --ignore-digest');
143
143
  console.log(' --ignore-digest <sha> Digest used with --ignore-model');
144
144
  console.log(' --digest <sha> Digest used by ignore when the model name is ambiguous');
145
145
  console.log(' --interval-seconds <n> Override daemon scan interval');
@@ -1022,7 +1022,7 @@ function buildInteractiveInboxSections(inbox) {
1022
1022
  shellLine('Ready to connect', inbox.groups.ready_to_connect.length),
1023
1023
  shellLine('Needs confirmation', inbox.groups.needs_confirmation.length),
1024
1024
  shellLine('Connected', inbox.groups.connected.length),
1025
- shellLine('Ignored', inbox.groups.ignored.length),
1025
+ shellLine('Denied on this device', inbox.groups.ignored.length),
1026
1026
  ...(inbox.summary.next_recommended_action ? [shellLine('Next action', formatSmartInboxActionValue(inbox.summary.next_recommended_action))] : []),
1027
1027
  ],
1028
1028
  },
@@ -1044,7 +1044,7 @@ function buildInteractiveInboxItemSections(item, group) {
1044
1044
  : group === 'connected'
1045
1045
  ? 'Connected'
1046
1046
  : group === 'ignored'
1047
- ? 'Ignored'
1047
+ ? 'Denied on this device'
1048
1048
  : group.replaceAll('_', ' ');
1049
1049
  const detailKeys = [
1050
1050
  'verification_summary',
@@ -1424,7 +1424,7 @@ function formatSmartInboxActionValue(action, itemType, connectableModelName) {
1424
1424
  case 'defer':
1425
1425
  return 'Ask Later';
1426
1426
  case 'ignore':
1427
- return 'Do Not Register';
1427
+ return 'Deny on This Device';
1428
1428
  default:
1429
1429
  return action.replaceAll('_', ' ');
1430
1430
  }
@@ -1451,7 +1451,7 @@ function printSmartInbox(inbox) {
1451
1451
  printInboxGroup('Ready to Connect', inbox.groups.ready_to_connect);
1452
1452
  printInboxGroup('Needs Confirmation', inbox.groups.needs_confirmation);
1453
1453
  printInboxGroup('Connected', inbox.groups.connected);
1454
- printInboxGroup('Ignored', inbox.groups.ignored);
1454
+ printInboxGroup('Denied on This Device', inbox.groups.ignored);
1455
1455
  if (inbox.summary.next_recommended_action) {
1456
1456
  console.log(`Next recommended action: ${formatSmartInboxActionValue(inbox.summary.next_recommended_action)}`);
1457
1457
  }
@@ -1881,11 +1881,11 @@ async function run() {
1881
1881
  if (ignoreModel && ignoreDigest) {
1882
1882
  const ignored = service.markModelIgnored(ignoreModel, ignoreDigest);
1883
1883
  if (!ignored) {
1884
- console.error(`[forkit-connect] Model not found for ignore: ${ignoreModel} ${ignoreDigest}`);
1884
+ console.error(`[forkit-connect] Model not found for deny-on-this-device: ${ignoreModel} ${ignoreDigest}`);
1885
1885
  process.exitCode = 2;
1886
1886
  return;
1887
1887
  }
1888
- console.log(`[forkit-connect] Ignored model: ${ignoreModel} (${ignoreDigest})`);
1888
+ console.log(`[forkit-connect] Denied model on this device: ${ignoreModel} (${ignoreDigest})`);
1889
1889
  return;
1890
1890
  }
1891
1891
  const result = await service.runDiscoveryCycle();
@@ -2840,7 +2840,7 @@ async function run() {
2840
2840
  case 'connected':
2841
2841
  return 'Connected';
2842
2842
  case 'ignored':
2843
- return 'Ignored';
2843
+ return 'Denied on this device';
2844
2844
  default:
2845
2845
  return group.replaceAll('_', ' ');
2846
2846
  }
@@ -2907,21 +2907,21 @@ async function run() {
2907
2907
  }
2908
2908
  const ignored = service.markModelIgnored(model.model, model.digest);
2909
2909
  if (!ignored) {
2910
- console.error('Ignore failed.');
2910
+ console.error('Deny on this device failed.');
2911
2911
  process.exitCode = 2;
2912
2912
  return;
2913
2913
  }
2914
- console.log(`[forkit-connect] Ignored model: ${model.model} (${model.digest})`);
2914
+ console.log(`[forkit-connect] Denied model on this device: ${model.model} (${model.digest})`);
2915
2915
  process.exitCode = 0;
2916
2916
  return;
2917
2917
  }
2918
2918
  if (item.item_type === 'runtime') {
2919
2919
  service.markRuntimeIgnored(selector);
2920
- console.log(`[forkit-connect] Ignored runtime: ${item.display_name}`);
2920
+ console.log(`[forkit-connect] Denied runtime on this device: ${item.display_name}`);
2921
2921
  process.exitCode = 0;
2922
2922
  return;
2923
2923
  }
2924
- console.log('[forkit-connect] Ignore is not available for this inbox item type from the public menu yet.');
2924
+ console.log('[forkit-connect] Deny on this device is not available for this inbox item type from the public menu yet.');
2925
2925
  };
2926
2926
  const runInteractiveDeferInboxItem = (item) => {
2927
2927
  const selector = extractInboxItemSelector(item);
@@ -4060,7 +4060,7 @@ async function run() {
4060
4060
  }
4061
4061
  const ignored = service.markModelIgnored(resolvedModel.model, resolvedModel.digest);
4062
4062
  if (!ignored) {
4063
- console.error('Ignore failed.');
4063
+ console.error('Deny on this device failed.');
4064
4064
  process.exitCode = 2;
4065
4065
  return;
4066
4066
  }
package/dist/launcher.js CHANGED
@@ -687,8 +687,8 @@ function buildDiscovery(service) {
687
687
  let actionLabel = 'Register';
688
688
  let actionTone = 'primary';
689
689
  if (group === 'ignored') {
690
- statusLabel = 'Ignored';
691
- statusMeta = 'Held back locally until you review it again.';
690
+ statusLabel = 'Denied on this device';
691
+ statusMeta = 'This device will keep it out of the active review queue until you reopen it.';
692
692
  statusTone = 'muted';
693
693
  actionLabel = 'Review';
694
694
  actionTone = 'muted';
@@ -777,7 +777,14 @@ function buildDiscovery(service) {
777
777
  let statusTone = 'ok';
778
778
  let actionLabel = 'Register';
779
779
  let actionTone = 'primary';
780
- if (agent.status === 'inactive' || group === 'ignored') {
780
+ if (group === 'ignored') {
781
+ statusLabel = 'Denied on this device';
782
+ statusMeta = 'This device will keep it out of the active review queue until you reopen it.';
783
+ statusTone = 'muted';
784
+ actionLabel = 'Review';
785
+ actionTone = 'muted';
786
+ }
787
+ else if (agent.status === 'inactive') {
781
788
  statusLabel = 'Inactive';
782
789
  statusMeta = 'Paused locally until the agent is seen again.';
783
790
  statusTone = 'muted';
@@ -861,8 +868,8 @@ function buildDiscovery(service) {
861
868
  statusTone = 'ok';
862
869
  }
863
870
  else if (group === 'ignored') {
864
- statusLabel = 'Ignored';
865
- statusMeta = 'Runtime review is deferred locally.';
871
+ statusLabel = 'Denied on this device';
872
+ statusMeta = 'This device will keep runtime review out of the active queue until you reopen it.';
866
873
  statusTone = 'muted';
867
874
  }
868
875
  else if (group === 'needs_confirmation') {
@@ -6558,7 +6565,7 @@ function renderLauncherHtml(launcherToken) {
6558
6565
  </button>
6559
6566
  <button id="quick-review-ignore" class="danger" type="button">
6560
6567
  <span class="quick-review-menu-copy">
6561
- <span class="quick-review-menu-label">Ignore locally</span>
6568
+ <span class="quick-review-menu-label">Deny on this device</span>
6562
6569
  <span class="quick-review-menu-meta">Keep it out of this device review queue.</span>
6563
6570
  </span>
6564
6571
  </button>
@@ -6953,7 +6960,7 @@ function renderLauncherHtml(launcherToken) {
6953
6960
  <div class="discovery-review-actions">
6954
6961
  <button class="primary" id="discovery-review-primary" type="button" disabled>Review</button>
6955
6962
  <button class="secondary" id="discovery-review-defer" type="button" disabled>Defer 24h</button>
6956
- <button class="danger" id="discovery-review-ignore" type="button" disabled>Ignore</button>
6963
+ <button class="danger" id="discovery-review-ignore" type="button" disabled>Deny on this device</button>
6957
6964
  </div>
6958
6965
  <div class="review-resolution-actions" id="discovery-review-resolution" hidden>
6959
6966
  <button class="review-resolution-button" id="discovery-review-resolution-primary" type="button" hidden></button>
@@ -7592,11 +7599,11 @@ function renderLauncherHtml(launcherToken) {
7592
7599
  }
7593
7600
 
7594
7601
  function canReviewDefer(item) {
7595
- return Boolean(item && (item.kind === 'model' || item.kind === 'runtime') && item.inboxGroup !== 'ignored');
7602
+ return Boolean(item && (item.kind === 'model' || item.kind === 'agent' || item.kind === 'runtime') && item.inboxGroup !== 'ignored');
7596
7603
  }
7597
7604
 
7598
7605
  function canReviewIgnore(item) {
7599
- return Boolean(item && (item.kind === 'model' || item.kind === 'runtime') && item.inboxGroup !== 'ignored');
7606
+ return Boolean(item && (item.kind === 'model' || item.kind === 'agent' || item.kind === 'runtime') && item.inboxGroup !== 'ignored');
7600
7607
  }
7601
7608
 
7602
7609
  function getQuickReviewItems() {
@@ -8183,15 +8190,15 @@ function renderLauncherHtml(launcherToken) {
8183
8190
  async function submitQuickReviewIgnore() {
8184
8191
  const item = getQuickReviewItem();
8185
8192
  if (!item || !canReviewIgnore(item)) {
8186
- setQuickReviewStatus('Ignore is not available for this item.', 'warn');
8193
+ setQuickReviewStatus('Deny on this device is not available for this item.', 'warn');
8187
8194
  return;
8188
8195
  }
8189
8196
  const result = await postAction('/api/discovery/review/ignore', 'Ignoring this review item...', {
8190
8197
  method: 'POST',
8191
8198
  body: JSON.stringify({ kind: item.kind, selector: item.selector }),
8192
8199
  });
8193
- setQuickReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
8194
- setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
8200
+ setQuickReviewStatus(result.message || 'Item denied on this device.', result.ok ? 'ok' : 'warn');
8201
+ setActivityMessage(result.message || 'Item denied on this device.', result.ok ? 'ok' : 'warn');
8195
8202
  await refreshAll();
8196
8203
  resetReviewResolutionActions('quick-review');
8197
8204
  setQuickReviewOptionsOpen(false);
@@ -8433,15 +8440,15 @@ function renderLauncherHtml(launcherToken) {
8433
8440
  async function submitDiscoveryReviewIgnore() {
8434
8441
  const item = getSelectedDiscoveryItem();
8435
8442
  if (!item || !canReviewIgnore(item)) {
8436
- setDiscoveryReviewStatus('Ignore is not available for this item.', 'warn');
8443
+ setDiscoveryReviewStatus('Deny on this device is not available for this item.', 'warn');
8437
8444
  return;
8438
8445
  }
8439
8446
  const result = await postAction('/api/discovery/review/ignore', 'Ignoring this review item...', {
8440
8447
  method: 'POST',
8441
8448
  body: JSON.stringify({ kind: item.kind, selector: item.selector }),
8442
8449
  });
8443
- setDiscoveryReviewStatus(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
8444
- setActivityMessage(result.message || 'Item ignored locally.', result.ok ? 'ok' : 'warn');
8450
+ setDiscoveryReviewStatus(result.message || 'Item denied on this device.', result.ok ? 'ok' : 'warn');
8451
+ setActivityMessage(result.message || 'Item denied on this device.', result.ok ? 'ok' : 'warn');
8445
8452
  await refreshAll();
8446
8453
  resetReviewResolutionActions('discovery-review');
8447
8454
  }
@@ -9689,14 +9696,14 @@ function renderLauncherHtml(launcherToken) {
9689
9696
  selectedDiscoveryId = item.id;
9690
9697
  if (item.kind === 'runtime') {
9691
9698
  if (item.inboxGroup === 'ignored') {
9692
- setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently ignored locally.'), 'warn');
9699
+ setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently denied on this device.'), 'warn');
9693
9700
  return;
9694
9701
  }
9695
9702
  setView('runtime');
9696
9703
  return;
9697
9704
  }
9698
9705
  if (item.inboxGroup === 'ignored') {
9699
- setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently ignored locally.'), 'warn');
9706
+ setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently denied on this device.'), 'warn');
9700
9707
  return;
9701
9708
  }
9702
9709
  if (item.kind === 'model' && (item.actionLabel === 'Open' || item.actionLabel === 'Review')) {
@@ -9704,7 +9711,7 @@ function renderLauncherHtml(launcherToken) {
9704
9711
  return;
9705
9712
  }
9706
9713
  if (item.kind === 'model' && item.actionLabel === 'Undo') {
9707
- setActivityMessage('Ignored model restore is not available from the launcher yet.', 'warn');
9714
+ setActivityMessage('Restore from denied-on-this-device state is not available from the launcher yet.', 'warn');
9708
9715
  return;
9709
9716
  }
9710
9717
  if (item.kind === 'model') {
@@ -9726,14 +9733,14 @@ function renderLauncherHtml(launcherToken) {
9726
9733
  selectedDiscoveryId = item.id;
9727
9734
  if (item.kind === 'runtime') {
9728
9735
  if (item.inboxGroup === 'ignored') {
9729
- setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently ignored locally.'), 'warn');
9736
+ setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently denied on this device.'), 'warn');
9730
9737
  return;
9731
9738
  }
9732
9739
  setView('runtime');
9733
9740
  return;
9734
9741
  }
9735
9742
  if (item.inboxGroup === 'ignored') {
9736
- setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently ignored locally.'), 'warn');
9743
+ setActivityMessage(item.detailSummary || item.statusMeta || (item.name + ' is currently denied on this device.'), 'warn');
9737
9744
  return;
9738
9745
  }
9739
9746
  if (item.actionLabel === 'Open' || item.actionLabel === 'Review') {
@@ -9742,7 +9749,7 @@ function renderLauncherHtml(launcherToken) {
9742
9749
  }
9743
9750
  if (item.kind === 'model') {
9744
9751
  if (item.actionLabel === 'Undo') {
9745
- setActivityMessage('Ignored model restore is not available from the launcher yet.', 'warn');
9752
+ setActivityMessage('Restore from denied-on-this-device state is not available from the launcher yet.', 'warn');
9746
9753
  return;
9747
9754
  }
9748
9755
  await openRegistrationScopeDialog(item);
@@ -11239,10 +11246,8 @@ function createLauncherApp(options) {
11239
11246
  return;
11240
11247
  }
11241
11248
  if (kind === 'agent') {
11242
- response.status(400).json({
11243
- ok: false,
11244
- message: 'Agent defer is not available yet in the local review surface.',
11245
- });
11249
+ options.service.deferDetectedAgent(selector, Number.isFinite(hours) ? hours : 24);
11250
+ response.json({ ok: true, message: 'Agent review deferred for 24 hours.' });
11246
11251
  return;
11247
11252
  }
11248
11253
  response.status(400).json({ ok: false, message: 'Unsupported review kind.' });
@@ -11262,19 +11267,17 @@ function createLauncherApp(options) {
11262
11267
  }
11263
11268
  if (kind === 'model') {
11264
11269
  options.service.ignoreDetectedModel(selector);
11265
- response.json({ ok: true, message: 'Model review ignored locally.' });
11270
+ response.json({ ok: true, message: 'Model review denied on this device.' });
11266
11271
  return;
11267
11272
  }
11268
11273
  if (kind === 'runtime') {
11269
11274
  options.service.ignoreRuntimeSuggestion(selector);
11270
- response.json({ ok: true, message: 'Runtime review ignored locally.' });
11275
+ response.json({ ok: true, message: 'Runtime review denied on this device.' });
11271
11276
  return;
11272
11277
  }
11273
11278
  if (kind === 'agent') {
11274
- response.status(400).json({
11275
- ok: false,
11276
- message: 'Agent ignore is not available yet in the local review surface.',
11277
- });
11279
+ options.service.ignoreDetectedAgent(selector);
11280
+ response.json({ ok: true, message: 'Agent review denied on this device.' });
11278
11281
  return;
11279
11282
  }
11280
11283
  response.status(400).json({ ok: false, message: 'Unsupported review kind.' });
@@ -402,6 +402,7 @@ export declare class ConnectV1Service {
402
402
  private buildInboxItemId;
403
403
  private isDeferredReviewActive;
404
404
  private getModelReviewDisposition;
405
+ private getAgentReviewDisposition;
405
406
  private getRuntimeReviewDisposition;
406
407
  private buildModelVerificationSummary;
407
408
  private buildRuntimeVerificationSummary;
@@ -768,7 +769,9 @@ export declare class ConnectV1Service {
768
769
  runDoctorChecks(): Promise<DoctorCheck[]>;
769
770
  markModelIgnored(modelName: string, digest: string): boolean;
770
771
  ignoreDetectedModel(selector: string): boolean;
772
+ ignoreDetectedAgent(selector: string): boolean;
771
773
  deferDetectedModel(selector: string, deferHours?: number): boolean;
774
+ deferDetectedAgent(selector: string, deferHours?: number): boolean;
772
775
  markRuntimeIgnored(selector: string): boolean;
773
776
  deferRuntimeSuggestion(selector: string, deferHours?: number): boolean;
774
777
  ignoreRuntimeSuggestion(selector: string): boolean;
@@ -2153,6 +2153,15 @@ class ConnectV1Service {
2153
2153
  }
2154
2154
  return 'active';
2155
2155
  }
2156
+ getAgentReviewDisposition(agent) {
2157
+ if (agent.review_state === 'ignored') {
2158
+ return 'ignored';
2159
+ }
2160
+ if (agent.review_state === 'deferred' && this.isDeferredReviewActive(agent.review_deferred_until)) {
2161
+ return 'deferred';
2162
+ }
2163
+ return 'active';
2164
+ }
2156
2165
  getRuntimeReviewDisposition(runtimePassport) {
2157
2166
  if (runtimePassport.review_state === 'ignored') {
2158
2167
  return 'ignored';
@@ -3607,6 +3616,8 @@ class ConnectV1Service {
3607
3616
  source_label: input.candidate.source_label ?? null,
3608
3617
  detection_reason: input.candidate.reason ?? null,
3609
3618
  },
3619
+ review_state: input.existing?.review_state,
3620
+ review_deferred_until: input.existing?.review_deferred_until ?? null,
3610
3621
  };
3611
3622
  }
3612
3623
  queueAgentC2Event(agent, eventType, metadata = {}, occurredAt) {
@@ -4434,29 +4445,35 @@ class ConnectV1Service {
4434
4445
  const sourceLabel = typeof metadata.source_label === 'string' && metadata.source_label.trim()
4435
4446
  ? metadata.source_label.trim()
4436
4447
  : null;
4437
- const reviewDisposition = link
4448
+ const agentReviewStatus = link
4438
4449
  ? 'linked_agent'
4439
4450
  : agent.agent_type === 'unknown_ai_tool'
4440
4451
  ? 'unknown_ai_tool'
4441
4452
  : agent.detected_at === agent.last_seen_at
4442
4453
  ? 'new_agent'
4443
4454
  : 'known_agent';
4455
+ const reviewDisposition = this.getAgentReviewDisposition(agent);
4456
+ if (reviewDisposition === 'deferred') {
4457
+ continue;
4458
+ }
4444
4459
  const agentObservation = this.buildAgentObservationStatus(agent);
4445
- const group = agent.status === 'inactive'
4460
+ const group = reviewDisposition === 'ignored'
4446
4461
  ? 'ignored'
4447
- : link
4448
- ? 'connected'
4449
- : candidate.model
4450
- ? 'needs_confirmation'
4451
- : 'ready_to_connect';
4462
+ : agent.status === 'inactive'
4463
+ ? 'ignored'
4464
+ : link
4465
+ ? 'connected'
4466
+ : candidate.model
4467
+ ? 'needs_confirmation'
4468
+ : 'ready_to_connect';
4452
4469
  const observedWorkspaceId = normalizeDisplayText(metadata.observation_workspace_id ?? metadata.workspaceId ?? metadata.workspace_id);
4453
4470
  const observedProjectId = normalizeDisplayText(metadata.observation_project_id ?? metadata.projectId ?? metadata.project_id);
4454
4471
  const scopeSuggestion = this.resolveScopeSuggestion(currentState, observedWorkspaceId, observedProjectId);
4455
4472
  const itemId = this.buildInboxItemId('agent', agent.agent_id);
4456
4473
  const recommendedAction = link ? 'open_on_forkit' : 'link_agent_to_model';
4457
4474
  const allowedActions = this.filterInboxActionsForBinding(link
4458
- ? ['open_on_forkit', 'ignore']
4459
- : ['link_agent_to_model', 'ignore', 'open_on_forkit'], currentState);
4475
+ ? ['open_on_forkit', 'defer', 'ignore']
4476
+ : ['link_agent_to_model', 'defer', 'ignore', 'open_on_forkit'], currentState);
4460
4477
  if (scopeSuggestion.scope_mismatch_detected && scopeSuggestion.scope_mismatch_reason) {
4461
4478
  this.maybeRecordScopeMismatchEvidence(currentState, {
4462
4479
  itemType: 'agent',
@@ -4501,7 +4518,8 @@ class ConnectV1Service {
4501
4518
  last_observed_tool_at: agentObservation.lastObservedToolAt,
4502
4519
  last_tool_decision: agentObservation.lastToolDecision,
4503
4520
  source_label: sourceLabel,
4504
- review_disposition: reviewDisposition,
4521
+ review_disposition: agentReviewStatus,
4522
+ local_review_disposition: reviewDisposition,
4505
4523
  scope_suggestion: scopeSuggestion.scope_suggestion,
4506
4524
  scope_suggestion_kind: scopeSuggestion.scope_suggestion_kind,
4507
4525
  scope_suggestion_source_label: scopeSuggestion.scope_suggestion_source_label,
@@ -9024,6 +9042,21 @@ class ConnectV1Service {
9024
9042
  const model = this.resolveModelSelection(selector, state);
9025
9043
  return this.markModelIgnored(model.model, model.digest);
9026
9044
  }
9045
+ ignoreDetectedAgent(selector) {
9046
+ const state = this.stateStore.readState();
9047
+ const agent = this.findAgentByIdOrName(state, selector);
9048
+ if (!agent)
9049
+ return false;
9050
+ const nextAgents = state.detected_agents.map((item) => item.agent_id === agent.agent_id
9051
+ ? {
9052
+ ...item,
9053
+ review_state: 'ignored',
9054
+ review_deferred_until: null,
9055
+ }
9056
+ : item);
9057
+ this.stateStore.replaceDetectedAgents(nextAgents);
9058
+ return true;
9059
+ }
9027
9060
  deferDetectedModel(selector, deferHours = 24) {
9028
9061
  const state = this.stateStore.readState();
9029
9062
  const model = this.resolveModelSelection(selector, state);
@@ -9038,6 +9071,22 @@ class ConnectV1Service {
9038
9071
  this.stateStore.replaceDetectedModels(nextModels);
9039
9072
  return true;
9040
9073
  }
9074
+ deferDetectedAgent(selector, deferHours = 24) {
9075
+ const state = this.stateStore.readState();
9076
+ const agent = this.findAgentByIdOrName(state, selector);
9077
+ if (!agent)
9078
+ return false;
9079
+ const deferredUntil = new Date(Date.now() + Math.max(1, deferHours) * 60 * 60 * 1000).toISOString();
9080
+ const nextAgents = state.detected_agents.map((item) => item.agent_id === agent.agent_id
9081
+ ? {
9082
+ ...item,
9083
+ review_state: 'deferred',
9084
+ review_deferred_until: deferredUntil,
9085
+ }
9086
+ : item);
9087
+ this.stateStore.replaceDetectedAgents(nextAgents);
9088
+ return true;
9089
+ }
9041
9090
  markRuntimeIgnored(selector) {
9042
9091
  const state = this.stateStore.readState();
9043
9092
  const runtimePassport = this.resolveRuntimePassportSelection(selector, state);
@@ -569,6 +569,8 @@ export interface DetectedAgent {
569
569
  persistence?: 'temporary' | 'permanent';
570
570
  discovery_sources: AgentDiscoverySource[];
571
571
  metadata: Record<string, unknown>;
572
+ review_state?: ReviewDisposition | undefined;
573
+ review_deferred_until?: string | null;
572
574
  }
573
575
  /**
574
576
  * AgentLink (M2: Agent Class Metadata in linkage)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forkit-connect",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Forkit Connect Local Engine - The Global AI Governance Fabric",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",