@shawnstack/quickforge 1.7.5 → 1.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/AgentProfilesPage-BfA_sQ3A.js +1 -0
- package/dist/assets/ChatPanelHost-Dul1ahKj.js +307 -0
- package/dist/assets/CloudAccountSettingsPage-qZh0lTUN.js +1 -0
- package/dist/assets/PluginsPage-CPE5tbbf.js +1 -0
- package/dist/assets/ScheduledTasksPage-PTxL_PPg.js +2 -0
- package/dist/assets/SettingsWorkspacePage-BE2T4d3P.js +1696 -0
- package/dist/assets/ShareLinksSettingsPage-Bl8fP62X.js +1 -0
- package/dist/assets/SharedConversationPage-BK9XJnEe.js +1 -0
- package/dist/assets/TerminalDock-CL7HU9DI.js +2 -0
- package/dist/assets/{WorkspaceInspector-D9kdnLDd.js → WorkspaceInspector-MNBLIKMn.js} +5 -5
- package/dist/assets/{dist-DLKC_odd.js → dist-yDHbi5ux.js} +1 -1
- package/dist/assets/{esm-Df4edDpj.js → esm-sdsK6gaB.js} +2 -2
- package/dist/assets/{icons-CXPP1fFT.js → icons-2D18YbSI.js} +1 -1
- package/dist/assets/index-BTPzyq7d.js +65 -0
- package/dist/assets/index-BiCTgUlY.css +3 -0
- package/dist/assets/{mcp-servers-dialog-BUSPvaqI.js → mcp-servers-dialog-GY7sdfIr.js} +2 -2
- package/dist/assets/{logger-8REuLPGK.js → model-display-label-C6-0eDxF.js} +1 -1
- package/dist/assets/{monaco-D6OhXgjg.js → monaco-DvXErxp2.js} +1 -1
- package/dist/assets/{react-vendor-1KC5PyRx.js → react-vendor-3UaA-C9W.js} +1 -1
- package/dist/assets/{skills-dialog-BThnIUBs.js → skills-dialog-C1fJ1Jl7.js} +3 -3
- package/dist/assets/{web-DoV8MSMn.js → web-B5qgZJRv.js} +1 -1
- package/dist/index.html +8 -8
- package/dist/sw.js +17 -0
- package/package.json +2 -1
- package/runtime-assets/agent/darwin-arm64/qf-agent +0 -0
- package/runtime-assets/agent/darwin-x64/qf-agent +0 -0
- package/runtime-assets/agent/linux-arm64/qf-agent +0 -0
- package/runtime-assets/agent/linux-x64/qf-agent +0 -0
- package/runtime-assets/agent/win32-x64/qf-agent.exe +0 -0
- package/server/access-policy.mjs +11 -0
- package/server/agent-manager.mjs +349 -48
- package/server/agent-profiles.mjs +27 -8
- package/server/auto-compaction.mjs +78 -9
- package/server/cloud/client.mjs +4 -7
- package/server/cloud/config.mjs +9 -16
- package/server/cloud/credential-store.mjs +10 -3
- package/server/cloud/identity.mjs +17 -60
- package/server/cloud/models.mjs +1 -1
- package/server/cloud/qf-agent-process.mjs +571 -0
- package/server/cloud/runtime.mjs +11 -5
- package/server/cloud/service-config.mjs +8 -4
- package/server/context-usage.mjs +11 -4
- package/server/index.mjs +96 -37
- package/server/mcp/registry.mjs +8 -7
- package/server/message-converters.mjs +40 -19
- package/server/model-catalog.mjs +11 -4
- package/server/opencode-acp-agent.mjs +1151 -0
- package/server/public-api.mjs +11 -5
- package/server/routes/agent-profiles.mjs +6 -3
- package/server/routes/agent.mjs +43 -3
- package/server/routes/backup.mjs +5 -4
- package/server/routes/cloud.mjs +54 -35
- package/server/routes/shares.mjs +7 -8
- package/server/routes/storage.mjs +9 -24
- package/server/routes/system.mjs +7 -4
- package/server/utils/process-tree.mjs +78 -0
- package/dist/assets/AgentProfilesPage-XNjuqJs7.js +0 -1
- package/dist/assets/ChatPanelHost-CuF-PZpc.js +0 -292
- package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +0 -1
- package/dist/assets/PluginsPage-BQgp0qN-.js +0 -1
- package/dist/assets/ScheduledTasksPage-DdekATzq.js +0 -2
- package/dist/assets/SettingsWorkspacePage-Cb54Ic91.js +0 -1639
- package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +0 -1
- package/dist/assets/SharedConversationPage-CjMtSQTW.js +0 -1
- package/dist/assets/TerminalDock-D4zJBJc4.js +0 -2
- package/dist/assets/index-_ZhK6bvp.js +0 -65
- package/dist/assets/index-iVV2bP_f.css +0 -3
package/server/public-api.mjs
CHANGED
|
@@ -68,7 +68,7 @@ function createVersionMismatchError(health, options = {}) {
|
|
|
68
68
|
return error
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function buildEnv(options = {}) {
|
|
71
|
+
export function buildEnv(options = {}) {
|
|
72
72
|
const host = normalizeHost(options.host)
|
|
73
73
|
const port = getPort(options.port)
|
|
74
74
|
const shareLan = options.shareLan === true
|
|
@@ -84,6 +84,10 @@ function buildEnv(options = {}) {
|
|
|
84
84
|
if (options.workspaceDir) env.QUICKFORGE_WORKSPACE_DIR = path.resolve(options.workspaceDir)
|
|
85
85
|
if (options.vitePort) env.QUICKFORGE_VITE_PORT = String(options.vitePort)
|
|
86
86
|
if (options.terminal === false) env.QUICKFORGE_TERMINAL = '0'
|
|
87
|
+
if (options.qfAgentPath) env.QUICKFORGE_QF_AGENT_PATH = path.resolve(options.qfAgentPath)
|
|
88
|
+
if (options.qfAgentIdentityDir) env.QUICKFORGE_QF_AGENT_IDENTITY_DIR = path.resolve(options.qfAgentIdentityDir)
|
|
89
|
+
if (options.qfAgentEnabled !== undefined) env.QUICKFORGE_QF_AGENT_ENABLED = options.qfAgentEnabled === false ? '0' : '1'
|
|
90
|
+
if (options.runtimeKind) env.QUICKFORGE_RUNTIME_KIND = String(options.runtimeKind)
|
|
87
91
|
if (options.allowRemote || shareLan) env.QUICKFORGE_ALLOW_REMOTE = '1'
|
|
88
92
|
delete env.ELECTRON_RUN_AS_NODE
|
|
89
93
|
delete env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
|
|
@@ -172,7 +176,7 @@ export async function startQuickForge(options = {}) {
|
|
|
172
176
|
|
|
173
177
|
if (options.inline === true) {
|
|
174
178
|
prepareQuickForgeEnv(options)
|
|
175
|
-
await import(serverScriptUrl)
|
|
179
|
+
const serverModule = await import(serverScriptUrl)
|
|
176
180
|
const health = await waitForQuickForge(options)
|
|
177
181
|
if (!health) throw new Error('QuickForge failed to start: health check timed out')
|
|
178
182
|
return {
|
|
@@ -184,7 +188,8 @@ export async function startQuickForge(options = {}) {
|
|
|
184
188
|
reused: false,
|
|
185
189
|
inline: true,
|
|
186
190
|
async stop() {
|
|
187
|
-
|
|
191
|
+
await serverModule.stopQuickForgeServer()
|
|
192
|
+
return true
|
|
188
193
|
},
|
|
189
194
|
}
|
|
190
195
|
}
|
|
@@ -242,8 +247,9 @@ export async function startQuickForge(options = {}) {
|
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
export async function stopQuickForge(instance) {
|
|
245
|
-
if (!instance
|
|
246
|
-
if (instance.
|
|
250
|
+
if (!instance || instance.reused) return false
|
|
251
|
+
if (typeof instance.stop === 'function') return instance.stop()
|
|
252
|
+
if (!instance.child || instance.child.killed) return false
|
|
247
253
|
instance.child.kill('SIGTERM')
|
|
248
254
|
return true
|
|
249
255
|
}
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
getAgentProfile,
|
|
41
41
|
listAgentProfiles,
|
|
42
42
|
listAvailableAgentTools,
|
|
43
|
-
|
|
43
|
+
updateBuiltinAgentOverrides,
|
|
44
44
|
updateCustomAgentProfile,
|
|
45
45
|
} from '../agent-profiles.mjs'
|
|
46
46
|
|
|
@@ -194,9 +194,12 @@ export async function handleAgentProfilesApi(req, res, url, context = {}) {
|
|
|
194
194
|
const current = await getAgentProfile(id)
|
|
195
195
|
const body = await readJsonBody(req)
|
|
196
196
|
if (current?.builtin) {
|
|
197
|
+
const allowedKeys = ['model', 'thinkingLevel']
|
|
197
198
|
const keys = Object.keys(body || {})
|
|
198
|
-
if (keys.length
|
|
199
|
-
|
|
199
|
+
if (keys.length === 0 || keys.some((key) => !allowedKeys.includes(key))) {
|
|
200
|
+
throw requestError('Built-in agents only allow model and thinkingLevel updates', 403)
|
|
201
|
+
}
|
|
202
|
+
sendJson(res, 200, { agent: agentProfileSnapshot(await updateBuiltinAgentOverrides(id, await canonicalizeProfileModel(body, current, context))) })
|
|
200
203
|
return
|
|
201
204
|
}
|
|
202
205
|
if (current?.readonly) throw requestError('Read-only agents cannot be modified from the API', 403)
|
package/server/routes/agent.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { logger } from '../utils/logger.mjs'
|
|
|
3
3
|
import { resolveModelBinding } from '../model-catalog.mjs'
|
|
4
4
|
import {
|
|
5
5
|
createAgent,
|
|
6
|
+
validateAgentHarness,
|
|
6
7
|
runPrompt,
|
|
7
8
|
abortRun,
|
|
8
9
|
steerAgent,
|
|
@@ -22,6 +23,9 @@ import {
|
|
|
22
23
|
updateSessionYoloMode,
|
|
23
24
|
updateSessionModel,
|
|
24
25
|
updateSessionThinkingLevel,
|
|
26
|
+
updateSessionHarnessConfigOption,
|
|
27
|
+
updateSessionHarnessMode,
|
|
28
|
+
forkSession,
|
|
25
29
|
approveToolCall,
|
|
26
30
|
rejectToolCall,
|
|
27
31
|
approveAutoCompact,
|
|
@@ -161,12 +165,13 @@ export async function handleAgentApi(req, res, url, context = {}) {
|
|
|
161
165
|
// POST /api/agents/:sessionId — create/ensure agent
|
|
162
166
|
if (req.method === 'POST' && parts.length === 3) {
|
|
163
167
|
const body = await readJsonBody(req)
|
|
168
|
+
const harness = validateAgentHarness(body?.harness)
|
|
164
169
|
let config
|
|
165
|
-
if (body?.modelRef || body?.model) {
|
|
170
|
+
if (harness === 'quickforge' && (body?.modelRef || body?.model)) {
|
|
166
171
|
const binding = await resolveModelBinding(body, { context, legacySnapshot: body?.model })
|
|
167
|
-
config = { ...body, model: binding.model, modelRef: binding.modelRef, modelAccessContext: context, resolvePersistedModel: true }
|
|
172
|
+
config = { ...body, harness, model: binding.model, modelRef: binding.modelRef, modelAccessContext: context, resolvePersistedModel: true }
|
|
168
173
|
} else {
|
|
169
|
-
config = { ...body, modelAccessContext: context, resolvePersistedModel: true }
|
|
174
|
+
config = { ...body, harness, modelAccessContext: context, resolvePersistedModel: true }
|
|
170
175
|
}
|
|
171
176
|
const session = await createAgent(sessionId, config)
|
|
172
177
|
sendJson(res, 200, {
|
|
@@ -175,6 +180,8 @@ export async function handleAgentApi(req, res, url, context = {}) {
|
|
|
175
180
|
scope: session.scope,
|
|
176
181
|
title: session.title,
|
|
177
182
|
source: session.source || undefined,
|
|
183
|
+
harness: session.harness,
|
|
184
|
+
harnessSessionId: session.agent.harnessSessionId || session.harnessSessionId || undefined,
|
|
178
185
|
channelId: session.channelId || undefined,
|
|
179
186
|
channelName: session.channelName || undefined,
|
|
180
187
|
accessMode: session.accessMode,
|
|
@@ -243,6 +250,39 @@ export async function handleAgentApi(req, res, url, context = {}) {
|
|
|
243
250
|
return
|
|
244
251
|
}
|
|
245
252
|
|
|
253
|
+
// POST /api/agents/:sessionId/harness/config-option — update an advertised Harness config option
|
|
254
|
+
if (req.method === 'POST' && subPath === 'harness/config-option') {
|
|
255
|
+
const body = await readJsonBody(req)
|
|
256
|
+
if (typeof body?.configId !== 'string' || !body.configId || body.value === undefined) {
|
|
257
|
+
const error = new Error('Missing configId or value in request body')
|
|
258
|
+
error.statusCode = 400
|
|
259
|
+
throw error
|
|
260
|
+
}
|
|
261
|
+
const result = await updateSessionHarnessConfigOption(sessionId, body.configId, body.value)
|
|
262
|
+
sendJson(res, 200, result)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// POST /api/agents/:sessionId/harness/mode — update an advertised Harness mode
|
|
267
|
+
if (req.method === 'POST' && subPath === 'harness/mode') {
|
|
268
|
+
const body = await readJsonBody(req)
|
|
269
|
+
if (typeof body?.modeId !== 'string' || !body.modeId) {
|
|
270
|
+
const error = new Error('Missing modeId in request body')
|
|
271
|
+
error.statusCode = 400
|
|
272
|
+
throw error
|
|
273
|
+
}
|
|
274
|
+
const result = await updateSessionHarnessMode(sessionId, body.modeId)
|
|
275
|
+
sendJson(res, 200, result)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// POST /api/agents/:sessionId/fork — fork the entire current OpenCode session
|
|
280
|
+
if (req.method === 'POST' && subPath === 'fork') {
|
|
281
|
+
const result = await forkSession(sessionId)
|
|
282
|
+
sendJson(res, 200, result)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
246
286
|
// POST /api/agents/:sessionId/rollback — roll back from a message index on the authoritative server state
|
|
247
287
|
if (req.method === 'POST' && subPath === 'rollback') {
|
|
248
288
|
const body = await readJsonBody(req)
|
package/server/routes/backup.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { promises as fs } from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import { isAuthenticatedAppClient } from '../access-policy.mjs'
|
|
4
5
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
5
6
|
import {
|
|
6
7
|
ensureStorage,
|
|
@@ -580,11 +581,11 @@ async function restoreValidatedBackup(backup, mode = 'replace') {
|
|
|
580
581
|
return summary
|
|
581
582
|
}
|
|
582
583
|
|
|
583
|
-
export async function handleBackupApi(req, res, url, context = {}) {
|
|
584
|
-
if (context
|
|
585
|
-
const error = new Error('Backup import and export
|
|
584
|
+
export async function handleBackupApi(req, res, url, context = { isLocalRequest: true }) {
|
|
585
|
+
if (!isAuthenticatedAppClient(context)) {
|
|
586
|
+
const error = new Error('Backup import and export require a local or authenticated remote client.')
|
|
586
587
|
error.statusCode = 403
|
|
587
|
-
error.errorCode = '
|
|
588
|
+
error.errorCode = 'backup_auth_required'
|
|
588
589
|
throw error
|
|
589
590
|
}
|
|
590
591
|
if (req.method === 'GET' && url.pathname === '/api/backup/export') {
|
package/server/routes/cloud.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getQfAgentStatus } from '../cloud/qf-agent-process.mjs'
|
|
2
|
+
import { isAuthenticatedAppClient } from '../access-policy.mjs'
|
|
1
3
|
import { CloudClient, CloudApiError } from '../cloud/client.mjs'
|
|
2
4
|
import { parseCloudBaseUrl } from '../cloud/config.mjs'
|
|
3
5
|
import { createCloudCredentialStore } from '../cloud/credential-store.mjs'
|
|
@@ -7,7 +9,6 @@ import {
|
|
|
7
9
|
readCloudServiceConfig,
|
|
8
10
|
saveCloudServiceConfig,
|
|
9
11
|
} from '../cloud/service-config.mjs'
|
|
10
|
-
import { isTailscaleAddress } from '../utils/network.mjs'
|
|
11
12
|
import { readJsonBody, sendJson } from '../utils/response.mjs'
|
|
12
13
|
|
|
13
14
|
const CLOUD_CONFIG_BODY_MAX_BYTES = 16 * 1024
|
|
@@ -79,6 +80,8 @@ export function createCloudRouteHandler({
|
|
|
79
80
|
credentialStoreFactory = createCloudCredentialStore,
|
|
80
81
|
invalidateRuntime = invalidateCloudRuntime,
|
|
81
82
|
cloudClientFactory = (config) => new CloudClient(config),
|
|
83
|
+
qfAgentStatus = getQfAgentStatus,
|
|
84
|
+
onServiceConfigChanged,
|
|
82
85
|
} = {}) {
|
|
83
86
|
async function runtimeOrError() {
|
|
84
87
|
try {
|
|
@@ -88,19 +91,23 @@ export function createCloudRouteHandler({
|
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
return async function handleCloudApi(req, res, url, {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
remoteAuthorized = false,
|
|
95
|
-
} = {}) {
|
|
96
|
-
const tailscaleAllowed = remoteAuthorized === true
|
|
97
|
-
&& isTailscaleAddress(remoteAddress)
|
|
98
|
-
if (!isLocalRequest && !tailscaleAllowed) {
|
|
99
|
-
throw routeError('QuickForge Cloud is available only on this computer or an authorized Tailscale client.', 403, 'cloud_local_only')
|
|
94
|
+
return async function handleCloudApi(req, res, url, context = {}) {
|
|
95
|
+
if (!isAuthenticatedAppClient(context)) {
|
|
96
|
+
throw routeError('QuickForge Cloud is available only on this computer or an authenticated remote client.', 403, 'cloud_local_only')
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
requireProtectedCloudWrite(req, url.pathname)
|
|
103
100
|
|
|
101
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/remote/status') {
|
|
102
|
+
const service = await readServiceConfig({ strict: false })
|
|
103
|
+
if (!service.enabled) {
|
|
104
|
+
sendJson(res, 200, { ...qfAgentStatus(), enabled: false, status: 'disabled', serverUrl: null, pid: null, verificationUriComplete: null, error: null })
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
sendJson(res, 200, qfAgentStatus())
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
104
111
|
if (req.method === 'GET' && url.pathname === '/api/cloud/config') {
|
|
105
112
|
sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
|
|
106
113
|
return
|
|
@@ -125,19 +132,30 @@ export function createCloudRouteHandler({
|
|
|
125
132
|
|
|
126
133
|
if (req.method === 'PUT' && url.pathname === '/api/cloud/config') {
|
|
127
134
|
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
if (body?.enabled !== undefined && typeof body.enabled !== 'boolean') {
|
|
136
|
+
throw routeError('QuickForge Cloud enabled must be a boolean.', 400, 'cloud_enabled_invalid')
|
|
137
|
+
}
|
|
138
|
+
const currentConfig = await readServiceConfig({ strict: false })
|
|
139
|
+
const nextUrl = parseCloudBaseUrl(body?.cloudUrl === undefined ? currentConfig.cloudUrl : body.cloudUrl).href
|
|
140
|
+
const nextEnabled = body?.enabled === undefined ? currentConfig.enabled === true : body.enabled
|
|
141
|
+
const urlChanged = !sameCloudUrl(currentConfig.cloudUrl, nextUrl)
|
|
142
|
+
if (body?.cloudUrl !== undefined) {
|
|
143
|
+
const credentialStore = credentialStoreFactory()
|
|
144
|
+
const identityRecord = await credentialStore.read()
|
|
145
|
+
if (identityRecord.refreshToken || identityRecord.pendingDeviceFlow) {
|
|
146
|
+
const boundUrl = String(identityRecord.pendingDeviceFlow?.sessionCloudUrl || identityRecord.sessionCloudUrl || '').trim()
|
|
147
|
+
const sameUrl = boundUrl ? sameCloudUrl(boundUrl, nextUrl) : false
|
|
148
|
+
if (!sameUrl) {
|
|
149
|
+
throw routeError('A QuickForge Cloud session is active. Sign out first, or explicitly rebuild the identity before switching services.', 409, 'cloud_session_active')
|
|
150
|
+
}
|
|
136
151
|
}
|
|
137
152
|
}
|
|
138
|
-
await saveServiceConfig(nextUrl)
|
|
153
|
+
await saveServiceConfig({ cloudUrl: nextUrl, enabled: nextEnabled })
|
|
139
154
|
invalidateRuntime()
|
|
140
|
-
|
|
155
|
+
const saved = await readServiceConfig({ strict: false })
|
|
156
|
+
const notify = context.onCloudServiceConfigChanged || onServiceConfigChanged
|
|
157
|
+
if (typeof notify === 'function') await notify(saved, { urlChanged })
|
|
158
|
+
sendJson(res, 200, publicCloudServiceConfig(saved))
|
|
141
159
|
return
|
|
142
160
|
}
|
|
143
161
|
|
|
@@ -145,7 +163,7 @@ export function createCloudRouteHandler({
|
|
|
145
163
|
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
146
164
|
requireDangerousConfirmation(body)
|
|
147
165
|
const { runtime } = await runtimeOrError()
|
|
148
|
-
if (runtime?.
|
|
166
|
+
if (runtime?.identity) await runtime.identity.resetIdentity()
|
|
149
167
|
else {
|
|
150
168
|
const store = credentialStoreFactory()
|
|
151
169
|
await store.rotateInstallation()
|
|
@@ -158,25 +176,32 @@ export function createCloudRouteHandler({
|
|
|
158
176
|
const { runtime: current, configurationError } = await runtimeOrError()
|
|
159
177
|
if (req.method === 'GET' && url.pathname === '/api/cloud/status') {
|
|
160
178
|
if (configurationError) {
|
|
161
|
-
sendJson(res, 200, { configured: false, mode: 'local', configurationError: configurationError.message })
|
|
179
|
+
sendJson(res, 200, { configured: false, enabled: false, mode: 'local', configurationError: configurationError.message })
|
|
162
180
|
return
|
|
163
181
|
}
|
|
164
182
|
if (!current?.enabled) {
|
|
165
|
-
|
|
183
|
+
const localStatus = current?.identity ? await current.identity.status() : current?.store ? await current.store.readPublic() : { mode: 'local' }
|
|
184
|
+
sendJson(res, 200, { ...localStatus, configured: Boolean(current?.config?.baseUrl), enabled: false })
|
|
166
185
|
return
|
|
167
186
|
}
|
|
168
|
-
sendJson(res, 200, { configured: true, ...(await current.identity.status()) })
|
|
187
|
+
sendJson(res, 200, { configured: true, enabled: true, ...(await current.identity.status()) })
|
|
169
188
|
return
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
if (configurationError) throw routeError(configurationError.message, 503, 'cloud_configuration_error')
|
|
173
|
-
|
|
192
|
+
|
|
193
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/logout') {
|
|
194
|
+
if (current?.identity) await current.identity.logout()
|
|
195
|
+
else if (current?.store) await current.store.clearSession({ rotateInstallationBeforeRegistration: true })
|
|
196
|
+
else await credentialStoreFactory().clearSession({ rotateInstallationBeforeRegistration: true })
|
|
197
|
+
invalidateRuntime()
|
|
198
|
+
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!current?.enabled) throw routeError('QuickForge Cloud is disabled.', 503, 'cloud_disabled')
|
|
174
203
|
|
|
175
204
|
try {
|
|
176
|
-
if (req.method === 'POST' && url.pathname === '/api/cloud/guest/start') {
|
|
177
|
-
sendJson(res, 201, await current.identity.startGuest())
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
205
|
if (req.method === 'POST' && url.pathname === '/api/cloud/device/start') {
|
|
181
206
|
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
182
207
|
sendJson(res, 201, await current.identity.startDeviceFlow())
|
|
@@ -204,12 +229,6 @@ export function createCloudRouteHandler({
|
|
|
204
229
|
sendJson(res, 200, await current.identity.installations())
|
|
205
230
|
return
|
|
206
231
|
}
|
|
207
|
-
if (req.method === 'POST' && url.pathname === '/api/cloud/logout') {
|
|
208
|
-
await current.identity.logout()
|
|
209
|
-
invalidateRuntime()
|
|
210
|
-
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
211
|
-
return
|
|
212
|
-
}
|
|
213
232
|
const revokeMatch = url.pathname.match(/^\/api\/cloud\/installations\/([^/]+)$/)
|
|
214
233
|
if (req.method === 'DELETE' && revokeMatch) {
|
|
215
234
|
await current.identity.revokeInstallation(decodeURIComponent(revokeMatch[1]))
|
package/server/routes/shares.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
2
|
import { readSessionValue } from '../storage.mjs'
|
|
3
|
-
import { getLanUrls
|
|
3
|
+
import { getLanUrls } from '../utils/network.mjs'
|
|
4
|
+
import { isAuthenticatedAppClient } from '../access-policy.mjs'
|
|
4
5
|
import {
|
|
5
6
|
createConversationShare,
|
|
6
7
|
deleteConversationShare,
|
|
@@ -28,7 +29,7 @@ function shareUrlForRequest(req, shareId, port) {
|
|
|
28
29
|
return `${baseUrl}/share/${encodeURIComponent(shareId)}`
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export async function handleSharesApi(req, res, url, context = {}) {
|
|
32
|
+
export async function handleSharesApi(req, res, url, context = { isLocalRequest: true }) {
|
|
32
33
|
const parts = url.pathname.split('/').filter(Boolean)
|
|
33
34
|
|
|
34
35
|
if (req.method === 'GET' && url.pathname === '/api/shares') {
|
|
@@ -51,9 +52,8 @@ export async function handleSharesApi(req, res, url, context = {}) {
|
|
|
51
52
|
const password = passwordProvided ? body.password.trim() : undefined
|
|
52
53
|
const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
|
|
53
54
|
const allowCloudUsage = permission === 'operate' && body?.allowCloudUsage === true
|
|
54
|
-
if (allowCloudUsage && context
|
|
55
|
-
|
|
56
|
-
const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authorized Tailscale client.')
|
|
55
|
+
if (allowCloudUsage && !isAuthenticatedAppClient(context)) {
|
|
56
|
+
const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authenticated remote client.')
|
|
57
57
|
error.statusCode = 403
|
|
58
58
|
throw error
|
|
59
59
|
}
|
|
@@ -134,9 +134,8 @@ export async function handleSharesApi(req, res, url, context = {}) {
|
|
|
134
134
|
const password = typeof body?.password === 'string' ? body.password : undefined
|
|
135
135
|
const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
|
|
136
136
|
const allowCloudUsage = body?.allowCloudUsage
|
|
137
|
-
if (allowCloudUsage === true && context
|
|
138
|
-
|
|
139
|
-
const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authorized Tailscale client.')
|
|
137
|
+
if (allowCloudUsage === true && !isAuthenticatedAppClient(context)) {
|
|
138
|
+
const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authenticated remote client.')
|
|
140
139
|
error.statusCode = 403
|
|
141
140
|
throw error
|
|
142
141
|
}
|
|
@@ -2,6 +2,7 @@ import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
|
2
2
|
import { readStore, writeStore, atomicUpdate, getComparable, getStoreRevision, readSessionStoreScoped, readSessionValue, writeSessionValue, deleteSessionValue, ensureStorage, dataDir, configDir, storageDir, cacheDir, logsDir } from '../storage.mjs'
|
|
3
3
|
import { AUTO_ARCHIVE_SETTINGS_KEY, archiveInactiveSessions, normalizeAutoArchiveSettings } from '../auto-archive.mjs'
|
|
4
4
|
import { directorySize } from '../utils/workspace.mjs'
|
|
5
|
+
import { isAuthenticatedAppClient } from '../access-policy.mjs'
|
|
5
6
|
|
|
6
7
|
const metadataIndexCache = new Map()
|
|
7
8
|
const MAX_METADATA_INDEX_CACHE_ENTRIES = 50
|
|
@@ -94,7 +95,14 @@ async function readIndexedValues(store, indexName, direction, scope, projectId,
|
|
|
94
95
|
return values
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
export async function handleStorageApi(req, res, url, context = {}) {
|
|
98
|
+
export async function handleStorageApi(req, res, url, context = { isLocalRequest: true }) {
|
|
99
|
+
if (!isAuthenticatedAppClient(context)) {
|
|
100
|
+
const error = new Error('Storage access requires a local or authenticated remote client.')
|
|
101
|
+
error.statusCode = 403
|
|
102
|
+
error.errorCode = 'storage_auth_required'
|
|
103
|
+
throw error
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
const parts = url.pathname.split('/').filter(Boolean)
|
|
99
107
|
|
|
100
108
|
if (req.method === 'GET' && url.pathname === '/api/storage/quota') {
|
|
@@ -116,29 +124,6 @@ export async function handleStorageApi(req, res, url, context = {}) {
|
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
const store = decodeSegment(parts[2])
|
|
119
|
-
const remotelySensitiveStores = new Set([
|
|
120
|
-
'provider-keys',
|
|
121
|
-
'custom-providers',
|
|
122
|
-
'mcp',
|
|
123
|
-
'plugins',
|
|
124
|
-
'scheduled-tasks',
|
|
125
|
-
'agent-profile-overrides',
|
|
126
|
-
'custom-agents',
|
|
127
|
-
])
|
|
128
|
-
if (context.isLocalRequest === false && remotelySensitiveStores.has(store)) {
|
|
129
|
-
const error = new Error('This storage area is available only on this computer.')
|
|
130
|
-
error.statusCode = 403
|
|
131
|
-
error.errorCode = 'storage_local_only'
|
|
132
|
-
throw error
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const remotelyReadOnlyStores = new Set(['sessions', 'sessions-metadata'])
|
|
136
|
-
if (context.isLocalRequest === false && remotelyReadOnlyStores.has(store) && req.method !== 'GET') {
|
|
137
|
-
const error = new Error('Remote clients must modify conversations through the Agent API.')
|
|
138
|
-
error.statusCode = 403
|
|
139
|
-
error.errorCode = 'storage_remote_read_only'
|
|
140
|
-
throw error
|
|
141
|
-
}
|
|
142
127
|
|
|
143
128
|
if (req.method === 'GET' && parts[3] === 'keys') {
|
|
144
129
|
const prefix = url.searchParams.get('prefix') || ''
|
package/server/routes/system.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
2
2
|
import { getLanUrls } from '../utils/network.mjs'
|
|
3
|
+
import { isAuthenticatedAppClient } from '../access-policy.mjs'
|
|
3
4
|
|
|
4
5
|
export async function handleSystemApi(req, res, url, context) {
|
|
5
6
|
if (req.method === 'GET' && url.pathname === '/api/system/about') {
|
|
@@ -18,9 +19,10 @@ export async function handleSystemApi(req, res, url, context) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
if (req.method === 'POST' && url.pathname === '/api/system/update') {
|
|
21
|
-
if (!context
|
|
22
|
-
const error = new Error('Update
|
|
22
|
+
if (!isAuthenticatedAppClient(context)) {
|
|
23
|
+
const error = new Error('Update requires a local or authenticated remote client')
|
|
23
24
|
error.statusCode = 403
|
|
25
|
+
error.errorCode = 'system_update_requires_auth'
|
|
24
26
|
throw error
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -36,9 +38,10 @@ export async function handleSystemApi(req, res, url, context) {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
if (req.method === 'POST' && url.pathname === '/api/system/restart') {
|
|
39
|
-
if (!context
|
|
40
|
-
const error = new Error('Restart
|
|
41
|
+
if (!isAuthenticatedAppClient(context)) {
|
|
42
|
+
const error = new Error('Restart requires a local or authenticated remote client')
|
|
41
43
|
error.statusCode = 403
|
|
44
|
+
error.errorCode = 'system_restart_requires_auth'
|
|
42
45
|
throw error
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { once } from 'node:events'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_GRACE_MS = 1000
|
|
5
|
+
const DEFAULT_FORCE_WAIT_MS = 1000
|
|
6
|
+
|
|
7
|
+
function isRunning(child) {
|
|
8
|
+
return Boolean(child?.pid) && child.exitCode == null && child.signalCode == null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function waitForExit(child, timeoutMs, setTimer = setTimeout, clearTimer = clearTimeout) {
|
|
12
|
+
if (!isRunning(child)) return Promise.resolve(true)
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
let settled = false
|
|
15
|
+
const finish = (exited) => {
|
|
16
|
+
if (settled) return
|
|
17
|
+
settled = true
|
|
18
|
+
clearTimer(timer)
|
|
19
|
+
child.removeListener?.('exit', onExit)
|
|
20
|
+
resolve(exited)
|
|
21
|
+
}
|
|
22
|
+
const onExit = () => finish(true)
|
|
23
|
+
const timer = setTimer(() => finish(!isRunning(child)), Math.max(0, timeoutMs))
|
|
24
|
+
timer?.unref?.()
|
|
25
|
+
child.once?.('exit', onExit)
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runTaskkill(pid, force, spawnImpl = spawn) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const args = ['/PID', String(pid), '/T', ...(force ? ['/F'] : [])]
|
|
32
|
+
let command
|
|
33
|
+
try {
|
|
34
|
+
command = spawnImpl('taskkill.exe', args, { windowsHide: true, stdio: 'ignore' })
|
|
35
|
+
} catch {
|
|
36
|
+
resolve(false)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
command.once?.('error', () => resolve(false))
|
|
40
|
+
command.once?.('exit', (code) => resolve(code === 0))
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function signalProcessTree(child, signal, options = {}) {
|
|
45
|
+
if (!child?.pid) return false
|
|
46
|
+
const platform = options.platform ?? process.platform
|
|
47
|
+
if (platform === 'win32') {
|
|
48
|
+
return runTaskkill(child.pid, signal === 'SIGKILL', options.spawnImpl)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const killImpl = options.killImpl ?? process.kill
|
|
52
|
+
try {
|
|
53
|
+
killImpl(-child.pid, signal)
|
|
54
|
+
return true
|
|
55
|
+
} catch {
|
|
56
|
+
try {
|
|
57
|
+
return child.kill?.(signal) !== false
|
|
58
|
+
} catch {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function terminateProcessTree(child, options = {}) {
|
|
65
|
+
if (!isRunning(child)) return
|
|
66
|
+
const graceMs = options.graceMs ?? DEFAULT_GRACE_MS
|
|
67
|
+
const forceWaitMs = options.forceWaitMs ?? DEFAULT_FORCE_WAIT_MS
|
|
68
|
+
const timerOptions = { setTimer: options.setTimer, clearTimer: options.clearTimer }
|
|
69
|
+
|
|
70
|
+
await signalProcessTree(child, 'SIGTERM', options)
|
|
71
|
+
if (await waitForExit(child, graceMs, timerOptions.setTimer, timerOptions.clearTimer)) return
|
|
72
|
+
|
|
73
|
+
await signalProcessTree(child, 'SIGKILL', options)
|
|
74
|
+
await Promise.race([
|
|
75
|
+
once(child, 'exit').catch(() => {}),
|
|
76
|
+
waitForExit(child, forceWaitMs, timerOptions.setTimer, timerOptions.clearTimer),
|
|
77
|
+
])
|
|
78
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{A as t,At as n,Ft as r,Vt as i,Wt as a,dt as o,o as s,u as ee}from"./icons-CXPP1fFT.js";import{i as c,n as l}from"./react-vendor-1KC5PyRx.js";import{n as u,t as te}from"./model-reference-o41fJ6jQ.js";import{B as d,Gt as ne,Jt as re,Kt as ie,Yt as ae,dn as f,in as oe,on as p,ot as se,tn as m,z as ce}from"./index-_ZhK6bvp.js";import{i as le,r as ue,t as de}from"./model-identity-CgA6_Pbm.js";var h=e(a(),1),fe=c(),g=l(),_=144,v=82,pe=4,y=8;function b(e){return JSON.stringify(u(e))}function x(e){if(!e)return{mode:`inherit`};try{return{mode:`fixed`,...JSON.parse(e)}}catch{return{mode:`inherit`}}}function S(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 C(){return{name:``,label:``,description:``,systemPrompt:``,allowedTools:[`read_file`,`grep_files`],maxRuntimeMs:`3600000`,maxToolCalls:`300`,enabledAsSubagent:!0,modelMode:`inherit`,fixedModelValue:``,thinkingLevel:`inherit`}}function me(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:S(e.model),thinkingLevel:e.thinkingLevel??`inherit`}}function he(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`?x(e.fixedModelValue):{mode:`inherit`},thinkingLevel:e.thinkingLevel}}function w(e){return!!(e.name.trim()&&e.label.trim()&&e.allowedTools.length>0)}async function T(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 E(){let[e,a]=(0,h.useState)([]),[c,l]=(0,h.useState)([]),[E,D]=(0,h.useState)(!1),[O,k]=(0,h.useState)(null),[A,j]=(0,h.useState)(()=>C()),[M,N]=(0,h.useState)(!1),[P,F]=(0,h.useState)(``),[I,ge]=(0,h.useState)(!1),[L,_e]=(0,h.useState)(),[R,ve]=(0,h.useState)([]),[ye,be]=(0,h.useState)(`off`),[z,B]=(0,h.useState)(``),[V,H]=(0,h.useState)(null),[U,W]=(0,h.useState)(null);async function xe(){let[e,t]=await Promise.all([T(`/api/agent-profiles`),T(`/api/agent-profiles/available-tools`)]);a(e.agents),l(t.tools)}(0,h.useEffect)(()=>{let e=!1;async function t(){try{let[t,n]=await Promise.all([T(`/api/agent-profiles`),T(`/api/agent-profiles/available-tools`)]);if(e)return;a(t.agents),l(n.tools)}catch(t){e||B(t instanceof Error?t.message:f(`requestFailed`))}}return t(),()=>{e=!0}},[]),(0,h.useEffect)(()=>{let e=!1;async function t(){try{let t=await re(),[n,r]=await Promise.all([ie(t),te().catch(()=>[])]),i=r.length?r:n;ve(i);let a=i.filter(m),o=await ae(t),s=(o.model&&m(o.model)?a.find(e=>le(e,o.model)):void 0)??a[0];if(e)return;_e(s),be(o.thinkingLevel??ne(s))}catch{}}return t(),()=>{e=!0}},[]),(0,h.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,h.useMemo)(()=>e.find(e=>e.id===O)??null,[e,O]),K=(0,h.useMemo)(()=>e.find(e=>e.id===V)??null,[e,V]),q=!!G?.readonly,J=!!(G?.readonly&&!G?.builtin),Y=(0,h.useMemo)(()=>{let e=x(A.fixedModelValue);if(e.mode!==`fixed`)return;let t=S(e);return R.find(e=>b(e)===t)??(`provider`in e?R.find(t=>ue(t,e)):void 0)},[A.fixedModelValue,R]),Se=(0,h.useMemo)(()=>R.filter(m),[R]),Ce=(0,h.useMemo)(()=>de(Se,Y),[Se,Y]),we=Y?b(Y):A.fixedModelValue,X=A.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(y,Math.min(n.right-_,window.innerWidth-_-y)),i=n.bottom+pe,a=n.top-pe-v;W({left:r,top:i+v<=window.innerHeight-y?i:Math.max(y,a)}),H(t)}function Z(e,t){j(n=>({...n,[e]:t}))}function Ee(e){ce(Y??null,Ce,e=>Z(`fixedModelValue`,e?b(e):``),{anchor:e.currentTarget,noneLabel:f(`agentModelInherit`)})}function De(e){j(t=>({...t,allowedTools:t.allowedTools.includes(e)?t.allowedTools.filter(t=>t!==e):[...t.allowedTools,e]}))}function Oe(){k(null),j(C()),F(``),B(``),D(!0)}function Q(e){k(e.id),j(me(e)),F(``),B(``),D(!0)}function $(){M||I||(D(!1),k(null),j(C()),F(``))}async function ke(){let e=P.trim();if(!e){B(f(`aiFillAgentInputRequired`));return}if(!L){B(f(`aiFillAgentNoModel`));return}ge(!0),B(``);try{let t=await T(`/api/agent-profiles/ai-fill`,{method:`POST`,body:JSON.stringify({instruction:e,modelRef:u(L),model:L,thinkingLevel:ye})});j(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:f(`aiFillAgentFailed`))}finally{ge(!1)}}async function Ae(){if(w(A)){N(!0),B(``);try{let e=G?.builtin?{model:A.modelMode===`fixed`?x(A.fixedModelValue):{mode:`inherit`}}:he({...A,thinkingLevel:X?`off`:A.thinkingLevel});O?await T(`/api/agent-profiles/${encodeURIComponent(O)}`,{method:`PATCH`,body:JSON.stringify(e)}):await T(`/api/agent-profiles`,{method:`POST`,body:JSON.stringify(e)}),$(),await xe()}catch(e){B(e instanceof Error?e.message:f(`requestFailed`))}finally{N(!1)}}}async function je(e){if(e.builtin||e.readonly)return;let t=!e.enabledAsSubagent,n=e.enabledAsSubagent;a(n=>n.map(n=>n.id===e.id?{...n,enabledAsSubagent:t}:n)),H(null);try{await T(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`PATCH`,body:JSON.stringify({enabledAsSubagent:t})})}catch(t){a(t=>t.map(t=>t.id===e.id?{...t,enabledAsSubagent:n}:t)),B(t instanceof Error?t.message:f(`requestFailed`))}}async function Me(e){if(!(e.builtin||e.readonly)&&await se({description:f(`confirmDeleteAgent`),confirmLabel:f(`confirmDelete`),cancelLabel:f(`cancel`),variant:`destructive`})){B(``);try{await T(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await xe()}catch(e){B(e instanceof Error?e.message:f(`requestFailed`))}}}return E?(0,g.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,g.jsx)(`div`,{className:`quickforge-settings-heading`,children:(0,g.jsxs)(`h3`,{className:`quickforge-settings-title`,children:[G?.builtin?f(`builtinAgentModelSettings`):f(G?`editAgent`:`createAgent`),(0,g.jsx)(oe,{label:G?.builtin?f(`builtinAgentModelOnly`):G?.readonly?f(`readonlyAgentDescription`):f(`agentsDescription`)})]})}),(0,g.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":f(G?`editAgent`:`createAgent`),children:[(0,g.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,g.jsxs)(`button`,{className:`quickforge-settings-button quickforge-settings-button-secondary`,type:`button`,onClick:$,disabled:M||I,children:[(0,g.jsx)(i,{className:`mr-2 size-4`}),f(`back`)]}),(0,g.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,g.jsx)(`div`,{className:`quickforge-settings-row-title`,children:G?.builtin?f(`builtinAgentModelSettings`):f(G?`editAgent`:`createAgent`)}),G?.builtin?(0,g.jsx)(`div`,{className:`quickforge-settings-row-description`,children:f(`builtinAgentModelOnly`)}):G?.readonly?(0,g.jsx)(`div`,{className:`quickforge-settings-row-description`,children:f(`readonlyAgentDescription`)}):null]})]}),(0,g.jsx)(`div`,{className:`px-5 py-4`,children:(0,g.jsxs)(`div`,{className:`space-y-4`,children:[(0,g.jsxs)(`div`,{className:`rounded-2xl border border-border bg-muted/20 p-3`,children:[(0,g.jsxs)(`div`,{className:`mb-2 flex items-center gap-2 text-sm font-medium text-foreground`,children:[(0,g.jsx)(ee,{className:`size-4 text-primary`}),f(`aiFillAgent`),(0,g.jsx)(oe,{label:f(`aiFillAgentDescription`)})]}),(0,g.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:P,disabled:q||I,onChange:e=>F(e.target.value),placeholder:f(`aiFillAgentPlaceholder`)}),(0,g.jsx)(`div`,{className:`mt-2 flex justify-end`,children:(0,g.jsxs)(p,{variant:`outline`,size:`sm`,onClick:()=>void ke(),disabled:q||I||!P.trim(),children:[(0,g.jsx)(ee,{className:`mr-1 size-3.5`}),f(I?`aiFillAgentLoading`:`aiFillAgent`)]})})]}),(0,g.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`agentName`),(0,g.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:A.name,disabled:q,onChange:e=>Z(`name`,e.target.value),placeholder:`reviewer`})]}),(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`agentLabel`),(0,g.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:A.label,disabled:q,onChange:e=>Z(`label`,e.target.value),placeholder:f(`agentLabelPlaceholder`)})]})]}),G?(0,g.jsxs)(`div`,{className:`rounded-xl border border-border bg-muted/20 px-3 py-2 text-sm`,children:[(0,g.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:f(`agentSourcePath`)}),(0,g.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?f(`builtinAgent`):`-`})]}):null,(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`agentDescription`),(0,g.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:A.description,disabled:q,onChange:e=>Z(`description`,e.target.value)})]}),(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`agentSystemPrompt`),(0,g.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:A.systemPrompt,disabled:q,onChange:e=>Z(`systemPrompt`,e.target.value)})]}),(0,g.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`agentModelMode`),(0,g.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:A.modelMode,disabled:J,onChange:e=>Z(`modelMode`,e.target.value),children:[(0,g.jsx)(`option`,{value:`inherit`,children:f(`agentModelInherit`)}),(0,g.jsx)(`option`,{value:`fixed`,children:f(`agentModelFixed`)})]})]}),(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`agentFixedModel`),(0,g.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||A.modelMode!==`fixed`,onChange:e=>Z(`fixedModelValue`,e.target.value),children:[(0,g.jsx)(`option`,{value:``,children:f(`agentModelInherit`)}),Ce.map(e=>(0,g.jsx)(`option`,{value:b(e),children:d(e)},b(e)))]}),(0,g.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||A.modelMode!==`fixed`,onClick:Ee,children:[(0,g.jsx)(`span`,{className:`truncate`,children:Y?d(Y):f(`agentModelInherit`)}),(0,g.jsx)(n,{className:`ml-2 size-4 shrink-0 text-muted-foreground`})]})]})]}),(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`agentThinkingLevel`),(0,g.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&&A.thinkingLevel!==`inherit`?`off`:A.thinkingLevel,disabled:q||X,onChange:e=>Z(`thinkingLevel`,e.target.value),children:[(0,g.jsx)(`option`,{value:`inherit`,children:f(`agentThinkingInherit`)}),(0,g.jsx)(`option`,{value:`off`,children:f(`thinkingOff`)}),(0,g.jsx)(`option`,{value:`low`,children:f(`thinkingLow`)}),(0,g.jsx)(`option`,{value:`medium`,children:f(`thinkingMedium`)}),(0,g.jsx)(`option`,{value:`high`,children:f(`thinkingHigh`)}),(0,g.jsx)(`option`,{value:`xhigh`,children:f(`thinkingXHigh`)})]}),(0,g.jsx)(`span`,{className:`mt-1 block text-xs text-muted-foreground`,children:f(X?`agentThinkingUnsupported`:`agentThinkingDescription`)})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`div`,{className:`mb-2 text-sm font-medium text-foreground`,children:f(`allowedTools`)}),(0,g.jsx)(`div`,{className:`grid gap-2 sm:grid-cols-2`,children:c.map(e=>(0,g.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,g.jsx)(`input`,{type:`checkbox`,className:`mt-1`,disabled:q,checked:A.allowedTools.includes(e.name),onChange:()=>De(e.name)}),(0,g.jsxs)(`span`,{children:[(0,g.jsx)(`span`,{className:`font-medium text-foreground`,children:e.label}),(0,g.jsx)(`span`,{className:`ml-2 font-mono text-xs text-muted-foreground`,children:e.name}),e.riskLevel===`dangerous`?(0,g.jsx)(`span`,{className:`ml-2 rounded-full bg-amber-500/10 px-2 py-0.5 text-xs text-amber-700`,children:f(`highRiskTool`)}):null,(0,g.jsx)(`span`,{className:`mt-1 block text-xs text-muted-foreground`,children:e.description})]})]},e.name))})]}),(0,g.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`maxRuntimeMs`),(0,g.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:A.maxRuntimeMs,disabled:q,onChange:e=>Z(`maxRuntimeMs`,e.target.value)})]}),(0,g.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[f(`maxToolCalls`),(0,g.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:A.maxToolCalls,disabled:q,onChange:e=>Z(`maxToolCalls`,e.target.value)})]})]}),(0,g.jsxs)(`label`,{className:`flex items-center gap-2 text-sm text-foreground`,children:[(0,g.jsx)(`input`,{type:`checkbox`,checked:A.enabledAsSubagent,disabled:q,onChange:e=>Z(`enabledAsSubagent`,e.target.checked)}),f(`enabledAsSubagent`)]}),z?(0,g.jsx)(`div`,{className:`rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:z}):null]})}),(0,g.jsxs)(`div`,{className:`quickforge-settings-divider flex justify-end gap-2 px-5 py-4`,children:[(0,g.jsx)(p,{variant:`outline`,onClick:$,disabled:M||I,children:f(`cancel`)}),(0,g.jsx)(p,{onClick:Ae,disabled:M||I||J||!G?.builtin&&!w(A)||A.modelMode===`fixed`&&!A.fixedModelValue,children:f(`save`)})]})]})]}):(0,g.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,g.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":f(`agentsTab`),children:[(0,g.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,g.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,g.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[(0,g.jsx)(r,{className:`size-4 text-primary`}),f(`agentsTab`)]}),(0,g.jsx)(`div`,{className:`quickforge-settings-row-description`,children:f(`agentsDescription`)})]}),(0,g.jsx)(`button`,{className:`quickforge-settings-button quickforge-settings-button-primary`,type:`button`,onClick:Oe,children:f(`createAgent`)})]}),z?(0,g.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:z}):null,e.length===0?(0,g.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:f(`loading`)}):e.map(e=>(0,g.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,g.jsx)(`div`,{className:`quickforge-settings-list-item-main quickforge-agent-profile-row-main`,children:(0,g.jsxs)(`div`,{className:`quickforge-agent-profile-summary`,children:[(0,g.jsx)(`span`,{className:`quickforge-agent-profile-label`,title:e.label,children:e.label}),e.description?(0,g.jsx)(`span`,{className:`quickforge-agent-profile-description`,title:e.description,children:e.description}):null]})}),(0,g.jsxs)(`div`,{className:`quickforge-settings-list-item-actions`,onClick:e=>e.stopPropagation(),children:[(0,g.jsxs)(`label`,{className:`quickforge-settings-switch`,"aria-disabled":e.builtin||e.readonly?`true`:`false`,title:e.enabledAsSubagent?f(`disableAsSubagent`):f(`enableAsSubagent`),children:[(0,g.jsx)(`input`,{type:`checkbox`,checked:e.enabledAsSubagent,disabled:e.builtin||e.readonly,onChange:()=>void je(e)}),(0,g.jsx)(`span`,{"aria-hidden":`true`})]}),(0,g.jsx)(`button`,{className:`quickforge-settings-icon-action`,type:`button`,onClick:t=>Te(t,e.id),title:f(`moreActions`),"aria-label":f(`moreActions`),"aria-haspopup":`menu`,"aria-expanded":V===e.id,children:(0,g.jsx)(o,{className:`size-4`})})]})]},e.id))]}),K&&U?(0,fe.createPortal)((0,g.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":f(`moreActions`),onClick:e=>e.stopPropagation(),children:[(0,g.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,g.jsx)(t,{className:`size-3.5`}),K.builtin?f(`builtinAgentModelSettings`):f(`editTask`)]}),(0,g.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,g.jsx)(s,{className:`size-3.5`}),f(`delete`)]})]}),document.body):null]})}export{E as AgentProfilesPage};
|