@shawnstack/quickforge 1.7.6 → 1.7.8

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.
Files changed (87) hide show
  1. package/dist/assets/AgentProfilesPage-BSM5w3bn.js +1 -0
  2. package/dist/assets/ChatPanelHost-cd80S4EX.js +48 -0
  3. package/dist/assets/CloudAccountSettingsPage-BJMrfgy9.js +1 -0
  4. package/dist/assets/{PluginsPage-BDKYfLna.js → PluginsPage-mJkTmVVV.js} +1 -1
  5. package/dist/assets/ScheduledTasksPage-DJj_NLm9.js +2 -0
  6. package/dist/assets/{SettingsWorkspacePage-DSy66Tuw.js → SettingsWorkspacePage-7lQ0Wl63.js} +391 -391
  7. package/dist/assets/ShareLinksSettingsPage-DjFq_Lnb.js +1 -0
  8. package/dist/assets/SharedConversationPage-BbuNpcpP.js +1 -0
  9. package/dist/assets/TerminalDock-CczcCJJE.js +2 -0
  10. package/dist/assets/WorkspaceInspector-0i6x-diB.js +36 -0
  11. package/dist/assets/{icons-2D18YbSI.js → icons-DM_qJ7VJ.js} +1 -1
  12. package/dist/assets/index-D-J_8Smf.css +3 -0
  13. package/dist/assets/index-DFASc15l.js +66 -0
  14. package/dist/assets/local-tools-Dn1Y9aPe.js +270 -0
  15. package/dist/assets/{model-display-label-C6-0eDxF.js → logger-8REuLPGK.js} +1 -1
  16. package/dist/assets/{mcp-servers-dialog-BEV4_TXC.js → mcp-servers-dialog-BG4b67ea.js} +2 -2
  17. package/dist/assets/{model-reference-o41fJ6jQ.js → model-display-label-CURwH0xM.js} +1 -1
  18. package/dist/assets/{monaco-DvXErxp2.js → monaco-bDr860xx.js} +1 -1
  19. package/dist/assets/{react-vendor-3UaA-C9W.js → react-vendor-BpWW_Nvp.js} +1 -1
  20. package/dist/assets/{skills-dialog-BTJ7mzk_.js → skills-dialog-DIzZvQqW.js} +3 -3
  21. package/dist/index.html +8 -8
  22. package/package.json +1 -1
  23. package/server/acp/server.mjs +17 -1
  24. package/server/agent-manager.mjs +251 -31
  25. package/server/ai-http-logger.mjs +162 -19
  26. package/server/ai-provider-options.mjs +4 -1
  27. package/server/auto-archive.mjs +42 -29
  28. package/server/auto-compaction.mjs +43 -1
  29. package/server/cloud/auto-approval.mjs +155 -0
  30. package/server/cloud/client.mjs +5 -0
  31. package/server/cloud/config.mjs +8 -16
  32. package/server/cloud/qf-agent-process.mjs +163 -3
  33. package/server/cloud/service-config.mjs +1 -1
  34. package/server/context-usage.mjs +11 -4
  35. package/server/index.mjs +69 -13
  36. package/server/lan-access-backup.mjs +256 -0
  37. package/server/lan-access-cutover.mjs +306 -0
  38. package/server/lan-access-json-file.mjs +75 -0
  39. package/server/lan-access-service.mjs +155 -0
  40. package/server/lan-access-store.mjs +110 -9
  41. package/server/maintenance/downgrade-lan-access-v1.mjs +101 -0
  42. package/server/maintenance/downgrade-session-state-v1.mjs +130 -0
  43. package/server/maintenance/downgrade-share-v1.mjs +101 -0
  44. package/server/maintenance/export-lan-access-v1.mjs +80 -0
  45. package/server/maintenance/export-scheduled-runs-v1.mjs +45 -0
  46. package/server/maintenance/export-session-state-v1.mjs +67 -0
  47. package/server/maintenance/export-share-v1.mjs +77 -0
  48. package/server/routes/agent.mjs +37 -4
  49. package/server/routes/backup.mjs +221 -39
  50. package/server/routes/cloud.mjs +28 -1
  51. package/server/routes/scheduled-tasks.mjs +347 -134
  52. package/server/routes/shares.mjs +4 -2
  53. package/server/routes/storage.mjs +138 -8
  54. package/server/routes/workspace.mjs +265 -4
  55. package/server/scheduled-runs-backup.mjs +235 -0
  56. package/server/scheduled-runs-cutover.mjs +497 -0
  57. package/server/scheduled-task-runs-service.mjs +279 -0
  58. package/server/session-index-service.mjs +495 -0
  59. package/server/session-state-backup.mjs +277 -0
  60. package/server/session-state-cutover.mjs +370 -0
  61. package/server/session-state-service.mjs +709 -0
  62. package/server/share-backup.mjs +255 -0
  63. package/server/share-cutover.mjs +296 -0
  64. package/server/share-json-file.mjs +60 -0
  65. package/server/share-service.mjs +163 -0
  66. package/server/share-store.mjs +196 -19
  67. package/server/skills.mjs +0 -1
  68. package/server/sqlite/database.mjs +233 -0
  69. package/server/sqlite/lan-access-repository.mjs +652 -0
  70. package/server/sqlite/migrations.mjs +577 -0
  71. package/server/sqlite/scheduled-task-runs-repository.mjs +464 -0
  72. package/server/sqlite/session-index-repository.mjs +297 -0
  73. package/server/sqlite/session-state-repository.mjs +786 -0
  74. package/server/sqlite/share-repository.mjs +854 -0
  75. package/server/storage.mjs +317 -1
  76. package/dist/assets/AgentProfilesPage-Dl5Peqov.js +0 -1
  77. package/dist/assets/ChatPanelHost-D3987TUJ.js +0 -307
  78. package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +0 -1
  79. package/dist/assets/ScheduledTasksPage--GAki3lB.js +0 -2
  80. package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +0 -1
  81. package/dist/assets/SharedConversationPage-B9BUVCri.js +0 -1
  82. package/dist/assets/TerminalDock-CT1nYo7P.js +0 -2
  83. package/dist/assets/WorkspaceInspector-BnfDLf9a.js +0 -36
  84. package/dist/assets/index-1ei6nhs-.css +0 -3
  85. package/dist/assets/index-BivLuZvX.js +0 -65
  86. package/dist/assets/mermaid-renderer-jEbqFaTe.js +0 -2
  87. /package/dist/assets/{plugin-api-Uu2xAB4d.js → plugin-api-BrdQeM4B.js} +0 -0
