@shawnstack/quickforge 1.7.7 → 1.7.9
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/bin/quickforge.mjs +10 -1
- package/dist/assets/AgentProfilesPage-BSM5w3bn.js +1 -0
- package/dist/assets/ChatPanelHost-cd80S4EX.js +48 -0
- package/dist/assets/CloudAccountSettingsPage-BJMrfgy9.js +1 -0
- package/dist/assets/{PluginsPage-CPE5tbbf.js → PluginsPage-mJkTmVVV.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DJj_NLm9.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-BE2T4d3P.js → SettingsWorkspacePage-7lQ0Wl63.js} +389 -389
- package/dist/assets/ShareLinksSettingsPage-DjFq_Lnb.js +1 -0
- package/dist/assets/SharedConversationPage-BbuNpcpP.js +1 -0
- package/dist/assets/TerminalDock-CczcCJJE.js +2 -0
- package/dist/assets/WorkspaceInspector-0i6x-diB.js +36 -0
- package/dist/assets/{icons-2D18YbSI.js → icons-DM_qJ7VJ.js} +1 -1
- package/dist/assets/{index-BiCTgUlY.css → index-D-J_8Smf.css} +1 -1
- package/dist/assets/index-DFASc15l.js +66 -0
- package/dist/assets/local-tools-Dn1Y9aPe.js +270 -0
- package/dist/assets/{model-display-label-C6-0eDxF.js → logger-8REuLPGK.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-GY7sdfIr.js → mcp-servers-dialog-BG4b67ea.js} +2 -2
- package/dist/assets/{model-reference-o41fJ6jQ.js → model-display-label-CURwH0xM.js} +1 -1
- package/dist/assets/{monaco-DvXErxp2.js → monaco-bDr860xx.js} +1 -1
- package/dist/assets/{react-vendor-3UaA-C9W.js → react-vendor-BpWW_Nvp.js} +1 -1
- package/dist/assets/{skills-dialog-C1fJ1Jl7.js → skills-dialog-DIzZvQqW.js} +3 -3
- package/dist/index.html +8 -8
- package/package.json +1 -1
- package/server/acp/server.mjs +17 -1
- package/server/agent-manager.mjs +251 -31
- package/server/ai-http-logger.mjs +162 -19
- package/server/ai-provider-options.mjs +4 -1
- package/server/auto-archive.mjs +42 -29
- package/server/cloud/auto-approval.mjs +155 -0
- package/server/cloud/client.mjs +5 -0
- package/server/cloud/qf-agent-process.mjs +163 -3
- package/server/cloud/service-config.mjs +1 -1
- package/server/index.mjs +69 -13
- package/server/lan-access-backup.mjs +256 -0
- package/server/lan-access-cutover.mjs +306 -0
- package/server/lan-access-json-file.mjs +75 -0
- package/server/lan-access-service.mjs +155 -0
- package/server/lan-access-store.mjs +110 -9
- package/server/maintenance/downgrade-lan-access-v1.mjs +101 -0
- package/server/maintenance/downgrade-session-state-v1.mjs +130 -0
- package/server/maintenance/downgrade-share-v1.mjs +101 -0
- package/server/maintenance/export-lan-access-v1.mjs +80 -0
- package/server/maintenance/export-scheduled-runs-v1.mjs +45 -0
- package/server/maintenance/export-session-state-v1.mjs +67 -0
- package/server/maintenance/export-share-v1.mjs +77 -0
- package/server/public-api.mjs +21 -2
- package/server/routes/agent.mjs +37 -4
- package/server/routes/backup.mjs +221 -39
- package/server/routes/cloud.mjs +28 -1
- package/server/routes/scheduled-tasks.mjs +347 -134
- package/server/routes/shares.mjs +4 -2
- package/server/routes/storage.mjs +138 -8
- package/server/routes/workspace.mjs +265 -4
- package/server/scheduled-runs-backup.mjs +235 -0
- package/server/scheduled-runs-cutover.mjs +497 -0
- package/server/scheduled-task-runs-service.mjs +279 -0
- package/server/session-index-service.mjs +495 -0
- package/server/session-state-backup.mjs +277 -0
- package/server/session-state-cutover.mjs +370 -0
- package/server/session-state-service.mjs +709 -0
- package/server/share-backup.mjs +255 -0
- package/server/share-cutover.mjs +296 -0
- package/server/share-json-file.mjs +60 -0
- package/server/share-service.mjs +163 -0
- package/server/share-store.mjs +196 -19
- package/server/skills.mjs +0 -1
- package/server/sqlite/database.mjs +233 -0
- package/server/sqlite/lan-access-repository.mjs +652 -0
- package/server/sqlite/migrations.mjs +577 -0
- package/server/sqlite/scheduled-task-runs-repository.mjs +464 -0
- package/server/sqlite/session-index-repository.mjs +297 -0
- package/server/sqlite/session-state-repository.mjs +786 -0
- package/server/sqlite/share-repository.mjs +854 -0
- package/server/storage.mjs +317 -1
- package/dist/assets/AgentProfilesPage-BfA_sQ3A.js +0 -1
- package/dist/assets/ChatPanelHost-Dul1ahKj.js +0 -307
- package/dist/assets/CloudAccountSettingsPage-qZh0lTUN.js +0 -1
- package/dist/assets/ScheduledTasksPage-PTxL_PPg.js +0 -2
- package/dist/assets/ShareLinksSettingsPage-Bl8fP62X.js +0 -1
- package/dist/assets/SharedConversationPage-BK9XJnEe.js +0 -1
- package/dist/assets/TerminalDock-CL7HU9DI.js +0 -2
- package/dist/assets/WorkspaceInspector-MNBLIKMn.js +0 -36
- package/dist/assets/index-BTPzyq7d.js +0 -65
- package/dist/assets/mermaid-renderer-jEbqFaTe.js +0 -2
- /package/dist/assets/{plugin-api-Uu2xAB4d.js → plugin-api-BrdQeM4B.js} +0 -0
package/server/storage.mjs
CHANGED
|
@@ -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
|
-
|
|
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-BTPzyq7d.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};
|