agent-relay-server 0.60.0 → 0.61.0
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/docs/openapi.json +516 -1
- package/package.json +3 -2
- package/public/assets/MessageBubble-CX63ZV4X.js +2 -0
- package/public/assets/MessageBubble-CX63ZV4X.js.map +1 -0
- package/public/assets/{PlanGraphPanel-CXix1C-H.js → PlanGraphPanel-Dby7wq9c.js} +2 -2
- package/public/assets/{PlanGraphPanel-CXix1C-H.js.map → PlanGraphPanel-Dby7wq9c.js.map} +1 -1
- package/public/assets/{activity-CRafQ1Je.js → activity-2xPOScu_.js} +2 -2
- package/public/assets/{activity-CRafQ1Je.js.map → activity-2xPOScu_.js.map} +1 -1
- package/public/assets/{agent-profiles-DW4o5-ND.js → agent-profiles-BfGTTbeY.js} +2 -2
- package/public/assets/{agent-profiles-DW4o5-ND.js.map → agent-profiles-BfGTTbeY.js.map} +1 -1
- package/public/assets/agents-Bg3WOBRV.js +2 -0
- package/public/assets/{agents-Cbegv0Hm.js.map → agents-Bg3WOBRV.js.map} +1 -1
- package/public/assets/{analytics-DX6T7JX3.js → analytics-De0cEZGx.js} +3 -3
- package/public/assets/{analytics-DX6T7JX3.js.map → analytics-De0cEZGx.js.map} +1 -1
- package/public/assets/{automation-CzSBv4gg.js → automation-CHxhzq2t.js} +2 -2
- package/public/assets/{automation-CzSBv4gg.js.map → automation-CHxhzq2t.js.map} +1 -1
- package/public/assets/{badge-Cy6qO_Jz.js → badge-DhLyCPag.js} +2 -2
- package/public/assets/{badge-Cy6qO_Jz.js.map → badge-DhLyCPag.js.map} +1 -1
- package/public/assets/branch-state-badge-DSyWKluD.js +2 -0
- package/public/assets/{branch-state-badge-CRA50r81.js.map → branch-state-badge-DSyWKluD.js.map} +1 -1
- package/public/assets/{button-73wpRw65.js → button-K20wAZG1.js} +2 -2
- package/public/assets/{button-73wpRw65.js.map → button-K20wAZG1.js.map} +1 -1
- package/public/assets/card-CcZ9hLI7.js +2 -0
- package/public/assets/{card-BU6PvVHv.js.map → card-CcZ9hLI7.js.map} +1 -1
- package/public/assets/{channels-B1MQCPCT.js → channels-BG0Nib7y.js} +2 -2
- package/public/assets/{channels-B1MQCPCT.js.map → channels-BG0Nib7y.js.map} +1 -1
- package/public/assets/chat-CjP_crSl.js +2 -0
- package/public/assets/{chat-BqIaFYGJ.js.map → chat-CjP_crSl.js.map} +1 -1
- package/public/assets/{connectors-C9aFJIrh.js → connectors-DWh7MwV_.js} +2 -2
- package/public/assets/{connectors-C9aFJIrh.js.map → connectors-DWh7MwV_.js.map} +1 -1
- package/public/assets/{copy-button-HRopUwui.js → copy-button-eF3cKp4Q.js} +2 -2
- package/public/assets/{copy-button-HRopUwui.js.map → copy-button-eF3cKp4Q.js.map} +1 -1
- package/public/assets/display-DzNvOu1j.js +3 -0
- package/public/assets/{display-CketawEF.js.map → display-DzNvOu1j.js.map} +1 -1
- package/public/assets/{dist-Kkpnjx_M.js → dist-1dhfdhm9.js} +2 -2
- package/public/assets/{dist-Kkpnjx_M.js.map → dist-1dhfdhm9.js.map} +1 -1
- package/public/assets/dist-BLBF7qqr.js +2 -0
- package/public/assets/{dist-De2oguBm.js.map → dist-BLBF7qqr.js.map} +1 -1
- package/public/assets/{dist-T7u7IZGJ.js → dist-BrQsqBbc.js} +2 -2
- package/public/assets/{dist-T7u7IZGJ.js.map → dist-BrQsqBbc.js.map} +1 -1
- package/public/assets/{dist-Cko2KPW8.js → dist-COQuunEv.js} +2 -2
- package/public/assets/{dist-Cko2KPW8.js.map → dist-COQuunEv.js.map} +1 -1
- package/public/assets/{dist-CLHMeXYi.js → dist-Ci5Y37nj.js} +2 -2
- package/public/assets/{dist-CLHMeXYi.js.map → dist-Ci5Y37nj.js.map} +1 -1
- package/public/assets/{dist-Ci1MM51P.js → dist-CoROafNg.js} +2 -2
- package/public/assets/{dist-Ci1MM51P.js.map → dist-CoROafNg.js.map} +1 -1
- package/public/assets/{dist-CC5f7oYw.js → dist-I-MFUv51.js} +2 -2
- package/public/assets/{dist-CC5f7oYw.js.map → dist-I-MFUv51.js.map} +1 -1
- package/public/assets/{dist-DFLciw0h.js → dist-UQGKwqZH.js} +2 -2
- package/public/assets/{dist-DFLciw0h.js.map → dist-UQGKwqZH.js.map} +1 -1
- package/public/assets/dist-YpHfnnIA.js +2 -0
- package/public/assets/{dist-DUoZnlB2.js.map → dist-YpHfnnIA.js.map} +1 -1
- package/public/assets/dist-qd198ib9.js +2 -0
- package/public/assets/{dist-D41tzJYg.js.map → dist-qd198ib9.js.map} +1 -1
- package/public/assets/{dist-DmN94rc8.js → dist-yGOq6sB3.js} +2 -2
- package/public/assets/{dist-DmN94rc8.js.map → dist-yGOq6sB3.js.map} +1 -1
- package/public/assets/{es2015-Df08Opjx.js → es2015-CRxz9lOj.js} +2 -2
- package/public/assets/{es2015-Df08Opjx.js.map → es2015-CRxz9lOj.js.map} +1 -1
- package/public/assets/{formatted-body-impl-DOXH7oDF.js → formatted-body-impl-u0W_hWYA.js} +2 -2
- package/public/assets/{formatted-body-impl-DOXH7oDF.js.map → formatted-body-impl-u0W_hWYA.js.map} +1 -1
- package/public/assets/index-BwfVOzUG.js +21 -0
- package/public/assets/{index-br_Y6Cvt.js.map → index-BwfVOzUG.js.map} +1 -1
- package/public/assets/index-Dn_GDXbi.css +2 -0
- package/public/assets/{input-B2EDz-BE.js → input-BHYNpXAG.js} +2 -2
- package/public/assets/{input-B2EDz-BE.js.map → input-BHYNpXAG.js.map} +1 -1
- package/public/assets/{insights-B1edq5lk.js → insights-CAF9Hi5D.js} +2 -2
- package/public/assets/{insights-B1edq5lk.js.map → insights-CAF9Hi5D.js.map} +1 -1
- package/public/assets/{integrations-Cm4fjp7_.js → integrations-CG9qyHUf.js} +2 -2
- package/public/assets/{integrations-Cm4fjp7_.js.map → integrations-CG9qyHUf.js.map} +1 -1
- package/public/assets/{lib-BZrjIp-o.js → lib-Df4aph5u.js} +2 -2
- package/public/assets/{lib-BZrjIp-o.js.map → lib-Df4aph5u.js.map} +1 -1
- package/public/assets/lucide-react-L2UxFDic.js +9 -0
- package/public/assets/lucide-react-L2UxFDic.js.map +1 -0
- package/public/assets/{maintenance-D6zvw0fU.js → maintenance-DgHA35ve.js} +2 -2
- package/public/assets/{maintenance-D6zvw0fU.js.map → maintenance-DgHA35ve.js.map} +1 -1
- package/public/assets/{managed-agents-CzfEznM1.js → managed-agents-Rk6ghKR9.js} +2 -2
- package/public/assets/{managed-agents-CzfEznM1.js.map → managed-agents-Rk6ghKR9.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DZlCaTHQ.js → markdown-preview-impl-NWMtJdYz.js} +2 -2
- package/public/assets/{markdown-preview-impl-DZlCaTHQ.js.map → markdown-preview-impl-NWMtJdYz.js.map} +1 -1
- package/public/assets/memory-DamwWa9A.js +2 -0
- package/public/assets/{memory-CUUAzJDc.js.map → memory-DamwWa9A.js.map} +1 -1
- package/public/assets/{messages-6QZH46LG.js → messages--NU-YIL6.js} +2 -2
- package/public/assets/{messages-6QZH46LG.js.map → messages--NU-YIL6.js.map} +1 -1
- package/public/assets/{orchestrators-CrcqkPuc.js → orchestrators-DQnIn6qx.js} +2 -2
- package/public/assets/{orchestrators-CrcqkPuc.js.map → orchestrators-DQnIn6qx.js.map} +1 -1
- package/public/assets/{overview-418xXZO_.js → overview-DCmNgXDf.js} +2 -2
- package/public/assets/{overview-418xXZO_.js.map → overview-DCmNgXDf.js.map} +1 -1
- package/public/assets/{pairs-CnedATgT.js → pairs-DNELeC9P.js} +2 -2
- package/public/assets/{pairs-CnedATgT.js.map → pairs-DNELeC9P.js.map} +1 -1
- package/public/assets/{react-dom-WI6TjVe7.js → react-dom-CrP_AtcL.js} +2 -2
- package/public/assets/{react-dom-WI6TjVe7.js.map → react-dom-CrP_AtcL.js.map} +1 -1
- package/public/assets/{security-DS13pbag.js → security-Dd3Kh0oC.js} +2 -2
- package/public/assets/{security-DS13pbag.js.map → security-Dd3Kh0oC.js.map} +1 -1
- package/public/assets/{select-BOprGZDS.js → select-DQbeJZX0.js} +2 -2
- package/public/assets/{select-BOprGZDS.js.map → select-DQbeJZX0.js.map} +1 -1
- package/public/assets/settings-BMAJrBIH.js +4 -0
- package/public/assets/settings-BMAJrBIH.js.map +1 -0
- package/public/assets/store-B8xP6z6T.js +9 -0
- package/public/assets/store-B8xP6z6T.js.map +1 -0
- package/public/assets/tasks-BYR4L9GP.js +2 -0
- package/public/assets/{tasks-B-4oCPuv.js.map → tasks-BYR4L9GP.js.map} +1 -1
- package/public/assets/teams-CoTifrq_.js +2 -0
- package/public/assets/{teams-sCQAs_QA.js.map → teams-CoTifrq_.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BhqS2XpS.js → terminal-viewer-impl-B1YzfpE6.js} +3 -3
- package/public/assets/{terminal-viewer-impl-BhqS2XpS.js.map → terminal-viewer-impl-B1YzfpE6.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-fOL_3RHB.js → use-keyboard-viewport-DrRH_QzG.js} +2 -2
- package/public/assets/{use-keyboard-viewport-fOL_3RHB.js.map → use-keyboard-viewport-DrRH_QzG.js.map} +1 -1
- package/public/assets/{work-queue-BDhjlwUH.js → work-queue-x57vlgyZ.js} +2 -2
- package/public/assets/{work-queue-BDhjlwUH.js.map → work-queue-x57vlgyZ.js.map} +1 -1
- package/public/assets/{workspaces-6I-C69nf.js → workspaces-CYm5bLg9.js} +3 -3
- package/public/assets/{workspaces-6I-C69nf.js.map → workspaces-CYm5bLg9.js.map} +1 -1
- package/public/index.html +22 -22
- package/runner/src/config.ts +66 -2
- package/src/automations.ts +3 -4
- package/src/cli/index.ts +11 -0
- package/src/cli/merges.ts +89 -0
- package/src/config-store.ts +5 -6
- package/src/config.ts +4 -0
- package/src/connectors.ts +5 -0
- package/src/db/merge-lease.ts +95 -1
- package/src/db/migrations.ts +21 -0
- package/src/db/schema.ts +1 -1
- package/src/index.ts +5 -2
- package/src/maintenance.ts +8 -8
- package/src/mcp-merge-tools.ts +66 -0
- package/src/mcp-provider-catalog.ts +9 -0
- package/src/mcp.ts +7 -8
- package/src/merge-pause-maintenance.ts +13 -0
- package/src/provider-catalog-store.ts +33 -4
- package/src/provider-config-store.ts +216 -0
- package/src/recipe-runner.ts +2 -1
- package/src/recipe-validator.ts +4 -3
- package/src/routes/agent-sessions.ts +4 -2
- package/src/routes/agents-spawn.ts +2 -2
- package/src/routes/commands.ts +20 -0
- package/src/routes/index.ts +19 -2
- package/src/routes/insights.ts +4 -3
- package/src/routes/merges.ts +95 -0
- package/src/routes/provider-config.ts +89 -41
- package/src/routes/settings.ts +127 -0
- package/src/routes/steward.ts +5 -5
- package/src/routes/tokens.ts +32 -1
- package/src/security.ts +36 -37
- package/src/services/merge-pause.ts +37 -0
- package/src/services/spawn-agent.ts +10 -8
- package/src/settings/modules.ts +149 -0
- package/src/settings/registry.ts +397 -0
- package/src/spawn-command.ts +5 -1
- package/src/team-template-config.ts +2 -2
- package/src/token-db.ts +20 -0
- package/src/workspace-pr-completion.ts +70 -0
- package/public/assets/MessageBubble-BrEKymoV.js +0 -2
- package/public/assets/MessageBubble-BrEKymoV.js.map +0 -1
- package/public/assets/agents-Cbegv0Hm.js +0 -2
- package/public/assets/branch-state-badge-CRA50r81.js +0 -2
- package/public/assets/card-BU6PvVHv.js +0 -2
- package/public/assets/chat-BqIaFYGJ.js +0 -2
- package/public/assets/display-CketawEF.js +0 -3
- package/public/assets/dist-D41tzJYg.js +0 -2
- package/public/assets/dist-DUoZnlB2.js +0 -2
- package/public/assets/dist-De2oguBm.js +0 -2
- package/public/assets/index-DaNOQJci.css +0 -2
- package/public/assets/index-br_Y6Cvt.js +0 -21
- package/public/assets/lucide-react-H_-8G79A.js +0 -9
- package/public/assets/lucide-react-H_-8G79A.js.map +0 -1
- package/public/assets/memory-CUUAzJDc.js +0 -2
- package/public/assets/settings-BgInn_f3.js +0 -10
- package/public/assets/settings-BgInn_f3.js.map +0 -1
- package/public/assets/store-DHO_a8BD.js +0 -9
- package/public/assets/store-DHO_a8BD.js.map +0 -1
- package/public/assets/tasks-B-4oCPuv.js +0 -2
- package/public/assets/teams-sCQAs_QA.js +0 -2
package/src/routes/index.ts
CHANGED
|
@@ -4,14 +4,14 @@ import { deleteAgentProfileRoute, getAgentProfileRoute, getAgentProfilesRoute, p
|
|
|
4
4
|
import { deleteAgentTerminalSession, postAgentAction, postAgentPermissionDecision, postAgentPrompt, postAgentTerminalSession } from "./agent-sessions";
|
|
5
5
|
import { deleteArtifactById, getArtifactById, getArtifactContent, getArtifacts, headArtifactContent, postArtifact } from "./artifacts";
|
|
6
6
|
import { deleteAutomationById, getAutomationById, getAutomationRuns, getAutomations, patchAutomation, postAutomation, postAutomationRun } from "./automations";
|
|
7
|
-
import { deleteCommandById, getProviderConfigRoute, getProviderConfigsRoute, getProvidersRoute, postProviderConfigTestRoute, putProviderConfigRoute } from "./provider-config";
|
|
7
|
+
import { deleteCommandById, deleteProviderModelRoute, getProviderConfigRoute, getProviderConfigsRoute, getProvidersRoute, patchProviderModelRoute, postProviderConfigTestRoute, putProviderConfigRoute, putProviderModelRoute } from "./provider-config";
|
|
8
8
|
import { deleteConfigKey, getConfigKey, getConfigKeyHistory, getConfigNamespace, putConfigKey } from "./config";
|
|
9
9
|
import { deleteInboxDraftRoute, getChatHistoryImportsRoute, getInboxStateRoute, patchInboxThreadState, postChatHistoryImportRoute, putInboxDraft } from "./inbox";
|
|
10
10
|
import { deleteMemoryRoute, getMemories, getMemoryBrokerInfo, getMemoryById, getMemoryStats, patchMemory, postMemory, postMemoryInject, postMemorySearch } from "./memory";
|
|
11
11
|
import { deleteMessageById, getCursorRoute, getMessageById, getMessageDeliveryRoute, getMessageStatusRoute, getMessageThread, getMessages, getQueuedMessagesRoute, patchMessage, postClaimMessage, postMessage, postMessageDeliveryAction, postMessageDeliveryAttempt, postMessageReaction, postRenewMessageClaim, postSystemBroadcast } from "./messages";
|
|
12
12
|
import { deleteOrchestratorById, getOrchestratorById, getOrchestrators, patchOrchestratorAgents, postOrchestrator, postOrchestratorAction, postOrchestratorHeartbeat } from "./orchestrator";
|
|
13
13
|
import { deleteSpawnPolicyRoute, getSpawnPoliciesHealth, getSpawnPoliciesRoute, getSpawnPolicyHealth, getSpawnPolicyRoute, getSpawnPolicyStatus, postSpawnPolicyRestart, postSpawnPolicyStart, postSpawnPolicyStop, putSpawnPolicyRoute } from "./spawn-policy";
|
|
14
|
-
import { deleteTokenProfileRoute, getTokenById, getTokenProfiles, getTokens, patchTokenProfile, postIntegrationRuntimeToken, postInteractiveRunnerRuntimeToken, postMcpRuntimeToken, postOrchestratorRunnerToken, postRuntimeTokenRenew, postToken, postTokenProfile, postTokenRevoke } from "./tokens";
|
|
14
|
+
import { deleteTokenProfileRoute, getTokenById, getTokenMe, getTokenProfiles, getTokens, patchTokenProfile, postIntegrationRuntimeToken, postInteractiveRunnerRuntimeToken, postMcpRuntimeToken, postOrchestratorRunnerToken, postRuntimeTokenRenew, postToken, postTokenProfile, postTokenRevoke } from "./tokens";
|
|
15
15
|
import { deleteWorkspaceById, getWorkspaceById, getWorkspaceDiagnostics, getWorkspaceDiff, getWorkspaceGitState, getWorkspaceMergePreview, getWorkspaceOrphans, getWorkspaceStewards, getWorkspaces, postWorkspaceAction, postWorkspaceCleanupStale, postWorkspaceOrphanReclaim } from "./workspaces";
|
|
16
16
|
import { error, type Handler } from "./_shared";
|
|
17
17
|
import { getActivityEvents, postActivityEvent } from "./activity";
|
|
@@ -30,11 +30,13 @@ import { getPlanByIdRoute, getPlansRoute } from "./plans";
|
|
|
30
30
|
import { getBlackboardEntryRoute, getBlackboardRoute } from "./blackboard";
|
|
31
31
|
import { getProjectEntriesRoute, getProjectEntryRoute, getProjectRoute, getProjectsRoute } from "./projects";
|
|
32
32
|
import { getRecipeByName, getRecipeInstanceArtifacts, getRecipeInstanceById, getRecipeInstances, getRecipes, postRecipeInstanceArtifacts, postRecipeStart, postRecipeStop } from "./recipes";
|
|
33
|
+
import { getSettingRoute, getSettingsNamespaceRoute, getSettingsRoute, putSettingRoute, putSettingsDefaultRoute } from "./settings";
|
|
33
34
|
import { getStewardConfigRoute, getWorkspaceConfigRoute, putStewardConfigRoute, putWorkspaceConfigRoute } from "./steward";
|
|
34
35
|
import { getTaskById, getTaskEvents, getTasks, patchTaskStatus, postClaimTask, postRenewTaskClaim } from "./tasks";
|
|
35
36
|
import { getMyTeamRoute, getTeamOutcomesRoute, getTeamsRoute, postMyTeamDissolveRoute, putMyTeamBriefRoute } from "./teams";
|
|
36
37
|
import { postTeamDissolveByIdRoute } from "./teams";
|
|
37
38
|
import { deleteTeamTemplateRoute, getTeamTemplateRoute, getTeamTemplatesRoute, postTeamTemplateRoute, putTeamTemplateRoute } from "./team-templates";
|
|
39
|
+
import { getMergePauseById, getMergePauses, postMergePause, postMergePauseRenew, postMergePauseResume } from "./merges";
|
|
38
40
|
import { postMcp } from "../mcp";
|
|
39
41
|
import { postOrchestratorBootstrap, postOrchestratorBootstrapExchange } from "./orchestrator-bootstrap";
|
|
40
42
|
|
|
@@ -139,6 +141,12 @@ const routes: Route[] = [
|
|
|
139
141
|
route("POST", "/api/workspaces/:id/actions", postWorkspaceAction),
|
|
140
142
|
route("DELETE", "/api/workspaces/:id", deleteWorkspaceById),
|
|
141
143
|
|
|
144
|
+
route("GET", "/api/merges/pauses", getMergePauses),
|
|
145
|
+
route("POST", "/api/merges/pause", postMergePause),
|
|
146
|
+
route("POST", "/api/merges/resume", postMergePauseResume),
|
|
147
|
+
route("POST", "/api/merges/renew", postMergePauseRenew),
|
|
148
|
+
route("GET", "/api/merges/pauses/:id", getMergePauseById),
|
|
149
|
+
|
|
142
150
|
route("POST", "/api/system/broadcast", postSystemBroadcast),
|
|
143
151
|
route("GET", "/api/teams", getTeamsRoute),
|
|
144
152
|
route("GET", "/api/teams/outcomes", getTeamOutcomesRoute),
|
|
@@ -161,6 +169,7 @@ const routes: Route[] = [
|
|
|
161
169
|
route("GET", "/api/recipes/:name", getRecipeByName),
|
|
162
170
|
route("GET", "/api/tokens", getTokens),
|
|
163
171
|
route("POST", "/api/tokens", postToken),
|
|
172
|
+
route("GET", "/api/tokens/me", getTokenMe),
|
|
164
173
|
route("POST", "/api/runtime-tokens/renew", postRuntimeTokenRenew),
|
|
165
174
|
route("POST", "/api/runtime-tokens/interactive-runner", postInteractiveRunnerRuntimeToken),
|
|
166
175
|
route("POST", "/api/runtime-tokens/mcp-client", postMcpRuntimeToken),
|
|
@@ -189,6 +198,11 @@ const routes: Route[] = [
|
|
|
189
198
|
route("GET", "/api/insights/observations", getInsightsObservationsRoute),
|
|
190
199
|
route("POST", "/api/insights/observations", postInsightsObservationRoute),
|
|
191
200
|
route("POST", "/api/continuation-archives", postContinuationArchiveRoute),
|
|
201
|
+
route("GET", "/api/settings", getSettingsRoute),
|
|
202
|
+
route("GET", "/api/settings/:namespace", getSettingsNamespaceRoute),
|
|
203
|
+
route("GET", "/api/settings/:namespace/:key", getSettingRoute),
|
|
204
|
+
route("PUT", "/api/settings/:namespace", putSettingsDefaultRoute),
|
|
205
|
+
route("PUT", "/api/settings/:namespace/:key", putSettingRoute),
|
|
192
206
|
route("GET", "/api/config/:namespace", getConfigNamespace),
|
|
193
207
|
route("GET", "/api/config/:namespace/:key/history", getConfigKeyHistory),
|
|
194
208
|
route("GET", "/api/config/:namespace/:key", getConfigKey),
|
|
@@ -206,6 +220,9 @@ const routes: Route[] = [
|
|
|
206
220
|
route("GET", "/api/spawn-policy/:name/health", getSpawnPolicyHealth),
|
|
207
221
|
route("GET", "/api/providers", getProvidersRoute),
|
|
208
222
|
route("GET", "/api/providers/config", getProviderConfigsRoute),
|
|
223
|
+
route("PUT", "/api/providers/:provider/models/:alias", putProviderModelRoute),
|
|
224
|
+
route("PATCH", "/api/providers/:provider/models/:alias", patchProviderModelRoute),
|
|
225
|
+
route("DELETE", "/api/providers/:provider/models/:alias", deleteProviderModelRoute),
|
|
209
226
|
route("GET", "/api/providers/:provider/config", getProviderConfigRoute),
|
|
210
227
|
route("PUT", "/api/providers/:provider/config", putProviderConfigRoute),
|
|
211
228
|
route("POST", "/api/providers/:provider/config/test", postProviderConfigTestRoute),
|
package/src/routes/insights.ts
CHANGED
|
@@ -3,11 +3,12 @@ import { ValidationError } from "../db";
|
|
|
3
3
|
import { cleanString } from "../validation";
|
|
4
4
|
import { emitConfigChanged } from "../sse";
|
|
5
5
|
import { error, json, parseBody, type Handler } from "./_shared";
|
|
6
|
-
import { getInsightsConfigEntry
|
|
6
|
+
import { getInsightsConfigEntry } from "../config-store";
|
|
7
7
|
import { getObservationStats, listObservationProjects, listObservations, recordObservation } from "../insights-db";
|
|
8
|
+
import { getRegisteredSettingEntry, setRegisteredSettingValue } from "./settings";
|
|
8
9
|
import { isRecord } from "agent-relay-sdk";
|
|
9
10
|
|
|
10
|
-
export const getInsightsConfigRoute: Handler = () => json(
|
|
11
|
+
export const getInsightsConfigRoute: Handler = () => json(getRegisteredSettingEntry("insights", "default"));
|
|
11
12
|
|
|
12
13
|
export const putInsightsConfigRoute: Handler = async (req) => {
|
|
13
14
|
const parsed = await parseBody<unknown>(req);
|
|
@@ -17,7 +18,7 @@ export const putInsightsConfigRoute: Handler = async (req) => {
|
|
|
17
18
|
? parsed.body.value
|
|
18
19
|
: parsed.body;
|
|
19
20
|
const updatedBy = isRecord(parsed.body) ? cleanString(parsed.body.updatedBy, "updatedBy", { max: 200 }) : undefined;
|
|
20
|
-
const entry =
|
|
21
|
+
const entry = setRegisteredSettingValue("insights", "default", value, updatedBy);
|
|
21
22
|
emitConfigChanged(entry.namespace, entry.key, entry.version);
|
|
22
23
|
return json(entry, entry.version === 1 ? 201 : 200);
|
|
23
24
|
} catch (e) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Auto-merge coordination routes. Generic pause/resume primitive for callers
|
|
2
|
+
// that need Relay's auto-merge sweep to defer a target briefly (#448).
|
|
3
|
+
import { isRecord } from "agent-relay-sdk";
|
|
4
|
+
import { ValidationError } from "../db";
|
|
5
|
+
import { getAutoMergePause, listAutoMergePauses, pauseAutoMerge, renewAutoMergePause, resumeAutoMerge } from "../services/merge-pause";
|
|
6
|
+
import { cleanString } from "../validation";
|
|
7
|
+
import { authAuditMetadata, authorizeRoute, error, json, parseBody, type Handler } from "./_shared";
|
|
8
|
+
|
|
9
|
+
function cleanTtlMs(value: unknown): number | undefined {
|
|
10
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
11
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
12
|
+
throw new ValidationError("ttlMs must be a positive integer");
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function actorFromRequest(req: Request): string {
|
|
18
|
+
const audit = authAuditMetadata(req);
|
|
19
|
+
const auth = isRecord(audit.auth) ? audit.auth : undefined;
|
|
20
|
+
return typeof auth?.sub === "string" ? auth.sub
|
|
21
|
+
: typeof auth?.name === "string" ? auth.name
|
|
22
|
+
: "root";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const getMergePauses: Handler = (req) => {
|
|
26
|
+
const denied = authorizeRoute(req, { scope: "command:read" });
|
|
27
|
+
if (denied) return denied;
|
|
28
|
+
return json({ pauses: listAutoMergePauses() });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const postMergePause: Handler = async (req) => {
|
|
32
|
+
const denied = authorizeRoute(req, { scope: "command:write" });
|
|
33
|
+
if (denied) return denied;
|
|
34
|
+
const parsed = await parseBody<unknown>(req);
|
|
35
|
+
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
36
|
+
try {
|
|
37
|
+
if (!isRecord(parsed.body)) return error("body required");
|
|
38
|
+
const repoRoot = cleanString(parsed.body.repoRoot, "repoRoot", { required: true, max: 1000 })!;
|
|
39
|
+
const baseRef = cleanString(parsed.body.baseRef, "baseRef", { max: 240 }) ?? "main";
|
|
40
|
+
const holder = cleanString(parsed.body.holder, "holder", { max: 240 }) ?? actorFromRequest(req);
|
|
41
|
+
const reason = cleanString(parsed.body.reason, "reason", { max: 500 });
|
|
42
|
+
const ttlMs = cleanTtlMs(parsed.body.ttlMs);
|
|
43
|
+
const result = pauseAutoMerge({ repoRoot, baseRef, holder, reason, ttlMs });
|
|
44
|
+
if (!result.ok) {
|
|
45
|
+
return json({ error: "merge target already paused", pause: result.pause }, 409);
|
|
46
|
+
}
|
|
47
|
+
return json({ pause: result.pause }, 201);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (e instanceof ValidationError) return error(e.message, 400);
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const postMergePauseRenew: Handler = async (req) => {
|
|
55
|
+
const denied = authorizeRoute(req, { scope: "command:write" });
|
|
56
|
+
if (denied) return denied;
|
|
57
|
+
const parsed = await parseBody<unknown>(req);
|
|
58
|
+
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
59
|
+
try {
|
|
60
|
+
if (!isRecord(parsed.body)) return error("body required");
|
|
61
|
+
const id = cleanString(parsed.body.id, "id", { required: true, max: 120 })!;
|
|
62
|
+
const ttlMs = cleanTtlMs(parsed.body.ttlMs);
|
|
63
|
+
const pause = renewAutoMergePause(id, ttlMs);
|
|
64
|
+
return pause ? json({ pause }) : error("merge pause not found or expired", 404);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
if (e instanceof ValidationError) return error(e.message, 400);
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const postMergePauseResume: Handler = async (req) => {
|
|
72
|
+
const denied = authorizeRoute(req, { scope: "command:write" });
|
|
73
|
+
if (denied) return denied;
|
|
74
|
+
const parsed = await parseBody<unknown>(req);
|
|
75
|
+
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
76
|
+
try {
|
|
77
|
+
if (!isRecord(parsed.body)) return error("body required");
|
|
78
|
+
const id = cleanString(parsed.body.id, "id", { max: 120 });
|
|
79
|
+
const repoRoot = cleanString(parsed.body.repoRoot, "repoRoot", { max: 1000 });
|
|
80
|
+
const baseRef = cleanString(parsed.body.baseRef, "baseRef", { max: 240 });
|
|
81
|
+
if (!id && !repoRoot) return error("id or repoRoot required", 400);
|
|
82
|
+
const released = resumeAutoMerge(id ? { id } : { repoRoot: repoRoot!, baseRef: baseRef ?? "main" });
|
|
83
|
+
return json({ released });
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (e instanceof ValidationError) return error(e.message, 400);
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const getMergePauseById: Handler = (req, params) => {
|
|
91
|
+
const denied = authorizeRoute(req, { scope: "command:read" });
|
|
92
|
+
if (denied) return denied;
|
|
93
|
+
const pause = getAutoMergePause(params.id!);
|
|
94
|
+
return pause ? json(pause) : error("merge pause not found", 404);
|
|
95
|
+
};
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
import { ValidationError, listOrchestrators } from "../db";
|
|
3
3
|
import { applyCommandToRecipe } from "../recipe-runner";
|
|
4
4
|
import { cleanString, cleanStringArray, optionalEnum } from "../validation";
|
|
5
|
-
import { defaultProviderConfig, loadProviderConfig, providerConfigPublic, writeProviderConfig } from "../../runner/src/config";
|
|
6
5
|
import { deleteCommand, getCommand } from "../commands-db";
|
|
7
|
-
import { effectiveProviderCatalogList } from "../provider-catalog-store";
|
|
6
|
+
import { effectiveProviderCatalogList, removeProviderModelOverride, setProviderModelDisabled, upsertProviderModelOverride } from "../provider-catalog-store";
|
|
8
7
|
import { emitCommand, error, json, parseBody, type Handler } from "./_shared";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
import { getProviderConfigEntry, listProviderConfigEntries, migrateLocalProviderConfigs, providerConfigPublic, setProviderConfig, VALID_PROVIDER_CONFIGS } from "../provider-config-store";
|
|
9
|
+
import { emitConfigChanged } from "../sse";
|
|
10
|
+
import { isRecord, type SpawnProvider } from "agent-relay-sdk";
|
|
11
|
+
import type { ProviderModelCatalogEntry } from "agent-relay-sdk/provider-catalog";
|
|
13
12
|
|
|
14
13
|
export const getProvidersRoute: Handler = () => {
|
|
15
14
|
return json({
|
|
@@ -25,17 +24,72 @@ export const getProvidersRoute: Handler = () => {
|
|
|
25
24
|
});
|
|
26
25
|
};
|
|
27
26
|
|
|
28
|
-
export const getProviderConfigsRoute: Handler = () => {
|
|
27
|
+
export const getProviderConfigsRoute: Handler = (req) => {
|
|
28
|
+
const host = new URL(req.url).searchParams.get("host") ?? undefined;
|
|
29
|
+
migrateLocalProviderConfigs();
|
|
30
|
+
const entries = listProviderConfigEntries(host);
|
|
29
31
|
return json(Object.fromEntries(VALID_PROVIDER_CONFIGS.map((provider) => {
|
|
30
|
-
|
|
31
|
-
return [provider, providerConfigPublic(config)];
|
|
32
|
+
return [provider, providerConfigPublic(entries[provider])];
|
|
32
33
|
})));
|
|
33
34
|
};
|
|
34
35
|
|
|
36
|
+
export const putProviderModelRoute: Handler = async (req, params) => {
|
|
37
|
+
const provider = optionalEnum(params.provider, "provider", VALID_PROVIDER_CONFIGS) as SpawnProvider | undefined;
|
|
38
|
+
if (!provider) return error("provider required");
|
|
39
|
+
const alias = cleanString(params.alias, "alias", { required: true, max: 120 })!;
|
|
40
|
+
const parsed = await parseBody<unknown>(req);
|
|
41
|
+
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
42
|
+
try {
|
|
43
|
+
if (!isRecord(parsed.body)) return error("model body must be an object");
|
|
44
|
+
const entry = cleanProviderModelEntry(alias, parsed.body);
|
|
45
|
+
return json(upsertProviderModelOverride({
|
|
46
|
+
provider,
|
|
47
|
+
alias,
|
|
48
|
+
entry,
|
|
49
|
+
disabled: optionalBoolean(parsed.body.disabled, "disabled"),
|
|
50
|
+
updatedBy: cleanString(parsed.body.updatedBy, "updatedBy", { max: 120 }),
|
|
51
|
+
}));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (e instanceof ValidationError) return error(e.message, 400);
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const patchProviderModelRoute: Handler = async (req, params) => {
|
|
59
|
+
const provider = optionalEnum(params.provider, "provider", VALID_PROVIDER_CONFIGS) as SpawnProvider | undefined;
|
|
60
|
+
if (!provider) return error("provider required");
|
|
61
|
+
const alias = cleanString(params.alias, "alias", { required: true, max: 120 })!;
|
|
62
|
+
const parsed = await parseBody<unknown>(req);
|
|
63
|
+
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
64
|
+
try {
|
|
65
|
+
if (!isRecord(parsed.body)) return error("model patch body must be an object");
|
|
66
|
+
if (parsed.body.disabled === undefined) throw new ValidationError("disabled required");
|
|
67
|
+
return json(setProviderModelDisabled(provider, alias, optionalBoolean(parsed.body.disabled, "disabled") ?? false, cleanString(parsed.body.updatedBy, "updatedBy", { max: 120 })));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (e instanceof ValidationError) return error(e.message, 400);
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const deleteProviderModelRoute: Handler = (_req, params) => {
|
|
75
|
+
const provider = optionalEnum(params.provider, "provider", VALID_PROVIDER_CONFIGS) as SpawnProvider | undefined;
|
|
76
|
+
if (!provider) return error("provider required");
|
|
77
|
+
try {
|
|
78
|
+
const alias = cleanString(params.alias, "alias", { required: true, max: 120 })!;
|
|
79
|
+
return json(removeProviderModelOverride(provider, alias));
|
|
80
|
+
} catch (e) {
|
|
81
|
+
if (e instanceof ValidationError) return error(e.message, 400);
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
35
86
|
export const getProviderConfigRoute: Handler = (_req, params) => {
|
|
36
87
|
const provider = optionalEnum(params.provider, "provider", VALID_PROVIDER_CONFIGS);
|
|
37
88
|
if (!provider) return error("provider required");
|
|
38
|
-
|
|
89
|
+
const host = new URL(_req.url).searchParams.get("host") ?? undefined;
|
|
90
|
+
const includeSecrets = new URL(_req.url).searchParams.get("includeSecrets") === "1";
|
|
91
|
+
migrateLocalProviderConfigs();
|
|
92
|
+
return json(providerConfigPublic(getProviderConfigEntry(provider, host), { maskEnv: !includeSecrets }));
|
|
39
93
|
};
|
|
40
94
|
|
|
41
95
|
export const putProviderConfigRoute: Handler = async (req, params) => {
|
|
@@ -45,23 +99,11 @@ export const putProviderConfigRoute: Handler = async (req, params) => {
|
|
|
45
99
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
46
100
|
try {
|
|
47
101
|
if (!isRecord(parsed.body)) return error("provider config body must be an object");
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
env: cleanEnvRecord(parsed.body.env),
|
|
54
|
-
pluginDirs: cleanStringArray(parsed.body.pluginDirs, "pluginDirs", { itemMax: 80, maxItems: 50 }) ?? defaults.pluginDirs,
|
|
55
|
-
defaultCapabilities: cleanStringArray(parsed.body.defaultCapabilities, "defaultCapabilities", { itemMax: 80, maxItems: 50 }) ?? defaults.defaultCapabilities,
|
|
56
|
-
defaultApprovalMode: cleanString(parsed.body.defaultApprovalMode, "defaultApprovalMode", { max: 80 }) ?? defaults.defaultApprovalMode,
|
|
57
|
-
defaultTags: cleanStringArray(parsed.body.defaultTags, "defaultTags", { itemMax: 80, maxItems: 50 }) ?? defaults.defaultTags,
|
|
58
|
-
chatCaptureMode: (optionalEnum(parsed.body.chatCaptureMode, "chatCaptureMode", ["final", "full"]) ?? defaults.chatCaptureMode) as "final" | "full",
|
|
59
|
-
headless: {
|
|
60
|
-
tmuxPrefix: cleanString(headless.tmuxPrefix, "headless.tmuxPrefix", { max: 120 }) ?? defaults.headless.tmuxPrefix,
|
|
61
|
-
shutdownTimeoutMs: cleanPositiveInt(headless.shutdownTimeoutMs, "headless.shutdownTimeoutMs") ?? defaults.headless.shutdownTimeoutMs,
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
return json(providerConfigPublic(writeProviderConfig(provider, config)));
|
|
102
|
+
const host = new URL(req.url).searchParams.get("host") ?? undefined;
|
|
103
|
+
const updatedBy = cleanString(parsed.body.updatedBy, "updatedBy", { max: 200 });
|
|
104
|
+
const entry = setProviderConfig(provider, parsed.body, { host, updatedBy });
|
|
105
|
+
emitConfigChanged(entry.namespace, entry.key, entry.version);
|
|
106
|
+
return json(providerConfigPublic(entry), entry.version === 1 ? 201 : 200);
|
|
65
107
|
} catch (e) {
|
|
66
108
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
67
109
|
throw e;
|
|
@@ -71,7 +113,8 @@ export const putProviderConfigRoute: Handler = async (req, params) => {
|
|
|
71
113
|
export const postProviderConfigTestRoute: Handler = async (_req, params) => {
|
|
72
114
|
const provider = optionalEnum(params.provider, "provider", VALID_PROVIDER_CONFIGS);
|
|
73
115
|
if (!provider) return error("provider required");
|
|
74
|
-
const
|
|
116
|
+
const host = new URL(_req.url).searchParams.get("host") ?? undefined;
|
|
117
|
+
const config = getProviderConfigEntry(provider, host).value;
|
|
75
118
|
const proc = Bun.spawn(["bash", "-lc", `command -v "$1"`, "bash", config.command], {
|
|
76
119
|
stdout: "pipe",
|
|
77
120
|
stderr: "pipe",
|
|
@@ -82,22 +125,27 @@ export const postProviderConfigTestRoute: Handler = async (_req, params) => {
|
|
|
82
125
|
new Response(proc.stderr).text(),
|
|
83
126
|
]);
|
|
84
127
|
return json({ ok: exitCode === 0, command: config.command, path: stdout.trim(), error: stderr.trim() }, exitCode === 0 ? 200 : 422);
|
|
85
|
-
}
|
|
128
|
+
}
|
|
86
129
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
130
|
+
function cleanProviderModelEntry(alias: string, body: Record<string, unknown>): ProviderModelCatalogEntry {
|
|
131
|
+
const source = isRecord(body.entry) ? body.entry : body;
|
|
132
|
+
const efforts = cleanStringArray(source.efforts, "efforts", { itemMax: 120, maxItems: 50 }) ?? [];
|
|
133
|
+
const defaultEffort = cleanString(source.defaultEffort, "defaultEffort", { max: 120 });
|
|
134
|
+
if (defaultEffort && !efforts.includes(defaultEffort)) throw new ValidationError("defaultEffort must be included in efforts");
|
|
135
|
+
return {
|
|
136
|
+
alias,
|
|
137
|
+
label: cleanString(source.label, "label", { required: true, max: 120 })!,
|
|
138
|
+
providerModel: cleanString(source.providerModel, "providerModel", { required: true, max: 160 })!,
|
|
139
|
+
efforts,
|
|
140
|
+
...(defaultEffort ? { defaultEffort } : {}),
|
|
141
|
+
...(isRecord(source.limits) ? { limits: source.limits as ProviderModelCatalogEntry["limits"] } : {}),
|
|
142
|
+
...(isRecord(source.capabilities) ? { capabilities: source.capabilities as unknown as ProviderModelCatalogEntry["capabilities"] } : {}),
|
|
143
|
+
};
|
|
96
144
|
}
|
|
97
145
|
|
|
98
|
-
function
|
|
99
|
-
if (value === undefined) return undefined;
|
|
100
|
-
if (typeof value !== "
|
|
146
|
+
function optionalBoolean(value: unknown, field: string): boolean | undefined {
|
|
147
|
+
if (value === undefined || value === null) return undefined;
|
|
148
|
+
if (typeof value !== "boolean") throw new ValidationError(`${field} must be a boolean`);
|
|
101
149
|
return value;
|
|
102
150
|
}
|
|
103
151
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Auto-split from routes.ts (#299). Domain: schema-driven settings.
|
|
2
|
+
import { ValidationError } from "../db";
|
|
3
|
+
import { getConfig, setConfig } from "../config-store";
|
|
4
|
+
import { ensureProviderConfigDescriptors, migrateLocalProviderConfigs } from "../provider-config-store";
|
|
5
|
+
import { getSettingDescriptor, listSettingDescriptors, validateSettingValue, PROVIDER_CONFIG_NAMESPACE } from "../settings/registry";
|
|
6
|
+
import { isRequestAuthorizedFor } from "../security";
|
|
7
|
+
import { cleanString } from "../validation";
|
|
8
|
+
import { emitConfigChanged } from "../sse";
|
|
9
|
+
import { error, json, normalizeConfigPathParam, parseBody, type Handler } from "./_shared";
|
|
10
|
+
import { isRecord, type ConfigEntry, type SettingDescriptor, type SettingEntry } from "agent-relay-sdk";
|
|
11
|
+
|
|
12
|
+
type SettingEnvelope = SettingEntry;
|
|
13
|
+
|
|
14
|
+
export const getSettingsRoute: Handler = (req) => {
|
|
15
|
+
migrateLocalProviderConfigs();
|
|
16
|
+
ensureProviderConfigDescriptors();
|
|
17
|
+
return json({ settings: listVisibleSettings(req) });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getSettingsNamespaceRoute: Handler = (req, params) => {
|
|
21
|
+
const namespace = normalizeConfigPathParam(params.namespace, "namespace");
|
|
22
|
+
migrateLocalProviderConfigs();
|
|
23
|
+
ensureProviderConfigDescriptors();
|
|
24
|
+
return json({ settings: listVisibleSettings(req).filter((setting) => setting.namespace === namespace) });
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const getSettingRoute: Handler = (req, params) => {
|
|
28
|
+
const namespace = normalizeConfigPathParam(params.namespace, "namespace");
|
|
29
|
+
const key = normalizeConfigPathParam(params.key, "key");
|
|
30
|
+
migrateLocalProviderConfigs();
|
|
31
|
+
ensureProviderConfigDescriptors(namespace === PROVIDER_CONFIG_NAMESPACE ? [key] : []);
|
|
32
|
+
const descriptor = getSettingDescriptor(namespace, key);
|
|
33
|
+
if (!descriptor) return error("setting not found", 404);
|
|
34
|
+
const readable = canAccessSetting(req, descriptor.scope.read);
|
|
35
|
+
if (!readable) return error("forbidden", 403);
|
|
36
|
+
return json(settingEnvelope(req, descriptor));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const putSettingsDefaultRoute: Handler = (req, params) => putSetting(req, params.namespace, "default");
|
|
40
|
+
export const putSettingRoute: Handler = (req, params) => putSetting(req, params.namespace, params.key);
|
|
41
|
+
|
|
42
|
+
export function getRegisteredSettingEntry(namespace: string, key: string): ConfigEntry | null {
|
|
43
|
+
ensureProviderConfigDescriptors(namespace === PROVIDER_CONFIG_NAMESPACE ? [key] : []);
|
|
44
|
+
const descriptor = getSettingDescriptor(namespace, key);
|
|
45
|
+
return descriptor ? currentSettingEntry(descriptor) : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function setRegisteredSettingValue(namespace: string, key: string, value: unknown, updatedBy?: string): ConfigEntry {
|
|
49
|
+
ensureProviderConfigDescriptors(namespace === PROVIDER_CONFIG_NAMESPACE ? [key] : []);
|
|
50
|
+
const descriptor = getSettingDescriptor(namespace, key);
|
|
51
|
+
if (!descriptor) throw new ValidationError("setting not found");
|
|
52
|
+
const merged = mergeSettingDefaults(descriptor.defaults, value);
|
|
53
|
+
const validation = validateSettingValue(namespace, key, merged);
|
|
54
|
+
if (!validation.valid) throw new ValidationError(validation.errors.join("; "));
|
|
55
|
+
return setConfig(namespace, key, merged, updatedBy);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function putSetting(req: Request, namespaceRaw: string | undefined, keyRaw: string | undefined): Promise<Response> {
|
|
59
|
+
const namespace = normalizeConfigPathParam(namespaceRaw, "namespace");
|
|
60
|
+
const key = normalizeConfigPathParam(keyRaw, "key");
|
|
61
|
+
const parsed = await parseBody<unknown>(req);
|
|
62
|
+
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
63
|
+
if (!isRecord(parsed.body)) return error("JSON object body required", 400);
|
|
64
|
+
if (!Object.prototype.hasOwnProperty.call(parsed.body, "value")) return error("value required", 400);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
migrateLocalProviderConfigs();
|
|
68
|
+
ensureProviderConfigDescriptors(namespace === PROVIDER_CONFIG_NAMESPACE ? [key] : []);
|
|
69
|
+
const descriptor = getSettingDescriptor(namespace, key);
|
|
70
|
+
if (!descriptor) return error("setting not found", 404);
|
|
71
|
+
if (descriptor.tier !== "server-runtime") return error("setting is read-only", 403);
|
|
72
|
+
if (!canAccessSetting(req, descriptor.scope.write)) return error("forbidden", 403);
|
|
73
|
+
const updatedBy = cleanString(parsed.body.updatedBy, "updatedBy", { max: 200 });
|
|
74
|
+
const validation = validateSettingValue(namespace, key, parsed.body.value);
|
|
75
|
+
if (!validation.valid) return json({ error: "invalid setting value", details: validation.errors }, 400);
|
|
76
|
+
const entry = setConfig(namespace, key, parsed.body.value, updatedBy);
|
|
77
|
+
emitConfigChanged(entry.namespace, entry.key, entry.version);
|
|
78
|
+
return json(settingEnvelope(req, descriptor, entry), entry.version === 1 ? 201 : 200);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
if (e instanceof ValidationError) return error(e.message, 400);
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function listVisibleSettings(req: Request): SettingEnvelope[] {
|
|
86
|
+
return listSettingDescriptors()
|
|
87
|
+
.map((descriptor) => settingEnvelope(req, descriptor))
|
|
88
|
+
.filter((setting) => setting.readable);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function settingEnvelope(req: Request, descriptor: SettingDescriptor, entry = currentSettingEntry(descriptor)): SettingEnvelope {
|
|
92
|
+
return {
|
|
93
|
+
...descriptor,
|
|
94
|
+
value: entry.value,
|
|
95
|
+
version: entry.version,
|
|
96
|
+
updatedAt: entry.updatedAt,
|
|
97
|
+
updatedBy: entry.updatedBy,
|
|
98
|
+
readable: canAccessSetting(req, descriptor.scope.read),
|
|
99
|
+
writable: descriptor.tier === "server-runtime" && canAccessSetting(req, descriptor.scope.write),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function currentSettingEntry(descriptor: SettingDescriptor): ConfigEntry {
|
|
104
|
+
const entry = getConfig(descriptor.namespace, descriptor.key);
|
|
105
|
+
if (entry) return entry;
|
|
106
|
+
return {
|
|
107
|
+
namespace: descriptor.namespace,
|
|
108
|
+
key: descriptor.key,
|
|
109
|
+
value: descriptor.defaults,
|
|
110
|
+
version: 0,
|
|
111
|
+
updatedAt: "default",
|
|
112
|
+
updatedBy: "system",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function canAccessSetting(req: Request, scope: string): boolean {
|
|
117
|
+
return isRequestAuthorizedFor(req, { scope });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function mergeSettingDefaults(defaults: unknown, value: unknown): unknown {
|
|
121
|
+
if (!isRecord(defaults) || !isRecord(value)) return value;
|
|
122
|
+
const merged: Record<string, unknown> = { ...defaults };
|
|
123
|
+
for (const [key, item] of Object.entries(value)) {
|
|
124
|
+
merged[key] = mergeSettingDefaults(defaults[key], item);
|
|
125
|
+
}
|
|
126
|
+
return merged;
|
|
127
|
+
}
|
package/src/routes/steward.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { ValidationError } from "../db";
|
|
|
3
3
|
import { cleanString } from "../validation";
|
|
4
4
|
import { emitConfigChanged } from "../sse";
|
|
5
5
|
import { error, json, parseBody, type Handler } from "./_shared";
|
|
6
|
-
import {
|
|
6
|
+
import { getRegisteredSettingEntry, setRegisteredSettingValue } from "./settings";
|
|
7
7
|
import { isRecord } from "agent-relay-sdk";
|
|
8
8
|
|
|
9
|
-
export const getStewardConfigRoute: Handler = () => json(
|
|
9
|
+
export const getStewardConfigRoute: Handler = () => json(getRegisteredSettingEntry("steward", "default"));
|
|
10
10
|
|
|
11
11
|
export const putStewardConfigRoute: Handler = async (req) => {
|
|
12
12
|
const parsed = await parseBody<unknown>(req);
|
|
@@ -16,7 +16,7 @@ export const putStewardConfigRoute: Handler = async (req) => {
|
|
|
16
16
|
? parsed.body.value
|
|
17
17
|
: parsed.body;
|
|
18
18
|
const updatedBy = isRecord(parsed.body) ? cleanString(parsed.body.updatedBy, "updatedBy", { max: 200 }) : undefined;
|
|
19
|
-
const entry =
|
|
19
|
+
const entry = setRegisteredSettingValue("steward", "default", value, updatedBy);
|
|
20
20
|
emitConfigChanged(entry.namespace, entry.key, entry.version);
|
|
21
21
|
return json(entry, entry.version === 1 ? 201 : 200);
|
|
22
22
|
} catch (e) {
|
|
@@ -25,7 +25,7 @@ export const putStewardConfigRoute: Handler = async (req) => {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
export const getWorkspaceConfigRoute: Handler = () => json(
|
|
28
|
+
export const getWorkspaceConfigRoute: Handler = () => json(getRegisteredSettingEntry("workspace", "default"));
|
|
29
29
|
|
|
30
30
|
export const putWorkspaceConfigRoute: Handler = async (req) => {
|
|
31
31
|
const parsed = await parseBody<unknown>(req);
|
|
@@ -35,7 +35,7 @@ export const putWorkspaceConfigRoute: Handler = async (req) => {
|
|
|
35
35
|
? parsed.body.value
|
|
36
36
|
: parsed.body;
|
|
37
37
|
const updatedBy = isRecord(parsed.body) ? cleanString(parsed.body.updatedBy, "updatedBy", { max: 200 }) : undefined;
|
|
38
|
-
const entry =
|
|
38
|
+
const entry = setRegisteredSettingValue("workspace", "default", value, updatedBy);
|
|
39
39
|
emitConfigChanged(entry.namespace, entry.key, entry.version);
|
|
40
40
|
return json(entry, entry.version === 1 ? 201 : 200);
|
|
41
41
|
} catch (e) {
|
package/src/routes/tokens.ts
CHANGED
|
@@ -4,12 +4,43 @@ import { ValidationError, getOrchestrator, upsertIntegrationRegistry } from "../
|
|
|
4
4
|
import { auditEvent, authAuditMetadata, authorizeRoute, error, isRootCredentialRequest, json, parseBody, type Handler } from "./_shared";
|
|
5
5
|
import { cleanString, cleanStringArray, cleanTokenConstraints, optionalEnum } from "../validation";
|
|
6
6
|
import { createToken, deleteTokenProfile, getToken, getTokenProfile, listTokenProfiles, listTokens, renewToken, revokeToken, updateTokenProfile, upsertTokenProfile } from "../token-db";
|
|
7
|
-
import { getComponentAuth } from "../security";
|
|
7
|
+
import { getComponentAuth, getIntegrationAuth, isAuthorized } from "../security";
|
|
8
8
|
import { issueIntegrationRuntimeToken, issueInteractiveRunnerRuntimeToken, issueMcpRuntimeToken, reissueRunnerRuntimeToken } from "../runtime-tokens";
|
|
9
9
|
import { type SpawnProvider } from "../types";
|
|
10
10
|
|
|
11
11
|
export const getTokens: Handler = () => json(listTokens());
|
|
12
12
|
|
|
13
|
+
export const getTokenMe: Handler = (req) => {
|
|
14
|
+
const component = getComponentAuth(req);
|
|
15
|
+
if (component) {
|
|
16
|
+
return json({
|
|
17
|
+
actor: component.sub,
|
|
18
|
+
kind: "component",
|
|
19
|
+
role: component.role,
|
|
20
|
+
scopes: component.scope,
|
|
21
|
+
constraints: component.constraints,
|
|
22
|
+
jti: component.jti,
|
|
23
|
+
exp: component.exp,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const integration = getIntegrationAuth(req);
|
|
28
|
+
if (integration) {
|
|
29
|
+
return json({
|
|
30
|
+
actor: integration.name,
|
|
31
|
+
kind: "integration",
|
|
32
|
+
scopes: integration.scopes,
|
|
33
|
+
targets: integration.targets,
|
|
34
|
+
channels: integration.channels,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isAuthorized(req)) {
|
|
39
|
+
return json({ actor: "server", kind: "server", scopes: ["*"] });
|
|
40
|
+
}
|
|
41
|
+
return error("unauthorized", 401);
|
|
42
|
+
};
|
|
43
|
+
|
|
13
44
|
function cleanTtlSeconds(value: unknown): number | undefined {
|
|
14
45
|
if (value === undefined || value === null || value === "") return undefined;
|
|
15
46
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|