@@ -67,6 +67,21 @@ let bucketIndexBuilt = false
67
67
 
68
68
  // Monotonic in-process revisions for cache invalidation in route-level indexes.
69
69
  const storeRevisions = new Map()
70
+ let sessionMetadataCommitHook = null
71
+
72
+ export function registerSessionMetadataCommitHook(hook) {
73
+ if (hook !== null && typeof hook !== 'function') throw new TypeError('Session metadata commit hook must be a function or null')
74
+ sessionMetadataCommitHook = hook
75
+ }
76
+
77
+ async function notifySessionMetadataCommit(change) {
78
+ if (!sessionMetadataCommitHook) return
79
+ try {
80
+ await sessionMetadataCommitHook(change)
81
+ } catch {
82
+ // JSON is authoritative. Index failures must never change commit semantics.
83
+ }
84
+ }
70
85
 
71
86
  function bumpStoreRevision(storeName) {
72
87
  storeRevisions.set(storeName, (storeRevisions.get(storeName) || 0) + 1)
@@ -400,6 +415,59 @@ async function writeJsonAtomic(file, data) {
400
415
  await fs.rename(tmp, file)
401
416
  }
402
417
 
418
+ export async function materializeSessionJsonMirrorEntry(entry) {
419
+ const bucket = entry.scope === 'project' ? { scope: 'project', projectId: entry.projectId } : { scope: 'global' }
420
+ if (entry.operation === 'upsert') {
421
+ await writeJsonAtomic(sessionDataFile(entry.sessionId, bucket), entry.state)
422
+ const file = sessionStoreFile('sessions-metadata', bucket)
423
+ const metadata = await readJsonFile(file, {})
424
+ metadata[entry.sessionId] = entry.metadata
425
+ await writeJsonAtomic(file, metadata)
426
+ sessionBucketIndex.set(entry.sessionId, bucket)
427
+ bumpStoreRevision('sessions-metadata')
428
+ return
429
+ }
430
+ const file = sessionStoreFile('sessions-metadata', bucket)
431
+ const metadata = await readJsonFile(file, {})
432
+ delete metadata[entry.sessionId]
433
+ await writeJsonAtomic(file, metadata)
434
+ await fs.rm(sessionDataFile(entry.sessionId, bucket), { force: true })
435
+ sessionBucketIndex.delete(entry.sessionId)
436
+ bumpStoreRevision('sessions-metadata')
437
+ try {
438
+ const { deleteSessionAssets } = await import('./session-assets.mjs')
439
+ await deleteSessionAssets(bucket, entry.sessionId)
440
+ } catch { /* Sidecars are best-effort after the authoritative commit. */ }
441
+ try {
442
+ const { deleteCloudChatIdempotencySession } = await import('./cloud/chat-idempotency-store.mjs')
443
+ await deleteCloudChatIdempotencySession(entry.sessionId)
444
+ } catch { /* Sidecars are best-effort after the authoritative commit. */ }
445
+ }
446
+
447
+ export async function readPhysicalSessionStateBuckets() {
448
+ await ensureStorage()
449
+ const buckets = [
450
+ { scope: 'global', projectId: null },
451
+ ...(await listProjectIds()).map((projectId) => ({ scope: 'project', projectId })),
452
+ ]
453
+ const result = []
454
+ for (const bucket of buckets) {
455
+ const metadata = await readJsonFile(sessionStoreFile('sessions-metadata', bucket), {})
456
+ const sessions = {}
457
+ for (const file of await listSessionDataFiles(bucket)) {
458
+ const fileSessionId = path.basename(file, '.json')
459
+ sessions[fileSessionId] = await readJsonFile(file, null)
460
+ }
461
+ result.push({ ...bucket, sessions, metadata })
462
+ }
463
+ return result
464
+ }
465
+
466
+ async function sessionStateFacade() {
467
+ const service = await import('./session-state-service.mjs')
468
+ return service.isSessionStateAuthoritative() ? service : null
469
+ }
470
+
403
471
  async function readConfigFile() {
404
472
  return normalizeConfig(await readJsonFile(quickForgeConfigFile, defaultConfig()))
405
473
  }
@@ -588,6 +656,8 @@ export async function findSessionBucket(sessionId) {
588
656
  }
589
657
 
590
658
  export async function readSessionValue(sessionId) {
659
+ const facade = await sessionStateFacade()
660
+ if (facade) return facade.readSessionStateValue(sessionId)
591
661
  const bucket = await findSessionBucket(sessionId)
592
662
  if (bucket) return readJsonFile(sessionDataFile(sessionId, bucket), null)
593
663
 
@@ -598,6 +668,12 @@ export async function readSessionValue(sessionId) {
598
668
  }
599
669
 
600
670
  export async function writeSessionValue(sessionId, value) {
671
+ const facade = await sessionStateFacade()
672
+ if (facade) {
673
+ const saved = facade.saveSessionBody(sessionId, value)
674
+ void facade.drainSessionJsonMirror()
675
+ return saved.state
676
+ }
601
677
  return enqueueWrite('sessions', async () => {
602
678
  await ensureStorage()
603
679
  await writeSessionValueFile(sessionId, value)
@@ -605,6 +681,12 @@ export async function writeSessionValue(sessionId, value) {
605
681
  }
606
682
 
607
683
  export async function atomicSessionValueUpdate(sessionId, updateFn) {
684
+ const facade = await sessionStateFacade()
685
+ if (facade) {
686
+ const updated = await facade.atomicSessionStateUpdate(sessionId, updateFn)
687
+ void facade.drainSessionJsonMirror()
688
+ return updated
689
+ }
608
690
  return enqueueWrite('sessions', async () => {
609
691
  await ensureStorage()
610
692
  const current = await readSessionValue(sessionId)
@@ -616,6 +698,12 @@ export async function atomicSessionValueUpdate(sessionId, updateFn) {
616
698
  }
617
699
 
618
700
  export async function deleteSessionValue(sessionId) {
701
+ const facade = await sessionStateFacade()
702
+ if (facade) {
703
+ const deleted = facade.deleteSessionState(sessionId)
704
+ await facade.drainSessionJsonMirror()
705
+ return deleted
706
+ }
619
707
  return enqueueWrite('sessions', async () => {
620
708
  const bucket = await findSessionBucket(sessionId)
621
709
  if (!bucket) {
@@ -632,6 +720,174 @@ export async function deleteSessionValue(sessionId) {
632
720
  })
633
721
  }
634
722
 
723
+ /**
724
+ * Whether the session state service is currently authoritative (SQLite-backed).
725
+ * Re-exported for callers that must branch write behavior before invoking the
726
+ * facade (e.g. auto-archive chooses a single atomic transaction vs the legacy
727
+ * two-step JSON compensation flow).
728
+ */
729
+ export async function isSessionStateAuthoritative() {
730
+ const facade = await sessionStateFacade()
731
+ return Boolean(facade)
732
+ }
733
+
734
+ /**
735
+ * Write a session body and ensure its derived metadata exists so a single PUT
736
+ * never leaves a body orphaned. Authoritative: one SQLite transaction for
737
+ * body + derived/merged metadata. JSON: body file plus derived metadata merged
738
+ * onto the existing metadata record within the shared metadata write queue.
739
+ */
740
+ export async function writeSessionValueWithMetadata(sessionId, value) {
741
+ const facade = await sessionStateFacade()
742
+ if (facade) {
743
+ const saved = facade.saveSessionBody(sessionId, value)
744
+ void facade.drainSessionJsonMirror()
745
+ return saved.state
746
+ }
747
+ const saved = await enqueueWrite('sessions', async () => {
748
+ await ensureStorage()
749
+ await writeSessionValueFile(sessionId, value)
750
+ })
751
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
752
+ const bucket = sessionBucket(value)
753
+ const file = sessionStoreFile('sessions-metadata', bucket)
754
+ await enqueueWrite('sessions-metadata', async () => {
755
+ const previous = await readJsonFile(file, {})
756
+ const existing = previous[sessionId]
757
+ const derived = {
758
+ id: sessionId,
759
+ title: typeof value.title === 'string' ? value.title : (existing?.title ?? 'New chat'),
760
+ titleSource: value.titleSource ?? existing?.titleSource,
761
+ createdAt: value.createdAt ?? existing?.createdAt ?? new Date().toISOString(),
762
+ lastModified: value.lastModified ?? existing?.lastModified ?? value.createdAt ?? new Date().toISOString(),
763
+ messageCount: Array.isArray(value.messages) ? value.messages.length : (existing?.messageCount ?? 0),
764
+ stateVersion: Number.isInteger(value.stateVersion) && value.stateVersion >= 0 ? value.stateVersion : (existing?.stateVersion ?? 0),
765
+ scope: bucket.scope,
766
+ ...(bucket.scope === 'project' ? { projectId: bucket.projectId } : {}),
767
+ }
768
+ const next = { ...previous, [sessionId]: existing ? { ...existing, ...derived } : derived }
769
+ await writeJsonAtomic(file, next)
770
+ updateSessionMetadataBucketIndex(bucket, previous, next)
771
+ bumpStoreRevision('sessions-metadata')
772
+ await notifySessionMetadataCommit({ ...bucket, previous, next })
773
+ })
774
+ }
775
+ return saved
776
+ }
777
+
778
+ /**
779
+ * Delete an entire session (body + metadata). Authoritative: one SQLite
780
+ * transaction; JSON mirror materialization runs best-effort afterwards and
781
+ * failures never roll back the committed session delete.
782
+ */
783
+ export async function deleteSessionWithMetadata(sessionId) {
784
+ const facade = await sessionStateFacade()
785
+ if (facade) {
786
+ const deleted = facade.deleteSessionState(sessionId)
787
+ await facade.drainSessionJsonMirror()
788
+ return deleted
789
+ }
790
+ await deleteSessionValue(sessionId)
791
+ await atomicUpdate('sessions-metadata', (data) => {
792
+ delete data[sessionId]
793
+ return data
794
+ })
795
+ }
796
+
797
+ /**
798
+ * Atomically update a session record (body + metadata together).
799
+ * Authoritative: single SQLite transaction with CAS retry on conflict.
800
+ * JSON: body write plus scoped metadata merge (legacy two-step).
801
+ */
802
+ export async function atomicSessionRecordUpdate(sessionId, updateFn) {
803
+ const facade = await sessionStateFacade()
804
+ if (facade) {
805
+ const updated = await facade.atomicSessionRecordUpdate(sessionId, updateFn)
806
+ void facade.drainSessionJsonMirror()
807
+ return updated
808
+ }
809
+ return enqueueWrite('sessions', async () => {
810
+ const current = await readSessionValue(sessionId)
811
+ if (!current) return null
812
+ const bucket = sessionBucket(current)
813
+ const metadataFile = sessionStoreFile('sessions-metadata', bucket)
814
+ const metadata = (await readJsonFile(metadataFile, {}))[sessionId] || {}
815
+ const updated = await updateFn({
816
+ state: structuredClone(current),
817
+ metadata: structuredClone(metadata),
818
+ revision: null,
819
+ stateVersion: Number.isInteger(current.stateVersion) ? current.stateVersion : 0,
820
+ })
821
+ if (!updated) return null
822
+ await writeSessionValueFile(sessionId, updated.state)
823
+ await enqueueWrite('sessions-metadata', async () => {
824
+ const previousMetadata = await readJsonFile(metadataFile, {})
825
+ const nextMetadata = { ...previousMetadata, [sessionId]: { ...previousMetadata[sessionId], ...(updated.metadata || {}), id: sessionId } }
826
+ await writeJsonAtomic(metadataFile, nextMetadata)
827
+ updateSessionMetadataBucketIndex(bucket, previousMetadata, nextMetadata)
828
+ bumpStoreRevision('sessions-metadata')
829
+ await notifySessionMetadataCommit({ ...bucket, previous: previousMetadata, next: nextMetadata })
830
+ })
831
+ return { ...updated, state: updated.state }
832
+ })
833
+ }
834
+
835
+ /**
836
+ * Apply a multi-session batch of set/delete operations across
837
+ * `sessions`/`sessions-metadata` in a single authoritative SQLite transaction.
838
+ * The JSON fallback executes operations sequentially (legacy pre-cutover path).
839
+ */
840
+ export async function applySessionBatch(operations) {
841
+ if (!Array.isArray(operations) || operations.length === 0) throw new TypeError('Session batch operations are required')
842
+ const facade = await sessionStateFacade()
843
+ if (facade) {
844
+ const result = facade.applySessionBatch(operations)
845
+ void facade.drainSessionJsonMirror()
846
+ return result
847
+ }
848
+ for (const operation of operations) {
849
+ if (!['sessions', 'sessions-metadata'].includes(operation?.store)) throw new TypeError('Session batch only accepts sessions and sessions-metadata')
850
+ if (!['set', 'delete'].includes(operation?.type)) throw new TypeError('Invalid session batch operation')
851
+ if (typeof operation.key !== 'string' || !operation.key) throw new TypeError('Session batch key is required')
852
+ }
853
+ const result = { saved: 0, deleted: 0 }
854
+ for (const operation of operations) {
855
+ if (operation.type === 'delete') {
856
+ if (operation.store === 'sessions') {
857
+ const before = await readSessionValue(operation.key)
858
+ await deleteSessionWithMetadata(operation.key)
859
+ if (before) result.deleted += 1
860
+ } else {
861
+ const error = new TypeError('Metadata-only delete is not allowed')
862
+ error.errorCode = 'SESSION_FULL_DELETE_REQUIRED'
863
+ error.statusCode = 409
864
+ throw error
865
+ }
866
+ continue
867
+ }
868
+ if (operation.store === 'sessions') {
869
+ if (!operation.value || typeof operation.value !== 'object' || Array.isArray(operation.value)) throw new TypeError(`Invalid session body: ${operation.key}`)
870
+ await writeSessionValueWithMetadata(operation.key, operation.value)
871
+ result.saved += 1
872
+ } else {
873
+ if (!operation.value || typeof operation.value !== 'object' || Array.isArray(operation.value)) throw new TypeError(`Invalid session metadata: ${operation.key}`)
874
+ const existing = await readSessionValue(operation.key)
875
+ if (!existing) {
876
+ const error = new TypeError(`Session body is required for a new session: ${operation.key}`)
877
+ error.errorCode = 'SESSION_STATE_REQUIRED'
878
+ error.statusCode = 409
879
+ throw error
880
+ }
881
+ await atomicUpdate('sessions-metadata', (data) => {
882
+ data[operation.key] = { ...(data[operation.key] || {}), ...operation.value }
883
+ return data
884
+ })
885
+ result.saved += 1
886
+ }
887
+ }
888
+ return result
889
+ }
890
+
635
891
  async function listSessionStoreFiles(storeName) {
636
892
  return [
637
893
  sessionStoreFile(storeName, { scope: 'global' }),
@@ -661,12 +917,35 @@ async function readSessionStore(storeName) {
661
917
 
662
918
  export async function readSessionStoreScoped(storeName, scope, projectId) {
663
919
  await ensureStorage()
920
+ const facade = await sessionStateFacade()
921
+ if (facade && (storeName === 'sessions' || storeName === 'sessions-metadata')) {
922
+ return facade.readSessionStateStore(storeName, { scope, projectId })
923
+ }
664
924
  if (storeName === 'sessions') return readSessionValuesScoped(scope, projectId)
665
925
 
666
926
  const file = sessionStoreFile(storeName, { scope, projectId })
667
927
  return readJsonFile(file, {})
668
928
  }
669
929
 
930
+ export async function readPhysicalSessionMetadataBuckets() {
931
+ await ensureStorage()
932
+ const buckets = [
933
+ {
934
+ scope: 'global',
935
+ projectId: null,
936
+ metadata: await readJsonFile(sessionStoreFile('sessions-metadata', { scope: 'global' }), {}),
937
+ },
938
+ ]
939
+ for (const projectId of await listProjectIds()) {
940
+ buckets.push({
941
+ scope: 'project',
942
+ projectId,
943
+ metadata: await readJsonFile(sessionStoreFile('sessions-metadata', { scope: 'project', projectId }), {}),
944
+ })
945
+ }
946
+ return buckets
947
+ }
948
+
670
949
  async function writeSessionStore(storeName, data) {
671
950
  if (storeName === 'sessions') {
672
951
  await writeSessionValues(data)
@@ -710,7 +989,10 @@ async function writeSessionStore(storeName, data) {
710
989
  const bucket = bucketEntry?.bucket ?? (file === sessionStoreFile(storeName, { scope: 'global' })
711
990
  ? { scope: 'global' }
712
991
  : { scope: 'project', projectId: path.basename(path.dirname(file)) })
713
- updateSessionMetadataBucketIndex(bucket, previousByFile.get(file) ?? {}, bucketEntry?.data ?? {})
992
+ const previous = previousByFile.get(file) ?? {}
993
+ const next = bucketEntry?.data ?? {}
994
+ updateSessionMetadataBucketIndex(bucket, previous, next)
995
+ await notifySessionMetadataCommit({ ...bucket, previous, next })
714
996
  }
715
997
  bumpStoreRevision(storeName)
716
998
  }
@@ -855,6 +1137,22 @@ function enqueueWrite(queueName, operation) {
855
1137
  */
856
1138
  export async function atomicUpdate(storeName, updateFn) {
857
1139
  assertStore(storeName)
1140
+ const facade = await sessionStateFacade()
1141
+ if (facade && storeName === 'sessions-metadata') {
1142
+ const updated = facade.updateSessionMetadataBucket('global', null, (currentGlobal) => {
1143
+ const current = { ...facade.readSessionStateStore('sessions-metadata'), ...currentGlobal }
1144
+ return updateFn(current)
1145
+ })
1146
+ void facade.drainSessionJsonMirror()
1147
+ return updated
1148
+ }
1149
+ if (facade && storeName === 'sessions') {
1150
+ const current = facade.readSessionStateStore('sessions')
1151
+ const updated = await updateFn(current)
1152
+ facade.replaceSessionStateStore('sessions', updated)
1153
+ void facade.drainSessionJsonMirror()
1154
+ return updated
1155
+ }
858
1156
  const queueName = isConfigStore(storeName) ? configStoreLocations[storeName].queue : storeName
859
1157
  return enqueueWrite(queueName, async () => {
860
1158
  await ensureStorage()
@@ -880,6 +1178,12 @@ export async function atomicUpdate(storeName, updateFn) {
880
1178
  * @returns {Promise<object>} the updated scoped metadata
881
1179
  */
882
1180
  export async function atomicSessionMetadataUpdate(scope, projectId, updateFn) {
1181
+ const facade = await sessionStateFacade()
1182
+ if (facade) {
1183
+ const updated = await facade.atomicSessionMetadataStateUpdate(scope, projectId, updateFn)
1184
+ void facade.drainSessionJsonMirror()
1185
+ return updated
1186
+ }
883
1187
  const bucket = scope === 'project' ? { scope: 'project', projectId } : { scope: 'global' }
884
1188
  const file = sessionStoreFile('sessions-metadata', bucket)
885
1189
  // Scoped session-metadata writes MUST share the single 'sessions-metadata'
@@ -895,6 +1199,7 @@ export async function atomicSessionMetadataUpdate(scope, projectId, updateFn) {
895
1199
  await writeJsonAtomic(file, updated)
896
1200
  updateSessionMetadataBucketIndex(bucket, previousData, updated)
897
1201
  bumpStoreRevision('sessions-metadata')
1202
+ await notifySessionMetadataCommit({ ...bucket, previous: previousData, next: updated })
898
1203
  return updated
899
1204
  })
900
1205
  }
@@ -983,6 +1288,11 @@ export async function readStore(storeName) {
983
1288
  assertStore(storeName)
984
1289
  await ensureStorage()
985
1290
 
1291
+ const facade = await sessionStateFacade()
1292
+ if (facade && (storeName === 'sessions' || storeName === 'sessions-metadata')) {
1293
+ return facade.readSessionStateStore(storeName)
1294
+ }
1295
+
986
1296
  if (isConfigStore(storeName)) {
987
1297
  return readConfigStore(storeName)
988
1298
  }
@@ -992,6 +1302,12 @@ export async function readStore(storeName) {
992
1302
 
993
1303
  export async function writeStore(storeName, data) {
994
1304
  assertStore(storeName)
1305
+ const facade = await sessionStateFacade()
1306
+ if (facade && (storeName === 'sessions' || storeName === 'sessions-metadata')) {
1307
+ facade.replaceSessionStateStore(storeName, data)
1308
+ void facade.drainSessionJsonMirror()
1309
+ return
1310
+ }
995
1311
  const queueName = isConfigStore(storeName) ? configStoreLocations[storeName].queue : storeName
996
1312
 
997
1313
  return enqueueWrite(queueName, async () => {
@@ -1 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{A as t,Nt as n,Rt as r,Wt as i,d as a,mt as o,qt as s,s as ee}from"./icons-2D18YbSI.js";import{i as c,n as l}from"./react-vendor-3UaA-C9W.js";import{t as u}from"./model-display-label-C6-0eDxF.js";import{n as d,t as te}from"./model-reference-o41fJ6jQ.js";import{$t as ne,Qt as re,Tt as ie,at as ae,fn as f,ln as p,mn as m,nn as oe,tn as se,yn as h}from"./index-BivLuZvX.js";import{i as ce,r as le,t as ue}from"./model-identity-CgA6_Pbm.js";var g=e(s(),1),de=c(),_=l(),fe=144,pe=82,me=4,v=8;function y(e){return JSON.stringify(d(e))}function b(e){if(!e)return{mode:`inherit`};try{return{mode:`fixed`,...JSON.parse(e)}}catch{return{mode:`inherit`}}}function x(e){return!e||e.mode!==`fixed`?``:e.source===`cloud`?JSON.stringify({version:1,source:`cloud`,catalogId:e.catalogId}):e.source===`custom`?JSON.stringify({version:1,source:`custom`,providerId:e.providerId,modelId:e.modelId}):JSON.stringify({version:1,source:`legacy-custom`,provider:e.provider,modelId:e.modelId,...e.api?{api:e.api}:{},...e.baseUrl?{baseUrl:e.baseUrl}:{}})}function S(){return{name:``,label:``,description:``,systemPrompt:``,allowedTools:[`read_file`,`grep_files`],maxRuntimeMs:`3600000`,maxToolCalls:`300`,enabledAsSubagent:!0,modelMode:`inherit`,fixedModelValue:``,thinkingLevel:`inherit`}}function he(e){return{name:e.name,label:e.label,description:e.description??``,systemPrompt:e.systemPrompt??``,allowedTools:e.allowedTools??[],maxRuntimeMs:String(e.maxRuntimeMs??36e5),maxToolCalls:String(e.maxToolCalls??300),enabledAsSubagent:e.enabledAsSubagent,modelMode:e.model?.mode===`fixed`?`fixed`:`inherit`,fixedModelValue:x(e.model),thinkingLevel:e.thinkingLevel??`inherit`}}function ge(e){return{name:e.name.trim().toLowerCase(),label:e.label.trim(),description:e.description.trim(),systemPrompt:e.systemPrompt.trim(),allowedTools:e.allowedTools,maxRuntimeMs:Number(e.maxRuntimeMs||36e5),maxToolCalls:Number(e.maxToolCalls||300),enabledAsSubagent:e.enabledAsSubagent,model:e.modelMode===`fixed`?b(e.fixedModelValue):{mode:`inherit`},thinkingLevel:e.thinkingLevel}}function C(e){return!!(e.name.trim()&&e.label.trim()&&e.allowedTools.length>0)}async function w(e,t){let n=await fetch(e,{...t,headers:{"content-type":`application/json`,...t?.headers}}),r=await n.json().catch(()=>null);if(!n.ok)throw Error(r?.error||`请求失败`);return r}function T(){let[e,s]=(0,g.useState)([]),[c,l]=(0,g.useState)([]),[T,E]=(0,g.useState)(!1),[D,O]=(0,g.useState)(null),[k,A]=(0,g.useState)(()=>S()),[j,M]=(0,g.useState)(!1),[N,P]=(0,g.useState)(``),[F,I]=(0,g.useState)(!1),[L,_e]=(0,g.useState)(),[R,ve]=(0,g.useState)([]),[ye,be]=(0,g.useState)(`off`),[z,B]=(0,g.useState)(``),[V,H]=(0,g.useState)(null),[U,W]=(0,g.useState)(null);async function xe(){let[e,t]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);s(e.agents),l(t.tools)}(0,g.useEffect)(()=>{let e=!1;async function t(){try{let[t,n]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);if(e)return;s(t.agents),l(n.tools)}catch(t){e||B(t instanceof Error?t.message:h(`requestFailed`))}}return t(),()=>{e=!0}},[]),(0,g.useEffect)(()=>{let e=!1;async function t(){try{let t=await se(),[n,r]=await Promise.all([ne(t),te().catch(()=>[])]),i=r.length?r:n;ve(i);let a=i.filter(p),o=await oe(t),s=(o.model&&p(o.model)?a.find(e=>ce(e,o.model)):void 0)??a[0];if(e)return;_e(s),be(o.thinkingLevel??re(s))}catch{}}return t(),()=>{e=!0}},[]),(0,g.useEffect)(()=>{if(!V)return;let e=()=>{H(null),W(null)},t=t=>{t.key===`Escape`&&e()};return window.addEventListener(`click`,e),window.addEventListener(`blur`,e),window.addEventListener(`resize`,e),window.addEventListener(`scroll`,e,!0),document.addEventListener(`keydown`,t),()=>{window.removeEventListener(`click`,e),window.removeEventListener(`blur`,e),window.removeEventListener(`resize`,e),window.removeEventListener(`scroll`,e,!0),document.removeEventListener(`keydown`,t)}},[V]);let G=(0,g.useMemo)(()=>e.find(e=>e.id===D)??null,[e,D]),K=(0,g.useMemo)(()=>e.find(e=>e.id===V)??null,[e,V]),q=!!G?.readonly,J=!!(G?.readonly&&!G?.builtin),Y=(0,g.useMemo)(()=>{let e=b(k.fixedModelValue);if(e.mode!==`fixed`)return;let t=x(e);return R.find(e=>y(e)===t)??(`provider`in e?R.find(t=>le(t,e)):void 0)},[k.fixedModelValue,R]),Se=(0,g.useMemo)(()=>R.filter(p),[R]),Ce=(0,g.useMemo)(()=>ue(Se,Y),[Se,Y]),we=Y?y(Y):k.fixedModelValue,X=k.modelMode===`fixed`&&!!Y&&Y?.reasoning!==!0;function Te(e,t){if(e.stopPropagation(),V===t){H(null),W(null);return}let n=e.currentTarget.getBoundingClientRect(),r=Math.max(v,Math.min(n.right-fe,window.innerWidth-fe-v)),i=n.bottom+me,a=n.top-me-pe;W({left:r,top:i+pe<=window.innerHeight-v?i:Math.max(v,a)}),H(t)}function Z(e,t){A(n=>({...n,[e]:t}))}function Ee(e){ae(Y??null,Ce,e=>Z(`fixedModelValue`,e?y(e):``),{anchor:e.currentTarget,noneLabel:h(`agentModelInherit`)})}function De(e){A(t=>({...t,allowedTools:t.allowedTools.includes(e)?t.allowedTools.filter(t=>t!==e):[...t.allowedTools,e]}))}function Oe(){O(null),A(S()),P(``),B(``),E(!0)}function Q(e){O(e.id),A(he(e)),P(``),B(``),E(!0)}function $(){j||F||(E(!1),O(null),A(S()),P(``))}async function ke(){let e=N.trim();if(!e){B(h(`aiFillAgentInputRequired`));return}if(!L){B(h(`aiFillAgentNoModel`));return}I(!0),B(``);try{let t=await w(`/api/agent-profiles/ai-fill`,{method:`POST`,body:JSON.stringify({instruction:e,modelRef:d(L),model:L,thinkingLevel:ye})});A(e=>({...e,name:t.agent.name,label:t.agent.label,description:t.agent.description,systemPrompt:t.agent.systemPrompt}))}catch(e){B(e instanceof Error?e.message:h(`aiFillAgentFailed`))}finally{I(!1)}}async function Ae(){if(!C(k))return;M(!0),B(``);let e=X&&k.thinkingLevel!==`inherit`?`off`:k.thinkingLevel;try{let t=G?.builtin?{model:k.modelMode===`fixed`?b(k.fixedModelValue):{mode:`inherit`},thinkingLevel:e}:ge({...k,thinkingLevel:e});D?await w(`/api/agent-profiles/${encodeURIComponent(D)}`,{method:`PATCH`,body:JSON.stringify(t)}):await w(`/api/agent-profiles`,{method:`POST`,body:JSON.stringify(t)}),$(),await xe()}catch(e){B(e instanceof Error?e.message:h(`requestFailed`))}finally{M(!1)}}async function je(e){if(e.builtin||e.readonly)return;let t=!e.enabledAsSubagent,n=e.enabledAsSubagent;s(n=>n.map(n=>n.id===e.id?{...n,enabledAsSubagent:t}:n)),H(null);try{await w(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`PATCH`,body:JSON.stringify({enabledAsSubagent:t})})}catch(t){s(t=>t.map(t=>t.id===e.id?{...t,enabledAsSubagent:n}:t)),B(t instanceof Error?t.message:h(`requestFailed`))}}async function Me(e){if(!(e.builtin||e.readonly)&&await ie({description:h(`confirmDeleteAgent`),confirmLabel:h(`confirmDelete`),cancelLabel:h(`cancel`),variant:`destructive`})){B(``);try{await w(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await xe()}catch(e){B(e instanceof Error?e.message:h(`requestFailed`))}}}return T?(0,_.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,_.jsx)(`div`,{className:`quickforge-settings-heading`,children:(0,_.jsxs)(`h3`,{className:`quickforge-settings-title`,children:[G?.builtin?h(`builtinAgentModelSettings`):h(G?`editAgent`:`createAgent`),(0,_.jsx)(f,{label:G?.builtin?h(`builtinAgentModelOnly`):G?.readonly?h(`readonlyAgentDescription`):h(`agentsDescription`)})]})}),(0,_.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":h(G?`editAgent`:`createAgent`),children:[(0,_.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,_.jsxs)(`button`,{className:`quickforge-settings-button quickforge-settings-button-secondary`,type:`button`,onClick:$,disabled:j||F,children:[(0,_.jsx)(i,{className:`mr-2 size-4`}),h(`back`)]}),(0,_.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,_.jsx)(`div`,{className:`quickforge-settings-row-title`,children:G?.builtin?h(`builtinAgentModelSettings`):h(G?`editAgent`:`createAgent`)}),G?.builtin?(0,_.jsx)(`div`,{className:`quickforge-settings-row-description`,children:h(`builtinAgentModelOnly`)}):G?.readonly?(0,_.jsx)(`div`,{className:`quickforge-settings-row-description`,children:h(`readonlyAgentDescription`)}):null]})]}),(0,_.jsx)(`div`,{className:`px-5 py-4`,children:(0,_.jsxs)(`div`,{className:`space-y-4`,children:[(0,_.jsxs)(`div`,{className:`rounded-2xl border border-border bg-muted/20 p-3`,children:[(0,_.jsxs)(`div`,{className:`mb-2 flex items-center gap-2 text-sm font-medium text-foreground`,children:[(0,_.jsx)(a,{className:`size-4 text-primary`}),h(`aiFillAgent`),(0,_.jsx)(f,{label:h(`aiFillAgentDescription`)})]}),(0,_.jsx)(`textarea`,{className:`min-h-20 w-full resize-y rounded-xl border border-input bg-background px-3 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground/65 focus:border-ring disabled:opacity-60`,value:N,disabled:q||F,onChange:e=>P(e.target.value),placeholder:h(`aiFillAgentPlaceholder`)}),(0,_.jsx)(`div`,{className:`mt-2 flex justify-end`,children:(0,_.jsxs)(m,{variant:`outline`,size:`sm`,onClick:()=>void ke(),disabled:q||F||!N.trim(),children:[(0,_.jsx)(a,{className:`mr-1 size-3.5`}),h(F?`aiFillAgentLoading`:`aiFillAgent`)]})})]}),(0,_.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`agentName`),(0,_.jsx)(`input`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:k.name,disabled:q,onChange:e=>Z(`name`,e.target.value),placeholder:`reviewer`})]}),(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`agentLabel`),(0,_.jsx)(`input`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:k.label,disabled:q,onChange:e=>Z(`label`,e.target.value),placeholder:h(`agentLabelPlaceholder`)})]})]}),G?(0,_.jsxs)(`div`,{className:`rounded-xl border border-border bg-muted/20 px-3 py-2 text-sm`,children:[(0,_.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:h(`agentSourcePath`)}),(0,_.jsx)(`div`,{className:`mt-1 truncate font-mono text-xs text-foreground`,title:G.source?`${G.source}${G.relativePath?` · ${G.relativePath}`:``}`:void 0,children:G.source?`${G.source}${G.relativePath?` · ${G.relativePath}`:``}`:G.builtin?h(`builtinAgent`):`-`})]}):null,(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`agentDescription`),(0,_.jsx)(`input`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:k.description,disabled:q,onChange:e=>Z(`description`,e.target.value)})]}),(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`agentSystemPrompt`),(0,_.jsx)(`textarea`,{className:`mt-1 min-h-36 w-full resize-y rounded-xl border border-input bg-background px-3 py-2 text-sm outline-none focus:border-ring disabled:opacity-60`,value:k.systemPrompt,disabled:q,onChange:e=>Z(`systemPrompt`,e.target.value)})]}),(0,_.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`agentModelMode`),(0,_.jsxs)(`select`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:k.modelMode,disabled:J,onChange:e=>Z(`modelMode`,e.target.value),children:[(0,_.jsx)(`option`,{value:`inherit`,children:h(`agentModelInherit`)}),(0,_.jsx)(`option`,{value:`fixed`,children:h(`agentModelFixed`)})]})]}),(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`agentFixedModel`),(0,_.jsxs)(`select`,{className:`quickforge-model-select-desktop mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:we,disabled:J||k.modelMode!==`fixed`,onChange:e=>Z(`fixedModelValue`,e.target.value),children:[(0,_.jsx)(`option`,{value:``,children:h(`agentModelInherit`)}),Ce.map(e=>(0,_.jsx)(`option`,{value:y(e),children:u(e)},y(e)))]}),(0,_.jsxs)(`button`,{type:`button`,className:`quickforge-model-select-mobile mt-1 flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 text-sm text-left outline-none focus:border-ring disabled:opacity-60`,disabled:J||k.modelMode!==`fixed`,onClick:Ee,children:[(0,_.jsx)(`span`,{className:`truncate`,children:Y?u(Y):h(`agentModelInherit`)}),(0,_.jsx)(n,{className:`ml-2 size-4 shrink-0 text-muted-foreground`})]})]})]}),(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`agentThinkingLevel`),(0,_.jsxs)(`select`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:X&&k.thinkingLevel!==`inherit`?`off`:k.thinkingLevel,disabled:J||X,onChange:e=>Z(`thinkingLevel`,e.target.value),children:[(0,_.jsx)(`option`,{value:`inherit`,children:h(`agentThinkingInherit`)}),(0,_.jsx)(`option`,{value:`off`,children:h(`thinkingOff`)}),(0,_.jsx)(`option`,{value:`low`,children:h(`thinkingLow`)}),(0,_.jsx)(`option`,{value:`medium`,children:h(`thinkingMedium`)}),(0,_.jsx)(`option`,{value:`high`,children:h(`thinkingHigh`)}),(0,_.jsx)(`option`,{value:`xhigh`,children:h(`thinkingXHigh`)})]}),(0,_.jsx)(`span`,{className:`mt-1 block text-xs text-muted-foreground`,children:h(X?`agentThinkingUnsupported`:`agentThinkingDescription`)})]}),(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`div`,{className:`mb-2 text-sm font-medium text-foreground`,children:h(`allowedTools`)}),(0,_.jsx)(`div`,{className:`grid gap-2 sm:grid-cols-2`,children:c.map(e=>(0,_.jsxs)(`label`,{className:`flex items-start gap-2 rounded-xl border border-border bg-muted/20 p-3 text-sm disabled:opacity-60`,children:[(0,_.jsx)(`input`,{type:`checkbox`,className:`mt-1`,disabled:q,checked:k.allowedTools.includes(e.name),onChange:()=>De(e.name)}),(0,_.jsxs)(`span`,{children:[(0,_.jsx)(`span`,{className:`font-medium text-foreground`,children:e.label}),(0,_.jsx)(`span`,{className:`ml-2 font-mono text-xs text-muted-foreground`,children:e.name}),e.riskLevel===`dangerous`?(0,_.jsx)(`span`,{className:`ml-2 rounded-full bg-amber-500/10 px-2 py-0.5 text-xs text-amber-700`,children:h(`highRiskTool`)}):null,(0,_.jsx)(`span`,{className:`mt-1 block text-xs text-muted-foreground`,children:e.description})]})]},e.name))})]}),(0,_.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`maxRuntimeMs`),(0,_.jsx)(`input`,{type:`number`,className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:k.maxRuntimeMs,disabled:q,onChange:e=>Z(`maxRuntimeMs`,e.target.value)})]}),(0,_.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[h(`maxToolCalls`),(0,_.jsx)(`input`,{type:`number`,className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:k.maxToolCalls,disabled:q,onChange:e=>Z(`maxToolCalls`,e.target.value)})]})]}),(0,_.jsxs)(`label`,{className:`flex items-center gap-2 text-sm text-foreground`,children:[(0,_.jsx)(`input`,{type:`checkbox`,checked:k.enabledAsSubagent,disabled:q,onChange:e=>Z(`enabledAsSubagent`,e.target.checked)}),h(`enabledAsSubagent`)]}),z?(0,_.jsx)(`div`,{className:`rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:z}):null]})}),(0,_.jsxs)(`div`,{className:`quickforge-settings-divider flex justify-end gap-2 px-5 py-4`,children:[(0,_.jsx)(m,{variant:`outline`,onClick:$,disabled:j||F,children:h(`cancel`)}),(0,_.jsx)(m,{onClick:Ae,disabled:j||F||J||!G?.builtin&&!C(k)||k.modelMode===`fixed`&&!k.fixedModelValue,children:h(`save`)})]})]})]}):(0,_.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,_.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":h(`agentsTab`),children:[(0,_.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,_.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,_.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[(0,_.jsx)(r,{className:`size-4 text-primary`}),h(`agentsTab`)]}),(0,_.jsx)(`div`,{className:`quickforge-settings-row-description`,children:h(`agentsDescription`)})]}),(0,_.jsx)(`button`,{className:`quickforge-settings-button quickforge-settings-button-primary`,type:`button`,onClick:Oe,children:h(`createAgent`)})]}),z?(0,_.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:z}):null,e.length===0?(0,_.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:h(`loading`)}):e.map(e=>(0,_.jsxs)(`div`,{className:`quickforge-settings-list-item quickforge-agent-profile-row`,role:`button`,tabIndex:0,onClick:()=>Q(e),onKeyDown:t=>{t.target===t.currentTarget&&(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),Q(e))},children:[(0,_.jsx)(`div`,{className:`quickforge-settings-list-item-main quickforge-agent-profile-row-main`,children:(0,_.jsxs)(`div`,{className:`quickforge-agent-profile-summary`,children:[(0,_.jsx)(`span`,{className:`quickforge-agent-profile-label`,title:e.label,children:e.label}),e.description?(0,_.jsx)(`span`,{className:`quickforge-agent-profile-description`,title:e.description,children:e.description}):null]})}),(0,_.jsxs)(`div`,{className:`quickforge-settings-list-item-actions`,onClick:e=>e.stopPropagation(),children:[(0,_.jsxs)(`label`,{className:`quickforge-settings-switch`,"aria-disabled":e.builtin||e.readonly?`true`:`false`,title:e.enabledAsSubagent?h(`disableAsSubagent`):h(`enableAsSubagent`),children:[(0,_.jsx)(`input`,{type:`checkbox`,checked:e.enabledAsSubagent,disabled:e.builtin||e.readonly,onChange:()=>void je(e)}),(0,_.jsx)(`span`,{"aria-hidden":`true`})]}),(0,_.jsx)(`button`,{className:`quickforge-settings-icon-action`,type:`button`,onClick:t=>Te(t,e.id),title:h(`moreActions`),"aria-label":h(`moreActions`),"aria-haspopup":`menu`,"aria-expanded":V===e.id,children:(0,_.jsx)(o,{className:`size-4`})})]})]},e.id))]}),K&&U?(0,de.createPortal)((0,_.jsxs)(`div`,{className:`fixed z-50 w-36 overflow-hidden rounded-xl border border-border bg-popover py-1 text-sm shadow-quickforge`,style:{left:U.left,top:U.top},role:`menu`,"aria-label":h(`moreActions`),onClick:e=>e.stopPropagation(),children:[(0,_.jsxs)(`button`,{className:`flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,type:`button`,role:`menuitem`,disabled:K.readonly&&!K.builtin,onClick:()=>{H(null),W(null),Q(K)},children:[(0,_.jsx)(t,{className:`size-3.5`}),K.builtin?h(`builtinAgentModelSettings`):h(`editTask`)]}),(0,_.jsxs)(`button`,{className:`flex w-full items-center gap-2 px-3 py-2 text-left text-destructive hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,type:`button`,role:`menuitem`,disabled:K.builtin||K.readonly,onClick:()=>{H(null),W(null),Me(K)},children:[(0,_.jsx)(ee,{className:`size-3.5`}),h(`delete`)]})]}),document.body):null]})}export{T as AgentProfilesPage};